Reputation: 23
I have the below assignments at work and wonder if anyone can help me out. I need to use cursors in order to update records based on next records. Whenever the key is not equal to 8, i need to check all the records after it where key starts with 8 and update date2 with its max date
My data looks like this:
╔══════════════════════════════════════╗
║ ID date1 date2 key ║
╠══════════════════════════════════════╣
║ 1 1/2/2014 5/2/2014 1 ║
║ 1 5/2/2014 8/2/2014 8 ║
║ 1 8/2/2014 9/2/2014 8 ║
║ 1 11/2/2014 12/2/2014 1 ║
║ 1 12/2/2014 14/2/2014 8 ║
║ 2 12/2/2014 14/2/2014 1 ║
║ 2 14/2/2014 17/2/2014 8 ║
║ 3 20/2/2014 23/2/2014 1 ║
╚══════════════════════════════════════╝
After being updated, data will look like:
╔══════════════════════════════════════╗
║ ID date1 date2 key ║
╠══════════════════════════════════════╣
║ 1 1/2/2014 9/2/2014 1 ║
║ 1 5/2/2014 8/2/2014 8 ║
║ 1 8/2/2014 9/2/2014 8 ║
║ 1 11/2/2014 14/2/2014 1 ║
║ 1 12/2/2014 14/2/2014 8 ║
║ 2 12/2/2014 17/2/2014 1 ║
║ 2 14/2/2014 17/2/2014 8 ║
║ 3 20/2/2014 23/2/2014 1 ║
╚══════════════════════════════════════╝
Upvotes: 0
Views: 109
Reputation: 77737
If there was one key 1 row per ID and all existing key 8 rows with the same ID value were related to it, you could try this method:
WITH maxdates AS (
SELECT
*,
maxdate2 = MAX(CASE [key] WHEN 8 THEN date2 END) OVER (PARTITION BY ID)
FROM dbo.atable
)
UPDATE maxdates
SET date2 = maxdate2
WHERE [key] = 1
AND maxdate2 IS NOT NULL
;
This is how it works. The maxdates
common table expression uses a window MAX function to determine the maximum date2
values per group (per ID in this case). If this was the original dataset:
╔═══════════════════════════════╗
║ ID date1 date2 key ║
╠═══════════════════════════════╣
║ 1 1/2/2014 5/2/2014 1 ║
║ 1 5/2/2014 8/2/2014 8 ║
║ 1 8/2/2014 9/2/2014 8 ║
║ 2 12/2/2014 14/2/2014 1 ║
║ 2 14/2/2014 17/2/2014 8 ║
║ 3 20/2/2014 23/2/2014 1 ║
╚═══════════════════════════════╝
the CTE would turn it into the following:
╔══════════════════════════════════════════╗
║ ID date1 date2 key maxdate2 ║
╠══════════════════════════════════════════╣
║ 1 1/2/2014 5/2/2014 1 9/2/2014 ║
║ 1 5/2/2014 8/2/2014 8 9/2/2014 ║
║ 1 8/2/2014 9/2/2014 8 9/2/2014 ║
║ 2 12/2/2014 14/2/2014 1 17/2/2014 ║
║ 2 14/2/2014 17/2/2014 8 17/2/2014 ║
║ 3 20/2/2014 23/2/2014 1 NULL ║
╚══════════════════════════════════════════╝
The UPDATE statement would first filter out rows that are not to be updated, i.e. those where key
is 8 as well as the key 1 rows that have no associated key 8 rows (determining them by the absent maxdate2
), resulting in this subset:
╔══════════════════════════════════════════╗
║ ID date1 date2 key maxdate2 ║
╠══════════════════════════════════════════╣
║ 1 1/2/2014 5/2/2014 1 9/2/2014 ║
║ 2 12/2/2014 14/2/2014 1 17/2/2014 ║
╚══════════════════════════════════════════╝
and then update date2
with maxdate2
.
Now, this method could still be applicable even if multiple key 1 rows per ID were allowed. You would simply need to come up with another criterion to recognise subgroups of relevant rows within the same ID group. That is, you would first need to turn a dataset like this:
╔═══════════════════════════════╗
║ ID date1 date2 key ║
╠═══════════════════════════════╣
║ 1 1/2/2014 5/2/2014 1 ║
║ 1 5/2/2014 8/2/2014 8 ║
║ 1 8/2/2014 9/2/2014 8 ║
║ 1 12/2/2014 14/2/2014 1 ║
║ 1 14/2/2014 17/2/2014 8 ║
║ 1 20/2/2014 23/2/2014 1 ║
╚═══════════════════════════════╝
into something like this:
╔═════════════════════════════════════════╗
║ ID date1 date2 key rangeID ║
╠═════════════════════════════════════════╣
║ 1 1/2/2014 5/2/2014 1 1 ║
║ 1 5/2/2014 8/2/2014 8 1 ║
║ 1 8/2/2014 9/2/2014 8 1 ║
║ 1 12/2/2014 14/2/2014 1 2 ║
║ 1 14/2/2014 17/2/2014 8 2 ║
║ 1 20/2/2014 23/2/2014 1 3 ║
╚═════════════════════════════════════════╝
then apply the method.
One way to add such a criterion would be to use a conditional running count, as in this query:
WITH partitions AS (
SELECT
*,
rangeID = COUNT(CASE [key] WHEN 1 THEN 1 END) OVER (PARTITION BY ID ORDER BY date1)
FROM dbo.atable
),
maxdates AS (
SELECT
*,
maxdate2 = MAX(CASE [key] WHEN 8 THEN date2 END) OVER (PARTITION BY ID, rangeID)
FROM partitions
)
UPDATE maxdates
SET date2 = maxdate2
WHERE [key] = 1
AND maxdate2 IS NOT NULL
;
Basically, COUNT() OVER (... ORDER BY ...)
is a running count and the CASE expression makes it conditional: the count only increases on key 1 rows and remains the same on the others. The partitions
CTE obtains independent running count for every ID partition. As a result, you get rangeID values as illustrated previously.
The maxdates
CTE reads the result of partitions
and uses the rangeID values as the additional criterion I was talking about. The rest of the second query follows the first query's logic.
A live demonstration of this method can be found at SQL Fiddle.
Related manual pages that might be helpful:
Upvotes: 2
Reputation: 5316
You should not need a CURSOR
.
Are you trying to do the following?
(NB - I've arranged the dates as dd/mm/yyyy, this is how it is done on other planets)
DECLARE @MyData TABLE (ID INT, date1 DATE, date2 DATE, [key] INT)
INSERT INTO @MyData (ID, date1, date2, [key])
SELECT 1, '2/1/2014', '2/5/2014', 1 UNION ALL
SELECT 1, '2/5/2014', '2/8/2014', 8 UNION ALL
SELECT 1, '2/8/2014', '2/9/2014', 8 UNION ALL
SELECT 2, '2/12/2014', '2/14/2014', 1 UNION ALL
SELECT 2, '2/14/2014', '2/17/2014', 8 UNION ALL
SELECT 3, '2/20/2014', '2/23/2014', 1
UPDATE MD
SET MD.date2 = DT.MaxDate2
FROM @MyData MD
JOIN
(
SELECT ID
,MaxDate2 = MAX(Date2)
FROM @MyData
WHERE [key] = 8
GROUP BY ID
) DT ON DT.ID = MD.ID
WHERE MD.[key] != 8
SELECT *
FROM @MyData
Upvotes: 0