意外的 SQL 结果:字符串与直接 SQL

发布于 2024-11-10 00:43:02 字数 1485 浏览 0 评论 0原文

工作 SQL

以下代码按预期工作,返回两列数据(行号和有效值):

sql_amounts := '
  SELECT
    row_number() OVER (ORDER BY taken)::integer,
    avg( amount )::double precision
  FROM
    x_function( '|| id || ', 25 ) ca,
    x_table m
  WHERE
    m.category_id = 1 AND
    m.location_id = ca.id AND
    extract( month from m.taken ) = 1 AND
    extract( day from m.taken ) = 1
  GROUP BY
    m.taken
  ORDER BY
    m.taken';

FOR r, amount IN EXECUTE sql_amounts LOOP
  SELECT array_append( v_row, r::integer ) INTO v_row;
  SELECT array_append( v_amount, amount::double precision ) INTO v_amount;
END LOOP;

非工作 SQL

以下代码不按预期工作;第一列是行号,第二列是NULL

FOR r, amount IN
  SELECT
    row_number() OVER (ORDER BY taken)::integer,
    avg( amount )::double precision
  FROM
    x_function( id, 25 ) ca,
    x_table m
  WHERE
    m.category_id = 1 AND
    m.location_id = ca.id AND
    extract( month from m.taken ) = 1 AND
    extract( day from m.taken ) = 1
  GROUP BY
    m.taken
  ORDER BY
    m.taken
LOOP
  SELECT array_append( v_row, r::integer ) INTO v_row;
  SELECT array_append( v_amount, amount::double precision ) INTO v_amount;
END LOOP;

问题

当查询本身返回两个有效列时,为什么非工作代码会为第二列返回 NULL 值? (这个问题主要是学术性的;如果有一种方法可以表达查询而不用将其包装在文本字符串中,那就太好了。)

完整代码

http://pastebin.com/hgV8f8gL

软件

PostgreSQL 8.4

谢谢。

Working SQL

The following code works as expected, returning two columns of data (a row number and a valid value):

sql_amounts := '
  SELECT
    row_number() OVER (ORDER BY taken)::integer,
    avg( amount )::double precision
  FROM
    x_function( '|| id || ', 25 ) ca,
    x_table m
  WHERE
    m.category_id = 1 AND
    m.location_id = ca.id AND
    extract( month from m.taken ) = 1 AND
    extract( day from m.taken ) = 1
  GROUP BY
    m.taken
  ORDER BY
    m.taken';

FOR r, amount IN EXECUTE sql_amounts LOOP
  SELECT array_append( v_row, r::integer ) INTO v_row;
  SELECT array_append( v_amount, amount::double precision ) INTO v_amount;
END LOOP;

Non-Working SQL

The following code does not work as expected; the first column is a row number, the second column is NULL.

FOR r, amount IN
  SELECT
    row_number() OVER (ORDER BY taken)::integer,
    avg( amount )::double precision
  FROM
    x_function( id, 25 ) ca,
    x_table m
  WHERE
    m.category_id = 1 AND
    m.location_id = ca.id AND
    extract( month from m.taken ) = 1 AND
    extract( day from m.taken ) = 1
  GROUP BY
    m.taken
  ORDER BY
    m.taken
LOOP
  SELECT array_append( v_row, r::integer ) INTO v_row;
  SELECT array_append( v_amount, amount::double precision ) INTO v_amount;
END LOOP;

Question

Why does the non-working code return a NULL value for the second column when the query itself returns two valid columns? (This question is mostly academic; if there is a way to express the query without resorting to wrapping it in a text string, that would be great to know.)

Full Code

http://pastebin.com/hgV8f8gL

Software

PostgreSQL 8.4

Thank you.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

掩饰不了的爱 2024-11-17 00:43:02

这两种说法并不严格等同。

假设 id = 4,第一个在每次传递时都会计划/准备,其行为类似于:

prepare dyn_stmt as '... x_function( 4, 25 ) ...'; execute dyn_stmt;

另一个仅在第一遍时计划/准备,并且行为更像:(

prepare stc_stmt as '... x_function( $1, 25 ) ...'; execute stc_stmt(4);

循环实际上会使其为上面的内容准备一个游标,但这不是我们的重点。)

许多因素可以使两者产生不同的结果。

  • 调用该过程之前的搜索路径更改将被第二次调用忽略。特别是如果这使得 x_table 指向不同的东西。
  • 各种常量和对不可变函数的调用在第二次调用的计划中是“硬连线”的。

将此视为这些副作用的说明:

deallocate all;
begin;
prepare good as select now();
prepare bad as select current_timestamp;
execute good; -- yields the current timestamp
execute bad;  -- yields the current timestamp
commit;
execute good; -- yields the current timestamp
execute bad;  -- yields the timestamp at which it was prepared

为什么两者在您的情况下没有返回相同的结果取决于上下文(您只发布了 pl/pgsql 函数的一部分,因此很难说),但是我的我猜测您遇到了上述问题的变体。

The two statements aren't strictly equivalent.

Assuming id = 4, the first one gets planned/prepared on each pass, and behaves like:

prepare dyn_stmt as '... x_function( 4, 25 ) ...'; execute dyn_stmt;

The other gets planned/prepared on the first pass only, and behaves more like:

prepare stc_stmt as '... x_function( $1, 25 ) ...'; execute stc_stmt(4);

(The loop will actually make it prepare a cursor for the above, but that's besides the point for our sake.)

A number of factors can make the two yield different results.

  • Search path changes before calling the procedure will be ignored by the second call. In particular if this makes x_table point to something different.
  • Constants of all kinds and calls to immutable functions are "hard-wired" in the second call's plan.

Consider this as an illustration of these side-effects:

deallocate all;
begin;
prepare good as select now();
prepare bad as select current_timestamp;
execute good; -- yields the current timestamp
execute bad;  -- yields the current timestamp
commit;
execute good; -- yields the current timestamp
execute bad;  -- yields the timestamp at which it was prepared

Why the two aren't returning the same results in your case would depend on the context (you only posted part of your pl/pgsql function, so it's hard to tell), but my guess is you're running into a variation of the above kind of problem.

拥抱我好吗 2024-11-17 00:43:02

来自汤姆·莱恩:

我认为问题在于您假设“amount”将引用查询的表列,而实际上它是 plpgsql 函数的局部变量。除非您使用表的名称/别名限定列引用,否则第二种解释将优先。

注意:当存在这种类型的歧义时,PG 9.0 将默认抛出错误。

From Tom Lane:

I think the problem is that you're assuming "amount" will refer to a table column of the query, when actually it's a local variable of the plpgsql function. The second interpretation will take precedence unless you qualify the column reference with the table's name/alias.

Note: PG 9.0 will throw an error by default when there is an ambiguity of this type.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文