Каков наилучший способ хранения (и доступа) исторических отношений 1: M в реляционной базе данных? - PullRequest
4 голосов
/ 14 апреля 2009

Гипотетический пример:

У меня есть автомобили и владельцы. Каждый Автомобиль в данный момент принадлежит одному (и только одному) Владельцу, но право собственности может быть передано. Владельцы могут в любое время иметь ноль или более автомобилей. Я хочу сохранить исторические отношения в базе данных MySQL, чтобы при произвольном времени я мог посмотреть текущее назначение автомобилей владельцам.

т.е. Во время X (где X может быть сейчас или в любое время в прошлом):

  • Кому принадлежит машина Y?
  • Каким автомобилям (если есть) принадлежит владелец Z?

Создание таблицы M: N в SQL (с отметкой времени) достаточно просто, но я бы хотел избежать коррелированного подзапроса, так как эта таблица станет большой (и, следовательно, снизится производительность). Есть идеи? У меня есть ощущение, что есть способ сделать это, СОЕДИНЯЯ такую ​​таблицу с самим собой, но я не очень разбираюсь в базах данных.

ОБНОВЛЕНИЕ: Я хотел бы избежать использования полей "start_date" и "end_date" для каждой строки, так как это потребовало бы (потенциально) дорогостоящего поиска при каждой вставке новой строки. (Кроме того, это избыточно).

Ответы [ 6 ]

9 голосов
/ 14 апреля 2009

Создайте третью таблицу под названием CarOwners с полями для carid, ownerid, start_date и end_date. Когда автомобиль куплен, заполните первые три и проверьте таблицу, чтобы убедиться, что никто не указан в качестве владельца. Если есть, обновите запись, указав эти данные в качестве end_date.

Чтобы найти текущего владельца:

select carid, ownerid from CarOwner where end_date is null

Чтобы найти владельца в определенный момент времени:

select carid, ownerid from CarOwner where start_date < getdate()
and end_date > getdate()

getdate () специфичен для MS SQL Server, но в каждой базе данных есть функция, которая возвращает текущую дату - просто подставьте.

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

select co.carid, co.ownerid, o.owner_name, c.make, c.Model, c.year  
from  CarOwner co
JOIN Car c on co.carid = c.carid
JOIN Owner o on o.ownerid = co.ownerid
where co.end_date is null
2 голосов
/ 14 апреля 2009

Я обнаружил, что лучший способ справиться с такого рода требованиями - просто вести журнал VehicleEvents, одним из которых будет ChangeOwner. На практике вы можете получить ответы на все поставленные здесь вопросы - по крайней мере так же точно, как вы собираете события.

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

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

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

Попытка сохранить историческое состояние для чего-то подобного другим способом, который я пробовал, приводит к безумию. (Может я все еще поправляюсь.: D)

Кстати, отличительной чертой здесь, вероятно, является то, что это временные ряды или журнал событий, а не 1: m.

1 голос
/ 14 апреля 2009

Почему бы не иметь таблицу транзакций? Который будет содержать ID автомобиля, владельца ОТ, владельца ТО и дату совершения транзакции.

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

Чтобы найти автомобили, принадлежащие Владельцу 253 1 марта:

ВЫБРАТЬ * ОТ transactions ГДЕ ownerToId = 253 И date> '2009-03-01'

0 голосов
/ 14 апреля 2009

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

SELECT * FROM  CarOwner co 
WHERE   co.CarId = @CarId 
      AND co.TransferDate <= @AsOfDate 
      AND NOT EXISTS (SELECT * FROM CarOwner co2 
                WHERE  co2.CarId = @CarId 
                  AND co2.TransferDate <= @AsOfDate 
                  AND co2.TransferDate > co.Transferdate)

или небольшое изменение

SELECT * FROM  Car ca 
    JOIN CarOwner co ON ca.Id = co.CarId 
  AND co.TransferDate = (SELECT MAX(TransferDate) 
                  FROM CarOwner WHERE CarId = @CarId 
                                             AND TransferDate < @AsOfDate)
WHERE co.CarId = @CarId 

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

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

0 голосов
/ 14 апреля 2009

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

(общий синтаксис sql 92:)

CREATE TABLE Cars
(
CarID integer not null default autoincrement,
OwnerID integer not null, 
CarDescription varchar(100) not null,
CreatedOn timestamp not null default current timestamp,
Primary key (CarID),
FOREIGN KEY (OwnerID ) REFERENCES Owners(OwnerID )

)

CREATE TABLE Owners
(
OwnerID integer not null default autoincrement,
OwnerName varchar(100) not null,
Primary key(OwnerID )
)

CREATE TABLE HistoricalCarOwners
(
CarID integer not null,
OwnerID integer not null,
OwnedFrom timestamp null,
Owneduntil timestamp null,
primary key (cardid, ownerid),
FOREIGN KEY (OwnerID ) REFERENCES Owners(OwnerID ),
FOREIGN KEY (CarID ) REFERENCES Cars(CarID )
)

Лично я бы не трогал третью таблицу из моего клиентского приложения, а просто позволил бы базе данных выполнить работу и сохранить целостность данных - с помощью триггеров ON UPDATE И ON DELETE на таблице Cars для заполнения таблицы HistoricalCarOwners всякий раз, когда автомобиль меняет владельцев (т. е. всякий раз, когда в столбце OwnerId фиксируется ОБНОВЛЕНИЕ) или автомобиль удаляется.

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

select ownerid, ownername from owners o inner join historicalcarowners hco 
                                        on hco.ownerid = o.ownerid
                                        where hco.carid = :arg_id and
                                              :arg_timestamp between ownedfrom and owneduntil
order by ...

HTH, Винс

0 голосов
/ 14 апреля 2009

таблица машин может иметь идентификатор под названием ownerID, тогда вы можете просто

1.Выберите автомобиль из автомобилей, которые принадлежат владельцам внутреннего соединения, на car.ownerid = owner.ownerid, где ownerid = y

2.Выберите автомобиль из машины, где владелец = z

Не точный синтаксис, а простой псевдокод.

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