【问题标题】:Return default rows from a function when first SELECT does not return rows当第一个 SELECT 不返回行时,从函数返回默认行
【发布时间】:2018-05-15 23:58:00
【问题描述】:

我有这个功能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$;

【问题讨论】:

  • 不是您问题的完全答案,但我找到了一种方便的方法来选择特定记录并在它不存在时回退到默认值,即使用第一个查询 UNION ALL第二个查询,然后是LIMIT 1。这还有一个优点,它可以是一个普通的旧 LANGUAGE SQL 函数,可以内联。

标签: sql postgresql plpgsql


【解决方案1】:

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 会忽略实体名称中的大写/小写,除非被引用,因此在命名表/字段/函数时,通常认为使用驼峰式大小写是不好的风格。

【讨论】:

  • 查询很聪明,但也相对昂贵。
【解决方案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;

干净且快速。第一个查询很简单,并且尽可能快。当第一个查询返回任何行时,第二个查询永远不会执行。相关(查看“其他案例”一章):

我可能会使用d.name ILIKE namevalue 并通过三元索引来支持它。见:

对于第二个查询中的默认行,部分索引 也可能会有所回报。如果您可以从中获得仅索引扫描,请将您需要检索的(少数)列作为索引列包括在内:

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;

相关:

【讨论】:

  • 不错,非常优雅的解决方案
猜你喜欢
  • 2018-03-16
  • 2019-02-22
  • 2021-07-21
  • 1970-01-01
  • 2021-10-18
  • 2017-09-21
  • 1970-01-01
  • 2020-05-22
  • 1970-01-01
相关资源
最近更新 更多