Snowflake SQL stored procedure and save value from query with dynamic SQL - sql

I am writing a SQL stored procedure in Snowflake (language SQL NOT Javascript).
I am trying to save a count from a SELECT statement into a variable and the table name for the select statement needs to come from a variable.
Here is what I have so far but this is failing. I don't know where to put the USING or if I can even do this? I feel like I just don't have it syntactically correct yet.
create or replace procedure myprocedure(DBNAME varchar(16777216))
returns int
language sql
as
$$
DECLARE
excludeCount int;
fullyQualifiedProceduresTable varchar(16777216);
BEGIN
fullyQualifiedProceduresTable := CONCAT(DBNAME, '.INFORMATION_SCHEMA.PROCEDURES');
excludeCount := (SELECT count(*) as count from TABLE (?) WHERE PROCEDURE_OWNER = '<ROLE NAME>') USING fullyQualifiedProceduresTable ;
IF (excludeCount > 0) THEN
RETURN 1;
ELSE
RETURN 0;
END IF;
END;
$$;

This is what I got to work using slightly different syntax for using variables.
Syntax taken from here
create or replace procedure myprocedure(DBNAME varchar(16777216))
returns int
language sql
as
$$
DECLARE
excludeCount int;
fullyQualifiedProceduresTable varchar(16777216);
BEGIN
SELECT CONCAT(:DBNAME, '.INFORMATION_SCHEMA.PROCEDURES') into :fullyQualifiedProceduresTable;
SELECT count(*) into :excludeCount from IDENTIFIER(:fullyQualifiedProceduresTable) WHERE PROCEDURE_OWNER = '<role>';
IF (excludeCount > 0) THEN
RETURN 1;
ELSE
RETURN 0;
END IF;
END;
$$;

I am not sure why you need a procedure for this. Could you not do...
set table_name='abc';
set excludeCount=(select case when count(*)>0 then 1 else 0 end from identifier($table_name));

try this
call my_proc('bus_day');
create or replace procedure my_proc(table_name varchar)
returns table(a integer)
language sql
as
$$
declare
res RESULTSET;
query varchar default 'SELECT count(*) FROM ' || :table_name ;
begin
res := (execute immediate :query);
return table (res);
end;
$$;

Related

update in a function sql in dbeaver/postgres

I have to update a field in a table with concat() and I'm thinking to use a function with an update sql. I also want to have a rollback if update doesnt' work.
I have this function but it just works the select sql and for the first row of the table "clients"
CREATE OR REPLACE FUNCTION value_concat()
RETURNS record
LANGUAGE plpgsql
AS $function$
DECLARE
rows_affected integer := 0;
query constant text not null := 'select * from db.clients';
result record;
BEGIN
EXECUTE query INTO result;
RETURN result;
UPDATE db.clients SET clients.name = concat(clients.name, '-US');
exception when raise_exception
then
begin
rows_affected := 0;
rollback;
end;
RETURN record;
END;
$function$
;
Do I have to make a select sql before the update?
Why the update is not working, should I do a for/loop before the update sql?
The below code returns just one record and not all records form the select sql, why?
EXECUTE query INTO result;
RETURN result;
I hope this help.
CREATE OR REPLACE FUNCTION value_concat()
RETURNS TABLE (
field_name1 VARCHAR,
field_name2 INT [, ...]
)
LANGUAGE plpgsql
AS $function$
BEGIN
UPDATE db.clients SET clients.name = concat(clients.name, '-US');
RETURN QUERY
select * from db.clients;
exception when raise_exception
then
begin
rows_affected := 0;
rollback;
end;
RETURN record;
END;
$function$
;
Do I have to make a select SQL before the update?
There's no need to select, it works well. You should check the query to apply where for unwanted updates for other records.
Why the update is not working, should I do a for/loop before the
update SQL?
Because you're returning before the update and the rest of the code is always skipped.
The below code returns just one record and not all records form the
select sql, why?
It's because you just return a record type. the record type can only store one row from a result.

Postgres function to return number of rows deleted per schema

I'm trying to adapt a Postgres stored procedure into a function in order to provide some feedback to the caller.
The procedure conditionally deletes rows in specific schemas, and I'd want the function to do the same, but also return the amount of rows that were deleted for each schema.
The original stored procedure is:
create or replace procedure clear_tenants()
language plpgsql as $function$
declare
tenant text;
begin
for tenant in
select tenant_schema
from public.tenant_schema_mappings
loop
execute format($ex$
delete from %I.parent
where expiration_date_time < now()
$ex$, tenant);
end loop;
end
$function$;
My current transform into a function is:
CREATE OR REPLACE FUNCTION testfun()
RETURNS TABLE(tsname varchar, amount numeric) AS
$BODY$
declare
tenant text;
trow record;
BEGIN
for tenant in
select tenant_schema
from public.tenant_schema_mappings
loop
execute format($ex$
WITH deleted AS (
delete from %I.parent
where expiration_date_time < now()
IS TRUE RETURNING *
)
tsname := tenant;
amount := (SELECT * FROM deleted;);
return next;
$ex$, tenant);
end loop;
END
$BODY$ language plpgsql;
This is probably wrong in all kinds of ways. I'm definitely confused.
When executing this with SELECT * FROM testfun(), I get the following error:
ERROR: syntax error at or near "tsname"
LINE 7: tsname := tenant;
^
QUERY:
WITH deleted AS (
delete from anhbawys.parent
where expiration_date_time < now()
IS TRUE RETURNING *
)
tsname := tenant;
amount := (SELECT * FROM deleted;);
return next;
CONTEXT: PL/pgSQL function testfun() line 9 at EXECUTE
SQL state: 42601
So clearly I'm not properly assigning the row's columns, but I'm not sure how.
I have found this question which seemed similar, but it's bit complex for my understanding.
You can use GET DIAGNOSTICS after a DELETE statement to get the number of rows deleted:
CREATE OR REPLACE FUNCTION testfun()
RETURNS TABLE(tsname varchar, amount bigint) AS
$BODY$
declare
tenant text;
BEGIN
for tenant in
select tenant_schema
from tenant_schema_mappings
loop
execute format(
'delete from %I.parent
where expiration_date_time < now()', tenant);
tsname := tenant;
GET DIAGNOSTICS amount := ROW_COUNT;
return next;
end loop;
END
$BODY$
language plpgsql;
If the answer is as simple as your question there is a system catalog for this.
select schemaname , count(n_tup_del)
from pg_catalog.pg_stat_all_tables psat
group by schemaname
You can use get diagnostics (noddy function to get my point across)
create or replace function delfunc()
returns void
as $$
declare
affected_rows int ;
begin
delete from atable where a > 998 ;
GET DIAGNOSTICS affected_rows = ROW_COUNT;
insert into logtable values(affected_rows);
end $$
language plpgsql

Update PSQL Column with Default Date if erroneus date

I am using PostgreSQL 9.3 i have a column called appointment date in a table appointment which has erroneous dates like 21117-03-04 i wish to update all rows in the column to have a default date e.g. 1900-01-01 if the value on that column is any erroneous date. I have not yet tried any solution yet. please help.
you can use below procedure
DELIMITER $$
CREATE DEFINER=`root`#`localhost` PROCEDURE `MAKE_ACCURATE_DATE`()
BEGIN
DECLARE VAR_ID varchar(100);
DECLARE VAR_DATE DATE;
DECLARE VAR_FINISHED INT(11) DEFAULT 0;
DECLARE DATABASE_CURSOR CURSOR FOR
SELECT DATE(DATE_COLUMN) DATE_COLUMN,ID
FROM YOUR_TABLE_NAME;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET VAR_FINISHED = 1;
OPEN DATABASE_CURSOR;
GET_NEXTRECORD: LOOP
FETCH DATABASE_CURSOR INTO VAR_DATE,VAR_ID;
IF VAR_FINISHED = 1 THEN
LEAVE GET_NEXTRECORD;
END IF;
IF VAR_DATE =NULL THEN
UPDATE YOUR_TABLE_NAME SET DATE_COLUMN='1900-01-01' WHERE ID=VAR_ID;
END IF;
END LOOP GET_NEXTRECORD;
CLOSE DATABASE_CURSOR;
END$$
DELIMITER ;
OR
FOR POSTGRES
CREATE OR REPLACE FUNCTION IS_VALID_DATE(S VARCHAR) RETURNS BOOLEAN AS $$
BEGIN
PERFORM S::DATE;
RETURN TRUE;
EXCEPTION WHEN OTHERS THEN
RETURN FALSE;
END;
$$ LANGUAGE PLPGSQL;
And make change in above function as below.
CREATE OR REPLACE FUNCTION MAKE_ACCURATE_DATE()
RETURNS void AS
$BODY$
DECLARE
RECORD RECORD;
COUNT INT;
BEGIN
COUNT=0;
FOR RECORD IN SELECT * FROM cpad.dtl_patientappointment;
LOOP
IF(!IS_VALID_DATE(RECORD.appointmentdate)) THEN
UPDATE cpad.dtl_patientappointment SET appointmentdate='1900-01-01' WHERE patientappointmentid=RECORD.patientappointmentid;
END IF;
END LOOP;
END;
$BODY$
LANGUAGE plpgsql;
And execute this line
SELECT MAKE_ACCURATE_DATE();
It will fetch your record from table one by one and as
DATE(DATE_COLUMN) is there,it will returns null if date is not proper.So after applying condition it will update that record with specific ID.
Hope this will helps.

PL/PgSQL Dynamic Subqueries

I am creating a trigger that runs a check raises an exception if it passes. To do this I need to use a dynamic call because I only have the table name as a string. I am using PostgreSQL but I can't figure out how there execute command works. When I do this:
CREATE OR REPLACE FUNCTION bleep() RETURNS table(id INT) AS $bleep$
BEGIN
RETURN QUERY EXECUTE 'SELECT (id) from Applicant';
END;
$bleep$ LANGUAGE plpgsql;
SELECT * from bleep();
It works perfectly and I get back a table of id's from Applicant. But when I do this:
CREATE OR REPLACE FUNCTION bleep() RETURNS BOOLEAN AS $bleep$
BEGIN
IF (EXISTS (EXECUTE 'SELECT (id) from Applicant')) THEN
RETURN TRUE;
ELSE
RETURN FALSE;
END IF;
END;
$bleep$ LANGUAGE plpgsql;
It tells me:
ERROR: syntax error at or near "EXECUTE" Position: 87
This is just a toy example I made to figure out how this works and I have read lots of docs and guides. If I can figure out this toy example I can make the full trigger work since I tried it with hardcoding a table name. How can I make this work?
CREATE OR REPLACE FUNCTION bleep() RETURNS BOOLEAN AS $bleep$
DECLARE
res bool;
BEGIN
EXECUTE 'SELECT exists (select 1 from Applicant)' INTO res;
return res;
END;
$bleep$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION bleep() RETURNS BOOLEAN AS $bleep$
BEGIN
return query EXECUTE 'SELECT exists (select 1 from Applicant)';
END;
$bleep$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION bleep() RETURNS
BOOLEAN AS $bleep$
BEGIN
IF (EXISTS (SELECT id from Applicant)) THEN
RETURN TRUE;
ELSE
RETURN FALSE;
END IF;
END;
$bleep$ LANGUAGE plpgsql;
I misunderstood your question.
How about this?
CREATE FUNCTION bleep(integer) RETURNS boolean
AS 'select case when count(*) = 0 then false else true end from Applicant where id = $1;'
LANGUAGE SQL
IMMUTABLE
RETURNS NULL ON NULL INPUT;
You can test by SQLFiddle: http://sqlfiddle.com/#!15/33954/1

SQL: send query to all database available

How is it possible to send a query to all databases on a server? I do not want to input all databases names, the script should auto-detect them.
example query:
SELECT SUM(tourney_results.amt_won)-SUM((tourney_summary.amt_buyin+tourney_summary.amt_fee)) as results
FROM tourney_results
INNER JOIN tourney_summary
ON tourney_results.id_tourney=tourney_summary.id_tourney
Where id_player=(SELECT id_player FROM player WHERE player_name='Apple');
So what I want to achieve here, if there is 2 databases, the first one would result 60, the second one would result 50, I need the 55 output here.
All databeses would have the same structure, tables etc.
You can do it using plpgsql and db_link. First install the db_link extension in the database you are connecting to:
CREATE EXTENSION dblink;
Then use a plpgsql function which iterates over all database on the server and executes the query. See this example (see comments inline). Note that I used a sample query in the function. You have to adapt the function with your real query:
CREATE or REPLACE FUNCTION test_dblink() RETURNS BIGINT AS
$$
DECLARE pg_database_row record;
query_result BIGINT;
_dbname TEXT;
_conn_name TEXT;
return_value BIGINT;
BEGIN
--initialize the final value
return_value = 0;
--first iterate over the records in the meta table pg_database
FOR pg_database_row in SELECT * FROM pg_database WHERE (NOT datistemplate) AND (datallowconn) LOOP
_dbname = pg_database_row.datname;
--build a connection name for db_link
_conn_name=_dbname||'myconn';
--close the connection is already active:
IF array_contains(dblink_get_connections(),_conn_name) THEN
PERFORM dblink_disconnect(_conn_name);
END IF;
-- open the connection with the actual database name
PERFORM dblink_connect(_dbname||'myconn', 'dbname='||_dbname);
-- check if the table does exist in the database:
PERFORM * FROM dblink(_conn_name,'SELECT 1 from pg_tables where tablename = ''your_table''') AS t(id int) ;
IF FOUND THEN
-- if the table exist, perform the query and save the result in a variable
SELECT * FROM dblink(_conn_name,'SELECT sum(id) FROM your_table limit 1') AS t(total int) INTO query_result;
IF query_result IS NOT NULL THEN
return_value = return_value + query_result;
END IF;
END IF;
PERFORM dblink_disconnect(_conn_name);
END LOOP;
RETURN return_value;
END;
$$
LANGUAGE 'plpgsql';
Execute the function with
select test_dblink();