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?

bikey77
fonte
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
1
verifique isto: stackoverflow.com/questions/12559876/…
Alireza

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
Andomar
fonte
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
30

FIND_IN_SET é seu amigo neste caso

select * from shirts where FIND_IN_SET(1,colors) 
Shakti Singh
fonte
4
find_in_set é muito lento para tabelas grandes
Jeff_Alieffson
23

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

SELECT * 
    FROM shirts 
    WHERE FIND_IN_SET('1',colors) > 0
Joe Stefanelli
fonte
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 !!!

RolandoMySQLDBA
fonte
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).

ColinM
fonte
3

Se você estiver usando MySQL, existe um método REGEXP que você pode usar ...

http://dev.mysql.com/doc/refman/5.1/en/regexp.html#operator_regexp

Então você usaria:

SELECT * FROM `shirts` WHERE `colors` REGEXP '\b1\b'
KOGI
fonte
Não foi possível descobrir isso, embora eu tenha certeza que é minha culpa. Obrigado um mil companheiro.
bikey77 de
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
CanSpice
fonte
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

Deepak Bhatta
fonte
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, ','))
Saranga kapilarathna
fonte
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');
Delickate
fonte
-7

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

select * from shirts where 1 IN (colors);
Apóstata
fonte