Oracle:有效地使用where子句过滤时间戳列以获取特定日期的所有记录

Posted

技术标签:

【中文标题】Oracle:有效地使用where子句过滤时间戳列以获取特定日期的所有记录【英文标题】:Oracle: efficiently where clause to filter timestamp column to get all records of a specific day 【发布时间】:2020-03-09 12:38:24 【问题描述】:

我在 Oracle 中有一个按时间戳列划分的每月分区表。该表包含来自 2019 年历史数据的超过 10 亿行。现在我想过滤此表以获取特定日期的所有结果,关于 HH24:MI:SS 部分。

我面临的问题 (#1) 是,当使用 TO_CHAR(TIMESTAMPCOLUMN, 'YYYY-MM-DD') 时,我的查询的运行时间在最近几个月增加了。示例:

SELECT * FROM BIG_PART_TABLE WHERE TO_CHAR(TIMESTAMPCOLUMN, 'YYYY-MM-DD') = '2019-01-01' --  3 sec
SELECT * FROM BIG_PART_TABLE WHERE TO_CHAR(TIMESTAMPCOLUMN, 'YYYY-MM-DD') = '2019-02-01' --  6 sec
SELECT * FROM BIG_PART_TABLE WHERE TO_CHAR(TIMESTAMPCOLUMN, 'YYYY-MM-DD') = '2019-12-01' -- 36 sec

所以我摆脱了TO_CHAR 并开始像这样过滤:

SELECT * FROM BIG_PART_TABLE WHERE TIMESTAMPCOLUMN BETWEEN DATE '2019-01-01' AND DATE '2019-01-02'  -- 0.032 sec
SELECT * FROM BIG_PART_TABLE WHERE TIMESTAMPCOLUMN BETWEEN DATE '2019-12-01' AND DATE '2019-12-02'  -- 0.031 sec

问题 (#2) 是我懒得写 BETWEEN 子句,除了它增加了出错的机会。

最后,我真正想要的是一个高效的单个 where 子句来过滤我的表,例如:

SELECT * FROM BIG_PART_TABLE WHERE TIMESTAMPCOLUMN = DATE '2019-01-01'

谢谢大家。

【问题讨论】:

您需要在表中添加一个日期列并将其用于分区。 【参考方案1】:

正确的方法是不要在日期列上使用日期函数 - 使用这样的函数会使查询成为非 SARGable,这意味着它不能利用日期列上的索引。

没有语法糖可以使表达式更短。

我还建议使用半开间隔而不是between

WHERE 
    TIMESTAMPCOLUMN >= DATE '2019-01-01'
    AND TIMESTAMPCOLUMN < DATE '2019-01-02'

BETWEEN 包含两端,因此您的表达式暗示2019-01-02 00:00:00 上的时间戳将被过滤掉,而这很可能不是您想要的。

【讨论】:

我认为这不能回答 OP 的问题。两个比较似乎比between 更复杂。 非常注意 BETWEEN 关键字中的 2019-01-02 00:00:00 @GordonLinoff 您可以写成WHERE TIMESTAMPCOLUMN BETWEEN TIMESTAMP '2019-01-01 00:00:00' AND TIMESTAMP '2019-01-01 23:59:59.999999999',但根据 GMB 的回答,在上限不包含在内的情况下,仅使用日期比较可能会更容易。【参考方案2】:

使用 partition_extension_clause 语法:

SELECT *
  FROM BIG_PART_TABLE PARTITION FOR (DATE '2019-12-01')
 WHERE TRUNC(TIMESTAMPCOLUMN) = DATE '2019-12-01' ;

这段代码还是有点乱。但至少这种语法允许您使用相同的日期文字,而不必创建全新的日期表达式。虽然代码有重复,但重复有点自证:第一个表达式是使用分区修剪来找到最近的段,第二个表达式是得到确切的行。

【讨论】:

【参考方案3】:

为了使用分区,Oracle 必须识别分区键。如果它使用完整的时间戳,那么您可能会遇到问题。

它很有可能使用trunc(TIMESTAMPCOLUMN)trunc(TIMESTAMPCOLUMN, 'DD')。如果是这样,那么您可以使用它

WHERE TRUNC(TIMESTAMPCOLUMN) = DATE '2019-01-01' 

一旦你弄清楚了,你就可以在表中添加一个计算列,这样你就有了:

alter table big_part_table add column timestampcolumn_date as trunc(timestampcolumn);

那么你可以在WHERE子句中使用timestampcolumn_date

【讨论】:

感谢您的回答戈登。 TRUNC 函数导致与 TO_CHAR 函数相同的性能问题。 alter table 让我在单词 AS 中出现语法错误。除此之外,我认为您的想法是拥有一个仅日期列,然后使用该列将表拆分为分区是一个好主意,而且很简单。但是,我需要重写 ETL 的某些部分。 @DanielBonetti 。 . .关键是用于定义分区的方法是您可以在where 子句中安全使用的方法。【参考方案4】:

在 Orace 中访问数据的最快方法是使用分区名称。

就像这个例子:

 select * from BIG_PART_TABLE partition(ParititonName);

【讨论】:

以上是关于Oracle:有效地使用where子句过滤时间戳列以获取特定日期的所有记录的主要内容,如果未能解决你的问题,请参考以下文章

Oracle:在 Where 子句中使用 Case 语句

sql优化

Oracle Where(条件)子句用法

Oracle Where(条件)子句用法

SQLite 中 WHERE 子句中的聚合函数

Oracle数据库--过滤和排序