2012-05-07 36 views
24

Tengo una columna en una de las tablas donde almaceno múltiples identificadores separados por comas. ¿Existe alguna manera de utilizar el valor de esta columna en la cláusula "IN" de una consulta?Valores separados por comas en MySQL "IN" cláusula

La columna (city) tiene valores como 6,7,8,16,21,2

que necesitan para su uso como

select * from table where e_ID in (Select city from locations where e_Id=?) 

Estoy satisfecho con la respuesta de Crozin, pero estoy abierto a sugerencias, opiniones y opciones.

Siéntase libre de compartir su opinión.

Respuesta

27

Basándose en el FIND_IN_SET() ejemplo de @Jeremy Smith, puede hacerlo con una unión por lo que no tiene que ejecutar una subconsulta.

SELECT * FROM table t 
JOIN locations l ON FIND_IN_SET(t.e_ID, l.city) > 0 
WHERE l.e_ID = ? 

Esto se conoce para llevar a cabo muy mal, ya que tiene que ver tabla-scan, la evaluación de la función FIND_IN_SET() para cada combinación de filas en table y locations. No puede hacer uso de un índice, y no hay forma de mejorarlo.

Sé que dijiste que intentabas sacar el mejor provecho de un mal diseño de la base de datos, pero debes comprender cuán drásticamente malo es esto.

Explicación: Supongamos que le pidiera que busque a todos en una guía telefónica cuya primera, media o última inicial es "J."No hay forma de que el orden ordenado del libro ayude en este caso, ya que debe escanear todas las páginas de todos modos.

La solución LIKE de @fthiella tiene un problema similar en cuanto a rendimiento. No se puede indexar.

véase también mi respuesta a Is storing a delimited list in a database column really that bad? para otros escollos de esta forma de almacenamiento de datos no normalizados

Si se puede crear una tabla adicional para almacenar un índice, puede asignar los lugares a cada entrada en la lista de ciudades.:

CREATE TABLE location2city (
location INT, 
city INT, 
PRIMARY KEY (location, city) 
); 

Asumiendo que tiene una tabla de consulta para todas las ciudades posibles (no sólo los mencionados en el table) puede soportar la ineficiencia de una vez para producir el mapeo:

INSERT INTO location2city (location, city) 
    SELECT l.e_ID, c.e_ID FROM cities c JOIN locations l 
    ON FIND_IN_SET(c.e_ID, l.city) > 0; 

Ahora puede ejecutar una consulta mucho más eficiente para encontrar las entradas en su table:

SELECT * FROM location2city l 
JOIN table t ON t.e_ID = l.city 
WHERE l.e_ID = ?; 

Esto puede hacer uso de un índice. Ahora solo tiene que tener cuidado de que cualquier INSERT/UPDATE/DELETE de filas en locations también inserte las filas de asignación correspondientes en location2city.

+0

+1 para una explicación tan lúcida –

+0

He utilizado la opción FIND_IN_SET. Estas son las mejores respuestas de las disponibles aquí. En lugar de uniones y subconsultas, esto se adapta mejor. Pero a menos que sea muy importante, no intente almacenar valores separados por comas en una columna. Eso no se ajusta al RDBMS. Lo he usado porque la tabla y los datos se crearon mucho antes y había muchos datos que no quiero arriesgar mediante Data Transfer. – Vijay

+0

@Vijay: Muchas gracias por su sugerencia y muchas gracias, Bill. –

25

Desde el punto de vista de MySQL, no está almacenando varios identificadores separados por comas; está almacenando un valor de texto, que tiene el mismo significado que "Hola mundo" o "Me gustan los pasteles". - es decir, no tiene ningún significado.

Lo que tienes que hacer es crear una tabla separada que unirá dos objetos de la base de datos. Obtenga más información acerca de las relaciones many-to-many o one-to-many (según sus requisitos) en bases de datos SQL.

+0

Gracias por su sugerencia y acepto que el diseño es pobre, pero tengo algunas limitaciones que no puedo cambiar. ¿Hay alguna manera de implementar esto ... –

+0

@Crozin, Obviamente está pidiendo una solución ... – Pacerier

5

IN toma las filas, por lo que tomar la columna separada comas para la búsqueda no hará lo que quiera pero si proporciona datos como este ('1', '2', '3') funcionará pero no puede guardar datos como esto en tu campo lo que sea que insertes en la columna lo tomará todo como una cadena.

24

En lugar de utilizar IN en la consulta, utilice FIND_IN_SET (docs):

SELECT * FROM table 
WHERE 0 < FIND_IN_SET(e_ID, (
      SELECT city FROM locations WHERE e_ID=?)) 

Las advertencias habituales sobre primera forma de normalización se aplican (la base de datos no debe almacenar varios valores en una sola columna), pero si estás atrapado con eso, entonces la afirmación anterior debería ayudar.

+0

Gracias por su respuesta.Esto es muy útil. ¿Alguna luz sobre el rendimiento? –

+2

El rendimiento va a ser relativamente bajo, porque 'FIND_IN_SET' no puede usar ningún índice. Si le preocupa el rendimiento (y tiene la autoridad para hacerlo), realmente necesitará dividir su columna 'locations' .city' en otra tabla usando 1nf, y luego podrá usar índices para ayudar a su desempeño. –

+1

Como dice @JeremySmyth, la teoría es almacenar una lista de números separados por comas en una columna que infringe una regla básica de diseño de base de datos, la regla de atomicidad (una celda, un valor). Por lo tanto, sería mejor rediseñar eso. PERO, si las restricciones no lo permiten, vaya con 'FIND_IN_SET'. – inigomedina

21

Esto no uso en la cláusula, pero debe hacer lo que tiene:

Select * 
from table 
where 
    CONCAT(',', (Select city from locations where e_Id=?), ',') 
    LIKE 
    CONCAT('%,', e_ID, ',%') 

pero hay que asegurarse de que e_ID no contiene ningún comas o cualquier carácter alegre.

p. Ej.

CONCAT(',', '6,7,8,16,21,2', ',') returns ',6,7,8,16,21,2,' 

e_ID=1 --> ',6,7,8,16,21,2,' LIKE '%,1,%' ? FALSE 
e_ID=6 --> ',6,7,8,16,21,2,' LIKE '%,6,%' ? TRUE 
e_ID=21 --> ',6,7,8,16,21,2,' LIKE '%,21,%' ? TRUE 
e_ID=2 --> ',6,7,8,16,21,2,' LIKE '%,2,%' ? TRUE 
e_ID=3 --> ',6,7,8,16,21,2,' LIKE '%,3,%' ? FALSE 
etc. 
+0

+1 @fthiella la más simple de todas las mejores respuestas :) y me gustan los ejemplos de código en las respuestas y preguntas: D – bonCodigo

+0

sí ... convierta el campo de comparación de un int a una cadena, y use like. Esto funciona. Sé que el problema se debe a un mal diseño de la base de datos, pero desafortunadamente a veces tenemos que trabajar con este diseño de base de datos defectuoso a veces sin tiempo suficiente para reingeniería. –

7

éste en Oracle para ..here concatenación de cadenas se realiza por wm_concat

select * from table where e_ID in (Select wm_concat(city) from locations where e_Id=?) 

sí estoy de acuerdo con raheel shan .. con el fin poner esta "en" cláusula tenemos que hacer que la columna en la fila de abajo el código uno hace ese trabajo.

select * from table where to_char(e_ID) 
in (
    select substr(city,instr(city,',',1,rownum)+1,instr(city,',',1,rownum+1)-instr(city,',',1,rownum)-1) from 
    (
    select ','||WM_CONCAT(city)||',' city,length(WM_CONCAT(city))-length(replace(WM_CONCAT(city),','))+1 CNT from locations where e_Id=?) TST 
    ,ALL_OBJECTS OBJ where TST.CNT>=rownum 
    ) ; 
6

debe usar

FIND_IN_SET Devuelve la posición del valor en la cadena de valores separados por comas

mysql> SELECT FIND_IN_SET('b','a,b,c,d'); 
    -> 2 
6

No sé si esto es lo que quiere lograr. Con MySQL no hay función para concatenar los valores de un grupo GROUP_CONCAT

Usted puede intentar algo como esto:

select * from table where e_ID in (Select GROUP_CONCAT(city SEPARATOR ',') from locations where e_Id=?) 
5

que necesita para los valores de la columna "split" de la ciudad. Será como:

SELECT * 
    FROM table 
WHERE e_ID IN (SELECT TO_NUMBER(
           SPLIT_STR(city /*string*/ 
              , ',' /*delimiter*/ 
              , 1 /*start_position*/ 
              ) 
           ) 
        FROM locations); 

Usted puede leer más sobre la función split_str MySQL aquí: http://blog.fedecarg.com/2009/02/22/mysql-split-string-function/

Además, he utilizado la función de Oracle TO_NUMBER aquí. Por favor, reemplácelo con una función MySQL adecuada.

2

Se puede crear una declaración preparada de forma dinámica como this

set @sql = concat('select * from city where city_id in (', 
        (select cities from location where location_id = 3), 
        ')'); 
prepare in_stmt from @sql; 
execute in_stmt; 
deallocate prepare in_stmt; 
Cuestiones relacionadas