web-dev-qa-db-fra.com

Sélection de plusieurs lignes dans une même requête avec plusieurs conditions

Je peux sélectionner plusieurs lignes avec une condition en utilisant quelque chose comme:

SELECT `Col`, `Col2` FROM `Table` WHERE `Col3` IN (?, ?, ?, ?, ?);
# This selects 5 rows

Comment cela peut-il être fait s'il existe plusieurs conditions (tous les entiers sont égaux aux opérations)?

Il y a trois conditions que la requête doit vérifier et toutes les trois constituent la clé primaire composite.

Une seule requête sélectionnera de 10 à 100 lignes (bien que la plupart du temps ce ne sera que 10) - elle doit être rapide en termes de performances. C'est pourquoi l'utilisation de plusieurs requêtes n'est pas une bonne idée.

Le CREATE TABLE l'instruction est:

CREATE TABLE `Table Name` (
  `X` smallint(6) unsigned NOT NULL,
  `Y` smallint(6) unsigned NOT NULL,
  `Z` smallint(6) unsigned NOT NULL,
  `Data` varchar(2048) NOT NULL DEFAULT '',
  PRIMARY KEY (`X`,`Y`,`Z`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Avec plusieurs requêtes, cela pourrait être fait comme ceci:

SELECT `One`, `Two` FROM `Table` WHERE `X` = ? AND `Y` = ? AND `Z` = ?;
SELECT `One`, `Two` FROM `Table` WHERE `X` = ? AND `Y` = ? AND `Z` = ?;
SELECT `One`, `Two` FROM `Table` WHERE `X` = ? AND `Y` = ? AND `Z` = ?;
# This selects 3 rows but I don't want to make 3 calls to the database server for that
7
J. Doe

Vous avez 2 options syntaxiques de base pour ce faire dans une seule requête et 2 options pour envoyer les valeurs dans la requête ou les charger d'abord dans une table:

  • le AND/OR normal (les parenthèses sont redondantes ici mais il est bon de les utiliser avec OR, juste au cas où le WHERE deviendrait plus compliqué):

    WHERE (  ( x = ? AND y = ? AND z = ? )
          OR ( x = ? AND y = ? AND z = ? )
          ... 
          OR ( x = ? AND y = ? AND z = ? )
          )
    
  • compact IN avec "constructeur de lignes":

    WHERE (x,y,z) IN ((?,?,?), (?,?,?), ...)
    
  • charger les triplets dans une table (temporaire) et JOIN:

    CREATE (TEMPORARY) TABLE tmp_table 
    --- ;
    
    INSERT INTO tmp_table (x,y,z)
    VALUES (?,?,?), (?,?,?), ... ;
    
    SELECT t.*
    FROM my_table AS t
      JOIN tmp_table AS tmp
      ON  ( t.x = tmp.x AND t.y = tmp.y AND t.z = tmp.z )
     ;
    
  • ou:

      ON  (t.x, t.y, t.z) = (tmp.x, tmp.y, tmp.z) 
    

Les 2 premières options sont équivalentes mais leur efficacité peut différer selon la version. Cette syntaxe de IN avec des tuples (constructeurs de lignes) n'utilise pas les index plus efficacement dans les anciennes versions. En 5.7, l'optimiseur identifie les deux syntaxes comme équivalentes (voir MySQL docs: Row Constructor Expression Optimization ). Testez!

L'autre choix d'utiliser une table peut être préférable lorsque vous souhaitez interroger un certain nombre de paramètres/triplets. Vous pouvez également indexer la table (temp). Testez!

Donc, le conseil de base est de tester dans votre version/configuration/installation, avec les tables ayant des tailles similaires à leurs tailles prédites, et avec un nombre varié de paramètres.


Deux autres méthodes qui pourraient également être testées:

  • Le simple UNION ALL. Puisque nous voulons simplement exécuter plusieurs requêtes identiques où seuls les paramètres diffèrent. Un inconvénient est que la requête devient vraiment longue et maladroite si la requête de base est complexe et que vous avez plusieurs triplets à vérifier:

    SELECT t.* FROM my_table AS t
    WHERE ( x = ? AND y = ? AND z = ? ) UNION ALL
    SELECT t.* FROM my_table AS t
    WHERE ( x = ? AND y = ? AND z = ? ) UNION ALL
    ---
    SELECT t.* FROM my_table AS t
    WHERE ( x = ? AND y = ? AND z = ? ) ;
    
  • Utilisation d'une table dérivée (avec UNION ALL) dans la variante JOIN. Cela peut utiliser une optimisation (qui a été ajoutée dans 5.5 ou 5.6) qui peut matérialiser et indexer une table dérivée:

    SELECT t.*
    FROM my_table AS t
      JOIN 
          ( SELECT ? AS x, ? AS y, ? AS z  UNION ALL
            SELECT ?, ?, ?  UNION ALL
            ---
            SELECT ?, ?, ?
          )
        AS tmp
      ON  ( t.x = tmp.x AND t.y = tmp.y AND t.z = tmp.z )
     ;
    
11
ypercubeᵀᴹ

Une note pour MariaDB. L'optimisation de l'expression du constructeur de lignes NE FONCTIONNE PAS! Ma version est 10.1.24 et je ne la vois pas non plus mentionnée dans les notes 10.2! Je ne l'ai pas testé pour un PRIMAIRE mais j'ai testé pour INDEX UNIQUE et normal.

De plus, si vous utilisez AND/OR et que la longueur de la requête devient élevée (à environ 1200 conditions - pas un chiffre exact), l'analyseur de requêtes ne parvient pas à choisir l'index approprié, vous devez donc ajouter une clause FORCE INDEX dans votre QUERY.

Requêtes longues - mauvaise analyse. Le constructeur de lignes aurait sauvé la situation.

1
Alex S