【问题标题】:How do I get the oldest date without using a correlated subquery in SQL?如何在不使用 SQL 中的相关子查询的情况下获取最早的日期?
【发布时间】:2012-11-04 21:19:29
【问题描述】:

以下代码列出了所有发票,我只想要供应商的最旧发票:

SELECT DISTINCT vendor_name, i.invoice_number AS OLDEST_INVOICE, 
    MIN(i.invoice_date), i.invoice_total
FROM vendors v
JOIN invoices i
ON i.vendor_id = v.vendor_id
GROUP BY vendor_name, invoice_number, invoice_total
ORDER BY MIN(i.invoice_date);

【问题讨论】:

  • 这段代码列出了所有的发票,我只想要供应商最旧的发票
  • GROUP BY vendor_name ORDER BY ...你想要什么?
  • 你的 SQL 引擎支持 ROW_NUMBER() 吗?
  • 我想在不使用相关子查询的情况下返回每个供应商的所有第一张发票并在发票日期之前订购

标签: sql oracle greatest-n-per-group correlated-subquery


【解决方案1】:

我们将使用ROW_NUMBER() 按日期对每个供应商的发票进行“排名”,然后仅选择每个供应商最旧的:

SELECT vendor_name, invoice_number AS oldest_invoice, invoice_date, invoice_total
  FROM vendors v
 INNER JOIN (SELECT invoices.*,
                    ROW_NUMBER() OVER (PARTITION BY vendor_id ORDER BY invoice_date ASC)
                      AS rn
               FROM invoices) i
       ON i.vendor_id = v.vendor_id
          AND
          i.rn = 1;

【讨论】:

  • 我的偏好通常是使用 Min 或 Max 而不是 Row_Number,因为它更节省内存 - RDBMS 不必维护它从中选择的发票的排名列表(在这种情况下)那些排名最低的,它只需要存储每个窗口的最小值或最大值。
  • @DavidAldridge,假设 invoice_number 不一定形成递增序列(例如,“OCT099”在“NOV001”之前),分区的最小/最大解决方案会是什么样子?
  • SELECT vendor_name, invoice_number AS old_invoice, invoice_date, invoice_total FROM vendor v, (SELECT invoices.*, min(invoice_date) OVER (PARTITION BY vendor_id) min_invoice_date_per_vendor FROM invoices) i 其中 i.vendor_id = v。 vendor_id 和 i.invoice_date = i.min_invoice_date_per_vendor
  • @DavidAldridge,对,+1。现在,你如何打破平局? ( oracle 是否提高了内存效率?乍一看,派生表 i 仍然选择每一行,它只是将 MIN() 日期附加到每一行。)
  • 如果可能出现平局情况,那么最好对独特的事物施加进一步的排序,而不是接受伪随机行,只要这样可以记录问题。是的,仍然必须选择每一行,节省来自查询执行,而不必维护所有行的数字排名。实际上,也许我的意思是节省 CPU 而不是节省内存,但我记得在使用 V$SQL_WORKAREA_ACTIVE 检查执行时有可衡量的节省。具有讽刺意味的是,也许我的记忆有问题。
【解决方案2】:

TDQD 时代——测试驱动的查询设计

每个供应商开具发票的最短日期如下:

SELECT vendor_id, MIN(invoice_date) AS invoice_date
  FROM invoices
 GROUP BY vendor_id

相应的最小发票编号(假设在为供应商开具发票的第一天可能已经发送了多张发票,如果 invoice_date 是没有时间组件的真实 DATE;如果 DATE 包含时间组件,则第二个 MIN() 可能是不必要的),是:

SELECT vendor_id, MIN(invoice_number) AS invoice_number
  FROM invoices AS i
  JOIN (SELECT vendor_id, MIN(invoice_date) AS invoice_date
          FROM invoices
         GROUP BY vendor_id
       ) AS j ON j.vendor_id = i.vendor_id AND j.invoice_date = i.invoice_date
 GROUP BY vendor_id

您可以将此表达式与其他表连接以满足您的查询要求:

SELECT v.*, i.*
  FROM vendors AS v
  JOIN (SELECT vendor_id, MIN(invoice_number) AS invoice_number
          FROM invoices AS i
          JOIN (SELECT vendor_id, MIN(invoice_date) AS invoice_date
                  FROM invoices
                 GROUP BY vendor_id
               ) AS j ON j.vendor_id = i.vendor_id AND j.invoice_date = i.invoice_date
         GROUP BY vendor_id
       ) AS inv_info ON v.vendor_id = inv_info.vendor_id
  JOIN invoices AS i ON i.invoice_number = inv_info.invoice_number

毫无疑问,还有其他设计方法。请注意,这些子查询都不是相关子查询。

TDQD 纯粹是名义上的;没有 DBMS 为检查这些查询在语法上是否有效而烦恼,更不用说返回正确的答案了。 OTOH,这是一种标准技术。

如果您喜欢在 GROUP BY 子句中列出大量列,您可以通过让 inv_info 子查询返回相关的发票列来完成与 invoices 的最终连接。我不喜欢写出很多列名——但如果我担心性能,我会衡量它是否会产生显着差异。

您可能会发现有一个 OLAP 函数/查询可以更快地完成这项工作。

【讨论】:

    【解决方案3】:

    试试这个:

    SELECT DISTINCT 
        v.vendor_name, 
        i.invoice_number AS OLDEST_INVOICE, 
        i2.MinDate, 
        i.invoice_total
    FROM vendors v
    INNER JOIN invoices i  ON i.vendor_id = v.vendor_id
    INNER JOIN
    (
        SELECT 
          invoice_number , 
          MIN(i.invoice_date) MinDate
        FROM invoices
        GROUP BY invoice_number
    ) i2 ON  i.invoice_number = i2.invoice_number
         AND i.invoice_date   = i2.MinDate
    ORDER BY i2.MinDate;
    

    【讨论】:

    • 不,我有这段代码,但我想修改它,使其不使用相关子查询 SELECT v.vendor_name, i.invoice_number AS OLDEST_INVOICE, i.invoice_date, i.invoice_total FROM vendor v加入发票 i ON i.vendor_id = v.vendor_id AND i.invoice_date = (SELECT MIN(invoice_date) FROM invoices WHERE vendor_id = v.vendor_id) ORDER BY i.invoice_date;
    • @MarcGordon - 我在答案中发布的查询与没有相关子查询的查询相同。
    • 它给了我这个错误报告:SQL 错误:ORA-00933: SQL 命令未正确结束 00933. 00000 - “SQL 命令未正确结束”
    • 此答案中的子查询不是相关子查询。它可能不是正确的子查询,但它不是相关的子查询。
    【解决方案4】:

    HAVING 不会在这里工作吗?

    SELECT DISTINCT vendor_name, i.invoice_number AS OLDEST_INVOICE, 
        MIN(i.invoice_date), i.invoice_total
    FROM vendors v
    JOIN invoices i
    ON i.vendor_id = v.vendor_id
    GROUP BY vendor_name, invoice_number, invoice_total
    HAVING i.invoice_date = MIN (i.invoice_date)
    ORDER BY MIN(i.invoice_date);
    

    【讨论】:

    • 对不起,我无法测试,因为我懒得输入表格定义。
    • 如果没有子查询,该技术将无法工作——实际上并没有使用 HAVING 子句。
    猜你喜欢
    • 1970-01-01
    • 2016-04-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-25
    • 2020-07-07
    • 1970-01-01
    相关资源
    最近更新 更多