Consulta MySQL encontrando valores em uma string separada por vírgulas


90

Eu tenho um campo COLORS (varchar(50))em uma minha tabela SHIRTSque contém uma string delimitada por vírgulas, como 1,2,5,12,15,. Cada número representa as cores disponíveis.

Ao executar a consulta select * from shirts where colors like '%1%'para obter todas as camisas vermelhas (cor = 1), também obtenho as camisas cuja cor é cinza (= 12) e laranja (= 15).

Como devo reescrever a consulta para que selecione APENAS a cor 1 e não todas as cores que contenham o número 1?


6
Você poderia fazer isso via regex, suponho, mas a solução muito melhor seria dividir as cores da camisa em uma tabela separada (cores) e usar uma tabela de junção (shirt_colors) usando os ids de cor / camisa para vinculá-las.
ceejayoz

Não consigo acreditar com 6 respostas, nenhum deles mencionou o tipo de dados SET do MySQL.
ColinM

Respostas:


185

A maneira clássica seria adicionar vírgulas à esquerda e à direita:

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

Mas find_in_set também funciona:

select * from shirts where find_in_set('1',colors) <> 0

Tentei find_in_set, mas ele retorna o mesmo resultado, não importa o valor da cor que eu inserir ... Alguma sugestão?
bikey77

@ bikey77: Talvez seja este o problema, a documentação diz: Esta função não funciona corretamente se o primeiro argumento contiver uma vírgula (“,”).
Andomar

Erro meu, foi um erro lógico devido aos mesmos valores fictícios. Funciona bem. Obrigado!
bikey77

@Andomar Antes de encontrar sua resposta, eu estava tendo problemas com o IN, mas o seu funciona como um encanto ... Muito obrigado ..
PHP Mentor

2
Tem um impacto no desempenho, pois Find_in_set não usa o índice
Kamran Shahid


23

Dê uma olhada na função FIND_IN_SET para MySQL.

SELECT * 
    FROM shirts 
    WHERE FIND_IN_SET('1',colors) > 0

1
Cuidado: o find in set não usa índices na tabela.
edigu

11

Isso vai funcionar com certeza, e eu realmente tentei:

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)

De uma chance !!!


Olá @rolandomysqldba, eu testo sua consulta e funciona bem, mas preciso fazer algumas alterações. Digamos que eu queira obter todas as camisas cujo valor de cor é 1,2 na coluna.
Ahsan Saeed

6

Se o conjunto de cores for mais ou menos fixo, a maneira mais eficiente e também mais legível seria usar constantes de string em seu aplicativo e, em seguida, usar o SETtipo do MySQL FIND_IN_SET('red',colors)em suas consultas. Ao usar o SETtipo com FIND_IN_SET , o MySQL usa um inteiro para armazenar todos os valores e usa a "and"operação binária para verificar a presença de valores, o que é muito mais eficiente do que escanear uma string separada por vírgulas.

Em SET('red','blue','green'), 'red'seria armazenado internamente como 1, 'blue'seria armazenado internamente como 2e 'green'seria armazenado internamente como 4. O valor 'red,blue'seria armazenado como 3( 1|2) e 'red,green'como 5( 1|4).



3

Na verdade, você deve corrigir o esquema do seu banco de dados para que tenha três tabelas:

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

Então, se você quiser encontrar todas as camisas que são vermelhas, faça uma 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

8
@Blindy: Isso só é verdade se você assumir que o OP tem direitos de edição no esquema do banco de dados; tem tempo para redesenhar o banco de dados, migrar os dados e refatorar todos os clientes; e que a redução da complexidade dessa consulta supera o aumento da complexidade no restante do aplicativo.
Andomar

1
@Andomar, então novamente quando ele se deparar com restrições de tamanho para recuperação de linhas e seus "registros" forem cortados, AQUI será quando a verdadeira diversão começará!
Blindy

3
@Blindy: Você está perdendo o ponto; Não estou argumentando que ele tem a melhor solução, apenas que nem todo mundo tem a liberdade de redesenhar seu ambiente de acordo com sua preferência
Andomar

Eu concordo com @Andomar
Adam B

3
select * from shirts where find_in_set('1',colors) <> 0

Funciona para mim


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, ','))

Exemplo

select * 
from customer f
where '11' = ANY (string_to_array(customerids, ','))

0

Você pode conseguir isso seguindo a função.

Execute a consulta a seguir para criar a função.

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)        
);

E chame esta função assim

msyql> select TOTAL_OCCURANCE('A,B,C,A,D,X,B,AB', 'A');

-7

Todas as respostas não estão realmente corretas, tente isto:

select * from shirts where 1 IN (colors);
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.