Requête MySQL recherche des valeurs dans une chaîne séparée par des virgules


90

J'ai un champ COLORS (varchar(50))dans ma table SHIRTSqui contient une chaîne délimitée par des virgules telle que1,2,5,12,15, . Chaque nombre représentant les couleurs disponibles.

Lors de l'exécution de la requête select * from shirts where colors like '%1%'pour obtenir toutes les chemises rouges (couleur = 1), j'obtiens également les chemises dont la couleur est le gris (= 12) et l'orange (= 15).

Comment dois-je réécrire la requête de manière à sélectionner UNIQUEMENT la couleur 1 et non toutes les couleurs contenant le numéro 1?


6
Vous pouvez le faire via regex, je suppose, mais la bien meilleure solution serait de diviser les couleurs de chemise dans une table séparée (couleurs) et d'utiliser une table de jointure (shirt_colors) en utilisant les identifiants de couleur / chemise pour les lier.
ceejayoz

Je ne peux pas croire avec 6 réponses, aucun d'entre eux n'a mentionné le type de données SET de MySQL ..
ColinM

Réponses:


185

La manière classique serait d'ajouter des virgules à gauche et à droite:

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

Mais find_in_set fonctionne également:

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

J'ai essayé find_in_set mais il renvoie le même résultat quelle que soit la valeur de couleur que j'entre ... Des suggestions?
bikey77

@ bikey77: C'est peut-être le problème, dit la documentation : Cette fonction ne fonctionne pas correctement si le premier argument contient une virgule («,»).
Andomar

Mon tort, c'était une erreur logique due aux mêmes valeurs factices. Ça fonctionne bien. Merci!
bikey77

@Andomar Avant de trouver votre réponse, je me débattais avec IN mais le vôtre est un travail comme un charme ... Merci beaucoup ..
PHP Mentor

2
Cela a un impact sur les performances car Find_in_set n'utilise pas d'index
Kamran Shahid


23

Jetez un œil à la fonction FIND_IN_SET pour MySQL.

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

1
Attention: find in set n'utilise pas d'index sur la table.
edigu

11

Cela fonctionnera à coup sûr, et je l'ai essayé:

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)

Essaie !!!


Hey @rolandomysqldba, je teste votre requête et cela fonctionne bien mais je dois y apporter des modifications. Disons que si je veux obtenir toutes les chemises dont la valeur de couleur est 1,2 dans la colonne.
Ahsan Saeed le

6

Si l'ensemble de couleurs est plus ou moins fixe, le moyen le plus efficace et le plus lisible serait d'utiliser des constantes de chaîne dans votre application, puis d'utiliser le SETtype de MySQL avec FIND_IN_SET('red',colors)dans vos requêtes. Lors de l'utilisation du SETtype avec FIND_IN_SET , MySQL utilise un entier pour stocker toutes les valeurs et utilise une "and"opération binaire pour vérifier la présence de valeurs, ce qui est bien plus efficace que de scanner une chaîne séparée par des virgules.

Dans SET('red','blue','green'), 'red'serait stocké en interne en tant que 1, 'blue'serait stocké en interne en tant que 2et 'green'serait stocké en interne en tant que 4. La valeur 'red,blue'serait stockée sous la forme 3( 1|2) et 'red,green'sous la forme 5( 1|4).



3

Vous devez en fait corriger le schéma de votre base de données de sorte que vous ayez trois tables:

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

Ensuite, si vous voulez trouver toutes les chemises rouges, vous feriez une requête comme:

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: Ce n'est vrai que si vous supposez que l'OP a des droits d'édition sur le schéma de la base de données; a le temps de repenser la base de données, de migrer les données et de refactoriser tous les clients; et que la réduction de la complexité de cette requête l'emporte sur l'augmentation de la complexité du reste de l'application.
Andomar

1
@Andomar, puis à nouveau quand il rencontrera des restrictions de taille pour les récupérations de lignes et que ses "enregistrements" seront coupés, CELA est là que le vrai plaisir commencera!
Blindy

3
@Blindy: Vous manquez le point; Je ne dis pas qu'il a la meilleure solution, juste que tout le monde n'a pas la liberté de repenser son environnement à son goût
Andomar

Je suis d'accord avec @Andomar
Adam B


0

1. Pour MySQL:

SELECT FIND_IN_SET(5, columnname) AS result 
FROM table

2.Pour Postgres SQL:

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

Exemple

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

0

Vous pouvez y parvenir en suivant la fonction.

Exécutez la requête suivante pour créer la fonction.

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

Et appelez cette fonction comme ça

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

-7

Toutes les réponses ne sont pas vraiment correctes, essayez ceci:

select * from shirts where 1 IN (colors);
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.