【问题标题】:PL/pgSQL column name the same as variablePL/pgSQL 列名与变量相同
【发布时间】:2015-06-18 11:29:18
【问题描述】:

我是 plpgsql 的新手,我正在尝试创建一个函数来检查表中是否存在某个值,如果不存在则添加一行。

CREATE OR REPLACE FUNCTION hire(
    id_pracownika integer,
    imie character varying,
    nazwisko character varying,
    miasto character varying,
    pensja real)
  RETURNS TEXT AS
$BODY$
DECLARE
wynik TEXT;
sprawdzenie INT;
BEGIN
sprawdzenie = id_pracownika;
IF EXISTS (SELECT id_pracownika FROM pracownicy WHERE id_pracownika=sprawdzenie) THEN
wynik = "JUZ ISTNIEJE";
RETURN wynik;
ELSE
INSERT INTO pracownicy(id_pracownika,imie,nazwisko,miasto,pensja)
VALUES (id_pracownika,imie,nazwisko,miasto,pensja);
wynik = "OK";
RETURN wynik;   
END IF;
END;
$BODY$
  LANGUAGE plpgsql VOLATILE
  COST 100;

问题是我收到错误消息说id_pracownika 是列名和变量。

如何在这种情况下指定“id_pracownika”指的是列名?

【问题讨论】:

  • 对(输入)参数和表/列名称使用不同的名称。如果不知道,PostgreSQL 怎么知道要使用什么?
  • documentaion 在这一点上很明确:您应该使用 hire.id_pracownika 引用参数,并使用 pracownicy.id_pracownika 引用列(像往常一样)。
  • 您已经在分配sprawdzenie = id_pracownika;,所以只需在INSERT INTO pracownicy(id_pracownika..,..,) VALUES(sprawdzenie,..,..) 中使用sprawdzenie

标签: postgresql plpgsql race-condition uniqueidentifier


【解决方案1】:

假设id_pracownika 是表的PRIMARY KEY。或者至少定义UNIQUE。 (如果不是NOT NULL,NULL 是一个极端情况。)

SELECTINSERT

您的函数是“SELECT or INSERT”的另一种实现——UPSERT 问题的变体,面对并发写入负载,它比看起来更复杂。见:

在 Postgres 9.5 或更高版本中使用 UPSERT

在 Postgres 9.5 或更高版本中,使用 UPSERT (INSERT ... ON CONFLICT ...) Details in the Postgres Wiki. 这个新语法做了一个干净的工作

CREATE OR REPLACE FUNCTION hire(
        _id_pracownika integer
      , _imie varchar
      , _nazwisko varchar
      , _miasto varchar
      , _pensja real)
  RETURNS text
  LANGUAGE plpgsql AS
$func$
BEGIN
   INSERT INTO pracownicy
          ( id_pracownika, imie, nazwisko, miasto, pensja)
   VALUES (_id_pracownika,_imie,_nazwisko,_miasto,_pensja)
   ON     CONFLICT DO NOTHING;

   IF FOUND THEN
      RETURN 'OK';
   ELSE
      RETURN 'JUZ ISTNIEJE';  -- already exists
   END IF;
END
$func$;

关于特殊变量FOUND

表限定列名以在必要时消除歧义。 (您也可以在函数参数前加上函数名称,但这很快就会变得尴尬。)
但是INSERT 的目标列表中的列名可能不是表限定的。无论如何,这些都不会模棱两可。

最好先验地避免歧义。有些人(包括我)喜欢在所有函数参数和变量前加上下划线。

如果您确实需要一个列名作为函数参数名,避免命名冲突的一种方法是在函数内使用ALIASALIAS 真正有用的少数情况之一。

或者通过ordinal position引用函数参数:$1在这种情况下为id_pracownika

如果所有其他方法都失败了,您可以通过设置#variable_conflict 来决定什么优先。见:

还有更多:

  • UPSERT 中的 RETURNING 子句存在复杂性。见:

  • 字符串文字(文本常量)必须用单引号括起来:'OK',而不是 "OK"。见:

  • 与其他编程语言相比,分配变量的成本相对更高。将分配保持在最低限度,以在 plpgsql 中获得最佳性能。尽量直接在 SQL 语句中做。

  • VOLATILE COST 100 是函数的默认装饰器。不需要把这些拼出来。

在 Postgres 9.4 或更早版本中没有 UPSERT

...
   IF EXISTS (SELECT FROM pracownicy p
             WHERE  p.id_pracownika = hire.id_pracownika) THEN
      RETURN 'JUZ ISTNIEJE';
   ELSE
      INSERT INTO pracownicy(id_pracownika,imie,nazwisko,miasto,pensja)
      VALUES (hire.id_pracownika,hire.imie,hire.nazwisko,hire.miasto,hire.pensja);
    
      RETURN 'OK';
   END IF;
...

SELECTINSERT 之间存在微小的竞争条件,因此在并发写入负载繁重的情况下并非万无一失。

EXISTS 表达式中,SELECT 列表无关紧要。 SELECT id_pracownikaSELECT 1,甚至SELECT 1/0——都一样。只需使用一个空的SELECT 列表。只有任何符合条件的行的存在才重要。见:

【讨论】:

  • 我认为第一个参数命名错误,应该是hire(sprawdzenie integer, etc…
  • 关于最后一部分,它只有在pracownicy.id_pracownika 上存在UNIQUE 约束时才有效,对吧? (无论如何是个好建议,我不知道ON CONFLICT)。编辑:哦,我猜你假设它是PRIMARY KEY 所以它是UNIQUE
  • @GG.:感谢您的指出。我修复/澄清了两者。将现代解决方案移到顶部。
  • @ErwinBrandstetter 根据您的回答,我创建了我的答案。但我还是有些困惑。我无法理解我列出的第 2 点和第 3 点。请澄清一下。
  • @Mark:请将您的问题发布为 question,而不是作为答案,事实并非如此。
【解决方案2】:

这是我测试的一个示例,我使用 EXECUTE 运行选择并将其结果放入游标中,使用动态列名。

1.创建表:

create table people (
  nickname varchar(9),
  name varchar(12),
  second_name varchar(12),
  country varchar(30)
  );

2。创建函数:

CREATE OR REPLACE FUNCTION fun_find_people (col_name text, col_value varchar)
RETURNS void AS
$BODY$
DECLARE
    local_cursor_p refcursor;
    row_from_people RECORD;

BEGIN
    open local_cursor_p FOR
        EXECUTE 'select * from people where '|| col_name || ' LIKE ''' || col_value || '%'' ';

    raise notice 'col_name: %',col_name;
    raise notice 'col_value: %',col_value;

    LOOP
        FETCH local_cursor_p INTO row_from_people; EXIT WHEN NOT FOUND;

        raise notice 'row_from_people.nickname: %',  row_from_people.nickname ;
        raise notice 'row_from_people.name: %', row_from_people.name ;
        raise notice 'row_from_people.country: %', row_from_people.country;
    END LOOP;
END;
$BODY$ LANGUAGE 'plpgsql'

3.运行函数 select fun_find_people('name', 'Cristian'); select fun_find_people('country', 'Chile');

【讨论】:

    【解决方案3】:

    用 Erwin Brandstetter 的回答激发灵感。

    CREATE OR REPLACE FUNCTION test_upsert(
            _parent_id int, 
            _some_text text)
      RETURNS text
      LANGUAGE plpgsql AS
    $func$
    DECLARE a text;
    BEGIN
       INSERT INTO parent_tree (parent_id, some_text)
       VALUES (_parent_id,_some_text)
       ON     CONFLICT DO NOTHING
       RETURNING 'ok' into a;
       return a;
    
       IF NOT FOUND THEN
            return 'JUZ ISTNIEJE';
       END IF;
    END
    $func$;
    
    1. 按照欧文的回答。我让a 变量保存返回类型文本。
    2. 如果冲突什么都不做,那么函数将什么也不返回。比如已经有parent_id = 10,那么结果如下:
    test_upsert
    ------------
    
    (1 row)
    
    1. 不确定:

      IF NOT FOUND THEN
           return 'JUZ ISTNIEJE';
      END IF;
      

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-03-06
      • 2018-08-01
      • 2017-05-13
      • 2010-12-09
      • 2015-12-31
      • 1970-01-01
      相关资源
      最近更新 更多