当没有记录时,FOUND 变量设置为“true”

FOUND variable set to `true` when there are NO records

PLPGSQL 函数中,我有一些行来检查记录是否存在。那么,即使没有记录,FOUND 变量也会设置为 TRUE。很奇怪的事情。

我测试的线路是:

query:='SELECT i.identity_id FROM app.identity as i,' || quote_ident(schema_name) || '.users as u,' || quote_ident(schema_name) || '.udata as ud WHERE i.identity_id=' || p_identity_id || ' and u.identity_id=i.identity_id and ud.identity_id=i.identity_id';
        RAISE WARNING 'query=%',query;
        PERFORM query;
        RAISE WARNING 'FOUND=%',FOUND;
        IF FOUND 
        THEN
            RAISE WARNING 'Record found, rising an exception';
            RAISE EXCEPTION USING ERRCODE = 'AA002';
        END IF;

这是输出:

WARNING:  query=SELECT i.identity_id FROM app.identity as i,"comp-1049007476".users as u,"comp-1049007476".udata as ud WHERE i.identity_id=-1615382132 and u.identity_id=i.identity_id and ud.identity_id=i.identity_id
WARNING:  FOUND=t
WARNING:  Record found, rising an exception

这是 pg_log :

 2017-01-09 09:47:21.906 CST > LOG:  execute <unnamed>: SELECT app.create_identity(::varchar,'') as identity_id
< 2017-01-09 09:47:21.906 CST > DETAIL:  parameters:  = 'someuser@domain.com'
< 2017-01-09 09:47:21.908 CST > WARNING:  new_identity_id=-1615382132
< 2017-01-09 09:47:21.908 CST > CONTEXT:  PL/pgSQL function app.create_identity(character varying,character varying) line 18 at RAISE
< 2017-01-09 09:47:21.917 CST > LOG:  execute <unnamed>: select app.add_user(       
                        ::integer,
                        ::varchar,                     
                        ::integer,                     
                        ::varchar,                     
                        ::varchar,                     
                        ::varchar,                     
                        ::varchar,                     
                        ::varchar,                     
                        ::varchar,                     
                        ::varchar,                    
                        ::integer,                    
                        ::integer,                    
                        ::integer,                    
                        ::integer,                    
                        ::integer,                    
                        ::integer,                    
                        ::smallint,                   
                        ::boolean,                    
                        ::boolean,                    
                        ::boolean,                    
                        ::boolean,                    
                        ::boolean                     
                        )
< 2017-01-09 09:47:21.917 CST > DETAIL:  parameters:  = '905220468',  = '763715373817831',  = '-1049007476',  = 'Some User',  = '44444',  = '',  = '',  = '',  = '',  = '',  = '-1615382132',  = '0',  = '1',  = '0',  = '0',  = '0',  = '0',  = 't',  = 'f',  = 'f',  = 'f',  = 'f'
< 2017-01-09 09:47:21.919 CST > WARNING:  query=SELECT i.identity_id FROM app.identity as i,"comp-1049007476".users as u,"comp-1049007476".udata as ud WHERE i.identity_id=-1615382132 and u.identity_id=i.identity_id and ud.identity_id=i.identity_id
< 2017-01-09 09:47:21.919 CST > CONTEXT:  PL/pgSQL function app.add_user(integer,character varying,integer,character varying,character varying,character varying,character varying,character varying,character varying,character varying,integer,integer,integer,integer,integer,integer,smallint,boolean,boolean,boolean,boolean,boolean) line 25 at RAISE
< 2017-01-09 09:47:21.919 CST > WARNING:  FOUND=t
< 2017-01-09 09:47:21.919 CST > CONTEXT:  PL/pgSQL function app.add_user(integer,character varying,integer,character varying,character varying,character varying,character varying,character varying,character varying,character varying,integer,integer,integer,integer,integer,integer,smallint,boolean,boolean,boolean,boolean,boolean) line 27 at RAISE
< 2017-01-09 09:47:21.919 CST > WARNING:  Record found, rising an exception
< 2017-01-09 09:47:21.919 CST > CONTEXT:  PL/pgSQL function app.add_user(integer,character varying,integer,character varying,character varying,character varying,character varying,character varying,character varying,character varying,integer,integer,integer,integer,integer,integer,smallint,boolean,boolean,boolean,boolean,boolean) line 30 at RAISE
< 2017-01-09 09:47:21.919 CST > ERROR:  Identity already registered

为什么我确定该记录不存在?好吧,我把我知道的值不在 table 中,但为了以防万一,我验证:

dev=> SELECT i.identity_id FROM app.identity as i,"comp-1049007476".users as u,"comp-1049007476".udata as ud WHERE i.identity_id=-1615382132 and u.identity_id=i.identity_id and ud.identity_id=i.identity_id;
 identity_id 
-------------
(0 rows)

dev=> 

查看,0 条记录。所以,最大的问题是,为什么 FOUND 变量会撒谎?这里发生了什么?

这是我的函数的完整代码:

CREATE OR REPLACE FUNCTION app.add_user(sess_identity_id int,session_str varchar,sess_company_id int,
    p_full_name         varchar,
    p_mob_phone         varchar,
    p_home_phone        varchar,
    p_work_phone        varchar,
    p_phone_ext         varchar,
    p_position          varchar,
    p_notes             varchar,
    p_identity_id       integer,
    p_user_id           integer,
    p_group_id          integer,
    p_shift_id          integer,
    p_boss_id           integer,
    p_crreated_by       integer,
    p_timezone          smallint,
    p_utype             boolean,
    p_create_root       boolean,
    p_has_subord        boolean,
    p_inactive          boolean,
    p_generic           boolean 
)
RETURNS integer as $$
DECLARE
    v_session                   bigint;
    schema_name                 varchar;
    query                       varchar;
    current_ts                  integer;
    v_user_id                   integer;
    v_generic                   integer;
    v_udata_id                  integer;
    _c                          text;
BEGIN
        SELECT extract(epoch from now())::integer into current_ts;
        SELECT session_str::bigint INTO v_session;
        schema_name:='comp' || sess_company_id;

        IF NOT EXISTS (
            SELECT 1 FROM app.session WHERE app.session.identity_id=sess_identity_id AND app.session.session=v_session
        ) THEN
            RAISE EXCEPTION USING ERRCODE = 'AA001';
        END IF;

        query:='SELECT i.identity_id FROM app.identity as i,' || quote_ident(schema_name) || '.users as u,' || quote_ident(schema_name) || '.udata as ud WHERE i.identity_id=' || p_identity_id || ' and u.identity_id=i.identity_id and ud.identity_id=i.identity_id';
        RAISE WARNING 'query=%',query;
        PERFORM query;
        RAISE WARNING 'FOUND=%',FOUND;
        IF FOUND 
        THEN
            RAISE WARNING 'Record found, rising an exception';
            RAISE EXCEPTION USING ERRCODE = 'AA002';
        END IF;

        query:='SELECT u.user_id,u.generic FROM ' || quote_ident(schema_name) || '.users as u,identity as i WHERE i.email=p_email AND i.identity_id=u.identity_id';
        RAISE WARNING 'query=%',query;
        EXECUTE query INTO v_user_id,v_generic;
        IF NOT FOUND
        THEN
            EXECUTE 'INSERT INTO .users(
                identity_id,
                group_id,
                shift_id,
                boss_id,
                created_by,
                date_inserted
                timezone,
                utype,
                create_root,
                has_subord,
                inactive,
                generic
            ) VALUES (,,,,,,,,,,,) RETURNING user_id' 
                INTO v_user_id 
                USING schema_name,p _identity_id,p_group_id,p_shift_id,p_boss_id,p_created_by,p_date_inserted,p_timezone,p_utype,p_create_root,p_has_subord,p_inactive,p_generc;
        END IF;
        if NOT v_generic THEN
            RAISE EXCEPTION USING ERRCODE = 'AA003';
        END IF;
        EXECUTE 'INSERT INTO .udata(user_id,identity_id,date_insert,full_name,mob_phone,home_phone,work_phone,phone_ext,position,notes) VALUES (,,,,,,,,,)'
                INTO v_udata_id
                USING schema_name,v_user_id,p_identity_id,current_ts,p_full_name,p_mob_phone,p_home_phone,p_work_phone,p_phone_ext,p_position,p_notes;
        RETURN v_udata_id;
EXCEPTION
    WHEN SQLSTATE 'AA001' THEN
        RAISE EXCEPTION USING ERRCODE = 'AA001', message = 'Invalid session';
    WHEN SQLSTATE 'AA002' THEN
        RAISE EXCEPTION USING ERRCODE = 'AA002', message = 'Identity already registered';
    WHEN SQLSTATE 'AA003' THEN
        RAISE EXCEPTION USING ERRCODE = 'AA003', message = 'Not a generic user, can not add email address';
    WHEN OTHERS THEN
        RAISE NOTICE 'add_user() failed with... error: % %',SQLSTATE,SQLERRM;
        GET STACKED DIAGNOSTICS _c = PG_EXCEPTION_CONTEXT;
        RAISE NOTICE 'context: >>%<<', _c;
        RAISE EXCEPTION USING MESSAGE = 'An error which is not handled by function';
END
$$ LANGUAGE plpgsql;

和table结构,以防万一:

dev=> \d+ app.identity;
                                    Table "app.identity"
    Column     |          Type          | Modifiers | Storage  | Stats target | Description 
---------------+------------------------+-----------+----------+--------------+-------------
 identity_id   | integer                | not null  | plain    |              | 
 created_by    | integer                |           | plain    |              | 
 email         | character varying(128) |           | extended |              | 
 date_inserted | integer                |           | plain    |              | 
 password      | character varying(32)  |           | extended |              | 
 validated     | smallint               |           | plain    |              | 
Indexes:
    "identity_pkey" PRIMARY KEY, btree (identity_id)
    "identity_email_key" UNIQUE CONSTRAINT, btree (email)

dev=> \d+ "comp-1049007476".users;
                                                        Table "comp-1049007476.users"
    Column     |   Type   |                                 Modifiers                                 | Storage | Stats target | Description 
---------------+----------+---------------------------------------------------------------------------+---------+--------------+-------------
 user_id       | integer  | not null default nextval('"comp-1049007476".users_user_id_seq'::regclass) | plain   |              | 
 identity_id   | integer  | default 0                                                                 | plain   |              | 
 group_id      | integer  | default 0                                                                 | plain   |              | 
 shift_id      | integer  | default 0                                                                 | plain   |              | 
 boss_id       | integer  | default 0                                                                 | plain   |              | 
 created_by    | integer  | default 0                                                                 | plain   |              | 
 date_inserted | integer  | default 0                                                                 | plain   |              | 
 timezone      | smallint | default 0                                                                 | plain   |              | 
 utype         | boolean  | default false                                                             | plain   |              | 
 create_root   | boolean  | default false                                                             | plain   |              | 
 has_subord    | boolean  | default false                                                             | plain   |              | 
 inactive      | boolean  | default false                                                             | plain   |              | 
 generic       | boolean  | default false                                                             | plain   |              | 

dev-> \d+ "comp-1049007476".udata 
                                                                Table "comp-1049007476.udata"
    Column     |          Type          |                                 Modifiers                                  | Storage  | Stats target | Description 
---------------+------------------------+----------------------------------------------------------------------------+----------+--------------+-------------
 udata_id      | integer                | not null default nextval('"comp-1049007476".udata_udata_id_seq'::regclass) | plain    |              | 
 user_id       | integer                | default 0                                                                  | plain    |              | 
 identity_id   | integer                | default 0                                                                  | plain    |              | 
 date_inserted | integer                | default 0                                                                  | plain    |              | 
 full_name     | character varying(64)  | default ''::character varying                                              | extended |              | 
 mob_phone     | character varying(16)  | default ''::character varying                                              | extended |              | 
 home_phone    | character varying(16)  | default ''::character varying                                              | extended |              | 
 work_phone    | character varying(16)  | default ''::character varying                                              | extended |              | 
 phone_ext     | character varying(8)   | default ''::character varying                                              | extended |              | 
 position      | character varying(64)  | default ''::character varying                                              | extended |              | 
 notes         | character varying(128) | default ''::character varying                                              | extended |              | 

dev-> 

基本问题在于 PERFORM 语句的使用。此语句专为不进行结果处理的函数求值而设计。它不适用于动态 SQL(其中 SQL 作为字符串输入)。

PLpgSQL 将几乎所有语句翻译成 SELECTs。 PERFORM也不例外。

PERFORM fx(10,20);

翻译成:

SELECT fx(10,20);

您的代码:

variable := 'SELECT * FROM foo';
PERFORM variable;

翻译成:

SELECT 'SELECT * FROM foo'; -- it is same like SELECT 'hello';

此查询 returns 一行和变量 FOUND 应该为真。所以没什么奇怪的。

在 PLpgSQL 中,您应该在静态 SQL 和动态 SQL 之间有很大的区别。仅 EXECUTE 语句支持动态 SQL(查询在运行时组装)。

用法 PERFORM variable 是此语句的简单错误用法 - 但它生成有效的 SQL 语句,可以在没有运行时错误的情况下对其进行评估。