Solved

To Double Cursor, or Not to Double Cursor...

Posted on 2016-07-29
21
115 Views
Last Modified: 2016-09-09
... 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.

really-big-flight.png
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
0
Comment
Question by:Jim Horn
  • 5
  • 4
  • 4
  • +4
21 Comments
 
LVL 42

Expert Comment

by:zephyr_hex
ID: 41735135
The relationship between the records you've shown is not clear (is there a flightId or something that can be used to relate the legs?).  Also, it's not clear how the final destination is determined (I would have guessed it was the destination of the leg with the largest number, but you've stated it's SLC).

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.
0
 
LVL 76

Assisted Solution

by:slightwv (䄆 Netminder)
slightwv (䄆 Netminder) earned 50 total points
ID: 41735136
Flown a few times but am no expert in their record keeping systems.

Is there a flight number that changes once the plane leaves the destination?

From what I've seen the origin to destination has one flight number and the trip back has another.

So flight 2345 would span legs 1 and 2 and flight 6789 would cover legs 3,4 and 5.

That would probably make things a little too easy?

As you know, I live in Oracle but this should port to SQL Server pretty nicely, I hope.

I'm thinking there is another way but it is the first path I went down and wanted to post it:
select
                max(case when legnumber=1 then originapt end) origin, 
                max(case when legnumber=maxleg then destapt end) dest 
from (
select legnumber, originapt, destapt, max(case when gapinminutes > 240 then legnumber end) over() maxleg
from tab1
)
/

Open in new window

0
 
LVL 12

Expert Comment

by:Dustin Saunders
ID: 41735152
1. Is every flight round trip like this?
2. Is there ever more than 1 stop > 240 minutes?
0
 
LVL 65

Author Comment

by:Jim Horn
ID: 41735164
>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.
  • 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.
0
 
LVL 12

Expert Comment

by:Dustin Saunders
ID: 41735171
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"

Open in new window

0
 
LVL 76

Assisted Solution

by:slightwv (䄆 Netminder)
slightwv (䄆 Netminder) earned 50 total points
ID: 41735174
Another attempt:
select max(origin) origin, max(dest) dest from (
select case when legnumber=1 then originapt end origin, case when gapInMinutes = max(gapInMinutes) over() then destapt end dest
from tab1
)
/

Open in new window

0
 
LVL 26

Assisted Solution

by:Zberteoc
Zberteoc earned 50 total points
ID: 41735176
In the question you make 2 contradictory affirmations: firs legs where <240 min and then >= 240 min. Which one is real.? I would say that one "string" of flights is until the first leg that has a gap>240 min.

What is the indication that these flights(rows) are even related? Is there a flight/airplane ID that should be the same?

My intuition tells me that you don't need any cursor for this but you will have to provide a more clear information and data. There has to be a fly identification that is common to all related entries and a very well defined rule that will tell where a "one way" flight starts and where it ends.

At that moment is only a matter of ordering by dates all the rows that belong to a "one way" flight and pick up the first and the last row.

As a recommandation don't post just a pciture that shows data. Rather post some text from where at least we can copy/paste the values in order to built some working data sample.
0
 
LVL 12

Assisted Solution

by:Dustin Saunders
Dustin Saunders earned 50 total points
ID: 41735189
Here's another go (assuming the destination of the first leg is the origin of the second leg):
SELECT (SELECT TOP 1 OriginApt FROM flights) As "Origin1", (SELECT TOP 1 DestApt FROM flights ORDER BY GapInMinutes Desc) As "Destination", (SELECT DestApt FROM flights WHERE GapInMinutes IS NULL) AS "Destination 2"

Open in new window


The first origin is always the source, the second origin is always the first destination (which will have the biggest time between regardless of whether it's 240 or slightly more) and the last destination is always the final row.  Is that right?  I assumed the last one always has a NULL value for gap in minutes, but could desc sort by LegNumber?
0
 
LVL 42

Accepted Solution

by:
zephyr_hex earned 250 total points
ID: 41735249
Given the following data:

data
The following query:

;WITH cte AS (
SELECT
t.FlightId, t.LegNumber, t.Origin, t.Destination, t.Gap, 0 AS [Level]
FROM #tmp_flight t
WHERE LegNumber = 1

UNION ALL

SELECT 
c.FlightId,
c.LegNumber, c.Origin, c.Destination, c.Gap, [Level] + 1
FROM #tmp_flight c
INNER JOIN cte p ON p.FlightId = c.FlightId AND p.LegNumber < c.LegNumber
WHERE c.Gap > 240
)
SELECT c1.FlightId, c1.Origin, ISNULL(c2.Destination,c1.Destination) AS Destination
FROM cte c1
LEFT OUTER JOIN cte c2 ON c2.FlightId = c1.FlightId AND c2.[Level] = (SELECT MAX([Level]) FROM cte ci WHERE ci.FlightId = c2.FlightId)
WHERE c1.[Level] = 0

Open in new window


Shows the Origin and Destination of the legs with a gap under 240, without using a cursor:

result
0
 
LVL 42

Assisted Solution

by:zephyr_hex
zephyr_hex earned 250 total points
ID: 41735256
Since there isn't a good SQL Fiddle, I'll also include my queries for the example table and data, to make it easier for others to repro:

CREATE TABLE #tmp_flight (
FlightId INT,
LegNumber TINYINT,
Origin CHAR(1),
Destination CHAR(1),
Gap SMALLINT
)

INSERT INTO #tmp_flight
SELECT 47 AS FlightId, 1 AS LegNumber, 'A' AS Origin, 'C' AS Destination, 80 AS Gap;


INSERT INTO #tmp_flight (FlightId, LegNumber, Origin, Destination, Gap) VALUES (47,2,'C', 'F', 2518),
(47,3,'F', 'I', 105), (47,4,'I', 'J', 230),(47,5,'J', 'A', NULL);

INSERT INTO #tmp_flight
SELECT 74 AS FlightId, 1 AS LegNumber, 'A' AS Origin, 'C' AS Destination, 60 AS Gap;


INSERT INTO #tmp_flight (FlightId, LegNumber, Origin, Destination, Gap) VALUES (74,2,'C', 'F', 80),
(74,3,'F', 'I', 105), (74,4,'I', 'J', 1447),(74,5,'J', 'A', NULL);

Open in new window

0
How your wiki can always stay up-to-date

Quip doubles as a “living” wiki and a project management tool that evolves with your organization. As you finish projects in Quip, the work remains, easily accessible to all team members, new and old.
- Increase transparency
- Onboard new hires faster
- Access from mobile/offline

 
LVL 48

Assisted Solution

by:PortletPaul
PortletPaul earned 50 total points
ID: 41735343
Pity there isn't a provided set of data (hint) that does include relevant columns. As it is stated that flight numbers aren't relevant, but that the data relates to a customer/booking I have "repurposed" "flightid" to "bookingid" as the following logic partitions lead and lag by booking
**PostgreSQL 9.3 Schema Setup**:

    CREATE TABLE tmp_flight
        (bookingId int, LegNumber int, Origin varchar(1), Destination varchar(1), Gap int)
    ;
    
    INSERT INTO tmp_flight
        (bookingId, LegNumber, Origin, Destination, Gap)
    VALUES
        (47, 1, 'A', 'C', 80),
        (47, 2, 'C', 'F', 2518),
        (47, 3, 'F', 'I', 105),
        (47, 4, 'I', 'J', 230),
        (47, 5, 'J', 'A', NULL),
        (74, 1, 'A', 'C', 60),
        (74, 2, 'C', 'F', 80),
        (74, 3, 'F', 'I', 105),
        (74, 4, 'I', 'J', 1447),
        (74, 5, 'J', 'A', NULL)
    ;

Open in new window

SELECT
      *
FROM (
      SELECT
            *
          , LAG(gap, 1) OVER (PARTITION BY bookingId ORDER BY LegNumber)  AS prevgap
          , LEAD(gap, 1) OVER (PARTITION BY bookingId ORDER BY LegNumber) AS nextgap
      FROM tmp_flight
      ) x
WHERE    (prevgap IS NULL AND nextgap > 240)
      OR (prevgap > 240 AND nextgap IS NULL)
      OR gap > 240
;

Open in new window

    | bookingid | legnumber | origin | destination |    gap | prevgap | nextgap |
    |-----------|-----------|--------|-------------|--------|---------|---------|
    |        47 |         1 |      A |           C |     80 |  (null) |    2518 |
    |        47 |         2 |      C |           F |   2518 |      80 |     105 |
    |        74 |         4 |      I |           J |   1447 |     105 |  (null) |
    |        74 |         5 |      J |           A | (null) |    1447 |  (null) |

Open in new window

see  http://sqlfiddle.com/#!15/cf965/1
note: it is postgres but the syntax used is ok for mssql

NB: this is only providing the wanted origins, sorry it isn't complete
0
 
LVL 29

Assisted Solution

by:Olaf Doschke
Olaf Doschke earned 50 total points
ID: 41735722
The question seems a bit constructed, as typically a customer would come up with the Origin and Destination to plan the flights in the first place. So shouldn't that be stored somewhere os original request?

Can we reformulate that problem to get
1. OrigApt of the first flight
2. DestApt of the flight with the longest gap to the next flight?

All within the group of one bookingid?

Is a check necessary, the last Destination is the first Origin? For the moment I'd skip that.

You don't have lead or lag, but once data is in such a state, wouldn't it suffice to query 1st leg and leg with max gap?

Declare @flights as Table  (bookingId int, LegNumber int, Origin varchar(1), Destination varchar(1), Gap int);
    
INSERT INTO @flights (bookingId, LegNumber, Origin, Destination, Gap)
VALUES
 (47, 1, 'A', 'C', 80),
 (47, 2, 'C', 'F', 2518),   -- expected result 47,A,F
 (47, 3, 'F', 'I', 105),
 (47, 4, 'I', 'J', 230),
 (47, 5, 'J', 'A', NULL),
 (74, 1, 'A', 'C', 60),
 (74, 2, 'C', 'F', 80),
 (74, 3, 'F', 'I', 105),
 (74, 4, 'I', 'J', 1447),   -- expected result 74,A,J
 (74, 5, 'J', 'A', NULL)

Select f1.bookingid,f1.Origin, f2.Destination
From @flights f1
inner join @flights f2 on f2.bookingId = f1.bookingid
Cross Apply (select max(gap) as maxgap 
              from @flights where bookingID=f2.bookingid) mg
Where f1.LegNumber = 1 and f2.gap = mg.maxgap

Open in new window


Bye, Olaf.
0
 
LVL 65

Author Comment

by:Jim Horn
ID: 41737470
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
0
 
LVL 29

Expert Comment

by:Olaf Doschke
ID: 41737515
My solution only uses cross apply, but it even gets simpler only making use of the gap>240 rule;

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

Open in new window


It has another good side if there are two destinations with a longer stay both are listed as destinations.

Bye, Olaf.
0
 
LVL 42

Expert Comment

by:zephyr_hex
ID: 41742839
Were you able to come up with a solution?  Or is there something further we can assist with?
0
 
LVL 65

Author Comment

by:Jim Horn
ID: 41742848
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
0
 
LVL 76

Expert Comment

by:slightwv (䄆 Netminder)
ID: 41742869
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).

/*
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
/

Open in new window

1
 
LVL 42

Assisted Solution

by:zephyr_hex
zephyr_hex earned 250 total points
ID: 41744639
yup, I get the same results with your simpler approach, slightwv.

MS SQL requires an alias here:

query
0
 
LVL 12

Expert Comment

by:Dustin Saunders
ID: 41762929
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.
0
 
LVL 65

Author Comment

by:Jim Horn
ID: 41791830
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..

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

Open in new window

0

Featured Post

Find Ransomware Secrets With All-Source Analysis

Ransomware has become a major concern for organizations; its prevalence has grown due to past successes achieved by threat actors. While each ransomware variant is different, we’ve seen some common tactics and trends used among the authors of the malware.

Join & Write a Comment

Suggested Solutions

In this article—a derivative of my DaytaBase.org blog post (http://daytabase.org/2011/06/18/what-week-is-it/)—I will explore a few different perspectives on which week today's date falls within using Microsoft SQL Server. First, to frame this stu…
Nowadays, some of developer are too much worried about data. Who is using data, who is updating it etc. etc. Because, data is more costlier in term of money and information. So security of data is focusing concern in days. Lets' understand the Au…
Familiarize people with the process of retrieving data from SQL Server using an Access pass-thru query. Microsoft Access is a very powerful client/server development tool. One of the ways that you can retrieve data from a SQL Server is by using a pa…
Viewers will learn how the fundamental information of how to create a table.

747 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

12 Experts available now in Live!

Get 1:1 Help Now