Execute Immediate in cursor on ibm db2 - cursor

I'm having difficulties creating a SP in which I pass in a name of a table and query the SYS2 library to find out if it has an auto-increment field. If it does I query for the max value of that field in the table and then alter the table so the next used value is that result plus 1. This is for use when migrating production data over to development.
I'm not sure if it is possible to use "Execute Immediate" as part of a cursor declaration. I'm still fairly new to db2 in general, never mind for IBM. So any assistance would be greatly appreciated. If "Execute Immediate" is not allowed in a cursor declaration, how would I go about doing this?
I'm getting the error on the Cursor declaration (line 10), but here is the exact error code I'm getting:
SQL State: 42601
Vendor Code: -199
Message: [SQL0199] Keyword IMMEDIATE not expected. Valid tokens: <END-OF-STATEMENT>. Cause . . . . . : The keyword IMMEDIATE was not expected here. A syntax error was detected at keyword IMMEDIATE. The partial list of valid tokens is <END-OF-STATEMENT>. This list assumes that the statement is correct up to the unexpected keyword. The error may be earlier in the statement but the syntax of the statement seems to be valid up to this point. Recovery . . . : Examine the SQL statement in the area of the specified keyword. A colon or SQL delimiter may be missing. SQL requires reserved words to be delimited when they are used as a name. Correct the SQL statement and try the request again.
And then finally here is my SP
/* Creating procedure DLLIB.SETNXTINC# */
CREATE OR REPLACE PROCEDURE DLLIB.SETNXTINC#(IN TABLE CHARACTER (10) ) LANGUAGE SQL CONTAINS SQL PROGRAM TYPE SUB CONCURRENT ACCESS RESOLUTION DEFAULT DYNAMIC RESULT SETS 0 OLD SAVEPOINT LEVEL COMMIT ON RETURN NO
SET #STMT1 = 'SELECT COLUMN_NAME ' ||
'FROM QSYS2.SYSCOLUMNS ' ||
'WHERE TABLE_SCHEMA =''DLLIB'' and table_name = ''' || TRIM(TABLE) || '''' ||
'AND HAS_DEFAULT = ''I'' ' ||
'OR HAS_DEFAULT = ''J'';';
DECLARE cursor1 CURSOR FOR
EXECUTE IMMEDIATE #STMT1;
OPEN cursor1;
WHILE (sqlcode == 0){
FETCH cursor1 INTO field;
SET #STMT2 = 'ALTER TABLE DLLIB.' || TRIM(TABLE) || ''' ' ||
'ALTER COLUMN ' || TRIM(field) || ' RESTART WITH ( ' ||
'SELECT MAX(' || TRIM(field) || ') ' ||
'FROM DLLIB.' || TRIM(TABLE) || ');';
EXECUTE IMMEDIATE #STMT2;
};;
/* Setting label text for DLLIB.SETNXTINC# */
LABEL ON ROUTINE DLLIB.SETNXTINC# ( CHAR() ) IS 'Set the next auto-increment';
/* Setting comment text for DLLIB.SETNXTINC# */
COMMENT ON PARAMETER ROUTINE DLLIB.SETNXTINC# ( CHAR() ) (TABLE IS 'Table from DLLIB' ) ;

First off, you don't need to dynamically prepare you first statement.
Secondly, you can't use a SELECT in the RESTART WITH, you'll have to use 2 statements
Thirdly, if you use VARCHAR instead of CHAR, you don't need to use any TRIM()s
Lastly, using TABLE as a parameter name is bad practice as it is a reserved word.
You want something like so
CREATE OR REPLACE PROCEDURE QGPL.SETNXTINC#(IN MYTABLE VARCHAR (128) )
LANGUAGE SQL
MODIFIES SQL DATA
PROGRAM TYPE SUB
CONCURRENT ACCESS RESOLUTION DEFAULT
DYNAMIC RESULT SETS 0
OLD SAVEPOINT LEVEL
COMMIT ON RETURN NO
BEGIN
declare mycolumn varchar(128);
declare stmt2 varchar(1000);
declare stmt3 varchar(1000);
declare mymaxvalue integer;
-- Table known at runtime, a static statement is all we need
SELECT COLUMN_NAME INTO mycolumn
FROM QSYS2.SYSCOLUMNS
WHERE TABLE_SCHEMA = 'DLLIB'
AND TABLE_NAME = mytable
AND HAS_DEFAULT = 'I'
OR HAS_DEFAULT = 'J';
-- Need to use a dynamic statement here
-- as the affected table is not known till runtime
-- need VALUES INTO as SELECT INTO can not be used dynamically
SET STMT2 = 'VALUES (SELECT MAX(' || mycolumn || ') ' ||
'FROM DLLIB.' || mytable || ')' || 'INTO ?';
PREPARE S2 from stmt2;
EXECUTE S2 using mymaxvalue;
-- we want to restart with a value 1 more than the current max
SET mymaxvalue = mymaxvalue + 1;
-- Need to use a dynamic statement here
-- as the affected table is not known till runtime
SET STMT3 = 'ALTER TABLE DLLIB.' || mytable || ' ALTER COLUMN '
|| mycolumn || ' RESTART WITH ' || char(mymaxvalue);
EXECUTE IMMEDIATE STMT3;
END;
One more thing to consider, you might want to LOCK the table in exclusive mode prior to running STMT2; otherwise there's a possibility that a record with a higher value got added between the execution of STMT2 and STMT3.

Related

Altering data types of all columns in a table (PostgreSQL 13)

I have a table import.hugo (import is schema) and I need to change all columns data type from text to numeric. The table already has data, so to alter column (for example) y I use this:
ALTER TABLE import.hugo ALTER COLUMN y TYPE numeric USING (y::numeric);
But the table has many columns and I donĀ“t want to do it manually.
I found something here and try it like this:
do $$
declare
t record;
begin
for t IN select column_name, table_name
from information_schema.columns
where table_name='hugo' AND data_type='text'
loop
execute 'alter table ' || t.table_name || ' alter column ' || t.column_name || ' type numeric';
end loop;
end$$;
When I run it, it doesn't work, it says: relation "hugo" does not exist
I tried many many more variants of this code, but I can't make it work.
I also don't know, how to implement this part: USING (y::numeric); (from the very first command) into this command.
Ideally, I need it in a function, where the user can define the name of a table, in which we are changing the columns data type. So function to call like this SELECT function_name(table_name_to_alter_columns);
Thanks.
Selecting table_name from information_schema.columns isn't enough, since the import schema is not in your search path. You can add the import schema to your search_path by appending import to whatever shows up in SHOW search_path, or you can just add the table_schema column to your DO block:
do $$
declare
t record;
begin
for t IN select column_name, table_schema || '.' || table_name as full_name
from information_schema.columns
where table_name='hugo' AND data_type='text'
loop
execute 'alter table ' || t.full || ' alter column ' || t.column_name || ' type numeric USING (' || t.column_name || '::numeric)';
end loop;
end$$;

Procedure with Implicit cursor is compiling but not printing

i am currently stuck on this pl/sql problem, i am trying to gather all the information of a APPLICANT who APPLIES to a certain POSITION (3 different tables) into a stored procedure.
Unfortunately i am very new to oracle and pl/sql so i think my joins may be sloppy aswell as my main problem of dbms_output.put_line is not printing out the data that i need. I figure maybe it is in the wrong place in the code block or there is a problem coming all the way down from my join statements.
enter code here
SET ECHO ON
SET FEEDBACK ON
SET LINESIZE 100
SET PAGESIZE 100
SET SERVEROUTPUT ON
CREATE OR REPLACE PROCEDURE APPLICANTS IS
first_name APPLICANT.FNAME%TYPE;
last_name APPLICANT.LNAME%TYPE;
position_number APPLIES.PNUMBER%TYPE;
position_title POSITION.TITLE%TYPE;
str VARCHAR(300);
CURSOR fnameCursor IS
SELECT FNAME
FROM APPLICANT;
BEGIN
FOR fnameCursor IN (SELECT APPLICANT.LNAME, APPLIES.PNUMBER,
POSITION.TITLE INTO last_name, position_number, position_title
FROM APPLICANT JOIN APPLIES ON APPLICANT.ANUMBER =
APPLIES.ANUMBER
JOIN POSITION ON POSITION.PNUMBER = APPLIES.PNUMBER
WHERE FNAME = first_name
ORDER BY LNAME DESC)
LOOP
str := position_number || '' || first_name || '' || last_name || ': ' ||
position_title;
dbms_output.put_line(str);
--EXIT WHEN fnameCursor%NOTFOUND;
END LOOP;
END APPLICANTS;
/
EXECUTE APPLICANTS;
It is surprising to know that the procedure is compiling. You are using an INTO clause inside an implicit cursor query. Also, I believe first_name should come as an argument to your procedure but you have not mentioned it.
More importantly, the columns selected/aliased within the cursor should be referred within the loop using cursor's record variable fnamecursor
CREATE OR REPLACE PROCEDURE APPLICANTS(first_name APPLICANT.FNAME%TYPE)
IS
str VARCHAR(300);
BEGIN
FOR fnamecursor IN (
SELECT applicant.lname as last_name,
applies.pnumber as position_number,
position.title as position_title
FROM applicant
JOIN applies ON applicant.anumber = applies.anumber
JOIN position ON position.pnumber = applies.pnumber
WHERE fname = first_name
ORDER BY lname DESC
) LOOP
str := fnamecursor.position_number || ' ' || first_name || ' ' ||
fnamecursor.last_name || ': ' || fnamecursor.position_title;
dbms_output.put_line(str);
END LOOP;
END applicants;
/

Oracle: update multiple columns with dynamic query

I am trying to update all the columns of type NVARCHAR2 to some random string in my database. I iterated through all the columns in the database of type nvarchar2 and executed an update statement for each column.
for i in (
select
table_name,
column_name
from
user_tab_columns
where
data_type = 'NVARCHAR2'
) loop
execute immediate
'update ' || i.table_name || 'set ' || i.column_name ||
' = DBMS_RANDOM.STRING(''X'', length('|| i.column_name ||'))
where ' || i.column_name || ' is not null';
Instead of running an update statement for every column of type nvarchar2, I want to update all the nvarchar columns of a particular table with a single update statement for efficiency(that is, one update statement per 1 table). For this, I tried to bulk collect all the nvarchar columns in a table, into a temporary storage. But, I am stuck at writing a dynamic update statement for this. Could you please help me with this? Thanks in advance!
You can try this one. However, depending on your table it could be not the fastest solution.
for aTable in (
select table_name,
listagg(column_name||' = nvl2('||column_name||', DBMS_RANDOM.STRING(''XX'', length('||column_name||')), NULL)') WITHIN GROUP (ORDER BY column_name) as upd,
listagg(column_name) WITHIN GROUP (ORDER BY column_name) as con
from user_tab_columns
where DATA_TYPE = 'NVARCHAR2'
group by table_name
) loop
execute immediate
'UPDATE '||aTable.table_name ||
' SET '||aTable.upd ||
' WHERE COALESCE('||aTable.con||') IS NOT NULL';
end loop;
Resulting UPDATE (verify with DBMS_OUTPUT.PUT_LINE(..)) should look like this:
UPDATE MY_TABLE SET
COL_A = nvl2(COL_A, DBMS_RANDOM.STRING('XX', length(COL_A)), NULL),
COL_B = nvl2(COL_B, DBMS_RANDOM.STRING('XX', length(COL_B)), NULL)
WHERE COALESCE(COL_A, COL_B) IS NOT NULL;
Please try this:
DECLARE
CURSOR CUR IS
SELECT
TABLE_NAME,
LISTAGG(COLUMN_NAME||' = DBMS_RANDOM.STRING(''X'', length(NVL('||
COLUMN_NAME ||',''A''))',', ')
WITHIN GROUP (ORDER BY COLUMN_ID) COLUMN_NAME
FROM DBA_TAB_COLUMNS
WHERE DATA_TYPE = 'NVARCHAR2'
GROUP BY TABLE_NAME;
TYPE TAB IS TABLE OF CUR%ROWTYPE INDEX BY PLS_INTEGER;
T TAB;
S VARCHAR2(4000);
BEGIN
OPEN CUR;
LOOP
FETCH CUR BULK COLLECT INTO T LIMIT 1000;
EXIT WHEN T.COUNT = 0;
FOR i IN 1..T.COUNT LOOP
S := 'UPDATE ' || T(i).TABLE_NAME || ' SET ' || T(i).COLUMN_NAME;
EXECUTE IMMEDIATE S;
END LOOP;
END LOOP;
COMMIT;
END;
/
I think that would do it. But as I said in the comments, you need to validate the syntax since I don't have an Oracle instance to test it.
for i in (
select table_name,
'update || i.table_name || set ' ||
listagg( column_name || '= NLV( ' || column_name || ', '
|| 'DBMS_RANDOM.STRING(''X'', length('|| column_name ||') ) )'
|| ';'
) WITHIN GROUP (ORDER BY column_name) as updCommand
from user_tab_columns
where DATA_TYPE = 'NVARCHAR2'
group by table_name
) loop
execute immediate i.updCommand;
end loop;
If you find any error, let me know in the comments so I can fix it.

MS SQL - CONTAINS full-text search w/ variable number of values, not using dynamic sql

I've created a full-text indexed column on a table.
I have a stored procedure to which I may pass the value of a variable "search this text". I want to search for "search", "this" and "text" within the full-text column. The number of words to search would be variable.
I could use something like
WHERE column LIKE '%search%' OR column LIST '%this%' OR column LIKE '%text%'
But that would require me to use dynamic SQL, which I'm trying to avoid.
How can I use my full-text search to find each of the words, presumably using CONTAINS, and without converting the whole stored procedure to dynamic SQL?
If you say you definitely have SQL Table Full Text Search Enabled, Then you can use query like below.
select * from table where contains(columnname,'"text1" or "text2" or "text3"' )
See link below for details
Full-Text Indexing Workbench
So I think I came up with a solution. I created the following scalar function:
CREATE FUNCTION [dbo].[fn_Util_CONTAINS_SearchString]
(
#searchString NVARCHAR(MAX),
#delimiter NVARCHAR(1) = ' ',
#ANDOR NVARCHAR(3) = 'AND'
)
RETURNS NVARCHAR(MAX)
AS
BEGIN
IF #searchString IS NULL OR LTRIM(RTRIM(#searchString)) = '' RETURN NULL
-- trim leading/trailing spaces
SET #searchString = LTRIM(RTRIM(#searchString))
-- remove double spaces (prevents empty search terms)
WHILE CHARINDEX(' ', #searchString) > 0
BEGIN
SET #searchString = REPLACE(#searchString,' ',' ')
END
-- reformat
SET #searchString = REPLACE(#searchString,' ','" ' + #ANDOR + ' "') -- replace spaces with " AND " (quote) AND (quote)
SET #searchString = ' "' + #searchString + '" ' -- surround string with quotes
RETURN #searchString
END
I can get my results:
DECLARE #ftName NVARCHAR (1024) = dbo.fn_Util_CONTAINS_SearchString('value1 value2',default,default)
SELECT * FROM Table WHERE CONTAINS(name,#ftName)
I would appreciate any comments/suggestions.
For your consideration.
I understand your Senior wants to avoid dynamic SQL, but it is my firm belief that Dynamic SQL is NOT evil.
In the example below, you can see that with a few parameters (or even defaults), and a 3 lines of code, you can:
1) Dynamically search any source
2) Return desired or all elements
3) Rank the Hit rate
The SQL
Declare #SearchFor varchar(max) ='Daily,Production,default' -- any comma delim string
Declare #SearchFrom varchar(150) ='OD' -- table or even a join statment
Declare #SearchExpr varchar(150) ='[OD-Title]+[OD-Class]' -- Any field or even expression
Declare #ReturnCols varchar(150) ='[OD-Nr],[OD-Title]' -- Any field(s) even with alias
Set #SearchFor = 'Sign(CharIndex('''+Replace(Replace(Replace(#SearchFor,' , ',','),', ',''),',',''','+#SearchExpr+'))+Sign(CharIndex(''')+''','+#SearchExpr+'))'
Declare #SQL varchar(Max) = 'Select * from (Select Distinct'+#ReturnCols+',Hits='+#SearchFor+' From '+#SearchFrom + ') A Where Hits>0 Order by Hits Desc'
Exec(#SQL)
Returns
OD-Nr OD-Title Hits
3 Daily Production Summary 2
6 Default Settings 1
I should add that my search string is comma delimited, but you can change to space.
Another note CharIndex can be substanitally faster that LIKE. Take a peek at
http://cc.davelozinski.com/sql/like-vs-substring-vs-leftright-vs-charindex

SQL Server expression substitution in SELECT statements as column names

How do I evaluate a character expression to resolve to a valid column name in a SELECT statement that would return column row values? Eg valid column name = Customer_1 == 'Customer_'+'1'
You need to use dynamic SQL. An example
DECLARE #DynSQL nvarchar(max)
DECLARE #Suffix int = 1
SET #DynSQL = N'SELECT Customer_' + CAST(#Suffix as nvarchar(10)) +
N' FROM YourTable WHERE foo = #foo'
EXEC sp_executesql #DynSQL, N'#foo int', #foo=1
As always with dynamic SQL you need to consider SQL injection if any of the inputs to the process will be user supplied.
How do I evaluate a character expression to resolve to a valid column name in a SELECT statement that would return column row values? Eg valid column name = Customer_1 == 'Customer_'+'1'
You're probably doing something wrong if you need to do this, but if you have to: build the column names as rows and then pivot.

Resources