TSQL:返回具有最早日期的行

时间:2009-06-16 18:28:44

标签: sql tsql

给出2个名为“table1”和“table1_hist”的表,结构上类似于:

TABLE1
id  status  date_this_status
1   open    2008-12-12
2   closed  2009-01-01
3   pending 2009-05-05
4   pending 2009-05-06
5   open    2009-06-01


TABLE1_hist
id  status  date_this_status
2   open    2008-12-24
2   pending 2008-12-26
3   open    2009-04-24
4   open    2009-05-04

如果table1是当前状态而table1_hist是table1的历史表,那么如何为每个具有最早日期的id返回行。换句话说,对于每个id,我需要知道它的最早状态和日期。

EXAMPLE:

For id 1 earliest status and date is open and 2008-12-12.
For id 2 earliest status and date is open and 2008-12-24.

我已经尝试过使用MIN(日期时间),联合,动态SQL等等。我今天刚刚到达了tsql编写器块并且卡住了。

编辑添加:呃。这是针对SQL2000数据库的,所以Alex Martelli的答案是行不通的。直到SQL2005才引入ROW_NUMBER。

7 个答案:

答案 0 :(得分:6)

SQL Server 2005及更高版本支持SQL标准的有趣(相对较新)方面,“排名/窗口函数”,允许:

WITH AllRows AS (
  SELECT id, status, date_this_status,
    ROW_NUMBER() OVER(PARTITION BY id ORDER BY date_this_status ASC) AS row,
  FROM (SELECT * FROM Table1 UNION SELECT * FROM Table1_hist) Both_tables
)
SELECT id, status, date_this_status
FROM AllRows
WHERE row = 1
ORDER BY id;

我还在使用nice(以及同样“new”)WITH语法来避免在主SELECT中嵌套子查询。

This article显示了如何在SQL Server中破解等效的ROW_NUMBER(以及RANKDENSE_RANK,另外两个“新”排名/窗口函数) 2000年 - 但这不一定很好,也不是特别好的,唉。

答案 1 :(得分:3)

以下代码示例完全自给自足,只需将其复制并粘贴到管理工作室查询中并点击F5 =)

DECLARE @TABLE1 TABLE
        (
        id                  INT,
        status              VARCHAR(50),
        date_this_status    DATETIME
        )

DECLARE @TABLE1_hist TABLE
        (
        id                  INT,
        status              VARCHAR(50),
        date_this_status    DATETIME
        )

--TABLE1
INSERT  @TABLE1
SELECT  1,  'open',     '2008-12-12'    UNION ALL
SELECT  2,  'closed',   '2009-01-01'    UNION ALL
SELECT  3,  'pending',  '2009-05-05'    UNION ALL
SELECT  4,  'pending',  '2009-05-06'    UNION ALL
SELECT  5,  'open',     '2009-06-01'

--TABLE1_hist
INSERT  @TABLE1_hist
SELECT  2,  'open',     '2008-12-24'    UNION ALL
SELECT  2,  'pending',  '2008-12-26'    UNION ALL
SELECT  3,  'open',     '2009-04-24'    UNION ALL
SELECT  4,  'open',     '2009-05-04'

SELECT      x.id,
            ISNULL(y.[status], x.[status])                  AS [status],
            ISNULL(y.date_this_status, x.date_this_status)  AS date_this_status
FROM        @TABLE1 x
LEFT JOIN   (
            SELECT      a.*
            FROM        @TABLE1_hist a
            INNER JOIN  (
                        SELECT      id,
                                    MIN(date_this_status) AS date_this_status
                        FROM        @TABLE1_hist
                        GROUP BY    id
                        ) b
                    ON  a.id = b.id
                    AND a.date_this_status = b.date_this_status
            ) y
        ON  x.id = y.id

答案 2 :(得分:2)

SELECT  id,
        status,
        date_this_status
FROM    ( SELECT    *
          FROM      Table1
          UNION
          SELECT    *
          from      TABLE1_hist
        ) a
WHERE   date_this_status = ( SELECT MIN(date_this_status)
                             FROM   ( SELECT    *
                                      FROM      Table1
                                      UNION
                                      SELECT    *
                                      from      TABLE1_hist
                                    ) t
                             WHERE  id = a.id
                           ) 

这有点难看,但似乎在MS SQL Server 2005中有效。

答案 3 :(得分:1)

您可以使用独占自我加入来完成此操作。加入历史表,然后再加入所有早期历史记录表。在where语句中,指定不允许存在任何先前的条目。

select t1.id,
    isnull(hist.status, t1.status),
    isnull(hist.date_this_status, t1.date_this_status)
from table1 t1
left join (
    select h1.id, h1.status, h1.date_this_status
    from table1_hist h1
    left join table1_hist h2
        on h2.id = h1.id
        and h2.date_this_status < h1.date_this_status
    where h2.date_this_status is null
) hist on hist.id = t1.id

有点心灵约束,但相当灵活和高效!

这假定没有两个具有完全相同日期的历史记录条目。如果有,请编写自联接,如:

left join table1_hist h2
    on h2.id = h1.id
    and (
        h2.date_this_status < h1.date_this_status
        or (h2.date_this_status = h1.date_this_status and h2.id < h1.id)
    )

答案 4 :(得分:1)

如果我正确理解了OP,那么给定的ID可能会出现在TABLE1或TABLE1_HISTORY中,或者两者都出现。

在结果集中,您希望返回每个不同的ID以及与该ID关联的最旧状态/日期,而不管最旧的那个表恰好位于哪个表中。

因此,查看两个表并返回 表中没有记录的任何记录,因为它的ID date_this_status

试试这个:

SELECT ID, status, date_this_status FROM table1 ta WHERE
     NOT EXISTS(SELECT null FROM table1 tb WHERE
         tb.id = ta.id
         AND tb.date_this_status < ta.date_this_status)
     AND NOT EXISTS(SELECT null FROM table1_history tbh WHERE
         tbh.id = ta.id
         AND tbh.date_this_status < ta.date_this_status)

UNION ALL

SELECT ID, status, date_this_status FROM table1_history tah WHERE
     NOT EXISTS(SELECT null FROM table1 tb WHERE
         tb.id = tah.id
         AND tb.date_this_status < tah.date_this_status)
     AND NOT EXISTS(SELECT null FROM table1_history tbh WHERE
         tbh.id = tah.id
         AND tbh.date_this_status < tah.date_this_status)

这里有三个基本假设:

  1. 您想要的每个ID在至少一个表格中至少有一条记录。
  2. 相同表中的相同ID不会有多条记录,并且具有相同的date_this_status值(可以使用DISTINCT缓解)
  3. 其他表中没有相同ID的记录具有相同的date_this_status值(可以使用UNION而不是UNION ALL来缓解)
  4. 我们可以进行两项略微优化:

    1. 如果ID在TABLE1_HISTORY中有记录,则它将始终比TABLE1中的记录更旧。
    2. TABLE1永远不会包含相同ID的多个记录(但历史记录表可能)。
    3. 所以:

      SELECT ID, status, date_this_status FROM table1 ta WHERE
           NOT EXISTS(SELECT null FROM table1_history tbh WHERE
               tbh.id = ta.id
               )
      
      UNION ALL
      
      SELECT ID, status, date_this_status FROM table1_history tah WHERE
           NOT EXISTS(SELECT null FROM table1_history tbh WHERE
               tbh.id = tah.id
               AND tbh.date_this_status < tah.date_this_status)
      

答案 5 :(得分:0)

如果这是表格的实际结构,则无法获得100%准确的答案,问题是对于任何给定记录,您可以为同一(最早)日期提供2种不同的状态,而您不会知道首先输入哪一个,因为历史表中没有主键

答案 6 :(得分:0)

暂时忽略“两个表”问题,我会使用以下逻辑......

SELECT
   id, status, date
FROM
   Table1_hist AS [data]
WHERE
   [data].date = (SELECT MIN(date) FROM Table1_hist WHERE id = [data].id)

(编辑:根据BlackTigerX的评论,这假设没有id可以有多个具有相同日期时间的状态。)

将此推断为两个表的简单方法是使用breitak67的答案。将所有“my_table”实例替换为将两个表UNION在一起的子查询。这里的潜在问题是性能问题,因为您可能会发现索引变得无法使用。

加快这种做法的一种方法可能是使用隐含的知识:
1.主表总是有每个id的记录 2.历史表并不总是有记录 3.历史表中的任何记录总是比主表中的记录“旧”。

SELECT
   [main].id,
   ISNULL([hist].status, [main].status),
   ISNULL([hist].date, [main].date)
FROM
   Table1          AS [main]
LEFT JOIN
(
   SELECT
      id, status, date
   FROM
      Table1_hist AS [data]
   WHERE
      [data].date = (SELECT MIN(date) FROM Table1_hist WHERE id = [data].id)
)
   AS [hist]
      ON [hist].id = [main].id
  • 查找历史记录表中每个ID的最旧状态。 (可以使用其索引)
  • LEFT JOIN到主表(每个id总是只有一条记录)
  • 如果[hist]包含一个值,则根据定义它是旧的
  • 如果[hist]没有值,请使用[main]值