是否有 Oracle 分析函数可以帮助将此 SQL 转换为视图?
Is there an Oracle analytic function that can help turn this SQL into a view?
考虑这个 table(简化),它可能有很多行:
CREATE TABLE v
(
m VARCHAR2(50),
ts date,
v NUMBER
)
/
那么下面的查询非常有用:
SELECT
m,
MIN(ts) min_ts,
MAX(ts) max_ts
FROM
v
WHERE
TO_DATE('2016-01-10','YYYY-MM-DD') <= ts AND
ts < TO_DATE('2016-01-20','YYYY-MM-DD') AND
m = '123'
GROUP BY
m
/
其中 TO_DATES 和“123”代表用户提供的过滤条件。现在,当我尝试将此 SQL 转换为视图并将条件放在上面时,我 运行 遇到了问题:
CREATE OR REPLACE VIEW vv AS
SELECT
m,
MIN(ts) min_ts,
MAX(ts) max_ts
FROM
v
GROUP BY
m
/
我无法在视图上提供 ts 日期过滤器,因为 Oracle 已经对结果进行了分组,例如,以下将不起作用(ORA-00904:"TS":标识符无效):
SELECT
*
FROM
vv
WHERE
TO_DATE('2016-01-10','YYYY-MM-DD') <= ts AND
ts < TO_DATE('2016-01-20','YYYY-MM-DD') AND
m='123'
/
那么在这种情况下是否有任何 Oracle 分析等函数可以帮助将 SQL 转换为视图?
可以做到,但需要使用上下文,以及执行上下文 setting/clearing 所需的包。您需要使用您的数据进行测试,看看这是否足够高效。
创建新上下文
create or replace context test_context using pkg_context_utils;
创建包以设置新上下文
create or replace package pkg_context_utils
as
procedure set_date (p_date in date);
procedure clear_date;
end pkg_context_utils;
/
create or replace package body pkg_context_utils
as
procedure set_date (p_date in date)
is
begin
dbms_session.set_context (namespace => 'test_context',
attribute => 'test_date',
value => p_date);
end set_date;
procedure clear_date
is
begin
dbms_session.clear_context(namespace => 'test_context',
client_id => null,
attribute => 'test_date');
end clear_date;
end pkg_context_utils;
/
使用 where 子句中的上下文创建视图
create or replace view test_view
as
select * from dual where sys_context('test_context', 'test_date') > sysdate;
运行 未设置上下文的视图
select * from test_view;
no rows selected.
将上下文设置在当前日期之后
begin
pkg_context_utils.set_date(trunc(sysdate + 1));
end;
/
select * from test_view;
DUMMY
-----
X
将上下文设置在当前日期之前
begin
pkg_context_utils.set_date(trunc(sysdate));
end;
/
select * from test_view;
no rows selected.
我在使用虚拟视图和该视图上的 %ROWTYPE 的包中使用流水线 table 函数,例如:
-- Dummy view
CREATE OR REPLACE VIEW vv AS
SELECT
m,
MIN(ts) min_ts,
MAX(ts) max_ts
FROM
v
WHERE
1=0
GROUP BY
m
/
CREATE OR REPLACE PACKAGE vv_utl
AS
TYPE tt_vv IS TABLE OF vv%ROWTYPE;
FUNCTION load
(
ifromts IN date,
itots IN date,
im IN varchar2 default null
) RETURN tt_vv PIPELINED;
END;
/
CREATE OR REPLACE PACKAGE BODY vv_utl
AS
FUNCTION load
(
ifromts IN date,
itots IN date,
im IN varchar2 default null
) RETURN tt_vv PIPELINED
AS
CURSOR vv_cur IS
SELECT
m,
MIN(ts) min_ts,
MAX(ts) max_ts
FROM
v
WHERE
ifromts <= ts AND
ts < itots AND
m = nvl(im,m)
GROUP BY
m;
BEGIN
FOR rec IN vv_cur LOOP
pipe row(rec);
END LOOP;
END;
END;
/
-- Test
SELECT
*
FROM
table(
vv_utl.load(
TO_DATE('2016-01-10','YYYY-MM-DD'),
TO_DATE('2016-01-20','YYYY-MM-DD')
)
)
/
我担心流水线 table 函数的性能(我在某处读到他们不缓存结果)并且因为我无法使我的函数具有确定性(一种让 Oracle 缓存结果的方法),我使用一个包来存储我的参数,以获得直接视图解决方案:
CREATE OR REPLACE PACKAGE vv_param
AS
PROCEDURE set
(
ifromts IN date,
itots IN date,
im IN varchar2 default null
);
FUNCTION get_fromts RETURN DATE;
FUNCTION get_tots RETURN DATE;
FUNCTION get_m RETURN VARCHAR2;
END;
/
CREATE OR REPLACE PACKAGE BODY vv_param
AS
lfromts date;
ltots date;
lm varchar2(50);
PROCEDURE set
(
ifromts IN date,
itots IN date,
im IN varchar2 default null
)
AS
BEGIN
lfromts := ifromts;
ltots := itots;
lm := im;
END;
FUNCTION get_fromts RETURN DATE AS BEGIN RETURN lfromts; END;
FUNCTION get_tots RETURN DATE AS BEGIN RETURN ltots; END;
FUNCTION get_m RETURN VARCHAR2 AS BEGIN RETURN lm; END;
END;
/
CREATE OR REPLACE VIEW vv AS
SELECT
m,
MIN(ts) min_ts,
MAX(ts) max_ts
FROM
v
WHERE
vv_param.get_fromts <= ts AND
ts < vv_param.get_tots AND
m = nvl(vv_param.get_m,m)
GROUP BY
m
/
BEGIN
vv_param.set(
TO_DATE('2016-01-10','YYYY-MM-DD'),
TO_DATE('2016-01-20','YYYY-MM-DD')
);
END;
/
-- Test
SELECT
*
FROM
vv
/
另外,当使用流水线 table 函数时,我看不到 EXPLAIN PLAN 的详细信息,因为 SQL 隐藏在包函数中。现在视图上的 EXPLAIN PLAN 正常工作。
这个例子显然很简单,但是在我的实际实现中有很多这样的视图,很多都依赖于其他视图。使用包变量解决方案允许我在所有这些视图中共享它们。是的,它并非没有陷阱,例如人们可能会忘记设置变量,如果您像 ODP.NET 那样重新使用会话,您可能会使用旧值并因此得到错误的结果。所以只有时间才能证明这是否是正确的方法!
考虑这个 table(简化),它可能有很多行:
CREATE TABLE v
(
m VARCHAR2(50),
ts date,
v NUMBER
)
/
那么下面的查询非常有用:
SELECT
m,
MIN(ts) min_ts,
MAX(ts) max_ts
FROM
v
WHERE
TO_DATE('2016-01-10','YYYY-MM-DD') <= ts AND
ts < TO_DATE('2016-01-20','YYYY-MM-DD') AND
m = '123'
GROUP BY
m
/
其中 TO_DATES 和“123”代表用户提供的过滤条件。现在,当我尝试将此 SQL 转换为视图并将条件放在上面时,我 运行 遇到了问题:
CREATE OR REPLACE VIEW vv AS
SELECT
m,
MIN(ts) min_ts,
MAX(ts) max_ts
FROM
v
GROUP BY
m
/
我无法在视图上提供 ts 日期过滤器,因为 Oracle 已经对结果进行了分组,例如,以下将不起作用(ORA-00904:"TS":标识符无效):
SELECT
*
FROM
vv
WHERE
TO_DATE('2016-01-10','YYYY-MM-DD') <= ts AND
ts < TO_DATE('2016-01-20','YYYY-MM-DD') AND
m='123'
/
那么在这种情况下是否有任何 Oracle 分析等函数可以帮助将 SQL 转换为视图?
可以做到,但需要使用上下文,以及执行上下文 setting/clearing 所需的包。您需要使用您的数据进行测试,看看这是否足够高效。
创建新上下文
create or replace context test_context using pkg_context_utils;
创建包以设置新上下文
create or replace package pkg_context_utils
as
procedure set_date (p_date in date);
procedure clear_date;
end pkg_context_utils;
/
create or replace package body pkg_context_utils
as
procedure set_date (p_date in date)
is
begin
dbms_session.set_context (namespace => 'test_context',
attribute => 'test_date',
value => p_date);
end set_date;
procedure clear_date
is
begin
dbms_session.clear_context(namespace => 'test_context',
client_id => null,
attribute => 'test_date');
end clear_date;
end pkg_context_utils;
/
使用 where 子句中的上下文创建视图
create or replace view test_view
as
select * from dual where sys_context('test_context', 'test_date') > sysdate;
运行 未设置上下文的视图
select * from test_view;
no rows selected.
将上下文设置在当前日期之后
begin
pkg_context_utils.set_date(trunc(sysdate + 1));
end;
/
select * from test_view;
DUMMY
-----
X
将上下文设置在当前日期之前
begin
pkg_context_utils.set_date(trunc(sysdate));
end;
/
select * from test_view;
no rows selected.
我在使用虚拟视图和该视图上的 %ROWTYPE 的包中使用流水线 table 函数,例如:
-- Dummy view
CREATE OR REPLACE VIEW vv AS
SELECT
m,
MIN(ts) min_ts,
MAX(ts) max_ts
FROM
v
WHERE
1=0
GROUP BY
m
/
CREATE OR REPLACE PACKAGE vv_utl
AS
TYPE tt_vv IS TABLE OF vv%ROWTYPE;
FUNCTION load
(
ifromts IN date,
itots IN date,
im IN varchar2 default null
) RETURN tt_vv PIPELINED;
END;
/
CREATE OR REPLACE PACKAGE BODY vv_utl
AS
FUNCTION load
(
ifromts IN date,
itots IN date,
im IN varchar2 default null
) RETURN tt_vv PIPELINED
AS
CURSOR vv_cur IS
SELECT
m,
MIN(ts) min_ts,
MAX(ts) max_ts
FROM
v
WHERE
ifromts <= ts AND
ts < itots AND
m = nvl(im,m)
GROUP BY
m;
BEGIN
FOR rec IN vv_cur LOOP
pipe row(rec);
END LOOP;
END;
END;
/
-- Test
SELECT
*
FROM
table(
vv_utl.load(
TO_DATE('2016-01-10','YYYY-MM-DD'),
TO_DATE('2016-01-20','YYYY-MM-DD')
)
)
/
我担心流水线 table 函数的性能(我在某处读到他们不缓存结果)并且因为我无法使我的函数具有确定性(一种让 Oracle 缓存结果的方法),我使用一个包来存储我的参数,以获得直接视图解决方案:
CREATE OR REPLACE PACKAGE vv_param
AS
PROCEDURE set
(
ifromts IN date,
itots IN date,
im IN varchar2 default null
);
FUNCTION get_fromts RETURN DATE;
FUNCTION get_tots RETURN DATE;
FUNCTION get_m RETURN VARCHAR2;
END;
/
CREATE OR REPLACE PACKAGE BODY vv_param
AS
lfromts date;
ltots date;
lm varchar2(50);
PROCEDURE set
(
ifromts IN date,
itots IN date,
im IN varchar2 default null
)
AS
BEGIN
lfromts := ifromts;
ltots := itots;
lm := im;
END;
FUNCTION get_fromts RETURN DATE AS BEGIN RETURN lfromts; END;
FUNCTION get_tots RETURN DATE AS BEGIN RETURN ltots; END;
FUNCTION get_m RETURN VARCHAR2 AS BEGIN RETURN lm; END;
END;
/
CREATE OR REPLACE VIEW vv AS
SELECT
m,
MIN(ts) min_ts,
MAX(ts) max_ts
FROM
v
WHERE
vv_param.get_fromts <= ts AND
ts < vv_param.get_tots AND
m = nvl(vv_param.get_m,m)
GROUP BY
m
/
BEGIN
vv_param.set(
TO_DATE('2016-01-10','YYYY-MM-DD'),
TO_DATE('2016-01-20','YYYY-MM-DD')
);
END;
/
-- Test
SELECT
*
FROM
vv
/
另外,当使用流水线 table 函数时,我看不到 EXPLAIN PLAN 的详细信息,因为 SQL 隐藏在包函数中。现在视图上的 EXPLAIN PLAN 正常工作。
这个例子显然很简单,但是在我的实际实现中有很多这样的视图,很多都依赖于其他视图。使用包变量解决方案允许我在所有这些视图中共享它们。是的,它并非没有陷阱,例如人们可能会忘记设置变量,如果您像 ODP.NET 那样重新使用会话,您可能会使用旧值并因此得到错误的结果。所以只有时间才能证明这是否是正确的方法!