DB2 Create Table X when X is a variable? - db2

In Oracle, we do this:
def TNAME=&1
create table &TNAME (foo varchar(10));
How to do the equivalent for DB2? The constraints are:
1. The create table statement is generated when TNAME is unknown
2. The create table statement is in a file which cannot be modified
3. Cannot create a temporary file with TNAME substituted by awk
4. Essentially we want to pass in the table name at run time as an argument
Possible?

The EXECUTE IMMEDIATE statement can be used in just about any block of SQL that is surrounded by a BEGIN and END, such as a stored procedure or anonymous compound statement.
CREATE PROCEDURE create_table_from_file( IN fileDir VARCHAR( 1024 ),
IN fileName VARCHAR( 128 ),
IN tabSchema VARCHAR( 128 ),
IN tabName VARCHAR( 128 )
)
BEGIN
DECLARE fh UTL_FILE.FILE_TYPE;
DECLARE sqlStmt VARCHAR( 32672 );
DECLARE currentLine VARCHAR( 32672 );
DECLARE SQLCODE INTEGER DEFAULT 0;
DECLARE SQLSTATE CHAR(5) DEFAULT '00000';
DECLARE SQLSTATE1 CHAR(5) DEFAULT '00000';
DECLARE CONTINUE HANDLER FOR SQLSTATE 'ORANF' SET SQLSTATE1 = SQLSTATE;
SET sqlStmt = 'CREATE TABLE '
|| CASE WHEN ( NULLIF( tabSchema, '' ) IS NOT NULL )
THEN RTRIM( tabSchema ) || '.'
ELSE '' END || tabName;
CALL UTL_DIR.CREATE_OR_REPLACE_DIRECTORY( 'createTablePath', fileDir );
SET fh = UTL_FILE.FOPEN( 'createTablePath', fileName, 'r' );
loop1: LOOP
CALL UTL_FILE.GET_LINE( fh, currentLine );
IF SQLSTATE1 = 'ORANF' THEN -- NO DATA FOUND
LEAVE loop1;
END IF;
SET sqlStmt = sqlStmt || ' ' || currentLine ;
END LOOP;
EXECUTE IMMEDIATE sqlStmt;
END
#

I created a file abc containing:
create table TNAME (foo varchar(10))
Then I did, on a Unix command-line:
sed s/TNAME/xyz/g abc | db2
which created a table called xyz in DB2 as I wanted. Thanks.

Related

postgresql execute dynamic sql command

I'm slowly learning more about PostgreSQL, as we are attempting to move to it from MSSQL Server.
In MSSQL I have the following code:
DECLARE ServiceabilityParameters
CURSOR FORWARD_ONLY READ_ONLY STATIC LOCAL FOR
SELECT WorkbookParameterType.ID,
WorkbookParameterType.Name,
WorkbookParameter.DefaultValue,
WorkbookParameter.CommandText
FROM WorkbookParameter
JOIN WorkbookParameterType ON WorkbookParameterType.ID = WorkbookParameter.WorkbookParameterTypeID
JOIN WorkbookParameterDirectionType ON WorkbookParameterDirectionType.ID = WorkbookParameter.WorkbookParameterDirectionTypeID
AND WorkbookParameterDirectionType.Writable = 1
WHERE WorkbookParameter.WorkbookID = #WorkbookID
OPEN ServiceabilityParameters
FETCH NEXT FROM ServiceabilityParameters INTO #WorkbookParameterTypeID, #WorkbookParameterTypeName, #WorkbookDefaultValue, #WorkbookCommandText
WHILE ##FETCH_STATUS = 0
BEGIN
DECLARE #ActualValue NVARCHAR(256) = NULL
IF #WorkbookCommandText IS NOT NULL
BEGIN
EXEC sp_executesql #statement = #WorkbookCommandText,
#params = N'#ApplicationContainerID INT, #Value NVARCHAR(256) OUTPUT',
#ApplicationContainerID = #ApplicationContainerID,
#Value = #ActualValue OUTPUT
END
IF #ActualValue IS NULL AND #WorkbookDefaultValue IS NOT NULL
BEGIN
SET #ActualValue = #WorkbookDefaultValue
END
INSERT #InputParameters (
ID, Name, Value
) VALUES (
#WorkbookParameterTypeID, #WorkbookParameterTypeName, #ActualValue
)
FETCH NEXT FROM ServiceabilityParameters INTO #WorkbookParameterTypeID, #WorkbookParameterTypeName, #WorkbookDefaultValue, #WorkbookCommandText
END
CLOSE ServiceabilityParameters
DEALLOCATE ServiceabilityParameters
I'm trying to work out how to do the sp_executesql part in a PostgreSQL function. I believe that I can do the rest, but most of the examples that I have found show a simple select with maybe a few variables, whereas I need to execute another function, with parameters, where the function name is text in a table.
Many Thanks.
In case you want to execute a function with parameters
EXECUTE 'SELECT Value FROM ' || v_workbookCommandText || '(ApplicationContainerID :=$1)'
INTO v_actualValue
USING v_applicationContainerID;
In case you need select records a function, you can using INOUT refcursor variable
EXECUTE 'SELECT Value FROM ' || v_workbookCommandText || '(ApplicationContainerID :=$1, refcur:= $2)'
INTO v_actualValue
USING v_applicationContainerID, my_cursor;
I think what you want to do is EXECUTE 'some string', like this:
EXECUTE 'SELECT count(*) FROM mytable WHERE inserted_by = $1 AND inserted <= $2'
INTO c
USING checked_user, checked_date;
Another option is to create and use your own PL/PGSQL functions.

How to delete column in all tables

I am use Firebird 2.5
I have multiple tables with column name 'col1', and I would like delete it.
I can use this statement:
DELETE FROM RDB$RELATION_FIELDS
WHERE RDB$FIELD_NAME = 'col1';
But I do not know is it safe.
I try to use execute block for multiple execute statements, but I do not know, how to combine it.
SET TERM ^ ;
EXECUTE BLOCK AS
DECLARE s AS VARCHAR(200)
BEGIN
WHILE (SELECT rf.RDB$RELATION_NAME FROM RDB$RELATION_FIELDS rf WHERE rf.RDB$FIELD_NAME = 'AKTYWNY';) DO
BEGIN
ALTER TABLE :s DROP c1;
END
END^
SET TERM ; ^
This is example how to do this in stored procedure :
create or alter procedure DELETE_COL (
F_COL char(31))
as
declare variable V_STAT varchar(256);
declare variable R_NAME char(31);
begin
for
select f.rdb$relation_name
from rdb$relation_fields f
join rdb$relations r on f.rdb$relation_name = r.rdb$relation_name
and r.rdb$view_blr is null
and (r.rdb$system_flag is null or r.rdb$system_flag = 0)
where f.rdb$field_name = :f_col
order by 1, f.rdb$field_position
into
:r_name -- Table Name
do
begin
v_stat = 'alter table ' || :r_name || ' drop ' || :f_col;
execute statement(v_stat); /*because alter table ... is not allowed here */
end
end
You can use this in execute block also.

Why am I getting relation already exists in this execute statement?

Here's the function:
CREATE OR REPLACE FUNCTION get_img(ptype text, pid integer, pdpi integer)
RETURNS bytea AS
$BODY$
declare psize char(1); pimg bytea;
begin
select size into psize from dpi_size where dpi in(select max(dpi) from dpi_size where dpi <= pdpi);
select coalesce(psize, 's') into psize;
if ptype = 'cat' then
execute 'select img_' || psize || ' into pimg from cat where id = $1' using pid;
end if;
return pimg;
end; $BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION get_img(text, integer, integer)
OWNER TO postgres;
Table cat has img_s, img_m, img_l and an id. Here's what I get when I run select.
select handyman_get_img ('cat', 11, 320)
ERROR: relation "pimg" already exists CONTEXT: SQL statement "select
img_l into pimg from cat where id = $1" PL/pgSQL function
get_img(text,integer,integer) line 8 at EXECUTE statement
Can anyone enlighten me why it says 'pimg' already exists?
Thanks.
You can't use a variable inside the string literal for execute.
The string passed to execute is run "as is" and select .. into pimg from ... is an old (non-standard) syntax that does the same as create table pimg as select ....
If you want to store the result of an execute into a variable you need to use a different syntax:
execute 'select img_' || psize || ' from cat where id = $1'
into pimg
using pid;
Note how the into is outside of the string that is being executed.

"Function does not exist" error when creating the function with more parameters

I have dropped a PL/pgSQL function and I try to recreate it with one more parameter (with default value). But I get a strange error:
ERROR: function vytvor_kod_sj(text, integer, integer) does not exist
SQL state: 42883
I would expect such an error while dropping or calling the function, not while creating it. I made sure that the error occurs exactly while trying to create it, not in any of the other functions or triggers I created from the same sql file.
I made a dummy function without the last parameter and it works now, but it is definitely not what I want - not only I don't need the function without the last parameter anymore, I usually call the function only with the first two or three parameter, so I need to drop it just after creating the new version of my function to avoid mismatches. Fortunately, there are no errors while doing this, but it is hardly optimal solution.
So does anyone know how to solve this mysterious problem?
I have PostgreSQL 9.3.4 on Windows 32; I use pgAdmin 1.18.1
My code:
CREATE OR REPLACE FUNCTION vytvor_kod_sj( _akce text, typ_sj integer,
podtyp integer DEFAULT 0, styl integer DEFAULT NULL )
RETURNS text AS $$
DECLARE
--styl integer;
_nazev_seq text;
_min integer;
_max integer;
_nazev text;
_soucasna integer;
BEGIN
IF styl IS NULL THEN
SELECT nomenklatura INTO styl FROM akce WHERE kod_akce = _akce;
END IF;
IF NOT EXISTS( SELECT id_nom FROM pro_nom_pravidlo_sj
WHERE id_nom = styl AND sj = typ_sj AND typ = podtyp ) THEN
IF podtyp = 0 OR NOT EXISTS( SELECT id_nom FROM pro_nom_pravidlo_sj
WHERE id_nom = styl AND sj = typ_sj AND typ = 0 ) THEN
RAISE NOTICE 'Pro daný typ stratigrafické jednotky není vytvořeno žádné pravidlo!';
RETURN NULL;
ELSE
podtyp := 0;
END IF;
END IF;
_nazev_seq := _akce || '_' || typ_sj || '_' || podtyp || '_seq';
IF NOT EXISTS (SELECT 0 FROM pg_class where relname = _nazev_seq ) THEN
SELECT min, max INTO _min, _max FROM pro_nom_pravidlo_sj
WHERE id_nom = styl AND sj = typ_sj AND typ = podtyp;
IF _max IS NOT NULL THEN
EXECUTE format('CREATE SEQUENCE %I MINVALUE %s MAXVALUE %s ', _nazev_seq, _min, _max);
ELSE
EXECUTE format('CREATE SEQUENCE %I MINVALUE %s ', _nazev_seq, _min);
END IF;
END IF;
--IF strict IS TRUE THEN
RETURN (
SELECT predpona FROM pro_nom_pravidlo_sj
WHERE id_nom = styl AND sj = typ_sj AND typ = podtyp
) || CAST(nextval(_nazev_seq) AS TEXT);
--END IF;
END;
$$ LANGUAGE plpgsql
SECURITY DEFINER;
ALTER FUNCTION vytvor_kod_sj( text, integer, integer ) OWNER TO ins_daemon;
The old version had styl variable declared, not as a parameter, and there was no test whether it is null. Otherwise I didn't change anything.
I noticed the cause of the problem just after posting the question:
ALTER FUNCTION vytvor_kod_sj( text, integer, integer ) OWNER TO ins_daemon;
It tries to alter the old function.

Generic trigger to restrict insertions based on count

Background
In a PostgreSQL 9.0 database, there are various tables that have many-to-many relationships. The number of those relationships must be restricted. A couple of example tables include:
CREATE TABLE authentication (
id bigserial NOT NULL, -- Primary key
cookie character varying(64) NOT NULL, -- Authenticates the user with a cookie
ip_address character varying(40) NOT NULL -- Device IP address (IPv6-friendly)
)
CREATE TABLE tag_comment (
id bigserial NOT NULL, -- Primary key
comment_id bigint, -- Foreign key to the comment table
tag_name_id bigint -- Foreign key to the tag name table
)
Different relationships, however, have different limitations. For example, in the authentication table, a given ip_address is allowed 1024 cookie values; whereas, in the tag_comment table, each comment_id can have 10 associated tag_name_ids.
Problem
Currently, a number of functions have these restrictions hard-coded; scattering the limitations throughout the database, and preventing them from being changed dynamically.
Question
How would you impose a maximum many-to-many relationship limit on tables in a generic fashion?
Idea
Create a table to track the limits:
CREATE TABLE imposed_maximums (
id serial NOT NULL,
table_name character varying(128) NOT NULL,
column_group character varying(128) NOT NULL,
column_count character varying(128) NOT NULL,
max_size INTEGER
)
Establish the restrictions:
INSERT INTO imposed_maximums
(table_name, column_group, column_count, max_size) VALUES
('authentication', 'ip_address', 'cookie', 1024);
INSERT INTO imposed_maximums
(table_name, column_group, column_count, max_size) VALUES
('tag_comment', 'comment_id', 'tag_id', 10);
Create a trigger function:
CREATE OR REPLACE FUNCTION impose_maximum()
RETURNS trigger AS
$BODY$
BEGIN
-- Join this up with imposed_maximums somehow?
select
count(1)
from
-- the table name
where
-- the group column = NEW value to INSERT;
RETURN NEW;
END;
Attach the trigger to every table:
CREATE TRIGGER trigger_authentication_impose_maximum
BEFORE INSERT
ON authentication
FOR EACH ROW
EXECUTE PROCEDURE impose_maximum();
Obviously it won't work as written... is there a way to make it work, or otherwise enforce the restrictions such that they are:
in a single location; and
not hard-coded?
Thank you!
I've been doing a similar type of generic triggers.
The most tricky part is to get the value entry in the NEW record based on the column name.
I'm doing it the following way:
convert NEW data into array;
find the attnum of the column and use it as an index for the array.
This approach works as long as there're no commas in the data :( I don't know of other ways how to convert NEW or OLD variables into the array of values.
The following function might help:
CREATE OR REPLACE FUNCTION impose_maximum() RETURNS trigger AS $impose_maximum$
DECLARE
_sql text;
_cnt int8;
_vals text[];
_anum int4;
_im record;
BEGIN
_vals := string_to_array(translate(trim(NEW::text), '()', ''), ',');
FOR _im IN SELECT * FROM imposed_maximums WHERE table_name = TG_TABLE_NAME LOOP
SELECT attnum INTO _anum FROM pg_catalog.pg_attribute a
JOIN pg_catalog.pg_class t ON t.oid = a.attrelid
WHERE t.relkind = 'r' AND t.relname = TG_TABLE_NAME
AND NOT a.attisdropped AND a.attname = _im.column_group;
_sql := 'SELECT count('||quote_ident(_im.column_count)||')'||
' FROM '||quote_ident(_im.table_name)||
' WHERE '||quote_ident(_im.column_group)||' = $1';
EXECUTE _sql INTO _cnt USING _vals[_anum];
IF _cnt > CAST(_im.max_size AS int8) THEN
RAISE EXCEPTION 'Maximum of % hit for column % in table %(%=%)',
_im.max_size, _im.column_count,
_im.table_name, _im.column_group, _vals[_anum];
END IF;
END LOOP;
RETURN NEW;
END; $impose_maximum$ LANGUAGE plpgsql;
This function will check for all conditions defined for a given table.
Yes, there is a way to make it work.
In my personal opinion your idea is the way to go. It just needs one level of "meta". So, the table imposed_restrictions should have trigger(s), which is (are) fired after insert, update and delete. The code should then in turn create, modify or remove triggers and functions.
Take a look at execute statement of PL/pgSQL, which - essentially - allows you to execute any string. Needless to say, this string may contain definitions of triggers, functions, etc. Obviously, you have the access to OLD and NEW in the triggers, so you can fill in the placeholders in the string and you are done.
I believe you should be able to accomplish what you want with this answer. Please note that this is my personal view on the topic and it might not be an optimal solution - I would like to see a different, maybe also more efficient, approach.
Edit - Below is a sample from one of my old projects. It is located inside the function that is triggered before update (though now I get to think of it, maybe it should have been called after ;) And yes, the code is messy, as it does not use the nice $escape$ syntax. I was really, really young then. Nonetheless, the snipped demonstrates that it is possible to achieve what you want.
query:=''CREATE FUNCTION '' || NEW.function_name || ''('';
IF NEW.parameter=''t'' THEN
query:=query || ''integer'';
END IF;
query:=query || '') RETURNS setof '' || type_name || '' AS'' || chr(39);
query:=query || '' DECLARE list '' || type_name || ''; '';
query:=query || ''BEGIN '';
query:=query || '' FOR list IN EXECUTE '' || chr(39) || chr(39);
query:=query || temp_s || '' FROM '' || NEW.table_name;
IF NEW.parameter=''t'' THEN
query:=query || '' WHERE id='' || chr(39) || chr(39) || ''||'' || chr(36) || ''1'';
ELSE
query:=query || '';'' || chr(39) || chr(39);
END IF;
query:=query || '' LOOP RETURN NEXT list; '';
query:=query || ''END LOOP; RETURN; END; '' || chr(39);
query:=query || ''LANGUAGE '' || chr(39) || ''plpgsql'' || chr(39) || '';'';
EXECUTE query;
These function + trigger could be used as a template. If You combine them with #Sorrow 's technique of dynamically generating the functions + triggers, this could solve the OP's problem.
Please note that, instead of recalculating the count for every affected row (by calling the COUNT() aggregate function), I maintain an 'incremental' count. This should be cheaper.
DROP SCHEMA tmp CASCADE;
CREATE SCHEMA tmp ;
SET search_path='tmp';
CREATE TABLE authentication
( id bigserial NOT NULL -- Primary key
, cookie varchar(64) NOT NULL -- Authenticates the user with a cookie
, ip_address varchar(40) NOT NULL -- Device IP address (IPv6-friendly)
, PRIMARY KEY (ip_address, cookie)
);
CREATE TABLE authentication_ip_count (
ip_address character varying(40) NOT NULL
PRIMARY KEY -- REFERENCES authentication(ip_address)
, refcnt INTEGER NOT NULL DEFAULT 0
--
-- This is much easyer:
-- keep the max value inside the table
-- + use a table constraint
-- , maxcnt INTEGER NOT NULL DEFAULT 2 -- actually 100
-- , CONSTRAINT no_more_cookies CHECK (refcnt <= maxcnt)
);
CREATE TABLE imposed_maxima
( id serial NOT NULL
, table_name varchar NOT NULL
, column_group varchar NOT NULL
, column_count varchar NOT NULL
, max_size INTEGER NOT NULL
, PRIMARY KEY (table_name,column_group,column_count)
);
INSERT INTO imposed_maxima(table_name,column_group,column_count,max_size)
VALUES('authentication','ip_address','cookie', 2);
CREATE OR REPLACE FUNCTION authentication_impose_maximum()
RETURNS trigger AS
$BODY$
DECLARE
dummy INTEGER;
BEGIN
IF (TG_OP = 'INSERT') THEN
INSERT INTO authentication_ip_count (ip_address)
SELECT sq.*
FROM ( SELECT NEW.ip_address) sq
WHERE NOT EXISTS (
SELECT *
FROM authentication_ip_count nx
WHERE nx.ip_address = sq.ip_address
);
UPDATE authentication_ip_count
SET refcnt = refcnt + 1
WHERE ip_address = NEW.ip_address
;
SELECT COUNT(*) into dummy -- ac.refcnt, mx.max_size
FROM authentication_ip_count ac
JOIN imposed_maxima mx ON (1=1) -- outer join
WHERE ac.ip_address = NEW.ip_address
AND mx.table_name = 'authentication'
AND mx.column_group = 'ip_address'
AND mx.column_count = 'cookie'
AND ac.refcnt > mx.max_size
;
IF FOUND AND dummy > 0 THEN
RAISE EXCEPTION 'Cookie moster detected';
END IF;
ELSIF (TG_OP = 'DELETE') THEN
UPDATE authentication_ip_count
SET refcnt = refcnt - 1
WHERE ip_address = OLD.ip_address
;
DELETE FROM authentication_ip_count ac
WHERE ac.ip_address = OLD.ip_address
AND ac.refcnt <= 0
;
-- ELSIF (TG_OP = 'UPDATE') THEN
-- (Only needed if we allow updates of ip-address)
-- otherwise the count stays the same.
END IF;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql;
CREATE TRIGGER trigger_authentication_impose_maximum
BEFORE INSERT OR UPDATE OR DELETE
ON authentication
FOR EACH ROW
EXECUTE PROCEDURE authentication_impose_maximum();
-- Test it ...
INSERT INTO authentication(ip_address, cookie) VALUES ('1.2.3.4', 'Some koekje' );
INSERT INTO authentication(ip_address, cookie) VALUES ('1.2.3.4', 'kaakje' );
INSERT INTO authentication(ip_address, cookie) VALUES ('1.2.3.4', 'Yet another cookie' );
RESULTS:
INSERT 0 1
CREATE FUNCTION
CREATE TRIGGER
INSERT 0 1
INSERT 0 1
ERROR: Cookie moster detected