AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • 主页
  • 系统&网络
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • 主页
  • 系统&网络
    • 最新
    • 热门
    • 标签
  • Ubuntu
    • 最新
    • 热门
    • 标签
  • Unix
    • 最新
    • 标签
  • DBA
    • 最新
    • 标签
  • Computer
    • 最新
    • 标签
  • Coding
    • 最新
    • 标签
主页 / dba / 问题 / 30346
Accepted
sjk
sjk
Asked: 2012-12-14 02:02:43 +0800 CST2012-12-14 02:02:43 +0800 CST 2012-12-14 02:02:43 +0800 CST

使用多个日期列进行分区修剪

  • 772

我在 Oracle 11g 数据库中有一个大表,其中包含几年的历史数据,所以我想按年份对其进行分区。问题是该表有多个日期列并且它们都用于查询,所以我不能只选择一个日期列并将其用作分区键。

大多数时间日期彼此接近,因此我为每一年创建了分区,加上一个“溢出”分区,其中包含跨越年份边界的行。这是一个简化的示例:

create table t (
  start_year int,
  end_year int,
  partition_year int as (case when start_year=end_year then start_year else 0 end),
  data blob 
)
partition by range(partition_year) (
  partition poverflow values less than (1000),
  partition p2000 values less than (2001),
  partition p2001 values less than (2002),
  partition p2002 values less than (2003),
  partition p2003 values less than (2004),
  partition p2004 values less than (2005)
);

这种方法的问题是 partition_year 必须在查询中显式引用,否则分区修剪(非常可取,因为表很大)不会生效。该表用于多个用户的即席聚合查询;我不能指望他们都记得这个逻辑。

这可以通过视图来解决

create or replace view v as
select *
from t
where partition_year=start_year 
  and partition_year=end_year 
  and partition_year>1000
union all
select *
from t partition (poverflow);

现在像这样的查询

select * from v where start_year >= 2003 and end_year <= 2004;

使用正确的分区(下面计划中的 5-6 + 1):

---------------------------------------------------------------------------------------------------
| Id  | Operation                  | Name | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
---------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT           |      |     1 |  4030 |     2   (0)| 00:00:01 |       |       |
|   1 |  VIEW                      | V    |     1 |  4030 |     2   (0)| 00:00:01 |       |       |
|   2 |   UNION-ALL                |      |       |       |            |          |       |       |
|   3 |    PARTITION RANGE ITERATOR|      |     1 |  2041 |     2   (0)| 00:00:01 |     5 |     6 |
|*  4 |     TABLE ACCESS FULL      | T    |     1 |  2041 |     2   (0)| 00:00:01 |     5 |     6 |
|   5 |    PARTITION RANGE SINGLE  |      |     1 |  2041 |     2   (0)| 00:00:01 |     1 |     1 |
|*  6 |     TABLE ACCESS FULL      | T    |     1 |  2041 |     2   (0)| 00:00:01 |     1 |     1 |
---------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   4 - filter("START_YEAR">=2003 AND "END_YEAR"<=2004 AND "END_YEAR">=2003 AND 
              "START_YEAR"<=2004 AND "PARTITION_YEAR"<=2004 AND "PARTITION_YEAR"="START_YEAR" AND 
              "PARTITION_YEAR"="END_YEAR")
   6 - filter("START_YEAR">=2003 AND "END_YEAR"<=2004)

问题是,如果我用日期替换 int 类型,这将不再起作用。我试图从日期中提取年份组件并向视图添加相应的约束,但未修剪分区。迄今为止更改 partition_year 的类型也没有帮助。

有什么办法可以在一个表中有多个日期列并且仍然能够使用分区修剪?

oracle database-design
  • 3 3 个回答
  • 5688 Views

3 个回答

  • Voted
  1. Chris Saxon
    2012-12-14T04:39:13+08:002012-12-14T04:39:13+08:00

    当函数应用于分区列时,Oracle 无法进行分区修剪。从文档:

    有几种情况优化器无法执行修剪。一个常见的原因是在分区列的顶部使用运算符时。这可以是显式运算符(例如,函数),甚至可以是 Oracle 引入的隐式运算符,作为执行语句所需的数据类型转换的一部分。

    您的视图必须应用某种形式的函数来确定开始和结束日期是否是同一年,所以我相信您对这种方法不走运。

    我们对类似问题的解决方案是在基表上创建物化视图,在物化视图上指定不同的分区键。

    我们已经对其进行了定制以匹配常见的基本查询,以便我们也获得查询重写的好处。您可能需要让用户直接使用 MV,以确保您根据需要进行分区修剪,而不是依赖查询重写。

    (更新以删除不正确的示例并添加有关将函数应用于分区列的信息)

    • 1
  2. sjk
    2012-12-18T02:59:12+08:002012-12-18T02:59:12+08:00

    我已经用这些数据测试了 Chris 提供的解决方案:

    insert into t (start_year,end_year) values (date'2011-01-01',date'2011-01-01');
    insert into t (start_year,end_year) values (date'2011-01-01',date'2011-01-02');
    

    如果我对视图运行查询:

    select * from v;
    

    我只拿回第一行。这是因为视图有一个相等谓词,但分区定义有 extract(year) 函数。

    如果我修改视图以包含提取功能:

    create or replace view v as
    select *
    from t
    where extract(year from partition_year)=extract(year from start_year)
      and extract(year from partition_year)=extract(year from end_year)
      and partition_year>date'2000-01-01'
    union all
    select *
    from t partition (poverflow);
    

    我得到了正确的结果,但分区修剪不再发生。

    • 0
  3. Best Answer
    sjk
    2012-12-19T23:27:00+08:002012-12-19T23:27:00+08:00

    我找到了部分解决方案

    通过将视图定义为

    create or replace view v as
    select *
    from t
    where partition_date between start_date and end_date 
      and partition_date > date'1000-01-01'
    union all
    select *
    from t partition (poverflow);
    

    以下查询正常工作,仅访问分区 1,4 和 5

    select * from v where start_date >= date'2002-01-01' and end_date <= date'2003-01-01';
    

    但是,查询

    select * from v where start_date = date'2002-01-01';
    

    扫描分区 1,4-6,而不是 1 和 4(使用 end_date 代替将访问分区 1-4)。在我们的例子中,这不是一个严重的限制,因为典型的查询只访问最近几年,针对过去特定日期和日期范围的查询很少见。

    这种方法的一个稍微不同的版本是将 partition_date 列定义为

    case when trunc(start_date,'YEAR')=trunc(end_date,'YEAR') then greatest(start_date,end_date) 
    else to_date('01.01.0001') end
    

    并且认为

    create or replace view v as
    select *
    from t
    where partition_date >= start_date and partition_date >= end_date
      and partition_date > date'1000-01-01'
    union all
    select *
    from t partition (poverflow);
    

    这具有相似的性能,但 start_date 和 end_date 都会导致访问最近几年。如果像这样放宽要求(只允许修剪前几年),则实际上不再需要溢出分区,并且解决方案简化为:

    create table t (
      start_date date,
      end_date date,
      partition_date date as (greatest(start_date,end_date)),
      data blob
    )
    partition by range(partition_date) (
      partition p2000 values less than (date'2001-01-01'),
      partition p2001 values less than (date'2002-01-01'),
      partition p2002 values less than (date'2003-01-01'),
      partition p2003 values less than (date'2004-01-01'),
      partition p2004 values less than (date'2005-01-01')
    );
    
    create or replace view v as
    select *
    from t
    where partition_date >= start_date and partition_date >= end_date;
    
    • 0

相关问题

  • 在数据仓库中实现多对多关系有哪些方法?

  • ORDER BY 使用文本列的自定义优先级

  • 舒服的sqlplus界面?[关闭]

  • 如何在数据库中找到最新的 SQL 语句?

  • 如何使用正则表达式查询名称?

Sidebar

Stats

  • 问题 205573
  • 回答 270741
  • 最佳答案 135370
  • 用户 68524
  • 热门
  • 回答
  • Marko Smith

    如何查看 Oracle 中的数据库列表?

    • 8 个回答
  • Marko Smith

    mysql innodb_buffer_pool_size 应该有多大?

    • 4 个回答
  • Marko Smith

    列出指定表的所有列

    • 5 个回答
  • Marko Smith

    从 .frm 和 .ibd 文件恢复表?

    • 10 个回答
  • Marko Smith

    如何在不修改我自己的 tnsnames.ora 的情况下使用 sqlplus 连接到位于另一台主机上的 Oracle 数据库

    • 4 个回答
  • Marko Smith

    你如何mysqldump特定的表?

    • 4 个回答
  • Marko Smith

    如何选择每组的第一行?

    • 6 个回答
  • Marko Smith

    使用 psql 列出数据库权限

    • 10 个回答
  • Marko Smith

    如何从 PostgreSQL 中的选择查询中将值插入表中?

    • 4 个回答
  • Marko Smith

    如何使用 psql 列出所有数据库和表?

    • 7 个回答
  • Martin Hope
    Mike Walsh 为什么事务日志不断增长或空间不足? 2012-12-05 18:11:22 +0800 CST
  • Martin Hope
    Stephane Rolland 列出指定表的所有列 2012-08-14 04:44:44 +0800 CST
  • Martin Hope
    haxney MySQL 能否合理地对数十亿行执行查询? 2012-07-03 11:36:13 +0800 CST
  • Martin Hope
    qazwsx 如何监控大型 .sql 文件的导入进度? 2012-05-03 08:54:41 +0800 CST
  • Martin Hope
    markdorison 你如何mysqldump特定的表? 2011-12-17 12:39:37 +0800 CST
  • Martin Hope
    pedrosanta 使用 psql 列出数据库权限 2011-08-04 11:01:21 +0800 CST
  • Martin Hope
    Jonas 如何使用 psql 对 SQL 查询进行计时? 2011-06-04 02:22:54 +0800 CST
  • Martin Hope
    Jonas 如何从 PostgreSQL 中的选择查询中将值插入表中? 2011-05-28 00:33:05 +0800 CST
  • Martin Hope
    Jonas 如何使用 psql 列出所有数据库和表? 2011-02-18 00:45:49 +0800 CST
  • Martin Hope
    bernd_k 什么时候应该使用唯一约束而不是唯一索引? 2011-01-05 02:32:27 +0800 CST

热门标签

sql-server mysql postgresql sql-server-2014 sql-server-2016 oracle sql-server-2008 database-design query-performance sql-server-2017

Explore

  • 主页
  • 问题
    • 最新
    • 热门
  • 标签
  • 帮助

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve