FIND_IN_SET () vs IN ()

125

J'ai 2 tables dans ma base de données. L'un concerne les commandes et l'autre les entreprises.

Les commandes ont cette structure:

OrderID     |     attachedCompanyIDs
------------------------------------
   1                     1,2,3
   2                     2,4

Et la société a cette structure:

CompanyID      |        name
--------------------------------------
    1                 Company 1
    2                 Another Company
    3                 StackOverflow
    4                 Nothing

Pour obtenir les noms des sociétés d'une commande, je peux faire une requête en tant que telle:

SELECT name FROM orders,company
WHERE orderID = 1 AND FIND_IN_SET(companyID, attachedCompanyIDs)

Cette requête fonctionne correctement, mais pas la requête suivante.

SELECT name FROM orders,company
WHERE orderID = 1 AND companyID IN (attachedCompanyIDs)

Pourquoi la première requête fonctionne-t-elle mais pas la seconde?

La première requête renvoie:

name
---------------
Company 1
Another Company
StackOverflow

La deuxième requête ne renvoie que:

name
---------------
Company 1

Pourquoi est-ce, pourquoi la première requête renvoie toutes les sociétés, mais la deuxième requête ne renvoie que la première?

Fusée Hazmat
la source
3
AttachCompanyIDs est une grande chaîne, donc mysql essaie de trouver une société dans ce son cast en entier
Haim Evgi
Je pense que c'est le meilleur exemple mysqltutorial.org/mysql-find_in_set
Shurvir Mori

Réponses:

100
SELECT  name
FROM    orders,company
WHERE   orderID = 1
        AND companyID IN (attachedCompanyIDs)

attachedCompanyIDsest une valeur scalaire qui est convertie en INT(type de companyID).

La distribution ne renvoie que les nombres jusqu'au premier non-chiffre (une virgule dans votre cas).

Donc,

companyID IN ('1,2,3')  companyID IN (CAST('1,2,3' AS INT))  companyID IN (1)

Dans PostgreSQL, vous pouvez convertir la chaîne en tableau (ou la stocker en tant que tableau en premier lieu):

SELECT  name
FROM    orders
JOIN    company
ON      companyID = ANY (('{' | attachedCompanyIDs | '}')::INT[])
WHERE   orderID = 1

et cela utiliserait même un index sur companyID.

Malheureusement, cela ne fonctionne pas MySQLcar ce dernier ne prend pas en charge les tableaux.

Vous pouvez trouver cet article intéressant (voir #2):

Mettre à jour:

S'il y a une limite raisonnable sur le nombre de valeurs dans les listes séparées par des virgules (par exemple, pas plus de 5), vous pouvez donc essayer d'utiliser cette requête:

SELECT  name
FROM    orders
CROSS JOIN
        (
        SELECT  1 AS pos
        UNION ALL
        SELECT  2 AS pos
        UNION ALL
        SELECT  3 AS pos
        UNION ALL
        SELECT  4 AS pos
        UNION ALL
        SELECT  5 AS pos
        ) q
JOIN    company
ON      companyID = CAST(NULLIF(SUBSTRING_INDEX(attachedCompanyIDs, ',', -pos), SUBSTRING_INDEX(attachedCompanyIDs, ',', 1 - pos)) AS UNSIGNED)
Quassnoi
la source
3
Merci pour l'explication. Je ne savais pas que le champ AttachCompanyIDs avait été converti en INT. Y a-t-il un moyen de contourner cela dans MySQL? FIND_IN_SETfonctionne, mais n'utilise pas d'index et peut être lent avec beaucoup d'informations dans la table Company.
Rocket Hazmat
1
Pouvez-vous expliquer cette mise à jour? Qu'est-ce que cela fait exactement, car cela semble fonctionner.
Rocket Hazmat
1
@Rocket: il supprime les poséléments depuis le début de CVSet transforme le reste en entier.
Quassnoi
9
Thumbs up (y) for10 things in MySQL (that won’t work as expected)
NullPointer
@Quassnoi, pourquoi écrivez-vous CROSS JOIN? Ne sont-ils pas tous identiques dans MySQL?
Pacerier
13

AttachCompanyIDs est une grande chaîne, donc mysql essaie de trouver une société dans son cast en entier

lorsque vous utilisez où dans

donc si comapnyid = 1:

companyID IN ('1,2,3')

c'est le retour vrai

mais si le chiffre 1 n'est pas en premier lieu

 companyID IN ('2,3,1')

son retour faux

Haim Evgi
la source
3

Pour obtenir le nom de toutes les sociétés associées, non basé sur un identifiant particulier.

SELECT 
    (SELECT GROUP_CONCAT(cmp.cmpny_name) 
    FROM company cmp 
    WHERE FIND_IN_SET(cmp.CompanyID, odr.attachedCompanyIDs)
    ) AS COMPANIES
FROM orders odr
Anupriya Pundir
la source
1

parce que la deuxième requête recherche des lignes avec l'ID 1 OU 2 OU 3, la première requête recherche l'une des valeurs délimitées par des virgules dans companyID,

et un autre problème ici est que vous ne joignez pas les tables sur une clé commune dans votre où vous allez donc obtenir une mutation de lignes qui = count (table1) * count (table2);

Votre problème existe vraiment avec la partie 2 de ma réponse. (avec votre deuxième requête)

superfro
la source
Il y a plus de lignes dans les deux tableaux que je n'en montre. Dans les deux tableaux, il y a l'ID de l'utilisateur sous lequel vous êtes connecté, rejoignez-vous sur cette aide?
Rocket Hazmat
Eh bien, vous ne devez rien changer si votre première requête ne renvoie pas les résultats attendus. Si la première requête renvoie les résultats souhaités, il n'y a vraiment pas de problème. Je pensais que vous étiez simplement curieux de savoir pourquoi les 2 ne montrent pas le même résultat.
superfro
@superfro, je suis curieux de savoir pourquoi les 2 ne montrent pas le même résultat.
Rocket Hazmat
votre deuxième requête utilise un where IN (valeurs) où la partie «valeurs» provient de la table, et c'est une chaîne. La chaîne est évaluée comme une valeur booléenne true qui = 1, raison pour laquelle elle n'affiche que la première ligne.
superfro
1
Si vous êtes préoccupé par les performances, vous devriez probablement penser à modifier la structure de votre base de données. Vous pouvez ajouter une table conjointe contenant 2 valeurs, order_ID et company_ID au lieu d'utiliser la liste délimitée par des virgules dans la table de commande. Cela vous permettrait de sélectionner le nom de la société à gauche pour rejoindre order_companies sur company.company_ID = order_companies.company_ID à gauche de rejoindre les commandes sur order_companies.order_ID = order.order_ID où orders.order_ID = 1; Cela utiliserait des index.
superfro
-1

Laissez-moi vous expliquer quand utiliser FIND_IN_SET et quand utiliser IN.

Prenons la table A qui a des colonnes nommées "aid", "aname". Prenons le tableau B qui a des colonnes nommées "bid", "bname", "aids".

Maintenant, il y a des valeurs fictives dans les tableaux A et B comme ci-dessous.

Tableau A

aide aname

1 pomme

2 banane

3 mangue

Tableau B

enchérir bname aids

1 pomme 1,2

2 Banane 2,1

3 Mangue 3,1,2

enter code here

Cas 1: si vous voulez obtenir ces enregistrements de la table b qui a 1 valeur présente dans les colonnes des aides, vous devez utiliser FIND_IN_SET.

Requête: sélectionnez * dans A JOIN B ON FIND_IN_SET (A.aid, b.aids) où A.aid = 1;

Cas 2: si vous voulez obtenir ces enregistrements de la table a qui a 1 OU 2 OU 3 valeur présente dans les colonnes d'aide, vous devez utiliser IN.

Requête: sélectionnez * dans A JOIN B ON A.aid IN (b.aids);

Maintenant, voici ce dont vous avez besoin via une requête mysql.

prashant
la source
Cette question était déjà résolue. De plus, je ne pense pas que votre deuxième exemple, avec IN, fonctionne ... c'était essentiellement le problème que j'essayais de résoudre au début.
Rocket Hazmat
-2
SELECT o.*, GROUP_CONCAT(c.name) FROM Orders AS o , Company.c
    WHERE FIND_IN_SET(c.CompanyID , o.attachedCompanyIDs) GROUP BY o.attachedCompanyIDs
amit gangrade
la source
6
Bienvenue à SO! Le code sans explications est rarement utile. Dans ce cas, il n'essaye même pas de répondre à la question "Pourquoi ...?". Veuillez également noter que cette question particulière a déjà une réponse acceptée qui donne une réponse bien reçue (> 80 votes!). En tant que nouvel utilisateur, il peut être préférable de se concentrer sur les questions sans réponse et / ou de poser vous-même les bonnes questions.
cfi