plpgsql中的语法错误

时间:2013-01-05 11:21:18

标签: sql postgresql plpgsql exists postgresql-8.4

我在PostgreSQL中有一个存储过程

CREATE OR REPLACE FUNCTION show_senti_lang_setting(IN _senti_id bigint)
RETURNS TABLE(lang_code character, native_name character varying, is_active boolean) AS
$BODY$
BEGIN
RETURN QUERY 
    SELECT
        l.lang_code,
        l.native_name,
        (CASE WHEN s.senti_id is NULL THEN FALSE
            ELSE TRUE
        END) is_active
    FROM
        language l
    LEFT JOIN senti_lang s
    ON s.lang_code=l.lang_code
    AND s.senti_id=_senti_id;
END;
$BODY$
LANGUAGE plpgsql VOLATILE STRICT;

错误是:

ERROR:  syntax error at or near "$1"
LINE 1: ...HEN s.senti_id is NULL THEN FALSE ELSE TRUE END)  $1  FROM l...
                                                         ^
QUERY:   SELECT l.lang_code, l.native_name, (CASE WHEN s.senti_id is NULL THEN FALSE ELSE TRUE END)  $1  FROM language l LEFT JOIN senti_lang s ON s.lang_code=l.lang_code AND s.senti_id= $2 
CONTEXT:  SQL statement in PL/PgSQL function "show_senti_lang_setting" near line 13

********** Error **********

ERROR: syntax error at or near "$1"
SQL state: 42601
Context: SQL statement in PL/PgSQL function "show_senti_lang_setting" near line 13

似乎错误是由plpgsql中的CASE引起的。相同的函数在SQL中运行良好:

CREATE OR REPLACE FUNCTION show_senti_lang_setting(bigint)
    RETURNS TABLE(lang_code character, native_name character varying, is_active boolean) AS
$BODY$

    SELECT
        l.lang_code,
        l.native_name,
        CASE WHEN s.senti_id is NULL THEN FALSE
            ELSE TRUE
        END is_active
    FROM
        language l
    LEFT JOIN senti_lang s
    ON s.lang_code=l.lang_code
    AND s.senti_id=$1;

$BODY$
  LANGUAGE sql VOLATILE STRICT;

2 个答案:

答案 0 :(得分:6)

关于你拥有什么:

版本8.4与返回行(OUT)中与列别名同名的is_active参数存在问题。这已经被修改了,它适用于PostgreSQL 9.1或更高版本(甚至可能是9.0)。这就是你的语法错误的原因。

列别名只是这个星座中的噪音。它们被丢弃以支持声明的OUT参数。他们唯一的目的可能是文档,所以只需将其作为评论并避免冲突开始:

    CASE WHEN s.senti_id is NULL THEN FALSE ELSE TRUE END -- AS is_active

另外:

  • 您应该将关键字AS与列别名一起使用(虽然通常可以跳过表别名)。
  • CASE声明周围不需要括号。

更好的形式

您拥有它的方式,始终返回表language中的所有行 - 以及找到senti_lang中匹配项的一个或多个实例。  同时定义函数STRICT,因此在为_senti_id提供NULL值时,根本不会获得任何行。很难想象一个明智的用例。

如果在senti_lang中找到多个匹配项,我不希望您为每种语言返回多行。所以你可以简化为:

CREATE OR REPLACE FUNCTION show_senti_lang_setting(IN _senti_id bigint)
RETURNS TABLE(lang_code character, native_name varchar, is_active boolean) AS
$func$
BEGIN
RETURN QUERY 
   SELECT l.lang_code
         ,l.native_name
         ,EXISTS (SELECT 1 FROM senti_lang s
                  WHERE  s.lang_code = l.lang_code
                  AND    s.senti_id = _senti_id) -- AS is_active
   FROM   language l;
END
$func$ LANGUAGE plpgsql VOLATILE STRICT;

我会提出一个问题,是否需要STRICT

Select 1

的含义

在下面的评论中回答后续问题。我引用the manual on EXISTS

  

因为结果仅取决于是否返回任何行,并且   不在这些行的内容上,子查询的输出列表是   通常不重要。一种常见的编码约定是全写   以EXISTS(SELECT 1 WHERE ...)的形式进行EXISTS测试。

基本上你可以编写任何语法上有效的表达式。无论如何它都被丢弃了 我们一直在讨论this related question下的可读性。

答案 1 :(得分:1)

这似乎是参数与case的列别名之间的名称冲突。

如果您在选择查询中重命名别名,则应该可以使用

SELECT
    l.lang_code,
    l.native_name,
    (CASE WHEN s.senti_id is NULL THEN FALSE
        ELSE TRUE
    END) as active_flag  -- <### this is the change 
FROM
    language l
LEFT JOIN senti_lang s
ON s.lang_code=l.lang_code
AND s.senti_id=_senti_id;

SQLFiddle示例:http://sqlfiddle.com/#!11/49075/1

case表达式的值由位置映射到“output”参数。所以他们可以有不同的名字(显然必须)。