虎虎漫画小说

繁体版 简体版
虎虎漫画小说 > > SQL语言艺术最新章节 > 第 13 章

第 13 章 免费阅读

先必须找到这些值本身(此为第一遍扫描,需比较记录),

接下来的用这些值作为第二遍扫描的搜索条件。而以滑动窗口(sliding window)为基础的OLAP

函数,可以将两遍扫描合而为一(至少表面上如此)。基于时间戳或日期的数据查询,非常特殊

也非常重要,本章在稍后的“基于日期的简单搜索或范围搜索”中专门讨论。

总结:当多个选取条件用于同一个表的不同记录时,可以使用基于滑动窗口工作的函数。

基于日期的简单搜索或范围搜索

Simple or Range Searching on Dates

搜索条件有多种,其中日期(和时间)占有特殊地位。日期极为常见,而且比其他数据类型更

可能成为范围搜索的条件,范围搜索可以是有界的(如“在某两天之间”),也可以是部分有界

(“在某天之前”)。通常,为了获得这种结果集,查询需要使用当前日期(如“前六个月”)。

上一节“通过聚合获得结果集”所举的例子,用到了sales_history 表。当时,条件位于amount 上,

其实对于sales_history这种表更常见的是日期条件,尤其是读取特定日期的数据、或读取两个日

期之间的数据。在保存历史数据的表中查找特定日期(或其对应值)时,必须特别注意确定当

前日期的方法,它可能成为聚合条件的基础。

第1章已指出,设计保存历史数据的表颇为困难,而且没有现成的简单解决方案。无论你对当前

数据、还是历史数据感兴趣,设计历史数据的存储方案都要根据如何使用数据决定,同时还要

看数据多快会过时。例如,零售系统中价格的变动速度比较慢(除非正在经受严重的通货膨胀),

而网络流量或财务设备的价格改变速度比较快,甚至快很多。

从宏观角度来看,关键是各项历史数据的数量:是“少量数据项、大量历史数据”,还是“大量数

据项、少量历史数据”,或是介于两者之间?其重点是:数据项的可选择xìng取决于数据项的总数、

取样频率(“每天一次”还是“每次改变时”)、时间长短(“永久”还是“一年”等)。因此,本节将首

先讨论“大量数据项、少量历史数据”的情况,接着讨论“少量数据项、大量历史数据”的情况,

最后讨论当前值问题。

大量数据项、少量历史数据

Many Items, Few Historical Values

既然没有为每个数据项保留大量历史数据,那么各项的ID可选择xìng很高。说明要查询哪些项,

限定参与查询的少数历史记录,就可确定特定日期(当前日期或以前日期)对应的值。这种情

况需要我们再次处理聚合值(aggregate value)。

除非建立了代理键(本情况不需要代理键),否则主键通常是复合键,由item_id和record_date组

成。为了查询特定日期的值,可采用两种方法:子查询和OLAP 函数。

使用子查询

查找某数据项在特定日期的值相对简单,但实际上,这种简单只是假象。通常你会遇到这样的

代码:

select whatever

from hist_data as outer

where outer.item_id = scomvalue

and outer.record_date = (select max(inner.record_date)

from hist_data as inner

where inner.item_id = outer.item_id

and inner.record_date <= reference_date)

考察这个查询的执行路径,我们发现:首先,内层查询与外层查询是有关联的(correlated), 因

为内层查询参照了item_id的值,该值是由外层查询返回的当前记录一个字段。下面,先来分析

外层查询。

理论上,复合键中的字段顺序不会有太大影响,但实际上它们非常重要。如果我们误把主键定

义为(record_date, item_id),而不是(item_id, record_date),前例的内层查询就非常依赖item_id

字段的索引,否则无法高效地向下访问树状结构索引。但我们知道,额外增加一个索引的代价

很高。

外层查询找到了保存item_id 历史的各条记录,接着使用当前item_id 值逐次执行子查询。注

意,内层查询只依赖item_id,这与外层查询处理的记录相同,这意味着我们执行相同的查询、

返回相同的结果。优化器会注意到查询总是返回相同的值吗?无法确定。所以最好不要冒这个

险。

在使用关联子查询时,如果它处理不同的记录后总是返回相同的值,就没有意义了。所以,应

该改用无关联子查询:

select whatever

from hist_data as outer

where outer.item_id = scomvalue

and outer.record_date = (select max(inner.record_date)

from hist_data as inner

where inner.item_id = scomvalue

and inner.record_date <= reference_date)

现在子查询的执行不需要访问表,只需访问主键索引就够了。

个人习惯各有不同,但如果DBMS支持将“子查询的输出”与多个字段进行比较(这个特xìng不是

所有产品都支持的),则应优先考虑基于主键比较:

select whatever

from hist_data as outer

where (outer.item_id, outer.record_date) in

(select inner.item_id, max(inner.record_date)

from hist_data as inner

where inner.item_id = scomvalue

and inner.record_date <= reference_date

group by inner.item_id)

让子查询返回的字段,完全与复合主键的字段相符,有一定道理。如果必须返回“数据项值的列

表”(例如是另一个查询的结果),则上述查询语句建议的执行路径非常合适。

只要每个数据项的历史信息数量都较少,以in() 列表或子查询取代内层查询中的scomvalue,

会使整个查询执行更高效。也可以用in 子句取代“相等xìng条件”,在多数情况下没有什么不同;

但偶有例外,例如,如果用户输错了item_id,采用in()时会返回未发现数据,而采用“相等xìng条

件”时会返回错误数据。

使用OLAP函数

我们在自连接(self-join)情况下,使用了诸如row_number()等OLAP函数,它们在查询“特定日

期某数据项的值”时也同样有用甚至高效。(但记住,OLAP函数会带来非关系的处理模式(注5)。)

注意

OLAP 函数属于SQL 的非关系层。这类函数的作用是:在查询中做最后(或几乎是最后)处

理。因为它们在过滤已完成后对结果集进行处理。

运用row_number()等函数,可以通过日期排序判断数据的“新旧程度(degree of freshness)”(也

就是距离现在有多久):

select row_number( ) over (partition by item_id

order by record_date desc) as freshness,

whatever

from hist_data

where item_id = scomvalue

and record_date <= reference_date

选取最新数据,只需保留freshness 值为1 的记录:

select x.<suitable_columns>

from (select row_number( ) over (partition by item_id

order by record_date desc) as freshness,

whatever

from hist_data

where item_id = scomvalue

and record_date <= reference_date) as x

where x.freshness = 1

理论上,使用OLAP 函数方法和子查询几乎没有差异。实际上,OLAP 函数只访问一次表,

即使需要为此而进行排序cāo作也不例外。OLAP函数对表不需要做额外的访问,甚至在使用主键

快速存取时也是如此。因此,采用OLAP 函数速度会比较快(尽管只是快一点点)。

少量数据项、大量历史数据

Many Historical Values Per Item

当存在大量历史数据时,情况有所不同—— 例如,监控系统中采集“度量值”的频率很高。这

里的困难在于,必须根据对极大量的数据进行排序,才能找到特定日期或最接近特定日期的值。

排序是代价很高的cāo作:如果我们应用第4章的原则,降低非关系层厚度的唯一方法,就是在关

系层多做一些工作,增加过滤条件的数量。此时,针对所需数据更精确地归类日期(或时间)

以缩小范围,便非常重要。如果我们只提供上限,就必须扫描并排序所有历史数据。所以如果

数据的采集频率很高,提供下限是有必要的。如果我们成功地把记录的“工作集”控制在可管理

的大小,就相当于回到了“少量历史记录”的情况。如果无法同时指定上限(例如当前日期)和

下限,我们的唯一希望就是根据数据项分区;我们只需在单一分区上cāo作,这比较接近“大结果

集”的情况。

结果集和别的数据存在与否有关

Result Set Predicated on Absence of Data

一个表中的哪些记录和另一个表中的数据不匹配?这种“识别例外”的需求经常出现。人们最常

想到的解决方案有两个:not in ()搭配非关联子查询,或者not exists()

搭配关联子查询。一般认为应该使用not exists。在子查询出现在高效搜索条件之后,使用not

『加入书签,方便阅读』