具有许多子选择的 INSERT - 性能和错误选择性
INSERT with many subselects - performance and error selectivity
我有一个 plpgsql
函数,我想在其中添加一行到 Data
table
其中许多列是从 tables TableA
、TableB
的子选择中更新的
和 Session
:
CREATE TABLE TableA (
a_id SERIAL PRIMARY KEY,
a_name TEXT UNIQUE NOT NULL
);
CREATE TABLE TableB (
b_id SERIAL PRIMARY KEY,
b_name TEXT UNIQUE NOT NULL
);
CREATE TABLE Session (
session_id SERIAL PRIMARY KEY
);
CREATE TABLE Data (
session_id INTEGER REFERENCES Session(session_id) NOT NULL,
a_id INTEGER REFERENCES TableA(a_id) NULL,
b_id INTEGER REFERENCES TableB(b_id) NULL
);
这很简单,但是功能必须尽可能快,而且我
需要特定的错误消息来区分子选择失败。
具体来说:
- 无效(或
NULL
)session id
- 无效的
a
名称(如果不是 NULL
)
- 无效的
b
名称(如果不是 NULL
)
首先,我尝试了最直接的方法——只选择了我想要的所有值
需要,检查它是否有错误,然后插入值:
CREATE OR REPLACE FUNCTION store_data(ssid INTEGER, a TEXT, b TEXT) RETURNS VOID
AS $$
DECLARE
_a_id INTEGER = NULL;
_b_id INTEGER = NULL;
BEGIN
PERFORM 1 FROM Session WHERE session_id = ssid;
IF NOT FOUND THEN
RAISE EXCEPTION 'INVALID SESSION: %', ssid;
END IF;
IF a_name IS NOT NULL THEN
SELECT INTO _a_id a_id
FROM TableA WHERE a_name = a;
IF NOT FOUND THEN
RAISE EXCEPTION 'INVALID A NAME: %', a;
END IF;
END IF;
IF b_name IS NOT NULL THEN
SELECT INTO _b_id b_id
FROM TableA WHERE b_name = b;
IF NOT FOUND THEN
RAISE EXCEPTION 'INVALID B NAME: %', b;
END IF;
END IF;
INSERT INTO Data (session_id, a_id, b_id) VALUES (ssid, _a_id, _b_id);
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
这工作得很好,但速度不是很快。我需要让它更快,所以我的
另一种方法是使用子选择:
...
BEGIN
INSERT INTO Data (session_id, a_id, b_id)
VALUES (
(SELECT session_id FROM Session WHERE session_id = ssid),
CASE WHEN a IS NULL THEN
NULL
ELSE
(SELECT a_id FROM TableA WHERE a_name = a)
END,
CASE WHEN b IS NULL THEN
NULL
ELSE
(SELECT b_id FROM TableB WHERE b_name = b)
END
);
-- but no error handling :(
END;
...
这有点快,但我不知道如何找出哪个子选择
失败并报告什么错误。
我的问题:有没有办法在保持特定错误的同时使其更快
消息?
该解决方案必须适用于 postgres 8.4。
假设当前的 Postgres 9.4.
在 INSERT
:
之后使用 RETURNING
clause of the INSERT
statement 检查
CREATE OR REPLACE FUNCTION store_data(ssid int, a text, b text)
RETURNS void AS
$func$
DECLARE
_rec record;
BEGIN
INSERT INTO data (session_id, a_id, b_id)
VALUES ((SELECT t.session_id FROM session t WHERE t.session_id = )
, (SELECT t.a_id FROM tablea t WHERE t.a_name = )
, (SELECT t.b_id FROM tableb t WHERE t.b_name = )) -- tableb!
RETURNING *
INTO _rec;
IF _rec.session_id IS NULL THEN -- cannot be NULL
RAISE EXCEPTION 'INVALID SESSION: %', ssid;
ELSIF _rec.a_id IS NULL AND a IS NOT NULL THEN -- allow NULL input
RAISE EXCEPTION 'INVALID A NAME: %', a;
ELSIF _rec.b_id IS NULL AND b IS NOT NULL THEN
RAISE EXCEPTION 'INVALID B NAME: %', b;
END IF;
END
$func$ LANGUAGE plpgsql SECURITY DEFINER
SET search_path = public, pg_temp; -- adapt
如果无法找到查找中的行 table,则每个子选择都会产生 NULL 值。因此,总是插入(并返回)一行。
警惕未经过 table 限定的参数、变量和列名之间的命名冲突。
您可能应该在使用 SECURITY DEFINER
时提供 search_path
。详情:
- How does the search_path influence identifier resolution and the "current schema"
如果 NOT NULL
constraints 在 a_id
列和 table data
中的 b_id
,那么您只需要:
INSERT INTO data (session_id, a_id, b_id)
VALUES ((SELECT ssid FROM session t WHERE t.session_id = )
, (SELECT t.a_id FROM tablea t WHERE t.a_name = )
, (SELECT t.b_id FROM tableb t WHERE t.b_name = ));
如果其中一个值导致 NULL,您会收到一条错误消息,告诉您违反了哪个 NOT NULL
约束。
您可能希望也可能不希望在查找中插入缺失值 tables:
- Is SELECT or INSERT in a function prone to race conditions?
我有一个 plpgsql
函数,我想在其中添加一行到 Data
table
其中许多列是从 tables TableA
、TableB
的子选择中更新的
和 Session
:
CREATE TABLE TableA (
a_id SERIAL PRIMARY KEY,
a_name TEXT UNIQUE NOT NULL
);
CREATE TABLE TableB (
b_id SERIAL PRIMARY KEY,
b_name TEXT UNIQUE NOT NULL
);
CREATE TABLE Session (
session_id SERIAL PRIMARY KEY
);
CREATE TABLE Data (
session_id INTEGER REFERENCES Session(session_id) NOT NULL,
a_id INTEGER REFERENCES TableA(a_id) NULL,
b_id INTEGER REFERENCES TableB(b_id) NULL
);
这很简单,但是功能必须尽可能快,而且我 需要特定的错误消息来区分子选择失败。 具体来说:
- 无效(或
NULL
)session id
- 无效的
a
名称(如果不是NULL
) - 无效的
b
名称(如果不是NULL
)
首先,我尝试了最直接的方法——只选择了我想要的所有值 需要,检查它是否有错误,然后插入值:
CREATE OR REPLACE FUNCTION store_data(ssid INTEGER, a TEXT, b TEXT) RETURNS VOID
AS $$
DECLARE
_a_id INTEGER = NULL;
_b_id INTEGER = NULL;
BEGIN
PERFORM 1 FROM Session WHERE session_id = ssid;
IF NOT FOUND THEN
RAISE EXCEPTION 'INVALID SESSION: %', ssid;
END IF;
IF a_name IS NOT NULL THEN
SELECT INTO _a_id a_id
FROM TableA WHERE a_name = a;
IF NOT FOUND THEN
RAISE EXCEPTION 'INVALID A NAME: %', a;
END IF;
END IF;
IF b_name IS NOT NULL THEN
SELECT INTO _b_id b_id
FROM TableA WHERE b_name = b;
IF NOT FOUND THEN
RAISE EXCEPTION 'INVALID B NAME: %', b;
END IF;
END IF;
INSERT INTO Data (session_id, a_id, b_id) VALUES (ssid, _a_id, _b_id);
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
这工作得很好,但速度不是很快。我需要让它更快,所以我的 另一种方法是使用子选择:
...
BEGIN
INSERT INTO Data (session_id, a_id, b_id)
VALUES (
(SELECT session_id FROM Session WHERE session_id = ssid),
CASE WHEN a IS NULL THEN
NULL
ELSE
(SELECT a_id FROM TableA WHERE a_name = a)
END,
CASE WHEN b IS NULL THEN
NULL
ELSE
(SELECT b_id FROM TableB WHERE b_name = b)
END
);
-- but no error handling :(
END;
...
这有点快,但我不知道如何找出哪个子选择 失败并报告什么错误。
我的问题:有没有办法在保持特定错误的同时使其更快 消息?
该解决方案必须适用于 postgres 8.4。
假设当前的 Postgres 9.4.
在 INSERT
:
RETURNING
clause of the INSERT
statement 检查
CREATE OR REPLACE FUNCTION store_data(ssid int, a text, b text)
RETURNS void AS
$func$
DECLARE
_rec record;
BEGIN
INSERT INTO data (session_id, a_id, b_id)
VALUES ((SELECT t.session_id FROM session t WHERE t.session_id = )
, (SELECT t.a_id FROM tablea t WHERE t.a_name = )
, (SELECT t.b_id FROM tableb t WHERE t.b_name = )) -- tableb!
RETURNING *
INTO _rec;
IF _rec.session_id IS NULL THEN -- cannot be NULL
RAISE EXCEPTION 'INVALID SESSION: %', ssid;
ELSIF _rec.a_id IS NULL AND a IS NOT NULL THEN -- allow NULL input
RAISE EXCEPTION 'INVALID A NAME: %', a;
ELSIF _rec.b_id IS NULL AND b IS NOT NULL THEN
RAISE EXCEPTION 'INVALID B NAME: %', b;
END IF;
END
$func$ LANGUAGE plpgsql SECURITY DEFINER
SET search_path = public, pg_temp; -- adapt
如果无法找到查找中的行 table,则每个子选择都会产生 NULL 值。因此,总是插入(并返回)一行。
警惕未经过 table 限定的参数、变量和列名之间的命名冲突。
您可能应该在使用 SECURITY DEFINER
时提供 search_path
。详情:
- How does the search_path influence identifier resolution and the "current schema"
如果 NOT NULL
constraints 在 a_id
列和 table data
中的 b_id
,那么您只需要:
INSERT INTO data (session_id, a_id, b_id)
VALUES ((SELECT ssid FROM session t WHERE t.session_id = )
, (SELECT t.a_id FROM tablea t WHERE t.a_name = )
, (SELECT t.b_id FROM tableb t WHERE t.b_name = ));
如果其中一个值导致 NULL,您会收到一条错误消息,告诉您违反了哪个 NOT NULL
约束。
您可能希望也可能不希望在查找中插入缺失值 tables:
- Is SELECT or INSERT in a function prone to race conditions?