当第一个SELECT不返回行时,从函数返回默认行

时间:2018-05-15 23:58:00

标签: sql postgresql plpgsql

我有这个功能http://rextester.com/VIHMIG61446

CREATE OR REPLACE FUNCTION myTestProcedure(namevalue character varying)
 RETURNS TABLE(id integer, name character varying, isdefault boolean)
 LANGUAGE plpgsql
AS $function$
  BEGIN
    IF EXISTS(SELECT
        Domain.id,
        Domain.name,
        Domain.isdefault
      FROM Domain
      where lower(Domain.name) like namevalue)
    THEN
      RETURN QUERY SELECT
        Domain.id,
        Domain.name,
        Domain.isdefault
      FROM Domain
      where lower(Domain.name) like namevalue;
    ELSE
     RETURN QUERY SELECT
        Domain.id,
        Domain.name,
        Domain.isdefault
      FROM Domain
      where Domain.isdefault = true;
    END IF;   
  END
$function$;

我正在寻找一种不重复if的整个查询的方法,所以我决定使用with as存储结果,但它对我不起作用http://rextester.com/MVMVA73088

我应该如何使用with as

CREATE OR REPLACE FUNCTION myTestProcedure(namevalue character varying)
 RETURNS TABLE(id integer, name character varying, isdefault boolean)
 LANGUAGE plpgsql
AS $function$
  BEGIN
    with temporal_result as (
      SELECT
        Domain.id,
        Domain.name,
        Domain.isdefault
      FROM Domain
      where lower(Domain.name) like namevalue
    )
    IF EXISTS(temporal_result)
    THEN
      RETURN QUERY SELECT * from temporal_result;
    ELSE
     RETURN QUERY SELECT
        Domain.id,
        Domain.name,
        Domain.isdefault
      FROM Domain
      where Domain.isdefault = true;
    END IF;   
  END
$function$;

2 个答案:

答案 0 :(得分:3)

可以完全避免if-else逻辑。等效结果可以写为单个查询。函数BOOL_AND是一个聚合函数,如果任何值为false,则返回false,否则返回true

即使多行与lower(name) like '<namevalue>'条件匹配,或者您有多个默认值,以下查询也能正常工作。

SELECT subquery.id, subquery.name, subquery.isdefault
    FROM (SELECT d.id,
       d.name,
       d.isdefault,
       BOOL_AND(d.isdefault) OVER () default_and
    FROM domain d
    WHERE lower(d.name) like 'robert' or isdefault) subquery
WHERE isdefault = default_and

至于为什么你得到IF EXISTS(temporal_result)的错误,那不是有效的sql。在sql语句中进行此类分支是非法的。您可以做的是将第一个查询的结果保存到临时表中,并执行if-else分支引用临时表。您的存储过程的正确版本如下:

CREATE OR REPLACE FUNCTION mytestprocedure(namevalue character varying)
 RETURNS TABLE(id integer, name character varying, isdefault boolean)
 LANGUAGE plpgsql
AS $function$
  BEGIN
    CREATE TEMPORARY TABLE temporal_result as 
      SELECT
        d.id,
        d.name,
        d.isdefault
      FROM domain d
      where lower(d.name) like namevalue
    ;
    IF EXISTS(SELECT TRUE FROM temporal_result) THEN
      RETURN QUERY SELECT * from temporal_result;
    ELSE
      RETURN QUERY SELECT
        d.id,
        d.name,
        d.isdefault
      FROM domain d
      where d.isdefault = true;
      END IF;  
  DROP TABLE temporal_result;
  RETURN;
  END;
$function$;

请注意,必须在程序结束时删除表格。

另请注意,除非引用,否则postgresql会忽略实体名称中的大/小写情况,因此在命名表/字段/函数时使用驼峰的情况通常被认为是不好的。

答案 1 :(得分:2)

我建议改为检查特殊的plpgsql变量FOUND

CREATE OR REPLACE FUNCTION my_test_func(namevalue varchar)
  RETURNS TABLE(id integer, name varchar, isdefault boolean) AS
$func$
BEGIN
   RETURN QUERY 
   SELECT d.id, d.name, d.isdefault
   FROM   domain d
   WHERE  lower(d.name) LIKE namevalue;

   IF NOT FOUND THEN
      RETURN QUERY 
      SELECT d.id, d.name, d.isdefault
      FROM   domain d
      WHERE  d.isdefault;
   END IF;   
END
$func$  LANGUAGE plpgsql STABLE;

清洁并快速。第一个查询很简单,并且尽可能快。第一个返回任何行时,永远不会执行第二个查询。相关(查看章节&#34;其他案例&#34;):

我可能会使用d.name ILIKE namevalue并使用trigram索引支持它。参见:

第二个查询中的默认行的部分索引也可能付费。如果您可以从中获取仅索引扫描,请包含您需要检索的(少数)列作为索引列:

CREATE INDEX domain_defaults_idx ON domain (id, name, isdefault) WHERE isdefault;

是的,我们必须包含isdefault,即使在逻辑上多余。 Postgres目前(第10页)不够聪明,无法从WHERE条件中获得值。

如果您无法获得仅索引扫描,那么具有常量表达式的索引会更便宜:

CREATE INDEX domain_defaults_idx ON domain ((TRUE)) WHERE isdefault;

相关: