【问题标题】:Why is IS NOT NULL false when checking a row type?为什么检查行类型时 IS NOT NULL 为假?
【发布时间】:2015-02-05 15:36:31
【问题描述】:

我有一个函数registration(),它应该在某些情况下向表中添加一行。我已经包含了一个 sn-p 代码和一个调用的输出。

如果select * 返回一个非空表行(它根据RAISE NOTICE 执行)我想引发异常而不是添加行。该示例似乎表明rowt 不为空,但rowt IS NOT NULL 返回f(并且未引发异常)。

我希望这是我没有看到的小事。

select * into rowt from Email where email_email = eml;
RAISE NOTICE '%, rowt IS NOT NULL:%',rowt, rowt IS NOT NULL;
if rowt IS NOT NULL THEN
   RAISE EXCEPTION 'email address, %, already registered.' , eml;
END IF;

输出:

NOTICE:  (7,,,), rowt IS NOT NULL:f

registration 
--------------
    21
(1 row)

CREATE TABLE IF NOT EXISTS Email ( 
   email_email VARCHAR(50) NOT NULL, 
   email_password VARCHAR(50) NOT NULL,
   email_id integer DEFAULT nextval('email_email_id_seq'::regclass) NOT NULL,
   email_person_id integer
);
CREATE OR REPLACE FUNCTION registration( wr text ) RETURNS integer AS $rL$
DECLARE
    eml text;
    pwd text;
    nm text;
    rle text;
    emid integer;
    rowt Email%ROWTYPE;
BEGIN
    eml := getWebVarValue( wr , 'email' );
    select * into rowt from Email where email_email = eml;
    RAISE NOTICE '%, rowt IS NOT NULL:%', rowt, rowt IS NOT NULL;
    IF rowt IS NOT NULL THEN
       RAISE EXCEPTION 'email address, %, already registered.' , eml;
    END IF;
    pwd := getWebVarValue( wr , 'password' );
    IF pwd IS NULL THEN
       RAISE EXCEPTION 'No password specified in registration.';
    END IF;
    INSERT INTO Email VALUES (eml,pwd) RETURNING Email.email_id INTO emid;
    --nm =  getWebVarValue( wr , 'name' );
    --rle = getWebVarValue( wr , 'role' );
    RETURN emid;
END;
$rL$ LANGUAGE plpgsql;

【问题讨论】:

  • "我想引发异常而不是添加行" --- 并发调用呢?如果 2 个调用同时通过了这个检查怎么办?
  • 你能编辑你的帖子并显示Email的整个功能和表格结构吗?根据目前的信息,无法说出问题所在。一种可能的原因:rowt 是 RECORD 类型的变量,IS NOT NULL 应该对记录字段进行操作。
  • 参见"Executing a Query with a Single-row Result",以及特殊的FOUND 变量。
  • @zerkms: SET ISOLATION LEVEL SERIALIZABLE 是一个不错的安全默认值,您可以随时根据具体情况将其关闭。
  • @Kevin:注意:SET ISOLATION LEVEL SERIALIZABLE 会对数据库负载产生负面影响。它显着增加了锁的数量。

标签: postgresql null row plpgsql


【解决方案1】:

<row-type> IS NOT NULL

作为@Pavel provided,检查<row-type> IS NOT NULL 并没有像您预期的那样工作。当且仅当每一列NOT NULL时,它才返回TRUE

可以反转你的测试表达式:

IF rowt IS NULL THEN
   -- do nothing
ELSE 
   RAISE EXCEPTION 'email address, %, already registered.' , eml;
END IF;

您找到的任何行都包含至少一列NOT NULL,因此rowt IS NULL 仅在未找到任何内容时返回TRUE

见:

为允许全 NULL 行的表留下一个极端情况。

更好的解决方案

改为测试特殊变量FOUND(如@Mike commented):

PERFORM FROM email WHERE email_email = eml;

IF FOUND THEN
   RAISE EXCEPTION 'email, %, already registered.', eml;
END IF;

由于我们实际上对返回的行并不感兴趣,因此将 SELECT 替换为 PERFORM 以丢弃结果。要么相应地设置特殊变量FOUND
SELECT 列表(或 PERFROM 列表,真的)可以为空,因为只有一行的存在才重要。

更简单,但是,使用EXISTS

IF EXISTS (SELECT FROM email WHERE email_email = eml) THEN
   RAISE EXCEPTION 'email, %, already registered.', eml;
END IF;

见:

【讨论】:

    【解决方案2】:

    针对 ROW 类型的 NULL 测试是特定的:

    postgres=# SELECT r, r IS NULL AS "is null", r IS NOT NULL AS "is not null" 
                  FROM (VALUES(NULL,NULL),
                              (10, NULL), 
                              (10,10)) r ;
        r    | is null  | is not null 
    ---------+----------+--------------
     (,)     | t        | f
     (10,)   | f        | f
     (10,10) | f        | t
     (3 rows)
    

    所以NOT NULL只有在所有字段都不为空时才返回true。

    【讨论】:

      【解决方案3】:

      从您的代码中可以看出,您希望通过将电子邮件地址插入表格中来注册它,但前提是该电子邮件地址尚未注册并且提供了密码。对于初学者,您应该更改表定义以反映这些要求:

      CREATE TABLE email ( 
          id        serial PRIMARY KEY,
          addr      varchar(50) UNIQUE NOT NULL, 
          passw     varchar(50) NOT NULL,
          person_id integer
      );
      

      addr 上的 UNIQUE 约束意味着 PG 将不允许重复的电子邮件地址,因此您不必对此进行测试。您应该在进行插入时测试一个独特的违规行为。

      对于函数,我建议你传入电子邮件地址和密码,而不是将业务逻辑放在函数内部。像这样,该函数具有更少的依赖关系,并且可以更容易地在其他上下文中重用(例如通过您的 Web 应用程序通过其他方式注册电子邮件地址)。使函数STRICT 确保pwd 不为空,从而为您节省另一个测试。

      CREATE OR REPLACE FUNCTION registration(eml text, pwd text) RETURNS integer AS $rL$
      DECLARE
          emid integer;
      BEGIN
          INSERT INTO email (addr, passw) VALUES (eml, pwd) RETURNING id INTO emid;
          RETURN emid;
      EXCEPTION
          WHEN unique_violation THEN
              RAISE 'Email address % already registered', eml;
              RETURN NULL;
      END;
      $rL$ LANGUAGE plpgsql STRICT;
      

      【讨论】:

        【解决方案4】:

        您只想测试该电子邮件所在的行是否存在

        这可以很简单地实现,使用EXISTS 子查询表达式:

        IF EXISTS(SELECT 1 FROM email WHERE email_email = eml) THEN
           RAISE EXCEPTION 'email address, %, already registered.', eml;
        END IF;
        

        特殊变量FOUND 也可以工作,但当您想使用找到的行中的某些字段时,它更有价值。

        一般来说,<row-type> IS [ NOT ] [ DISTINCT FROM ] NULL 有特殊的规则,并且并不总是相互反转(如@Pavel 指出的);有 3 种不同的方法可以针对某种类型进行测试 未知状态:

        SELECT r,
          r IS NULL AS "is null",
          r IS NOT NULL AS "is not null",
          r IS DISTINCT FROM NULL AS "is distinct from null"
        FROM (
          VALUES
            (ROW(10::int, 10::int)),
            (ROW(10::int, NULL::int)),
            (ROW(NULL::int, NULL::int)),
            (NULL)
        ) AS s(r);
        
        -- R            IS NULL     IS NOT NULL     IS DISTINCT FROM NULL
        -----------------------------------------------------------------
        -- '(10,10)'    'f'         't'             't'
        -- '(10,)'      'f'         'f'             't'
        -- '(,)'        't'         'f'             't'
        -- NULL         't'         'f'             'f'
        

        SQLFiddle

        Note:如果表达式是行值的,那么IS NULL行表达式本身为空或*时为真* 当所有行的字段为空时**,而 IS NOT NULL行表达式本身为非空所有行的字段为非时为真-null。由于这种行为,IS NULLIS NOT NULL 并不总是为行值表达式返回相反的结果,即包含 NULL 和非空值的行值表达式对于两个测试都将返回 false。 此定义符合 SQL 标准,是对 PostgreSQL 8.2 之前版本所表现出的不一致行为的更改。

        此外,当有人使用复合类型而不是行构造函数时,操作符的处理也发生了一些变化:

        Note: 如果结果取决于比较两个NULL 值或NULL 和非NULL,SQL 规范要求逐行比较返回NULL。 PostgreSQL 仅在比较两个行构造函数的结果或将行构造函数与子查询的输出进行比较时才会这样做(如第 9.22 节中所示)。在比较两个复合类型值的其他上下文1中,两个NULL 字段值被认为相等,NULL 被认为大于非@ 987654343@。为了使复合类型具有一致的排序和索引行为,这是必要的。

        1 虽然我找不到任何查询,但可以这样工作。

        【讨论】:

        • 首先:在这种情况下,当没有使用查询结果时,您不应该使用IF EXISTS (SELECT * ...;请改用PERFORM第二:你的例子看起来很像 Pavel Stehule 在你之前几个小时发布的,但也许这只是我......
        • @Patrick 在这种情况下不是,EXISTS(PERFORM ...) 不起作用,选择的结果被 EXISTS 消耗。 2:我只是想强调一下,为什么会发生这种情况(即此定义符合 SQL 标准)——主要是因为可能有人对此也感兴趣(例如我自己:我以前没有遇到过这个,这就是为什么我测试了我能做的)。
        • EXISTS(PERFORM ... 从不工作,但PERFORM * ...; IF FOUND THEN ...唯一可接受的方式 ([postgresql.org/docs/9.3/static/…) 来测试数据是否存在。
        【解决方案5】:

        我遇到了同样的问题,并通过在 SQL 选择中强制转换为::text (arr[1]::text is not null) 来解决它,访问复合/记录类型中的某个数组对此:

        select
          arr,
          arr[1]       is not null  as nn,
          arr[1]::text              as as_txt,
          arr[1]::text is not null  as as_txt_nn
        from ...  -- "...": some composite type and an array type for it must exist
        
        row|  arr         nn    as_txt    as_txt_nn
        ===|  -------     ----- ------    ---------
        1  |  {(1,a)}     true  (1,a)     true
        2  |  {(1,NULL)}  false (1,NULL)  true
        3  |  {NULL}      false <NULL>    false
        
        -- hint: "<NULL>" is the null value representation of 
        --                your sql execution environment
        

        所以as_txt_nn 条件正确地检查问题,以区分与问题相关的第 2 行和第 3 行,如果第一个复合数组为空或给定。

        nn 条件的行为方式(如前几篇文章所述)在某种程度上仅返回 true,如果所有复合列都是 not null .

        它也应该适用于 PGPLSQL 函数。

        【讨论】:

          猜你喜欢
          • 2022-11-21
          • 2020-03-22
          • 1970-01-01
          • 1970-01-01
          • 2012-05-04
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多