Classement par ordre des valeurs dans une clause SQL IN ()

154

Je me demande s'il existe un moyen (peut-être un meilleur moyen) de classer par ordre des valeurs dans une clause IN ().

Le problème est que j'ai 2 requêtes, une qui obtient tous les identifiants et la seconde qui récupère toutes les informations. Le premier crée l'ordre des ID par lesquels je veux que le second trie. Les ID sont placés dans une clause IN () dans le bon ordre.

Ce serait donc quelque chose comme (extrêmement simplifié):

SELECT id FROM table1 WHERE ... ORDER BY display_order, name

SELECT name, description, ... WHERE id IN ([id's from first])

Le problème est que la deuxième requête ne renvoie pas les résultats dans le même ordre que les ID sont placés dans la clause IN ().

Une solution que j'ai trouvée est de mettre tous les ID dans une table temporaire avec un champ à incrémentation automatique qui est ensuite joint à la deuxième requête.

Y a-t-il une meilleure option?

Remarque: Comme la première requête est exécutée «par l'utilisateur» et la seconde est exécutée dans un processus d'arrière-plan, il n'y a aucun moyen de combiner la requête 2 en 1 à l'aide de sous-requêtes.

J'utilise MySQL, mais je pense qu'il pourrait être utile de noter les options disponibles pour les autres bases de données.

Darryl Hein
la source

Réponses:

186

Utilisez la FIELD()fonction de MySQL :

SELECT name, description, ...
FROM ...
WHERE id IN([ids, any order])
ORDER BY FIELD(id, [ids in order])

FIELD() renverra l'index du premier paramètre égal au premier paramètre (autre que le premier paramètre lui-même).

FIELD('a', 'a', 'b', 'c')

retournera 1

FIELD('a', 'c', 'b', 'a')

retournera 3

Cela fera exactement ce que vous voulez si vous collez les identifiants dans la IN()clause et la FIELD()fonction dans le même ordre.

ʞɔıu
la source
3
@Vojto trier par un ordre arbitraire est lent par définition. Cela fait de son mieux car il n'y a aucune garantie que INet les FIELDparamètres sont égaux. Faire cela dans un code de programme peut être plus rapide en utilisant ces connaissances supplémentaires. Bien sûr, il pourrait être plus sage de placer cette charge sur le client plutôt que sur le serveur si vous avez à l'esprit les performances du serveur.
ivan_pozdeev
1
et quoi sqlite?
fnc12
15

Voir ci-dessous comment obtenir des données triées.

SELECT ...
  FROM ...
 WHERE zip IN (91709,92886,92807,...,91356)
   AND user.status=1
ORDER 
    BY provider.package_id DESC 
     , FIELD(zip,91709,92886,92807,...,91356)
LIMIT 10
Pradeep Singh
la source
11

Deux solutions qui me viennent à l'esprit:

  1. order by case id when 123 then 1 when 456 then 2 else null end asc

  2. order by instr(','||id||',',',123,456,') asc

( instr()vient d'Oracle; peut-être que vous avez locate()ou charindex()ou quelque chose comme ça)

John Nilsson
la source
Pour MySQL, FIELD_IN_SET () peut également être utilisé: dev.mysql.com/doc/refman/5.0/en
Darryl Hein
6

Ans pour obtenir des données triées.

SELECT ...
FROM ...
ORDER  BY FIELD(user_id,5,3,2,...,50)  LIMIT 10
Gulshan Prajapati
la source
6

Si vous souhaitez effectuer un tri arbitraire sur une requête en utilisant des valeurs entrées par la requête dans MS SQL Server 2008+ , cela peut être fait en créant une table à la volée et en effectuant une jointure comme celle-ci (en utilisant la nomenclature d'OP).

SELECT table1.name, table1.description ... 
FROM (VALUES (id1,1), (id2,2), (id3,3) ...) AS orderTbl(orderKey, orderIdx) 
LEFT JOIN table1 ON orderTbl.orderKey=table1.id
ORDER BY orderTbl.orderIdx

Si vous remplacez l'instruction VALUES par quelque chose d'autre qui fait la même chose, mais en ANSI SQL, cela devrait fonctionner sur n'importe quelle base de données SQL.

Remarque: La deuxième colonne de la table créée (orderTbl.orderIdx) est nécessaire lors de l'interrogation de jeux d'enregistrements supérieurs à 100 environ. À l'origine, je n'avais pas de colonne orderIdx, mais j'ai trouvé qu'avec des ensembles de résultats supérieurs à 100, je devais trier explicitement par cette colonne; dans SQL Server Express 2014 de toute façon.

Ian
la source
Cette question concerne MySQL ... pas sûr que votre requête fonctionnerait avec MySQL.
Darryl Hein
2
@Darryl, ce ne serait pas le cas. Mais cet article apparaît comme le meilleur résultat lorsque vous recherchez sur Google comment commander selon la clause IN, donc j'ai pensé que je pouvais aussi bien le poster ici et que la méthodologie générale s'applique à tous les serveurs SQL conformes à ANSI (remplacez simplement l'instruction VALUES par une norme façon de créer une table).
Ian
Remarque, cela a fonctionné pour moi, mais seulement après avoir remplacé les références à orderTbl.orderKey, orderTbl.orderIndexpar orderKey,orderIndex
Peter
5
SELECT ORDER_NO, DELIVERY_ADDRESS 
from IFSAPP.PURCHASE_ORDER_TAB 
where ORDER_NO in ('52000077','52000079','52000167','52000297','52000204','52000409','52000126') 
ORDER BY instr('52000077,52000079,52000167,52000297,52000204,52000409,52000126',ORDER_NO)

a très bien fonctionné

Ravi Ranjan
la source
A travaillé pour moi sur Oracle. Rapide et facile. Merci.
Menachem
4

La clause IN décrit un ensemble de valeurs et les ensembles n'ont pas d'ordre.

Votre solution avec une jointure puis un classement sur la display_ordercolonne est la solution la plus correcte; tout le reste est probablement un hack spécifique au SGBD (ou fait des choses avec les fonctions OLAP en SQL standard). Certes, la jointure est la solution la plus portable (bien que la génération des données avec les display_ordervaleurs puisse être problématique). Notez que vous devrez peut-être sélectionner les colonnes de classement; c'était une exigence dans SQL standard, bien que je pense que cela a été assoupli il y a quelque temps (peut-être aussi longtemps que SQL-92).

Jonathan Leffler
la source
3

Utilisez la fonction MySQL FIND_IN_SET :

  SELECT * 
    FROM table_name 
   WHERE id IN (..,..,..,..) 
ORDER BY FIND_IN_SET (coloumn_name, .., .., ..);
Sarthak Sawhney
la source
2

Pour Oracle, la solution de John utilisant la fonction instr () fonctionne. Voici une solution légèrement différente qui a fonctionné - SELECT id FROM table1 WHERE id IN (1, 20, 45, 60) ORDER BY instr('1, 20, 45, 60', id)

V Patel
la source
2

J'ai juste essayé de faire cela avec MS SQL Server où nous n'avons pas FIELD ():

SELECT table1.id
... 
INNER JOIN
    (VALUES (10,1),(3,2),(4,3),(5,4),(7,5),(8,6),(9,7),(2,8),(6,9),(5,10)
    ) AS X(id,sortorder)
        ON X.id = table1.id
    ORDER BY X.sortorder

Notez que j'autorise également la duplication.

Tony
la source
1

Ma première pensée a été d'écrire une seule requête, mais vous avez dit que ce n'était pas possible car l'une est exécutée par l'utilisateur et l'autre est exécutée en arrière-plan. Comment stockez-vous la liste des identifiants à passer de l'utilisateur au processus d'arrière-plan? Pourquoi ne pas les mettre dans une table temporaire avec une colonne pour signifier la commande.

Alors que diriez-vous de ceci:

  1. Le bit d'interface utilisateur s'exécute et insère des valeurs dans une nouvelle table que vous créez. Il insérerait l'identifiant, la position et une sorte d'identifiant de numéro de travail)
  2. Le numéro de travail est transmis au processus d'arrière-plan (au lieu de tous les identifiants)
  3. Le processus d'arrière-plan effectue une sélection dans le tableau de l'étape 1 et vous vous joignez pour obtenir les autres informations dont vous avez besoin. Il utilise le numéro de travail dans la clause WHERE et trie par la colonne de position.
  4. Le processus d'arrière-plan, une fois terminé, est supprimé de la table en fonction de l'identificateur de travail.
WW.
la source
1

Je pense que vous devriez réussir à stocker vos données de manière à ce que vous fassiez simplement une jointure et ce sera parfait, donc pas de hacks et de choses compliquées.

J'ai par exemple une liste "Récemment jouée" des identifiants de piste, sur SQLite je fais simplement:

SELECT * FROM recently NATURAL JOIN tracks;
kroe
la source
1
Oh, j'aimerais que la vie soit aussi simple :)
Darryl Hein
0

Essayez ceci:

SELECT name, description, ...
WHERE id IN
    (SELECT id FROM table1 WHERE...)
ORDER BY
    (SELECT display_order FROM table1 WHERE...),
    (SELECT name FROM table1 WHERE...)

Les WHERE nécessiteront probablement un petit ajustement pour que les sous-requêtes corrélées fonctionnent correctement, mais le principe de base doit être sain.

le chaos
la source
voir la note et aussi, comme indiqué, les exemples de requêtes sont extrêmement simplifiés, je ne peux donc pas les combiner en 1 requête avec des sous-requêtes.
Darryl Hein