Performance 具有多个子选择的插入-性能和错误选择性

Performance 具有多个子选择的插入-性能和错误选择性,performance,postgresql,exception,constraints,plpgsql,Performance,Postgresql,Exception,Constraints,Plpgsql,我有一个plpgsql函数,我想在其中向Data表中添加一行 其中许多列是从表TableA,TableB 和会话: 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

我有一个
plpgsql
函数,我想在其中向
Data
表中添加一行 其中许多列是从表
TableA
TableB
会话

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
);
这很简单,但函数必须尽可能快,而且 需要特定的错误消息来区分subselect失败。 具体而言:

  • 无效(或
    NULL
    会话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。
在插入
后使用检查:

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 = $1)
         , (SELECT t.a_id       FROM tablea  t WHERE t.a_name = $2)
         , (SELECT t.b_id       FROM tableb  t WHERE t.b_name = $3))   -- 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
如果找不到查找表中的行,则每个子选择都会导致空值。因此,始终只插入(并返回)一行

要小心未经表限定的参数、变量和列名之间的命名冲突

使用
安全定义程序时,您可能应该提供
搜索路径
。详情:

如果您在表
数据
中的
a_id
b_id
列上也有

   INSERT INTO data (session_id, a_id, b_id)
   VALUES ((SELECT ssid FROM session t WHERE t.session_id = $1)
         , (SELECT t.a_id FROM tablea  t WHERE t.a_name = $2)
         , (SELECT t.b_id FROM tableb  t WHERE t.b_name = $3));
如果其中一个值导致为NULL,则会收到一条错误消息,告诉您违反了哪个
非NULL
约束

您可能希望也可能不希望在查找表中插入缺少的值:


您的功能的预期(粗略)成功/失败率是多少?另外,像往常一样,请添加您的Postgres版本。@ErwinBrandstetter我还没有测量它,但它应该是99%成功,1%失败。版本是8.4(在某些情况下,它运行在9.1上,但它必须运行在8.4上)。对于这里的问题,版本号总是很重要的——在使用过时的软件时更是如此()。我的答案应该也适用于8.4,但我没有测试。有两个输入错误,现已修复。@ErwinBrandstetter好的,我添加了postgres版本。谢谢,但这不起作用,因为只有
session\u id
非空的
a_id
b_id
可以
NULL
。我应该说的,我的错。我将编辑这个问题(它在匿名中丢失了)。至于命名冲突-这就是为什么我使用短的非冲突参数名称而不是
$1
符号,而每一列都有唯一的长名称,因此我在selects中没有所有表的名称,我可以使用
使用()
语法连接。至于搜索路径,这是匿名的-在实数代码中,我对“我的”db对象使用完全限定的名称,如
namespaceX.funcY
。@JanSpurny:我的第一个(主)版本的函数没有假定
notnull
约束,应该按原样工作。您可以在函数中省略对
\u rec.session\u id
的检查,因为这包含在
NOT NULL
约束中,但无论如何,使用它不会花费太多<代码>安全定义者
:是的,这是一个很好的实践。我仍然会养成遵循建议的习惯。是的,它会起作用,但我必须在
a
b
中添加
NULL
以确保正确处理
a
b
中的
NULL
参数。谢谢。我加上这个是为了让它完整。