Jim Horn
asked on
To Double Cursor, or Not to Double Cursor...
... that is the question.
I'm working with some flight data at a SQL Server 2008R2 client, and specifically I have a requirement to come up with the ultimate origin (OrigApt) and destination (DestApt) for a given flight.
But some flights have one or more connections, such as in the below image, where my requirement is to come up with one row where OriginApt = YKM and DestApt=SLC, ignoring all of the connecting flights (i.e. SEA and BOI), which I'll define for now as any leg where the GapInMinutes (i.e. time between landing and next flight taking off) is less than 240 minutes.
So .. Does anyone have any ideas on how to pull this off other than a double cursor, with the outer cursor being the PK for the overall flight (not shown in the image), the inner cursor being each leg in the image, and incrimenting a number whenever there is a GapInMinutes > 240?
Thanks in advance.
Jimbo
I'm working with some flight data at a SQL Server 2008R2 client, and specifically I have a requirement to come up with the ultimate origin (OrigApt) and destination (DestApt) for a given flight.
But some flights have one or more connections, such as in the below image, where my requirement is to come up with one row where OriginApt = YKM and DestApt=SLC, ignoring all of the connecting flights (i.e. SEA and BOI), which I'll define for now as any leg where the GapInMinutes (i.e. time between landing and next flight taking off) is less than 240 minutes.
So .. Does anyone have any ideas on how to pull this off other than a double cursor, with the outer cursor being the PK for the overall flight (not shown in the image), the inner cursor being each leg in the image, and incrimenting a number whenever there is a GapInMinutes > 240?
Thanks in advance.
Jimbo
SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
1. Is every flight round trip like this?
2. Is there ever more than 1 stop > 240 minutes?
2. Is there ever more than 1 stop > 240 minutes?
ASKER
>The relationship between the records you've shown is not clear
I hid the PK. The image is a single customer/booking, sorted by OriginDt, which I used to UPDATE LegNumber and GapInMinutes.
Yakima, WA to Salt Lake City, UT had one layover, going back had two.
>Is there a flight number that changes once the plane leaves the destination?
Yes but flight number is not relevant here as in the above example each of the five legs had a different flight number.
>1. Is every flight round trip like this?
No. I'm dealing with multiple scenarios where I have almost all of them covered, this is just a complex one.
>2. Is there ever more than 1 stop > 240 minutes?
Probably, but management has decided for now that that's the definition to use.
I hid the PK. The image is a single customer/booking, sorted by OriginDt, which I used to UPDATE LegNumber and GapInMinutes.
Yakima, WA to Salt Lake City, UT had one layover, going back had two.
>Is there a flight number that changes once the plane leaves the destination?
Yes but flight number is not relevant here as in the above example each of the five legs had a different flight number.
>1. Is every flight round trip like this?
No. I'm dealing with multiple scenarios where I have almost all of them covered, this is just a complex one.
- Single flight - Good
- Two legs, there and back - Good
- Two legs, three different airport codes - Good
- Only one layover for any given trip - Good, BUT
- More than one layover for any given trip - This question, not handled yet.
>2. Is there ever more than 1 stop > 240 minutes?
Probably, but management has decided for now that that's the definition to use.
This is probably laughable, but the only way I know of to do it :P
DECLARE @origin1 varchar(50), @dest1 varchar(50), @origin2 varchar(50), @dest2 varchar(50), @leg int
SET @origin1 = (SELECT TOP 1 OriginApt FROM flights ORDER BY OriginDt Asc)
SET @dest1 = (SELECT TOP 1 DestApt FROM flights ORDER BY GapInMinutes Desc, DestDt Asc)
SET @leg = (SELECT TOP 1 LegNumber FROM flights ORDER BY GapInMinutes Desc, DestDt Asc)
SET @origin2 = (SELECT OriginApt FROM flights WHERE LegNumber = @leg + 1)
SET @dest2 = (SELECT TOP 1 DestApt FROM flights ORDER BY LegNumber Desc)
SELECT @origin1 As "Out Origin", @dest1 As "Out Destination", @origin2 AS "Return Origin", @dest2 AS "Return Destination"
SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
All - I have a near-working solution based on zepher_hex's comment in #a41735249, so everyone hang tight while I nail this down and then I'll post the complete answer.
Also this client is on 2008R2, so I don't have the ability to use LEAD, LAG, and FIRST_VALUE.
TIA
-Jim
Also this client is on 2008R2, so I don't have the ability to use LEAD, LAG, and FIRST_VALUE.
TIA
-Jim
My solution only uses cross apply, but it even gets simpler only making use of the gap>240 rule;
It has another good side if there are two destinations with a longer stay both are listed as destinations.
Bye, Olaf.
Select f1.bookingid,f1.Origin, f2.Destination
From @flights f1
left join @flights f2 on f2.bookingId = f1.bookingid And f2.Gap>240
Where f1.LegNumber = 1
It has another good side if there are two destinations with a longer stay both are listed as destinations.
Bye, Olaf.
Were you able to come up with a solution? Or is there something further we can assist with?
ASKER
An accept is inbound on #a41735249, but I have to put a few fires out before I can dress up my code and post it in this question. Sorry for the delay. -Jim
I forgot to come back to this.
If #a41735249 produces the correct results with the provided data, take a look at this (Again, Oracle specific but should port to SQL Server).
If #a41735249 produces the correct results with the provided data, take a look at this (Again, Oracle specific but should port to SQL Server).
/*
drop table tab1 purge;
create table tab1(flightid number, legnumber number, originApt char(3), destApt char(3), gapInMinutes number);
insert into tab1 values(47,1,'A','C',80);
insert into tab1 values(47,2,'C','F',2518);
insert into tab1 values(47,3,'F','I',105);
insert into tab1 values(47,4,'I','J',230);
insert into tab1 values(47,5,'J','A',null);
insert into tab1 values(74,1,'A','C',60);
insert into tab1 values(74,2,'C','F',80);
insert into tab1 values(74,3,'F','I',105);
insert into tab1 values(74,4,'I','J',1447);
insert into tab1 values(74,5,'J','A',null);
commit;
*/
select flightid, max(origin) origin, max(dest) dest from (
select flightid, case when legnumber=1 then originapt end origin, case when gapInMinutes > 240 then destapt end dest
from tab1
)
group by flightid
/
SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
I'd propose to accept zephry & netminder's posts, but St. Jimbo is very active on EE so perhaps he can post the finalized code or make the accept decision on this.
ASKER
The solution is below, which is longer then desired but it works for flights with two 'hops' and ignoring connecting flights. As soon as we upgrade beyond 2008R2 I hope to revisit this code and rewrite using LEAD and LAG to remove the cursors.
If I remember correctly the existence of multiple 'legs' made CROSS APPLY not useful, but don't quote me on that.
Thanks to all that participated. Splitting points..
If I remember correctly the existence of multiple 'legs' made CROSS APPLY not useful, but don't quote me on that.
Thanks to all that participated. Splitting points..
DECLARE @KeyField1_parent varchar(17)
DECLARE @KeyField1 varchar(17)
Declare @LegNumber int, @LegGroupNumber int, @GapInMinutes int
Declare @i int = 1, @NextParentLeg bit = 0
DECLARE cFlight CURSOR FOR
SELECT DISTINCT l.KeyField1
FROM temp.leg l
LEFT JOIN temp.flight f ON l.KeyField1 = f.KeyField1
WHERE f.KeyField1 IS NULL -- Has not already been accounted for in MatchingRule's 1-3
ORDER BY KeyField1
OPEN cFlight
FETCH NEXT FROM cFlight INTO @KeyField1_parent
WHILE @@FETCH_STATUS = 0
BEGIN
--SELECT @KeyField1_parent
DECLARE cLeg CURSOR FOR
SELECT KeyField1, LegNumber, LegGroupNumber, GapInMinutes
FROM temp.leg
WHERE KeyField1 = @KeyField1_parent
ORDER BY LegNumber
--SELECT 'Outer' as label, KeyField1, LegNumber, LegGroupNumber, GapInMinutes
--FROM temp.leg
--WHERE KeyField1 = @KeyField1_parent
--ORDER BY LegNumber
OPEN cLeg
FETCH NEXT FROM cLeg INTO @KeyField1, @LegNumber, @LegGroupNumber, @GapInMinutes
WHILE (@@FETCH_STATUS = 0)
begin
-- SELECT 'Inner' as label, @KeyField1, @LegNumber, @LegGroupNumber, @GapInMinutes
IF @NextParentLeg = 1
begin
SET @i = @i + 1
SET @NextParentLeg = 0
end
IF @GapInMinutes >= 240
begin
SET @NextParentLeg = 1
end
UPDATE temp.leg
SET LegGroupNumber = @i
WHERE CURRENT OF cLeg
FETCH NEXT FROM cLeg INTO @KeyField1, @LegNumber, @LegGroupNumber, @GapInMinutes
END
CLOSE cLeg
DEALLOCATE cLeg
SELECT @i = 1, @NextParentLeg = 0
-- Get the next flight.
FETCH NEXT FROM cFlight
INTO @KeyField1_parent
END
CLOSE cFlight
DEALLOCATE cFlight
--SELECT *
--FROM temp.leg
--WHERE KeyField1 = @KeyField1_parent
--ORDEr BY KeyField1, LegNumber
-- 5 - More than two overall legs. Per Thomas chat pick the first Origin and the Destination with the largest GapInMinutes.
INSERT INTO temp.flight (KeyField1, City1, City2, l.OriginDt, MatchingRule)
SELECT f.KeyField1, minleg.OriginApt, maxleg.DestApt, maxleg.OriginDt, 5
FROM (SELECT DISTINCT KeyField1 FROM temp.leg WHERE LegGroupNumber > 2) f
LEFT JOIN (
SELECT KeyField1, OriginApt
FROM temp.leg
WHERE LegNumber = 1) minleg ON f.KeyField1 = minleg.KeyField1
LEFT JOIN (
SELECT l.KeyField1, l.DestApt, OriginDt
FROM temp.leg l
JOIN (SELECT KeyField1, MAX(GapInMinutes) as MaxGap FROM temp.leg GROUP BY KeyField1) maxgap
ON l.KeyField1 = maxgap.KeyField1 AND l.GapInMinutes = maxgap.MaxGap) maxleg ON f.KeyField1 = maxleg.KeyField1
INSERT INTO temp.flight_leg (KeyField1, City1, City2, l.OriginDt, MatchingRule)
SELECT l.KeyField1, l.OriginApt, l.DestApt, l.OriginDt, f.MatchingRule
FROM temp.leg l
JOIN temp.flight f ON f.KeyField1 = l.KeyField1
WHERE f.MatchingRule = 5
-- 6 and 7: Two overall legs, one or more layovers
IF OBJECT_ID('tempdb..temp.flight_with_layover') IS NOT NULL
DROP TABLE temp.flight_with_layover
CREATE TABLE temp.flight_with_layover (
KeyField1 varchar(17),
OriginApt1 char(3),
DestApt1 char(3),
OriginApt2 char(3),
DestApt2 char(3),
OriginDt1 datetime,
OriginDt2 datetime)
-- When SQL Server us upgraded beyond 2008R2 recode this query using LEAD and LAG.
;with scope as (
SELECT DISTINCT l.KeyField1
FROM temp.leg l
LEFT JOIN temp.flight f ON l.KeyField1 = f.KeyField1
WHERE f.KeyField1 IS NULL AND l.LegGroupNumber = 2),
MinMaxLegNumberOne as (
SELECT l.KeyField1, MIN(LegNumber) as MinLegNumberOne, MAX(LegNumber) as MaxLegNumberOne
FROM temp.leg l
JOIN scope s ON l.KeyField1 = s.KeyField1
WHERE LegGroupNumber = 1
GROUP BY l.KeyField1),
MinMaxLegNumberTwo as (
SELECT l.KeyField1, MIN(LegNumber) as MinLegNumberTwo, MAX(LegNumber) as MaxLegNumberTwo
FROM temp.leg l
JOIN scope s ON l.KeyField1 = s.KeyField1
WHERE LegGroupNumber = 2
GROUP BY l.KeyField1),
OriginAptOne as (
SELECT l.KeyField1, l.OriginApt, l.OriginDt as OriginDt1
FROM temp.leg l
JOIN MinMaxLegNumberOne one ON l.KeyField1 = one.KeyField1 AND l.LegNumber = one.MinLegNumberOne),
DestAptOne as (
SELECT l.KeyField1, l.DestApt
FROM temp.leg l
JOIN MinMaxLegNumberOne one ON l.KeyField1 = one.KeyField1 AND l.LegNumber = one.MaxLegNumberOne),
OriginAptTwo as (
SELECT l.KeyField1, l.OriginApt, l.OriginDt as OriginDt2
FROM temp.leg l
JOIN MinMaxLegNumberTwo one ON l.KeyField1 = one.KeyField1 AND l.LegNumber = one.MinLegNumberTwo),
DestAptTwo as (
SELECT l.KeyField1, l.DestApt
FROM temp.leg l
JOIN MinMaxLegNumberTwo one ON l.KeyField1 = one.KeyField1 AND l.LegNumber = one.MaxLegNumberTwo)
INSERT INTO temp.flight_with_layover (KeyField1, OriginApt1, DestApt1, OriginApt2, DestApt2, OriginDt1, OriginDt2)
SELECT DISTINCT
s.KeyField1,
o1.OriginApt as OriginApt1, d1.DestApt as DestApt1, o2.OriginApt as OriginApt2, d2.DestApt as DestApt2,
o1.OriginDt1, o2.OriginDt2
FROM temp.leg l
JOIN scope s ON l.KeyField1 = s.KeyField1
JOIN OriginAptOne o1 ON l.KeyField1 = o1.KeyField1
JOIN DestAptOne d1 ON l.KeyField1 = d1.KeyField1
JOIN OriginAptTwo o2 ON l.KeyField1 = o2.KeyField1
JOIN DestAptTwo d2 ON l.KeyField1 = d2.KeyField1
In all my years with SQL, I have NEVER had to use a cursor, so I suspect there is likely a way to get your results without one. My first guess would be a CTE, but the relationships I've mentioned above need to be explained before I can determine if that's a possibility. CTE's are ideal for recursive relationships.