如何在mysql中使用带有group by的子查询?

Posted

技术标签:

【中文标题】如何在mysql中使用带有group by的子查询?【英文标题】:How to use subquery with group by in mysql? 【发布时间】:2020-02-09 18:36:08 【问题描述】:

我有一张针对特定用户的多行表。我有 2019、2018、2016 等多年的数据。我有两个 场景:

1.我想要特定 INVOICE_YEAR 的数据。

2,但同时我想要为特定发票生成的第一日期 用户。

我的 sql 查询:

$yearOf这是动态年份输入变量。

$yearOf = 2019;

$Records = "SELECT MIN(inv.INVOICE_DATE) AS MIN_INVOICE_DATE
                    FROM invoices as inv
                    LEFT JOIN customers as cm ON cm.CUSTOMER_ID = inv.CUSTOMER_ID 
                    where inv.INVOICE_YEAR IN (".$yearOf.")
                    group by inv.CUSTOMER_ID ORDER BY cm.CUSTOMER_NAME ASC";

您可以看到我的查询,如果我想要 INVOICE_YEAR IN ('2019') 的所有用户的数据。我的第一个条件将满足,我将获得 INVOICE_YEAR = 2019 的用户的所有数据。

但同时我想要第一个发票日期,所以为此我使用了MIN(inv.INVOICE_DATE),但因为我使用了where inv.INVOICE_YEAR IN (".$yearOf."),所以它给了我特定年份的第一个发票日期。

但我想要所有用户的整个表中的第一个发票日期。

我尝试使用子查询,但它显示 子查询返回超过 1 行的错误

我的子查询查询:

$Records = "SELECT 
                    (
                    SELECT MIN(inv.INVOICE_DATE) AS MIN_INVOICE_DATE FROM invoices AS inv GROUP BY inv.CUSTOMER_ID) AS MIN_INVOICE_DATE
                    FROM invoices as inv 
                    LEFT JOIN customers as cm ON cm.CUSTOMER_ID = inv.CUSTOMER_ID 
                    where inv.INVOICE_YEAR IN (".$yearOf.")
                    group by inv.CUSTOMER_ID ORDER BY cm.CUSTOMER_NAME ASC";

例如:

有 3 个客户 101,102,103

表中的数据如下:

id | customer_id | invoice_date | invoice_year
1  | 101         | 2019-01-01   |  2019
2  | 101         | 2016-01-01   |  2016
3  | 101         | 2017-01-01   |  2017
4  | 101         | 2016-01-02   |  2016
5  | 102         | 2019-01-02   |  2019
6  | 103         | 2018-01-02   |  2018
7  | 103         | 2019-01-07   |  2019
8  | 102         | 2015-01-02   |  2015

由于我请求查询以获取 INVOICE_YEAR 2019 的数据以及特定用户的第一个发票日期,因此它应该提供如下输出:

id | customer_id | invoice_date | invoice_year | min_invoice_date
1  | 101         | 2019-01-01   |  2019        | 2016-01-01
5  | 102         | 2019-01-02   |  2019        | 2015-01-02
7  | 103         | 2019-01-07   |  2019        | 2019-01-07  

如我所愿所有用户的第一个发票日期 IN COLUMN MIN_INVOICE_DATE

但它向我显示的数据如下:

id | customer_id | invoice_date | invoice_year | min_invoice_date
1  | 101         | 2019-01-01   |  2019        | 2019-01-01
5  | 102         | 2019-01-02   |  2019        | 2019-01-02
7  | 103         | 2019-01-07   |  2019        | 2019-01-07  

【问题讨论】:

请添加示例数据和预期结果以澄清您的问题。 @GMB 请查看我添加的示例以简要了解我的问题,谢谢 I removed the blockquote 更早。你为什么又把它们加回来了? 即使 2019 年该用户有很多发票,您也只希望每个用户有 1 行?如果是,你会展示哪一个? Blockquote 是用于错误消息或引用某些内容,而不是为了您自己的个人突出享受。你为什么用那些?我觉得我已经置若罔闻了。 【参考方案1】:

您想为每个客户提取 2019 年最早的发票,以及他们在整个表格中最早发票的日期。

mysql 8.0 中,您可以使用窗口函数来解决这个问题:

SELECT id, customer_id, invoice_date, invoice_year, min_invoice_date
FROM (
    SELECT 
        t.*,
        ROW_NUMBER() OVER(PARTITION BY customer_id, invoice_year ORDER BY invoice_date) rn,
        MIN(invoice_date) OVER(PARTITION BY customer_id) min_invoice_date
    FROM mytable t
) x
WHERE invoice_year = 2019 AND rn = 1

在早期版本中,您可以:

JOIN 带有一个查询的表,该查询计算每个客户的整体最小值 invoice_date 使用带有 NOT EXISTS 条件的相关子查询来过滤 2019 年最早的发票面值客户

查询:

SELECT t.id, t.customer_id, t.invoice_date, t.invoice_year, m.min_invoice_date
FROM mytable t
INNER JOIN (
    SELECT customer_id, MIN(invoice_date) min_invoice_date 
    FROM mytable 
    GROUP BY customer_id
) m ON m.customer_id = t.customer_id
WHERE 
    t.invoice_year = 2019
    AND NOT EXISTS (
        SELECT 1 
        FROM mytable t1
        WHERE t1.invoice_year = 2019 
        AND t1.customer_id = t.customer_id
        AND t1.invoice_date < t.invoice_date
    )

在这个demo on DB Fiddle中,两个查询都返回:

| id  | customer_id | invoice_date | invoice_year | min_invoice_date |
| --- | ----------- | ------------ | ------------ | ---------------- |
| 1   | 101         | 2019-01-01   | 2019         | 2016-01-01       |
| 5   | 102         | 2019-01-02   | 2019         | 2015-01-02       |
| 7   | 103         | 2019-01-07   | 2019         | 2018-01-02       |

【讨论】:

感谢您的回复兄弟,但您仍然无法理解我的问题。 @GMB @amitsutar:我再次查看了您的问题并更新了我的答案。就您而言,新查询确实会为您的示例数据产生预期的结果。【参考方案2】:

您希望查看每个日历年中每个客户的第一张(最早日期)发票的详细信息。您希望过滤结果仅涵盖一年。

因此,从一个子查询开始,查找每个日历年每个客户的第一张发票的日期。 (https://www.db-fiddle.com/f/bmBZ14Vr9Re6ahpfs2FF2X/0)

      SELECT MIN(invoice_date) first_invoice_date,
             YEAR(invoice_date) calendar_year,
             customer_id
        FROM invoices
       GROUP BY YEAR(invoice_date), customer_id

然后通过将该子查询加入到您的原始invoices 表中来检索这些发票的详细信息。 (https://www.db-fiddle.com/f/bmBZ14Vr9Re6ahpfs2FF2X/1)

SELECT invoices.*
  FROM invoices
  JOIN (
          SELECT MIN(invoice_date) first_invoice_date,
                 YEAR(invoice_date) calendar_year,
                 customer_id
            FROM invoices
           GROUP BY YEAR(invoice_date), customer_id
       ) firsts 
           ON invoices.customer_id = firsts.customer_id
          AND invoices.invoice_date = firsts.first_invoice_date
  ORDER BY invoices.customer_id, 
           invoices.invoice_year, 
           invoices.invoice_date

然后,输入WHERE invoices.invoice_year = 2019 以获得您想要的年份。

请注意,您的invoice_year 列是不必要的,因为它始终可以从YEAR(invoice_date) 计算得出。你应该考虑摆脱它。

【讨论】:

感谢您的回复兄弟,但您的查询不适合我的问题。 @O.Jones 它向我显示所有客户的相同 MIN_INVOICE_DATE 这很奇怪。在 dbfiddle 中,它向我显示了不同客户的不同日期。我将 dbfiddle 基于您的示例数据。 db-fiddle.com/f/bmBZ14Vr9Re6ahpfs2FF2X/2

以上是关于如何在mysql中使用带有group by的子查询?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Laravel 8 中使用 PostgreSQL 中的子查询通过 group by 子句获取行值?

SUM GROUP BY与多个表上的子查询

带有连接和group by子句的选择查询中的MySQL性能问题

如何在没有算术计数的SQL子查询中使用GROUP BY

SQL 不能使用从 group by 中的子查询返回的列

mysql学习-group by的使用