【问题标题】:How can I tune my Oracle SQL query to run faster? [duplicate]如何调整我的 Oracle SQL 查询以更快地运行? [复制]
【发布时间】:2021-02-08 04:56:11
【问题描述】:

class_period 表中有超过 10,000 条记录。当我运行如下所示的查询时,获取数据需要花费太多时间。

您能帮帮我吗?如何加快查询速度?

 WITH DATA AS
     ( SELECT distinct class_time , class_id   
       from class_period       
       
      )
   SELECT distinct class_id, trim(regexp_substr(class_time, '[^:]+', 1, LEVEL)) class_time
    FROM DATA
    CONNECT BY regexp_substr(class_time , '[^:]+', 1, LEVEL) IS NOT NULL

作为图片附加的示例数据 enter image description here

作为图片附加的所需数据 enter image description here

我正在使用 oracle 11g。

【问题讨论】:

  • 请编辑您的问题并包含CLASS_PERIOD 表的表布局和示例数据,以及EXPLAIN PLAN 查询的结果。谢谢。
  • @BobJarvis-ReinstateMonica ,感谢您的回复,我已将示例数据作为图像附加,并将所需数据作为图像附加 -
  • 考虑一下为什么这个查询的结果很快很重要。您真的需要所有 10,000 行的值,还是只需要了解一些?一般建议是以对性能更友好的方式存储数据,并且每行只有一个 class_time 值 - 为此您可能需要一个额外的表。
  • @abdulraheemdev - 感谢您的更改。以后请记住,人们更喜欢将代码和数据作为文本,而不是图像。文本可以轻松复制、粘贴和编辑,这使您更有可能得到答案,因为许多人不会重新键入图像中的代码或数据。

标签: sql oracle plsql oracle11g


【解决方案1】:
  1. 修正您的查询,这样您就不需要使用DISTINCT。您的方法的问题在于,您使用的是具有多行输入的分层查询,并且无法将层次结构的每个级别与上一级相关联,因此查询会将其与 ALL 的项目相关联层次结构的上一层,您将在每个深度处生成越来越多的重复行。这是非常低效的。
  2. 从使用正则表达式更改为简单的字符串函数。

您可以使用:

WITH bounds ( class_id, class_time, start_pos, end_pos ) AS (
  SELECT class_id,
         class_time,
         1,
         INSTR( class_time, ':', 1 )
  FROM   data
UNION ALL
  SELECT class_id,
         class_time,
         end_pos + 1,
         INSTR( class_time, ':', end_pos + 1 )
  FROM   bounds
  WHERE  end_pos > 0
)
SELECT class_id,
       CASE end_pos
       WHEN 0
       THEN SUBSTR( class_time, start_pos )
       ELSE SUBSTR( class_time, start_pos, end_pos - start_pos )
       END AS class_time
FROM   bounds;

其中,对于样本数据:

CREATE TABLE data ( class_id, class_time ) AS
SELECT 1, '0800AM:0830AM' FROM DUAL UNION ALL
SELECT 1, '0900AM' FROM DUAL UNION ALL
SELECT 2, '0830AM:0900AM:0930AM' FROM DUAL UNION ALL
SELECT 2, '1000AM' FROM DUAL;

输出:

CLASS_ID |上课时间 --------: | :--------- 1 | 0800AM 1 | 0900AM 2 | 0830AM 2 |上午 1000 点 1 | 0830AM 2 | 0900AM 2 | 0930AM

db小提琴here

但是,更好的方法是更改​​存储数据的模型并停止将其存储为分隔字符串,而是将其存储在单独的表中,或者可能作为嵌套表中的集合。

使用第二个表的示例是:

CREATE TABLE data (
  class_id   NUMBER PRIMARY KEY
);

CREATE TABLE class_times (
  class_id   NUMBER REFERENCES data ( class_id ),
  class_time VARCHAR2(6)
);

INSERT ALL
  INTO data ( class_id ) VALUES ( 1 )
  INTO data ( class_id ) VALUES ( 2 )
  INTO class_times ( class_id, class_time ) VALUES ( 1, '0800AM' )
  INTO class_times ( class_id, class_time ) VALUES ( 1, '0830AM' )
  INTO class_times ( class_id, class_time ) VALUES ( 1, '0900AM' )
  INTO class_times ( class_id, class_time ) VALUES ( 2, '0830AM' )
  INTO class_times ( class_id, class_time ) VALUES ( 2, '0900AM' )
  INTO class_times ( class_id, class_time ) VALUES ( 2, '0930AM' )
  INTO class_times ( class_id, class_time ) VALUES ( 2, '1000AM' )
SELECT * FROM DUAL;

那么您的查询将是(假设您需要来自data 的其他列以及class_id):

SELECT d.class_id,
       c.class_time
FROM   data d
       INNER JOIN class_times c
       ON ( d.class_id = c.class_id );

哪些输出:

CLASS_ID |上课时间 --------: | :--------- 1 | 0800AM 1 | 0830AM 1 | 0900AM 2 | 0830AM 2 | 0900AM 2 | 0930AM 2 |上午 1000 点

使用嵌套表的示例是:

CREATE TYPE stringlist IS TABLE OF VARCHAR2(6);

CREATE TABLE data (
  class_id   NUMBER,
  class_time stringlist
) NESTED TABLE class_time STORE AS data__class_time;

INSERT INTO data ( class_id, class_time )
SELECT 1, stringlist( '0800AM','0830AM' ) FROM DUAL UNION ALL
SELECT 1, stringlist( '0900AM' ) FROM DUAL UNION ALL
SELECT 2, stringlist( '0830AM','0900AM','0930AM' ) FROM DUAL UNION ALL
SELECT 2, stringlist( '1000AM' ) FROM DUAL;

那么您的查询将变为:

SELECT d.class_id,
       ct.COLUMN_VALUE AS class_time
FROM   data d
       CROSS APPLY TABLE ( d.class_time ) ct

哪些输出:

CLASS_ID |上课时间 --------: | :--------- 1 | 0800AM 1 | 0830AM 1 | 0900AM 2 | 0830AM 2 | 0900AM 2 | 0930AM 2 |上午 1000 点

db小提琴here

【讨论】:

    【解决方案2】:

    MT0 发现了connect by 过滤器允许读取所有行的大问题。您不需要将其转换为递归 CTE,因为您已经区分了您正在投影的所有列,可以将其视为您的主键(假设它不可为空或您不想要空值) .

    你还需要一个特殊的过滤器,这样它就不会误以为你有一个无限循环。

     WITH DATA AS
         ( SELECT distinct class_time , class_id   
           from class_period       
          )
       SELECT distinct class_id, trim(regexp_substr(class_time, '[^:]+', 1, LEVEL)) class_time
        FROM DATA
        CONNECT BY regexp_substr(class_time , '[^:]+', 1, LEVEL) IS NOT NULL
    and prior class_time = class_time
    and prior class_id = class_id
    and prior sys_guid() is not null
    

    prior sys_guid() is not null 是一个特殊的过滤器,用于防止它与ORA-01436: CONNECT BY loop in user data 发生错误。

    这应该类似于递归 CTE。

    【讨论】:

    • 最好使用CROSS APPLY db<>fiddle。我知道为什么你的方法有效,但我有问题 SYS_GUID() 永远不是 NULL 所以你的最后一个 CONNECT BY 过滤器总是正确的,不应该是必要的(或者应该能够用 1=1 替换)但是它对于查询的正常工作是必要的,因为它可以防止行的相互关联。
    • 另外,this question 也有关于性能的讨论。
    • 没有人喜欢使用sys_guid,但它可以解决问题(并且trick 是正确的词)。你是对的,我没有意识到 connect by 在这里的表现有多么糟糕(尽管正则表达式并没有特别帮助),当你有大量行开始时,递归 CTE 能够做的批处理非常有效来自。
    猜你喜欢
    • 1970-01-01
    • 2011-06-04
    • 2016-02-27
    • 2020-01-25
    • 1970-01-01
    • 2012-06-27
    • 2011-05-18
    • 2016-11-29
    相关资源
    最近更新 更多