Выполнить немедленно в курсоре на ibm db2

У меня возникли трудности с созданием SP, в котором я передаю имя таблицы и запрашиваю библиотеку SYS2, чтобы узнать, есть ли в ней поле автоинкремента. Если это так, я запрашиваю максимальное значение этого поля в таблице, а затем изменяю таблицу, чтобы следующим используемым значением был этот результат плюс 1. Это используется при переносе производственных данных в разработку.

Я не уверен, можно ли использовать «Execute Immediate» как часть объявления курсора. Я все еще новичок в db2 в целом, не говоря уже об IBM. Поэтому любая помощь будет принята с благодарностью. Если «Execute Immediate» не разрешено в объявлении курсора, как мне это сделать?

Я получаю сообщение об ошибке в объявлении Cursor (строка 10), но вот точный код ошибки, который я получаю:

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.

И вот, наконец, мой 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' ) ;

person d.lanza38    schedule 26.03.2015    source источник


Ответы (1)


Во-первых, вам не нужно динамически подготавливать первый оператор.

Во-вторых, вы не можете использовать SELECT в RESTART WITH, вам придется использовать 2 оператора

В-третьих, если вы используете VARCHAR вместо CHAR, вам не нужно использовать TRIM().

Наконец, использование TABLE в качестве имени параметра является плохой практикой, поскольку это зарезервированное слово.

Вы хотите что-то вроде этого

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;

Еще одна вещь, которую следует учитывать, вы можете захотеть БЛОКИРОВАТЬ таблицу в монопольном режиме перед запуском STMT2; в противном случае существует вероятность того, что между выполнением STMT2 и STMT3 была добавлена ​​запись с более высоким значением.

person Charles    schedule 27.03.2015
comment
Спасибо, это сработало отлично. Я ожидал только подробностей о том, что я делал неправильно, а не полного кода. Но я предполагал, что делаю много неправильно, это очень помогает, спасибо. - person d.lanza38; 27.03.2015
comment
Рад, что смог помочь. Я действительно выполнял ту же задачу в прошлом. Но я использовал RPG вместе с системным API, чтобы определить имя столбца. Так что было интересно сравнить с чистым SQL-решением. - person Charles; 29.03.2015