当前位置: 首页 > 面试题库 >

如何在SQL查询中使用(func())。*语法避免使用多个函数评估?

邓禄
2023-03-14
问题内容

Context
当函数返回aTABLE或a时SETOF composite-type,例如以下示例函数:

CREATE FUNCTION func(n int) returns table(i int, j bigint) as $$
BEGIN
  RETURN QUERY select 1,n::bigint 
      union all select 2,n*n::bigint
      union all select 3,n*n*n::bigint;
END
$$ language plpgsql;

可以通过多种方法访问结果:

1)select * from func(3)将产生以下输出列:

i Ĵ
--- +-
 1 | 3
 2 | 9
 3 | 27

2)select func(3)将仅产生ROW类型的一个输出列。

unc

(1,3)
(2,9)
(3,27)


3)`select (func(3)).*`会产生类似#1的结果:

i Ĵ
— +-
1 | 3
2 | 9
3 | 27


当函数参数来自表或子查询时,语法#3是唯一可能的语法,如下所示:

select N, (func(N)).* from (select 2 as N union select 3 as N) s;


或如此相关答案所示。如果LATERAL JOIN可以的话,可以使用它,但是直到发布PostgreSQL 9.3为止,才不支持它,并且以前的版本仍然会使用很多年。

问题
现在,语法#3的问题在于该函数被调用的次数与结果中列的调用次数相同。没有明显的原因,但是它确实发生了。通过RAISE NOTICE 'called for %', n在函数中添加,我们可以在9.2版中看到它。使用上面的查询,它输出:

NOTICE: called for 2
NOTICE: called for 2
NOTICE: called for 3
NOTICE: called for 3

现在,如果将函数更改为返回4列,如下所示:

CREATE FUNCTION func(n int) returns table(i int, j bigint,k int, l int) as $$
BEGIN
raise notice ‘called for %’, n;
RETURN QUERY select 1,n::bigint,1,1
union all select 2,nn::bigint,1,1
union all select 3,n
n*n::bigint,1,1;
END
$$ language plpgsql stable;


然后相同的查询输出:

NOTICE: called for 2
NOTICE: called for 2
NOTICE: called for 2
NOTICE: called for 2
NOTICE: called for 3
NOTICE: called for 3
NOTICE: called for 3
NOTICE: called for 3


需要2个函数调用,实际进行了8个调用。该比率是输出列数。

使用语法2会产生相同的结果(除了输出列的布局),这些多次调用不会发生:

select N,func(N) from (select 2 as N union select 3 as N) s;


给出:

NOTICE: called for 2
NOTICE: called for 3


随后是6个结果行:

n | func
— + ------------
2 | (1,2,1,1)
2 | (2,4,1,1)
2 | (3,8,1,1)
3 | (1,3,1,1)
3 | (2,9,1,1)
3 | (3,27,1,1)
```

问题
是否存在仅通过执行最少的必需函数调用即可达到预期结果的9.2语法或构造?

额外的问题:为什么要进行多次评估?


问题答案:

您可以将其包装在子查询中,但是如果没有OFFSET 0hack ,就不能保证安全。在9.3中,使用LATERAL。该问题是由于解析器有效地将宏扩展*为列列表而引起的。

解决方法
在哪里:

SELECT (my_func(x)).* FROM some_table;

将计算函数中结果列的my_func n时间n,公式如下:

SELECT (mf).* FROM (
    SELECT my_func(x) AS mf FROM some_table
) sub;

通常不会,并且往往不会在运行时添加其他扫描。为了保证不会执行多次评估,您可以使用OFFSET 0黑客或滥用PostgreSQL的失败来跨CTE边界进行优化:

SELECT (mf).* FROM (
    SELECT my_func(x) AS mf FROM some_table OFFSET 0
) sub;

或者:

WITH tmp(mf) AS (
    SELECT my_func(x) FROM some_table
)
SELECT (mf).* FROM tmp;

在PostgreSQL 9.3中,您可以LATERAL用来获得更合理的行为:

SELECT mf.*
FROM some_table
LEFT JOIN LATERAL my_func(some_table.x) AS mf ON true;

LEFT JOIN LATERAL ... ON true即使函数调用不返回任何行,也保留与原始查询类似的所有行。

演示版
创建一个不可内联的函数作为演示:

CREATE OR REPLACE FUNCTION my_func(integer)
RETURNS TABLE(a integer, b integer, c integer) AS $$
BEGIN
    RAISE NOTICE 'my_func(%)',$1;
    RETURN QUERY SELECT $1, $1, $1;
END;
$$ LANGUAGE plpgsql;

和伪数据表:

CREATE TABLE some_table AS SELECT x FROM generate_series(1,10) x;

然后尝试以上版本。您会看到第一个每次调用会引发三个通知;后者只举一个。

为什么?
好问题。这太糟糕了。

看起来像:

(func(x)).*

扩展为:

(my_func(x)).i, (func(x)).j, (func(x)).k, (func(x)).l

在解析,根据一看debug_print_parse,debug_print_rewritten和debug_print_plan。(修剪后的)分析树如下所示:

   :targetList (
      {TARGETENTRY 
      :expr 
         {FIELDSELECT 
         :arg 
            {FUNCEXPR 
            :funcid 57168 
                 ...
            }
         :fieldnum 1 
         :resulttype 23 
         :resulttypmod -1 
         :resultcollid 0
         }
      :resno 1 
      :resname i 
       ...
      }
      {TARGETENTRY 
      :expr 
         {FIELDSELECT 
         :arg 
            {FUNCEXPR 
            :funcid 57168 
                 ...
            }
         :fieldnum 2 
         :resulttype 20 
         :resulttypmod -1 
         :resultcollid 0
         }
      :resno 2 
      :resname j 
       ...
      }
      {TARGETENTRY 
      :expr 
         {FIELDSELECT 
         :arg 
            {FUNCEXPR 
            :funcid 57168 
             ...
            }
         :fieldnum 3 
         :...
         }
      :resno 3 
      :resname k 
       ...
      }
      {TARGETENTRY 
      :expr 
         {FIELDSELECT 
         :arg 
            {FUNCEXPR 
            :funcid 57168 
             ...
            }
         :fieldnum 4 
          ...
         }
      :resno 4 
      :resname l 
       ...
      }
   )

因此,基本上,我们使用的是哑巴分析器黑客工具,通过克隆节点来扩展通配符。



 类似资料:
  • 问题内容: 这个查询非常简单,我要做的就是按字段对给定类别中的所有文章进行排序: 但是它运行非常缓慢。这是EXPLAIN所说的: 有没有一种方法可以重写此查询或向我的PHP脚本中添加其他逻辑,以避免并加快速度? 表结构: 更新 我已经索引了。我想我的情况在文档中得到了解释: 在某些情况下,MySQL无法使用索引来解析ORDER BY,尽管它仍然使用索引来查找与WHERE子句匹配的行。这些情况包括:

  • 问题内容: 我已经编写了类似这样的方法。但是我猜这应该进行重构。谁能建议最好的方法来避免使用多个if语句? 我在这里不能使用大小写转换,因为我的“条件”是。 问题答案: 您可以使用来保存您的解决方案: 为了使此代码起作用,您需要像在类中那样进行定义和定义,如下所示:

  • 本文向大家介绍如何避免MySQL查询中的OR语句过多?,包括了如何避免MySQL查询中的OR语句过多?的使用技巧和注意事项,需要的朋友参考一下 使用MySQL避免太多的OR语句。让我们首先创建一个表- 使用插入命令在表中插入一些记录- 使用select语句显示表中的所有记录- 这将产生以下输出- 以下是避免在MySQL查询中使用太多OR语句的查询,即使用- 这将产生以下输出-

  • 问题内容: 在下面的示例代码中,表名称是一个输入参数。在这种情况下,如何避免使用进行SQL注入。下面是示例代码,我尝试使用它来避免它,但是它不起作用。谁能告诉我如何纠正它? 输出:打印消息: 所有输入参数将完全替换,并删除一行。请让我知道如何处理这种情况。 问题答案: 您可以将表名包含在 但是,如果使用两部分命名约定,例如,则必须添加其他解析,因为这将导致: 无效的对象名称[dbo.tablena

  • 问题内容: 我有下面列出的3个表: 该SQL描述了我想要的: 问题是,在这种情况下,我有一个很大的NOT IN值,据我所知它将影响服务器性能(我不确定,因为我从未尝试对其进行基准测试或Google评估)。有什么建议吗? 问题答案: 试试这个 :

  • 问题内容: 我解释自己 我有一个填写查询的表格(例如): 但只有“ id”为必填项,所有其他参数均为​​可选。如何填写(或重新创建)该查询的预声明? 问题答案: 您将不得不使用多个准备好的语句,或者只是在运行时创建一条语句,检查您拥有哪些参数。 像这样: 更新/警告 :不要直接将参数值添加到查询字符串中,而应使用等。如上所示,查询字符串应仅包含值的占位符(例如),以防止SQL注入攻击。 我的意思是