Как эффективно выполнять запросы к базе данных? - PullRequest
3 голосов
/ 12 июня 2009

Извините за длинный вопрос!

У нас есть две таблицы базы данных, например, Автомобиль и Колесо. Они связаны с тем, что колесо принадлежит автомобилю, а автомобиль имеет несколько колес. Колеса, однако, можно менять, не влияя на «версию» автомобиля. Запись автомобиля может быть обновлена ​​(например, покраска), не влияя на версию колес (то есть без каскадного обновления).

Например, автомобильный стол в настоящее время выглядит так:

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), но он не улучшил наш запрос.

Пример запроса: Показать идентификатор автомобиля 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)

И, если вы хотите попробовать это, тогда создайте таблицу SQL и вставьте запись 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 ]

3 голосов
/ 12 июня 2009

Этот вид таблицы известен как таблица состояния действительного времени в литературе. Общепринято, что каждая строка должна моделировать период, имея дату начала и дату окончания. По сути, единицей работы в SQL является строка, а строка должна полностью определять сущность; имея только одну дату в строке, ваши запросы не только усложняются, но и ваш дизайн подвергается риску, разбивая податомные части на разные строки.

Как упомянул Эрвин Смут, одна из окончательных книг по этому вопросу:

Ричард Т. Снодграсс (1999). Разработка ориентированных на время приложений баз данных в SQL

Он распечатан, но, к счастью, доступен для бесплатного скачивания в формате PDF (ссылка выше).

Я действительно прочитал это и реализовал многие концепции. Большая часть текста содержится в стандарте ISO / ANSI SQL-92, и хотя некоторые из них были реализованы в проприетарных синтаксисах SQL, включая SQL Server (также доступен для загрузки), я нашел концептуальную информацию гораздо более полезной.

У Джо Селко также есть книга «Мышление в наборах: вспомогательные, временные и виртуальные таблицы в SQL», в основном взятая из работы Снодграсса, хотя я должен сказать, где эти два расходящихся я считаю подходами Снодграсса предпочтительнее.

Я согласен, что это сложно реализовать в продуктах SQL, которые у нас сейчас есть. Мы долго и усердно думаем, прежде чем делать данные временными; если нам удастся сойти с рук только «историческое», то мы это сделаем. Большая часть временной функциональности в SQL-92 отсутствует в SQL Server, например. ИНТЕРВАЛ, ПЕРЕКРЫТИЯ и т. Д. Некоторые вещи, такие как последовательные «первичные ключи», чтобы гарантировать, что периоды не перекрываются, не могут быть реализованы с использованием ограничений CHECK в SQL Server, что требует триггеров и / или пользовательских функций.

Книга Снодграсса основана на его работе над SQL3, предлагаемым расширением Standard SQL, чтобы обеспечить гораздо лучшую поддержку временных баз данных, хотя, к сожалению, это, кажется, было эффективно отложено много лет назад: (

1 голос
/ 12 июня 2009

Этот запрос вернет дубликаты, если у вас есть две строки с одинаковым точным временем версии для одного идентификатора автомобиля, но это вопрос определения того, что вы считаете «самым последним» в этой ситуации. У меня еще не было возможности проверить это, но я думаю, что это даст вам то, что вам нужно. По крайней мере, это довольно близко.

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
1 голос
/ 12 июня 2009

В зависимости от вашего приложения вы можете захотеть перенести управление версиями во вторичные таблицы аудита, которые будут иметь как дату начала, так и дату окончания, равную нулю. В OLTP с высоким трафиком я обнаружил, что использование подхода к управлению версиями может стать довольно дорогим, и если большинство ваших операций чтения используют последнюю версию, это может быть полезно.

Используя дату начала и окончания, вы можете запросить вспомогательные таблицы в поисках даты, которая находится между началом и окончанием или больше, чем начало.

1 голос
/ 12 июня 2009

Хранение времени окончания в таблице для каждой ситуации действительно облегчает выражение запросов, но создает проблему поддержания правил целостности, таких как «никакие две разные ситуации для одного автомобиля (колесо / ...) могут перекрываться» (все еще разумно выполнимо) и «не может быть пробелов во временных рядах отдельных ситуаций какого-либо отдельного (автомобиль / колесо / ...)» (больше проблем).

Отсутствие времени окончания в таблице для каждой ситуации вынуждает вас писать самостоятельные объединения каждый раз, когда вам нужно вызвать оператор Аллена (перекрытия, слияния, содержит, ...) для интервалов времени, подразумеваемых единственным временем колонка у вас есть.

SQL - это просто кошмар, если вам нужно делать такие временные вещи.

И, между прочим, даже просто точная формулировка этих запросов на естественном языке является кошмаром. Для иллюстрации: вы сказали, что вам нужны запросы «как есть», но в ваших примерах исключены ситуации «как» 10:05 (wheelVer 3) и 10:00 (черный цвет). И это несмотря на то, что эти ситуации определенно также «по состоянию на» 09: 50.

Вас может заинтересовать чтение "Временные данные и реляционная модель". Имейте в виду, что трактовка в этой книге полностью абстрактна, поскольку, как говорится в самой книге, «эта книга не о технологиях, доступных сегодня где-либо».

Другой стандартный учебник по этому предмету (как мне сказали) - это Снодграсс, но я не знаю название. Мне сказали, что авторы этих двух книг придерживаются совершенно противоположных позиций относительно того, каким должно быть решение.

1 голос
/ 12 июня 2009

Начиная с запросов легче, когда у каждой строки есть время начала и окончания. Хранение времени окончания в таблице будет наиболее эффективным, но если это сложно, вы можете запросить его следующим образом:

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)

Вы можете сохранить это в табличной хранимой процедуре, но это может привести к значительному снижению производительности.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...