【问题标题】:Select count(*) returns 0 but select * returns 2 rowsselect count(*) 返回 0 但 select * 返回 2 行
【发布时间】:2020-08-20 02:08:47
【问题描述】:

这与this question 非常相似,只是一个完整的最小示例。

我有一个只有左连接的简单选择查询(来自非空表)。最后一个左连接恰好是一个空表。

查询应返回 2 个非空行,但只需将其更改为 count(*) 查询即可使其返回 0 作为行数。

相同的 SQL 在 MySQL 和 MSSQL 上都能正常工作(修复 PK 语法后)。

完整(如果未注释可重新运行)示例:

-- DROP TABLE first;
-- DROP TABLE second;
-- DROP TABLE empty;

CREATE TABLE first (
  pk int,
  fk int
);
ALTER TABLE first
 ADD CONSTRAINT PK_first PRIMARY KEY (pk);

CREATE TABLE second (
  pk int
);
ALTER TABLE second
 ADD CONSTRAINT PK_second PRIMARY KEY (pk);

CREATE TABLE empty (
  pk int
);

ALTER TABLE first ADD CONSTRAINT FK_first FOREIGN KEY (fk)
 REFERENCES second (pk) ENABLE;

INSERT INTO second (pk)
  VALUES (5);
  
INSERT INTO first (pk, fk)
  VALUES (1, 5);
INSERT INTO first (pk, fk)
  VALUES (2, 5);

SELECT
  COUNT(*)
FROM first
LEFT OUTER JOIN second
  ON (first.fk = second.pk)
LEFT OUTER JOIN empty
  ON (1 = 1);

最后一个查询在我的机器上返回 0,但是将 count(*) 更改为 * 使其返回 2 行。

谁能重现这个?我的 db_version 是 11.2.0.2。

解释计划似乎看到了应该返回的 2 行:

----------------------------------------------------------------------------------
| Id  | Operation             | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |          |     1 |    13 |     3   (0)| 00:00:01 |
|   1 |  SORT AGGREGATE       |          |     1 |    13 |            |          |
|   2 |   MERGE JOIN CARTESIAN|          |     2 |    26 |     3   (0)| 00:00:01 |
|   3 |    VIEW               |          |     1 |       |     2   (0)| 00:00:01 |
|   4 |     TABLE ACCESS FULL | EMPTY    |     1 |       |     2   (0)| 00:00:01 |
|   5 |    BUFFER SORT        |          |     2 |    26 |     3   (0)| 00:00:01 |
|   6 |     INDEX FULL SCAN   | PK_FIRST |     2 |    26 |     1   (0)| 00:00:01 |
----------------------------------------------------------------------------------
 
Note
-----
   - dynamic sampling used for this statement (level=2)

我对动态采样了解不多,但是如果我alter session set OPTIMIZER_DYNAMIC_SAMPLING=0;,那么计划每一步显示82行。

删除主键可以解决 Oracle 上的问题,但这并不是一个合适的解决方案。

将join移到空表中也可以解决问题,但它是带有重言式过滤器的外连接,所以应该是noop。

这实际上是出于某种原因在 Oracle 上的预期行为吗?或者我的服务器只是被窃听了? MSSQL 和 MySQL 都返回 2 作为计数。

编辑:第 2 轮

再添加 2 个表就足够了,并且该错误也在 11.2.0.4 中显示。任何人都可以在更新的 Oracle 版本上重现它吗?

一个在线小提琴here

CREATE TABLE first (
  pk int,
  fk int
);
ALTER TABLE first
 ADD CONSTRAINT PK_first PRIMARY KEY (pk);

CREATE TABLE second (
  pk int,
  fk int
);
ALTER TABLE second
 ADD CONSTRAINT PK_second PRIMARY KEY (pk);

CREATE TABLE third (
  pk int,
  fk int
);
ALTER TABLE third
 ADD CONSTRAINT PK_third PRIMARY KEY (pk);

CREATE TABLE fourth (
  pk int
);
ALTER TABLE fourth
 ADD CONSTRAINT PK_fourth PRIMARY KEY (pk);

CREATE TABLE empty (
  pk int
);

ALTER TABLE first ADD CONSTRAINT FK_first FOREIGN KEY (fk)
 REFERENCES second (pk) ENABLE;

ALTER TABLE second ADD CONSTRAINT FK_second FOREIGN KEY (fk)
 REFERENCES third (pk) ENABLE;

ALTER TABLE third ADD CONSTRAINT FK_third FOREIGN KEY (fk)
 REFERENCES fourth (pk) ENABLE;

INSERT INTO fourth (pk)
  VALUES (50);
  
INSERT INTO third (pk, fk)
  VALUES (10, 50);
INSERT INTO third (pk, fk)
  VALUES (11, 50);

INSERT INTO second (pk, fk)
  VALUES (5, 10);
INSERT INTO second (pk, fk)
  VALUES (6, 10);

INSERT INTO first (pk, fk)
  VALUES (1, 5);
INSERT INTO first (pk, fk)
  VALUES (2, 5);

SELECT
  COUNT(*)
FROM first
LEFT OUTER JOIN second
  ON (first.fk = second.pk)
LEFT OUTER JOIN third
  ON (first.pk = third.pk)
LEFT OUTER JOIN fourth
  ON (third.fk = fourth.pk)
LEFT OUTER JOIN empty
  ON (1 = 1);

无论如何,共识似乎是这是过时 Oracle 版本中的一个错误。

【问题讨论】:

  • 响应对链接问题的评论,将查询更改为 SELECT COUNT(1) OVER () 立即返回 0 行。
  • Works in Oracle 18(和 12.1) - 很可能是您不受支持(且不再维护)的版本中的错误。
  • 哈.. 只是做了同样的事.. @hwnn.. 它真的在 11.2 坏了 - sqlfiddle.com/#!4/f91ac/1
  • 刚刚在第二台机器上试了一下,它工作正常。 db_version 11.2.0.4 - 他们很快修复了它。

标签: sql oracle


【解决方案1】:

11.2.0.2 版本太旧(已经 EOL),看起来甚至从未打过补丁。

您的错误的明显解决方法是提示no_query_transformation,尝试:

SELECT--+ no_query_transformation
  COUNT(*)
FROM first
LEFT OUTER JOIN second
  ON (first.fk = second.pk)
LEFT OUTER JOIN empty
  ON (1 = 1);

更新和添加:您可以使用提示 NO_ELIMINATE_JOIN 禁用联接消除: http://sqlfiddle.com/#!4/9cf338/10

SELECT--+ NO_ELIMINATE_JOIN(second)
  COUNT(*)
FROM first
LEFT OUTER JOIN second
  ON (first.fk = second.pk)
LEFT OUTER JOIN empty e
  ON (1 = 1);

或 _optimizer_join_elimination_enabled: http://sqlfiddle.com/#!4/9cf338/10

    SELECT--+ opt_param('_optimizer_join_elimination_enabled' 'false')
      COUNT(*)
    FROM first
    LEFT OUTER JOIN second
      ON (first.fk = second.pk)
    LEFT OUTER JOIN third
      ON (first.pk = third.pk)
    LEFT OUTER JOIN fourth
      ON (third.fk = fourth.pk)
    LEFT OUTER JOIN empty
      ON (1 = 1);

【讨论】:

  • 谢谢,这行得通。这是否可以在生产中使用,因为它会禁用优化?或者是否有更具体的提示会降低性能影响?可以帮助因任何原因无法升级的人。
  • @JakubFojtik 是的,当然,例如在您的情况下最明显的是NO_OUTER_JOIN_TO_INNER,但有很多细微差别。如果您使用 dbms_xplan.display_cursor(..,..,'advanced') 提供完整的查询以及实际执行计划的完整输出,那就更好了
  • 谢谢,_optimizer_join_elimination_enabled 甚至可以在我们较旧的数据库 v11.2.0.2 上工作,而且似乎足够具体,不会禁用太多优化。
猜你喜欢
  • 2016-10-19
  • 2013-04-27
  • 2015-05-10
  • 1970-01-01
  • 2021-08-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多