检查行类型时,为什么IS NOT NULL为false?

时间:2014-12-08 01:52:58

标签: postgresql null row plpgsql

我有一个函数registration(),它应该在某些情况下向表中添加一行。我已经包含了一段代码和来自通话的输出。

如果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;

5 个答案:

答案 0 :(得分:11)

作为@Pavel provided,支票<row-type> IS NOT NULL并不像您期望的那样有效。如果(且仅当) 每一列 为非NULL,它将返回TRUE

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

CREATE OR REPLACE FUNCTION registration(wr text)
  RETURNS integer AS
$rL$
    ...

    SELECT * INTO rowt FROM email WHERE email_email = eml;

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

    ...
$rL$ LANGUAGE plpgsql;

你可以在测试中反转你的表达。

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

您找到的任何现有行至少包含一列NOT NULL,因此如果找不到任何内容,rowt IS NULL只会返回TRUE

相关答案以及更多详情:

答案 1 :(得分:4)

对于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。

答案 2 :(得分:1)

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

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

UNIQUE上的addr约束意味着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;

答案 3 :(得分:0)

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

这可以通过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所述);有三种不同的方法可以对某种方法进行测试 未知状态:

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 为真并且所有行的字段都是非空的。由于此行为,IS NULLIS NOT NULL并不总是返回行值表达式的反向结果,即包含NULL和非空值的行值表达式将返回两个测试都是假的。 此定义符合SQL标准,并且是对8.2之前的PostgreSQL版本所表现出的不一致行为的更改。

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

  

Note:如果结果取决于比较两个NULL值或NULL和非{{1},则SQL规范要求按行进行比较以返回NULL }}。 PostgreSQL仅在比较两个行构造函数的结果或将行构造函数与子查询的输出进行比较时执行此操作(如第9.22节所述)。在其他上下文 1 中,比较两个复合类型值,两个NULL字段值被视为相等,并且NULL被认为大于非NULL。这对于复合类型具有一致的排序和索引行为是必要的。

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

答案 4 :(得分:0)

我遇到了同样的问题并通过强制转换为::text arr[1]::text is not null )在SQL select中访问复合内的某个数组/记录类型与此类似:

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 条件在某种程度上表现出来(如前面的帖子所述),如果所有复合列都是{{1,则仅返回true }}

它也适用于PGPLSQL函数。