插入许多子选择 - 性能和错误选择性

时间:2015-07-22 10:45:06

标签: performance postgresql exception constraints plpgsql

我有一个plpgsql函数,我希望在Data表中添加一行 从表TableATableB的子选择更新许多列的位置 和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
);

这很容易,但功能必须尽可能快,而且我 需要特定的错误消息来区分subselect失败。 具体做法是:

  • 无效(或NULLsession 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;
...

这有点快,但我无法弄清楚如何找出哪个subselect 失败,报告错误。

我的问题:是否有办法在保持特定错误的同时加快速度 消息?

解决方案必须适用于postgres 8.4。

1 个答案:

答案 0 :(得分:1)

假设当前的Postgres 9.4 使用RETURNING clause of the INSERT statement检查INSERT

之后
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

如果无法找到查找表中的行,则每个子选择都会产生NULL值。因此,总是插入(并返回)一行。

警惕未经表限定的参数,变量和列名之间的命名冲突。

使用search_path时,您应该提供SECURITY DEFINER。详细说明:

如果您在表a_id中的b_id列和data列上有NOT NULL constraints,那么您只需要:< / p>

   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,则会收到一条错误消息,告诉您违反了哪个NOT NULL约束。

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