while loop in procedure sybase - tsql

In oracle when we write a procedure we do it like this :
COUNT_START:= LL_COUNT;
WHILE LL_COUNT > 0 LOOP
IF LL_COUNT > 20 THEN
COUNT_START := 20;
ELSE
COUNT_START := LL_COUNT;
LL_COUNT := 0;
END IF;
I am writing a procedure in sybase :
CREATE PROCEDURE P_RDS_EOD_ARCH_PURGE
v_test numeric(10,0)
AS
BEGIN
DECLARE #ID VARCHAR(200)
DECLARE #NAME VARCHAR(200)
DECLARE #TYPE VARCHAR(200)
DECLARE #SQL_TXT VARCHAR(255)
SELECT #ID = '1'
SELECT #NAME = 'P_TEST'
SELECT #TYPE = 'SELECT'
SELECT #SQL_TXT ='RANDOM QUERY'
EXECUTE #ID, #NAME , #TYPE, #SQL_TXT
After I execute my query I want to do a LOOP and to assign values, how to do that in sybase

Loops are pretty basic in TSQL:
while boolean_expression
statement
If your statement is more than 1 line, then you can enclose it in begin/end statements;
while aStatementIsTrue
begin
update myTable
set myCol = "whatever"
select someOtherThing
end
More details can be found in the Sybase T-SQL User's Guide.

Related

How do I integrate a FOR loop into my dynamic queries?

I am trying to run a dynamic query. I have a static query that works :
CREATE OR REPLACE
FUNCTION update_points_in_polygon(
)
RETURNS trigger LANGUAGE plpgsql AS $function$
-- Rule for clarity : one filter per function. Can choose three options : point count unit count and label concat
-- the one rule will be applied to all 3 options if they are selected
DECLARE tmprow record;
BEGIN
FOR tmprow IN
SELECT c.objectid, count(a.*) point_count
FROM sandbox.simple_address a
JOIN sandbox.polygon_address_units c
ON st_intersects(a.wkb_geometry, c.wkb_geometry)
WHERE st_intersects(c.wkb_geometry, NEW.wkb_geometry)
GROUP BY c.objectid -- tmp TABLE fetchin nb OF addresses IN a polygon WHERE polygon interects point
LOOP
UPDATE sandbox.polygon_address_units
SET address_count = tmprow.point_count
WHERE objectid = tmprow.objectid; -- UPDATE point_count COLUMN OF TABLES fetched IN FIRST SELECT
END LOOP;
RETURN NEW;
END;
$function$;
When I try to replace the loop like this :
EXECUTE 'FOR tmprow IN
SELECT c.objectid, count(a.*) point_count
FROM sandbox.simple_address a
JOIN sandbox.polygon_address_units c
ON st_intersects(a.wkb_geometry, c.wkb_geometry)
WHERE st_intersects(c.wkb_geometry, NEW.wkb_geometry)
GROUP BY c.objectid
LOOP
UPDATE sandbox.polygon_address_units
SET address_count = tmprow.point_count
WHERE objectid = tmprow.objectid;
END LOOP;';
I get the following message :
SQL Error [42601]: ERROR: syntax error at or near "FOR" Where:
PL/pgSQL function update_points_in_polygon() line 7 at EXECUTE
The aim is to be able to define table_names and columns dynamically. I've also tried to make two execute statements and keep the LOOP in plpgsql instead of plain SQL that's run with EXECUTE:
CREATE OR REPLACE
FUNCTION update_points_in_polygon(
)
RETURNS trigger LANGUAGE plpgsql AS $function$
-- Rule for clarity : one filter per function. Can choose three options : point count unit count and label concat
-- the one rule will be applied to all 3 options if they are selected
DECLARE tables_schema varchar;
DECLARE polygon_table_name varchar;
DECLARE polygon_point_count_column_name varchar;
DECLARE point_table_name varchar;
DECLARE tmprow record;
BEGIN
tables_schema = TG_ARGV[0]; -- frontier_ftth
polygon_table_name = TG_ARGV[1]; -- fdh
polygon_point_count_column_name = TG_ARGV[2]; -- point_count
point_table_name = TG_ARGV[4];
FOR tmprow IN
EXECUTE format('SELECT c.objectid, count(a.*) point_count
FROM %I.%I a
JOIN %I.%I c
ON st_intersects(a.wkb_geometry, c.wkb_geometry)
WHERE st_intersects(c.wkb_geometry, st_geomFromText(''%s'', 4326))
GROUP BY c.objectid',
tables_schema, point_table_name, tables_schema, polygon_table_name,
st_astext(st_geomfromewkb(NEW.wkb_geometry)))
LOOP
EXECUTE format('UPDATE %I.%I c1
SET %I = tmprow.point_count
WHERE c1.objectid = tmprow.objectid',
tables_schema, polygon_table_name, polygon_point_count_column_name);
END LOOP;
RETURN NEW;
END;
$function$
With this version of the function, I get the following error :
SQL Error [22004]: ERROR: null values cannot be formatted as an SQL identifier
Where: PL/pgSQL function update_points_in_polygon() line 17 at FOR over EXECUTE statement
This is a barebones version of my setup :
CREATE TABLE sandbox.simple_address (
objectid serial4 NOT NULL,
wkb_geometry geometry(point, 4326) NULL,
);
CREATE TABLE sandbox.polygon_address_units (
objectid serial4 NOT NULL,
address_count int4 NULL,
wkb_geometry geometry(multipolygon, 4326) NULL,
);
CREATE TRIGGER onallactions AFTER INSERT
OR UPDATE ON
sandbox.simple_address FOR EACH ROW
WHEN ((pg_trigger_depth() < 1)) EXECUTE PROCEDURE
update_points_in_polygon(
'sandbox', 'polygon_address_units', 'address_count',
'simple_address');
INSERT INTO sandbox.polygon_address_units(wkb_geometry)
VALUES(ST_SetSRID(st_astext(st_geomfromtext('MULTIPOLYGON (((-1 1, 1 1, 1 -1, -1 -1, -1 1)))')),4326));
INSERT INTO sandbox.simple_address(wkb_geometry)
VALUES(ST_SetSRID(st_astext(st_geomfromtext('POINT(0 0)')),4326));
How do use these two queries to properly update the number_of_points column in my polygon layers when I add a point that intersects it ? There might be multiple polygons to update.
EDIT: The solution was to call tmprow to format the second string
FOR tmprow IN
EXECUTE format('SELECT c.objectid, count(a.*) point_count
FROM %I.%I a
JOIN %I.%I c
ON st_intersects(a.wkb_geometry, c.wkb_geometry)
WHERE st_intersects(c.wkb_geometry, st_geomFromText(''%s'', 4326))
GROUP BY c.objectid',
tables_schema, point_table_name, tables_schema, polygon_table_name,
st_astext(st_geomfromewkb(NEW.wkb_geometry)))
LOOP
EXECUTE format('UPDATE %I.%I c1
SET %I = %s
WHERE c1.objectid = %s ',
tables_schema, polygon_table_name, polygon_point_count_column_name, tmprow.point_count, tmprow.objectid);
END LOOP;
RETURN NEW;
END;
$function$

Triggers in Postgres: Access NEW fields by name at runtime

In Postgres, someone knows how to substitute the value of the variable in a NEW.variable in a trigger?
For instance, I have a variable with value order_code. I want to execute NEW.variable so that it's getting in fact NEW.order_code.
In detailed:
I have a function to obtain the primary key column of a table:
CREATE FUNCTION getPrimaryKey(_table_name VARCHAR(50))
RETURNS SETOF VARCHAR(50) AS $$
DECLARE
primary_key VARCHAR(50);
BEGIN
FOR primary_key IN SELECT a.attname
FROM pg_index i
JOIN pg_attribute a ON a.attrelid = i.indrelid
AND a.attnum = ANY(i.indkey)
WHERE i.indrelid = _table_name::regclass
AND i.indisprimary LOOP
RETURN NEXT primary_key;
END LOOP;
END;
$$ LANGUAGE plpgsql;
Then I have a trigger to collect some info when an INSERT is done in a table. The procedure in the trigger is called from several triggers from different tables. That's why it's so generic and I have this need.
What I want is to obtain the primary key of the object inserted.
CREATE FUNCTION logAudit()
RETURNS trigger AS $$
DECLARE primary_key VARCHAR(50);
BEGIN
primary_key := getprimarykey(TG_TABLE_NAME::VARCHAR(50));
INSERT INTO test VALUES (TG_TABLE_NAME);
INSERT INTO test VALUES (NEW.primary_key);
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER audit_in_client
AFTER INSERT ON tb_client
FOR EACH STATEMENT EXECUTE PROCEDURE logAudit();
The NEW.primary_key is what is causing me issues. I expect primary_key to be the column name of the source table where the insert happened. What I want in NEW.primary_key is to actually use the value in the variable.
Here is the example of anonymous pl/pgsql block which doing something what you want:
do $$
declare
v pg_database = (pg_database) from pg_database where datname = 'template1';
fname text = 'datname';
n text;
begin
n := to_jsonb(v)->>fname;
raise info '%', n;
end $$;
Output:
INFO: template1
It is working example. In your trigger function it could be something like
declare
pk_name text;
pk_value text;
begin
pk_name := getprimarykey(TG_TABLE_NAME::VARCHAR(50));
pk_value := to_jsonb(NEW) ->> pk_name;
-- Do what you want with pk_value here
return null;
end $$;

Procedure to check count, store result and delete records

I want to create stored procedure to check count of query result. Then if count is > 0 to execute some query to delete records in other table. Below see what i got so far.
CREATE OR REPLACE PROCEDURE myprocedure(tableName VARCHAR, age INT, secondTable VARCHAR)
AS
$$
declare cnt := SELECT COUNT(*) FROM %tableName% WHERE ageID =%id%;
declare result;
BEGIN
EXECUTE cnt;
IF cnt >= 1 THEN
result := SELECT ID FROM %tableName% WHERE ageID =%id%
--remove records from secondTable
EXECUTE DELETE FROM %secondTable% WHERE ID IN (result)
END IF;
COMMIT;
END;
As documented in the manual you can't "reference" a variable with %tableName% and you certainly can not use a variable within a SQL statement for an identifier. You will need to use dynamic SQL.
You also got the DECLARE part completely wrong. You only write the keyword once, and you have to define a data type for the variables.
To create SQL strings that contain identifier, use format() and the %I placeholder to properly deal with identifiers that need quoting.
CREATE OR REPLACE PROCEDURE myprocedure(p_tablename VARCHAR, p_age INT, p_secondtable VARCHAR)
AS
$$
declare
l_sql text;
cnt integer;
BEGIN
l_sql := format('select count(*) from %I where ageid = :1', p_tablename);
EXECUTE l_sql
using p_age
into cnt;
IF cnt >= 1 THEN
l_sql := format('DELETE FROM %I WHERE ID IN (SELECT id FROM %I where ageid = :1)', p_secondtable, p_tablename);
EXECUTE l_sql using p_age;
END IF;
$$
language plpgsql;
But checking for the count before doing the delete is pretty pointless, you can simply that to a single DELETE statement:
CREATE OR REPLACE PROCEDURE myprocedure(p_tablename VARCHAR, p_age INT, p_secondtable VARCHAR)
AS
$$
declare
l_sql text;
cnt integer;
BEGIN
l_sql := format('DELETE FROM %I WHERE id IN (SELECT t.id FROM %I as t where t.ageid = :1)', p_secondtable, p_tablename);
EXECUTE l_sql using p_age;
END IF;
$$
language plpgsql;
Because the DELETE statement won't delete anything if the sub-select doesn't return any rows (which would be the case for cnt = 0). And you only need to query the first table once.

ColdFusion CFQuery block as per postgres

I have not been able to get this code block to run my query against postgres in CFQuery of ColdFusion:
<cfquery name="uiCustomColumn" datasource="#arguments.dsn#">
DECLARE resultValue int;
DECLARE nextId bigint;
BEGIN
IF (( select count( udc_id ) from user_defined_column WHERE udc_is_active = true ) >= 10) THEN
INSERT INTO user_defined_column(udc_id)
VALUES(<cfqueryparam value="#this.getLabel()#" cfsqltype="cf_sql_varchar" maxlength="25">)
END IF;
END;
</cfquery>
What you have there is plpgsql syntax (the default PostgreSQL procedural language), not SQL.
You'd need to wrap this in a DO command or CREATE FUNCTION with it.
Or rewrite it with SQL syntax. Something along these lines:
INSERT INTO user_defined_column(udc_id)
SELECT <this.getLabel()>
WHERE (
SELECT count(udc_id) > 9
FROM user_defined_column
WHERE udc_is_active
)

How do you use variables in a simple PostgreSQL script?

For example, in MS-SQL, you can open up a query window and run the following:
DECLARE #List AS VARCHAR(8)
SELECT #List = 'foobar'
SELECT *
FROM dbo.PubLists
WHERE Name = #List
How is this done in PostgreSQL? Can it be done?
Complete answer is located in the official PostgreSQL documentation.
You can use new PG9.0 anonymous code block feature (http://www.postgresql.org/docs/9.1/static/sql-do.html )
DO $$
DECLARE v_List TEXT;
BEGIN
v_List := 'foobar' ;
SELECT *
FROM dbo.PubLists
WHERE Name = v_List;
-- ...
END $$;
Also you can get the last insert id:
DO $$
DECLARE lastid bigint;
BEGIN
INSERT INTO test (name) VALUES ('Test Name')
RETURNING id INTO lastid;
SELECT * FROM test WHERE id = lastid;
END $$;
DO $$
DECLARE
a integer := 10;
b integer := 20;
c integer;
BEGIN
c := a + b;
RAISE NOTICE'Value of c: %', c;
END $$;
You can use:
\set list '''foobar'''
SELECT * FROM dbo.PubLists WHERE name = :list;
That will do
Here's an example of using a variable in plpgsql:
create table test (id int);
insert into test values (1);
insert into test values (2);
insert into test values (3);
create function test_fn() returns int as $$
declare val int := 2;
begin
return (SELECT id FROM test WHERE id = val);
end;
$$ LANGUAGE plpgsql;
SELECT * FROM test_fn();
test_fn
---------
2
Have a look at the plpgsql docs for more information.
I've came across some other documents which they use \set to declare scripting variable but the value is seems to be like constant value and I'm finding for way that can be acts like a variable not a constant variable.
Ex:
\set Comm 150
select sal, sal+:Comm from emp
Here sal is the value that is present in the table 'emp' and comm is the constant value.
Building on #nad2000's answer and #Pavel's answer here, this is where I ended up for my Flyway migration scripts. Handling for scenarios where the database schema was manually modified.
DO $$
BEGIN
IF NOT EXISTS(
SELECT TRUE FROM pg_attribute
WHERE attrelid = (
SELECT c.oid
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE
n.nspname = CURRENT_SCHEMA()
AND c.relname = 'device_ip_lookups'
)
AND attname = 'active_date'
AND NOT attisdropped
AND attnum > 0
)
THEN
RAISE NOTICE 'ADDING COLUMN';
ALTER TABLE device_ip_lookups
ADD COLUMN active_date TIMESTAMP;
ELSE
RAISE NOTICE 'SKIPPING, COLUMN ALREADY EXISTS';
END IF;
END $$;
For use variables in for example alter table:
DO $$
DECLARE name_pk VARCHAR(200);
BEGIN
select constraint_name
from information_schema.table_constraints
where table_schema = 'schema_name'
and table_name = 'table_name'
and constraint_type = 'PRIMARY KEY' INTO name_pk;
IF (name_pk := '') THEN
EXECUTE 'ALTER TABLE schema_name.table_name DROP CONSTRAINT ' || name_pk;
Postgresql does not have bare variables, you could use a temporary table.
variables are only available in code blocks or as a user-interface feature.
If you need a bare variable you could use a temporary table:
CREATE TEMP TABLE list AS VALUES ('foobar');
SELECT dbo.PubLists.*
FROM dbo.PubLists,list
WHERE Name = list.column1;
I had to do something like this
CREATE OR REPLACE FUNCTION MYFUNC()
RETURNS VOID AS $$
DO
$do$
BEGIN
DECLARE
myvar int;
...
END
$do$
$$ LANGUAGE SQL;
You can also simply make a constant query that you use in the actual query:
WITH vars as (SELECT 'foobar' AS list)
SELECT *
FROM dbo.PubLists, vars
WHERE Name = vars.list
Given the popularity, and somewhat incomplete answers I'll provide two solutions.
A do block that won't return rows. You can return rows with a transaction cursor, but it's a bit messy.
A function (that returns rows)
Below I'll use an over-baked example of updating the tweet on the bottom right "blurb" with "hello world".
id (serial)
pub_id (text)
tweet (text)
1
abc
hello world
2
def
blurb
A simple do block
do $$
declare
src_pub_id text;
dst_pub_id text;
src_id int;
dest_id int;
src_tweet text;
begin
src_pub_id := 'abc';
dst_pub_id := 'def';
-- query result into a temp variable
src_id := (select id from tweets where pub_id = src_pub_id);
-- query result into a temp variable (another way)
select tweet into src_tweet from tweets where id = src_id;
dest_id := (select id from tweets where pub_id = dst_pub_id);
update tweets set tweet=src_tweet where id = dest_id;
end $$ language plpgsql; -- need the language to avoid ERROR 42P13
A function
create or replace function sync_tweets(
src_pub_id text, -- function arguments
dst_pub_id text
) returns setof tweets as -- i.e. rows. int, text work too
$$
declare
src_id int; -- temp function variables (not args)
dest_id int;
src_tweet text;
begin
-- query result into a temp variable
src_id := (select id from tweets where pub_id = src_pub_id);
-- query result into a temp variable (another way)
select tweet into src_tweet from tweets where id = src_id;
dest_id := (select id from tweets where pub_id = dst_pub_id);
update tweets set tweet=src_tweet where id = dest_id;
return query -- i.e. rows, return 0 with return int above works too
select * from tweets where pub_id in (src_pub_id, dst_pub_id);
end
$$ language plpgsql; -- need the language to avoid ERROR 42P13
-- Run it!
select * from sync_tweets('abc', 'def');
-- Optional drop if you don't want the db to keep your function
drop function if exists sync_tweets(text, text);
/*
Outputs
__________________________________________________
| id (serial) | pub_id (text) | tweet (text) |
|---------------|-----------------|----------------|
| 1 | abc | hello world |
| 2 | def | blurb |
--------------------------------------------------
*/