开发者

Coalesce over Rows in MSSQL 2008,

I'm trying to determine the best approach here in MSSQL 2008.

Here is my sample data

TransDate  Id     Active
-------------------------
1/18 1pm   5      1    
1/18 2pm   5      0    
1/18 3pm   5      Null    
1/开发者_开发百科18 4pm   5      1    
1/18 5pm   5      0    
1/18 6pm   5      Null

If grouped by Id and ordered by the TransDate, I want the last Non Null Value for the Active Column, and the MAX of TransDate

SELECT MAX(TransDate) AS TransDate, 
       Id,
       --LASTNonNull(Active) AS Active

Here would be the results:

TransDate  Id  Active
---------------------    
1/18 6pm   5   0

It would be like a Coalesce but over the rows, instead of two values/columns.

There would be many other columns that would also have this similiar method applied, so I really don't want to make a seperate join for each of the columns.

Any ideas?


I'd probably use a correlated sub query.

SELECT MAX(TransDate)             AS TransDate,
       Id,
       (SELECT TOP (1) Active
        FROM   T t2
        WHERE  t2.Id = t1.Id
               AND Active IS NOT NULL
        ORDER  BY TransDate DESC) AS Active
FROM   T t1
GROUP  BY Id  

A way without

SELECT
    Id,
    MAX(TransDate) AS TransDate,
    CAST(RIGHT(MAX(CONVERT(CHAR(23),TransDate,121) + CAST(Active AS CHAR(1))),1) AS BIT) AS Active,
    /*You can probably figure out a more efficient thing to 
    compare than the above depending on your data. e.g.*/
    CAST(MAX(DATEDIFF(SECOND,'19500101',TransDate) * CAST(10 AS BIGINT) + Active)%10  AS BIT) AS Active2
FROM T
GROUP BY Id

Or following the comments would cross apply work better for you?

WITH T (TransDate, Id, Active, SomeOtherColumn) AS
(
select GETDATE(), 5, 1, 'A' UNION ALL
select 1+GETDATE(), 5, 0, 'B' UNION ALL
select 2+GETDATE(), 5, null, 'C' UNION ALL
select 3+GETDATE(), 5, 1, 'D' UNION ALL
select 4+GETDATE(), 5, 0, 'E' UNION ALL
select 5+GETDATE(), 5, null,'F'

),
T1 AS
(
SELECT MAX(TransDate) AS TransDate,
       Id
FROM   T
GROUP  BY Id  
)
SELECT T1.TransDate,
       Id,
       CA.Active AS Active,
       CA.SomeOtherColumn AS SomeOtherColumn
FROM   T1
CROSS APPLY (SELECT TOP (1) Active, SomeOtherColumn
        FROM   T t2
        WHERE  t2.Id = T1.Id
               AND Active IS NOT NULL
        ORDER  BY TransDate DESC) CA


This example should help, using analytical functions Max() OVER and Row_Number() OVER

create table tww( transdate datetime, id int, active bit)
insert tww select GETDATE(), 5, 1
insert tww select 1+GETDATE(), 5, 0
insert tww select 2+GETDATE(), 5, null
insert tww select 3+GETDATE(), 5, 1
insert tww select 4+GETDATE(), 5, 0
insert tww select 5+GETDATE(), 5, null

select maxDate as Transdate, id, Active
from (
    select *,
        max(transdate) over (partition by id) maxDate,
        ROW_NUMBER() over (partition by id
                order by case when active is not null then 0 else 1 end, transdate desc) rn
    from tww
) x
where rn=1

Another option, quite expensive, would be doing it through XML. For educational purposes only

select
    ID = n.c.value('@id', 'int'),
    trandate = n.c.value('(data/transdate)[1]', 'datetime'),
    active = n.c.value('(data/active)[1]', 'bit')
from
(select xml=convert(xml,
    (select id [@id],
        (   select *
            from tww t
            where t.id=tww.id
            order by transdate desc
            for xml path('data'), type)
    from tww
    group by id
    for xml path('node'), root('root'), elements)
)) x cross apply xml.nodes('root/node') n(c)

It works on the principle that the XML generated has each record as a child node of the ID. Null columns have been omitted, so the first column found using xpath (child/columnname) is the first non-null value similar to COALESCE.


You could use a subquery:

SELECT MAX(TransDate) AS TransDate
,      Id
,      (
       SELECT  TOP 1 t2.Active 
       FROM    YourTable t2
       WHERE   t1.id = t2.id
               and t2.Active is not null 
       ORDER BY 
               t2.TransDate desc
       )
FROM   YourTable t1


I created a temp table named #temp to test my solution, and here is what I came up with:

transdate              id  active
1/1/2011 12:00:00 AM    5   1
1/2/2011 12:00:00 AM    5   0
1/3/2011 12:00:00 AM    5   null
1/4/2011 12:00:00 AM    5   1
1/5/2011 12:00:00 AM    5   0
1/6/2011 12:00:00 AM    5   null
1/1/2011 12:00:00 AM    6   2
1/2/2011 12:00:00 AM    6   3
1/3/2011 12:00:00 AM    6   null
1/4/2011 12:00:00 AM    6   2
1/5/2011 12:00:00 AM    6   null

This query...

select max(a.transdate) as transdate, a.id, (
  select top (1) b.active
  from #temp b
  where b.active is not null
  and b.id = a.id
  order by b.transdate desc
) as active
from #temp a
group by a.id

Returns these results.

transdate              id  active
1/6/2011 12:00:00 AM    5   0
1/5/2011 12:00:00 AM    6   2


Assuming a table named "test1", how about using ROW_NUMBER, OVER and PARTITION BY?

SELECT transdate, id, active FROM 
    (SELECT transdate, ROW_NUMBER() OVER(PARTITION BY id ORDER BY transdate desc) AS rownumber, id, active  
     FROM test1
     WHERE active is not null) a 
WHERE a.rownumber = 1
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜