sélectionner toutes les lignes avec une valeur minimale

9

Dans Sqlite 3, j'essaie de comprendre comment sélectionner des lignes en fonction d'une valeur minimale. Je pense que je suis limité en ne connaissant pas suffisamment la terminologie associée pour rechercher efficacement sur Google.

Le tableau ressemble à:

num         text        num2      
----------  ----------  ----------
0           a           1         
0           a           2         
1           a           3         
1           b           4         

Je veux obtenir les lignes où num2est 1, 2, et 4. Je veux faire la sélection en fonction de la valeur minimale de num pour chaque valeur unique de la colonne de texte.

Donc, pour text = 'a', la valeur minimale de numest 0, donc je veux les lignes 1 et 2. Pour text = 'b', la valeur minimale de numest 1, donc je veux la ligne 4.

En utilisant diverses combinaisons de group by, je peux obtenir des lignes 1et 2ou des lignes 1et 4. J'ai l'impression qu'il me manque un composant SQL qui ferait ce que je veux, mais je n'ai pas réussi à comprendre ce que cela pourrait être.

Quelle est la bonne façon de faire ce type de requête?

Solution possible

J'ai trouvé un moyen de le faire. Je ne suis pas assez réputé pour répondre à ma propre question, alors je fais la mise à jour ici. Je ne sais pas si c'est toujours correct ou à quoi ressemble l'efficacité. Tout commentaire est le bienvenu.

J'ai utilisé une instruction de sélection composée, où une requête trouve la valeur minimale de num pour chaque valeur unique de texte:

sqlite> select num, text from t group by text having num = min( num );
num         text      
----------  ----------
0           a         
1           b         

Ensuite, je l'ai joint à la table complète pour obtenir toutes les lignes correspondant à ces deux colonnes.

sqlite> with u as
      ( select num, text from t group by text having num = min( num ) )
        select t.* from t join u on t.num = u.num and t.text = u.text;
num         text        num2      
----------  ----------  ----------
0           a           1         
0           a           2         
1           b           4         
user35292
la source

Réponses:

10

Comme vous l'avez vu, un simple GROUP BY ne fonctionnera pas car il ne retournera qu'un seul enregistrement par groupe.

Votre inscription fonctionne bien. Pour une grande table, elle ne sera efficace que s'il existe un index sur les colonnes de jointure ( numet text).

Vous pouvez également utiliser une sous-requête corrélée:

SELECT *
FROM t
WHERE num = (SELECT MIN(num)
             FROM t AS t2
             WHERE t2.text = t.text);

SQLFiddle

Lors de son exécution, cette requête ne nécessite pas de table temporaire (votre requête le fait pour le résultat de u), mais exécutera la sous-requête pour chaque enregistrement t, elle textdoit donc être indexée. (Ou utilisez un index sur les deux textet numpour obtenir un indice de couverture .)

CL.
la source
il n'a pas de table temporaire dans sa requête, seulement un CTE, ce qui est assez différent.
ypercubeᵀᴹ
Une fois exécutée, le résultat de la urequête est stocké dans une table temporaire, qu'il soit écrit en tant que CTE, vue ou en ligne en tant que sous-requête.
CL.
Merci, cette version est beaucoup plus facile à écrire que celle dans laquelle je suis tombée. Connaître la bonne terminologie est également utile pour moi pour approfondir cette question.
user35292
@CL Est-ce ainsi que SQLite exécute les requêtes avec les CTE? Avez-vous une référence pour cela? Parce que les autres SGBD n'utilisent pas nécessairement les tables temporaires pour les ctes.
ypercubeᵀᴹ
@ypercube Les CTE, les vues et les sous-requêtes sont aplatis ou implémentés comme coroutines, si possible. Mais un GROUP BY sur une colonne non indexée doit pouvoir collecter les données de tous les groupes en parallèle, il nécessite donc une certaine forme de table temporaire (dans toutes les bases de données).
CL.
1

J'ai tendance à faire ce genre de chose avec une auto-jointure externe:

SELECT
    M1.Num,
    M1.Text,
    M1.Num2
FROM
    MyDb M1
LEFT OUTER JOIN
    MyDB M2
ON
    M1.text = M2.text
AND
    M1.num > m2.num
WHERE
    M2.num is null

C'est essentiellement dire; donnez-moi tous les enregistrements qui n'ont pas une valeur supérieure, c'est-à-dire null.

BAISER
la source
1

Alors, comment pouvez-vous trouver vous-même la réponse à votre question la prochaine fois? À mon avis, c'est en décomposant et en suivant la logique. Et vous avez raison:

Je veux faire la sélection en fonction de la valeur minimale de num pour chaque valeur unique de la colonne de texte

Cela se traduit par:

select text, min(num) from t group by text;

(Cela devrait être équivalent à votre havingrequête. Il pourrait être intéressant de regarder les lignes où numest égal à NULL. Le plus précis: regardez quel effet les lignes avec des valeurs nulles ont, que vous voudrez peut-être filtrer en premier avec a where num is not null)

De là, vous pouvez obtenir le résultat souhaité en:

select * from t where (num, text) in ( *insert query above* )

Ou en utilisant une jointure:

select t1.* from t t1,
    (select text, min(num) as n from t group by text) t2
where t1.num = t2.n and t1.text = t2.text.

Et lorsque les performances ne sont pas suffisantes pour vos tables, commencez à examiner des instructions plus complexes.

Grimaldi
la source
-2

Cette requête ne devrait-elle pas être exactement ce dont vous avez besoin?

select min(num), text, num2 group by text, num2
Jens W.
la source
Cela renverra les quatre enregistrements, car les num2valeurs sont uniques.
CL.