如何有效地进行数据库查询?

时间:2009-06-12 11:57:36

标签: sql database database-design

请原谅这个冗长的问题!

我们有两个数据库表,例如汽车和车轮。它们的关系在于车轮属于汽车而汽车有多个车轮。然而,车轮可以在不影响汽车“版本”的情况下进行更换。汽车的记录可以更新(例如油漆作业),而不会影响车轮的版本(即没有级联更新)。

例如,Car table目前看起来像这样:

CarId, CarVer, VersionTime, Colour
   1      1       9:00       Red
   1      2       9:30       Blue
   1      3       9:45       Yellow
   1      4      10:00       Black

车轮表看起来像这样(这辆车只有两个车轮!)

WheelId, WheelVer, VersionTime, CarId
   1         1           9:00     1
   1         2           9:40     1
   1         3          10:05     1
   2         1           9:00     1

所以,这辆两轮车有4个版本。它的第一个轮子(WheelId 1)没有改变。第二个轮子在10:05被更换(例如涂漆)。

如何有效地执行可根据需要加入其他表的查询?请注意,这是一个新数据库,我们拥有该模式,可以更改它或添加审计表以使此查询更容易。我们尝试过一种审计表方法(包括列:CarId,CarVersion,WheelId,WheelVersion,CarVerTime,WheelVerTime),但它并没有真正改进我们的查询。

示例查询:按原样显示车牌ID,包括截至9:50的车轮记录。此查询应导致返回这两行:

WheelId, WheelVer, WheelVerTime, CarId, CarVer, CarVerTime, CarColour
   1         2         9:40        1       3       9:45      Yellow
   2         1         9:00        1       3       9:45      Yellow

我们可以提出的最佳查询是:

select c.CarId, c.VersionTime, w.WheelId,w.WheelVer,w.VersionTime,w.CarId
from Cars c, 
(    select w.WheelId,w.WheelVer,w.VersionTime,w.CarId
    from Wheels w
    where w.VersionTime <= "12 Jun 2009 09:50" 
     group by w.WheelId,w.CarId
     having w.WheelVer = max(w.WheelVer)
) w
where c.CarId = w.CarId
and c.CarId = 1
and c.VersionTime <= "12 Jun 2009 09:50" 
group by c.CarId, w.WheelId,w.WheelVer,w.VersionTime,w.CarId
having c.CarVer = max(c.CarVer)

并且,如果你想尝试这个,那么创建表和插入记录SQL就在这里:

create table Wheels
(
WheelId int not null,
WheelVer int not null,
VersionTime datetime not null,
CarId int not null,
 PRIMARY KEY  (WheelId,WheelVer)
)
go

insert into Wheels values (1,1,'12 Jun 2009 09:00', 1)
go
insert into Wheels values (1,2,'12 Jun 2009 09:40', 1)
go
insert into Wheels values (1,3,'12 Jun 2009 10:05', 1)
go
insert into Wheels values (2,1,'12 Jun 2009 09:00', 1)
go


create table Cars
(
CarId int not null,
CarVer int not null,
VersionTime datetime not null,
colour varchar(50) not null,
 PRIMARY KEY  (CarId,CarVer)
)
go

insert into Cars values (1,1,'12 Jun 2009 09:00', 'Red')
go
insert into Cars values (1,2,'12 Jun 2009 09:30',  'Blue')
go
insert into Cars values (1,3,'12 Jun 2009 09:45',  'Yellow')
go
insert into Cars values (1,4,'12 Jun 2009 10:00',  'Black')
go

5 个答案:

答案 0 :(得分:3)

这种表在文献中称为有效时状态表。普遍接受的是,每一行应通过具有开始日期和结束日期来建模期间。基本上,SQL中的工作单元是行,一行应该完全定义实体;通过每行只有一个日期,不仅使您的查询变得更加复杂,而且通过将子原子部分拆分到不同的行来破坏您的设计。

如Erwin Smout所述,有关该主题的权威书籍之一是:

Richard T. Snodgrass(1999)。 Developing Time-Oriented Database Applications in SQL

它已绝版,但很高兴可以免费下载PDF(上面的链接)。

我实际上已经阅读过并实现了许多概念。大部分文本都在ISO / ANSI标准SQL-92中,虽然有些已经用专有的SQL语法实现,包括SQL Server(也可用作下载),但我发现概念信息更有用。

Joe Celko还有一本书,“在集合中思考:SQL中的辅助,时间和虚拟表”,主要源于Snodgrass的工作,但我不得不说两个分歧在哪里我发现Snodgrass的方法更可取。

我同意这些东西很难在我们目前拥有的SQL产品中实现。在使数据具有时间性之前,我们认真思考;如果我们能够摆脱“历史”,那么我们就会。 SQL Server中缺少SQL-92中的大部分时间功能,例如INTERVAL,OVERLAPS等等。确保句点不重叠的有序“主键”这些基本内容无法使用SQL Server中的CHECK约束实现,因此需要触发器和/或UDF。

Snodgrass的书基于他对SQL3的工作,SQL3是对标准SQL的一种建议扩展,为时态数据库提供了更好的支持,但遗憾的是,这似乎在几年前就被有效搁置了:(

答案 1 :(得分:1)

当每行有开始和结束时间时,查询更容易。将结束时间存储在表中将是最有效的,但如果这很难,您可以像下面这样查询:

select 
    ThisCar.CarId
,   StartTime = ThisCar.VersionTime
,   EndTime = NextCar.VersionTime
from Cars ThisCar
left join Cars NextCar
    on NextCar.CarId = ThisCar.CarId
    and ThisCar.VersionTime < NextCar.VersionTime
left join Cars BetweenCar
    on BetweenCar.CarId = BetweenCar.CarId
    and ThisCar.VersionTime < BetweenCar.VersionTime
    and BetweenCar.VersionTime < NextCar.VersionTime
where BetweenCar.CarId is null

您可以将其存储在视图中。假设该视图称为vwCars,您可以选择特定日期的汽车,如:

select * 
from vwCars
where StartTime <= '2009-06-12 09:15' 
and ('2009-06-12 09:15' < EndTime or EndTime is null)

您可以将其存储在表值存储过程中,但这可能会导致严重的性能损失。

答案 2 :(得分:1)

根据您的应用程序,您可能希望将版本控制推送到辅助审核表,这将具有开始和可以为空的结束日期。我发现在一个高流量的OLTP中,使用版本控制方法会变得相当昂贵,如果你的大多数读取都是最新版本,那么这可能是有益的。

通过使用开始日期和结束日期,您可以查询辅助表格,查找开始和结束之间或更长时间的日期,然后开始。

答案 3 :(得分:1)

在每种情况下将结束时间存储在表中会使查询更容易表达,但会产生维护完整性规则的问题,例如“同一辆车(轮子/ ......)没有两个不同的情况可能会重叠” (仍然合理地可行)和“任何单一(汽车/轮子......)的不同情况的时间序列中都不会出现漏洞”(更麻烦)。

对于每种情况,不在表中存储结束时间会强制您在每次需要在唯一时间隐含的时间间隔内调用Allen运算符(重叠,合并,包含...)时编写自联接你有专栏。

如果你需要做这种时间的东西,SQL只是一场噩梦。

顺便说一下,即使用自然语言准确地制定这些查询也是一场噩梦。为了说明:你说你需要“as-of”查询,但你的例子排除了“as-of”10:05(wheelVer 3)和10:00(黑色)的情况。尽管事实上这些情况肯定也是“as”09:50。

您可能对阅读“时间数据和关系模型”感兴趣。请记住,本书中的处理完全是抽象的,因为正如本书所说,“这本书不是关于今天任何地方都可用的技术”。

关于这个问题的另一本标准教科书(我被告知)是由斯诺德格拉斯撰写的,但我不知道这个标题。我被告知这两本书的作者对解决方案应该采取完全相反的立场。

答案 4 :(得分:1)

如果您有两行与单个车辆ID具有相同的确切版本时间,则此查询将返回重复项,但这是定义您认为在该情况下的“最新”行的问题。我还没有机会测试这个,但我认为它会给你你需要的东西。它至少非常接近。

SELECT
     C.car_id,
     C.car_version,
     C.colour,
     C.version_time AS car_version_time,
     W.wheel_id,
     W.wheel_version,
     W.version_time AS wheel_version_time,
FROM
     Cars C
LEFT OUTER JOIN Cars C2 ON
     C2.car_id = C.car_id AND
     C2.version_time <= @as_of_time AND
     C2.version_time > C.version_time
LEFT OUTER JOIN Wheels W ON
     W.car_id = C.car_id AND
     W.version_time <= @as_of_time
LEFT OUTER JOIN Wheels W2 ON
     W2.car_id = C.car_id AND
     W2.wheel_id = W.wheel_id AND
     W2.version_time <= @as_of_time AND
     W2.version_time > W.version_time
WHERE
     C.version_time <= @as_of_time AND
     C2.car_id IS NULL AND
     W2.wheel_id IS NULL