Reglas generales para simplificar instrucciones SQL


Estoy buscando algunas "reglas de inferencia" (similares a establecer reglas de operación o reglas de lógica) que puedo usar para reducir una consulta SQL en complejidad o tamaño. ¿Existe algo así? ¿Algún papel, alguna herramienta? ¿Alguna equivalencia que hayas encontrado por tu cuenta? Es de alguna manera similar a la optimización de consultas, pero no en términos de rendimiento.

Para que sea diferente: Tener una consulta (compleja) con uniones, subelecciones, uniones es posible (o no) reducirla a un SQL equivalente más simple declaración, que está produciendo el mismo resultado, mediante el uso de algunas reglas de transformación?

Por lo tanto, estoy buscando transformaciones equivalentes de sentencias SQL como el hecho de que la mayoría de las subselecciones se pueden reescribir como una COMBINACIÓN.

Author: Roman C, 2009-07-01

8 answers

Para expresarlo de manera diferente: Tener una consulta (compleja) con uniones, subelecciones, uniones ¿es posible (o no) reducirla a una sentencia SQL equivalente más simple, que produce el mismo resultado, mediante el uso de algunas reglas de transformación?

Eso es exactamente lo que los optimizadores hacen para ganarse la vida (no es que esté diciendo que siempre lo hacen bien).

Dado que SQL es un lenguaje basado en conjuntos, generalmente hay más de una forma de transformar una consulta a otra.

Como esta consulta:

SELECT  *
FROM    mytable
WHERE   col1 > @value1 OR col2 < @value2

Se puede transformar en esto: {[43]]}

SELECT  *
FROM    mytable
WHERE   col1 > @value1
UNION
SELECT  *
FROM    mytable
WHERE   col2 < @value2

O esto:

SELECT  mo.*
FROM    (
        SELECT  id
        FROM    mytable
        WHERE   col1 > @value1
        UNION
        SELECT  id
        FROM    mytable
        WHERE   col2 < @value2
        ) mi
JOIN    mytable mo
ON      mo.id = mi.id

, que parecen más feos pero pueden producir mejores planes de ejecución.

Una de las cosas más comunes es reemplazar esta consulta:

SELECT  *
FROM    mytable
WHERE   col IN
        (
        SELECT  othercol
        FROM    othertable
        )

Con este: {[43]]}

SELECT  *
FROM    mytable mo
WHERE   EXISTS
        (
        SELECT  NULL
        FROM    othertable o
        WHERE   o.othercol = mo.col
        )

En algunos RDBMS's (como PostgreSQL), DISTINCT y GROUP BY utilizar los diferentes planes de ejecución, por lo que a veces es mejor sustituir uno por el otro:

SELECT  mo.grouper,
        (
        SELECT  SUM(col)
        FROM    mytable mi
        WHERE   mi.grouper = mo.grouper
        )
FROM    (
        SELECT  DISTINCT grouper
        FROM    mytable
        ) mo

Vs.

SELECT  mo.grouper, SUM(col)
FROM    mytable
GROUP BY
        mo.grouper

En PostgreSQL, DISTINCT ordena y GROUP BY hashes.

MySQL lacks FULL OUTER JOIN, por lo que se puede reescribir como folloing:

SELECT  t1.col1, t2.col2
FROM    table1 t1
LEFT OUTER JOIN
        table2 t2
ON      t1.id = t2.id

Vs.

SELECT  t1.col1, t2.col2
FROM    table1 t1
LEFT JOIN
        table2 t2
ON      t1.id = t2.id
UNION ALL
SELECT  NULL, t2.col2
FROM    table1 t1
RIGHT JOIN
        table2 t2
ON      t1.id = t2.id
WHERE   t1.id IS NULL

, pero ver este artículo en mi blog sobre cómo hacer esto de manera más eficiente en MySQL:

Esta consulta jerárquica en Oracle:

SELECT  DISTINCT(animal_id) AS animal_id
FROM    animal
START WITH
        animal_id = :id
CONNECT BY
        PRIOR animal_id IN (father, mother)
ORDER BY
        animal_id

Se puede transformar a esto: {[43]]}

SELECT  DISTINCT(animal_id) AS animal_id
FROM    (
        SELECT  0 AS gender, animal_id, father AS parent
        FROM    animal
        UNION ALL
        SELECT  1, animal_id, mother
        FROM    animal
        )
START WITH
        animal_id = :id
CONNECT BY
        parent = PRIOR animal_id
ORDER BY
        animal_id

, siendo esta última más performante.

Ver este artículo en mi blog para los detalles del plan de ejecución:

Para encontrar todos los rangos que se superponen al rango dado, puede usar la siguiente consulta:

SELECT  *
FROM    ranges
WHERE   end_date >= @start
        AND start_date <= @end

, pero en SQL Server esta consulta más compleja produce los mismos resultados más rápido:

SELECT  *
FROM    ranges
WHERE   (start_date > @start AND start_date <= @end)
        OR (@start BETWEEN start_date AND end_date)

, y lo creas o no, tengo un artículo en mi blog sobre esto también:

SQL Server también carece de una forma eficiente de hacer agregados acumulativos, por lo que esta consulta:

SELECT  mi.id, SUM(mo.value) AS running_sum
FROM    mytable mi
JOIN    mytable mo
ON      mo.id <= mi.id
GROUP BY
        mi.id

Puede ser reescrito de manera más eficiente usando, Señor ayúdame, cursores (me escuchaste bien: cursors, more efficiently y SQL Server en una oración).

Ver este artículo en mi blog sobre cómo hacerlo:

Hay un cierto tipo de consulta comúnmente cumplida en las aplicaciones financieras que busca la tasa efectiva para una moneda, como este en Oracle:

SELECT  TO_CHAR(SUM(xac_amount * rte_rate), 'FM999G999G999G999G999G999D999999')
FROM    t_transaction x
JOIN    t_rate r
ON      (rte_currency, rte_date) IN
        (
        SELECT  xac_currency, MAX(rte_date)
        FROM    t_rate
        WHERE   rte_currency = xac_currency
                AND rte_date <= xac_date
        )

Esta consulta se puede reescribir en gran medida para usar una condición de igualdad que permite un HASH JOIN en lugar de NESTED LOOPS:

WITH v_rate AS
        (
        SELECT  cur_id AS eff_currency, dte_date AS eff_date, rte_rate AS eff_rate
        FROM    (
                SELECT  cur_id, dte_date,
                        (
                        SELECT  MAX(rte_date)
                        FROM    t_rate ri
                        WHERE   rte_currency = cur_id
                                AND rte_date <= dte_date
                        ) AS rte_effdate
                FROM    (
                        SELECT  (
                                SELECT  MAX(rte_date)
                                FROM    t_rate
                                ) - level + 1 AS dte_date
                        FROM    dual
                        CONNECT BY
                                level <=
                                (
                                SELECT  MAX(rte_date) - MIN(rte_date)
                                FROM    t_rate
                                )
                        ) v_date,
                        (
                        SELECT  1 AS cur_id
                        FROM    dual
                        UNION ALL
                        SELECT  2 AS cur_id
                        FROM    dual
                        ) v_currency
                ) v_eff
        LEFT JOIN
                t_rate
        ON      rte_currency = cur_id
                AND rte_date = rte_effdate
        )
SELECT  TO_CHAR(SUM(xac_amount * eff_rate), 'FM999G999G999G999G999G999D999999')
FROM    (
        SELECT  xac_currency, TRUNC(xac_date) AS xac_date, SUM(xac_amount) AS xac_amount, COUNT(*) AS cnt
        FROM    t_transaction x
        GROUP BY
                xac_currency, TRUNC(xac_date)
        )
JOIN    v_rate
ON      eff_currency = xac_currency
        AND eff_date = xac_date

A pesar de ser voluminoso como un infierno, esta última consulta es 6 veces más rápida.

La idea principal aquí es reemplazar <= por =, lo que requiere construir una tabla de calendario en memoria. a JOIN con.

 60
Author: Quassnoi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-07-01 15:21:47

Aquí hay algunos de trabajar con Oracle 8 & 9 (por supuesto, a veces hacer lo contrario podría hacer que la consulta sea más simple o más rápida):

Los paréntesis se pueden eliminar si no se utilizan para anular la precedencia del operador. Un ejemplo simple es cuando todos los operadores booleanos en su cláusula where son los mismos: where ((a or b) or c) es equivalente a where a or b or c.

Una sub-consulta puede a menudo (si no siempre) ser fusionada con la consulta principal para simplificarla. En mi experiencia, esto a menudo mejora el rendimiento considerablemente:

select foo.a,
       bar.a
  from foomatic  foo,
       bartastic bar
 where foo.id = bar.id and
       bar.id = (
         select ban.id
           from bantabulous ban
          where ban.bandana = 42
       )
;

Es equivalente a

select foo.a,
       bar.a
  from foomatic    foo,
       bartastic   bar,
       bantabulous ban
 where foo.id = bar.id and
       bar.id = ban.id and
       ban.bandana = 42
;

Usando ANSI joins separa una gran cantidad de lógica "code monkey" de las partes realmente interesantes de la cláusula where: La consulta anterior es equivalente a

select foo.a,
       bar.a
  from foomatic    foo
  join bartastic   bar on bar.id = foo.id
  join bantabulous ban on ban.id = bar.id
 where ban.bandana = 42
;

Si desea verificar la existencia de una fila, no use count(*), en su lugar use rownum = 1 o coloque la consulta en una cláusula where exists para obtener solo una fila en lugar de todas.

 9
Author: l0b0,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-07-01 15:38:33
  • Supongo que la obvia es buscar cualquier Cursor que pueda ser reemplazado con una operación basada en SQL 'Set'.
  • Lo siguiente en mi lista es buscar cualquier sub-consulta correlacionada que pueda ser reescrita como una consulta no correlacionada
  • En procedimientos almacenados largos, divida instrucciones SQL separadas en sus propios procedimientos almacenados. De esa manera, obtendrán su propio plan de consultas en caché.
  • Busque transacciones que puedan tener su alcance acortado. Regularmente encuentro declaraciones dentro de un transacción que puede estar fuera de forma segura.
  • Las subeleccionas a menudo se pueden reescribir como uniones directas (los optimizadores modernos son buenos para detectar las simples)

Como @Quassnoi mencionó, el Optimizador a menudo hace un buen trabajo. Una forma de ayudarlo es asegurarse de que los índices y las estadísticas estén actualizados y que existan índices adecuados para su carga de trabajo de consulta.

 6
Author: Mitch Wheat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-07-01 14:25:23

Me gusta reemplazar todo tipo de subselección por consulta de unión.

Este es obvio:

SELECT  *
FROM    mytable mo
WHERE   EXISTS
        (
          SELECT  *
          FROM    othertable o
          WHERE   o.othercol = mo.col
        )

Por

SELECT  mo.*
FROM    mytable mo inner join othertable o on o.othercol = mo.col

Y este está bajo estimación:

SELECT  *
FROM    mytable mo
WHERE   NOT EXISTS
        (
          SELECT  *
          FROM    othertable o
          WHERE   o.othercol = mo.col
        )

Por

SELECT  mo.*
FROM    mytable mo left outer join othertable o on o.othercol = mo.col
WHERE   o.othercol is null

Podría ayudar al DBMS a elegir el buen plan de ejecución en una solicitud grande.

 5
Author: Cyril Gandon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-07-01 14:33:44

Me gusta que todos en un equipo sigan un conjunto de estándares para hacer que el código sea legible, mantenible, comprensible, lavable, etc.. :)

  • todos usan el mismo alias
  • no hay cursores. sin bucles
  • ¿por qué incluso pensar EN cuando se puede EXISTE
  • GUIÓN
  • Coherencia en el estilo de codificación

Hay algunas cosas más aquí ¿Cuáles son algunos de sus estándares de base de datos más útiles?

 5
Author: Raj More,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:26:07

Dada la naturaleza de SQL, es absolutamente necesario ser consciente de las implicaciones de rendimiento de cualquier refactorización. Refactorización SQL Applications es un buen recurso en refactorización con un fuerte énfasis en el rendimiento (ver Capítulo 5).

 4
Author: Jim Ferrans,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-07-01 15:58:25

Aunque la simplificación puede no ser igual a la optimización, la simplificación puede ser importante en la escritura de código SQL legible, que a su vez es fundamental para poder comprobar su código SQL para la corrección conceptual (no la corrección sintáctica, que su entorno de desarrollo debe comprobar por usted). Me parece que en un mundo ideal, escribiríamos el código SQL más simple y legible y luego el optimizador reescribiría ese código SQL para que estuviera en cualquier forma (quizás más detallada) ejecutaría el el más rápido.

He encontrado que pensar en las sentencias SQL como basadas en la lógica de conjunto es muy útil, particularmente si necesito combinar cláusulas where o averiguar una negación compleja de una cláusula where. Utilizo las leyes del álgebra booleana en este caso.

Las más importantes para simplificar una cláusula where son probablemente las Leyes de DeMorgan (tenga en cuenta que "·" es "Y" y "+" es "O"):

  • NO (x * y) = NO x + NO y
  • NO (x + y) = NO x * NO y

Esto se traduce en SQL a:

NOT (expr1 AND expr2) -> NOT expr1 OR NOT expr2
NOT (expr1 OR expr2) -> NOT expr1 AND NOT expr2

Estas leyes pueden ser muy útiles para simplificar las cláusulas where con muchas partes anidadas AND y OR.

También es útil recordar que la declaración field1 IN (value1, value2, ...) es equivalente a field1 = value1 OR field1 = value2 OR ... . Esto le permite negar el IN () una de dos maneras:

NOT field1 IN (value1, value2)  -- for longer lists
NOT field1 = value1 AND NOT field1 = value2  -- for shorter lists

Una sub-consulta puede ser pensada de esta manera también. Por ejemplo, esto negaba la cláusula where:

NOT (table1.field1 = value1 AND EXISTS (SELECT * FROM table2 WHERE table1.field1 = table2.field2))

Se puede reescribir como:

NOT table1.field1 = value1 OR NOT EXISTS (SELECT * FROM table2 WHERE table1.field1 = table2.field2))

Estas leyes hacen no le dice cómo transformar una consulta SQL usando una subconsulta en una usando una combinación, pero la lógica booleana puede ayudarlo a comprender los tipos de combinación y lo que debe devolver su consulta. Por ejemplo, con mesas A y B un INNER JOIN es como A AND B, a LEFT OUTER JOIN es como (A AND NOT B) OR (A AND B) que se simplifica a A OR (A AND B), y FULL OUTER JOIN es A OR (A AND B) OR B que se simplifica a A OR B.

 3
Author: Nick Seigal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-04-23 04:31:32

Mi enfoque es aprender teoría relacional en general y álgebra relacional en particular. Luego aprenda a detectar las construcciones utilizadas en SQL para implementar operadores del álgebra relacional (por ejemplo, cuantificación universal, también conocida como división) y el cálculo (por ejemplo, cuantificación existencial). El truco es que SQL tiene características que no se encuentran en el modelo relacional, por ejemplo, nulls, que probablemente sea mejor refactorizar de todos modos. Lectura recomendada: SQL y Teoría Relacional: Cómo Escribir con Precisión Código SQL Por C. J. Date .

En este sentido, no estoy convencido de que "el hecho de que la mayoría de las subselecciones se puedan reescribir como una COMBINACIÓN" represente una simplificación.

Tome esta consulta por ejemplo:

SELECT c 
  FROM T1 
 WHERE c NOT IN ( SELECT c FROM T2 );

Reescribir usando JOIN

SELECT DISTINCT T1.c 
  FROM T1 NATURAL LEFT OUTER JOIN T2 
 WHERE T2.c IS NULL;

La combinación es más detallada!

Alternativamente, reconozca que el constructo está implementando una antijoin en la proyección de c por ejemplo, pseudo algrbra

T1 { c } antijoin T2 { c }

Simplificación usando operadores relacionales:

SELECT c FROM T1 EXCEPT SELECT c FROM T2;
 0
Author: onedaywhen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-03-13 16:33:42