Filtrer une requête un-à-plusieurs en exigeant que tous répondent à plusieurs critères

StackOverflow https://stackoverflow.com/questions/481588

  •  20-08-2019
  •  | 
  •  

Question

Imaginez les tableaux suivants:

créer des zones de table (id int, nom texte, ...);

créer des objets de table (id int, box_id int, chose enum ('pomme,' banane ',' orange ');

Et les tableaux ressemblent à:

Boxes:
id | name
1  | orangesOnly
2  | orangesOnly2
3  | orangesBananas
4  | misc

thingsinboxes:
id | box_id | thing
1  |  1     | orange
2  |  1     | orange
3  |  2     | orange
4  |  3     | orange
5  |  3     | banana
6  |  4     | orange
7  |  4     | apple
8  |  4     | banana

Comment sélectionner les cases qui contiennent au moins une orange et rien qui ne soit une orange?

Comment cette échelle, en supposant que j’ai plusieurs centaines de milliers de boîtes et éventuellement un million de choses dans des boîtes?

J'aimerais si possible tout conserver en SQL plutôt que de post-traiter le jeu de résultats avec un script.

J'utilise postgres et mysql, donc les sous-requêtes sont probablement mauvaises, étant donné que mysql n'optimise pas les sous-requêtes (avant la version 6, de toute façon).

Était-ce utile?

La solution

SELECT b.*
FROM boxes b JOIN thingsinboxes t ON (b.id = t.box_id)
GROUP BY b.id
HAVING COUNT(DISTINCT t.thing) = 1 AND SUM(t.thing = 'orange') > 0;

Voici une autre solution qui n'utilise pas GROUP BY:

SELECT DISTINCT b.*
FROM boxes b
  JOIN thingsinboxes t1 
    ON (b.id = t1.box_id AND t1.thing = 'orange')
  LEFT OUTER JOIN thingsinboxes t2 
    ON (b.id = t2.box_id AND t2.thing != 'orange')
WHERE t2.box_id IS NULL;

Comme toujours, avant de tirer des conclusions sur l’évolutivité ou les performances d’une requête, vous devez l’essayer avec un jeu de données réaliste et en mesurer les performances.

Autres conseils

Je pense que la requête de Bill Karwin est très bien, cependant, si une proportion relativement petite de boîtes contient des oranges, vous devriez pouvoir accélérer les choses en utilisant un index sur le champ thing:

SELECT b.*
FROM boxes b JOIN thingsinboxes t1 ON (b.id = t1.box_id)
WHERE t1.thing = 'orange'
AND NOT EXISTS (
    SELECT 1
    FROM thingsinboxes t2
    WHERE t2.box_id = b.id
    AND t2.thing <> 'orange'
)
GROUP BY t1.box_id

La WHERE NOT EXISTS sous-requête ne sera exécutée qu’une fois par élément orange, donc elle n’est pas trop chère s’il n’ya pas beaucoup d’oranges.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top