Update value based on value in previous row - sql-server

I have the following table:
CREATE TABLE dbo.Persons
(
[Date] date null,
[PersonId] int null,
[Amount] int null,
[Value] int null
)
And here's some sample data:
INSERT INTO dbo.Persons ([Date], [PersonId], [Amount], [Value])
VALUES
('2020-01-01', 1, 200, NULL),
('2020-01-02', 1, 300, NULL),
('2020-01-03', 1, 400, NULL),
('2020-01-04', 1, 500, NULL),
('2020-01-01', 2, 200, NULL),
('2020-01-02', 2, 300, NULL),
('2020-01-03', 2, 400, NULL),
('2020-01-04', 2, 500, NULL),
('2020-01-01', 3, 0, NULL),
('2020-01-02', 3, 0, NULL),
('2020-01-03', 3, 0, NULL),
('2020-01-04', 3, 0, NULL)
My goal: update all of the values in the [Value] column based on values in other columns AND the previous value in [Value] column. Primary key is date+personId.
Explained in pseudo code, my logic needs to something like:
CASE
WHEN [Amount] > 200 AND previous row value IN [Value] = 1, then 2
WHEN [Amount] > 500 AND previous row value in [Value] = 2, then 3
WHEN [Date] > '2020-01-01' AND [Amount] = 500 AND previous row value in [Value] = 2, then 4
and so on - this captures the general T-SQL code logic..
I only want to update the [Value] column where [Value] is NULL also.
What is the play here? I've read about using LAG, While LOOP, Recursive CTE, etc., but unsure where to go.

You can use lag to reference the previous row's value. In your example data value would remain null though as your case logic depends on the previous row which is null to start.
However, this is how you might approach it with an updatable CTE
with u as (
select [Date], PersonId,
case
when Amount > 200 and Lag(Value) over(partition by PersonId order by [Date]) = 1 then 2
when Amount > 500 and Lag(Value) over(partition by PersonId order by [Date]) = 2 then 3
when [Date] > '20200101' and Amount = 500 and Lag(Value) over(partition by PersonId order by [Date])= 2 then 4
end NewValue
from Persons
)
update u set Value = NewValue;

You can add a row number and then self-join. You can extend this idea to Row-2, Row-3, etc.
With Qry1 (
Select -- Columns from Person table
, ROWNUMBER() OVER(PARTITION BY PersonId ORDER BY Date) As Seq
FROM Persons
)
Select -- Columns from ThisRow
-- Columns from PrevRow (values will be null if there is
-- no previous row for this PersonId and Date)
FROM Qry1 ThisRow
LEFT JOIN Qry1 PrevRow
ON ThisRow.PersonId = PrevRow.PersonId
AND ThisRow.Date = PrevRow.Date
AND ThisRow.Seq - 1 = PrevRow.Seq

Related

How to convert two rows into one column using pivot in sql

I want to convert two columns into one row using PIVOT FUNCTION in SQL database.
But I'm facing problems. I can convert 1 column into 1 row but not 2 columns into 1 row.
You can try this
Create Table Article (ArticleId Int, [Description] Varchar(10))
Insert Into Article Values (1, 'Test')
Create Table OrderForecast(ArticleId Int, [Week] Int, [Order] Int, Amount Int)
Insert Into OrderForecast Values (1, 51, 1, 0),(1, 52, 2, 150), (1, 1, 3, 0),(1, 2, 4, 200), (1, 3, 5,0)
Select ArticleId, [Description], Week51, Week52, Week1, Week2, Week3
from
(
select ArticleId, [Description], Amount, [Week]
from
(
SELECT OrderForecast.ArticleId, 'Week' + Convert(Varchar(10), OrderForecast.[Week]) as [Week], [Order], Amount,
Article.[Description] as [Description] FROM OrderForecast
Inner Join Article On OrderForecast.ArticleId = Article.ArticleId
)a
) d
pivot
(
max(Amount)
for [Week] in (Week51, Week52, Week1, Week2, Week3)
) piv;
You can find the demo Here

How to get the Previous & next row based on condition

I am trying to get the statement on fetching the previous and next rows of a selected row.
Declare #OderDetail table
(
Id int primary key,
OrderId int,
ItemId int,
OrderDate DateTime2,
Lookup varchar(15)
)
INSERT INTO #OderDetail
VALUES
(1, 10, 1, '2018-06-11', 'A'),
(2, 10, 2, '2018-06-11', 'BE'), --this
(3, 2, 1, '2018-06-04', 'DR'),
(4, 2, 2, '2018-06-04', 'D'), --this
(5, 3, 2, '2018-06-14', 'DD'), --this
(6, 4, 2, '2018-06-14', 'R');
DECLARE
#ItemId int = 2,
#orderid int = 10
Required output:
Input for the procedure is order id =10 and item id =2 and i need to check item-2 is in the any other order i.e only previous and next item of matched record/order as per order date
Is this what your after? (Updated to reflect edit [OrderDate] to question)
Declare #OderDetail table
(
Id int primary key,
OrderId int,
ItemId int,
OrderDate DateTime2,
Lookup varchar(15)
)
INSERT INTO #OderDetail
VALUES
(1, 10, 1, '2018-06-11', 'A'),
(2, 10, 2, '2018-06-11', 'BE'), --this
(3, 2, 1, '2018-06-04', 'DR'),
(4, 2, 2, '2018-06-04', 'D'), --this
(5, 3, 2, '2018-06-14', 'DD'), --this
(6, 4, 2, '2018-06-14', 'R');
declare #ItemId int=2 , #orderid int = 10;
Query
With cte As
(
Select ROW_NUMBER() OVER(ORDER BY OrderDate) AS RecN,
*
From #OderDetail Where ItemId=#ItemId
)
Select Id, OrderId, ItemId, [Lookup] From cte Where
RecN Between ((Select Top 1 RecN From cte Where OrderId = #orderid) -1) And
((Select Top 1 RecN From cte Where OrderId = #orderid) +1)
Order by id
Result:
Id OrderId ItemId Lookup
2 10 2 BE
4 2 2 D
5 3 2 DD
Another possible approach is to use LAG() and LEAD() functions, that return data from a previous and subsequent row form the same resul tset.
-- Table
DECLARE #OrderDetail TABLE (
Id int primary key,
OrderId int,
ItemId int,
OrderDate DateTime2,
Lookup varchar(15)
)
INSERT INTO #OrderDetail
VALUES
(1, 10, 1, '2018-06-11', 'A'),
(2, 10, 2, '2018-06-11', 'BE'), --this
(3, 2, 1, '2018-06-04', 'DR'),
(4, 2, 2, '2018-06-04', 'D'), --this
(5, 3, 2, '2018-06-14', 'DD'), --this
(6, 4, 2, '2018-06-14', 'R');
-- Item and order
DECLARE
#ItemId int = 2,
#orderid int = 10
-- Statement
-- Get previois and next ID for every order, grouped by ItemId, ordered by OrderDate
;WITH cte AS (
SELECT
Id,
LAG(Id, 1) OVER (PARTITION BY ItemId ORDER BY OrderDate) previousId,
LEAD(Id, 1) OVER (PARTITION BY ItemId ORDER BY OrderDate) nextId,
ItemId,
OrderId,
Lookup
FROM #OrderDetail
)
-- Select current, previous and next order
SELECT od.*
FROM cte
CROSS APPLY (SELECT * FROM #OrderDetail WHERE Id = cte.Id) od
WHERE (cte.OrderId = #orderId) AND (cte.ItemId = #ItemId)
UNION ALL
SELECT od.*
FROM cte
CROSS APPLY (SELECT * FROM #OrderDetail WHERE Id = cte.previousId) od
WHERE (cte.OrderId = #orderId) AND (cte.ItemId = #ItemId)
UNION ALL
SELECT od.*
FROM cte
CROSS APPLY (SELECT * FROM #OrderDetail WHERE Id = cte.nextId) od
WHERE (cte.OrderId = #orderId) AND (cte.ItemId = #ItemId)
Output:
Id OrderId ItemId OrderDate Lookup
2 10 2 11/06/2018 00:00:00 BE
4 2 2 04/06/2018 00:00:00 D
5 3 2 14/06/2018 00:00:00 DD
Update to given this data set: I see where you are going with this. Note that in SOME cases there IS no row before the given one - so it only returns 2 not 3. Here I updated the CTE version. Un-comment the OTHER row to see 3 not 2 as there is then one before the selected row with that Itemid.
Added a variable to demonstrate how this is better allowing you to get 1 before and after or 2 before/after if you change that number (i.e. pass a parameter) - and if less rows, or none are before or after it gets as many as it can within that constraint.
Data setup for all versions:
Declare #OderDetail table
(
Id int primary key,
OrderId int,
ItemId int,
OrderDate DateTime2,
Lookup varchar(15)
)
INSERT INTO #OderDetail
VALUES
(1, 10, 1, '2018-06-11', 'A'),
(2, 10, 2, '2018-06-11', 'BE'), --this
(3, 2, 1, '2018-06-04', 'DR'),
(4, 2, 2, '2018-06-04', 'D'), --this
(5, 3, 2, '2018-06-14', 'DD'), --this
(9, 4, 2, '2018-06-14', 'DD'),
(6, 4, 2, '2018-06-14', 'R'),
--(10, 10, 2, '2018-06-02', 'BE'), -- un-comment to see one before
(23, 4, 2, '2018-06-14', 'R');
DECLARE
#ItemId int = 2,
#orderid int = 2;
CTE updated version:
DECLARE #rowsBeforeAndAfter INT = 1;
;WITH cte AS (
SELECT
Id,
OrderId,
ItemId,
OrderDate,
[Lookup],
ROW_NUMBER() OVER (ORDER BY OrderDate,Id) AS RowNumber
FROM #OderDetail
WHERE
ItemId = #itemId -- all matches of this
),
myrow AS (
SELECT TOP 1
Id,
OrderId,
ItemId,
OrderDate,
[Lookup],
RowNumber
FROM cte
WHERE
ItemId = #itemId
AND OrderId = #orderid
)
SELECT
cte.Id,
cte.OrderId,
cte.ItemId,
cte.OrderDate,
cte.[Lookup],
cte.RowNumber
FROM ctE
INNER JOIN myrow
ON ABS(cte.RowNumber - myrow.RowNumber) <= #rowsBeforeAndAfter
ORDER BY OrderDate, OrderId;
You probably want the CTE method (See an original at the end of this) however:
Just to point out, this gets the proper results but is probably not what you are striving for since it is dependent on the row order and the item id not the actual row with those two values:
SELECT TOP 3
a.Id,
a.OrderId,
a.ItemId,
a.Lookup
FROM #OderDetail AS a
WHERE
a.ItemId = #ItemId
To fix that, you can use an ORDER BY and TOP 1 with a UNION, kind of ugly. (UPDATED with date sort and != on the id.)
SELECT
u.Id,
u.OrderId,
u.OrderDate,
u.ItemId,
u.Lookup
FROM (
SELECT
a.Id,
a.OrderId,
a.OrderDate,
a.ItemId,
a.Lookup
FROM #OderDetail AS a
WHERE
a.ItemId = #ItemId
AND a.OrderId = #orderid
UNION
SELECT top 1
b.Id,
b.OrderId,
b.OrderDate,
b.ItemId,
b.Lookup
FROM #OderDetail AS b
WHERE
b.ItemId = #ItemId
AND b.OrderId != #orderid
ORDER BY b.OrderDate desc, b.OrderId
UNION
SELECT top 1
b.Id,
b.OrderId,
b.OrderDate,
b.ItemId,
b.Lookup
FROM #OderDetail AS b
WHERE
b.ItemId = #ItemId
AND b.OrderId != #orderid
ORDER BY b.OrderDate asc, b.OrderId
) AS u
ORDER BY u.OrderDate asc, u.OrderId
I think its simple, you can check with min(Id) and Max(id) with left outer join or outer apply
like
Declare #ItemID int = 2
Select * From #OderDetail A
Outer Apply (
Select MIN(A2.Id) minID, MAX(A2.Id) maxID From #OderDetail A2
Where A2.ItemId =#ItemID
) I05
Outer Apply(
Select * From #OderDetail Where Id=minID-1
Union All
Select * From #OderDetail Where Id=maxID+1
) I052
Where A.ItemId =#ItemID Order By A.Id
Let me know if this helps you or you face any problem with it...
Regards,

SQL Server : adding complex rownumber

I was wondering if someone knows how I can get the missing results from the 2nd column ("As-IS-Rownumber", which are yellow). As example which shows the desired outcome, I added the 1st column("To-Be - Desired").
BTW I work with MS SQL.
I want a rownumber based on the columns KlantID and Repeat. However, when I use partition by, rank or dense_rank, I don't get the desired outcome, because of my last column.
I hope some1 can help me out.
DDL
create table tbl (
ToBeSubRow INT,
AsIsSubrow INT,
Rownumber INT,
KlantID INT,
Repeat CHAR(3)
)
insert tbl (ToBeSubRow, AsIsSubrow, Rownumber, KlantID, Repeat)
values (1,1,1,1,'NO'),
(2,null,2,1,'YES'),
(3,null,3,1,'YES'),
(1,1,4,1,'NO'),
(2,null,5,1,'YES'),
(1,1,5,2,'NO'),
(2,null,6,2,'YES'),
(3,null,7,2,'YES')
Thanks
Martijn
enter image description here
This keys on Repeat only
If a new KlantID does not start on no it breaks
declare #T table (Rownumber INT, Subrow INT, Unq INT, KlantID INT, Repeat CHAR(3));
insert #T (Rownumber, Subrow , Unq , KlantID , Repeat)
values (1, null, 1 , 1, 'NO'),
(2, null, null, 1, 'YES'),
(3, null, null, 1, 'YES'),
(4, null, 2, 1, 'NO'),
(5, null, null, 1, 'YES'),
(6, null, 1, 2, 'NO'),
(7, null, null, 2, 'YES'),
(8, null, null, 2, 'YES');
with CTEno as
( select T.Rownumber
, ROW_NUMBER() over (order by T.Rownumber) as r4
from #T T
where T.Repeat = 'No'
)
select t.*
, ISNULL(n.r4, (select top 1 n.r4 from CTEno n where n.Rownumber < t.Rownumber order by n.r4 desc)) as grp
, ROW_NUMBER() over (partition by (ISNULL(n.r4, (select top 1 n.r4 from CTEno n where n.Rownumber < t.Rownumber order by n.r4 desc)))
order by T.Rownumber) grpRow
from #T T
left join CTEno N
on T.Rownumber = N.Rownumber
order by T.Rownumber;
You need to add a column to use as a Group ID, like this:
--Add a column called Group_ID
ALTER TABLE ASIS ADD GROUP_ID INT NULL
--Populate Group_ID
UPDATE ASIS
SET Group_ID = CASE Rownumber WHEN 1 THEN 1
WHEN 2 THEN 1
WHEN 3 THEN 1
WHEN 4 THEN 2
WHEN 5 THEN 2
WHEN 6 THEN 3
WHEN 7 THEN 3
WHEN 8 THEN 3
END
--Use Row_Number() to get the values you desire
SELECT *, ROW_NUMBER() OVER(PARTITION BY Group_ID ORDER BY Rownumber) SubRow2
FROM ASIS

Query to get date rows older than a start date (not a simple WHERE)

I have a feeling this is quite simple, but I can't put my finger on the query. I'm trying to find all of the activities of an employee which corresponds to their start date in a specific location.
create table Locations (EmployeeID int, LocationID int, StartDate date);
create table Activities (EmployeeID int, ActivityID int, [Date] date);
insert into Locations values
(1, 10, '01-01-2010')
, (1, 11, '01-01-2012')
, (1, 11, '01-01-2013');
insert into Activities values
(1, 1, '02-01-2010')
, (1, 2, '04-01-2010')
, (1, 3, '06-06-2014');
Expected result:
EmployeeID LocationID StartDate EmployeeID ActivityID Date
1 10 '01-01-2010' 1 1 '02-01-2010'
1 10 '01-01-2010' 1 2 '04-01-2010'
1 11 '01-01-2013' 1 3 '06-06-2014'
So far, I have this, but it's not quite giving me the result I was hoping for. I somehow have to reference only the information from the most recent Location, which the la.StartDate <= a.Date does not filter out and includes information from older locations as well.
select *
from Locations la
inner join Activities a on la.EmployeeID = a.EmployeeID
and la.StartDate <= a.Date
Give this one a try:
with Locations as (
select
*
from (values
(1, 10, '01-01-2010')
, (1, 11, '01-01-2012')
, (1, 11, '01-01-2013')
) la (EmployeeID, LocationID, StartDate)
),
Activities as (
select
*
from (
values
(1, 1, '02-01-2010')
, (1, 2, '04-01-2010')
, (1, 3, '06-06-2014')
) a (EmployeeID, ActivityID, [Date])
)
select
la.*,
a.*
from Activities a
cross apply (
select
*
from (
select
la.*,
ROW_NUMBER() OVER (
PARTITION BY
EMPLOYEEID
ORDER BY
DATE DESC
) seqnum
from Locations la
where
la.EmployeeID = a.EmployeeID and
la.StartDate <= a.Date
) la
where
la.seqnum = 1
) la
Thank you all, but I managed to find the answer:
select *
from LocationAssociations la
inner join Activities a on la.EmployeeID = a.EmployeeID
and la.StartDate = (select max(StartDate) from LocationAssociations where StartDate >= la.StartDate and StartDate <= a.Date)

how to find row number of first non zero value in a column in SQL Server 2012

I have a table T1 with columns countryid, stateid, Value.
I need row number of stateid value which has the first non-zero value for every country. Data in the table is ordered by CountryId ASC, StateId ASC.
(Countryid, Stateid, Value)
(1, 11, 0)
(1, 12, 1)
(1, 13, 0)
(2, 21, 0)
(2, 22, 0)
(2, 23, 1)
(3, 31, 0)
(3, 32, 10)
(3, 33, 0)
(3, 34, 20)
I need output like below.
for countryid 1, row number I need as output should be 2.
for countryid 2, row number I need as output should be 3.
for countryid 3, row number I need as output should be 2.
Can be done using partitions
;With CTE_T1 As
(
Select
CountryID, StateID, Value,
Row_Number Over (Partition By CountryID Order By Value) As RK
From
T1
Where
IsNull(Values, 0) <> 0
)
Select *
From CTE_T1
Where RK = 1
The order by clause in the partition will be used to choose the record if there are more than one non zero values.
WITH FormattedT1 AS
(
SELECT Countryid, Stateid, Value, ROW_NUMBER() OVER(PARTITION BY Countryid ORDER BY ...) AS num
FROM T1
WHERE Value > 0
)
SELECT Countryid, Stateid, Value
FROM FormattedT1
WHERE num = 1
Guessing from required result set, as stated in the OP, the following query returns the desired 'row number' per countryid:
SELECT countryid, MIN(rn) AS rn
FROM (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY countryid
ORDER BY stateid) AS rn
FROM T1 ) t
WHERE Value <> 0
GROUP BY countryid
So, my guess is that to each record of T1 a row number is assigned within each countryid partition. Record with lowest stateid within a countryid partition has row number = 1, next record has row number = 2, etc.
Output based on sample data of OP:
countryid rn
---------------
1 2
2 3
3 2

Resources