Как бы я отследил все изменения цены в дБ, чтобы получить цену на продукт «х» на дату «у»

8

Мне нужно будет отслеживать изменения цен на товары, чтобы я мог запросить в БД цену товара на определенную дату. Информация используется в системе, которая вычисляет прошлые аудиты, поэтому она должна возвращать правильную цену для правильного продукта на основе даты покупки.

Я бы предпочел использовать postgres при создании базы данных.

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

Гуннар Норред
источник
1
скопировать на запись в другую таблицу. Если таблица, pricesсоздайте таблицу prices_historyс похожими столбцами. Hibernate Envers может автоматизировать это для вас
Нил Макгиган

Ответы:

11

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

Бизнес правила

Давайте начнем анализировать ситуацию с концептуального уровня. Так что, если в вашей сфере бизнеса,

  • продукт приобретается на один-ко-многим Цены ,
  • каждая цена покупки становится текущей при точной дате начала , и
  • Цена EndDate (что указывает на даты , когда цена перестает быть ток ) равен StartDate из непосредственно следующей Цена ,

тогда это означает, что

  • нет никаких разрывов между отдельными периодами, в течение которых цены являются текущими (временной ряд является непрерывным или соединенным ), и
  • EndDate из Цены является выводимым ИГДОМ.

IDEF1X - схема показана на рисунке 1 , хотя и очень упрощена, изображает такой сценарий:

Рис. 1 - Цены на продукт Упрощенная диаграмма IDEF1X - Сценарий A

Описательная логическая схема

И следующая схема логического уровня SQL-DDL, основанная на упомянутой диаграмме IDEF1X, иллюстрирует выполнимый подход, который вы можете адаптировать к вашим собственным точным потребностям:

-- At the physical level, you should define a convenient 
-- indexing strategy based on the data manipulation tendencies
-- so that you can supply an optimal execution speed of the
-- queries declared at the logical level; thus, some testing 
-- sessions with considerable data load should be carried out.

CREATE TABLE Product (
    ProductNumber INT      NOT NULL,
    Etcetera      CHAR(30) NOT NULL,
    --
    CONSTRAINT Product_PK PRIMARY KEY (ProductNumber)
);

CREATE TABLE Price (
    ProductNumber INT  NOT NULL,
    StartDate     DATE NOT NULL,
    Amount        INT  NOT NULL, -- Retains the amount in cents, but there are other options regarding the type of use.
    --
    CONSTRAINT Price_PK            PRIMARY KEY (ProductNumber, StartDate),
    CONSTRAINT Price_to_Product_FK FOREIGN KEY (ProductNumber)
        REFERENCES Product (ProductNumber),
    CONSTRAINT AmountIsValid_CK    CHECK       (Amount >= 0)
);

PriceТаблица имеет составной первичный ключ, состоящие из двух колонок, т.е. ProductNumber(сдержанные, в свою очередь, в качестве внешнего ключа , который делает ссылку на Product.ProductNumber) и StartDate(указывая конкретную Дату , в которой определенный продукт был куплен в определенной Цене ) ,

В случае, если товары приобретаются на разных Цены в тот же день , вместо StartDateколонки, вы можете включать в себя один помеченный , как StartDateTimeчто держит Instant , когда данный продукт был куплен в точном цена . ПЕРВИЧНЫЙ КЛЮЧ должен быть объявлен как (ProductNumber, StartDateTime).

Как показано, вышеупомянутая таблица является обычной, потому что вы можете объявить операции SELECT, INSERT, UPDATE и DELETE, чтобы напрямую манипулировать ее данными, следовательно, она (a) позволяет избежать установки дополнительных компонентов и (b) может использоваться во всех основные платформы SQL с некоторыми изменениями, если это необходимо.

Образцы манипулирования данными

В качестве примера некоторые манипуляции операции , которые кажутся полезными, давайте скажем , что вы вставили следующие данные в Productи Priceтаблицы, соответственно:

INSERT INTO Product
    (ProductNumber, Etcetera)
VALUES
    (1750, 'Price time series sample'); 

INSERT INTO Price
    (ProductNumber, StartDate, Amount)
VALUES
    (1750, '20170601', 1000),
    (1750, '20170603', 3000),   
    (1750, '20170605', 4000),
    (1750, '20170607', 3000);

Так как Price.EndDateточка извлекаемых данных, то вы должны получить ее через точно производную таблицу, которая может быть создана как представление , чтобы создать «полный» временной ряд, как показано ниже:

CREATE VIEW PriceWithEndDate AS

    SELECT  P.ProductNumber,
            P.Etcetera AS ProductEtcetera,
           PR.Amount   AS PriceAmount,
           PR.StartDate,
           (
                SELECT MIN(StartDate)
                      FROM Price InnerPR
                     WHERE P.ProductNumber   = InnerPR.ProductNumber
                       AND InnerPR.StartDate > PR.StartDate
           ) AS EndDate
        FROM Product P
        JOIN Price   PR
          ON P.ProductNumber = PR.ProductNumber;

Затем следующая операция, которая выбирает непосредственно из этого представления

  SELECT ProductNumber,
         ProductEtcetera,
         PriceAmount,
         StartDate,
         EndDate
    FROM PriceWithEndDate 
ORDER BY StartDate DESC;

поставляет следующий набор результатов:

ProductNumber  ProductEtcetera     PriceAmount  StartDate   EndDate
-------------  ------------------  -----------  ----------  ----------
         1750  Price time series         4000  2017-06-07  NULL      -- (*) 
         1750  Price time series         3000  2017-06-05  2017-06-07
         1750  Price time series         2000  2017-06-03  2017-06-05
         1750  Price time series         1000  2017-06-01  2017-06-03

-- (*) A ‘sentinel’ value would be useful to avoid the NULL marks.

Теперь, давайте предположим , что вы заинтересованы в получении целые Priceданные для Productпрежде всего идентифицируются ProductNumber 1750 по Date 2 июня 2017 года . Видя, что Priceутверждение (или строка) является текущим или эффективным в течение всего интервала, который проходит от (i) его StartDateдо (ii) его EndDate, тогда эта операция DML

 SELECT ProductNumber,
        ProductEtcetera,
        PriceAmount,
        StartDate,
        EndDate
   FROM PriceWithEndDate
  WHERE ProductNumber = 1750        -- (1) 
    AND StartDate    <= '20170602'  -- (2)
    AND EndDate      >= '20170602'; -- (3)

-- (1), (2) and (3): You can supply parameters in place of fixed values to make the query more versatile.

дает следующий набор результатов

ProductNumber  ProductEtcetera     PriceAmount  StartDate   EndDate
-------------  ------------------  -----------  ----------  ----------
         1750  Price time series         1000  2017-06-01  2017-06-03

который обращается к указанному требованию.

Как показано, PriceWithEndDateпредставление играет первостепенную роль в получении большей части извлекаемых данных и может быть ВЫБРАНО ИЗ довольно обычным способом.

Учитывая, что вашей платформой предпочтений является PostgreSQL, этот контент с официального сайта документации содержит информацию о «материализованных» представлениях , которые могут помочь оптимизировать скорость выполнения с помощью механизмов физического уровня, если указанный аспект становится проблематичным. Другие системы управления базами данных SQL (СУБД) предлагают физические инструменты, которые очень похожи, хотя может применяться другая терминология, например, «индексированные» представления в Microsoft SQL Server.

Вы можете увидеть обсуждаемые примеры кода DDL и DML в действии в этой скрипте db <> и в этой скрипте SQL .

Связанные ресурсы

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

  • Эти сообщения о переполнении стека охватывают очень важные вопросы, касающиеся типа столбца, который содержит данные о валюте в PostgreSQL.

Ответы на комментарии

Это похоже на работу, которую я проделал, но я обнаружил, что гораздо удобнее / эффективнее работать с таблицей, в которой цена (в данном случае) имеет столбец startdate и столбец enddate - так что вы просто ищете строки с targettdate > = startdate и targettdate <= enddate. Конечно, если данные не хранятся с этими полями (включая конечную дату 31 декабря 9999 года, а не пустую, где нет фактической конечной даты), вам придется поработать над ее созданием. Я фактически заставлял это работать каждый день, с датой окончания = сегодняшняя дата по умолчанию. Кроме того, мое описание требует enddate 1 = startdate 2 минус 1 день. - @Robert Carnegie , 2017-06-22 20: 56: 01Z

Метод, который я предлагаю выше, обращается к бизнес-сфере с ранее описанными характеристиками , следовательно, применение вашего предложения об объявлении EndDateстолбца (который отличается от «поля») названной базовой таблицы Priceбудет означать, что логическая структура базы данных будет не отражать концептуальную схему правильно, и концептуальная схема должна быть определена и отражена с точностью, включая дифференцирование (1) базовой информации от (2) выводимой информации.

Кроме того, такой порядок действий привел бы к дублированию, поскольку EndDateзатем его можно было бы получить с помощью (a) производной таблицы, а также с помощью (b) названной базовой таблицы Priceс таким образом дублированным EndDateстолбцом. Хотя это и возможно, если практикующий врач решит следовать указанному подходу, он или она должны решительно предупредить пользователей базы данных о неудобствах и недостатках, которые с этим связаны. Одним из таких неудобств и неэффективностей является, например, настоятельная необходимость разработки механизма, который всегда обеспечивает , чтобы каждое Price.EndDateзначение равнялось значению Price.StartDateстолбца непосредственно следующего ряда для имеющегося Price.ProductNumberзначения.

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

Если аспект эффективности, о котором вы говорите, связан со скоростью выполнения некоторых операций с данными, то этим нужно управлять в соответствующем месте, то есть на физическом уровне, например, с помощью выгодной стратегии индексации, основанной на (1 ) конкретные тенденции запросов и (2) конкретные физические механизмы, предоставляемые СУБД использования. В противном случае, отказ от соответствующего концептуально-логического отображения и компрометация целостности данных легко превращает надежную систему (т. Е. Ценный организационный актив) в ненадежный ресурс.

Разрывные или несвязанные временные ряды

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

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

Я представил указанный сценарий на диаграмме IDEF1X, показанной на рисунке 2 .

Рис. 2 - Цены на продукт Упрощенная диаграмма IDEF1X - Сценарий B

В этом случае, да, гипотетическая Priceтаблица должна быть объявлена ​​способом, подобным этому:

CREATE TABLE Price (
    ProductNumber INT  NOT NULL,
    StartDate     DATE NOT NULL,
    EndDate       DATE NOT NULL,
    Amount        INT  NOT NULL,
    --
    CONSTRAINT Price_PK            PRIMARY KEY (ProductNumber, StartDate, EndDate),
    CONSTRAINT Price_to_Product_FK FOREIGN KEY (ProductNumber)
        REFERENCES Product (ProductNumber),
    CONSTRAINT DatesOrder_CK       CHECK       (EndDate >= StartDate)
);

И да, эта логическая структура DDL упрощает администрирование на физическом уровне, поскольку вы можете создать стратегию индексирования, которая охватывает EndDateстолбец (который, как показано, объявлен в базовой таблице) в относительно более простых конфигурациях.

Затем операция SELECT, как показано ниже

 SELECT  P.ProductNumber,
         P.Etcetera,
        PR.Amount,
        PR.StartDate,
        PR.EndDate
   FROM Price   PR
   JOIN Product P
  WHERE P.ProductNumber = 1750       
    AND StartDate      <= '20170602'  
    AND EndDate        >= '20170602';

могут быть использованы для получения целых Priceданных для Productглавным образом идентифицированы ProductNumber 1750 на Date 2 июня 2017 года .

MDCCL
источник
Это похоже на работу, которую я проделал, но я обнаружил, что гораздо удобнее / эффективнее работать с таблицей, в которой цена (в данном случае) имеет столбец startdate и столбец enddate - так что вы просто ищете строки с targettdate > = startdate и targettdate <= enddate. Конечно, если данные не хранятся с этими полями (включая конечную дату 31-го Trumptember 9999, а не Null, где нет фактической конечной даты), вам придется поработать над ее созданием. Я фактически заставлял это работать каждый день, с датой окончания = сегодняшняя дата по умолчанию. Кроме того, мое описание требует enddate 1 = startdate 2 минус 1 день.
Роберт Карнеги
4

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

Эта концепция также выглядит довольно независимой от БД, поскольку она предлагается на различных платформах RDBMS .

Джон Айсбренер
источник
1

Я дал здесь ответ, который относительно прост и не требует специальных расширений для базы данных (поэтому будет работать с любой базой данных).

TommCatt
источник