SQL WITH keyword inside CURSOR - sql

Observing some strange behavior when using WITH keyword inside CURSOR:
WITH minuteList (aMinute) AS
(
SELECT #startTime UNION ALL
SELECT DATEADD(MINUTE, 1, aMinute)
FROM minuteList
WHERE aMinute < DATEADD(MINUTE, 9, #startTime)
)
SELECT * FROM minuteList
The above code works perfectly creating a table with one aMinute column and 10 datetime rows with 1 minute interval. However, the code below enters into an infinite loop printing only #startTime value endlessly.
DECLARE cursor1 CURSOR FOR
WITH minuteList (aMinute) AS
(
SELECT #startTime UNION ALL
SELECT DATEADD(MINUTE, 1, aMinute)
FROM minuteList
WHERE aMinute < DATEADD(MINUTE, 9, #startTime)
)
SELECT * FROM minuteList
OPEN cursor1
FETCH NEXT FROM cursor1 INTO #laterTime
WHILE ##FETCH_STATUS = 0 BEGIN
PRINT #laterTime
END
CLOSE cursor1;
DEALLOCATE cursor1;
Can someone explain what's happening and why the infinite loop occurs?

I have done a line-by-line analysis of your cursor call to explain why it goes into an infinite loop.
OPEN cursor1 // Opens your cursor
FETCH NEXT FROM cursor1 INTO #laterTime // Takes the first row returned by the
cursor and inserts it into the
variable #laterTime
WHILE ##FETCH_STATUS = 0 BEGIN // Checks if the next value has been
fetched and executes
the code inside the loop
PRINT #laterTime // Prints the current row
END // Ends the loop when FETCH_STATUS is
not 0 (when fetch fails).
CLOSE cursor1; // Closes the cursor.
The cause for the infinite loop is the fact that ##FETCH_STATUS is never changed because you have accessed only the first row from the cursor (the fetch has not failed because it has not been called more than once). After printing #laterTime, you need to fetch the next value of the cursor inside the loop. And only when that fetch fails will the loop terminate.
Consider this example as a reference.
Modified code will look like this:
OPEN cursor1
FETCH NEXT FROM cursor1 INTO #laterTime
WHILE ##FETCH_STATUS = 0 BEGIN
PRINT #laterTime
FETCH NEXT FROM cursor1 INTO #laterTime
END
CLOSE cursor1;

Related

Sybase looping not working - interactive sql

im trying the do a simple cursor that will give me data , executing the cursor in a 5 seconds interval .
but it only shows 1 row and stop... im working on sybase interactive sql , this is the query:
DECLARE #codigo INTEGER
DECLARE alarm_history_cur CURSOR FOR
SELECT AlarmHistoryID FROM DBA.AlarmHistory
WHERE DBA.AlarmHistory.Area = '1'
AND DBA.AlarmHistory.DateTimeOccurred < '2021-01-01 00:00:00.000'
OPEN alarm_history_cur
FETCH NEXT alarm_history_cur INTO #codigo
while ##sqlstatus = 0
BEGIN
WAITFOR DELAY '00:00:05'
SELECT * FROM DBA.AlarmHistory WHERE AlarmHistoryID = #codigo
END
BEGIN
FETCH NEXT alarm_history_cur
INTO #codigo
END
CLOSE alarm_history_cur
DEALLOCATE CURSOR alarm_history_cur
thanks in advance for further help.
Update :
im using sybase central - sql anywhere ..i will let some pictures to you
-##version pictureenter image description here
-original select
-what actually the syntax shows
enter image description here
enter image description here
The fetch command needs to be in the same begin/end pair associated with the while loop.
Instead of this:
while ##sqlstatus = 0
BEGIN
WAITFOR DELAY '00:00:05'
SELECT * FROM DBA.AlarmHistory WHERE AlarmHistoryID = #codigo
END
BEGIN
FETCH NEXT alarm_history_cur INTO #codigo
END
Try this:
while ##sqlstatus = 0
BEGIN
WAITFOR DELAY '00:00:05'
SELECT * FROM DBA.AlarmHistory WHERE AlarmHistoryID = #codigo
FETCH NEXT alarm_history_cur INTO #codigo
END

SQL count number of time series events, with some some start or stop entries missing

I have some start/stop events and I need to count the number of total events but sometimes a start or stop is missing, for example:
Time Event
10:50 START
10:52 STOP
10:59 START
11:01 STOP
11:45 STOP
Count(Event) Where Event='START'
Would return 2, I also need to count the missing START value, so the result should be 3. Any ideas on how this could be done? Thanks!
Two constraints must be met to enable event counting.
Two START-STOP periods cannot overlap.
Two consecutive and chronologically ordered START and STOP event cannot be possibly originated from two different events, namely START+(missing TOP) and (missing START)+STOP.
It the conditions are met, a simple state machine can be implemented to detect the "missing" events. Such a row-by-row logic could (almost always) be implemented using the cursor syntax.
N.B. To exemplify the generality of the cursor method you can also see other answers A (update columns), B (a tedious algo) I made. The code structures are highly similar.
Test Dataset
use [testdb];
if OBJECT_ID('testdb..test') is not null
drop table testdb..test;
create table test (
[time] varchar(50),
[event] varchar(50),
);
insert into test ([time], [event])
values ('10:50', 'START'),('10:52', 'STOP'),('10:59', 'START'),
('11:01', 'STOP'),('11:45', 'STOP'),('11:50', 'STOP'),('11:55', 'START');
select * from test;
Code
/* cursor variables */
-- storage for each row
declare #time varchar(50),
#event varchar(50),
#state int = 0, -- state variable
#count int = 0; -- event count
-- open a cursor ordered by [time]
declare cur CURSOR local
for select [time], [event]
from test
order by [time]
open cur;
/* main loop */
while 1=1 BEGIN
/* fetch next row and check termination condition */
fetch next from cur
into #time, #event;
-- termination condition
if ##FETCH_STATUS <> 0 begin
-- check unfinished START before exit
if #state = 1
set #count += 1;
-- exit loop
break;
end
/* program body */
-- case 1. state = 0 (clear state)
if #state = 0 begin
-- 1-1. normal case -> go to state 1
if #event = 'START'
set #state = 1;
-- 1-2. a STOP without START -> keep state 0 and count++
else if #event = 'STOP'
set #count += 1;
-- guard
else
print '[Error] Bad event name: ' + #event
end
-- case 2. start = 1 (start is found)
else if #state = 1 begin
-- 2-1. normal case -> go to state 0 and count++
if #event = 'STOP' begin
set #count += 1;
set #state = 0;
end
-- 2-2. a START without STOP -> keep state 1 and count++
else if #event = 'START'
set #count += 1;
-- guard
else
print '[Error] Bad event name: ' + #event
end
END
-- cleanup
close cur;
deallocate cur;
Result
print #count; -- correct answer: 5
Tested on SQL Server 2017 (linux docker image, latest version).
Well, you could count each start and then each "stop" where the preceding event is not a start:
select count(*)
from (select t.*,
lag(event) over (order by time) as prev_event
from t
) t
where event = 'start' or
(prev_event = 'stop' and event = 'stop');

sybase For loop that consists of an Query for every result got from the select statement

SELECT distinct(a.acct_num)
FROM customer_acct a,
customer_acct_history b LIKE "%000%"
WHERE a.acct_num *= b.acct_num AND acct_type='C'
Based on the Output of this query ,It returns a lot of customer account numbers . I am trying to do a select Query returning top 1 for every account number . I am planning to link the o/p of first query in the second sql like where a.acct_num=( output of first Query ) . I want to do this in a loop and select top 1 for every account number. Any help on how to proceed would be appreciated
Is a good place for the CURSOR use:
declare curs cursor
for
SELECT distinct(a.acct_num)
FROM customer_acct a,
customer_acct_history b LIKE "%000%"
WHERE a.acct_num *= b.acct_num AND acct_type='C'
OPEN curs
DECLARE #acct_num INT
fetch curs into #acct_num
/* now loop, processing all the rows
** ##sqlstatus = 0 means successful fetch
** ##sqlstatus = 1 means error on previous fetch
** ##sqlstatus = 2 means end of result set reached
*/
while (##sqlstatus != 2)
BEGIN
SELECT Sometning FROM somwhere where #acct_num = ...
fetch curs into #acct_num
END
GL!

cursor skips every other row

I have a cursor that works but skips every other record. My fetch next looks like this
OPEN DemandCur
While 1 = 1
BEGIN
FETCH NEXT FROM DemandCur INTO
#----,
#+++++
select #index = (select demand from TechCoDemand where Date = '2014-11-30')
IF #index <= 0 BREAK;
IF ##FETCH_STATUS <> 0 BREAK;
FETCH NEXT FROM DemandCur INTO
#---,
#++++
End
Close DemandCur
Deallocate DemandCur
I changed it to
FETCH NEXT
And it stopped skipping records but I get an error message after the query is done:
Msg 16916, Level 16, State 1, Line 121
A cursor with the name 'NEXT' does not exist.
Change your code to this....
OPEN DemandCur
FETCH NEXT FROM DemandCur INTO #----, #+++++
While ##FETCH_STATUS = 0
BEGIN
select #index = (select demand from TechCoDemand where Date = '2014-11-30')
IF #index <= 0 BREAK;
FETCH NEXT FROM DemandCur INTO #---, #++++
END
Close DemandCur
Deallocate DemandCur
As others have alluded to in the comments above, you're fetching twice within your while loop. While (ha!) you do need to "prime the pump" (so to speak) by fetching one row outside of the while loop first, here's an idiom that I like to use to avoid that entirely.
declare cursor foobar for
select ...
open foobar
while(1=1)
begin
fetch next from foobar into ...
if (##fetch_status <> 0)
break
--process results
end
close foobar
deallocate foobar
This has the benefit of only having to have one fetch statement to maintain but, more germane to this conversation, avoids your error entirely.

Cursor error in SQL

I'm trying to implement a cursor, & after solving many errors I've finally come to a point where it runs, but it goes into infinite loop...
I've put the image of table below as image.
Aim of cursor: to calculate bowling average & store in 'bowling_avg' column.
here's the cursor code :
DECLARE
CURSOR BOWL_AVG IS SELECT SID,MATCHES,BOWLING_AVG,WICKETS
FROM BOWLING_STATS ;
NSID BOWLING_STATS.SID%TYPE;
NMATCHES BOWLING_STATS.MATCHES%TYPE;
NBOWLING_AVG BOWLING_STATS.BOWLING_AVG%TYPE;
NWICKETS BOWLING_STATS.WICKETS%TYPE;
BEGIN
OPEN BOWL_AVG;
IF BOWL_AVG%ISOPEN THEN
LOOP
FETCH BOWL_AVG INTO NSID,NMATCHES,NBOWLING_AVG,NWICKETS;
EXIT WHEN BOWL_AVG%NOTFOUND;
IF BOWL_AVG%FOUND THEN
LOOP
UPDATE BOWLING_STATS SET BOWLING_AVG=NWICKETS/NMATCHES WHERE SID=NSID ;
EXIT WHEN BOWL_AVG%NOTFOUND;
END LOOP;
END IF;
END LOOP;
ELSE
DBMS_OUTPUT.PUT_LINE('UNABLE TO OPEN CURSOR');
END IF;
CLOSE BOWL_AVG;
END;
I'm running this in oracle database 10g.
I ask for assistance in finding the error.
Thanks in advance.
Adding whitespace to your code makes it clearer what you're doing:
declare
cursor bowl_avg is
select sid, matches, bowling_avg, wickets
from bowling_stats;
nsid bowling_stats.sid%type;
nmatches bowling_stats.matches%type;
nbowling_avg bowling_stats.bowling_avg%type;
nwickets bowling_stats.wickets%type;
begin
-- 1. Open Cursor
open bowl_avg;
-- 2. Check if Cursor is open
if bowl_avg%isopen then
-- 3. Loop
loop
-- 4. Get record
fetch bowl_avg into nsid, nmatches, nbowling_avg, nwickets;
-- 5. Exit if no records left
exit when bowl_avg%notfound;
-- 6. If there is a record
if bowl_avg%found then
-- 7. Loop
loop
update bowling_stats
set bowling_avg = nwickets / nmatches
where sid = nsid;
-- 8. Exit if there is no record.
exit when bowl_avg%notfound;
end loop;
end if;
end loop;
else
dbms_output.put_line('unable to open cursor');
end if;
close bowl_avg;
end;
/
There are a number of contradictions in there.
In 1 and 2 you're opening a cursor and then checking if there is an open cursor. A error will be raised if the cursor didn't open so you can ignore this step.
In 5 and 6 you exit if you can't fetch a new record then check if you have a record. This is a contradiction so stage 6 will (almost) always evaluate to true.
in 7 and 8 you loop, exiting when you don't have a record. As you've just checked (twice) that you do in fact have a record you'll never exit this loop.
If you insist on doing this with cursors then you can remove most of your code and it should work fine:
declare
cursor bowl_avg is
select sid, matches, bowling_avg, wickets
from bowling_stats;
nsid bowling_stats.sid%type;
nmatches bowling_stats.matches%type;
nbowling_avg bowling_stats.bowling_avg%type;
nwickets bowling_stats.wickets%type;
begin
-- 1. Open Cursor
open bowl_avg;
-- 2. Loop
loop
-- 3. Get record
fetch bowl_avg into nsid, nmatches, nbowling_avg, nwickets;
-- 4. Exit loop if there is no record.
exit when bowl_avg%notfound;
-- 5. Perform UPDATE statement.
update bowling_stats
set bowling_avg = nwickets / nmatches
where sid = nsid;
end loop;
close bowl_avg;
end;
/
As always a single UPDATE statement without using loops, cursors or PL/SQL will be significantly more effective.