Mysql
 sql >> Base de données >  >> RDS >> Mysql

Comment utiliser FIND_IN_SET en utilisant la liste de données

Pensez tout d'abord à stocker les données de manière normalisée. Voici une bonne lecture :Le stockage d'une liste délimitée dans une colonne de base de données est-il vraiment si mauvais ?

Maintenant - En supposant le schéma et les données suivants :

create table products (
  id int auto_increment,
  upc varchar(50),
  upc_variation text,
  primary key (id),
  index (upc)
);
insert into products (upc, upc_variation) values
  ('01234', '01234,12345,23456'),
  ('56789', '45678,34567'),
  ('056789', '045678,034567');

Nous voulons trouver des produits avec des variantes '12345' et '34567' . Le résultat attendu est la 1ère et la 2ème lignes.

Schéma normalisé - relation plusieurs à plusieurs

Au lieu de stocker les valeurs dans une liste séparée par des virgules, créez un nouveau tableau, qui mappe les ID de produit avec les variantes :

create table products_upc_variations (
  product_id int,
  upc_variation varchar(50),
  primary key (product_id, upc_variation),
  index  (upc_variation, product_id)
);
insert into products_upc_variations (product_id, upc_variation) values 
  (1, '01234'),
  (1, '12345'),
  (1, '23456'),
  (2, '45678'),
  (2, '34567'),
  (3, '045678'),
  (3, '034567');

La requête de sélection serait :

select distinct p.*
from products p
join products_upc_variations v on v.product_id = p.id
where v.upc_variation in ('12345', '34567');

Comme vous le voyez - Avec un schéma normalisé, le problème peut être résolu avec une requête assez basique. Et nous pouvons utiliser efficacement les indices.

"Exploiter" un INDEX FULLTEXT

Avec un INDEX FULLTEXT sur (upc_variation) vous pouvez utiliser :

select p.*
from products p
where match (upc_variation) against ('12345 34567');

Cela a l'air assez "joli" et est probablement efficace. Mais même si cela fonctionne pour cet exemple, je ne me sentirais pas à l'aise avec cette solution, car je ne peux pas dire exactement quand cela ne fonctionne pas.

Utilisation de JSON_OVERLAPS()

Depuis MySQL 8.0.17, vous pouvez utiliser JSON_OVERLAPS() . Vous devez soit stocker les valeurs sous forme de tableau JSON, soit convertir la liste en JSON "à la volée" :

select p.*
from products p
where json_overlaps(
  '["12345","34567"]',
  concat('["', replace(upc_variation, ',', '","'), '"]')
);

Aucun index ne peut être utilisé pour cela. Mais ni l'un ni l'autre ne le peut pour FIND_IN_SET() .

Utiliser JSON_TABLE()

Depuis MySQL 8.0.4, vous pouvez utiliser JSON_TABLE() pour générer une représentation normalisée des données "à la volée". Là encore, vous devez soit stocker les données dans un tableau JSON, soit convertir la liste en JSON dans la requête :

select distinct p.*
from products p
join json_table(
  concat('["', replace(p.upc_variation, ',', '","'), '"]'),
  '$[*]' columns (upcv text path '$')
) v
where v.upcv in ('12345', '34567');

Aucun index ne peut être utilisé ici. Et c'est probablement la solution la plus lente de toutes présentées dans cette réponse.

LIKE / REGEXP

Vous pouvez également utiliser une expression régulière :

select p.*
from products p
where p.upc_variation rlike '(^|,)(12345|34567)(,|$)'

Voir démo de toutes les requêtes sur dbfiddle.uk