Sql 如何有效地执行数据库as查询?

Sql 如何有效地执行数据库as查询?,sql,database,database-design,Sql,Database,Database Design,请原谅这个冗长的问题 我们有两个数据库表,例如Car和Wheel。它们之间的联系在于一个轮子属于一辆汽车,而一辆汽车有多个轮子。但是,可以在不影响汽车“版本”的情况下更换车轮。可以在不影响车轮版本的情况下更新车辆记录(例如喷漆作业)(即无级联更新) 例如,Car表当前看起来如下所示: CarId, CarVer, VersionTime, Colour 1 1 9:00 Red 1 2 9:30 Blue 1

请原谅这个冗长的问题

我们有两个数据库表,例如Car和Wheel。它们之间的联系在于一个轮子属于一辆汽车,而一辆汽车有多个轮子。但是,可以在不影响汽车“版本”的情况下更换车轮。可以在不影响车轮版本的情况下更新车辆记录(例如喷漆作业)(即无级联更新)

例如,Car表当前看起来如下所示:

CarId, CarVer, VersionTime, Colour
   1      1       9:00       Red
   1      2       9:30       Blue
   1      3       9:45       Yellow
   1      4      10:00       Black
车轮表看起来像这样(这辆车只有两个车轮!)

这辆两轮车有4个版本。它的第一个车轮(车轮ID 1)没有改变。第二个车轮于10:05更换(如涂漆)

我如何高效地处理可以根据需要连接到其他表的查询?请注意,这是一个新的数据库,我们拥有该模式,可以对其进行更改或添加审计表,以简化查询。我们尝试了一种审计表方法(使用列:CarId、CarVersion、WheelId、WheelVersion、CarVerTime、WheelVerTime),但它并没有真正改善我们的查询

示例查询:按原样显示汽车ID 1,包括截至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)

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

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
选择
卡里德
,StartTime=ThisCar.VersionTime
,EndTime=NextCar.VersionTime
从汽车到这辆车
左接下一辆车
下一个car.CarId=ThisCar.CarId
而ThisCar.VersionTime
您可以将其存储在视图中。假设视图名为vwCars,则可以为特定日期选择一辆汽车,如:

select * 
from vwCars
where StartTime <= '2009-06-12 09:15' 
and ('2009-06-12 09:15' < EndTime or EndTime is null)
选择*
来自大众汽车

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


通过使用开始和结束日期,您可以查询辅助表,查找介于开始和停止之间或大于开始的日期。

在表中存储每种情况的结束时间确实使查询更易于表达,但会造成维护完整性规则的问题,例如“同一辆车(车轮/…)的两种不同情况不可能重叠”(仍然合理可行)和“任何一辆车(车轮/…)的不同情况的时间序列中不可能有漏洞”(更令人不安)

如果不在表中存储每种情况的结束时间,则每次需要调用Allen运算符(重叠、合并、包含等)时,都会强制您在您仅有的时间列所暗示的时间间隔上写入自联接

如果你需要做这种时态的事情,SQL只是一场噩梦

顺便说一句,即使只是用自然语言精确地表达这些查询也是一场噩梦。举例来说:你说你需要“截至”查询,但你的例子排除了“截至”10:05(wheelVer 3)和10:00(黑色)的情况。尽管这些情况肯定也是“截至”“09:50

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


另一本关于这个主题的标准教科书(我听说)是斯诺德格拉斯的,但我不知道书名。有人告诉我,这两本书的作者对解决方案应该是什么持完全相反的立场。

这种表在文献中被称为有效的时间状态表。人们普遍认为,每一行都应该通过一个开始日期和一个结束日期来模拟一个期间。基本上,SQL中的工作单元是行,行应该完全定义实体;由于每行只有一个日期,不仅查询变得更加复杂,而且将子原子部分拆分到不同的行也会影响设计

正如埃尔文·斯穆特所提到的,关于这一主题的权威著作之一是:

Richard T.Snodgrass(1999年)

这本书已经绝版,但很高兴可以免费下载PDF(上面的链接)

我已经读过了,并且实现了很多概念。本文的大部分内容都是ISO/ANSI标准SQL-92,尽管其中一些已经用专有的SQL语法实现,包括SQL Server(也可以下载),但我发现概念信息更有用

乔·塞尔科(Joe Celko)还有一本书《在集合中思考:SQL中的辅助表、时态表和虚拟表》(Thinking in Sets:Auxiliary,Temporal,and Virtual Tables in SQL),这本书很大程度上源于斯诺德格拉斯的工作,尽管我不得不说我认为斯诺德格拉斯的方法更可取

我同意这种东西很难在我们目前的SQL产品中实现。在使数据时态化之前,我们要经过漫长而艰苦的思考;如果我们能摆脱仅仅是“历史”的束缚,那么我们会的。SQL Server中缺少SQL-92中的许多时态功能,例如间隔、重叠等。SQL Server中无法使用检查约束实现一些基本功能,如确保周期不重叠的顺序“主键”,这就需要触发器和/或UDF


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

如果您有两行带有
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
select * 
from vwCars
where StartTime <= '2009-06-12 09:15' 
and ('2009-06-12 09:15' < EndTime or EndTime is null)
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