【问题标题】:Inconsistent behavior with `CROSS APPLY` and `OUTER APPLY``CROSS APPLY` 和 `OUTER APPLY` 的行为不一致
【发布时间】:2017-07-24 09:44:23
【问题描述】:

我在 Oracle-12c 中有一个架构,类似于带有 accountspostscomments 的典型论坛。我正在编写一个查询来获取...

  1. 一个用户
  2. 该用户的所有帖子
  3. 每个帖子上的 cmets
  4. 以及每条评论的作者。

查询如下所示:

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,因为稍后我将添加 OFFSETFETCH 以进行分页。但是,问题是CROSS APPLY 忽略了没有 cmets 的帖子,这是我不想要的。我想保留结果中的帖子,即使它们没有 cmets。

所以我尝试将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 确实是必要的,但是那个场景的细节与我要问的实际问题无关——这是关于@ 之间这种看似无证的行为差异987654339@和OUTERAPPLY

编辑:

Oracle 版本: Database 12c Standard Edition Release 12.1.0.2.0 - 64bit Production

客户端: Oracle SQL Developer 版本 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

【问题讨论】:

  • 您是否正在寻找对为什么会发生这种情况的明确解释,或者您是否正在寻找一种可以编写查询(应用 CROSS/OUTER)的方法?
  • 您使用的是 Oracle 12.1.0.2 或更高版本吗?如果是这样,您可能会遇到导致此行为的错误 20356733。
  • 是的,它的版本是 12.1.0.2.0!

标签: sql oracle join oracle12c


【解决方案1】:

您的查询没有问题。您遇到了在 12.1.0.2 中引入的错误 20356733/21547130,如 here 所述。为了克服它,请使用 12.1.0.2 之前的版本或应用最新更新(链接线程声称修复可在 12.1.0.2.160419 补丁集更新中获得)。

这个答案主要是由Matthew McPeakMartin Smith 找到的。我刚刚按如下所述进行了第一次尝试,发现该问题在 Oracle 12.1.0.1 上无法重现。

第一次回答尝试:

我已经使用您的架构创建了测试数据库,并且两个查询对我来说都可以正常工作。第一个不返回没有 cmets 的帖子,第二个返回所有帐户帖子,没有任何 ORA-00904 错误。我在 Oracle 12c 上做了这个测试。

继续您的问题:

  1. 尝试从您的帖子中复制/粘贴并执行第二个查询。有时一些讨厌的拼写错误会潜入查询中。您帖子中的确切查询对我来说符合预期。

  2. 如果您仍然遇到同样的错误,请提供您用于创建 accountspostscomments 表的 DDL。

  3. 请指定您使用的 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

【讨论】:

  • 你能发布你的测试吗?因为我能够在 12c 上重现他的错误。 (12.1.0.2.0)
  • Matthew McPeak,我已经用我的测试数据库更新了我的答案。
  • 有趣。在 12.1.0.2 上,使用您的 DDL 和 OP 的查询,我得到了与 OP 相同的 ORA-00904 错误。 OP 可能遇到 12.1.0.2 中引入的错误 20356733。
  • 看起来相关 community.oracle.com/message/14144318#14144318 - 我没有 Oracle 帐户,因此无法点击错误链接。它提到了 21547130 和 20356733
  • 仅供参考,Oracle 20356733 是此问题的基本错误。错误 21547130 是他们记录的将修复移植到 Oracle Linux 的错误。它们是相同的“错误”/问题。
【解决方案2】:

对于CodeFuller's 的回答,我只想补充一点(A)有一个可用于这个错误的补丁,(B)有一个在 12.1.0.2 中工作的变通 SQL,虽然我不知道它是否将服务于您的目的。

解决方法基本上是嵌套您的联接,如下所示:

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

参考:表 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');

【讨论】:

  • 自动生成SQL。这种嵌套解决方法是可能的,但更难生成。我没有付费的 Oracle 帐户,因此无法访问该补丁。我能做些什么来解决这个问题?如果没有,我会做嵌套。
  • 您可能仍会尝试在Oracle Support website 上注册。开发人员许可证明确不附带技术支持,但这可能只是意味着您无法通过网站记录服务请求。它可能仍然允许您阅读支持文档和下载补丁。我个人已经好几年没有获得开发者许可证了,所以我不能确定更多。
猜你喜欢
  • 2011-10-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-12-01
  • 1970-01-01
  • 2013-04-28
  • 2019-02-11
  • 1970-01-01
相关资源
最近更新 更多