sql >> Base de Datos >  >> RDS >> Mysql

Cómo usar FIND_IN_SET usando una lista de datos

En primer lugar, considere almacenar los datos de forma normalizada. Aquí hay una buena lectura:¿Es realmente tan malo almacenar una lista delimitada en una columna de la base de datos?

Ahora, asumiendo el siguiente esquema y datos:

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

Queremos encontrar productos con variaciones '12345' y '34567' . El resultado esperado es la primera y la segunda fila.

Esquema normalizado:relación de muchos a muchos

En lugar de almacenar los valores en una lista separada por comas, cree una nueva tabla que mapee los ID de productos con variaciones:

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 consulta de selección sería:

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

Como puede ver, con un esquema normalizado, el problema se puede resolver con una consulta bastante básica. Y podemos usar índices de manera efectiva.

"Explotación" de un ÍNDICE DE TEXTO COMPLETO

Con un ÍNDICE DE TEXTO COMPLETO en (upc_variation) puedes usar:

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

Esto se ve bastante "bonito" y probablemente sea eficiente. Pero aunque funciona para este ejemplo, no me sentiría cómodo con esta solución, porque no puedo decir exactamente cuándo no funciona.

Uso de JSON_OVERLAPS()

Desde MySQL 8.0.17 puede usar JSON_OVERLAPS() . Debe almacenar los valores como una matriz JSON o convertir la lista a JSON "sobre la marcha":

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

No se puede utilizar ningún índice para esto. Pero tampoco puede para FIND_IN_SET() .

Usando JSON_TABLE()

Desde MySQL 8.0.4 puede usar JSON_TABLE() para generar una representación normalizada de los datos "sobre la marcha". Aquí nuevamente, almacenaría los datos en una matriz JSON o convertiría la lista a JSON en la consulta:

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

Aquí no se puede utilizar ningún índice. Y esta es probablemente la solución más lenta de todas las presentadas en esta respuesta.

ME GUSTA/REGEXP

También puede usar una expresión regular :

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

Consulte la demostración de todas las consultas en dbfiddle.uk