【问题标题】:Matching only one specific row in a JOIN where many exist仅匹配存在许多的 JOIN 中的一个特定行
【发布时间】:2009-09-24 15:44:00
【问题描述】:

(Advantage Database Server) 我有一个服务提供商表,出于审计目的,它永远不会被删除。他们有开始日期和结束日期;对于名称或地址等更改,现有行的结束日期,创建一个新行,并为更改的数据分配一个新的开始日期。

在处理向这些提供商付款的过程中,我需要一个汇总页面,其中列出了提供商名称、地址、标识符 (ProvID) 和支付的总金额。这是在一个相当简单的查询中使用 SUM() 和 GROUP BY 完成的。

当指定的提供者标识符有两行或多行时,就会出现此问题。我最终得到了重复的行(如果没有被抓到,可能会导致向该提供商多次付款)。

我的第一个想法是使用子选择之类的东西(丑陋,但执行得相当快):

SELECT ... FROM service s
INNER JOIN provider p ON p.ProvID = s.ProvID
AND (p.EndDate IS NULL or p.EndDate = (SELECT Max(EndDate) FROM
   provider lu WHERE lu.ProvID = s.ProvID))

不幸的是,这仍然找到了两行; NULL EndDate 一行,MAX(EndDate) 一行。

我在其他情况下(例如,为在特定日期提供的服务找到正确的 ProvID)使用

来处理这个问题
p.EndDate is null or (s.ServiceDate BETWEEN p.StartDate AND p.EndDate)

很遗憾,由于问题查询是带有聚合的 GROUP BY,因此服务日期不可用。

有什么建议吗?

编辑:我要查找的是带有 NULL EndDate 的行(如果存在),或者带有 Max(EndDate) 的行(如果 NULL 行不存在)。例如,供应商昨天被终止,但上周确实工作,我们将在下周向他们付款。

【问题讨论】:

    标签: sql advantage-database-server


    【解决方案1】:

    所以我猜如果有一行结束日期为 NULL,你想要那个,否则你想要结束日期最大的那个?

    我不确定 ADS,但以下内容适用于 SQL Server:

    SELECT ... FROM service s
    INNER JOIN provider p ON p.ProvID = s.ProvID
    AND (COALESCE(p.EndDate, '2037-01-01') = (
       SELECT Max(COALESCE(EndDate, '2037-01-01')) FROM
       provider lu WHERE lu.ProvID = s.ProvID)
    )
    

    COALESCE 运算符返回第一个非空参数,所以这基本上只是将空值设置为远在未来的时间,这样 SELECT MAX 会给你一个结束日期为 NULL 的参数(如果有一个) .

    【讨论】:

    • 我接受这个答案是因为它非常漂亮,而且比 najmeddine 的 NOT EXISTS 答案略快(但只是因为索引选择好)。谢谢,基普。 :-)
    • 刚刚合并到我的代码库并针对实际数据进行了测试。 100% 工作,没有明显的性能损失。再次感谢,基普!其他 ADS 用户注意:COALESCE() 调用需要添加一个:将它们都更改为 COALESCE(EndDate, CAST('2037-01-01' AS SQL_DATE)),因为 ADS 不会自动从日期开始转换像其他数据库一样的文字。
    • @Ken White:如果您有不止一行的结束日期为 NULL,那么您将得到不止一行的结果。我想这将被视为您的架构中的损坏数据。
    【解决方案2】:

    在第二种情况下,只有在没有 NULL EndDate 的情况下才能获得最大值

    SELECT ... FROM service s
    INNER JOIN provider p ON p.ProvID = s.ProvID
    AND (   p.EndDate IS NULL 
         or (p.EndDate = (SELECT Max(EndDate) 
                            FROM provider lu 
                           WHERE lu.ProvID = s.ProvID)
             AND NOT EXISTS (SELECT NULL 
                               FROM provider lu 
                              WHERE lu.ProvID = s.ProvID 
                                AND lu.EndDate IS NULL)
            )
        )
    

    【讨论】:

    • @najmeddine:我接受了 Kip 的回答,因为它比你的快(因为他使用了两个 COALESCE() 函数调用,而不是为 NOT EXISTS 测试添加另一个子选择) .不过,你的也很好用,所以我也赞成。谢谢!
    【解决方案3】:

    也许使用子查询代替第二个表:

    SELECT ... FROM service s
    INNER JOIN (SELECT ..., Max(EndDate) FROM
       provider lu WHERE lu.ProvID = s.ProvID GROUP BY ...) p ON p.ProvID = s.ProvID
    

    这是假设如果没有最大结束日期,您将返回 NULL。

    【讨论】:

      【解决方案4】:

      您指的是数据仓库的 Type-2 维度。

      您必须按 ID 按 StartDate 和 EndDate 加入才能获得正确的数据。

      OTTOMH 代码

      SELECT TransactionId, TransactionType
      FROM TransactionList Tx
          INNER JOIN TransactionType TxType
              ON Tx.TransactionTypeId = TxType.TxTypeId
              AND Tx.TransactionDate Between TxType.StartDate and TxType.EndDate
      

      【讨论】:

      • 正如我所提到的,我不能这样做,因为我使用的是聚合函数和 GROUP BY。由于聚合,交易日期不可用。
      • 道歉。我显然掩盖了那部分。
      • 没问题。感谢您的尝试,Raj。
      【解决方案5】:

      您的提供者表中的什么表示当前日期? EndDate=NULL, EndDate=Max(EndDate) 还是 EndDate='9999-01-01'?这三个都是有效的选择,但这应该是明确的,因为如果不是这样,无论您如何巧妙地制作这个特定的查询,您最终都会在查询中遇到重复的行。所以我建议在提供者表中修复它,然后这样的东西应该可以工作:

      select p.name, p.address, p.id, sum(s.amount)
        from provider p
        join service s on p.id=s.provider_id
      where p.endDate is NULL
      group by p.name, p.address, p.id
      

      【讨论】:

      • 无法修复提供者表。它是历史数据和当前数据,审计人员不允许我们出于任何原因更改它。如果重组数据是一种选择,我就不需要在这里发帖了。不过,谢谢。
      • 再想一想,你确实有一个明确的条件。您想运送到当前地址,对吗?那么你就不需要 s.ServiceDate。只需使用“其中 p.EndDate 为空或(p.StartDate 和 p.EndDate 之间的系统日期)”。这有帮助吗?
      • 不幸的是,没有。请参阅我对原始帖子的编辑,关于在期限日期之前提供出色服务的终止提供商,但在期限日期之后支付。 SysDate 不会介于开始和结束之间,在处理付款时也不会出现 NULL 结束日期行。
      猜你喜欢
      • 1970-01-01
      • 2013-11-18
      • 2019-12-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多