Previous value and current value - sql

I would like to pivot the following data such that I can have a previous column and a current column based on the values in column rn. If there is only 1 record then the current and the previous values will be the same value.
(I am using ssms 2008)
CREATE TABLE #TEST1
(ACCT_ID INT, RN INT, LoadDate Date)
INSERT INtO #TEST1 VALUES (1, 1, '2016-12-21')
INSERT INtO #TEST1 VALUES (2, 1, NULL)
INSERT INtO #TEST1 VALUES (3, 1, '2017-10-06')
INSERT INtO #TEST1 VALUES (3, 2, NULL)
INSERT INtO #TEST1 VALUES (4, 1, '2016-12-21')
SELECT * FROM #TEST1
ACCT_ID RN LoadDate
1 1 2016-12-21
2 1 NULL
3 1 2017-10-06
3 2 NULL
4 1 2016-12-21
Based on the data above, I did a pivot table
SELECT ACCT_ID, [1] as Prev, [2] as Curr FROM
(
SELECT * fROM #TEST1 S
pivot( MAX(LoadDate) for RN IN ([1],[2]) ) U
)X
How can for example acct_id 1 and 4 have the same value in current as the previous value.

I guess you need something like this. It displays the Curr value if the Prev is empty.
select acct_id, curr, case when prev = ' ' then null else coalesce(prev, curr) end
from
(
SELECT acct_id,
max(CASE WHEN RN = 1 THEN loaddate END) as Curr,
max(CASE WHEN RN = 2 THEN coalesce(loaddate, ' ') END) as Prev
FROM #test1 t
GROUP BY acct_id
) t
demo

I'm not sure if pivot will work in this case, I apologize if you specifically require an answer using pivot.
Here I'm taking the MAX(RN) via subquery, then doing select statements for the previous and current LoadDate. The current date is the date provided by the MAX(RN). The previous date is the date of MAX(RN) - 1 if MAX(RN) is greater than 1, else it is the previous date where RN equals 1.
SELECT dT.ACCT_ID
,(SELECT T1.LoadDate FROM #TEST1 T1 WHERE T1.ACCT_ID = dT.ACCT_ID AND T1.RN = CASE WHEN dT.MaxRN = 1 THEN 1 ELSE dT.MaxRN - 1 END) [Prev]
,(SELECT T1.LoadDate FROM #TEST1 T1 WHERE T1.ACCT_ID = dT.ACCT_ID AND T1.RN = dT.MaxRN) [Curr]
FROM (
SELECT ACCT_ID
,MAX(RN) [MaxRN]
FROM #TEST1
GROUP BY ACCT_ID
) AS dT

I would use conditional aggregation:
select acct_id,
max(case when rn = 1 then loaddate end) as load_date,
max(case when rn = 2 then loaddate end) as load_date_prev
from #test1 t
group by acct_id;

Related

Group by a column and build a single result row based on condition

I need to group rows by account.
If there's only one row in a group, select it.
If there are multiple rows per group, select columns of the row with order_number equal to 4 but set order_number to 1.
myTable data:
account order_number status state
1111 4 ok full
2256 4 ok full
3344 1 NULL NULL
1111 1 NULL NULL
8743 4 ok full
2256 1 NULL NULL
Here's what I've tried:
select
account,
order_number,
status,
state,
case
when order_number = '1' then 'pass'
when order_number = '4' then 'fail'
end as ' TEST RESULTS '
from myTable
This is the result I'm trying to achieve:
account order_number status state
1111 1 ok full
2256 1 ok full
3344 1 NULL NULL
8743 4 ok full
here is simplest way & probably most performant solution:
select accounts
,case when cnt > 1 then 1 else order_number end order_number
,status,state
from (
select *
, row_number() over (partition by account order by case when order_number = 4 then 1 else 0 end desc) rn
, count(*) over (partition by account) cnt
) t
where rn = 1
You can do it this way:
Create dummy table for testing
[Create table test1(accounts varchar(10), order_number int, status varchar(10),state varchar(10))
insert into test1 values('1111',4,'ok','full')
insert into test1 values( '2256' , 4 , 'ok' , 'full')
insert into test1 values('3344' , 1 , NULL , NULL)
insert into test1 values('1111' , 1 , NULL , NULL)
insert into test1 values('8743' , 4 , 'ok' , 'full')
insert into test1 values('2256' , 1 , NULL , NULL)][1]
Query, no hard coded values
Select accounts,
order_number,
status,
state
from (
select row_number() over(partition by t1.accounts order by t1.order_number desc) rnum,
t1.accounts,
isnull(t2.order_number,t1.order_number) order_number ,
t1.status,
t1.state
from test1 t1
left join (select * from test1 where order_number=1) t2 on t1.accounts = t2.accounts and t1.order_number <> t2.order_number
) a
where rnum = 1
Result set
accounts order_number status state
---------- ------------ ---------- ----------
1111 1 ok full
2256 1 ok full
3344 1 NULL NULL
8743 4 ok full
UPDATE: Adding Test Result Column
Select accounts,
order_number,
status,
state,
[TEST RESULTS]
from (
select row_number() over(partition by t1.accounts order by t1.order_number desc) rnum,
t1.accounts,
isnull(t2.order_number,t1.order_number) order_number ,
t1.status,
t1.state,
case
when isnull(t2.order_number,t1.order_number) = '1' then 'pass'
when isnull(t2.order_number,t1.order_number) = '4' then 'fail'
end as 'TEST RESULTS'
from test1 t1
left join (select * from test1 where order_number=1) t2 on t1.accounts = t2.accounts and t1.order_number <> t2.order_number
) a
where rnum = 1
Just another option using WITH TIES in concert with the window functions min() over() and row_number() over()
Example
Select top 1 with ties
account
,order_number = min(order_number) over(partition by account)
,status
,state
From myTable
Order By row_number() over (partition by account order by order_number desc)
Results
account order_number status state
1111 1 ok full
2256 1 ok full
3344 1 NULL NULL
8743 4 ok full
I only have Access to work with. Output accomplished with:
Query1:
SELECT Q1.account, Q1.order_number, Q2.status, Q2.state
FROM (SELECT DISTINCT account, order_number FROM myTable WHERE order_number = 1) AS Q1
INNER JOIN (SELECT DISTINCT account, status, state FROM myTable WHERE order_number=4) AS Q2
ON Q1.account = q2.account;
Query2:
SELECT account, order_number, status, state FROM Query1
UNION SELECT account, order_number, status, state FROM myTable WHERE NOT account IN(SELECT account FROM Query1);
This query has the desired result, but I have doubts about the WHERE NOT EXISTS part, because I don't know what is the meaning of order_number and, if your real problem is more complex than your question, it may become complicated.
Just changed ok to 1 and full to 1
SELECT [T1].[account], [T1].[order_number], COALESCE([T2].[status], [T1].[status]) AS [status], COALESCE([T2].[state], [T1].[state]) AS [state]
FROM [dbo].[myTable] [T1]
LEFT JOIN [dbo].[myTable] [T2]
ON [T2].[account] = [T1].[account]
AND [T2].[order_number] = 4
WHERE NOT EXISTS (
SELECT 1
FROM [dbo].[myTable] [T3]
WHERE [T3].[account] = [T1].[account]
AND [T1].[order_number] = 4
AND [T3].[order_number] = 1
);
If order_number is always 1 and 4 then below could be the most optimized solution for your problem. Here first I have put a account wise sequence number for all rows starting from 1 in descending order of order_number. So If there is one row for any account number then it 's sequence number (rn) will be 1 and if there are more than one rows then row with order_number 4 will have the sequence number (rn) 1.
So we got our row to select. And to replace order_number 4 with 1 if it's not the only row we calculated row count for each account as column cnt. If cnt>1 and order_number is 4 then we replaced order_number with 1 using case when.
Schema and insert statements:
create table myTable(account int, order_number int, status varchar(10), state varchar(10));
insert into myTable values(1111, 4, 'ok', 'full');
insert into myTable values(2256, 4, 'ok', 'full');
insert into myTable values(3344, 1, NULL, NULL);
insert into myTable values(1111, 1, NULL, NULL);
insert into myTable values(8743, 4, 'ok', 'full');
insert into myTable values(2256, 1, NULL, NULL);
Query:
with cte as
(
select account,order_number,status,state,row_number()over(partition by account order by order_number desc)rn,
count(order_number)over(partition by account )cnt
from mytable
)select account,(case when order_number=4 and cnt>1 then 1 else order_number end) order_number,status,state
from cte where rn=1
Output:
account
order_number
status
state
1111
1
ok
full
2256
1
ok
full
3344
1
null
null
8743
4
ok
full
db<>fiddle here
You can achieve the above result by using Aggregate Function Like MIN & MAX
DECLARE #myTable TABLE (account int, order_number int, status varchar(10), state varchar(10))
INSERT INTO #myTable VALUES(1111, 4, 'ok', 'full');
INSERT INTO #myTable VALUES(2256, 4, 'ok', 'full');
INSERT INTO #myTable VALUES(3344, 1, NULL, NULL);
INSERT INTO #myTable VALUES(1111, 1, NULL, NULL);
INSERT INTO #myTable VALUES(8743, 4, 'ok', 'full');
INSERT INTO #myTable VALUES(2256, 1, NULL, NULL);
Query:
SELECT account,MIN(order_number) order_number,MAX(status) status,MAX(State) State
FROM #myTable
GROUP BY account

Swap two adjacent rows of a column in sql

I'm trying to solve this following problem:
Write a sql query to swap two adjacent rows in a column of a table.
Input table
Name Id
A 1
B 2
C 3
D 4
E 5
Output table
Name Id
A 2
B 1
C 4
D 3
E 5
Description:- 1 is associated with A and 2 with B, swap them, thus now 1 is associated with B and 2 with A, Similarly do for C and D, Since E doesn't has any pair, leave it as it is.
Note:- This may be solved using CASE Statements, but I am trying for a generalized solution, Say currently it is only 5 rows, it may be 10,20 etc..
Eg:
SELECT
*,CASE WHEN Name = A then 2 ELSEIF Name = B then 1 etc...
FROM YourTable
You can use window functions to solve this.
on MySQL (>= 8.0):
SELECT ID, IFNULL(CASE WHEN t.rn % 2 = 0 THEN LAG(Name) OVER (ORDER BY ID) ELSE LEAD(Name) OVER (ORDER BY ID) END, Name) AS Name
FROM (
SELECT ID, Name, ROW_NUMBER() OVER (ORDER BY ID) AS rn
FROM table_name
) t
demo on dbfiddle.uk
on SQL-Server:
SELECT ID, ISNULL(CASE WHEN t.rn % 2 = 0 THEN LAG(Name) OVER (ORDER BY ID) ELSE LEAD(Name) OVER (ORDER BY ID) END, Name) AS Name
FROM (
SELECT ID, Name, ROW_NUMBER() OVER (ORDER BY ID) AS rn
FROM table_name
) t
demo on dbfiddle.uk
If you have sql-server, you can try this.
DECLARE #YourTable TABLE (Name VARCHAR(10), Id INT)
INSERT INTO #YourTable VALUES
('A', 1),
('B', 2),
('C', 3),
('D', 4),
('E', 5)
;WITH CTE AS (
SELECT *, ROW_NUMBER()OVER(ORDER BY Name) AS RN FROM #YourTable
)
SELECT T1.Name, ISNULL(T2.Id, T1.Id) Id FROM CTE T1
LEFT JOIN CTE T2 ON T1.RN + CASE WHEN T1.RN%2 = 0 THEN - 1 ELSE 1 END = T2.RN
Result:
Name Id
---------- -----------
A 2
B 1
C 4
D 3
E 5
You didn't specify your DBMS, but the following is standard ANSI SQL.
You can use a values() clause to provide the mapping of the IDs and then join against that:
with id_map (source_id, target_id) as (
values
(1, 2),
(2, 1)
)
select t.name, coalesce(m.target_id, t.id) as mapped_id
from the_table t
left join id_map m on m.source_id = t.id
order by name;
Alternatively if you only want to specify the mapping once for one direction, you can use this:
with id_map (source_id, target_id) as (
values
(1, 2)
)
select t.name,
case id
when m.source_id then m.target_id
when m.target_id then m.source_id
else id
end as mapped_id
from the_table t
left join id_map m on t.id in (m.source_id, m.target_id)
order by name;
Online example: https://rextester.com/FBFH52231

Cumulative subtraction across rows

Table 1:
Table 2:
How can I subtract the Committed value (7) from the IncomingQuantity cumulatively across rows? So that the result would look like:
Thanks!
You need a cumulative sum and some arithmetic:
select t.*,
(case when running_iq - incomingquantity >= committed then 0
when running_iq > committed then running_iq - committed
else incomingquantity
end) as from_this_row
from (select t2.*, t1.committed,
sum(incomingquantity) over (order by rowid) as running_iq
from table1 t1 cross join
table2 t2
) t;
you can also make use of the built-in functions such as ROW_NUMBER(), LAST_VALUE(), and LAG() with CASE
here is an example :
DECLARE
#t1 TABLE ( ProductID VARCHAR(50), ICommitted INT)
INSERT INTO #t1 VALUES ('Some product', 7)
DECLARE
#t2 TABLE (RowID INT, DueDate DATE, IncommingQuantity INT)
INSERT INTO #t2 VALUES
(1,'2018-11-19', 5),
(2,'2018-11-20', 4),
(3,'2018-11-20', 4),
(4,'2018-11-20', 3),
(5,'2018-11-22', 12)
SELECT
RowID
, DueDate
, CASE
WHEN RowID = 1
THEN 0
WHEN RowID = LAST_VALUE(RowID) OVER(ORDER BY (SELECT NULL) )
THEN IncommingQuantity
WHEN ROW_NUMBER() OVER(PARTITION BY DueDate ORDER BY RowID) > 1
THEN IncommingQuantity
ELSE ICommitted - LAG(IncommingQuantity) OVER (ORDER BY RowID)
END IncommingQuantity
FROM #t2 t2
CROSS APPLY (SELECT t1.ICommitted FROM #t1 t1) e
I ended up doing this simply with WHILE loop inside a user function. The other solutions I would not work properly in 100% of cases

SQL group by if values are close

Class| Value
-------------
A | 1
A | 2
A | 3
A | 10
B | 1
I am not sure whether it is practical to achieve this using SQL.
If the difference of values are less than 5 (or x), then group the rows (of course with the same Class)
Expected result
Class| ValueMin | ValueMax
---------------------------
A | 1 | 3
A | 10 | 10
B | 1 | 1
For fixed intervals, we can easily use "GROUP BY". But now the grouping is based on nearby row's value. So if the values are consecutive or very close, they will be "chained together".
Thank you very much
Assuming MSSQL
You are trying to group things by gaps between values. The easiest way to do this is to use the lag() function to find the gaps:
select class, min(value) as minvalue, max(value) as maxvalue
from (select class, value,
sum(IsNewGroup) over (partition by class order by value) as GroupId
from (select class, value,
(case when lag(value) over (partition by class order by value) > value - 5
then 0 else 1
end) as IsNewGroup
from t
) t
) t
group by class, groupid;
Note that this assumes SQL Server 2012 for the use of lag() and cumulative sum.
Update:
*This answer is incorrect*
Assuming the table you gave is called sd_test, the following query will give you the output you are expecting
In short, we need a way to find what was the value on the previous row. This is determined using a join on row ids. Then create a group to see if the difference is less than 5. and then it is just regular 'Group By'.
If your version of SQL Server supports windowing functions with partitioning the code would be much more readable.
SELECT
A.CLASS
,MIN(A.VALUE) AS MIN_VALUE
,MAX(A.VALUE) AS MAX_VALUE
FROM
(SELECT
ROW_NUMBER()OVER(PARTITION BY CLASS ORDER BY VALUE) AS ROW_ID
,CLASS
,VALUE
FROM SD_TEST) AS A
LEFT JOIN
(SELECT
ROW_NUMBER()OVER(PARTITION BY CLASS ORDER BY VALUE) AS ROW_ID
,CLASS
,VALUE
FROM SD_TEST) AS B
ON A.CLASS = B.CLASS AND A.ROW_ID=B.ROW_ID+1
GROUP BY A.CLASS,CASE WHEN ABS(COALESCE(B.VALUE,0)-A.VALUE)<5 THEN 1 ELSE 0 END
ORDER BY A.CLASS,cASE WHEN ABS(COALESCE(B.VALUE,0)-A.VALUE)<5 THEN 1 ELSE 0 END DESC
ps: I think the above is ANSI compliant. So should run in most SQL variants. Someone can correct me if it is not.
These give the correct result, using the fact that you must have the same number of group starts as ends and that they will both be in ascending order.
if object_id('tempdb..#temp') is not null drop table #temp
create table #temp (class char(1),Value int);
insert into #temp values ('A',1);
insert into #temp values ('A',2);
insert into #temp values ('A',3);
insert into #temp values ('A',10);
insert into #temp values ('A',13);
insert into #temp values ('A',14);
insert into #temp values ('b',7);
insert into #temp values ('b',8);
insert into #temp values ('b',9);
insert into #temp values ('b',12);
insert into #temp values ('b',22);
insert into #temp values ('b',26);
insert into #temp values ('b',67);
Method 1 Using CTE and row offsets
with cte as
(select distinct class,value,ROW_NUMBER() over ( partition by class order by value ) as R from #temp),
cte2 as
(
select
c1.class
,c1.value
,c2.R as PreviousRec
,c3.r as NextRec
from
cte c1
left join cte c2 on (c1.class = c2.class and c1.R= c2.R+1 and c1.Value < c2.value + 5)
left join cte c3 on (c1.class = c3.class and c1.R= c3.R-1 and c1.Value > c3.value - 5)
)
select
Starts.Class
,Starts.Value as StartValue
,Ends.Value as EndValue
from
(
select
class
,value
,row_number() over ( partition by class order by value ) as GroupNumber
from cte2
where PreviousRec is null) as Starts join
(
select
class
,value
,row_number() over ( partition by class order by value ) as GroupNumber
from cte2
where NextRec is null) as Ends on starts.class=ends.class and starts.GroupNumber = ends.GroupNumber
** Method 2 Inline views using not exists **
select
Starts.Class
,Starts.Value as StartValue
,Ends.Value as EndValue
from
(
select class,Value ,row_number() over ( partition by class order by value ) as GroupNumber
from
(select distinct class,value from #temp) as T
where not exists (select 1 from #temp where class=t.class and Value < t.Value and Value > t.Value -5 )
) Starts join
(
select class,Value ,row_number() over ( partition by class order by value ) as GroupNumber
from
(select distinct class,value from #temp) as T
where not exists (select 1 from #temp where class=t.class and Value > t.Value and Value < t.Value +5 )
) ends on starts.class=ends.class and starts.GroupNumber = ends.GroupNumber
In both methods I use a select distinct to begin because if you have a dulpicate entry at a group start or end things go awry without it.
Here is one way of getting the information you are after:
SELECT Under5.Class,
(
SELECT MIN(m2.Value)
FROM MyTable AS m2
WHERE m2.Value < 5
AND m2.Class = Under5.Class
) AS ValueMin,
(
SELECT MAX(m3.Value)
FROM MyTable AS m3
WHERE m3.Value < 5
AND m3.Class = Under5.Class
) AS ValueMax
FROM
(
SELECT DISTINCT m1.Class
FROM MyTable AS m1
WHERE m1.Value < 5
) AS Under5
UNION
SELECT Over4.Class,
(
SELECT MIN(m4.Value)
FROM MyTable AS m4
WHERE m4.Value >= 5
AND m4.Class = Over4.Class
) AS ValueMin,
(
SELECT Max(m5.Value)
FROM MyTable AS m5
WHERE m5.Value >= 5
AND m5.Class = Over4.Class
) AS ValueMax
FROM
(
SELECT DISTINCT m6.Class
FROM MyTable AS m6
WHERE m6.Value >= 5
) AS Over4

Using SQL to get the previous rows data

I have a requirement where I need to get data from the previous row to use in a calculation to give a status to the current row. It's a history table. The previous row will let me know if a data has changed in a date field.
I've looked up using cursors and it seems a little complicated. Is this the best way to go?
I've also tried to assgin a value to a new field...
newField =(Select field1 from Table1 where "previous row") previous row is where I seem to get stuck. I can't figure out how to select the row beneath the current row.
I'm using SQL Server 2005
Thanks in advance.
-- Test data
declare #T table (ProjectNumber int, DateChanged datetime, Value int)
insert into #T
select 1, '2001-01-01', 1 union all
select 1, '2001-01-02', 1 union all
select 1, '2001-01-03', 3 union all
select 1, '2001-01-04', 3 union all
select 1, '2001-01-05', 4 union all
select 2, '2001-01-01', 1 union all
select 2, '2001-01-02', 2
-- Get CurrentValue and PreviousValue with a Changed column
;with cte as
(
select *,
row_number() over(partition by ProjectNumber order by DateChanged) as rn
from #T
)
select
C.ProjectNumber,
C.Value as CurrentValue,
P.Value as PreviousValue,
case C.Value when P.Value then 0 else 1 end as Changed
from cte as C
inner join cte as P
on C.ProjectNumber = P.ProjectNumber and
C.rn = P.rn + 1
-- Count the number of changes per project
;with cte as
(
select *,
row_number() over(partition by ProjectNumber order by DateChanged) as rn
from #T
)
select
C.ProjectNumber,
sum(case C.Value when P.Value then 0 else 1 end) as ChangeCount
from cte as C
inner join cte as P
on C.ProjectNumber = P.ProjectNumber and
C.rn = P.rn + 1
group by C.ProjectNumber
This really depends on what tells you a row is a "Previous Row". however, a self join should do what you want:
select *
from Table1 this
join Table2 prev on this.incrementalID = prev.incrementalID+1
If you have the following table
CREATE TABLE MyTable (
Id INT NOT NULL,
ChangeDate DATETIME NOT NULL,
.
.
.
)
The following query will return the previous record for any record from MyTable.
SELECT tbl.Id,
tbl.ChangeDate,
hist.Id,
hist.ChangeDate
FROM MyTable tbl
INNER JOIN MyTable hist
ON hist.Id = tbl.Id
AND hiost.ChangeDate = (SELECT MAX(ChangeDate)
FROM MyTable sub
WHERE sub.Id = tbl.Id AND sub.ChangeDate < tbl.ChangeDate)