Règles générales pour simplifier les déclarations SQL

je suis à la recherche de quelques" règles d'inférence " (semblables à des règles d'opération ou des règles de logique) que je peux utiliser pour réduire une requête SQL dans la complexité ou la taille. Existe-t-il quelque chose comme ça? Tous les papiers, tous les outils? Des équivalences que vous avez trouvées vous-même? C'est en quelque sorte similaire à l'optimisation des requêtes, mais pas en termes de performance.

pour l'exprimer autrement: avoir une requête (complexe) avec jointures, sous-sélections, UNIONs est-il possible (ou non) de la réduire à une déclaration SQL équivalente plus simple, qui produit le même résultat, en utilisant certaines règles de transformation?

donc, je suis à la recherche de transformations équivalentes de déclarations SQL comme le fait que la plupart des sous-sélects peuvent être réécrits comme une jointure.

64
demandé sur Roman C 2009-07-01 18:14:46

8 réponses

pour l'exprimer différemment: avoir une requête (complexe) avec jointures, sous-sélections, UNIONs est-il possible (ou non) de la réduire à une déclaration SQL plus simple et équivalente, qui produit le même résultat, en utilisant certaines règles de transformation?

C'est exactement ce que font les optimiseurs pour gagner leur vie (ce n'est pas que je dis qu'ils le font toujours bien).

depuis SQL est un langage basé sur des ensembles, Il ya généralement plus qu'un moyen de transformer une requête à l'autre.

Comme cette requête:

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

peut être transformé en ceci:

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

ou ceci:

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

, qui semblent plus moches mais peuvent donner de meilleurs plans d'exécution.

une des choses les plus communes à faire est de remplacer cette requête:

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

avec celui-ci:

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

dans certains RDBMS 's (comme PostgreSQL ), DISTINCT et GROUP BY utiliser les différents plans d'exécution, il est donc parfois préférable de remplacer l'un par l'autre:

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

Dans PostgreSQL , DISTINCT trie et GROUP BY les tables de hachage.

MySQL manque FULL OUTER JOIN , il peut donc être réécrit comme suit:

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

, mais voir cet article dans mon blog sur la façon de le faire plus efficacement dans MySQL :

Cette requête hiérarchique dans 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

peut être transformé en ceci:

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

, ce dernier étant plus performant.

voir cet article sur mon blog pour les détails du plan d'exécution:

pour trouver toutes les plages qui chevauchent la plage donnée, vous pouvez utiliser la requête suivante:

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

, mais dans SQL Server cette requête plus complexe donne les mêmes résultats plus rapidement:

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

, et croyez-le ou pas, j'ai un article dans mon blog sur ce trop:

SQL Server manque également d'un moyen efficace de faire des agrégats cumulatifs, de sorte que cette question:

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

peut être réécrit plus efficacement en utilisant, Seigneur aide-moi, curseurs (vous m'avez bien entendu: cursors , more efficiently et SQL Server en une phrase).

voir cet article dans mon blog sur la façon de le faire:

il existe un certain type de requête couramment rencontré dans les applications financières qui recherche le taux effectif d'une monnaie, comme celle-ci dans 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
        )

cette requête peut être fortement réécrite pour utiliser une condition d'égalité qui permet un HASH JOIN au lieu 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

en dépit d'être volumineux comme un enfer, la dernière requête est 6 fois plus rapide.

l'idée principale ici est de remplacer <= par = , ce qui nécessite la construction d'une table de calendrier en mémoire. à JOIN avec.

60
répondu Quassnoi 2009-07-01 15:21:47

voici quelques exemples de travail avec Oracle 8 & 9 (bien sûr, parfois faire le contraire pourrait rendre la requête plus simple ou plus rapide):

les parenthèses peuvent être supprimées si elles ne sont pas utilisées pour outrepasser la priorité de l'opérateur. Un exemple simple est quand tous les opérateurs booléens dans votre clause where sont les mêmes: where ((a or b) or c) est équivalent à where a or b or c .

une sous-requête peut souvent (si pas toujours) être fusionnée avec la requête principale pour simplifier. D'après mon expérience, cela améliore souvent considérablement les performances:

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

est équivalent à

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
;

en utilisant ANSI joins sépare beaucoup de logique de "singe de code" des parties vraiment intéressantes de la clause où: la requête précédente est équivalente à

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 vous voulez vérifier l'existence d'une rangée, n'utilisez pas count(*) , utilisez plutôt rownum = 1 ou mettez la requête dans une clause where exists pour aller chercher une seule rangée au lieu de toutes.

9
répondu l0b0 2009-07-01 15:38:33
  • je suppose que l'évidence est de chercher des curseurs qui peuvent être remplacés par une opération basée sur SQL 'Set'.
  • ensuite sur ma liste, est de chercher toutes les sous-requêtes corrélées qui peuvent être réécrites comme une requête non-corrélée
  • dans les procédures stockées de longue durée, décomposer des instructions SQL séparées dans leurs propres procédures stockées. De cette façon, ils obtiendront leur propre plan de requête en cache.
  • Rechercher des transactions qui peuvent avoir leur portée réduite. Je trouve régulièrement à l'intérieur d'une transaction des déclarations qui peuvent être en toute sécurité à l'extérieur.
  • les sous-sélections peuvent souvent être réécrites comme des jointures simples (les optimiseurs modernes sont bons pour repérer les simples)

comme @Quassnoi l'a mentionné, L'Optimiseur fait souvent du bon travail. Une façon de l'aider est de s'assurer que les index et les statistiques sont à jour, et que des index appropriés existent pour votre charge de travail d'interrogation.

6
répondu Mitch Wheat 2009-07-01 14:25:23

j'aime remplacer toute sorte de sous-select par join query.

celui-ci est évident:

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

par

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

et celui-ci est sous-estimé :

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

par

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

qu'Elle pourrait aider les SGBD pour choisir le bon plan d'exécution dans une grande demande.

5
répondu Cyril Gandon 2009-07-01 14:33:44

j'aime tout le monde sur une équipe de suivre un ensemble de normes afin de rendre le code lisible et maintenable, compréhensible, machine, etc.. :)

  • tout le monde utilise le même alias
  • pas de curseurs. pas de boucles
  • pourquoi pensez vous quand vous ne pouvez EXISTE
  • tiret
  • cohérence du style de codage

il y a d'autres choses ici Quelles sont certaines de vos normes de base de données les plus utiles?

5
répondu Raj More 2017-05-23 12:26:07

étant donné la nature de SQL, vous devez absolument être conscient des implications de performance de tout remaniement. Refactoring des Applications SQL est une bonne ressource sur le refactoring avec un lourd accent sur la performance (voir le Chapitre 5).

4
répondu Jim Ferrans 2009-07-01 15:58:25

bien que la simplification ne soit peut-être pas synonyme d'optimisation, la simplification peut être importante dans l'écriture du code SQL lisible, ce qui est à son tour critique pour être en mesure de vérifier votre code SQL pour l'exactitude conceptuelle (pas l'exactitude syntaxique, que votre environnement de développement devrait vérifier pour vous). Il me semble que dans un monde idéal, nous écririons le code SQL le plus simple et lisible, puis l'optimiseur réécrirait ce code SQL sous n'importe quelle forme (peut-être plus verbeux) exécuter le plus rapide.

j'ai trouvé qu'il est très utile de penser aux énoncés SQL comme basés sur une logique établie, surtout si j'ai besoin de combiner des clauses où ou de comprendre une négation complexe d'une clause où. J'utilise les lois de l'algèbre booléenne dans ce cas.

les plus importantes pour simplifier une clause où sont probablement les lois de DeMorgan (notez que · * " Est " et " et " + "est" ou"):

  • PAS (x · y) = x + PAS y
  • PAS (x + y) = x · de ne PAS y

cela se traduit en SQL par:

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

ces lois peuvent être très utiles pour simplifier où les clauses avec beaucoup de "emboîtés AND et OR parties.

il est également utile de se rappeler que la mention field1 IN (value1, value2, ...) est équivalente à field1 = value1 OR field1 = value2 OR ... . Cela vous permet de nier le IN () une de deux façons:

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

Une sous-requête peut être pensé de cette façon aussi. Par exemple, ceci a annulé où clause:

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

peut être réécrit comme:

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

ces lois ne vous disent pas comment transformer une requête SQL en utilisant un sous-jeu en utilisant une jointure, mais la logique booléenne peut vous aider à comprendre les types de jointure et ce que votre requête devrait être de retour. Par exemple, avec les tableaux A et B , un INNER JOIN est comme A AND B , un LEFT OUTER JOIN est comme (A AND NOT B) OR (A AND B) qui simplifie à A OR (A AND B) , et un FULL OUTER JOIN est A OR (A AND B) OR B qui simplifie à A OR B .

3
répondu Nick Seigal 2015-04-23 04:31:32

Mon approche est d'apprendre la théorie relationnelle en général et de l'algèbre relationnelle en particulier. Apprenez ensuite à repérer les constructions utilisées dans SQL pour mettre en œuvre les opérateurs à partir de l'algèbre relationnelle (par exemple, Universal quantification A. K. A. division) et le calcul (par exemple quantification existentielle). Le gotcha est que SQL a des caractéristiques qui ne se trouvent pas dans le modèle relationnel par exemple nulls, qui sont probablement mieux remaniés loin de toute façon. Lecture recommandée: SQL et la Théorie Relationnelle: Comment Ecrire le code SQL exact par C. J. Date .

dans cet esprit, Je ne suis pas convaincu que" le fait que la plupart des sous-sélections puissent être réécrites en jointure " représente une simplification.

prenez cette requête par exemple:

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

réécrire en utilisant la jointure

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

le jointure est plus verbeux!

alternativement, reconnaître la construction est la mise en œuvre d'un antijoin sur la projection de c p.ex. pseudo algrbra

T1 { c } antijoin T2 { c }

Simplification à l'aide d'opérateurs relationnels:

SELECT c FROM T1 EXCEPT SELECT c FROM T2;
0
répondu onedaywhen 2012-03-13 16:33:42