如何从包含日期时间列的表中获取某些行和“先前”行?

Posted

技术标签:

【中文标题】如何从包含日期时间列的表中获取某些行和“先前”行?【英文标题】:How can I get certain rows and the "previous" rows from a table that includes a datetime column? 【发布时间】:2014-07-01 18:03:35 【问题描述】:

我有两张桌子,Alpha 和 Bravo。 Bravo 有一列 id(整数,主键)和其他一些与此问题无关的列。 Alpha 有列id(整数,主键),bravo_id(Bravo 表的外键),special(单个字符,对于大多数行为 null,但对于某些重要行具有值),created(日期时间),以及其他一些与此问题无关的内容。

我想从 Alpha 中获取所有特殊行,加上对于每个特殊行我想从 Alpha 中获取与同一行 Beta 相关联的“上一个”非特殊行(也就是说,我想获得具有相同bravo_id 的Alpha 行和比特殊行的created 更早的最新created),并且我需要保留特殊行及其上一行链接。

目前我正在使用 n+1 个查询来执行此操作:

SELECT id, bravo_id, created FROM Alpha WHERE special IS NOT NULL

对初始查询中的每个结果进行类似这样的查询:

SELECT id, created FROM Alpha
WHERE special IS NULL AND bravo_id = BrvN AND created < CrtN ORDER BY created DESC

显然这是非常低效的。有没有一种方法可以在单个查询中检索此信息,将每个特殊行及其前一个非特殊行放在结果的单行中?

我们的产品同时支持 SQL Server(如果相关,则为 2008 R2)和 Oracle(如果相关,则为 11g),因此适用于这两者的查询将是理想的,但只查询两者中的一个就可以了。

编辑:“创建”可能是用词不当。该列中的日期时间是创建引用对象的时间,而 不是 是它被输入数据库的时间(可能是几秒到几年后的任何时间)。基于 created 列的 Alpha 行排序与基于 id 列的排序(这是传统的递增标识/序列)几乎没有相关性。

【问题讨论】:

在 Oracle(或 SQL Server 2012)中,您可以使用 lag() 函数从“上一个”行中获取值。 【参考方案1】:
SELECT a.Id, a.Bravo_Id, a.Created, d.Id, d.Created FROM @Alpha a
OUTER APPLY
(
    SELECT TOP 1 da.id, da.Created 
    FROM @Alpha da
    WHERE da.Special IS NULL
        AND da.Bravo_Id = a.Bravo_Id
        AND da.Created < a.Created
    ORDER BY da.Created DESC
) d
WHERE a.Special IS NOT NULL

您可以使用 apply (ms sql server query) 绑定这两个查询

【讨论】:

【参考方案2】:

这适用于 SQL Server 和 Oracle:

select A.id, A.bravo_id, A.created, B.id, B.created
from Alpha A
left join Alpha B on A.bravo_id = B.bravo_id
                 and B.created < A.created
                 and B.special is null
where A.special is not null
  and (B.created is null or
       B.created = (select max(S.created)
                    from Alpha S
                    where S.special is null
                      and S.bravo_id = A.bravo_id
                      and S.created < A.created))

它在所有行中使用相同的外键并创建了一个较低/较旧的连接,然后使用 where 子句将它们过滤掉(注意不要排除没有较旧行的 A 行)。

【讨论】:

【参考方案3】:

很遗憾,SQL Server 2008 不支持累积和。这是解决问题的方法。

对于 Alpha 中的每一行,计算 alpha 之后的特殊行数。这将分配一个分组。在组内,然后使用row_number() 枚举值,并选择前两个。

select a.*
from (select a.*,
             row_number() over (partition by bravo, grp order by id desc) as seqnum
      from (select a.*,
                   (select count(*)
                    from alpha a2
                    where a2.bravo = a.bravo and a2.special = 1 and
                          a2.id >= a.id
                   ) as grp
            from alpha a
           ) a
     ) a
where seqnum <= 2;

在 Oracle(或 SQL Server 2012)中,您可以这样写:

select a.*
from (select a.*,
             row_number() over (partition by bravo, grp order by id desc) as seqnum
      from (select a.*,
                   sum(case when special = 1 then 1 else 0 end) over (partition by bravo order by id desc
                   ) as grp
            from alpha a
           ) a
     ) a
where seqnum <= 2;

【讨论】:

这似乎有潜力,但我意识到我没有正确解释created 列的使用。请查看我对原始问题的编辑,并检查您的答案是否需要调整。

以上是关于如何从包含日期时间列的表中获取某些行和“先前”行?的主要内容,如果未能解决你的问题,请参考以下文章

如何从 EF 中的表中仅选择某些字段

如何从具有唯一列的表中获取行,由另一列决定

使用SQL获取行和列取决于值? [重复]

从 SQL Server 中 4 列的表中获取每组的前 N ​​行

如何将包含现有行和新行的新列添加到表中?

MySql操作「数据查询」-20211222