DBMS_SQL.NUMBER_TABLE внутри оператора обновления, выдающего ошибку неверного типа данных - PullRequest
1 голос
/ 09 октября 2019

Почему выборка работает, но при обновлении выбрасываются неверные данные?

DECLARE
    L_NUMBER NUMBER;
    L_NUMBER_TABLE DBMS_SQL.NUMBER_TABLE;
    L_LAST_PRINTED_DATE DATE := SYSDATE;
BEGIN
    L_NUMBER_TABLE(0) := 1033000;

-- THIS WORKS
SELECT TB.COLUMN_VALUE
INTO L_NUMBER
FROM TABLE(L_NUMBER_TABLE) TB;

-- THIS DOES NOT WORK
--  ERROR AT LINE 1
--  ORA-00902: INVALID DATATYPE
--  ORA-06512: AT LINE 13
UPDATE SCHEMA.REAL_NUMBER_TABLE
SET    REAL_NUMBER_DATE           = L_LAST_PRINTED_DATE
WHERE  EXISTS (  SELECT TB.COLUMN_VALUE
                    FROM TABLE( L_NUMBER_TABLE ) TB
                    WHERE TB.COLUMN_VALUE = REAL_NUMBER_COLUMN );


END;

Я пытаюсь перебрать курсор и обновить последнюю напечатанную дату первичной последовательности, найденной внутри курсора. Я попытался зациклить курсор, но затем, когда я вернул курсор клиенту, он выдает ошибку отсутствия индекса. Поэтому я был вынужден сделать два курсора, один для циклического перехода, а другой для возврата. Моя цель состоит в том, чтобы узнать, какой самый простой и обслуживаемый способ хранения коллекций для обновления таблиц.

DECLARE
    CURSOR L_ORIGINAL_CURSOR IS SELECT ...

    L_CURSOR_COLUMN_1    PLS_INTEGER;
    L_CURSOR_COLUMN_2    PLS_INTEGER;
    L_CURSOR_COLUMN_3    PLS_INTEGER;
    -- Keep adding or removing the number of columns to match...1/2 09182019515PM
    -- L_CURSOR_COLUMN_4    PLS_INTEGER;
    L_NUMBER_TABLE DBMS_SQL.NUMBER_TABLE;
    L_COUNTER PLS_INTEGER;
    L_LAST_PRINTED_DATE DATE := SYSDATE;
BEGIN    
    OPEN L_ORIGINAL_CURSOR;
        LOOP
            -- Keep adding or removing the number of columns to match... 2/2 09182019515PM
            FETCH L_ORIGINAL_CURSOR INTO L_CURSOR_COLUMN_1, L_CURSOR_COLUMN_2, L_CURSOR_COLUMN_3; -- , L_CURSOR_COLUMN_4;
                IF L_ORIGINAL_CURSOR%NOTFOUND THEN
                    EXIT;
                END IF;

                IF L_ORIGINAL_CURSOR%FOUND THEN
                        -- CURRENT SOLUTION IS TO UPDATE HERE BOUNCING BETWEEN SQL AND PLSQL ENGINES
                        -- UPDATE ....
                    -- WANTED IMPLEMENTATION
                    L_COUNTER := L_COUNTER + 1;
                END IF;
                -- WANTED IMPLEMENTATION STORE PK IN MY COLLECTION
                L_NUMBER_TABLE(L_COUNTER) := L_CURSOR_COLUMN_1;
        END LOOP;

    -- IF COLLECTION IS BIGGER THAN 0
    IF L_NUMBER_TABLE.COUNT > 0 THEN
        -- SCRIPT BREAKS HERE
        UPDATE ...
        SET ... = L_LAST_PRINTED_DATE
        WHERE EXISTS (  SELECT TB.COLUMN_VALUE
                        FROM TABLE(L_NUMBER_TABLE) TB
                        WHERE TB.COLUMN_VALUE = ...   );
    END IF;
    CLOSE L_ORIGINAL_CURSOR;

    OPEN L_CURSOR FOR SELECT ...
END SP_GET_PM_WORK_ORDERS;
/
SHOW ERRORS;

В настоящее время встроенный одиночный SQL-оператор обновления работает, но требует нескольких переходов между механизмами SQL и PLSQL. Почему мне разрешено делать выборки, но не обновления с моей таблицей номеров?

ВЕРСИЯ БАЗЫ ДАННЫХ: 12.1.0.2.0

Ответы [ 2 ]

5 голосов
/ 10 октября 2019

DBMS_SQL.NUMBER_TABLE - это ассоциативный массив PL / SQL, определенный в пакете DBMS_SQL как:

TYPE number_table IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;

Это данные PL / SQLтип и не должен работать в операторах SQL (мне еще предстоит выяснить, почему ваш первый оператор работает).

Если вы хотите, чтобы тип данных работал в SQL, вам нужно использовать коллекцию (безINDEX BY; также называется типом данных вложенной таблицы):

CREATE TYPE number_table IS TABLE OF NUMBER;

Или фиксированной длины VARRAY:

CREATE TYPE number_array IS VARRAY(10) OF NUMBER;

Например:

Установка Oracle :

CREATE TABLE real_number_table ( real_number_column, real_number_date ) AS
  SELECT 1033000, DATE '2019-01-01' FROM DUAL;

CREATE TYPE number_table IS TABLE OF NUMBER;

Оператор PL / SQL 1 :

Тогда ваш SQLОператор будет работать с типом данных коллекции:

DECLARE
  L_NUMBER NUMBER;
  L_NUMBER_TABLE NUMBER_TABLE;
  L_LAST_PRINTED_DATE DATE := SYSDATE;
BEGIN
  L_NUMBER_TABLE := NUMBER_TABLE();
  L_NUMBER_TABLE.EXTEND;
  L_NUMBER_TABLE( L_NUMBER_TABLE.COUNT ) := 1033000;

  UPDATE REAL_NUMBER_TABLE
  SET    REAL_NUMBER_DATE = L_LAST_PRINTED_DATE
  WHERE  EXISTS (
    SELECT TB.COLUMN_VALUE
    FROM TABLE( L_NUMBER_TABLE ) TB
    WHERE TB.COLUMN_VALUE = REAL_NUMBER_COLUMN
  );
END;
/

, а затем:

SELECT * FROM real_number_table;

выводит:

REAL_NUMBER_COLUMN | REAL_NUMBER_DATE
-----------------: | :---------------
           1033000 | 09-OCT-19  

PL / SQL Statement 2 :

Или вы можете упростить его и использовать оператор MEMBER OF (это работает только с типами данных коллекции, но не VARRAY s):

DECLARE
  L_NUMBER NUMBER;
  L_NUMBER_TABLE NUMBER_TABLE := NUMBER_TABLE( 1033000 );
  L_LAST_PRINTED_DATE DATE := SYSDATE + 1;
BEGIN
  UPDATE REAL_NUMBER_TABLE
  SET    REAL_NUMBER_DATE = L_LAST_PRINTED_DATE
  WHERE  REAL_NUMBER_COLUMN MEMBER OF L_NUMBER_TABLE;
END;
/

и затем:

SELECT * FROM real_number_table;

выходы:

REAL_NUMBER_COLUMN | REAL_NUMBER_DATE
-----------------: | :---------------
           1033000 | 10-OCT-19       

db <> fiddle здесь


Ваш окончательный анонимный блок PL / SQL может быть переписан как:

DECLARE
  L_NUMBER_TABLE NUMBER_TABLE;
  L_LAST_PRINTED_DATE DATE := SYSDATE;
BEGIN
  SELECT column1
  BULK COLLECT INTO L_NUMBER_TABLE
  FROM   your_table; -- as per L_ORIGINAL_CURSOR

  IF L_NUMBER_TABLE.COUNT > 0 THEN
    UPDATE other_table
    SET    date_column = L_LAST_PRINTED_DATE
    WHERE  number_column MEMBER OF L_NUMBER_TABLE;
  END IF;
END;
/
0 голосов
/ 10 октября 2019

MT0 предоставил правильное решение. В случае, если REAL_NUMBER_COLUMN является уникальным / первичным ключом, вы также можете использовать эту (довольно неизвестную) версию:

UPDATE (
    SELECT REAL_NUMBER_COLUMN, REAL_NUMBER_DATE
    FROM SCHEMA.REAL_NUMBER_TABLE 
       JOIN TABLE(L_NUMBER_TABLE) ON COLUMN_VALUE = REAL_NUMBER_COLUMN
    )
SET REAL_NUMBER_DATE = L_LAST_PRINTED_DATE;
...