【问题标题】:Comparing two dates in different tables SQL with criteria将不同表 SQL 中的两个日期与条件进行比较
【发布时间】:2013-03-30 06:13:13
【问题描述】:

点击图片查看表结构和问题http://i.stack.imgur.com/odWKL.png

SELECT B.ENTITYID, 
       B.BALANCEDATE,
       B.BALANCE, 
       MIN(DATEDIFF(DAY,B.BALANCEDATE,C.STATUSDATE)) RECENT
FROM   BALANCES B JOIN STATUS C ON B.ENTITYID = C.ENTITYID
GROUP  BY B.ENTITYID, B.BALANCEDATE,B.BALANCE
HAVING B.ENTITYID =1

我已经尝试了以下方法,但由于更多的嵌套选择在访问相似属性时存在问题,因此无法再进一步:

【问题讨论】:

  • 你在用什么RDBMSRDBMS 代表关系数据库管理系统RDBMS is the basis for SQL,适用于所有现代数据库系统,如 MS SQL Server、IBM DB2、Oracle、MySQL 等...

标签: sql date-comparison


【解决方案1】:

@ljh's Answer if you are working on SQL Server。下面的解决方案适用于 MySQL。因为 MySQL 不支持 CTEWindow Function,所以有点乱。

MySQL

SET @entity_name = 'ABCD';

SELECT  b.*, d.Status
FROM    Entity a
        INNER JOIN Balances b
            ON a.EntityID = b.EntityID
        LEFT JOIN
        (
            SELECT  a.EntityID, 
                    a.StatusDate StartDate, 
                    b.StatusDate + Interval -1 DAY EndDate,
                    a.Status
            FROM
                    (
                        SELECT  b.*, @r1 := @r1 + 1 AS Row_number
                        FROM    `Entity` a
                                INNER JOIN Status b
                                    ON a.EntityID = b.EntityID
                                CROSS JOIN (SELECT @r1 := 0) rowCount
                        WHERE   a.EntityName = @entity_name
                        ORDER   BY b.Status ASC
                    ) a
                    LEFT JOIN
                    (
                        SELECT  b.*, @r2 := @r2 + 1 AS Row_number
                        FROM    `Entity` a
                                INNER JOIN Status b
                                    ON a.EntityID = b.EntityID
                                CROSS JOIN (SELECT @r2 := 1) rowCount
                        WHERE   a.EntityName = @entity_name
                        ORDER   BY b.Status ASC
                    ) b ON  a.Row_number = b.Row_number
        ) d 
        ON b.BalanceDate BETWEEN d.StartDate AND d.EndDate
WHERE   a.EntityName = @entity_name

简要分解

由于 MySQL 不支持 Windowing Function,例如 ROW_NUMBER(),下面的查询使用 User Variable 为每条记录提供类似于 ROW_NUMBER() 的行号,然后将其用于连接在另一个子查询上。

SELECT  b.*, @r1 := @r1 + 1 AS Row_number
FROM    `Entity` a
        INNER JOIN Status b
            ON a.EntityID = b.EntityID
        CROSS JOIN (SELECT @r1 := 0) rowCount
WHERE   a.EntityName = @entity_name
ORDER   BY b.Status ASC

输出

╔══════════╦═════════════════════════════════╦════════╦════════════╗
║ ENTITYID ║           STATUSDATE            ║ STATUS ║ ROW_NUMBER ║
╠══════════╬═════════════════════════════════╬════════╬════════════╣
║        1 ║ May, 29 2010 00:00:00+0000      ║ A      ║          1 ║
║        1 ║ April, 16 2010 00:00:00+0000    ║ B      ║          2 ║
║        1 ║ April, 02 2010 00:00:00+0000    ║ C      ║          3 ║
║        1 ║ February, 26 2010 00:00:00+0000 ║ D      ║          4 ║
╚══════════╩═════════════════════════════════╩════════╩════════════╝

为记录提供行号的主要目的是将其用于连接另一个子查询,因此我们可以获得每个StatusStartDateEndDate。这在SQL Server 2012 上很容易,因为它有一个名为LAG() 的窗口函数

╔══════════╦═════════════════════════════════╦══════════════════════════════╦════════╗
║ ENTITYID ║            STARTDATE            ║           ENDDATE            ║ STATUS ║
╠══════════╬═════════════════════════════════╬══════════════════════════════╬════════╣
║        1 ║ May, 29 2010 00:00:00+0000      ║ (null)                       ║ A      ║
║        1 ║ April, 16 2010 00:00:00+0000    ║ May, 28 2010 00:00:00+0000   ║ B      ║
║        1 ║ April, 02 2010 00:00:00+0000    ║ April, 15 2010 00:00:00+0000 ║ C      ║
║        1 ║ February, 26 2010 00:00:00+0000 ║ April, 01 2010 00:00:00+0000 ║ D      ║
╚══════════╩═════════════════════════════════╩══════════════════════════════╩════════╝

一旦组织好状态范围。它现在是每个 BalancesLookUp 状态的基础。

最终结果

╔══════════╦═════════════════════════════════╦═════════╦════════╗
║ ENTITYID ║           BALANCEDATE           ║ BALANCE ║ STATUS ║
╠══════════╬═════════════════════════════════╬═════════╬════════╣
║        1 ║ May, 01 2010 00:00:00+0000      ║     100 ║ B      ║
║        1 ║ April, 01 2010 00:00:00+0000    ║      50 ║ D      ║
║        1 ║ March, 01 2010 00:00:00+0000    ║      75 ║ D      ║
║        1 ║ February, 01 2010 00:00:00+0000 ║      85 ║ (null) ║
╚══════════╩═════════════════════════════════╩═════════╩════════╝

SQL Server 2012

上面在MySQL 中演示的查询可以通过使用Common Table Expression 和使用LAG() (introduce in SQL Server 2012 only)Window Function 轻松转换为TSQL

WITH lookupTable
AS
(
    SELECT  EntityID, 
            StatusDate StartDate, 
            DATEADD(DAY, -1, LAG(StatusDate) OVER(PARTITION BY EntityID ORDER BY Status)) EndDate,
            Status
    FROM    Status
)
SELECT  b.*, d.Status
FROM    Entity a
        INNER JOIN Balances b
            ON a.EntityID = b.EntityID
        LEFT JOIN lookupTable d
            ON b.BalanceDate BETWEEN d.StartDate AND d.EndDate AND
               d.EntityID = a.EntityID
WHERE   a.EntityName = 'ABCD'

输出

╔══════════╦═════════════════════════════════╦═════════╦════════╗
║ ENTITYID ║           BALANCEDATE           ║ BALANCE ║ STATUS ║
╠══════════╬═════════════════════════════════╬═════════╬════════╣
║        1 ║ May, 01 2010 00:00:00+0000      ║     100 ║ B      ║
║        1 ║ April, 01 2010 00:00:00+0000    ║      50 ║ D      ║
║        1 ║ March, 01 2010 00:00:00+0000    ║      75 ║ D      ║
║        1 ║ February, 01 2010 00:00:00+0000 ║      85 ║ (null) ║
╚══════════╩═════════════════════════════════╩═════════╩════════╝

【讨论】:

  • 你好..你能解释一下你的查询吗?
【解决方案2】:

以 SQL Server 2012 作为 RDBMS 为例。这是您需要的第一个查询。
此答案使用 SQL CTE(通用表表达式),可能不适用于其他 RDBMS 系统。
SQL FIDDLE DEMO
查询说明:
1.首先连接[Balances]和[Status]表,使用BalanceDate>StatusDate过滤结果,返回两个表中的所有列,这是因为后面需要所有。
2.将步骤.1的输出与[Entity]表连接,使用EntityName过滤结果,仍然保留3个表中的所有列,当然不需要重复的EntityID。
3.使用CTE保存加入
4. 使用 CTE 保存应用于连接输出的排名
5. 使用 Rank 数字过滤结果和按 BalanceDate 排序


;with CTE_AfterJoin
as 
(
    select E.EntityID, E.EnityName, C.BalanceDate, C.Balance, C.StatusDate, C.status
    from Entity E
    left join (
            select B.EntityID, B.BalanceDate, B.Balance,S.StatusDate, S.[Status]
            from Balances B 
            left join  [Status] S
            on B.EntityID = S.EntityID and B.BalanceDate > S.StatusDate
            ) C
    on E.EntityID = C.EntityID
    where E.EnityName = 'ABCD'
),
CTE_afterRank
as 
(
    select EnityName, BalanceDate, Balance, 
           rank() over (partition by BalanceDate order by StatusDate desc) as Rn, Status 
    from CTE_AfterJoin
)
select EnityName, BalanceDate, Balance, Status
from CTE_afterRank
where Rn = 1
order by BalanceDate desc

【讨论】:

    【解决方案3】:

    同样在 SQLServer2005+ 中,您可以将选项与 APPLY() 运算符一起使用。

    APPLY 运算符允许您连接两个表表达式。每次对左表表达式中的每一行处理右表表达式。最终结果集包含从左表表达式中选择的所有列,然后是右表表达式的所有列。 OUTER APLLY 对于右表表达式中没有对应匹配的行,它在右表表达式的列中包含 NULL 值。

    SELECT e.EntityName, b.BalanceDate AS Date, b.Balance, o.Status
    FROM Entity e JOIN Balances b ON e.EntityID = b.EntityID
                  OUTER APPLY (
                               SELECT TOP 1 s.Status AS Status                           
                               FROM Status s
                               WHERE b.EntityID = s.EntityID 
                                 AND s.StatusDate < b.BalanceDate
                               ORDER BY s.StatusDate DESC
                               ) o
    WHERE e.EntityName = 'ABCD' 
    

    为了提高性能(强制 INDEX SEEK 操作)将此索引与 INCLUDE 子句一起使用。INCLUDE 子句将数据添加到最低/叶级别,而不是在索引树中。这使得索引更小,因为它不是树的一部分

    CREATE INDEX x ON Status(StatusDate) INCLUDE(EntityID, Status)
    CREATE INDEX x ON Entity(EntityName) INCLUDE(EntityID)
    CREATE INDEX x ON Balances(EntityID, BalanceDate, Balance)
    

    SQLFiddle上的演示

    【讨论】:

    • 首先,非常感谢大家的大力帮助。我将 Alexander 的答案标记为问题的解决方案,因为它适用于 SQL Server 2005+ ......尽管 SQL 2012 和 MySQL 的示例同样出色,但 Alexander 的答案的绝对兼容性证明对于将其标记为解决方案至关重要。谢谢大家
    • @talktome 始终牢记,当出现此类问题时,您应该始终指定您正在使用的数据库服务器。我们不是可以预测您正在使用的服务器的通灵者。 SQL 表示结构化查询语言。它没有在首字母缩写词中提到任何服务器。
    • @J W ...我的错,我错过了提及 SQL Server 的部分。您提到正在使用的数据库是正确的。但无论如何,我希望这对使用 SQL Server 2012 和 MySQL 的人来说是一种变相的祝福。完全感谢你,你的详细回答和惊人的解释。
    猜你喜欢
    • 2019-02-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多