讨厌的SQL查询:有没有一种方法可以在没有游标的情况下找到分组集中的第一行和最后一行?

时间:2012-09-10 21:17:12

标签: sql sql-server-2008 tsql cursor

我的数据如下:

data sample

我需要做的是,对于具有相同ClientId的记录,我需要对PlaceId不为空的连续行(使用CpId)进行分组,并找到每个行的第一行和最后一行group,以便我可以从第一行检索DateAdmitted值,从最后一行检索DateDischarged值。因此,上述数据需要像这样组织,然后根据我需要的值进行过滤:

enter image description here

使用上面的示例,我希望基于ClientId

的以下内容
ClientId    FirstCpIdInSet    DateAdmitted    LastCpIdInSet    DateDischarged
-----------------------------------------------------------------------------
1967        NULL              NULL            NULL             NULL
1983        45                1986-12-29      45               1987-10-09
1983        47                1990-10-01      49               2009-04-12
1983        52                2009-08-31      52               2009-11-30
1988        62                1997-12-15      65               2000-01-07

ClientId 1967可以从结果集中排除,因为它永远不会有PlaceId不为空的行。还有其他几点需要注意:

  • 这是从使用CpId作为IDENTITY创建的临时表中获取的,并且该表填充了严格的ORDER BY,因此CpId是顺序的需要的订单。
  • 对于那些PlaceId且单个ClientId是连续的行,DateAdmitted应该等于上一行中的DateDischarged

如果可能的话,我真的希望能够在没有光标的情况下做到这一点,但是在困惑了两天之后我就无法理解。这是在SQL Server 2008 R2上。

2 个答案:

答案 0 :(得分:2)

确实令人讨厌的查询。像大多数SQL问题一样,它以正确的顺序解决问题的不同方面。我的解决方案不使用游标。它确实使用外部应用和分区。

实现: row_number()over(xx order by yy)本身不起作用,因为yy通常跨越多个xx分区。

示例数据:

id  state
1   a
2   a
3   b
4   c

期望的范围:

1 <= x < 3
3 <= x < 4
4 <= x

步骤1 - 使用外部应用查找每行的下一个状态转换。这允许您根据所需的任何条件检查每一行的下一个值。此步骤可能会生成比您想要的更多信息。几行可以转换为相同的值。在此示例中,id 1和2在id 3处转换。

伪代码:

select t1.id, t1.state, t3.id, t3.state
from table1 t1
outer apply
(
  select 
    --only grab one row
    top 1 t2.id, t2.state
  from table1 t2 
  where 
    --grab a value that's generated after the current value.
    t1.id < t2.id 
    -- add whatever join logic you need for your case.
    and t1.memberid=t2.memberid 
    -- make sure you get the correct order, typically an identity or time
    order by t2.id asc
) T3 

此查询生成如下内容:

id  state id    state
1   a     3     b
2   a     3     b
3   b     4     c
4   c     null  null

我们不希望id = 2的行。

步骤2 - 通过转换列进行分区可以获得行号,当发生状态转换时,该值始终为1。只需按1过滤,即可进行状态转换。

初步结果:

row_number  id  state   id  state
1           1   a       3   b
2           2   a       3   b
1           3   b       4   c
1           4   c      null null

过滤结果:

row_number  id  state   id  state
1           1   a       3   b
1           3   b       4   c
1           4   c      null null

答案 1 :(得分:1)

你没有说出你的第一个基础和最后一个基础。我假设它是CPID。您可以使用排名功能执行此操作:

select ClientID, PlaceId,
       max(CpID) as max(CPId),
       min(case when seqnumasc = 1 then DateAdmitted end) as DateAdmitted,
       max(case when seqnumdesc = 1 then DateDischarged end) as DateDischarged
from (select t.*,
             row_number() over (partition by clientID, placeID order by cpid) as seqnumasc
             row_number() over (partition by clientID, placeID order by cpid desc) as seqnumdesc
      from t
     ) t
where placeID is not null
group by ClientID, placeID

这将按顺序放置nubmers以确定每个组中的第一行和最后一行。但是,为什么你不能只使用最小和最大日期添加和释放?

基于增强的信息。 。 。

现在问题似乎是根据以下条件定义记录的“集合”:

  • 连续CPID
  • 同一客户,同一家公司
  • 地方不为空

如果是这样,以下内容将为您提供“设置ID”。这基于从CPID中减去序列号,使用技巧将连续值汇集在一起​​。此差异是连续值的常量,提供设置ID。

select clientid, setid,
       min(DateAdmitted) as DateAdmitted,
       max(DateDischarged) as DateDischarged,
       min(cpid) as minCPID,
       max(cpid) as maxCPID
from (select clientid, setid, cpid,
             row_number() over (partition by clientid, setid order by cpid) as seqnum,
             count(*) over (partition by clientid, setid) as setsize
      from (select t.*,
                   (cpid - row_number() over (partition by clientid order by cpid)
                   ) as setid
            from t
            where PlaceID is not NULL
           ) t
    ) t
group by clientid, setid