12

我正在尝试在 Oracle 中运行以下 SQL 语句,它需要很长时间才能运行:

SELECT orderID FROM tasks WHERE orderID NOT IN 
(SELECT DISTINCT orderID FROM tasks WHERE
 engineer1 IS NOT NULL AND engineer2 IS NOT NULL)

如果我只运行 IN 子句中的子部分,它在 Oracle 中运行得非常快,即

SELECT DISTINCT orderID FROM tasks WHERE
engineer1 IS NOT NULL AND engineer2 IS NOT NULL

为什么整个语句在 Oracle 中需要这么长时间?在 SQL Server 中,整个语句运行得很快。

或者,我应该使用更简单/不同/更好的 SQL 语句吗?

有关该问题的更多详细信息:

  • 每个订单由许多任务组成
  • 将分配每个订单(其中一个或多个任务将设置engineer1 和engineer2)或者可以取消分配订单(其所有任务的engineer 字段都具有空值)
  • 我正在尝试查找所有未分配的 orderID。

以防万一,表中有大约 120k 行,每个订单有 3 个任务,因此大约有 40k 个不同的订单。

对答案的回应:

  • 我更喜欢在 SQL Server 和 Oracle 中都可以使用的 SQL 语句。
  • 任务仅在 orderID 和 taskID 上有一个索引。
  • 我尝试了语句的 NOT EXISTS 版本,但在我取消它之前它运行了 3 多分钟。也许需要一个 JOIN 版本的语句?
  • 还有一个带有 orderID 列的“订单”表。但我试图通过不将其包含在原始 SQL 语句中来简化问题。

我猜在原始 SQL 语句中,子查询每次都会为 SQL 语句第一部分中的每一行运行 - 即使它是静态的并且只需要运行一次?

执行

ANALYZE TABLE tasks COMPUTE STATISTICS;

使我原来的 SQL 语句执行得更快。

尽管我仍然很好奇为什么我必须这样做,以及是否/何时需要再次运行它?

统计信息为 Oracle 的基于成本的优化器提供了确定不同执行计划效率所需的信息:例如,表中的行数、行的平均宽度、每列的最高和最低值、每列不同值的数量,索引的聚类因子等。

在一个小型数据库中,您可以设置一个工作来每天晚上收集统计数据,然后不用管它。其实这是10g下的默认值。对于较大的实现,您通常必须权衡执行计划的稳定性与数据变化的方式,这是一个棘手的平衡。

Oracle 还有一个称为“动态采样”的功能,用于对表进行采样以确定执行时的相关统计信息。它更常用于数据仓库,其中采样的开销超过了长时间运行查询的潜在性能提升。

4

18 回答 18

10

如果您分析所涉及的表,这种类型的问题通常会消失(因此 Oracle 对数据的分布有更好的了解)

ANALYZE TABLE tasks COMPUTE STATISTICS;
于 2008-09-23T13:57:51.500 回答
3

“IN” - 子句在 Oracle 中非常慢。事实上,Oracle 中的内部查询优化器不能很好地处理带有“IN”的语句。尝试使用“存在”:

SELECT orderID FROM tasks WHERE orderID NOT EXISTS 
    (SELECT DISTINCT orderID FROM tasks WHERE
         engineer1 IS NOT NULL AND engineer2 IS NOT NULL)`print("code sample");`

注意:请检查查询是否构建相同的数据结果。

伊迪丝说:哎呀,查询的格式不是很好,但总体思路是正确的。Oracle 必须为第二个(内部)查询完成全表扫描,构建结果,然后将它们与第一个(外部)查询进行比较,这就是它变慢的原因。尝试

SELECT orderID AS oid FROM tasks WHERE NOT EXISTS 
    (SELECT DISTINCT orderID AS oid2 FROM tasks WHERE
         engineer1 IS NOT NULL AND engineer2 IS NOT NULL and oid=oid2)

或类似的东西;-)

于 2008-09-23T11:55:15.380 回答
3

我会尝试使用连接

SELECT 
    t.orderID 
FROM 
    tasks  t
    LEFT JOIN tasks t1
        ON t.orderID =  t1.orderID
        AND t1.engineer1 IS NOT NULL 
        AND t1.engineer2 IS NOT NULL
WHERE
    t1.orderID IS NULL 

如果将原始查询指定为:

SELECT orderID FROM orders WHERE orderID NOT IN 
(SELECT DISTINCT orderID FROM tasks WHERE
 engineer1 IS NOT NULL AND engineer2 IS NOT NULL)

(假设您有列出所有订单的订单表)

然后可以使用连接将其重写为:

SELECT 
    o.orderID 
FROM 
    orders o
    LEFT JOIN tasks t
        ON o.orderID =  t.orderID
        AND t.engineer1 IS NOT NULL 
        AND t.engineer2 IS NOT NULL
WHERE
    t.orderID IS NULL 
于 2008-09-23T12:49:45.627 回答
2

一些问题:

  • 任务中有多少行?
  • 上面定义了哪些索引?
  • 最近有分析过表格吗?

编写相同查询的另一种方法是:

select orderid from tasks
minus
select orderid from tasks
where engineer1 IS NOT NULL AND engineer2 IS NOT NULL

但是,我宁愿希望查询涉及“订单”表:

select orderid from ORDERS
minus
select orderid from tasks
where engineer1 IS NOT NULL AND engineer2 IS NOT NULL

或者

select orderid from ORDERS
where orderid not in
( select orderid from tasks
  where engineer1 IS NOT NULL AND engineer2 IS NOT NULL
)

或者

select orderid from ORDERS
where not exists
( select null from tasks
  where tasks.orderid = orders.orderid
  and   engineer1 IS NOT NULL OR engineer2 IS NOT NULL
)
于 2008-09-23T12:17:57.590 回答
2

我同意 TZQTZIO,我没有收到您的询问。

如果我们假设查询确实有意义,那么您可能想尝试按照某些建议使用 EXISTS 并避免 IN。IN 并不总是坏的,并且有可能表明它实际上比 EXISTS 表现更好。

问题标题不是很有帮助。我可以在一个 Oracle 数据库中设置此查询,使其运行缓慢,并使其在另一个数据库中快速运行。有许多因素决定了数据库如何解析查询、对象统计信息、SYS 模式统计信息和参数,以及服务器性能。Sqlserver vs. Oracle 不是这里的问题。

对于那些对查询调优和性能感兴趣并想了解更多信息的人来说,一些要搜索的谷歌术语是“oak table oracle”和“oracle jonathan lewis”。

于 2008-09-23T12:29:21.540 回答
1

我认为有几个人拥有几乎正确的 SQL,但缺少内部和外部查询之间的连接。
试试这个:

SELECT t1.orderID 
FROM   tasks t1
WHERE  NOT EXISTS
       (SELECT 1 
        FROM   tasks t2 
        WHERE  t2.orderID   = t1.orderID
        AND    t2.engineer1 IS NOT NULL 
        AND    t2.engineer2 IS NOT NULL)
于 2008-09-23T13:30:31.220 回答
1

“虽然我仍然很好奇为什么我必须这样做,以及是否/何时需要再次运行它?”

统计信息为 Oracle 的基于成本的优化器提供了确定不同执行计划效率所需的信息:例如,表中的行数、行的平均宽度、每列的最高和最低值、每列不同值的数量,索引的聚类因子等。

在一个小型数据库中,您可以设置一个工作来每天晚上收集统计数据,然后不用管它。其实这是10g下的默认值。对于较大的实现,您通常必须权衡执行计划的稳定性与数据变化的方式,这是一个棘手的平衡。

Oracle 还有一个称为“动态采样”的功能,用于对表进行采样以确定执行时的相关统计信息。它更常用于数据仓库,其中采样的开销超过了长时间运行查询的潜在性能提升。

于 2008-09-23T15:21:05.013 回答
0

你的查询不一样吗

SELECT orderID FROM tasks
WHERE engineer1 IS NOT NULL OR engineer2 IS NOT NULL

?

于 2008-09-23T11:59:50.567 回答
0

怎么样 :

SELECT DISTINCT orderID FROM tasks t1 WHERE NOT EXISTS (SELECT * FROM tasks t2 WHERE t2.orderID=t1.orderID AND (engineer1 IS NOT NULL OR engineer2 IS NOT NULL));

我不是优化专家,但也许您也忽略了 Oracle 数据库中的一些索引。

于 2008-09-23T12:02:18.563 回答
0

另一种选择是使用 MINUS(MSSQL 除外)

SELECT orderID FROM tasks
MINUS
SELECT DISTINCT orderID FROM tasks WHERE engineer1 IS NOT NULL 
AND engineer2 IS NOT NULL
于 2008-09-23T12:05:22.457 回答
0

如果您决定创建一个 ORDERS 表,我会向它添加一个 ALLOCATED 标志,并创建一个位图索引。这种方法还迫使您修改业务逻辑以保持标志更新,但查询速度会快如闪电。这取决于应用程序的查询有多重要。

关于答案,在这种情况下越简单越好。忘记子查询、连接、不同和分组依据,它们根本不需要!

于 2008-09-23T13:20:56.113 回答
0

表中有多少行满足“engineer1 IS NOT NULL ANDengineer2 IS NOT NULL”条件?

这(大致)告诉您是否值得尝试使用索引来检索关联的 orderid。

在 Oracle 中编写可以很好地处理未索引案例的查询的另一种方法是:

select distinct orderid
from
(
select orderid,
       max(case when engineer1 is null and engineer2 is null then 0 else 1)
          over (partition by orderid)
          as max_null_finder
from   tasks
)
where max_null_finder = 0
于 2008-09-23T14:04:03.433 回答
0

Oracle 优化器在处理 MINUS 语句方面做得很好。如果您使用 MINUS 重新编写查询,它可能会运行得非常快:

SELECT orderID FROM tasks
MINUS
SELECT DISTINCT orderID FROM tasks WHERE
 engineer1 IS NOT NULL AND engineer2 IS NOT NULL
于 2008-09-23T14:08:40.100 回答
0

新花样。

如果

  • COUNT() 函数不计算 NULL 值

  • 您希望所有任务的 orderID 都没有将engineer1 或engineer2 设置为值

那么这应该做你想要的:

SELECT orderID
FROM tasks
GROUP BY orderID
HAVING COUNT(engineer1) = 0 AND COUNT(engineer2) = 0

请测试一下。

于 2008-09-23T14:54:17.713 回答
0

我同意 ΤZΩΤZΙΟΥ 和 wearejimbo 的观点,即您的查询应该是......

SELECT DISTINCT orderID FROM Tasks 
WHERE Engineer1 IS NULL OR Engineer2 IS NULL;

我不了解 SQL Server,但此查询将无法利用任何索引,因为空行不在索引中。对此的解决方案是以允许创建仅包含空值行的基于函数的索引的方式重新编写查询。这可以通过 NVL2 完成,但可能无法移植到 SQL Server。

我认为最好的答案不是满足您的标准,而是为每个平台编写最适合该平台的不同声明。

于 2008-10-12T17:56:08.170 回答
-1

这是一种替代方法,我认为它可以满足您的需求:

SELECT orderID
 FROM tasks
 GROUP BY orderID
 HAVING COUNT(engineer1) = 0 OR COUNT(engineer2) = 0

我不确定您是否要在 HAVING 子句中使用“AND”或“OR”。听起来根据业务逻辑,这两个字段要么都被填充,要么都为 NULL;如果可以保证,那么您可以将条件减少到仅检查engineer1。

我认为,您的原始查询会为每个 orderID 提供多行,而我的只会提供一个。我猜这没关系,因为您只获取 orderID。

于 2008-09-23T12:16:07.297 回答
-1

如果在 Engineer1 和 Engineer2 列上没有索引,那么您将始终在 SQL Server 中生成表扫描,以及在 Oracle 中可能存在的等效项。

如果您只需要具有未分配任务的订单,那么以下内容应该在两个平台上都可以正常工作,但您还应该考虑将索引添加到任务表以提高查询性能。

SELECT DISTINCT orderID 
FROM tasks 
WHERE (engineer1 IS NULL OR engineer2 IS NULL)
于 2008-09-23T12:51:35.323 回答
-2

Oracle 的子查询“不好”。通常最好使用连接。

这是一篇关于如何使用 join 重写子查询的文章: http ://www.dba-oracle.com/sql/t_rewrite_subqueries_performance.htm

于 2008-09-23T12:13:04.417 回答