sql server merge rows and get latest value - sql

I have a table like this,
Id A B C D touchedwhen
1 NULL yes NULL yes 2015-02-26 14:10:01.870
1 NULL NULL no no 2015-02-26 14:10:40.370
and need to merge them in to one row like this,
Id A B C D touchedwhen
1 NULL yes no no 2015-02-26 14:10:40.370.
Note : if value is present in both rows take the latest one by date..
Tried this query:
select id,
max(a),
max(b),
max(c),
max(d), -- data in both rows hence take the latest
max(touchedwhen)
from
[dbo].[Table_1]
group by id;

If you were just looking for the last value per id, you could use:
last_value(a) over(
partition by id
order by touchedwhen desc
rows between unbounded preceding and unbounded following) as a
But you're looking for the last value per id that is not null. The only thing I can come up with is a four-part join, with each subquery calculating the latest non-null value for a, b, c, d:
select ids.id
, a.a
, b.b
, c.c
, d.d
, ids.tw
from (
select id
, max(touchedwhen) as tw
from YourTable
group by
id
) ids
left join
(
select row_number() over (
partition by id
order by touchedwhen desc) rn
, a
, id
from YourTable
where a is not null
) a
on a.id = ids.id
and a.rn = 1
left join
(
select row_number() over (
partition by id
order by touchedwhen desc) rn
, b
, id
from YourTable
where b is not null
) b
on b.id = ids.id
and b.rn = 1
left join
(
select row_number() over (
partition by id
order by touchedwhen desc) rn
, c
, id
from YourTable
where c is not null
) c
on c.id = ids.id
and c.rn = 1
left join
(
select row_number() over (
partition by id
order by touchedwhen desc) rn
, d
, id
from YourTable
where d is not null
) d
on d.id = ids.id
and d.rn = 1

-- Get all latest values in one row for each primary key
WITH CTE(row_num, Id, A, B, C, D, touchedwhen) AS (
SELECT ROW_NUMBER() OVER(PARTITION BY Id ORDER BY touchedwhen DESC), Id, A, B, C, D FROM Table_1)
UPDATE CTE SET
A = (SELECT TOP 1 t.A FROM Table_1 t WHERE t.A IS NOT NULL AND t.Id = CTE.Id ORDER BY t.touchedwhen DESC),
B = (SELECT TOP 1 t.B FROM Table_1 t WHERE t.B IS NOT NULL AND t.Id = CTE.Id ORDER BY t.touchedwhen DESC),
C = (SELECT TOP 1 t.C FROM Table_1 t WHERE t.C IS NOT NULL AND t.Id = CTE.Id ORDER BY t.touchedwhen DESC),
D = (SELECT TOP 1 t.D FROM Table_1 t WHERE t.D IS NOT NULL AND t.Id = CTE.Id ORDER BY t.touchedwhen DESC)
WHERE row_num = 1
-- Delete extra rows per primary key after copying latest values to one row
WITH CTE(row_num) AS (
SELECT ROW_NUMBER() OVER(PARTITION BY Id ORDER BY touchedwhen DESC) FROM Table_1)
DELETE FROM CTE WHERE row_num > 1

Related

join two tables with the max date of the filled field

I have a scenario where i should get VAL from table B by joining A and B with the max date of the filled field, e.g:
A:
F1 F2 F3
-- -- --
1 2 t1
2 3 t2
B:
F1 F2 VAL date
---- ---- --- ----------
1 NULL v10 12/30/2020
1 NULL v11 01/31/2020
NULL 2 v20 02/28/2020
NULL 2 v22 03/30/2020
Desired result:
1 2 t1 v11 01/31/2020
2 3 t2 v22 03/30/2020
Thank you in advance.
You can use MAX() and FIRST_VALUE() window functions:
SELECT DISTINCT A.f1, A.f2, A.f3,
FIRST_VALUE(B.VAL) OVER (PARTITION BY COALESCE(B.f1, B.f2) ORDER BY "date" DESC) VAL,
MAX(B."date") OVER (PARTITION BY COALESCE(B.f1, B.f2)) "date"
FROM A INNER JOIN B
ON A.f1 = COALESCE(B.f1, B.f2)
See the demo.
select A.* , maxB.* , B.VAL
from
A
join B
on A.f1 = B.F1
or A.f2 = B.F2
join
(
select
F1, F2 ,VAL , max(date) date
from
B
group by F1,F2
) maxB
on
B.date = maxB.date
and (
maxB.f1 = B.F1
or maxB.f2 = B.F2
)
Try with this
SELECT
R.F1, R.F2, R.F3, R.VAL,R.date
FROM
(
SELECT
A.F1, A.F2, A.F3,
B.VAL,B.date, RANK() OVER(PARTITION BY A.F1,A.F2,A.F3 ORDER BY B.date DESC) as Rank
FROM A
JOIN B ON B.F1 = A.F1 OR B.F2 = A.F1
) R
WHERE
R.Rank = 1
You can use ROW_NUMBER() analytic function within the subquery
WITH AB AS
(
SELECT A.f1, A.f2, B.val, B."date",
ROW_NUMBER() OVER (PARTITION BY NVL(B.f1, B.f2) ORDER BY "date" DESC) AS rn
FROM A
JOIN B
ON A.f1=B.f1 OR A.f1=B.f2
)
SELECT f1,f2,val,"date"
FROM AB
WHERE rn = 1
If ties(equal values) for the date values is the case and all should be included within the result set, then replace ROW_NUMBER() with DENSE_RANK() function.
Demo

SQL get the last time number was set to 1

I have a sql table similar to the below. I would like to get the last time it was changed from 0 to 1 and the last time it changed back to 0 (highlighted below for an example id).
What I have tried is:
select * from Table t1 join Table t2 on t1.id = t2.id join Table t3 on t1.id = t3.id
where t1.flag = 1 and t2.flag = 0 and t3.flag
group by t1.id
having min(t1.createdtime) between max(t2.createdtime) and min(t3.createdtime)
For this dataset, you could use lag() to bring in the flag of the previous row, use it as a filter condition, and then aggregate:
select
id,
max(createdtime) createdtime,
flag
from (
select
t.*,
lag(flag) over(partition by id order by createdtime) lagflag
from mytable t
) t
where (flag = 0 and lagflag = 1) or (flag = 1 and lagflag = 0)
group by id, flag
You can use lag() to get the recent flag. So you can filter for changes, i.e. when the (current) flag and the recent flag relate as desired. To get only the most recent of the changes you can filter by row_number().
SELECT z.id,
z.createdtime,
z.flag
FROM (SELECT y.id,
y.createdtime,
y.flag FROM (SELECT x.id,
x.createdtime,
x.flag,
row_number() OVER (PARTITION BY x.id
ORDER BY x.createdtime DESC) rn
FROM (SELECT t.id,
t.createdtime,
t.flag,
lag(t.flag) OVER (PARTITION BY t.id
ORDER BY createdtime) recentflag
FROM elbat t) x
WHERE x.flag = 0
AND x.recentflag = 1) y
WHERE y.rn = 1
UNION ALL
SELECT y.id,
y.createdtime,
y.flag FROM (SELECT x.id,
x.createdtime,
x.flag,
row_number() OVER (PARTITION BY x.id
ORDER BY x.createdtime DESC) rn
FROM (SELECT t.id,
t.createdtime,
t.flag,
lag(t.flag) OVER (PARTITION BY t.id
ORDER BY createdtime) recentflag
FROM elbat t) x
WHERE x.flag = 1
AND x.recentflag = 0) y
WHERE y.rn = 1) z
ORDER BY z.createdtime DESC;
Use lag() in a CTE to get the previous flag for each row and then NOT EXISTS:
with cte as(
select *, lag(flag) over(partition by id order by createdtime) prevflag
from tablename
)
select c.id, c.createdtime, c.flag
from cte c
where c.flag <> c.prevflag
and not exists (
select 1 from cte
where id = c.id and flag = c.flag and prevflag = c.prevflag and createdtime > c.createdtime
)
order by c.createdtime
Or:
with cte as(
select *, lag(flag) over(partition by id order by createdtime) prevflag
from tablename
)
select id, max(createdtime) createdtime, flag
from cte
where flag <> prevflag
group by id, flag
order by createdtime
See the demo.
Results:
> id | createdtime | flag
> -: | :------------------ | ---:
> 5 | 2019-11-02 14:30:00 | 1
> 5 | 2020-08-01 14:30:00 | 0

Need Full Outer Join without having Cross Join

Need to join two table without having cross join between them.
The join condition need to be made on Tabl.month = Tab2.month
Input
Table1 Table2
Month ID Month ID
1 a 1 a
1 b 1 b
1 c 2 g
2 d 3 i
2 e 3 j
3 f 3 k
Output:
Month_Tab1 ID_Tab1 Month_Tab2 ID_Tab2
1 a 1 a
1 b 1 b
1 c Null Null
2 d 2 g
2 e Null Null
3 f 3 i
Null Null 3 j
Null Null 3 k
The above o/p is required, without cross join, have tried full outer but cross join is happening as the ID is duplicate in both Tables. Left/Right join also cannt be applicable as either of the table might have larger set of ID's.
You want a full join, but with row_number() to identify the matches:
select t1.month month_tab1, t1.id id_tab1, t2.month month_tab2, t2.id id_tab2
from (
select t.*, row_number() over(partition by month order by id) rn from table1 t
) t1
full join (
select t.*, row_number() over(partition by month order by id) rn from table2 t) t2
on t2.month = t1.month and t2.rn = t1.rn
You can use a full outer join:
select
a.month,
a.id,
b.month,
b.id
from (
select month, id,
row_number() over(partition by month order by id) as n
from table1
) a
full outer join (
select month, id,
row_number() over(partition by month order by id) as n
from table2
) b on b.month = a.month and b.n = a.n
order by coalesce(a.month, b.month), coalesce(a.n, b.n)

Is it possible to JOIN a table on the TOP 1 if there is no unique identifier?

For Example
SELECT
a.SomethingInCommon,
tbl1.Status AS Status1,
tbl2.Status AS Status2,
tbl3.Status AS Status3
FROM Maintable a
LEFT OUTER JOIN SecondTable tbl1 ON
tbl1.ID = (SELECT TOP 1 ID
FROM SecondTable SomethingInCommon = a.SomethingInCommon)
LEFT OUTER JOIN SecondTable tbl2 ON
tbl2.ID = (SELECT TOP 1 ID
FROM SecondTable WHERE SomethingInCommon = a.SomethingInCommon
AND ID NOT IN (SELECT TOP 1 ID
FROM SecondTABLE
WHERE SomethingInCommon = a.SomethingInCommon))
LEFT OUTER JOIN SecondTable tbl3 ON
tbl23.ID = (SELECT TOP 1 ID
FROM SecondTable
WHERE SomethingInCommon = a.SomethingInCommon
AND ID NOT IN (SELECT TOP 2 ID
FROM SecondTABLE WHERE SomethingInCommon = a.SomethingInCommon))
This query joins SecondTable three times to show a record like
SomethingInCommon | Status1 | Status2 | Status 3
Is there anyway to accomplish these results if SecondTable does not have the unique identifier column (ID) ?
Perhaps maybe creating a temporary unique ID on the fly?
If you don't have IDs but know the order you want, you could create artificial IDs using ROW_NUMBER() and then do your TOP 1's off of that.
WITH TEMP AS (
SELECT 3 a, 1 b UNION ALL
SELECT 2, 1 UNION ALL
SELECT 1, 1 UNION ALL
SELECT 2, 2 UNION ALL
SELECT 1, 2)
SELECT A, B, ROW_NUMBER() OVER (PARTITION BY B ORDER BY B ASC) as RowNumber FROM TEMP
;WITH TEMP AS (
SELECT 3 a, 1 b UNION ALL
SELECT 2, 1 UNION ALL
SELECT 1, 1 UNION ALL
SELECT 2, 2 UNION ALL
SELECT 1, 2)
SELECT A, B, ROW_NUMBER() OVER (ORDER BY A ASC) as RowNumber FROM TEMP
As Raphael said in the comment, this can be done with CTE like below
with cte
as
(
SELECT M.SomethingInCommon, S.ID, ROW_NUMBER() OVER ( Partition by S.SomethingInCommon ORDER BY S.ID desc) as rn
FROM Maintable M
LEFT JOIN SecondTable S
on M.SomethingInCommon = S.SomethingInCommon
)
SELECT cte.SomethingInCommon
case when rn =1 then cte.ID end as Status1,
case when rn =2 then cte.ID end as Status2,
case when rn =3 then cte.ID end as Status3
where rn <=3
If you want the top three statuses, then you can use conditional aggregation:
select m.somethingincommon,
max(case when seqnum = 1 then status end) as status1,
max(case when seqnum = 2 then status end) as status2,
max(case when seqnum = 3 then status end) as status3
from maintable m left join
(select s.*,
row_number() over (partition by s.somethingincommon order by (select NULL)) as seqnum
from secondtable
) s
on m.somethingincommon = s.somethingincommon
group by m.somethingincommon;
If you prefer, you can do this with multiple joins:
with s as (
select s.*,
row_number() over (partition by s.somethingincommon order by (select NULL)) as seqnum
from secondtable
)
select m.*, s1.status as status, s2.status as status2, s3.status as status3
from maintable m left join
s s1
on m.somethingincommon = s1.somethingincommon and
s1.seqnum = 1 left join
s s2
on m.somethingincommon = s2.somethingincommon and
s2.seqnum = 2 left join
s s3
on m.somethingincommon = s3.somethingincommon and
s3.seqnum = 3;

T-SQL using SUM for a running total

I have a simple table with some dummy data setup like:
|id|user|value|
---------------
1 John 2
2 Ted 1
3 John 4
4 Ted 2
I can select a running total by executing the following sql(MSSQL 2008) statement:
SELECT a.id, a.user, a.value, SUM(b.value) AS total
FROM table a INNER JOIN table b
ON a.id >= b.id
AND a.user = b.user
GROUP BY a.id, a.user, a.value
ORDER BY a.id
This will give me results like:
|id|user|value|total|
---------------------
1 John 2 2
3 John 4 6
2 Ted 1 1
4 Ted 2 3
Now is it possible to only retrieve the most recent rows for each user? So the result would be:
|id|user|value|total|
---------------------
3 John 4 6
4 Ted 2 3
Am I going about this the right way? any suggestions or a new path to follow would be great!
No join is needed, you can speed up the query this way:
select id, [user], value, total
from
(
select id, [user], value,
row_number() over (partition by [user] order by id desc) rn,
sum(value) over (partition by [user]) total
from users
) a
where rn = 1
try this:
;with cte as
(SELECT a.id, a.[user], a.value, SUM(b.value) AS total
FROM users a INNER JOIN users b
ON a.id >= b.id
AND a.[user] = b.[user]
GROUP BY a.id, a.[user], a.value
),
cte1 as (select *,ROW_NUMBER() over (partition by [user]
order by total desc) as row_num
from cte)
select id,[user],value,total from cte1 where row_num=1
SQL Fiddle Demo
add where statement:
select * from
(
your select statement
) t
where t.id in (select max(id) from table group by user)
also you can use this query:
SELECT a.id, a.user, a.value,
(select max(b.value) from table b where b.user=a.user) AS total
FROM table a
where a.id in (select max(id) from table group by user)
ORDER BY a.id
Adding a right join would perform better than nested select.
Or even simpler:
SELECT MAX(id), [user], MAX(value), SUM(value)
FROM table
GROUP BY [user]
Compatible with SQL Server 2008 or later
DECLARE #AnotherTbl TABLE
(
id INT
, somedate DATE
, somevalue DECIMAL(18, 4)
, runningtotal DECIMAL(18, 4)
)
INSERT INTO #AnotherTbl
(
id
, somedate
, somevalue
, runningtotal
)
SELECT LEDGER_ID
, LL.LEDGER_DocDate
, LL.LEDGER_Amount
, NULL
FROM ACC_Ledger LL
ORDER BY LL.LEDGER_DocDate
DECLARE #RunningTotal DECIMAL(18, 4)
SET #RunningTotal = 0
UPDATE #AnotherTbl
SET #RunningTotal=runningtotal = #RunningTotal + somevalue
FROM #AnotherTbl
SELECT *
FROM #AnotherTbl