Consulta de MySQL que busca valores en una cadena separada por comas

90

Tengo un campo COLORS (varchar(50))en mi tabla SHIRTSque contiene una cadena delimitada por comas como 1,2,5,12,15,. Cada número representa los colores disponibles.

Al ejecutar la consulta select * from shirts where colors like '%1%'para obtener todas las camisetas rojas (color = 1), también obtengo las camisetas cuyo color es gris (= 12) y naranja (= 15).

¿Cómo debo reescribir la consulta para que seleccione SOLO el color 1 y no todos los colores que contienen el número 1?

bikey77
fuente
6
Podría hacer esto a través de expresiones regulares, supongo, pero la solución mucho mejor sería dividir los colores de la camisa en una tabla separada (colores) y usar una tabla de unión (shirt_colors) usando los identificadores de color / camisa para vincularlos.
ceejayoz
No puedo creer que con 6 respuestas ninguno de ellos mencionó el tipo de datos SET de MySQL ..
ColinM
1
verifique esto: stackoverflow.com/questions/12559876/…
Alireza

Respuestas:

185

La forma clásica sería agregar comas a la izquierda y a la derecha:

select * from shirts where CONCAT(',', colors, ',') like '%,1,%'

Pero find_in_set también funciona:

select * from shirts where find_in_set('1',colors) <> 0
Andomar
fuente
Intenté find_in_set pero devuelve el mismo resultado sin importar el valor de color que ingrese ... ¿Alguna sugerencia?
bikey77
@ bikey77: Tal vez este sea el problema, dice la documentación : Esta función no funciona correctamente si el primer argumento contiene un carácter de coma (“,”).
Andomar
Me equivoqué, fue un error lógico debido a los mismos valores ficticios. Funciona bien. ¡Gracias!
bikey77
@Andomar Antes de encontrar tu respuesta, no estaba luchando con IN, pero el tuyo funciona como un encanto ... Muchas gracias ..
Mentor de PHP
2
Tiene un impacto en el rendimiento ya que Find_in_set no usa el índice
Kamran Shahid
30

FIND_IN_SET es tu amigo en este caso

select * from shirts where FIND_IN_SET(1,colors) 
Shakti Singh
fuente
4
find_in_set es demasiado lento para tablas grandes
Jeff_Alieffson
23

Eche un vistazo a la función FIND_IN_SET para MySQL.

SELECT * 
    FROM shirts 
    WHERE FIND_IN_SET('1',colors) > 0
Joe Stefanelli
fuente
1
Cuidado: buscar en el conjunto no usa índices en la tabla.
edigu
11

Esto funcionará con seguridad, y de hecho lo probé:

lwdba@localhost (DB test) :: DROP TABLE IF EXISTS shirts;
Query OK, 0 rows affected (0.08 sec)

lwdba@localhost (DB test) :: CREATE TABLE shirts
    -> (<BR>
    -> id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    -> ticketnumber INT,
    -> colors VARCHAR(30)
    -> );<BR>
Query OK, 0 rows affected (0.19 sec)

lwdba@localhost (DB test) :: INSERT INTO shirts (ticketnumber,colors) VALUES
    -> (32423,'1,2,5,12,15'),
    -> (32424,'1,5,12,15,30'),
    -> (32425,'2,5,11,15,28'),
    -> (32426,'1,2,7,12,15'),
    -> (32427,'2,4,8,12,15');
Query OK, 5 rows affected (0.06 sec)
Records: 5  Duplicates: 0  Warnings: 0

lwdba@localhost (DB test) :: SELECT * FROM shirts WHERE LOCATE(CONCAT(',', 1 ,','),CONCAT(',',colors,',')) > 0;
+----+--------------+--------------+
| id | ticketnumber | colors       |
+----+--------------+--------------+
|  1 |        32423 | 1,2,5,12,15  |
|  2 |        32424 | 1,5,12,15,30 |
|  4 |        32426 | 1,2,7,12,15  |
+----+--------------+--------------+
3 rows in set (0.00 sec)

Darle una oportunidad !!!

RolandoMySQLDBA
fuente
Hola @rolandomysqldba, pruebo tu consulta y funciona bien, pero necesito hacer algunos cambios. Digamos si quiero obtener todas las camisetas cuyo valor de color es 1,2 en la columna.
Ahsan Saeed
6

Si el conjunto de colores es más o menos fijo, la forma más eficiente y también más legible sería usar constantes de cadena en su aplicación y luego usar el SETtipo de MySQL FIND_IN_SET('red',colors)en sus consultas. Cuando se usa el SETtipo con FIND_IN_SET , MySQL usa un entero para almacenar todos los valores y usa la "and"operación binaria para verificar la presencia de valores, lo cual es mucho más eficiente que escanear una cadena separada por comas.

En SET('red','blue','green'), 'red'se almacenaría internamente como 1, 'blue'se almacenaría internamente como 2y 'green'se almacenaría internamente como 4. El valor 'red,blue'se almacenaría como 3( 1|2) y 'red,green'como 5( 1|4).

ColinM
fuente
3

En realidad, debería arreglar el esquema de su base de datos para tener tres tablas:

shirt: shirt_id, shirt_name
color: color_id, color_name
shirtcolor: shirt_id, color_id

Luego, si desea encontrar todas las camisetas que son rojas, haría una consulta como:

SELECT *
FROM shirt, color
WHERE color.color_name = 'red'
  AND shirt.shirt_id = shirtcolor.shirt_id
  AND color.color_id = shirtcolor.color_id
CanSpice
fuente
8
@Blindy: Eso solo es cierto si asume que el OP tiene derechos de edición en el esquema de la base de datos; tiene tiempo para rediseñar la base de datos, migrar los datos y refactorizar todos los clientes; y que la reducción de la complejidad de esta consulta supera el aumento de complejidad en el resto de la aplicación.
Andomar
1
@Andomar, entonces nuevamente cuando se encuentre con restricciones de tamaño para la recuperación de filas y sus "registros" se recorten, ¡ESO es cuando comenzará la verdadera diversión!
Blindy
3
@Blindy: Estás perdiendo el punto; No estoy argumentando que él tiene la mejor solución, solo que no todos tienen la libertad de rediseñar su entorno a su gusto
Andomar
Estoy de acuerdo con @Andomar
Adam B
3
select * from shirts where find_in_set('1',colors) <> 0

Funciona para mi

Deepak Bhatta
fuente
0

1. Para MySQL:

SELECT FIND_IN_SET(5, columnname) AS result 
FROM table

2.Para Postgres SQL:

SELECT * 
FROM TABLENAME f
WHERE 'searchvalue' = ANY (string_to_array(COLUMNNAME, ','))

Ejemplo

select * 
from customer f
where '11' = ANY (string_to_array(customerids, ','))
Saranga kapilarathna
fuente
0

Puede lograr esto siguiendo la función.

Ejecute la siguiente consulta para crear la función.

DELIMITER ||
CREATE FUNCTION `TOTAL_OCCURANCE`(`commastring` TEXT, `findme`     VARCHAR(255)) RETURNS int(11)
NO SQL
-- SANI: First param is for comma separated string and 2nd for string to find.
return ROUND (   
    (
        LENGTH(commastring)
        - LENGTH( REPLACE ( commastring, findme, "") ) 
    ) / LENGTH(findme)        
);

Y llama a esta función así

msyql> select TOTAL_OCCURANCE('A,B,C,A,D,X,B,AB', 'A');
Delicado
fuente
-7

Todas las respuestas no son realmente correctas, intente esto:

select * from shirts where 1 IN (colors);
Reincidente
fuente