I have created following stored procedure to update recon_dashboard table.
create or replace procedure ca_adhoc_view.sp_count_recon_refresh()
language plpgsql as
$$
declare
f record;
BEGIN
for f in select alvid from ca_adhoc_view.recon_dashboard
loop
raise notice '% alv',f.alv;
execute 'update ca_adhoc_view.recon_dashboard set alv_count =
(select count(*) from ' || f.alv || ')
where id='|| f.id;
end loop;
END;
$$;
It works fine unless there is a null value in the alv, then it throws an error.
I tried to check for null like:
create or replace procedure ca_adhoc_view.sp_count_recon_refresh()
language plpgsql as
$$
declare
f record;
BEGIN
for f in select alv,id from ca_adhoc_view.recon_dashboard
loop
raise notice '% alv',f.alv;
execute 'update ca_adhoc_view.recon_dashboard set alv_count =
(IF '||f.alv||' is not null
then (select count(*) from ' || f.alv || ')
END IF;)
where id='|| f.id;
end loop;
END;
$$;
But it throws error when I try to call the SP.
Error: SQL Error [42601]: ERROR: syntax error at or near "ca_view"
Where: PL/pgSQL function "sp_count_recon_refresh" line 7 at execute statement
In error "ca_view" is a recod. alv column has values like 'ca_view.table1', 'ca_view.table2' and so on.
This probably fixes your problems:
CREATE OR REPLACE PROCEDURE ca_adhoc_view.sp_count_recon_refresh()
LANGUAGE plpgsql AS
$func$
DECLARE
f record;
BEGIN
FOR f IN
SELECT alv, id FROM ca_adhoc_view.recon_dashboard
LOOP
RAISE NOTICE '% alv', f.alv;
IF f.alv IS NOT NULL THEN
EXECUTE format(
'UPDATE ca_adhoc_view.recon_dashboard
SET alv_count = (SELECT count(*) FROM %I)
WHERE id = $1', f.alv)
USING f.id;
END IF;
END LOOP;
END
$func$;
"Problems" (plural). Besides defending against a NULL value for the table name with proper syntax, this also prevents SQL injection. Your original is wide open there. Related:
PostgreSQL - Writing dynamic sql in stored procedure that returns a result set
Table name as a PostgreSQL function parameter
That said, it would be more efficient to run a single UPDATE instead of updating one row per loop iteration. Consider building and executing a single statement.
Related
I am trying to call a procedure inside a procedure but this gives me an error like:
Uncaught exception of type 'STATEMENT_ERROR' on line 19 at position 2 : This session warehouse WH_STD_EDWQA_ANALYST no longer exists.
My parent procedure construct is like creating a warehouse & the child procedure is to populate a metadata table(custom) by use of table(result_scan(last_query_id())).
Parent procedure construct:
create or replace procedure wh_resource_govern(type varchar, env varchar, ..., varchar)
returns varchar not null
language sql
as
$$
declare
wh_name varchar;
wh_setup varchar;
lv_acct_name varchar;
begin
wh_name := 'WH_' || type || '_' || env || '_' || team;
wh_setup := 'CREATE OR REPLACE WAREHOUSE' || ' ' || wh_name || ' ' || 'WITH' || ' '
|| 'WAREHOUSE_SIZE = ' || v_wh_size || ' '
...,
|| 'COMMENT= '|| '"' || v_created_by || '"' ;
execute immediate wh_setup;
commit;
call load_all_warehouse_metadata('a', 'b', 'c'); ----> This is where it is getting stuck.
end;
$$
;
Child procedure construct is given as below:
create or replace procedure load_all_warehouse_metadata(wh_type varchar, wh_env varchar, wh_team varchar)
returns varchar not null
language sql
as
$$
declare
lv_acct_name varchar;
begin
select current_account() into lv_acct_name;
show warehouses;
insert into ALL_WAREHOUSE_METADATA (account_name, warehouse_type, .., .., )
select :lv_acct_name, :wh_type, :wh_env, :wh_team, "name", ..., ...,
from table(result_scan(last_query_id()));
end;
$$
;
Any inputs on how to address this would be really helpful.
Creating a warehouse immediately makes it the current warehouse for the session, example:
create or replace warehouse FOO;
select current_warehouse(); -- FOO
drop warehouse FOO();
select current_warehouse(); -- NULL
When you run execute immediate wh_setup; in the first SP, it's setting the session's warehouse to the one you just created. Calling a child SP using owner's rights (default) from a warehouse that isn't the one that started the SP is causing context problems for the warehouse.
You can reproduce this error as follows:
create or replace procedure SP1()
returns varchar not null
language sql
--execute as caller
as
$$
declare
currentWarehouse varchar;
begin
select current_warehouse() into currentWarehouse;
create or replace warehouse FOO;
call SP2();
return currentWarehouse;
end;
$$;
create or replace procedure SP2()
returns varchar not null
language sql
--execute as caller
as
$$
declare
currentWarehouse varchar;
begin
select current_warehouse() into currentWarehouse;
create or replace temp table FOO(s string);
insert into FOO(S) values ('Bar');
return 'Done';
end;
$$;
call SP1();
You can fix this code sample immediately by uncommenting the two commented-out ownership options for SP1 and SP2:
--execute as caller (Remove the comment markers and recreate both SPs.)
You can also run a SQL command to use warehouse <wh_name> in your SP(s), but you must run the SP as caller in order to change warehouse context this way.
I have the following T-SQL script that copies the value of an old column into a new one, then drops the old column. See here:
--step 1: create new column
IF NOT EXISTS(SELECT 1 from sys.columns
WHERE Name = N'UserColumn2'
AND Object_ID = Object_ID(N'Account'))
BEGIN
ALTER TABLE Account
ADD UserColumn2 int null
;
END
GO
;
--step 2: copy and drop
IF NOT EXISTS(SELECT 1 from sys.columns
WHERE Name = N'UserColumn1'
AND Object_ID = Object_ID(N'Account'))
BEGIN
PRINT 'Column ''UserColumn1'' does not exist.';
END
ELSE
BEGIN
UPDATE Account
SET UserColumn2 = UserColumn1
WHERE UserColumn1 is not null
;
BEGIN TRY
Declare #count int;
SELECT #count = Count(AccountID)
FROM Account
WHERE UserColumn2 <> UserColumn1
;
IF #count > 0
BEGIN
--PRINT 'Not all records were properly updated. UserColumn1 has not been dropped.';
THROW 50000,'Not all records were properly updated. UserColumn1 has not been dropped.',1
;
END
ELSE
BEGIN
ALTER TABLE Account
DROP Column UserColumn1
;
END
END TRY
BEGIN CATCH THROW; END CATCH
END
GO
;
The first step runs correctly but the second step still throws an error in the ELSE block even if the UserColumn1 column doesn't exist:
(note: this actually throws on line 24 for the code here. The code in my SSMS doesn't have the comments for 'step 1', etc.)
Why is this happening and how can I prevent it?
I've tried removing the NOT and moving the instructions out of the ELSE block but the behavior did not change. I've also tried writing the beginning of the second step like this:
IF (SELECT 1 from sys.columns
WHERE Name = N'UserColumn1'
AND Object_ID = Object_ID(N'Account')) <> null
and I get the same result.
The issue is that the entire sql text is parsed and compiled before it's executed, the error is being thrown at compile time.
You could workaround it by executing the update statement in its own process using dynamic sql - although there is nothing dynamic in this usage, it simply defers the compilation and execution of the update statement where it only happens in your else condition:
IF NOT EXISTS(SELECT 1 from sys.columns
WHERE Name = N'UserColumn1'
AND Object_ID = Object_ID(N'Account'))
BEGIN
PRINT 'Column ''UserColumn1'' does not exist.';
END
ELSE
BEGIN
EXEC sp_executesql N'
UPDATE Account
SET UserColumn2 = UserColumn1
WHERE UserColumn1 is not null;'
...
...
I am fairly new at writing procedures (beyond the basics)
I am trying to write a stored procedure that inserts into a table (dbo.billing_batch) based on a select statement that loops through the list of results (#DealerID FROM dbo.vehicle_info).
The SELECT DISTINCT... statement on its own works perfectly and returns a list of 54 records.
The result of the SELECT statement is dynamic and will change from week to week, so I cannot count on 54 records each time.
I am trying to use WHILE #DealerID IS NOT NULL to loop through the INSERT routine.
The loop is supposed to update dbo.billing_batch, however it is inserting the same 1st record (BillingBatchRosterID, DealerID) over and over and over to infinity.
I know I must be doing something wrong (I have never written a stored procedure that loops).
Any help would be greatly appreciated!
Here is the stored procedure code:
ALTER PROCEDURE [dbo].[sp_billing_batch_set]
#varBillingBatchRosterID int
AS
SET NOCOUNT ON;
BEGIN
DECLARE #DealerID int
SELECT DISTINCT #DealerID = vi.DealerID
FROM dbo.vehicle_info vi
LEFT JOIN dbo.dealer_info di ON di.DealerID = vi.DealerID
WHERE di.DealerActive = 1
AND (vi.ItemStatusID < 4 OR vi.ItemStatusID = 5 OR vi.ItemStatusID = 8)
END
WHILE #DealerID IS NOT NULL
BEGIN TRY
INSERT INTO dbo.billing_batch (BillingBatchRosterID, DealerID)
VALUES(#varBillingBatchRosterID, -- BillingBatchRosterID - int
#DealerID) -- DealerID - int
END TRY
BEGIN CATCH
SELECT ' There was an error: ' + error_message() AS ErrorDescription
END CATCH
You have the same problems as another recent post here: Iterate over a table with a non-int id value
Why do a loop? Just do it as a single SQL statement
If you must use a loop, you will need to update your #Dealer value at each run (e.g., to the next DealerId) otherwise it will just infinitely loop with the same DealerID value
Don't do a loop.
Here's an example not needing a loop.
ALTER PROCEDURE [dbo].[P_billing_batch_set]
#varBillingBatchRosterID int
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRY
INSERT INTO dbo.billing_batch (DealerID, BillingBatchRosterID)
SELECT DISTINCT vi.DealerID, #varBillingBatchRosterID
FROM dbo.vehicle_info vi
INNER JOIN dbo.dealer_info di ON di.DealerID = vi.DealerID
WHERE di.DealerActive = 1
AND (vi.ItemStatusID < 4
OR vi.ItemStatusID = 5
OR vi.ItemStatusID = 8
);
END TRY
BEGIN CATCH
SELECT ' There was an error: ' + error_message() AS ErrorDescription;
END CATCH;
END;
Note I
Changed the LEFT JOIN to an INNER JOIN as your WHERE clause needs the record to exist in the dealer_info table
Moved the SET NOCOUNT ON; to be within the BEGIN-END section
Moved the END to the end
Renamed your stored procedure as per the excellent comment from #marc_s (on the question itself)
create or replace function f() RETURNS void AS
declare
tableArray text[] := '{"ADDRESS","CONSISTENCY_CHECK","DEPARTMENT_SUPERVISION"}';
tableName CHARACTER VARYING;
value INTEGER ;
BEGIN
FOREACH tableName IN ARRAY tableArray
LOOP
select user_id from tableName where user_id=2631;
if found then
update tableName set user_id=2651 where user_id=2631;
delete from tableName where user_id=2631;
END loop;
end;
here is the error that I get when trying to execute the pgplsql: ERROR syntax error at or near "loop"
There are more issues:
the body of function should be string - you can use apostrophes or more usual and practical $$ custom string separators.
the result of SELECT should not be lost. The clause INTO is missing.
table name should not be a variable - variable cannot be used as table name. In this case you need dynamic SQL - EXECUTE statement.
camel notation for variable names should not be used for SQL language, that is case insensitive
CREATE OR REPLACE FUNCTION f()
RETURNS void AS $$
DECLARE
table_array text[] := '{"ADDRESS","CONSISTENCY_CHECK","DEPARTMENT_SUPERVISION"}';
table_name text;
value integer ;
rc integer
BEGIN
FOREACH table_name IN ARRAY table_array
LOOP
EXECUTE format('SELECT * FROM %I WHERE user_id = $1', table_name) USING 2631;
-- variable FOUND cannot be used for dynamic SQL
GET DIAGNOSTICS rc = ROW_COUNT;
IF rc > 0 THEN
EXECUTE format('UPDATE %I SET user_id = $1 WHERE user_id = $2', table_name) USING 2651, 2631;
EXECUTE format('DELETE %I WHERE user_id = $1', table_name) USING 2631;
END IF;
END LOOP;
END;
$$ LANGUAGE plpgsql;
I have a stored procedure with multiple if-elseif- else statements. When I try to remove this if elseif statement and execute only single portion of that query, it returns results very fast, but when use this query with multiple another queries using if-elseif case statement, then it takes too much time...
For example:
if #Status = 1
begin
select .....
end
else if #Status = 2
begin
select .....
end
else if #Status = 3
begin
select .....
end
else if....
There are many more else if statements in this stored procedure..
Although it may be in some "don't do this" books, you can try to create a stored procedure for each status value and call it directly by building a dynamic TSQL statement ("exec yourproc_" + statusValue).
create proc youproc_1 as
begin
select your_data_for_status1;
end;
create proc youproc_2 as
begin
select your_data_for_status2;
end;
etc...