2009-06-12 2 views
3

Извините длинный вопрос!Как эффективно делать запросы по базе данных?

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

Например, таблица автомобилей в настоящее время выглядит следующим образом:

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

В таблице Wheels выглядит следующим образом (этот автомобиль имеет только два колеса!)

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 находится здесь:

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 

ответ

3

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

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

Ричард Т. Снодграсс (1999). Developing Time-Oriented Database Applications in SQL

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

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

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

Я согласен, что этот материал трудно реализовать в продуктах SQL, которые мы имеем в настоящее время. Мы считаем, что долго и упорно, прежде чем принимать данные во время; если мы сможем уйти просто «историческим», тогда мы это сделаем. Большая часть временной функциональности в SQL-92 отсутствует в SQL Server, например. INTERVAL, OVERLAPS и т. Д. Некоторые вещи, фундаментальные, как секвенированные «первичные ключи» для обеспечения того, чтобы периоды не перекрывались, не могут быть реализованы с использованием ограничений CHECK в SQL Server, что требует триггеров и/или UDF.

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

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) 

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

+0

Ваш запрос более эффективен (меньше сканирования таблиц), но не выполняет запрос запроса. Ваш запрос получает только последнюю версию, а не версию с 09:50. Возможно, мы сможем взять некоторые идеи из вашего запроса, поэтому спасибо. – ng5000

+0

Мы не сможем использовать представления, поскольку нам нужно будет передать компонент времени запроса в запрос. SP могут быть опцией, но с необходимостью присоединения к другим таблицам, возможно, потребуется посмотреть на функции таблицы. – ng5000

+0

Отредактировано новым подходом к датам. – Andomar

1

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

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

1

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

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

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

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

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

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

1

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

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 
+0

Несколько незначительных изменений для унифицирования имен (например, car_id to CarId) и ваш запрос работает. – ng5000

Смежные вопросы