【问题标题】:Return data for all dates within a single MySql table even when no results即使没有结果,也可以返回单个 MySql 表中所有日期的数据
【发布时间】:2019-03-12 11:04:12
【问题描述】:

我有一个数据表(如下所示)

| id | date        | product |
|----|-------------|---------|
| 1  | 2019-01-02  | prod 1  |
| 2  | 2019-01-02  | prod 2  |
| 3  | 2019-01-22  | prod 1  |
| 4  | 2019-02-02  | prod 1  |
| 5  | 2019-02-02  | prod 1  |
| 6  | 2019-03-02  | prod 1  |

我希望最终结果看起来像这样......

| product | 2019-01 | 2019-02 | 2019-03 |
|---------|---------|---------|---------|
| prod 1  | 2       | 3       | 0       |
| prod 2  | 1       | 0       | 0       | 

简而言之,我想遍历一个日期范围内的所有产品(所有日期都包含在一个表中)。当一个月没有结果时,返回 0。我相信 COALESCE 可能是解决这个问题的方法。

我已经尝试了这两种不同的方法...在单个查询中返回所有数据并循环遍历该数据,但如果没有数据,它永远不会返回字符串或我可以使用的东西(null)。

SELECT 
    product_name, 
    count(product_name) AS count
    DATE_FORMAT(date,'%Y-%m') AS date
FROM products
GROUP BY DATE_FORMAT(date,'%Y-%m'), company_name
ORDER BY date;

| product | date    | count |
|---------|---------|-------|
| prod 1  | 2019-01 | 2     |
| prod 2  | 2019-01 | 1     |
| prod 1  | 2019-02 | 3     |
| prod 2  | 2019-02 | 0     | <--- this row doesn't return

另外,我尝试循环遍历所有产品,然后单独循环遍历日期,但如果表中没有日期,则再次返回没有数据,因此我的表会倾斜。

我看到其他人将日期存储在另一个表中,然后循环遍历该表,这感觉有点矫枉过正,因为我将所有日期都保存在此表中,因此我尝试将表连接到自身上,但这也不起作用。

提前感谢您的帮助。

【问题讨论】:

  • mysql 无法生成您想要的结果(使用数据作为列名),afaik。 (这是某种旋转)除非您生成以这种方式显式命名列的查询 (select ... as `2019-01`...)。话虽如此,你不能无中生有地产生数据。要么您向它提供查询,要么您有一个包含所有(包括空)日期的表。 ;o/ 在查询中包含日期可能会起作用,但它通常很难看,而且你必须跳过一些环节。

标签: php mysql


【解决方案1】:

要获得您想要的第二个结果,您首先需要在表中 dateproduct_name 的所有不同值之间创建一个 CROSS JOIN。然后可以再次将其LEFT JOINed 到表中,以获取每个产品每个月的所有值,以及按产品名称和月份分组的结果:

SELECT 
    p.product_name, 
    count(p2.product_name) AS count,
    d.date
FROM (SELECT DISTINCT DATE_FORMAT(date,'%Y-%m') AS date
      FROM products) d
CROSS JOIN (SELECT DISTINCT product_name
            FROM products) p
LEFT JOIN products p2 ON DATE_FORMAT(p2.date,'%Y-%m') = d.date AND p2.product_name = p.product_name
GROUP BY p.product_name, d.date
ORDER BY p.product_name, d.date

输出

product_name    count   date
prod 1          2       2019-01
prod 1          3       2019-02
prod 2          1       2019-01
prod 2          0       2019-02

Demo on dbfiddle

要使第一个结果是可行的,但需要使用存储过程来动态创建数据透视表查询。将上面的结果用 PHP 处理以达到相同的结果要容易得多。

【讨论】:

    【解决方案2】:

    这基本上是一个数据透视表。

    可以在此处找到有关如何实现此目的的精彩教程:http://www.artfulsoftware.com/infotree/qrytip.php?id=78

    这是您的数据透视表动态解决方案。 只需使用@Nick 建议的查询并为其制作临时表

    create temporary table tmp as 
    SELECT 
        p.product_name, 
        count(p2.product_name) AS count,
        d.date
    FROM (SELECT DISTINCT DATE_FORMAT(date,'%Y-%m') AS date
          FROM products) d
    CROSS JOIN (SELECT DISTINCT product_name
                FROM products) p
    LEFT JOIN products p2 ON DATE_FORMAT(p2.date,'%Y-%m') = d.date AND p2.product_name = p.product_name
    GROUP BY p.product_name, d.date
    ORDER BY p.product_name, d.date;
    

    现在这里是动态 SP,它将根据未来的任何新产品将 Row 转换为列。

    delimiter $$
    DROP PROCEDURE IF EXISTS pivot $$
    CREATE PROCEDURE pivot(IN schema_name VARCHAR(64) 
                           , IN table_name VARCHAR(64)  
                           , IN id_name VARCHAR(64)     
                           , IN key_name VARCHAR(64)    
                           , IN value_name VARCHAR(64))
    pivot:BEGIN
    
        DECLARE CONTINUE HANDLER FOR NOT FOUND SET @error := 1;
        SET @error := 0;
    
    
        SELECT MAX(character_maximum_length) 
             INTO @maxlen 
            FROM information_schema.columns
            WHERE table_schema = schema_name
              AND table_name = table_name
              AND column_name = key_name
              AND data_type IN ('char', 'varchar');
    
              SET @maxlen = IFNULL(@maxlen,500);
    
        IF @error OR !@maxlen OR @maxlen IS NULL THEN
            SELECT '@error OR @maxlen=0 OR @maxlen IS NULL', @error, @maxlen;
            LEAVE pivot;
        END IF; 
        DROP TEMPORARY TABLE IF EXISTS temp_pivot;
        SET @sql := CONCAT('CREATE TEMPORARY TABLE temp_pivot (key_name VARCHAR(',
                           @maxlen,
                           ')) ENGINE=Memory SELECT DISTINCT `',
                           key_name,
                           '` key_name FROM `',
                           schema_name,
                           '`.`',
                           table_name,
                           '`;');
        PREPARE stmt FROM @sql;
        EXECUTE stmt;
        DROP PREPARE stmt;
    
        SELECT GROUP_CONCAT(CONCAT( ', MAX(CASE `',
                                    key_name,
                                    '` WHEN ''',
                                    temp_pivot.key_name,
                                    ''' THEN `',
                                    value_name,
                                    '`   else 0  END) `',
                                    temp_pivot.key_name,
                                    '`') SEPARATOR '')
            INTO @sql
            FROM temp_pivot;
    
          DROP TEMPORARY TABLE IF EXISTS Pivot_Check;
        SET @sql := CONCAT('CREATE TEMPORARY TABLE Pivot_Check AS ','SELECT `',
                           id_name,
                           '`',
                           @sql,
                           ' FROM `',
                           schema_name,
                           '`.`',
                           table_name,
                           '` GROUP BY `',
                           id_name,
                           '`;');
    
    
        PREPARE stmt FROM @sql;
        EXECUTE stmt;
        DROP PREPARE stmt;
        SET @error := NULL;
        SET @maxlen := NULL;
        SET @sql := NULL;
    END $$
    delimiter 
    

    然后调用 SP

    call pivot('schema_name','table_name','product','count','date');

    您的数据透视表已准备就绪,只需使用

    select * from Pivot_Check
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-09-09
      • 1970-01-01
      • 2012-09-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多