与 `CROSS APPLY` 和 `OUTER APPLY` 行为不一致
Inconsistent behavior with `CROSS APPLY` and `OUTER APPLY`
我在 Oracle-12c 中有一个架构,类似于具有 accounts
、posts
、comments
的典型论坛。我正在编写一个查询以获取...
- 一个用户
- 该用户的所有帖子
- 每个帖子的评论
- 以及每条评论的作者。
查询如下所示:
select "accounts".*, "p".*, "c".*, "author".*
from "accounts"
cross apply (
select * from "posts"
where "posts"."author_id" = "accounts"."id"
) "p"
cross apply (
select * from "comments"
where "comments"."post_id" = "p"."id"
) "c"
left join "accounts" "author" on "author"."id" = "c"."author_id"
where "accounts"."id" = 1
此查询按预期工作。我使用 CROSS APPLY
而不是典型的 JOIN,因为我将添加 OFFSET
和 FETCH
稍后分页。然而,问题是 CROSS APPLY
忽略了没有评论的帖子,这是我不想要的。我想 保留 结果中的帖子,即使它们没有评论。
所以我尝试将 CROSS APPLY
更改为 OUTER APPLY
。
select "accounts".*, "p".*, "c".*, "author".*
from "accounts"
outer apply (
select * from "posts"
where "posts"."author_id" = "accounts"."id"
) "p"
outer apply (
select * from "comments"
where "comments"."post_id" = "p"."id"
) "c"
left join "accounts" "author" on "author"."id" = "c"."author_id"
where "accounts"."id" = 1
但现在我得到这个错误:
ORA-00904: "p"."id": invalid identifier
00904. 00000 - "%s: invalid identifier"
*Cause:
*Action:
Error at Line: 9 Column: 34
出于某种原因,我的第二个 OUTER APPLY
加入抱怨我引用了第一个的结果中的 "p"."id"
。但是当我使用 CROSS APPLY
.
时没问题
这是怎么回事?为什么它们之间的行为存在差异?
编辑:OUTER APPLY
在这个基本示例中似乎没有必要。这是从一个更复杂的场景中提炼出来的,在这个场景中,我必须坚持 OUTER APPLY
确实是必要的,但该场景的细节与我要问的实际问题无关——这是关于这种看似未记录的行为差异在 CROSS
和 OUTER
之间 APPLY
.
编辑:
Oracle 版本: Database 12c Standard Edition Release 12.1.0.2.0 - 64bit Production
客户端: Oracle SQL 开发者版本 4.2.0.16.356
服务器: uname -a
的输出 - Linux ubuntu-1gb-sfo2-01 4.4.0-64-generic #85-Ubuntu SMP Mon Feb 20 11:50:30 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux
DDL: link
您的查询没有问题。您遇到了 12.1.0.2 中引入的错误 20356733/21547130,如 here 所述。为了克服它,请使用 12.1.0.2 之前的版本或应用最新更新(链接线程声称修复程序在 12.1.0.2.160419 补丁集更新中可用)。
此答案主要由 Matthew McPeak and Martin Smith 找到。我刚刚按照如下所述进行了第一次尝试,发现该问题在 Oracle 12.1.0.1 上无法重现。
第一次尝试回答:
我已经使用您的模式创建了测试数据库,这两个查询对我来说都工作正常。第一个没有return post没有注释,第二个return全是账号post,没有任何ORA-00904错误。我在 Oracle 12c 上做了这个测试。
继续你的问题:
尝试 copy/paste 并从您的 post 执行第二个查询。有时一些令人讨厌的拼写错误会潜入查询中。来自你的 post 的精确查询按预期对我有效。
如果您仍然遇到相同的错误,请提供您用于 accounts
、posts
和 comments
表创建的 DDL。
请说明您使用的 SQL 客户端。错误肯定是在服务器端,但在这种奇怪的情况下,每个小细节都会造成差异。
我的测试数据库:
CREATE TABLE "accounts"
(
"id" NUMBER(11) NOT NULL,
"name" NVARCHAR2(256),
CONSTRAINT ACCOUNTS_PK PRIMARY KEY ("id")
)
/
CREATE TABLE "posts"
(
"id" NUMBER(11) NOT NULL,
"author_id" NUMBER(11) NOT NULL,
"post_text" NVARCHAR2(1024),
CONSTRAINT POSTS_PK PRIMARY KEY ("id"),
CONSTRAINT POST_ACCOUNT_FK FOREIGN KEY ("author_id") REFERENCES "accounts" ("id") ON DELETE CASCADE
)
/
CREATE TABLE "comments"
(
"id" NUMBER(11) NOT NULL,
"author_id" NUMBER(11) NOT NULL,
"post_id" NUMBER(11) NOT NULL,
"comment_text" NVARCHAR2(1024),
CONSTRAINT COMMENTS_PK PRIMARY KEY ("id"),
CONSTRAINT COMMENT_ACCOUNT_FK FOREIGN KEY ("author_id") REFERENCES "accounts" ("id") ON DELETE CASCADE,
CONSTRAINT COMMENT_POST_FK FOREIGN KEY ("post_id") REFERENCES "posts" ("id") ON DELETE CASCADE
)
/
INSERT INTO "accounts"("id", "name") VALUES(1, 'testuser')
/
INSERT INTO "posts"("id", "author_id", "post_text") VALUES(1, 1, 'First test post')
/
INSERT INTO "posts"("id", "author_id", "post_text") VALUES(2, 1, 'Second test post')
/
INSERT INTO "comments"("id", "author_id", "post_id", "comment_text") VALUES(1, 1, 2, 'It is a very cool post')
/
COMMIT
对于 CodeFuller's 的回答,我只想补充一点:(A) 有一个补丁可以解决这个错误,并且 (B) 有一个解决方法 SQL 适用于 12.1.0.2 ,虽然我不知道它是否符合您的目的。
解决方法基本上是嵌套连接,如下所示:
SELECT accounts.*,
p.*,
author.*
FROM accounts
CROSS APPLY (SELECT posts.id,
posts.author_id,
posts.text,
comments.comment_author_id,
comments.comment_text
FROM posts
OUTER APPLY (SELECT comments.author_id comment_author_id,
comments.text comment_text
FROM comments
WHERE comments.post_id = posts.id) comments
WHERE posts.author_id = accounts.id) p
LEFT JOIN accounts author
ON author.id = p.comment_author_id
WHERE accounts.id = 1;
ID NAME ID_1 AUTHOR_ID TEXT COMMENT_AUTHOR_ID COMMENT_TEXT ID_2 NAME_1
---- --------- ---- --------- ------------------------------------------------- ----------------- --------------------------------------- ----- -------------------
1 Fred 1 1 Fred wrote this and it has comments 3 This is Helen's comment on Fred's post 3 Helen
1 Fred 2 1 Fred wrote this and it does not have any comments
-------- End of Data --------
2 row(s) fetched
参考:table 解决方法的 DDL
CREATE TABLE accounts
(
id NUMBER PRIMARY KEY,
name VARCHAR2 (30)
);
CREATE TABLE posts
(
id NUMBER PRIMARY KEY,
author_id NUMBER,
text VARCHAR2 (240)
);
CREATE TABLE comments
(
id NUMBER PRIMARY KEY,
post_id NUMBER,
author_id NUMBER,
text VARCHAR2 (240)
);
INSERT INTO accounts (id, name)
VALUES (1, 'Fred');
INSERT INTO accounts (id, name)
VALUES (2, 'Mary');
INSERT INTO accounts (id, name)
VALUES (3, 'Helen');
INSERT INTO accounts (id, name)
VALUES (4, 'Iqbal');
INSERT INTO posts (id, author_id, text)
VALUES (1, 1, 'Fred wrote this and it has comments');
INSERT INTO posts (id, author_id, text)
VALUES (2, 1, 'Fred wrote this and it does not have any comments');
INSERT INTO posts (id, author_id, text)
VALUES (3, 4, 'Iqbal wrote this and it does not have any comments');
INSERT INTO comments (id,
post_id,
author_id,
text)
VALUES (1,
1,
3,
'This is Helen''s comment on Fred''s post');
我在 Oracle-12c 中有一个架构,类似于具有 accounts
、posts
、comments
的典型论坛。我正在编写一个查询以获取...
- 一个用户
- 该用户的所有帖子
- 每个帖子的评论
- 以及每条评论的作者。
查询如下所示:
select "accounts".*, "p".*, "c".*, "author".*
from "accounts"
cross apply (
select * from "posts"
where "posts"."author_id" = "accounts"."id"
) "p"
cross apply (
select * from "comments"
where "comments"."post_id" = "p"."id"
) "c"
left join "accounts" "author" on "author"."id" = "c"."author_id"
where "accounts"."id" = 1
此查询按预期工作。我使用 CROSS APPLY
而不是典型的 JOIN,因为我将添加 OFFSET
和 FETCH
稍后分页。然而,问题是 CROSS APPLY
忽略了没有评论的帖子,这是我不想要的。我想 保留 结果中的帖子,即使它们没有评论。
所以我尝试将 CROSS APPLY
更改为 OUTER APPLY
。
select "accounts".*, "p".*, "c".*, "author".*
from "accounts"
outer apply (
select * from "posts"
where "posts"."author_id" = "accounts"."id"
) "p"
outer apply (
select * from "comments"
where "comments"."post_id" = "p"."id"
) "c"
left join "accounts" "author" on "author"."id" = "c"."author_id"
where "accounts"."id" = 1
但现在我得到这个错误:
ORA-00904: "p"."id": invalid identifier
00904. 00000 - "%s: invalid identifier"
*Cause:
*Action:
Error at Line: 9 Column: 34
出于某种原因,我的第二个 OUTER APPLY
加入抱怨我引用了第一个的结果中的 "p"."id"
。但是当我使用 CROSS APPLY
.
这是怎么回事?为什么它们之间的行为存在差异?
编辑:OUTER APPLY
在这个基本示例中似乎没有必要。这是从一个更复杂的场景中提炼出来的,在这个场景中,我必须坚持 OUTER APPLY
确实是必要的,但该场景的细节与我要问的实际问题无关——这是关于这种看似未记录的行为差异在 CROSS
和 OUTER
之间 APPLY
.
编辑:
Oracle 版本: Database 12c Standard Edition Release 12.1.0.2.0 - 64bit Production
客户端: Oracle SQL 开发者版本 4.2.0.16.356
服务器: uname -a
的输出 - Linux ubuntu-1gb-sfo2-01 4.4.0-64-generic #85-Ubuntu SMP Mon Feb 20 11:50:30 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux
DDL: link
您的查询没有问题。您遇到了 12.1.0.2 中引入的错误 20356733/21547130,如 here 所述。为了克服它,请使用 12.1.0.2 之前的版本或应用最新更新(链接线程声称修复程序在 12.1.0.2.160419 补丁集更新中可用)。
此答案主要由 Matthew McPeak and Martin Smith 找到。我刚刚按照如下所述进行了第一次尝试,发现该问题在 Oracle 12.1.0.1 上无法重现。
第一次尝试回答:
我已经使用您的模式创建了测试数据库,这两个查询对我来说都工作正常。第一个没有return post没有注释,第二个return全是账号post,没有任何ORA-00904错误。我在 Oracle 12c 上做了这个测试。
继续你的问题:
尝试 copy/paste 并从您的 post 执行第二个查询。有时一些令人讨厌的拼写错误会潜入查询中。来自你的 post 的精确查询按预期对我有效。
如果您仍然遇到相同的错误,请提供您用于
accounts
、posts
和comments
表创建的 DDL。请说明您使用的 SQL 客户端。错误肯定是在服务器端,但在这种奇怪的情况下,每个小细节都会造成差异。
我的测试数据库:
CREATE TABLE "accounts"
(
"id" NUMBER(11) NOT NULL,
"name" NVARCHAR2(256),
CONSTRAINT ACCOUNTS_PK PRIMARY KEY ("id")
)
/
CREATE TABLE "posts"
(
"id" NUMBER(11) NOT NULL,
"author_id" NUMBER(11) NOT NULL,
"post_text" NVARCHAR2(1024),
CONSTRAINT POSTS_PK PRIMARY KEY ("id"),
CONSTRAINT POST_ACCOUNT_FK FOREIGN KEY ("author_id") REFERENCES "accounts" ("id") ON DELETE CASCADE
)
/
CREATE TABLE "comments"
(
"id" NUMBER(11) NOT NULL,
"author_id" NUMBER(11) NOT NULL,
"post_id" NUMBER(11) NOT NULL,
"comment_text" NVARCHAR2(1024),
CONSTRAINT COMMENTS_PK PRIMARY KEY ("id"),
CONSTRAINT COMMENT_ACCOUNT_FK FOREIGN KEY ("author_id") REFERENCES "accounts" ("id") ON DELETE CASCADE,
CONSTRAINT COMMENT_POST_FK FOREIGN KEY ("post_id") REFERENCES "posts" ("id") ON DELETE CASCADE
)
/
INSERT INTO "accounts"("id", "name") VALUES(1, 'testuser')
/
INSERT INTO "posts"("id", "author_id", "post_text") VALUES(1, 1, 'First test post')
/
INSERT INTO "posts"("id", "author_id", "post_text") VALUES(2, 1, 'Second test post')
/
INSERT INTO "comments"("id", "author_id", "post_id", "comment_text") VALUES(1, 1, 2, 'It is a very cool post')
/
COMMIT
对于 CodeFuller's 的回答,我只想补充一点:(A) 有一个补丁可以解决这个错误,并且 (B) 有一个解决方法 SQL 适用于 12.1.0.2 ,虽然我不知道它是否符合您的目的。
解决方法基本上是嵌套连接,如下所示:
SELECT accounts.*,
p.*,
author.*
FROM accounts
CROSS APPLY (SELECT posts.id,
posts.author_id,
posts.text,
comments.comment_author_id,
comments.comment_text
FROM posts
OUTER APPLY (SELECT comments.author_id comment_author_id,
comments.text comment_text
FROM comments
WHERE comments.post_id = posts.id) comments
WHERE posts.author_id = accounts.id) p
LEFT JOIN accounts author
ON author.id = p.comment_author_id
WHERE accounts.id = 1;
ID NAME ID_1 AUTHOR_ID TEXT COMMENT_AUTHOR_ID COMMENT_TEXT ID_2 NAME_1
---- --------- ---- --------- ------------------------------------------------- ----------------- --------------------------------------- ----- -------------------
1 Fred 1 1 Fred wrote this and it has comments 3 This is Helen's comment on Fred's post 3 Helen
1 Fred 2 1 Fred wrote this and it does not have any comments
-------- End of Data --------
2 row(s) fetched
参考:table 解决方法的 DDL
CREATE TABLE accounts
(
id NUMBER PRIMARY KEY,
name VARCHAR2 (30)
);
CREATE TABLE posts
(
id NUMBER PRIMARY KEY,
author_id NUMBER,
text VARCHAR2 (240)
);
CREATE TABLE comments
(
id NUMBER PRIMARY KEY,
post_id NUMBER,
author_id NUMBER,
text VARCHAR2 (240)
);
INSERT INTO accounts (id, name)
VALUES (1, 'Fred');
INSERT INTO accounts (id, name)
VALUES (2, 'Mary');
INSERT INTO accounts (id, name)
VALUES (3, 'Helen');
INSERT INTO accounts (id, name)
VALUES (4, 'Iqbal');
INSERT INTO posts (id, author_id, text)
VALUES (1, 1, 'Fred wrote this and it has comments');
INSERT INTO posts (id, author_id, text)
VALUES (2, 1, 'Fred wrote this and it does not have any comments');
INSERT INTO posts (id, author_id, text)
VALUES (3, 4, 'Iqbal wrote this and it does not have any comments');
INSERT INTO comments (id,
post_id,
author_id,
text)
VALUES (1,
1,
3,
'This is Helen''s comment on Fred''s post');