Postgres for loop doesn't work with cursor? - database

I have a simple PostgreSQL script and try to run the for loop with cursor to fetch rows as pagination:
begin;
declare curs cursor for select id from postgres.core.security_group order by id asc;
fetch 4 from curs;
commit;
Working fine, but when I add a for loop to it, won't work:
CREATE OR REPLACE FUNCTION postgres.core.cursor_selection()
RETURNS SETOF varchar AS
$func$
DECLARE
curs cursor for select * from postgres.core.security_group;
_modules varchar; -- assuming data type integer
BEGIN
FOR _modules IN SELECT * FROM postgres.core.security_group ORDER BY id limit 10
LOOP
RETURN NEXT _modules;
END LOOP;
END
$func$ LANGUAGE plpgsql;
SELECT postgres.core.cursor_selection();
I have the loop not working properly and not showing more data other than the first 10 records. How do I get the data as a set of 10s on each page?
Much appreciated.

Functions in plpgsql don't stream. RETURN NEXT kind of looks like it streams via co-routine or something, but it really just accumulates all the rows until the end of the function and returns them at once.
And SQL doesn't have any looping constructs. Neither does psql (in a user-visible way) that I can find. So there is no way to do it just in SQL or with psql. But in psql, you can set FETCH_COUNT, which uses a cursor and FETCH behind the scenes. (If you set log_statement=all, you can see this in action in the log file.) If you really want do it manually for some reason, you could use \watch as an infinite loop.
begin;
declare curs cursor for select id from postgres.core.security_group order by id asc;
fetch 4 from curs \watch 0.1
commit;
You will have to break out of the loop yourself once it finishes or you get tired of it, like with ctrl-C (on Linux)
The usual way to use a cursor in SQL would be in some other programming language with a db driver, like python or JS or Java or Perl. But they too will usually have settings where the driver uses cursors behind the scenes without you needing to manually implement it. (like psycopg2's "named cursors" do)

Related

SQL Server 2014: create procedure with cursor output parameter

I am new to this stored procedures my question is related to cursor output parameter. what is the difference between cursor output parameter and a normal stored procedure like it just a variable or it effect the result or performance of query?
I am using SQL Server 2014. Creating a stored procedure I used the shortcut key alt+k, alt+x. in the list I have selected stored procedure after selecting the stored procedure, it ask to choose stored procedure type:
Create procedure basic template
Create procedure with cursor output parameter
Create procedure with output parameter.
I couldn't understand the 2nd stored procedure type. I tried to google but didn't get sufficient information. Anyone here to help me to understand will much appreciated. I have attached the 2nd stored procedure type sample script
CREATE PROCEDURE dbo.Sample_Procedure
#sample_procedure_cursor CURSOR VARYING OUTPUT
AS
SET #sample_procedure_cursor = CURSOR FOR
select 1
OPEN #sample_procedure_cursor
RETURN 0
I just want to understand is there any other output I can't see using "cursor varying output" keywords instead of using "#variable datatype;"
Deepak please refer to documentation samples at reference
If you are using the same cursor repeatedly in your SQL codes you can wrap the definition of the cursor into a SP once and refer to it later.
I copy below the sample code
First create the procedure
CREATE PROCEDURE dbo.uspCurrencyCursor
#CurrencyCursor CURSOR VARYING OUTPUT
AS
SET NOCOUNT ON;
SET #CurrencyCursor = CURSOR
FORWARD_ONLY STATIC FOR
SELECT CurrencyCode, Name
FROM Sales.Currency;
OPEN #CurrencyCursor;
GO
Then use it as follows
DECLARE #MyCursor CURSOR;
EXEC dbo.uspCurrencyCursor #CurrencyCursor = #MyCursor OUTPUT;
WHILE (##FETCH_STATUS = 0)
BEGIN;
FETCH NEXT FROM #MyCursor;
END;
CLOSE #MyCursor;
DEALLOCATE #MyCursor;
GO
A cursor as output is meant to encapsulate the definition of the cursor. This means that you execute an SP to retrieve a cursor that has already been initialized and is linked to a result set which is unknown to the caller, but the caller will use.
This opens up a potential problem in that the caller will need to know which variables to cast the fetching row into and might potentially break the usage of the cursor if done incorrectly. This wouldn't happen if the SP returned a result set with a SELECT or inserts into a temporary table created outside, for example.
In my opinion, there is little to none useful applications of this. To start with it's on very rare occasions that you want to use a cursor at all and they are usually with operations that don't involve DML and involve system operations, like creating files or sending emails. And even in those cases, hiding the result set from the caller seems pretty obscure.

Improve performance of an EXCEPT statement

I'm doing a data comparison between a SQL Server database and a linked server database (Sybase ASA, Oracle or Ingres depending on which linked server specified).
I have a cursor that loops through all the tables in each database and does an except.
OPEN MY_Cursor;
FETCH NEXT FROM MY_Cursor INTO #TableName;
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT * FROM OPENQUERY([LINKEDSERVER],'SELECT * FROM #TableName')
EXCEPT
SELECT * FROM #TableName
IF ##ROWCOUNT <> 0
BEGIN
PRINT #TableName
END
FETCH NEXT FROM My_Cursor INTO #TableName
END
This is doing a full table scan, and not returning results until all the rows have been compared. Since some of these tables have millions of rows, this is taking a very long time.
The execution plan indicates approx 95% of the performance problems are at the remote end. Unfortunately, I don't have permissions to create indexes or indexed views at the linked server database.
Is it possible to break the except statement and go onto the next iteration of the cursor as soon as any record that has a data difference is found?
I would try using a loop instead of a CURSOR. Cursors are extremely inefficient and there aren't many ways to optimize a cursor because of how they work, You are also using full table scans and the reason it is so slow is because you have to do a full table scan for each record you come across because of how cursors work. I would look at trying to use a while loop and see if that helps you time. I would also try to add a where clause to the SQL Statement.

Using a cursor with a stored procedure - Front End Software Error

I am receiving an error trying to build a macro using a stored procedure with a cursor.
Some relevant details need to be included. This is for a document in an Electronic Health Records system. First, I develop the templates/tables, then I create document macros to pull all the information into the EHR documents. I have used stored procedures hundreds of times, so I know that it is the cursor that is causing it to fail.
All I do is pass in the parameters and it creates a grid on the document. Whether or not it is a limitation of the software that is used to create the documents, I don't know. I figured I would ask here to make sure my code is correct.
Here is my table:
txt_enc_id enc_id_generate
12345 93847
12345 75430
12345 93946
I am passing in the enc_id on the document macro, which will match up to the txt_enc_id column, and I need a stored procedure to run for each of the enc_id_generate rows in that table
Here's what I have for my stored procedure:
#enc_id VARCHAR(36)
AS
BEGIN
SET NOCOUNT ON
DECLARE #enc_id_generate VARCHAR(36)
DECLARE enc_id_cursor CURSOR
FOR
(SELECT enc_id_generate
FROM my_table
WHERE txt_enc_id = #enc_id)
OPEN enc_id_cursor
FETCH NEXT FROM enc_id_cursor INTO #enc_id_generate
WHILE ##FETCH_STATUS = 0
BEGIN
-- Here is the stored procedure that needs to run for each record.
-- The only parameter it uses is #enc_id_generate
FETCH NEXT FROM enc_id_cursor INTO #enc_id_generate
END
CLOSE enc_id_cursor
DEALLOCATE enc_id_cursor
SET NOCOUNT OFF
END
When I try to create the macro, I pass in the #enc_id, click ok in the parameter window, and I am getting this error:
Failed to update the macro builder window.
Cannot find table 0.
Do I somehow need to NOT have the stored procedure inside the cursor, and somehow call it to execute? I don't know how to do that. I am not a SQL guru, so forgive me if I have murdered this code. I do know that cursors should be used sparingly, but the table holding those Encounter IDs will rarely have more than 2 or 3 records.
I hope this makes sense. Thanks in advance for any advice you may have.
Lyn

SQL Server for loop records

In Oracle, we can use
FOR employee_rec in (select id from mytbl)
LOOP
DBMS_OUTPUT.PUT_LINE(employee_rec.id);
END LOOP;
In SQL Server, do we have something similar?, if not, how to do this loop?
It's a little more verbose in SQL Server, if you want to do it right:
DECLARE #id INT;
DECLARE c CURSOR LOCAL STATIC READ_ONLY FORWARD_ONLY
FOR SELECT id FROM mytbl;
OPEN c;
FETCH NEXT FROM c INTO #id;
WHILE ##FETCH_STATUS = 0
BEGIN
PRINT #id;
FETCH NEXT FROM c INTO #id;
END
CLOSE c;
DEALLOCATE c;
However, usually we tend to avoid loops and cursors, since a relational database excels at set-based operations (in almost cases). There are exceptions of course, like any rule, but if I had to guess I would suspect that you don't need to loop to do what you are trying to do. What are you really trying to do? Why do you need to process each row individually?

Table Variable inside cursor, strange behaviour - SQL Server

I observed a strange thing inside a stored procedure with select on table variables. It always returns the value (on subsequent iterations) that was fetched in the first iteration of cursor. Here is some sample code that proves this.
DECLARE #id AS INT;
DECLARE #outid AS INT;
DECLARE sub_cursor CURSOR FAST_FORWARD
FOR SELECT [TestColumn]
FROM testtable1;
OPEN sub_cursor;
FETCH NEXT FROM sub_cursor INTO #id;
WHILE ##FETCH_STATUS = 0
BEGIN
DECLARE #Log TABLE (LogId BIGINT NOT NULL);
PRINT 'id: ' + CONVERT (VARCHAR (10), #id);
INSERT INTO Testtable2 (TestColumn)
OUTPUT inserted.[TestColumn] INTO #Log
VALUES (#id);
IF ##ERROR = 0
BEGIN
SELECT TOP 1 #outid = LogId
FROM #Log;
PRINT 'Outid: ' + CONVERT (VARCHAR (10), #outid);
INSERT INTO [dbo].[TestTable3] ([TestColumn])
VALUES (#outid);
END
FETCH NEXT FROM sub_cursor INTO #id;
END
CLOSE sub_cursor;
DEALLOCATE sub_cursor;
However, while I was posting the code on SO and tried various combinations, I observed that removing top from the below line, gives me the right values out of table variable inside a cursor.
SELECT TOP 1 #outid = LogId FROM #Log;
which would make it like this
SELECT #outid = LogId FROM #Log;
I am not sure what is happening here. I thought TOP 1 on table variable should work, thinking that a new table is created on every iteration of the loop. Can someone throw light on the table variable scoping and lifetime.
Update: I have the solution to circumvent the strange behavior here.
As a solution, I have declared the table at the top before the loop and deleting all rows at the beginning of the loop.
There are numerous things a bit off with this code.
First off, you roll back your embedded transaction on error, but I never see you commit it on success. As written, this will leak a transaction, which could cause major issues for you in the following code.
What might be confusing you about the #Log table situation is that SQL Server doesn't use the same variable scoping and lifetime rules as C++ or other standard programming languages. Even when declaring your table variable in the cursor block you will only get a single #Log table which then lives for the remainder of the batch, and which gets multiple rows inserted into it.
As a result, your use of TOP 1 is not really meaningful, since there's no ORDER BY clause to impose any sort of deterministic ordering on the table. Without that, you get whatever order SQL Server sees fit to give you, which in this case appears to be the insertion order, giving you the first inserted element of that log table every time you run the SELECT.
If you truly want only the last ID value, you will need to provide some real ordering criterion for your #Log table -- some form of autonumber or date field alongside the data column that can be used to provide the proper ordering for what you want to do.

Resources