PL/pgSQL 中的第一个循环后错误游标不存在
ERROR cursor does not exist after first loop in PL/pgSQL
我需要将大量 csv 文件加载到 PostgreSQL 数据库中。我有一个 table source_files
,其中包含文件路径和一个标志,该标志指示是否已经为我需要加载的所有 csv 文件加载了一个文件。
我编写了以下代码,它正确加载了第一个文件,但随后抛出错误:
ERROR: cursor "curs" does not exist
为什么会出现此错误,我该如何解决?
DO $$
DECLARE
file_record record;
curs CURSOR
FOR SELECT id, file_path
FROM source_files
WHERE added_to_db=FALSE
ORDER BY id;
BEGIN
OPEN curs;
LOOP
-- Get next entry in source file which contains name of csv to load
FETCH curs INTO file_record;
exit WHEN NOT found;
BEGIN
-- As we need to add a column to the data after loading csv but before inserting
-- into final table we use a temporary table mytemp
DROP TABLE mytemp;
CREATE TABLE mytemp
(
dataA numeric,
dataB numeric
);
-- Load csv file
EXECUTE FORMAT('COPY mytemp
FROM ''%s''
DELIMITER '',''
CSV HEADER;', file_record.file_path);
-- Add Column specifying what source file the data is from
ALTER TABLE mytemp
ADD COLUMN source_id int;
UPDATE mytemp
SET source_id=file_record.id;
-- Add the data to the destination table
INSERT INTO data_table(
dataA,
dataB,
source_id
)
SELECT
mytemp.dataA,
mytemp.dataB
mytemp.source_id
FROM
mytemp
-- Set a flag to indicate that the current file in source_files has been loaded
UPDATE source_files
SET added_to_db=TRUE WHERE CURRENT OF curs;
COMMIT;
END;
END LOOP;
CLOSE curs;
END $$;
您的代码有一个大问题 COMMIT
。您 可以 在 DO
语句中使用 COMMIT
,但游标会在事务结束后立即关闭。
在 SQL 中,您可以创建一个游标 WITH HOLD
,该游标在事务结束后仍然有效,但在 PL/pgSQL 中不可用。
我建议删除 COMMIT
。
您代码中的另一个错误是您使用了 format
函数,这会使您暴露于 SQL 注入。而不是
FORMAT('COPY mytemp
FROM ''%s''
DELIMITER '',''
CSV HEADER;', file_record.file_path);
使用
FORMAT('COPY mytemp
FROM %L
DELIMITER '',''
CSV HEADER;', file_record.file_path);
您可以通过在循环中使用隐式游标来简化代码:
FOR file_record IN
SELECT id, file_path
FROM source_files
WHERE added_to_db=FALSE
ORDER BY id
LOOP
...
END LOOP;
这样就省去了声明光标和 EXIT WHEN
的麻烦。 OPEN
和 CLOSE
语句无论如何都是不必要的。
我需要将大量 csv 文件加载到 PostgreSQL 数据库中。我有一个 table source_files
,其中包含文件路径和一个标志,该标志指示是否已经为我需要加载的所有 csv 文件加载了一个文件。
我编写了以下代码,它正确加载了第一个文件,但随后抛出错误:
ERROR: cursor "curs" does not exist
为什么会出现此错误,我该如何解决?
DO $$
DECLARE
file_record record;
curs CURSOR
FOR SELECT id, file_path
FROM source_files
WHERE added_to_db=FALSE
ORDER BY id;
BEGIN
OPEN curs;
LOOP
-- Get next entry in source file which contains name of csv to load
FETCH curs INTO file_record;
exit WHEN NOT found;
BEGIN
-- As we need to add a column to the data after loading csv but before inserting
-- into final table we use a temporary table mytemp
DROP TABLE mytemp;
CREATE TABLE mytemp
(
dataA numeric,
dataB numeric
);
-- Load csv file
EXECUTE FORMAT('COPY mytemp
FROM ''%s''
DELIMITER '',''
CSV HEADER;', file_record.file_path);
-- Add Column specifying what source file the data is from
ALTER TABLE mytemp
ADD COLUMN source_id int;
UPDATE mytemp
SET source_id=file_record.id;
-- Add the data to the destination table
INSERT INTO data_table(
dataA,
dataB,
source_id
)
SELECT
mytemp.dataA,
mytemp.dataB
mytemp.source_id
FROM
mytemp
-- Set a flag to indicate that the current file in source_files has been loaded
UPDATE source_files
SET added_to_db=TRUE WHERE CURRENT OF curs;
COMMIT;
END;
END LOOP;
CLOSE curs;
END $$;
您的代码有一个大问题 COMMIT
。您 可以 在 DO
语句中使用 COMMIT
,但游标会在事务结束后立即关闭。
在 SQL 中,您可以创建一个游标 WITH HOLD
,该游标在事务结束后仍然有效,但在 PL/pgSQL 中不可用。
我建议删除 COMMIT
。
您代码中的另一个错误是您使用了 format
函数,这会使您暴露于 SQL 注入。而不是
FORMAT('COPY mytemp
FROM ''%s''
DELIMITER '',''
CSV HEADER;', file_record.file_path);
使用
FORMAT('COPY mytemp
FROM %L
DELIMITER '',''
CSV HEADER;', file_record.file_path);
您可以通过在循环中使用隐式游标来简化代码:
FOR file_record IN
SELECT id, file_path
FROM source_files
WHERE added_to_db=FALSE
ORDER BY id
LOOP
...
END LOOP;
这样就省去了声明光标和 EXIT WHEN
的麻烦。 OPEN
和 CLOSE
语句无论如何都是不必要的。