【问题标题】:Mode of each column in MySQL (without explicitly writing column names)MySQL中每列的模式(不显式写列名)
【发布时间】:2022-01-16 01:33:51
【问题描述】:

我需要编写一个 MySQL 查询,它单独返回每列模式的出现次数,而不显式指定列名。

假设数据是:

| apples | bananas | oranges |
|   4    |    4    |    3    |
|   2    |    2    |    1    |
|   4    |    3    |    5    |
|   3    |    3    |    5    |
|   4    |    1    |    5    |

我要找的结果是:

| mode | count |
|  4   |   3   |
|  3   |   2   |
|  5   |   3   |

获取单个列的模式(苹果):

SELECT apples AS mode, COUNT(*) AS Count 
FROM tablename 
GROUP BY apples
HAVING COUNT(*) >= ALL (SELECT COUNT(*) FROM tablename GROUP BY apples);

要返回列名,我可以执行以下操作:

SELECT COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'tablename'

效率低下且使用明确的列名,我可以使用 UNION 实现结果:

SELECT apples AS mode, COUNT(*) AS Count 
FROM tablename 
GROUP BY apples
HAVING COUNT(*) >= ALL (SELECT COUNT(*) FROM tablename GROUP BY apples)
UNION 
SELECT bananas AS mode, COUNT(*) AS Count 
FROM tablename 
GROUP BY bananas 
HAVING COUNT(*) >= ALL (SELECT COUNT(*) FROM tablename GROUP BY bananas)
UNION 
SELECT oranges AS mode, COUNT(*) AS Count 
FROM tablename 
GROUP BY oranges
HAVING COUNT(*) >= ALL (SELECT COUNT(*) FROM tablename GROUP BY oranges)

我一直在尝试将这两者与 GROUP BY 和子查询结合起来的一堆不同的查询,但它们一团糟,而且我没有取得太大进展。我还没有生成一个查询来有效地显示每列的模式并明确指定列名(更不用说使用第二个查询返回的列名了)。

例如(我什至不觉得我在正确的轨道上):

SELECT COUNT(*) AS count
FROM tablename
GROUP BY (
    SELECT COLUMN_NAME
        FROM INFORMATION_SCHEMA.COLUMNS
        WHERE TABLE_NAME = 'tablename')
HAVING COUNT(*) >= ALL (SELECT COUNT(*) FROM tablename
                        GROUP BY (
                            SELECT COLUMN_NAME
                                FROM INFORMATION_SCHEMA.COLUMNS
                                WHERE TABLE_NAME = 'tablename'))

谢谢!!

【问题讨论】:

  • 您不能在查询中动态设置列名或表名。您需要动态生成sql查询文本然后执行。
  • 你知道列数吗?你的mysql版本是多少?
  • @ProGu 它必须是动态的,因此可以将相同的查询应用于具有任意列数的任何表。版本是 8.0
  • 从 information_schema 中选择 COLUMN_NAME 返回一个 字符串,而不是一个标识符。 @Shadow 是正确的,在 SQL 中,所有语法和标识符都必须在解析查询时固定。无法进行查询以在执行期间动态更改它引用的列。您必须分两步执行此操作:一是格式化查询,然后执行该查询。
  • 我尝试了很长时间才想出一些 hacky 方法来做到这一点,但结果却一无所获。如果我处于你的位置,我会编写一个小程序,发送一个查询以获取表名列表,然后遍历结果并为每一列发送一个额外的查询。所以结果将是一个包含 N 列的表,您以编程方式运行 N+1 个查询。我知道这不是您想要做的,但我认为根据您的要求,这是最好的方式。

标签: mysql sql


【解决方案1】:

在单个查询中使用纯 SQL 似乎不可能获得预期的结果,但它可能会构建一个如下所示的动态结果:

DROP TABLE IF EXISTS archives.sample_table;
CREATE TABLE archives.sample_table (
  apples INT,
  bananas INT,
  oranges INT
);

INSERT INTO sample_table VALUES 
(   4    ,    4    ,    3    ),
(   2    ,    2    ,    1    ),
(   4    ,    3    ,    5    ),
(   3    ,    3    ,    5    ),
(   4    ,    1    ,    5    )
;

SET SESSION group_concat_max_len = 9999999;
SET @table_name = 'sample_table';
SET @table_schema = 'archives';
WITH RECURSIVE column_info AS (
  SELECT ORDINAL_POSITION column_index, COLUMN_NAME column_name
  FROM INFORMATION_SCHEMA.COLUMNS
  WHERE TABLE_NAME = @table_name and TABLE_SCHEMA = @table_schema
),
query_text AS (
  SELECT i.column_index, CONCAT('SELECT column_name, column_value, counts FROM (  SELECT ''', i.column_name, ''' column_name, `', i.column_name, '` column_value, COUNT(*) counts, RANK() OVER(ORDER BY COUNT(*) DESC) rk  FROM ', @table_name, ' GROUP BY `', i.column_name, '` ) r WHERE rk = 1') single_query
  FROM column_info i
  WHERE i.column_index = 1
  UNION ALL
  SELECT i.column_index, CONCAT('SELECT column_name, column_value, counts FROM (  SELECT ''', i.column_name, ''' column_name, `', i.column_name, '` column_value, COUNT(*) counts, RANK() OVER(ORDER BY COUNT(*) DESC) rk  FROM ', @table_name, ' GROUP BY `', i.column_name, '` ) r WHERE rk = 1') single_query
  FROM query_text prev
  JOIN column_info i ON prev.column_index + 1 = i.column_index
)
SELECT GROUP_CONCAT(single_query SEPARATOR ' UNION ALL ') INTO @stat_query
FROM query_text
;
PREPARE stmt FROM @stat_query
;
EXECUTE stmt
;
DEALLOCATE PREPARE stmt
;

样本输出

mysql> DROP TABLE IF EXISTS archives.sample_table;
Query OK, 0 rows affected (0.03 sec)

mysql> CREATE TABLE archives.sample_table (  apples INT,  bananas INT,  oranges INT);
Query OK, 0 rows affected (0.05 sec)

mysql> INSERT INTO sample_table VALUES (   4    ,    4    ,    3    ),(   2    ,    2    ,    1    ),(   4    ,    3    ,    5    ),(   3    ,    3    ,    5    ),(   4    ,    1    ,    5    );
Query OK, 5 rows affected (0.01 sec)
Records: 5  Duplicates: 0  Warnings: 0

mysql> SET SESSION group_concat_max_len = 9999999;
Query OK, 0 rows affected (0.00 sec)

mysql> SET @table_name = 'sample_table';
Query OK, 0 rows affected (0.00 sec)

mysql> SET @table_schema = 'archives';
Query OK, 0 rows affected (0.00 sec)

mysql> WITH RECURSIVE column_info AS (  SELECT ORDINAL_POSITION column_index, COLUMN_NAME column_name  FROM INFORMATION_SCHEMA.COLUMNS  WHERE TABLE_NAME = @table_name and TABLE_SCHEMA = @table_schema),query_text AS (  SELECT i.column_index, CONCAT('SELECT column_name, column_value, counts FROM (  SELECT ''', i.column_name, ''' column_name, `', i.column_name, '` column_value, COUNT(*) counts, RANK() OVER(ORDER BY COUNT(*) DESC) rk  FROM ', @table_name, ' GROUP BY `', i.column_name, '` ) r WHERE rk = 1') single_query  FROM column_info i  WHERE i.column_index = 1  UNION ALL  SELECT i.column_index, CONCAT('SELECT column_name, column_value, counts FROM (  SELECT ''', i.column_name, ''' column_name, `', i.column_name, '` column_value, COUNT(*) counts, RANK() OVER(ORDER BY COUNT(*) DESC) rk  FROM ', @table_name, ' GROUP BY `', i.column_name, '` ) r WHERE rk = 1') single_query  FROM query_text prev  JOIN column_info i ON prev.column_index + 1 = i.column_index) SELECT GROUP_CONCAT(single_query SEPARATOR ' UNION ALL ') INTO @stat_query FROM query_text;
Query OK, 1 row affected (0.00 sec)

mysql> PREPARE stmt FROM @stat_query;
Query OK, 0 rows affected (0.00 sec)
Statement prepared

mysql> EXECUTE stmt;
+-------------+--------------+--------+
| column_name | column_value | counts |
+-------------+--------------+--------+
| apples      |            4 |      3 |
| bananas     |            3 |      2 |
| oranges     |            5 |      3 |
+-------------+--------------+--------+
3 rows in set (0.00 sec)

mysql> DEALLOCATE PREPARE stmt;
Query OK, 0 rows affected (0.00 sec)

【讨论】:

  • 我想出了一个使用存储过程来创建查询的替代解决方案,但这看起来也很棒。最终输出是准确的,非常感谢!
【解决方案2】:

根据@bill-karwin 和@shadow,任何在查询中动态设置列的尝试都是不可能的,所以我:

  1. 创建了一个生成查询文本以使用 UNION 生成模式的过程 - 它从 INFORMATION_SCHEMA 中获取列,然后遍历列以创建查询:
CREATE DEFINER=`root`@`localhost` PROCEDURE `column_modes`(OUT strsql TEXT)
BEGIN
    SET @cols = (SELECT COUNT(*)
                 FROM INFORMATION_SCHEMA.COLUMNS
                 WHERE TABLE_NAME = 'tablename');

    SET @ct = (SELECT COUNT(*) FROM tablename);

    SET @n = 0;
    SET @sql = '';
    WHILE @n < @cols DO
        PREPARE stmt FROM "SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'tablename' LIMIT ?, 1 INTO @col";
        EXECUTE stmt USING @n;
        DEALLOCATE PREPARE stmt;

        SET @sbq = CONCAT('SELECT ',
                          @col,
                          ' AS mode, COUNT(*)/',
                          @ct,
                          ' AS count FROM tablename GROUP BY ',
                          @col,
                          ' HAVING COUNT(*) >= ALL (SELECT COUNT(*) FROM tablename GROUP BY ',
                          @col,
                          ')');

        SET @sql = CONCAT(@sql,
                          "SELECT '",
                          @col,
                          "' as 'column', GROUP_CONCAT(mode SEPARATOR ',') as mode, GROUP_CONCAT(count SEPARATOR ',') as count FROM (",
                          @sbq,
                          ') temp');

        IF @n != (@cols - 1) THEN
            SET @sql = CONCAT(@sql, ' UNION ');
        END IF;
        SET @n = @n + 1;
    END WHILE;

    -- SELECT @sql; 
    SET strsql = @sql;
END
  1. 使用 PREPARE 执行查询:
CALL column_modes(@thisvar);
SELECT @thisvar;
PREPARE stmt FROM @thisvar;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

也非常感谢 @ProGu 提供第二个解决方案。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-11-22
    • 1970-01-01
    • 2020-04-12
    • 1970-01-01
    • 1970-01-01
    • 2022-11-02
    • 1970-01-01
    相关资源
    最近更新 更多