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.
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.
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.
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.
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.
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?
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).
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
.
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;
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