按枚举分组计数,包括可能计数为0的枚举值

vou*_*rus 2 mysql mariadb

我有一张桌子。字段之一是类别(由枚举表示)。有些类别的商品为零。

所以我这样做:

select category, count(*) as total from items group by category;
+------------+-------+
| category   | total |
+------------+-------+
| one        |  6675 |
+------------+-------+
Run Code Online (Sandbox Code Playgroud)

我想生成一个这样的表(其中两个是另一个可能的枚举值):

+------------+-------+
| category   | total |
+------------+-------+
| one        |  6675 |
+------------+-------+
| two        |  0    |
+------------+-------+
Run Code Online (Sandbox Code Playgroud)

如何使用mysql SQL查询执行此操作?

Mad*_*iya 5

对于那些可能的选项(值)不太多(首选<= 10)并且将来您将不会添加新选项的情况(通常不会很频繁),通常首选Enum数据类型。因此,Enum的一个好用例是性别:(m, f, n)。在您的情况下,最好是拥有所有可能类别的主表,而不是对它们使用Enum。然后,可以更容易地LEFT JOIN从Master表中进行操作。

但是,按照您的要求:

一个解决方案使用枚举类型生成表,并包含0个条目

适用于所有MySQL / MariaDB版本

我们将需要从中获取所有可能的Enum值的列表INFORMATION_SCHEMA.COLUMNS

SELECT
   SUBSTRING(COLUMN_TYPE, 6, CHAR_LENGTH(COLUMN_TYPE) - 6) AS enum_values
FROM
    information_schema.COLUMNS
WHERE
    TABLE_NAME = 'items'        -- your table name
AND
    COLUMN_NAME = 'category'    -- name of the column
AND 
    TABLE_SCHEMA = 'your_db'    -- name of the database (schema)
Run Code Online (Sandbox Code Playgroud)

但是,此查询将为您提供所有用逗号分隔的字符串的枚举值,如下所示:

'one','two','three','four'

现在,我们需要将该字符串转换为行。为此,我们可以使用“序列(数字系列)”表。您可以在数据库中定义一个永久表,该表存储从1到100的整数(在许多其他情况下您可能会发现此表也很有用)(或者,另一种方法是使用派生表 -选中此可获得一个想法:https ://stackoverflow.com/a/58052199/2469308)。

CREATE TABLE seq (n tinyint(3) UNSIGNED NOT NULL, PRIMARY KEY(n));
INSERT INTO seq (n) VALUES (1), (2), ...... , (99), (100);
Run Code Online (Sandbox Code Playgroud)

现在,我们将seq基于逗号的位置在“枚举值字符串”和表之间进行JOIN操作,以将枚举值提取到不同的行中。请注意,,我们将使用','(避免在值字符串中可能出现逗号的情况),而不只是使用(逗号)来提取枚举值。字符串操作利用所Substring_Index()Trim()Char_Length()等功能可用于提取的枚举值。您可以检查以下答案以获得有关此技术的一般想法:

模式(在DB Fiddle上查看

CREATE TABLE items 
(id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
 category ENUM('one','two','three','four'), 
 item_id INT UNSIGNED) ENGINE=InnoDB;

INSERT INTO items (category, item_id) 
VALUES ('one', 1), 
       ('two', 2), 
       ('one', 2), 
       ('one', 3);

CREATE TABLE seq (n tinyint(3) UNSIGNED NOT NULL, 
                  PRIMARY KEY(n));
INSERT INTO seq (n) VALUES (1),(2),(3),(4),(5);
Run Code Online (Sandbox Code Playgroud)

查询#1

SELECT Trim(BOTH '\'' FROM Substring_index(Substring_index(e.enum_values,
                                           '\',\'',
                                                  seq.n),
                                  '\',\'', -1)) AS cat
FROM   (SELECT Substring(column_type, 6, Char_length(column_type) - 6) AS
               enum_values
        FROM   information_schema.columns
        WHERE  table_name = 'items'
               AND column_name = 'category'
               AND table_schema = 'test') AS e
       JOIN seq
         ON ( Char_length(e.enum_values) - Char_length(REPLACE(e.enum_values,
                                                       '\',\'',
                                                       ''))
            ) / 3 >= seq.n - 1

| cat   |
| ----- |
| one   |
| two   |
| three |
| four  |
Run Code Online (Sandbox Code Playgroud)

现在,困难的部分已经完成。我们需要做的就是LEFT JOIN从此子查询(具有所有类别枚举值)到您的items表,以获取每个类别的计数。

最终查询如下(在DB Fiddle上查看):

SELECT all_cat.cat                   AS category,
       Count(i.item_id) AS total
FROM   (SELECT Trim(BOTH '\'' FROM Substring_index(
                                   Substring_index(e.enum_values,
                                          '\',\'',
                                                  seq.n),
                                                  '\',\'', -1)) AS cat
        FROM   (SELECT Substring(column_type, 6, Char_length(column_type) - 6)
                       AS
                       enum_values
                FROM   information_schema.columns
                WHERE  table_name = 'items'
                       AND column_name = 'category'
                       AND table_schema = 'test') AS e
               JOIN seq
                 ON ( Char_length(e.enum_values) - Char_length(
                                                   REPLACE(e.enum_values,
                                                   '\',\'',
                                                   ''))
                    ) / 3 >= seq.n - 1) AS all_cat
       LEFT JOIN items AS i
              ON i.category = all_cat.cat
GROUP  BY all_cat.cat
ORDER  BY total DESC;
Run Code Online (Sandbox Code Playgroud)

结果

| category | total |
| -------- | ----- |
| one      | 3     |
| two      | 1     |
| three    | 0     |
| four     | 0     |
Run Code Online (Sandbox Code Playgroud)