oracle 将参数化查询的结果插入CTE

tkqqtvp1  于 2023-03-22  发布在  Oracle
关注(0)|答案(3)|浏览(154)

我正在Oracle中查询一个包含数十亿行(BIG_TABLE)的表。该表按月更新(DTE是流量的月份)。DBA希望我们按流量的月份过滤查询(我不是在质疑这一点;实际上:(i)不这样做会导致性能急剧下降,以及(ii)DBA警告他可能会杀死未过滤的查询)。
因为我需要几个月(有时多达100),我使用参数化查询来传递我需要的月份(更多信息见下文)。我在中间表(INTERMEDIATE_TABLE,每月包含约2亿行)中收集所有月份的结果,然后聚合数据(FINAL_TABLE,用于分析)。无论流量的月份如何,总和必须由CHR完成。

-- query1
CREATE TABLE
  INTERMEDIATE_TABLE (
    CHR VARCHAR2(255),
    NBR NUMBER,
    DTE DATE
);

-- query2
INSERT INTO
  INTERMEDIATE_TABLE
SELECT
  CHR,
  NBR,
  DTE
FROM
  BIG_TABLE
WHERE
  DTE = TO_DATE(?, 'YYYY-MM-DD');

-- query3
CREATE TABLE
  FINAL_TABLE AS
SELECT
  CHR,
  SUM(NBR) AS NBR
FROM
  INTERMEDIATE_TABLE
GROUP BY
  CHR;

将查询保存为字符串(query1query2query3)后,我使用R的DBI运行查询。dbExecute()将日期逐个传递给query2中的相等比较,因此查询执行了3次(即列表不是一次传递的)。

library(DBI)
dbConnect(odbc::odbc(), ...)
dbExecute(con, query1)
dbExecute(con, query2, params = list(c("2020-01-01", "2020-02-01", "2020-03-01")))
dbExecute(con, query3)

我希望INTERMEDIATE_TABLE是临时的。我可以INSERT INTO CTE吗?
我对其他解决方案持开放态度,但我正在寻找ANSI解决方案,因为计划迁移到PostgreSQL。我想避免临时表,因为Oracle中的only the data is temporary,而不是表。当然,我可以在之后只DROP表,但首先使其临时似乎更优雅/明确(和有效?)。
一个可重复的例子:

CREATE TABLE
  BIG_TABLE (
    CHR VARCHAR2(255),
    NBR NUMBER,
    DTE DATE
);

INSERT ALL
  INTO BIG_TABLE VALUES ('A', 2, DATE '2020-01-01')
  INTO BIG_TABLE VALUES ('B', 3, DATE '2020-01-01')
  INTO BIG_TABLE VALUES ('A', 1, DATE '2020-02-01')
  INTO BIG_TABLE VALUES ('B', 2, DATE '2020-02-01')
  INTO BIG_TABLE VALUES ('A', 3, DATE '2020-02-01')
  INTO BIG_TABLE VALUES ('B', 2, DATE '2020-03-01')
  INTO BIG_TABLE VALUES ('B', 4, DATE '2020-03-01')
  INTO BIG_TABLE VALUES ('C', 1, DATE '2020-03-01')
  INTO BIG_TABLE VALUES ('B', 4, DATE '2020-04-01')
  INTO BIG_TABLE VALUES ('D', 2, DATE '2020-05-01')
SELECT 1 FROM DUAL;

预期输出:

CHR NBR
  A   6
  B  11
  C   1
5lwkijsr

5lwkijsr1#

您根本不需要任何类型的中间阶段,甚至不需要子查询。

CREATE TABLE FINAL_TABLE AS
SELECT CHR,
       SUM(NBR) NBR
  FROM BIG_TABLE
 WHERE DTE = ?
 GROUP BY CHR;

当然,对于10亿行,你需要并行性,这取决于表的配置方式。显式地请求它可能会有所帮助:

CREATE TABLE FINAL_TABLE PARALLEL (DEGREE 16) NOLOGGING  AS
SELECT CHR,
       SUM(NBR) NBR
  FROM BIG_TABLE
 WHERE DTE = ?
 GROUP BY CHR;

如果你需要多个月,你有几个选择。我建议一个单一的查询,以最大化分区的并行线程分布的方式,如果这是一个分区表:

CREATE TABLE final_table PARALLEL (DEGREE 16) NOLOGGING AS
SELECT chr,
       SUM(nbr) nbr
  FROM (SELECT chr,
               SUM(nbr) nbr
          FROM big_table
         WHERE dte = ?
         GROUP BY chr
        UNION ALL
        SELECT chr,
               SUM(nbr) nbr
          FROM big_table
         WHERE dte = ?
         GROUP BY chr
        UNION ALL
        SELECT chr,
               SUM(nbr) nbr
          FROM big_table
         WHERE dte = ?
         GROUP BY chr)
  GROUP BY chr

如果你需要拉取多个月,并让它们都在这个表中结束,而一次只拉取一个月,那么预先创建这个表并插入每个月:

CREATE TABLE final_table(dte date,chr varchar2(18),nbr integer); -- one time

INSERT /*+ append */ into final_table
SELECT /*+ parallel(16) */
       dte,
       chr,
       SUM(nbr) nbr
  FROM big_table
 WHERE dte = ?
 GROUP BY dte, 
          chr;

COMMIT;

-- repeat for every month.

然后在最后,如果你需要整个时间的聚合,你可以做一个group by来排除日期:

SELECT chr,
       SUM(nbr) nbr
  FROM final_table
 GROUP BY chr
1tu0hz3e

1tu0hz3e2#

您可以使用子查询来获取中间表并将其作为中间表使用,这样所有中间表都将在一个查询中运行。

CREATE TABLE
  FINAL_TABLE AS
SELECT
  CHR,
  SUM(NBR) NBR
FROM
  (SELECT
  CHR,
  NBR,
  DTE
FROM
  BIG_TABLE
WHERE
  DTE = TO_DATE(?, 'YYYY-MM-DD')) INTERMEDIATE_TABLE
GROUP BY
  CHR;

作为CTE

CREATE TABLE FINAL_TABLE 
AS 
WITH INTERMEDIATE_TABLE AS ( 
  SELECT
      CHR,
      NBR,
      DTE
    FROM
      BIG_TABLE
    WHERE
      DTE = TO_DATE(?, 'YYYY-MM-DD')
) 
    SELECT
      CHR,
      SUM(NBR) NBR
FROM INTERMEDIATE_TABLE
GROUP BY CHR;
bxgwgixi

bxgwgixi3#

考虑使用物化视图(MV)而不是临时表来执行此操作。这具有相同的效果-您正在预计算并将结果存储在表中-但具有更多的内置控件来帮助您刷新数据。
例如,创建以下MV:

create materialized view summaries as
  select chr, trunc ( dte, 'mm' ) mth, sum ( nbr ) tot
  from   big_table
  group  by chr, trunc ( dte, 'mm' );

这将存储一行/chr/month。除非每个月有数百万个'chr'值,否则这应该足以避免“按月过滤”的要求,因为每次阅读的数据将大大减少。
如果您将MV设置为fast refresh on commit,则数据库将自动使其保持最新。
即使你不得不使用“按月过滤”的方法,你仍然可以将其构建到MV中。
创建一个过滤表来存储所需的月份,在每次运行前插入值,并刷新MV:

create table filter_table (
  month_value date
);

create materialized view summaries as
  select chr, trunc ( dte, 'mm' ) mth, sum ( nbr ) tot
  from   big_table bt
  join   filter_table ft
  on     trunc ( dte, 'mm' ) = ft.month_value
  group  by chr, trunc ( dte, 'mm' );
  
insert into filter_table values ( date '2020-01-01' );
commit;

exec dbms_mview.refresh ( 'summaries' );

select * from summaries;

CHR    MTH                  TOT    
B      01-JAN-2020 00:00         3 
A      01-JAN-2020 00:00         2

相关问题