Update previous row, get value from other row - sql-server

I'm very new with T-SQL and I need some help. I have a table1. How I can do this process?
Should I use nested cursor or what? Need an advice on this.
Pls refer pic here -- http://i.stack.imgur.com/CHYIg.gif
Here is my code so far:
DECLARE #get_invckey datetime;
SET #get_invckey = '2013-04-09 13:30:43.537'
UPDATE table1
SET RUNTIME = #get_invckey
FROM
(
SELECT
DCREPORTTIME
FROM table1
WHERE
LOCATIONAME = 'MYQ01_PRNT_A_SP3'
AND EVENT LIKE '%RUN%'
) newdata
WHERE
LOCATIONAME = 'MYQ01_PRNT_A_SP3'
AND EVENT = 'MACHINE B STOPPED'

Not complete answer, for you question is not complex enough.
Just quick and simple example how this could be achieved.
Some sample data
DECLARE #Table table
( dcreporttime datetime, event_name varchar(55))
insert INTO #Table
SELECT '2013-04-09 13:34:01', 'RUNNING'
union
SELECT '2013-04-09 13:34:19', 'STOPPED'
union
SELECT '2013-04-09 13:34:29', 'RUNNING'
union
SELECT '2013-04-09 13:34:39', 'RUNNING'
union
SELECT '2013-04-09 13:35:19', 'STOPPED'
union
SELECT '2013-04-09 13:35:29', 'RUNNING'
union
SELECT '2013-04-09 13:35:39', 'RUNNING'
union
SELECT '2013-04-09 13:36:19', 'RUNNING'
union
SELECT '2013-04-09 13:37:19', 'STOPPED'
union
SELECT '2013-04-09 13:37:39', 'RUNNING'
And the query. It uses CTE (Common Table Expression) and Ranking functions:
;with cte as
(
SELECT RANK() OVER(ORDER by dcreporttime) as RankN, *
FROM #Table
)
select a.dcreporttime, a.event_name, a.dcreporttime, b.dcreporttime, DATEDIFF(ms, a.dcreporttime, b.dcreporttime) as runtime
from cte a
-- shift by Rank to compare with previous row
join cte b on a.RankN = b.RankN + 1
This simply you get time since previous event. I hope this could point you to solution. If don't, please be more specific in what you need :)

I have updated the view as follows:
create view [dbo].[cm] as
WITH cteMain
AS
(SELECT DCREPORTTIME,
ROW_NUMBER() OVER (ORDER BY DCREPORTTIME) AS rn,
RUNTIME,
[EVENT] AS EVENT
FROM
table1),
cte2 as (SELECT
sLeg.rn,
slead.DCREPORTTIME AS leadvalue,
sLeg.DCREPORTTIME AS lagvalue,
m.EVENT
FROM
cteMain AS m
LEFT OUTER JOIN cteMain AS sLead
ON sLead.rn = m.rn+1
LEFT OUTER JOIN cteMain AS sLeg
ON sLeg.rn = m.rn-1)
SELECT cte2.leadvalue, cte2.lagvalue, cte2.rn, cte2.EVENT
FROM cte2
Then, I revised the update query to:
update table1
set RUNTIME = DATEDIFF(ms, lagvalue, leadvalue)
FROM cm
where leadvalue=table1.DCREPORTTIME
and table1.EVENT='MACHINE RUNNING'
I used the first three rows of yours as sample data and this is what I see after the UPDATE:
Is this what you are looking for? I realize, by the way, that this can all be done without a view, but, I found this to be easier to work with, as far as taking on the problem step by step.
I based my query on a query I found at the following link:
Query to simulate Lead function in SQL Server 2008
See SQLFiddle at: SQLFiddle for Update previous row, get value from other row

Related

Update a Table Based on Count from Another Table

I am trying to do a COUNT against a table using a date range, from another table. The count has to also match with a reference number, here's the data format:
[Rev Old New] (Table 1)
Id Start Finish Amount Calls
41 2018-01-01 2018-06-01 111.01
[Calls] (Table 2)
Id Date Amount
3 2018-05-05 12.1
41 2018-01-03 11.7
41 2018-06-11 12.9
I am quite new to MS SQL so apologies for my rather basic knowledge!
So, I want the count of rows in [Calls], where the Date is between the Start and Finish dates in [Rev Old New] and the ID is the same in both tables (it's a client ref)
I want to UPDATE [Rev Old New] with this value in [Calls]
Here's what I have so far, not working and probably nowhere near the right syntax!
UPDATE [Insight].[dbo].[Rev Old New]. t2
SET [Calls] =
(SELECT COUNT(CASE WHERE t1.Date BETWEEN t2.[Start] AND t2.[Finish])
FROM [Insight].[dbo].[Calls] t1
WHERE t1.[Id] = t2.[Id])
The error I get is this:
Msg 102, Level 15, State 1, Line 1
Incorrect syntax near 't2'.
Msg 156, Level 15, State 1, Line 3
Incorrect syntax near the keyword 'WHERE'.
You don't need the CASE statement, a simple WHERE will suffice:
UPDATE [Insight].[dbo].[Rev Old New]
SET [Rev Old New].[Calls] = (SELECT COUNT(*) FROM [Insight].[dbo].[Calls] t1
WHERE t1.Date BETWEEN [Rev Old New].[Start] AND [Rev Old New].[Finish])
this may help
CREATE TABLE #RevOldNew(Id BIGINT, Start DATETIME, Finish DATETIME, Amount BIGINT, Calls INT)
CREATE TABLE #Calls(Id BIGINT,[Date] DATETIME, AMOUNT BIGINT)
INSERT INTO #RevOldNew
SELECT 1,'2018-06-01','2018-06-15',NULL,NULL UNION ALL
SELECT 1,'2018-07-16','2018-07-31',NULL,NULL UNION ALL
SELECT 1,'2018-08-01','2018-08-15',NULL,NULL UNION ALL
SELECT 1,'2018-08-16','2018-08-31',NULL,NULL UNION ALL
SELECT 2,'2018-07-01','2018-07-15',NULL,NULL UNION ALL
SELECT 2,'2018-08-01','2018-08-15',NULL,NULL UNION ALL
SELECT 2,'2018-08-16','2018-08-31',NULL,NULL UNION ALL
SELECT 3,'2018-07-16','2018-07-31',NULL,NULL UNION ALL
SELECT 3,'2018-08-01','2018-08-15',NULL,NULL UNION ALL
SELECT 3,'2018-08-16','2018-08-31',NULL,NULL
INSERT INTO #Calls
SELECT 1,'2018-07-16',23 UNION ALL
SELECT 1,'2018-07-21',534 UNION ALL
SELECT 1,'2018-07-28',456 UNION ALL
SELECT 1,'2018-08-02',43 UNION ALL
SELECT 1,'2018-08-11',565 UNION ALL
SELECT 1,'2018-08-20',56 UNION ALL
SELECT 2,'2018-07-05',576 UNION ALL
SELECT 2,'2018-08-22',54 UNION ALL
SELECT 2,'2018-08-29',676 UNION ALL
SELECT 3,'2018-07-17',32 UNION ALL
SELECT 3,'2018-08-15',43
;with cte
As (
SELECT r.id,r.Start,r.Finish, SUM(c.AMOUNT) Amount, COUNT(c.id) calls
FROM #RevOldNew r
LEFT JOIN #Calls c on r.id=c.id and c.Date between r.Start and r.Finish
Group by r.id,r.Start,r.Finish
)
UPDATE r
SET r.Amount=c.Amount,
r.Calls=c.calls
FROM #RevOldNew r
JOIN cte c on c.id=r.id and c.Start=r.Start and c.Finish=r.Finish
SELECT * from #RevOldNew
DROP TABLE #RevOldNew
DROP TABLE #Calls
First you should avoid spaces in table names. It's not a good practice.
Then a query which solves your problem is :
update [Rev Old New]
set Calls=(select count(*) from Calls where [Rev Old New].id = Calls.id and Calls.date >= [Rev Old New].Start and Calls.date <= [Rev Old New].Finish)
where
select count(*) from Calls where [Rev Old New].id = Calls.id and Calls.date >= [Rev Old New].Start and Calls.date <= [Rev Old New].Finish
count the lines from Calls with the id in [Rev Old New] with date in Calls between Finish and Start (included) in [Rev Old New]
I hope this helps.

T-SQL - get only latest row for selected condition

I have table with measurement with column SERIAL_NBR, DATE_TIME, VALUE.
There is a lot of data so when I need them to get the last 48 hours for 2000 devices
Select * from MY_TABLE where [TIME]> = DATEADD (hh, -48, #TimeNow)
takes a very long time.
Is there a way not to receive all the rows for each device, but only the latest entry? Would this speed up the query execution time?
Assuming that there is column named deviceId(change as per your needs), you can use top 1 with ties with window function row_number:
Select top 1 with ties *
from MY_TABLE
where [TIME]> = DATEADD (hh, -48, #TimeNow)
Order by row_number() over (
partition by deviceId
order by Time desc
);
You can simply create Common Table Expression that sorts and groups the entries and then pick the latest one from there.
;WITH numbered
AS ( SELECT [SERIAL_NBR], [TIME], [VALUE], row_nr = ROW_NUMBER() OVER (PARTITION BY [SERIAL_NBR] ORDER BY [TIME] DESC)
FROM MY_TABLE
WHERE [TIME]> = DATEADD (hh, -48, #TimeNow) )
SELECT [SERIAL_NBR], [TIME], [VALUE]
FROM numbered
WHERE row_nr = 1 -- we want the latest record only
Depending on the amount of data and the indexes available this might or might not be faster than Anthony Hancock's answer.
Similar to his answer you might also try the following:
(from MSSQL's point of view, the below query and Anthony's query are pretty much identical and they'll probably end up with the same query plan)
SELECT [SERIAL_NBR] , [TIME], [VALUE]
FROM MY_TABLE AS M
JOIN (SELECT [SERIAL_NBR] , max_time = MAX([TIME])
FROM MY_TABLE
GROUP BY [SERIAL_NBR]) AS L -- latest
ON L.[SERIAL_NBR] = M.[SERIAL_NBR]
AND L.max_time = M.[TIME]
WHERE M.DATE_TIME >= DATEADD(hh,-48,#TimeNow)
Your listed column values and your code don't quite match up so you'll probably have to change this code a little, but it sounds like for each SERIAL_NBR you want the record with the highest DATE_TIME in the last 48 hours. This should achieve that result for you.
SELECT SERIAL_NBR,DATE_TIME,VALUE
FROM MY_TABLE AS M
WHERE M.DATE_TIME >= DATEADD(hh,-48,#TimeNow)
AND M.DATE_TIME = (SELECT MAX(_M.DATE_TIME) FROM MY_TABLE AS _M WHERE M.SERIAL_NBR = _M.SERIAL_NBR)
This will get you details of the latest record per serial number:
Select t.SERIAL_NBR, q.FieldsYouWant
from MY_TABLE t
outer apply
(
selct top 1 t2.FieldsYouWant
from MY_TABLE t2
where t2.SERIAL_NBR = t.SERIAL_NBR
order by t2.[TIME] desc
)q
where t.[TIME]> = DATEADD (hh, -48, #TimeNow)
Also, worth sticking DATEADD (hh, -48, #TimeNow) into a variable rather than calculating inline.

SQL Query based on occurrence of records

After a long time, I am getting a chance to post a SQL Server question here.
I have a table variable as shown below, in SQL Server 2005. This table is populated by a stored procedure written by some other team.
This is a order processing system. Each order can be accomplished by multiple processes by various departments, based on the OPRouteCode.
Taking example for OrderNo = 2, it has two OPRouteCode - but both these OPRouteCodes are using the same processes by same departments. They are considered equivalent OPRouteCodes.
On the other hand, for example OrderNo = 1, the processes and departments vary; hence they are not equivalent.
What is the best way to select only orders that has non-equivalent OPRouteCodes.
Note: If there is only one OPRouteCode, it is considered as equivalent only. Non-equivalence come only if there are more than one OPRouteCode.
What is the best SQL Server query to get this result? I couldn't write anything working after hours of effort.
DECLARE #OrderProcess TABLE (OrderNo Int,
OPRouteCode VARCHAR(5),
Department VARCHAR(10),
Process VARCHAR(20) )
--Order = 1 OPRouteCode = '0023'
INSERT INTO #OrderProcess
SELECT 1,'0023' ,'103','Receive'
UNION ALL
SELECT 1,'0023' ,'104','Produce'
UNION ALL
SELECT 1,'0023' ,'104','Pack'
UNION ALL
SELECT 1,'0023' ,'105','Ship'
--Order = 1 OPRouteCode = '0077'
INSERT INTO #OrderProcess
SELECT 1,'0077' ,'103','Receive'
UNION ALL
SELECT 1,'0077' ,'104','Produce'
UNION ALL
SELECT 1,'0077' ,'105','Ship'
--Order = 2 OPRouteCode = '0044'
INSERT INTO #OrderProcess
SELECT 2,'0044' ,'105','Receive'
UNION ALL
SELECT 2,'0044' ,'106','Ship'
--Order = 2 OPRouteCode = '0055'
INSERT INTO #OrderProcess
SELECT 2,'0055' ,'105','Receive'
UNION ALL
SELECT 2,'0055' ,'106','Ship'
Table Variable
Expected Output
Alright I got it this time. Sorry for the wrong answer before.
Select OrderNo,OPRouteCode
From (
select OrderNo,OPRouteCode, RANK() OVER(PARTITION BY OrderNo,Department ORDER BY Process ) 'Rnk'
from #OrderProcess
) a
Where Rnk =2

Update Only Rows With Shared/Similar Value

OK - I have a simple table - below - what I am trying to do is to identify only those rows that have a shared value - so anything that has a shared "apc" value - DO x else Do Y
CREATE TABLE #test (hcpcs varchar(10), apc varchar(100), rate money)
INSERT INTO #test (hcpcs, apc)
SELECT '97110', '8009'
UNION ALL
SELECT '71020', '8009'
UNION ALL
SELECT '85025', '8006'
So - from the above - all those rows that share "8009" - I will gram those rows for an update - they will share the same "rate". Row with "8006" will not be a part of that update
You want:
WHERE EXISTS (SELECT *
FROM #test t2
WHERE t2.apc = #test.apc
AND t2.hcpcs != #test.hcpcs)
Make sure apc is indexed so that this can be done effectively.
IMO the most comfortable way to get all desired results is using SQL-Server's OVER Klause.
select *, count(*) over (partition by apc) as CNT
from #test order by CNT desc
will return all rows along with the duplicate information (order clause is just used so you can easily very this).
To turn this into an update, I'd use a CTE:
with T1 as (select hcpcs, count(*) over (partition by apc) as CNT
from #test)
update #test
set rate=#newrate
from #test inner join t1 on #test.hcpcs=T1.hcpcs
where CNT > 1
(please note that the order by clause had to vanish - it's not allowed in CTE and would be useless anyway :))
Just change whatever you want to the CASE expression. You can use the OUTPUT clause to check what it does, then remove it in the actual query.
UPDATE T
SET rate = CASE WHEN SRC.CNT > 1 THEN rate*5.00 ELSE rate*2.00 END
OUTPUT inserted.apc, deleted.rate old_rate, inserted.rate NEW_rate -- This is just to show results for testing
FROM #test T
JOIN (SELECT apc, COUNT(*) CNT
FROM #test
GROUP BY apc) SRC ON SRC.apc = T.apc

SQL Server Comparing Subsequent Rows for Duplicates

I am trying to write a SQL Server query but have had no luck and was wondering if anyone may have any ideas on how to achieve my query.
What i'm trying to do:
I have a table with several columns naming the ones that i am dealing with TaskID, StatusCode, Timestamp. Now this table just holds tasks for one of our systems that run throughout the day and when something runs it gets a timestamp and the statuscode depending on the status for that task.
Sometimes what happens is the task table will be updated with a new timestamp but the statusCode will not have changed since the last update of the task so for two or more consecutive rows of a given task the statusCode can be the same. When i say consecutive rows i mean with regards to timestamp.
So example task 88 could have twenty rows at statusCode 2 after which the status code changes to something else.
Now what i am trying to do with no luck at the moment is to retrieve a list from this table of all the tasks and the statuscodes and the timestamps but in the case where i have more than one consecutive row for a task with the same statuscode i just want to take the first row with the lowest timestamp and ignore the rest of the row until the statuscode for that task changes.
To make it simpler in this case you can assume that i have a taskid which i am filtering on so i am just looking at a single task.
Does anyone have any ideas as to how i can do this or perhaps something that i coudl probably read to help me?
Thanks
Irfan.
This are a couple ways of getting what you want:
SELECT
T1.task_id,
T1.status_code,
T1.status_timestamp
FROM
My_Table T1
LEFT OUTER JOIN My_Table T2 ON
T2.task_id = T1.task_id AND
T2.status_timestamp < T1.status_timestamp
LEFT OUTER JOIN My_Table T3 ON
T3.task_id = T1.task_id AND
T3.status_timestamp < T1.status_timestamp AND
T3.status_timestamp > T2.status_timestamp
WHERE
T3.task_id IS NULL AND
(T2.status_code IS NULL OR T2.status_code <> T1.status_code)
ORDER BY
T1.status_timestamp
or
SELECT
T1.task_id,
T1.status_code,
T1.status_timestamp
FROM
My_Table T1
LEFT OUTER JOIN My_Table T2 ON
T2.task_id = T1.task_id AND
T2.status_timestamp = (
SELECT
MAX(status_timestamp)
FROM
My_Table T3
WHERE
T3.task_id = T1.task_id AND
T3.status_timestamp < T1.status_timestamp)
WHERE
(T2.status_code IS NULL OR T2.status_code <> T1.status_code)
ORDER BY
T1.status_timestamp
Both methods rely on there being no exact matches of the status_timestamp values (two rows can't have the same exact status_timestamp for a given task_id.)
Something like
select TaskID,StatusCode,Min(TimeStamp)
from table
group by TaskID,StatusCode
order by 1,2
Note that is statuscode can duplicate, you will need an additional field, but hopefully this can point you in the right direction...
Something like the following should get you in the right direction....
CREATE TABLE #T
(
TaskId INT
,StatusCode INT
,StatusTimeStamp DATETIME
)
INSERT INTO #T
SELECT 1, 1, '2009-12-01 14:20'
UNION SELECT 1, 2, '2009-12-01 16:20'
UNION SELECT 1, 2, '2009-12-02 09:15'
UNION SELECT 1, 2, '2009-12-02 12:15'
UNION SELECT 1, 3, '2009-12-02 18:15'
;WITH CTE AS
(
SELECT TaskId
,StatusCode
,StatusTimeStamp
,ROW_NUMBER() OVER (PARTITION BY TaskId, StatusCode ORDER BY TaskId, StatusTimeStamp DESC) AS RNUM
FROM #T
)
SELECT TaskId
,StatusCode
,StatusTimeStamp
FROM CTE
WHERE RNUM = 1
DROP TABLE #T

Resources