Сидел все выходные, пытаясь разобраться в реализации итераций курсора в блоках процедур 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');





Я всегда нахожу запутанным работу с хранимыми процедурами, а также выборку и присвоение переменных, поэтому отнеситесь к нижеизложенному с долей скептицизма.
Я думаю, что это не тот способ, который вы используете "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.