FIND_IN_SET () vs IN ()

125

Tengo 2 tablas en mi base de datos. Uno es para pedidos y otro para empresas.

Los pedidos tienen esta estructura:

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

Y la empresa tiene esta estructura:

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

Para obtener los nombres de las empresas de un pedido, puedo hacer una consulta como tal:

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

Esa consulta funciona bien, pero la siguiente consulta no.

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

¿Por qué funciona la primera consulta pero no la segunda?

La primera consulta devuelve:

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

La segunda consulta solo devuelve:

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

¿Por qué es esto, por qué la primera consulta devuelve todas las empresas, pero la segunda consulta solo devuelve la primera?

Cohete Hazmat
fuente
3
attachCompanyIDs es una gran cadena, así que mysql intenta encontrar compañía en este su elenco a entero
Haim Evgi
Creo que este es el mejor ejemplo mysqltutorial.org/mysql-find_in_set
Shurvir Mori

Respuestas:

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

attachedCompanyIDses un valor escalar que se convierte en INT(tipo de companyID).

El reparto solo devuelve números hasta el primer no dígito (una coma en su caso).

Así,

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

En PostgreSQL, puede convertir la cadena en una matriz (o almacenarla como una matriz en primer lugar):

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

y esto incluso usaría un índice en companyID.

Desafortunadamente, esto no funciona MySQLya que este último no admite matrices.

Puede encontrar este artículo interesante (ver #2):

Actualizar:

Si hay algún límite razonable en el número de valores en las listas separadas por comas (por ejemplo, no más de 5), puede intentar usar esta consulta:

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
fuente
3
Gracias por la explicación. No me di cuenta de que el campo ID de la empresa adjunta se transfirió a un INT. ¿Hay alguna forma de evitar esto en MySQL? FIND_IN_SETfunciona, pero no usa índices, y puede ser lento con mucha información en la tabla Compañía.
Rocket Hazmat
1
¿Puedes explicar esa actualización? ¿Qué hace eso exactamente, porque parece funcionar?
Rocket Hazmat
1
@Rocket: quita los poselementos desde el principio CVSy convierte el resto en entero.
Quassnoi
9
Pulgares arriba (y) para10 things in MySQL (that won’t work as expected)
NullPointer
@Quassnoi, ¿por qué escribes CROSS JOIN? ¿No son todos iguales en MySQL?
Pacerier
13

attachCompanyIDs es una gran cadena, por lo que mysql intenta encontrar compañía en este su elenco a entero

cuando usas donde en

entonces si comapnyid = 1:

companyID IN ('1,2,3')

esto es cierto

pero si el número 1 no está en primer lugar

 companyID IN ('2,3,1')

su retorno falso

Haim Evgi
fuente
3

Para obtener el nombre de todas las empresas relacionadas, no se basa en una identificación particular

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
fuente
1

debido a que la segunda consulta busca filas con los ID 1 O 2 O 3, la primera consulta busca uno de los valores delimitados por comas que existen en la ID de compañía,

y otro problema aquí es que no está uniendo las tablas en una clave común en su ubicación, por lo que obtendrá una mutación de filas que = cuenta (tabla1) * cuenta (tabla2);

Su problema realmente existe con la parte 2 de mi respuesta. (con su segunda consulta)

superfro
fuente
Hay más filas en ambas tablas de las que estoy mostrando. En ambas tablas, ¿la identificación del usuario con el que ha iniciado sesión se uniría a esa ayuda?
Rocket Hazmat
Bueno, solo necesita cambiar algo si su primera consulta no devuelve los resultados esperados. Si la primera consulta devuelve los resultados que desea, entonces realmente no hay problema. Pensé que tenías curiosidad por qué los 2 no muestran el mismo resultado.
Superfro
@superfro, tengo curiosidad por saber por qué los 2 no muestran el mismo resultado.
Rocket Hazmat
su segunda consulta está utilizando un donde IN (valores) donde la parte 'valores' proviene de la tabla, y es una cadena. La cadena se está evaluando como un bool verdadero que = 1, por lo que solo muestra la primera fila.
Superfro
1
Si le preocupa el rendimiento, probablemente debería pensar en cambiar la estructura de su base de datos. Puede agregar una tabla conjunta que contenga 2 valores, order_ID y company_ID en lugar de usar la lista delimitada por comas en la tabla de pedidos. Esto le permitiría seleccionar el nombre de la compañía a la izquierda unirse a order_companies en company.company_ID = order_companies.company_ID dejar unirse a las órdenes en order_companies.order_ID = order.order_ID donde orders.order_ID = 1; Esto usaría índices.
Superfro
-1

Permítanme explicar cuándo usar FIND_IN_SET y cuándo usar IN.

Tomemos la tabla A que tiene columnas llamadas "ayuda", "aname". Tomemos la tabla B que tiene columnas llamadas "oferta", "nombre de b", "ayudas".

Ahora hay valores ficticios en la Tabla A y la Tabla B como se muestra a continuación.

Tabla A

ayuda aname

1 manzana

2 plátano

3 mango

Tabla B

oferta bname ayudas

1 manzana 1,2

2 plátano 2,1

3 mango 3,1,2

enter code here

Caso 1: si desea obtener esos registros de la tabla b que tiene 1 valor presente en las columnas de ayudas, debe usar FIND_IN_SET.

Consulta: seleccione * de A JOIN B ON FIND_IN_SET (A.aid, b.aids) donde A.aid = 1;

Caso 2: si desea obtener esos registros de la tabla a que tiene un valor 1 O 2 O 3 presente en las columnas de ayuda, entonces debe usar IN.

Consulta: seleccione * de A JOIN B ON A.aid IN (b.aids);

Ahora aquí, hasta usted, lo que necesita a través de la consulta mysql.

prashant
fuente
Esta pregunta ya estaba resuelta. Además, no creo que su segundo ejemplo, con IN, funcione ... ese fue básicamente el problema que estaba tratando de resolver al principio.
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
fuente
66
Bienvenido a SO! El código sin explicaciones rara vez es útil. En este caso, ni siquiera intenta responder la pregunta "¿Por qué ...?". También tenga en cuenta que esta pregunta en particular ya tiene una respuesta aceptada que da una respuesta bien recibida (> 80 votos!). Como nuevo usuario, puede ser mejor concentrarse en preguntas sin respuesta y / o hacer buenas preguntas usted mismo.
cfi