Query MySQL che trova valori in una stringa separata da virgole


90

Ho un campo COLORS (varchar(50))in una mia tabella SHIRTSche contiene una stringa delimitata da virgole come 1,2,5,12,15,. Ogni numero rappresenta i colori disponibili.

Quando eseguo la query select * from shirts where colors like '%1%'per ottenere tutte le camicie rosse (color = 1), ottengo anche le camicie il cui colore è grigio (= 12) e arancione (= 15).

Come devo riscrivere la query in modo che selezioni SOLO il colore 1 e non tutti i colori contenenti il ​​numero 1?


6
Potresti farlo tramite regex, suppongo, ma la soluzione migliore sarebbe quella di rompere i colori della maglietta in una tabella separata (colori) e utilizzare una tabella di unione (shirt_colors) usando gli id ​​di color / shirt per collegarli.
ceejayoz

Non riesco a credere che con 6 risposte nessuna di esse menzionasse il tipo di dati SET di MySQL ..
ColinM

Risposte:


185

Il modo classico sarebbe aggiungere virgole a sinistra ea destra:

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

Ma find_in_set funziona anche:

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

Ho provato find_in_set ma restituisce lo stesso risultato indipendentemente dal valore del colore che inserisco ... Qualche suggerimento?
bikey77

@ bikey77: Forse questo è il problema, la documentazione dice: Questa funzione non funziona correttamente se il primo argomento contiene una virgola (",").
Andomar

Sbaglio, è stato un errore logico dovuto agli stessi valori fittizi. Funziona bene. Grazie!
bikey77

@Andomar Prima di trovare la tua risposta stavo lottando con IN ma il tuo funziona come un incantesimo ... Grazie mille ..
PHP Mentor

2
Ha un impatto sulle prestazioni poiché Find_in_set non utilizza l'indice
Kamran Shahid,


23

Dai un'occhiata alla funzione FIND_IN_SET per MySQL.

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

1
Attenzione: find in set non utilizza gli indici sulla tabella.
edigu

11

Funzionerà di sicuro, e in realtà l'ho provato:

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)

Provaci !!!


Ehi @rolandomysqldba, ho testato la tua query e funziona bene, ma ho bisogno di apportare alcune modifiche. Diciamo se voglio ottenere tutte le camicie in cui il valore del colore è 1,2 nella colonna.
Ahsan Saeed

6

Se il set di colori è più o meno fisso, il modo più efficiente e anche più leggibile sarebbe quello di utilizzare le costanti di stringa nella tua app e quindi utilizzare il SETtipo di MySQL con FIND_IN_SET('red',colors)nelle tue query. Quando si utilizza il SETtipo con FIND_IN_SET , MySQL utilizza un intero per memorizzare tutti i valori e utilizza "and"operazioni binarie per verificare la presenza di valori, il che è molto più efficiente della scansione di una stringa separata da virgole.

In SET('red','blue','green'), 'red'sarebbe archiviato internamente come 1, 'blue'sarebbe archiviato internamente come 2e 'green'sarebbe archiviato internamente come 4. Il valore 'red,blue'verrebbe memorizzato come 3( 1|2) e 'red,green'come 5( 1|4).



3

Dovresti effettivamente correggere lo schema del tuo database in modo da avere tre tabelle:

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

Quindi, se vuoi trovare tutte le magliette rosse, devi fare una query come:

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: Questo è vero solo se si presume che l'OP abbia i diritti di modifica sullo schema del database; ha il tempo di riprogettare il database, migrare i dati e rifattorizzare tutti i client; e che la riduzione della complessità per questa query supera l'aumento della complessità nel resto dell'applicazione.
Andomar

1
@Andomar, poi di nuovo quando si imbatterà in restrizioni di dimensione per il recupero delle righe e i suoi "record" verranno ritagliati, QUESTO è quando inizierà il vero divertimento!
Blindy

3
@ Blindy: ti stai perdendo il punto; Non sto sostenendo che abbia la soluzione migliore, solo che non tutti hanno la libertà di ridisegnare il proprio ambiente a proprio piacimento
Andomar

Sono d'accordo con @Andomar
Adam B


0

1. Per MySQL:

SELECT FIND_IN_SET(5, columnname) AS result 
FROM table

2.Per Postgres SQL:

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

Esempio

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

0

È possibile ottenere ciò seguendo la funzione.

Eseguire la seguente query per creare la funzione.

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 chiama questa funzione in questo modo

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

-7

Tutte le risposte non sono proprio corrette, prova questo:

select * from shirts where 1 IN (colors);
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.