Динамический SQL и итерация курсора в Snowflake

Сидел все выходные, пытаясь разобраться в реализации итераций курсора в блоках процедур Snowflake.

Цель состоит в том, чтобы создать динамическую процедуру, которая при вызове проверяет все доступные таблицы в моей схеме базы данных Snowflake на наличие (не)существующих идентификаторов учетных записей. Эта процедура будет использоваться в качестве проверки контроля данных, чтобы проверить правильность выполнения нашей процедуры удаления данных.

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

То есть: SOURCE_DATABASE.LOM_US_DB_PUBLIC против SOURCE_DATABASE.LOM_EU_DB_PUBLIC

Текущая реализация:

Процедура проверяет наличие записей, связанных с указанным ACCOUNT_ID, во всех таблицах в данной схеме базы данных. Процедура принимает три параметра: DB_NAME, SCHEMA_NAME и ACCOUNT_ID. Он переключается на указанную базу данных и схему, перебирает все таблицы в схеме и создает SQL-запрос для подсчета записей с заданным ACCOUNT_ID в каждой таблице. Если записи найдены, результаты накапливаются в сводной строке, которая возвращается в конце процедуры.

Текущая проблема:

Я постоянно сталкиваюсь с проблемами при реализации 'For Loops' или при попытке работать с 'Execute Immediately' утверждениями. Я прочитал большую часть документации Snowflake, но не нашел решения этой проблемы.

Я продолжаю получать одни и те же сообщения об ошибках:

Syntax error: unexpected 'IMMEDIATE'. (line 22)

syntax error line 22 at position 35 unexpected 'INTO'. syntax error line 25 at position 8 unexpected 'IF'. (line 22)

ИЛИ

Syntax error: unexpected 'FOR'. (line 21)

Желаемый результат:

В конце итерации процедура возвращает сводку, содержащую сведения обо всех таблицах, содержащих записи для указанного ACCOUNT_ID.

PS: Рекомендации по возврату резюме приветствуются!

CREATE OR REPLACE PROCEDURE DATA_DELETION_TEST(
    DB_NAME STRING, -- Set Parameters to customize test for different DBs and Schemas
    SCHEMA_NAME STRING,
    ACCOUNT_ID STRING
)
RETURNS STRING -- Procedure returns a string as result 
LANGUAGE SQL -- Procedure is written in SQL language 
EXECUTE AS CALLER -- Procedure has the same access permissions as the user who is executing it
AS
$$ -- Start of the procedure body
DECLARE
    CURSOR_RESULT STRING DEFAULT ''; -- summarizes which tables contain data for the specified account ID.
    TABLE_NAME STRING; -- construct the SQL query dynamically for each table.
    SQL_TEXT STRING; -- SQL query string that checks for data connected to the given account ID in that specific table.
    QUERY_RESULT STRING; -- holds the count of records found in the table for the specified account ID.
BEGIN -- Start of the Procedure Body
    -- Switch to the specified database
    EXECUTE IMMEDIATE 'USE DATABASE ' || DB_NAME || ';';
    -- Switch to the specified schema
    EXECUTE IMMEDIATE 'USE SCHEMA ' || SCHEMA_NAME || ';';

    -- Iterate over the tables in the specified schema
    FOR RECORD IN (SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = SCHEMA_NAME) DO
        -- RECORD.TABLE_NAME refers to the TABLE_NAME column from the cursor's result set
        TABLE_NAME := RECORD.TABLE_NAME;

        -- Construct the SQL query to check for data
        SQL_TEXT := 'SELECT COUNT(*) FROM ' || SCHEMA_NAME || '.' || TABLE_NAME || ' WHERE ACCOUNT_ID = ''' || ACCOUNT_ID || '''';

        -- Execute the query and fetch the result
        EXECUTE IMMEDIATE SQL_TEXT INTO QUERY_RESULT;

        -- If data exists, store the result
        IF QUERY_RESULT > 0 THEN
            CURSOR_RESULT := CURSOR_RESULT || 'Table: ' || TABLE_NAME || ' has ' || QUERY_RESULT || ' records for Account ID: ' || ACCOUNT_ID || '. ';
        END IF;
    END FOR;

    RETURN CURSOR_RESULT;
END;
$$; -- End of the procedure body

CALL DATA_DELETION_TEST('SOURCE_DATABASE', 'LOM_US_DB_PUBLIC', 'your_account_id');
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
76
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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

Я думаю, что это не тот способ, который вы используете "INTO", также я изменил предложение "IF" и способ получения списка имен таблиц:

CREATE OR REPLACE PROCEDURE DATA_DELETION_TEST(
    DB_NAME STRING, -- Set Parameters to customize test for different DBs and Schemas
    SCHEMA_NAME STRING,
    ACCOUNT_ID STRING
)
RETURNS STRING -- Procedure returns a string as result 
LANGUAGE SQL -- Procedure is written in SQL language 
EXECUTE AS CALLER -- Procedure has the same access permissions as the user who is executing it
AS
DECLARE
    CURSOR_RESULT STRING DEFAULT ''; -- summarizes which tables contain data for the specified account ID.
    TABLE_NAME STRING; -- construct the SQL query dynamically for each table.
    SQL_TEXT STRING; -- SQL query string that checks for data connected to the given account ID in that specific table.
    QUERY_RESULT STRING; -- holds the count of records found in the table for the specified account ID.
    -- added vars:

    GET_TABLES_STATEMENT VARCHAR DEFAULT (
        ' select table_name from ' || :DB_NAME  || '.INFORMATION_SCHEMA.TABLES where table_schema = \'' || :SCHEMA_NAME || '\';'
    );
    TABLES_AVAILABLE RESULTSET DEFAULT (EXECUTE IMMEDIATE GET_TABLES_STATEMENT);
    CUR_TABLE CURSOR for TABLES_AVAILABLE;
    holder RESULTSET;
    
BEGIN -- Start of the Procedure Body
    -- Switch to the specified database
    EXECUTE IMMEDIATE 'USE DATABASE ' || DB_NAME || ';';
    -- Switch to the specified schema
    EXECUTE IMMEDIATE 'USE SCHEMA ' || SCHEMA_NAME || ';';

    -- Iterate over the tables in the specified schema
    FOR RECORD IN CUR_TABLE DO
        -- RECORD.TABLE_NAME refers to the TABLE_NAME column from the cursor's result set
        TABLE_NAME := RECORD.TABLE_NAME;
        -- Construct the SQL query to check for data
        SQL_TEXT := 'SELECT COUNT(*) AS CNT FROM ' || SCHEMA_NAME || '.' || TABLE_NAME || ' WHERE ACCOUNT_ID = ''' || ACCOUNT_ID || '''';

        -- Execute the query and fetch the result
        holder := (EXECUTE IMMEDIATE SQL_TEXT);
        let c1 cursor for holder;
        open c1;
        fetch c1 into QUERY_RESULT;
            
        -- If data exists, store the result
        IF (QUERY_RESULT > 0) THEN
            CURSOR_RESULT := CURSOR_RESULT || 'Table: ' || TABLE_NAME || ' has ' || QUERY_RESULT || ' records for Account ID: ' || ACCOUNT_ID || '. ';
        END IF;
    END FOR;

    RETURN CURSOR_RESULT;
END;

Тестирую сейчас: Некоторая подготовка к получению данных:

use schema test_db.sample_procedure_dvd;
create or replace table test_db.sample_procedure_dvd.sample_data as (
select 'US' as account_id, 1 as id
union all 
select 'UK', 2
);
create or replace table test_db.sample_procedure_dvd.sample_data_us as 
(select * from test_db.sample_procedure_dvd.sample_data where account_id = 'US');
create or replace table test_db.sample_procedure_dvd.sample_data_non_us as 
(select * from test_db.sample_procedure_dvd.sample_data where account_id != 'US');

Теперь у меня должно быть 3 таблицы, две из них содержат данные для account_id = США, одна только для Великобритании.

Вызов процедуры:

 CALL DATA_DELETION_TEST('TEST_DB', 'SAMPLE_PROCEDURE_DVD', 'US');

Возвращает ожидаемый результат:

Table: SAMPLE_DATA has 1 records for Account ID: US. Table: SAMPLE_DATA_US has 1 records for Account ID: US.

Вы также спрашивали об альтернативах «сводке возврата» — возможно, стоит вернуть таблицу (например, table_name|number_hits|timestamp of the call), хотя тогда вам нужно будет изменить ваши «возвраты»)

Цените помощь! В итоге я переписал сценарий на Java, продолжая сталкиваться с «Недопустимым значением выражения (?SqlExecuteImmediateDynamic?) для назначения». вопросы, при вызове процедуры. Отладка динамического SQL - это нечто... По сути, я создал временную таблицу результатов, которая создается после запуска оператора вызова, сохраняющего общее количество строк с заданным account_id в каждой предопределенной таблице. Затем я запускаю простой оператор Select для запроса таблицы Temp_result.

Paul 08.07.2024 19:03

Другие вопросы по теме