如何将 Oracle SQL 查询更快地调整为 运行?
How can I tune my Oracle SQL query to run faster?
我在 class_period
table 中有超过 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。
- 修正您的查询,这样您就不需要使用
DISTINCT
。您的方法的问题在于您使用的是具有多行输入的分层查询,并且无法将层次结构的每个级别与上一级相关联,因此查询会将其关联到 ALL层次结构上一级的项目,您将获得在每个深度生成的越来越多的重复行。这是非常低效的。
- 从使用正则表达式改为使用简单的字符串函数。
您可以使用:
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 | CLASS_TIME
-------: | :---------
1 | 0800AM
1 | 0900AM
2 | 0830AM
2 | 1000AM
1 | 0830AM
2 | 0900AM
2 | 0930AM
db<>fiddle here
但是,更好的方法是更改存储数据的模型并停止将其存储为定界字符串,而是将其存储在单独的 table 中,或者可能作为集合存储在嵌套 table.
使用第二个 table 的示例是:
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 | CLASS_TIME
-------: | :---------
1 | 0800AM
1 | 0830AM
1 | 0900AM
2 | 0830AM
2 | 0900AM
2 | 0930AM
2 | 1000AM
使用嵌套 table 的示例是:
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 | CLASS_TIME
-------: | :---------
1 | 0800AM
1 | 0830AM
1 | 0900AM
2 | 0830AM
2 | 0900AM
2 | 0930AM
2 | 1000AM
db<>fiddle here
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 类似。
我在 class_period
table 中有超过 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。
- 修正您的查询,这样您就不需要使用
DISTINCT
。您的方法的问题在于您使用的是具有多行输入的分层查询,并且无法将层次结构的每个级别与上一级相关联,因此查询会将其关联到 ALL层次结构上一级的项目,您将获得在每个深度生成的越来越多的重复行。这是非常低效的。 - 从使用正则表达式改为使用简单的字符串函数。
您可以使用:
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 | CLASS_TIME -------: | :--------- 1 | 0800AM 1 | 0900AM 2 | 0830AM 2 | 1000AM 1 | 0830AM 2 | 0900AM 2 | 0930AM
db<>fiddle here
但是,更好的方法是更改存储数据的模型并停止将其存储为定界字符串,而是将其存储在单独的 table 中,或者可能作为集合存储在嵌套 table.
使用第二个 table 的示例是:
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 | CLASS_TIME -------: | :--------- 1 | 0800AM 1 | 0830AM 1 | 0900AM 2 | 0830AM 2 | 0900AM 2 | 0930AM 2 | 1000AM
使用嵌套 table 的示例是:
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 | CLASS_TIME -------: | :--------- 1 | 0800AM 1 | 0830AM 1 | 0900AM 2 | 0830AM 2 | 0900AM 2 | 0930AM 2 | 1000AM
db<>fiddle here
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 类似。