web-dev-qa-db-fra.com

MySQL: diviser la valeur en colonne pour obtenir plusieurs lignes

I have some data in a table like so:
product_id      categories
10              9,12
11              8
12              11,18,5

I want a select statement that would produce this output:

product_id      category_id
10              9
10              12
11              8
12              11
12              18
12              5

Je ne sais pas comment formuler ce scénario pour pouvoir le rechercher sur Google.

5
Sparctus

Ce que vous recherchez est l'inverse d'une requête d'agrégation GROUP BY utilisant GROUP_CONCAT. Si vous êtes prêt à stocker les résultats dans une table temporaire, j'ai exactement ce qu'il vous faut.

Tout d'abord, voici le code pour utiliser vos exemples de données dans une table appelée prod et une table temporaire appelée prodcat pour contenir les résultats que vous recherchez.

use test
drop table if exists prod;
drop table if exists prodcat;
create table prod
(
  product_id int not null,
  categories varchar(255)
) engine=MyISAM;
create table prodcat
(
  product_id int not null,
  cat  int not null
) engine=MyISAM;
insert into prod values
(10,'9,12'),(11,'8'),(12,'11,18,5');
select * from prod;

Ici, il est chargé

mysql> use test
Database changed
mysql> drop table if exists prod;
Query OK, 0 rows affected (0.00 sec)

mysql> drop table if exists prodcat;
Query OK, 0 rows affected (0.00 sec)

mysql> create table prod
    -> (
    ->   product_id int not null,
    ->   categories varchar(255)
    -> ) engine=MyISAM;
Query OK, 0 rows affected (0.07 sec)

mysql> create table prodcat
    -> (
    ->   product_id int not null,
    ->   cat  int not null
    -> ) engine=MyISAM;
Query OK, 0 rows affected (0.06 sec)

mysql> insert into prod values
    -> (10,'9,12'),(11,'8'),(12,'11,18,5');
Query OK, 3 rows affected (0.00 sec)
Records: 3  Duplicates: 0  Warnings: 0

mysql> select * from prod;
+------------+------------+
| product_id | categories |
+------------+------------+
|         10 | 9,12       |
|         11 | 8          |
|         12 | 11,18,5    |
+------------+------------+
3 rows in set (0.00 sec)

mysql>

OK, vous avez besoin d'une requête pour assembler chaque product_id avec chaque catégorie. C'est ici:

select concat('insert into prodcat select ',product_id,',cat from (select NULL cat union select ',
replace(categories,',',' union select '),') A where cat IS NOT NULL;') ProdCatQueries from prod;

Ici, il est exécuté

mysql> select concat('insert into prodcat select ',product_id,',cat from (select NULL cat union select ',
    -> replace(categories,',',' union select '),') A where cat IS NOT NULL;') ProdCatQueries from prod;
+----------------------------------------------------------------------------------------------------------------------------------+
| ProdCatQueries                                                                                                                   |
+----------------------------------------------------------------------------------------------------------------------------------+
| insert into prodcat select 10,cat from (select NULL cat union select 9 union select 12) A where cat IS NOT NULL;                 |
| insert into prodcat select 11,cat from (select NULL cat union select 8) A where cat IS NOT NULL;                                 |
| insert into prodcat select 12,cat from (select NULL cat union select 11 union select 18 union select 5) A where cat IS NOT NULL; |
+----------------------------------------------------------------------------------------------------------------------------------+
3 rows in set (0.00 sec)

mysql>

Laisse-moi exécuter chaque ligne à la main

mysql> insert into prodcat select 10,cat from (select NULL cat union select 9 union select 12) A where cat IS NOT NULL;
Query OK, 2 rows affected (0.07 sec)
Records: 2  Duplicates: 0  Warnings: 0

mysql> insert into prodcat select 11,cat from (select NULL cat union select 8) A where cat IS NOT NULL;
Query OK, 1 row affected (0.00 sec)
Records: 1  Duplicates: 0  Warnings: 0

mysql> insert into prodcat select 12,cat from (select NULL cat union select 11 union select 18 union select 5) A where cat IS NOT NULL;
Query OK, 3 rows affected (0.00 sec)
Records: 3  Duplicates: 0  Warnings: 0

mysql>

OK bien. Les requêtes fonctionnent. La table prodcat s'est-elle correctement remplie?

mysql> select * from prodcat;
+------------+-----+
| product_id | cat |
+------------+-----+
|         10 |   9 |
|         10 |  12 |
|         11 |   8 |
|         12 |  11 |
|         12 |  18 |
|         12 |   5 |
+------------+-----+
6 rows in set (0.00 sec)

mysql>

OK super. Il a les données.

Pour être honnête, je pense que SQL Server peut effectuer tout cela dans une seule requête pivot sans table temporaire à la main.

J'aurais pu l'amener à un autre niveau et concaténer toutes les requêtes en une seule requête, mais le SQL aurait été incroyablement long. Si votre requête réelle avait des milliers de lignes, un seul MySQL n'aurait pas été pratique.

Au lieu d'exécuter manuellement les 3 requêtes INSERT, vous pouvez répercuter les 3 requêtes INSERT dans un fichier texte et l'exécuter en tant que script. Ensuite, vous avez un tableau avec les combinaisons de produits et catégories écrites individuellement.

5
RolandoMySQLDBA

Il n'y a pas de supercherie MySQL intégrée en soi, mais vous pouvez stocker une procédure personnalisée qui atteindra votre objectif de manière intelligente. En supposant que votre table products a les colonnes product_id, categories et le nouveau category_id:

DELIMITER $$
    CREATE FUNCTION SPLIT_STRING(val TEXT, delim VARCHAR(12), pos INT) RETURNS TEXT
    BEGIN
        DECLARE output TEXT;
        SET output = REPLACE(SUBSTRING(SUBSTRING_INDEX(val, delim, pos), CHAR_LENGTH(SUBSTRING_INDEX(val, delim, pos - 1)) + 1), delim, '');
        IF output = '' THEN
            SET output = null;
        END IF;
        RETURN output;
    END $$

    CREATE PROCEDURE TRANSFER_CELL()
    BEGIN
        DECLARE i INTEGER;
        SET i = 1;
        REPEAT
            INSERT INTO products (product_id, category_id)
            SELECT product_id, SPLIT_STRING(categories, ',', i)
            FROM products
            WHERE SPLIT_STRING(categories, ',', i) IS NOT NULL;
            SET i = i + 1;
        UNTIL ROW_COUNT() = 0
        END REPEAT;
    END $$
DELIMITER ;

CALL TRANSFER_CELL() ;

Ensuite, je supprimerais toutes les lignes WHERE categories NOT NULL.

2
Matt Hagemann