Разрывы и острова: расщепление островов на основе внешней таблицы - PullRequest
6 голосов
/ 02 мая 2019

Мой сценарий начинался аналогично проблеме островов и пробелов, где мне нужно было находить последовательные дни работы.Мой текущий запрос SQL отвечает: «ProductA был произведен в LocationA от DateA до DateB, всего X».

Однако этого недостаточно, когда мне нужно было бросить цены в смесь.Цены указаны в отдельной таблице и обрабатываются в C # после факта.Изменения цены - это, по сути, список записей, в которых говорится, что «ProductA из LocationA теперь представляет собой значение Y на единицу действующей датыC».

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

Код C # может эффективно обрабатывать применение цен, хотя мне нужно сделать это, хотя разделить острова на основе изменений цен,Моя цель - сделать так, чтобы при разборе SQL учитывалось ранжирование дней из другой таблицы, но у меня возникают проблемы с применением того, что я хочу сделать.


Текущий SQL, который генерирует мой остров, -следующим образом

SELECT MIN(ScheduledDate) as StartDate, MAX(ScheduledDate) as 
EndDate, ProductId, DestinationId, SUM(Quantity) as TotalQuantity
FROM (
    SELECT ScheduledDate, DestinationId, ProductId, PartitionGroup = DATEADD(DAY ,-1 * DENSE_RANK() OVER (ORDER BY ScheduledDate), ScheduledDate), Quantity
    FROM History
) tmp
GROUP BY PartitionGroup, DestinationId, ProductId;

Текущий SQL, который берет из таблицы PriceChange и ранжирует даты, выглядит следующим образом

DECLARE @PriceChangeDates TABLE(Rank int, SplitDate Date);
INSERT INTO @PriceChangeDates
SELECT DENSE_RANK() over (ORDER BY EffectiveDate) as Rank, EffectiveDate as SplitDate
FROM ProductPriceChange
GROUP BY EffectiveDate;

Моя мысль состоит в том, чтобы как-то обновить первые запросы внутри SELECTзаявление, чтобы каким-то образом воспользоваться таблицей @PriceChangeDates, созданной вторым запросом.Я бы подумал, что мы можем умножить параметр приращения DATEADD на ранг объявленной таблицы, но я изо всех сил пытаюсь записать его.

Если бы я как-то сделал это с помощью циклов, мой мыслительный процесс состоял бы в определении того, какойранг ScheduledDate будет из таблицы @PriceChangeDates, где его ранг - это ранг ближайшей даты, которая меньше, чем она сама может найти.Затем возьмите тот ранг, который он дает, и, я думаю, умножьте его на передаваемый параметр инкремента (или какую-то математическую функцию, например, сделайте *@PriceChangeDates.Count () для существующего параметра, а затем добавьте новый ранг, чтобы избежатьстолкновения).Тем не менее, это логика «цикла», а не логика «набора», и в SQL мне нужно думать в наборах.


Любая помощь и совет очень важны.Спасибо:)


ОБНОВЛЕНИЕ:

Пример данных и пример на SQLFiddle: http://www.sqlfiddle.com/#!18/af568/1

Где данные:

CREATE TABLE History
(
ProductId int,
DestinationId int,
ScheduledDate date,
Quantity float
);

INSERT INTO History (ProductId, DestinationId, ScheduledDate, Quantity)
VALUES
  (0, 1000, '20180401', 5),
  (0, 1000, '20180402', 10),
  (0, 1000, '20180403', 7),
  (3, 5000, '20180507', 15),
  (3, 5000, '20180508', 23),
  (3, 5000, '20180509', 52),
  (3, 5000, '20180510', 12),
  (3, 5000, '20180511', 14);

CREATE TABLE PriceChange
(
  ProductId int,
  DestinationId int,
  EffectiveDate date,
  Price float
);

INSERT INTO PriceChange (ProductId, DestinationId, EffectiveDate, Price)
VALUES
  (0, 1000, '20180201', 1),
  (0, 1000, '20180402', 2),
  (3, 5000, '20180101', 5),
  (3, 5000, '20180510', 20);

Желаемые результаты должны иметь оператор SQL, который генерирует результат:

StartDate   EndDate     ProductId   DestinationId   TotalQuantity
2018-04-01  2018-04-01  0           1000            5
2018-04-02  2018-04-03  0           1000            17
2018-05-07  2018-05-09  3           5000            90
2018-05-10  2018-05-11  3           5000            26

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

Ответы [ 4 ]

3 голосов
/ 11 мая 2019

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

В первом варианте я использовал APPLY, чтобы выбрать соответствующую цену для каждой строки в таблице History. Для каждой строки из таблицы History механизм ищет соответствующую строку из таблицы PriceChange. Даже с соответствующим индексом в таблице PriceChange, когда это делается с помощью одного поиска, это все равно означает 3,7 миллиона запросов в соединении цикла.

Мы можем просто объединить таблицы History и PriceChange вместе, и с соответствующими индексами на обеих таблицах это будет эффективное объединение слиянием.

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

INSERT INTO History (ProductId, DestinationId, ScheduledDate, Quantity)
VALUES
  (0, 1000, '20180601', 5),
  (0, 1000, '20180602', 10),
  (0, 1000, '20180603', 7),
  (3, 5000, '20180607', 15),
  (3, 5000, '20180608', 23),
  (3, 5000, '20180609', 52),
  (3, 5000, '20180610', 12),
  (3, 5000, '20180611', 14);

Промежуточный запрос

Здесь мы делаем FULL JOIN, а не LEFT JOIN, потому что вполне возможно, что дата изменения цены вообще не появится в таблице History.

WITH
CTE_Join
AS
(
    SELECT
        ISNULL(History.ProductId, PriceChange.ProductID) AS ProductID
        ,ISNULL(History.DestinationId, PriceChange.DestinationId) AS DestinationId
        ,ISNULL(History.ScheduledDate, PriceChange.EffectiveDate) AS ScheduledDate
        ,History.Quantity
        ,PriceChange.Price
    FROM
        History
        FULL JOIN PriceChange
            ON  PriceChange.ProductID = History.ProductID
            AND PriceChange.DestinationId = History.DestinationId
            AND PriceChange.EffectiveDate = History.ScheduledDate
)
,CTE2
AS
(
    SELECT
        ProductID
        ,DestinationId
        ,ScheduledDate
        ,Quantity
        ,Price
        ,MAX(CASE WHEN Price IS NOT NULL THEN ScheduledDate END)
            OVER (PARTITION BY ProductID, DestinationId ORDER BY ScheduledDate 
            ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS grp
    FROM CTE_Join
)
SELECT *
FROM CTE2
ORDER BY
    ProductID
    ,DestinationId
    ,ScheduledDate

Создать следующие индексы

CREATE UNIQUE NONCLUSTERED INDEX [IX_History] ON [dbo].[History]
(
    [ProductId] ASC,
    [DestinationId] ASC,
    [ScheduledDate] ASC
)
INCLUDE ([Quantity])

CREATE UNIQUE NONCLUSTERED INDEX [IX_Price] ON [dbo].[PriceChange]
(
    [ProductId] ASC,
    [DestinationId] ASC,
    [EffectiveDate] ASC
)
INCLUDE ([Price])

и объединение будет эффективным MERGE объединением в плане выполнения (не LOOP объединением)

merge join

Промежуточный результат

+-----------+---------------+---------------+----------+-------+------------+
| ProductID | DestinationId | ScheduledDate | Quantity | Price |    grp     |
+-----------+---------------+---------------+----------+-------+------------+
|         0 |          1000 | 2018-02-01    | NULL     | 1     | 2018-02-01 |
|         0 |          1000 | 2018-04-01    | 5        | NULL  | 2018-02-01 |
|         0 |          1000 | 2018-04-02    | 10       | 2     | 2018-04-02 |
|         0 |          1000 | 2018-04-03    | 7        | NULL  | 2018-04-02 |
|         0 |          1000 | 2018-06-01    | 5        | NULL  | 2018-04-02 |
|         0 |          1000 | 2018-06-02    | 10       | NULL  | 2018-04-02 |
|         0 |          1000 | 2018-06-03    | 7        | NULL  | 2018-04-02 |
|         3 |          5000 | 2018-01-01    | NULL     | 5     | 2018-01-01 |
|         3 |          5000 | 2018-05-07    | 15       | NULL  | 2018-01-01 |
|         3 |          5000 | 2018-05-08    | 23       | NULL  | 2018-01-01 |
|         3 |          5000 | 2018-05-09    | 52       | NULL  | 2018-01-01 |
|         3 |          5000 | 2018-05-10    | 12       | 20    | 2018-05-10 |
|         3 |          5000 | 2018-05-11    | 14       | NULL  | 2018-05-10 |
|         3 |          5000 | 2018-06-07    | 15       | NULL  | 2018-05-10 |
|         3 |          5000 | 2018-06-08    | 23       | NULL  | 2018-05-10 |
|         3 |          5000 | 2018-06-09    | 52       | NULL  | 2018-05-10 |
|         3 |          5000 | 2018-06-10    | 12       | NULL  | 2018-05-10 |
|         3 |          5000 | 2018-06-11    | 14       | NULL  | 2018-05-10 |
+-----------+---------------+---------------+----------+-------+------------+

Вы можете видеть, что столбец Price имеет много значений NULL. Нам нужно «заполнить» эти значения NULL предшествующим ненулевым значением.

Ицик Бен-Ган написал хорошую статью, показывающую, как эффективно решить эту проблему Последняя не пустая головоломка . Также см. Лучший способ заменить NULL на самое последнее ненулевое значение .

Это делается в CTE2 с использованием оконной функции MAX, и вы можете видеть, как она заполняет столбец grp. Это требует SQL Server 2012+. После того, как группы определены, мы должны удалить строки, где Quantity равно NULL, потому что эти строки не из таблицы History.

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

Остальная часть запроса почти такая же, как и в первом варианте.

Окончательный запрос

WITH
CTE_Join
AS
(
    SELECT
        ISNULL(History.ProductId, PriceChange.ProductID) AS ProductID
        ,ISNULL(History.DestinationId, PriceChange.DestinationId) AS DestinationId
        ,ISNULL(History.ScheduledDate, PriceChange.EffectiveDate) AS ScheduledDate
        ,History.Quantity
        ,PriceChange.Price
    FROM
        History
        FULL JOIN PriceChange
            ON  PriceChange.ProductID = History.ProductID
            AND PriceChange.DestinationId = History.DestinationId
            AND PriceChange.EffectiveDate = History.ScheduledDate
)
,CTE2
AS
(
    SELECT
        ProductID
        ,DestinationId
        ,ScheduledDate
        ,Quantity
        ,Price
        ,MAX(CASE WHEN Price IS NOT NULL THEN ScheduledDate END)
            OVER (PARTITION BY ProductID, DestinationId ORDER BY ScheduledDate 
            ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS grp
    FROM CTE_Join
)
,CTE_RN
AS
(
    SELECT
        ProductID
        ,DestinationId
        ,ScheduledDate
        ,grp
        ,Quantity
        ,ROW_NUMBER() OVER (PARTITION BY ProductId, DestinationId, grp ORDER BY ScheduledDate) AS rn1
        ,DATEDIFF(day, '20000101', ScheduledDate) AS rn2
    FROM CTE2
    WHERE Quantity IS NOT NULL
)
SELECT
    ProductId
    ,DestinationId
    ,MIN(ScheduledDate) AS StartDate
    ,MAX(ScheduledDate) AS EndDate
    ,SUM(Quantity) AS TotalQuantity
FROM
    CTE_RN
GROUP BY
    ProductId
    ,DestinationId
    ,grp
    ,rn2-rn1
ORDER BY
    ProductID
    ,DestinationId
    ,StartDate
;

Окончательный результат

+-----------+---------------+------------+------------+---------------+
| ProductId | DestinationId | StartDate  |  EndDate   | TotalQuantity |
+-----------+---------------+------------+------------+---------------+
|         0 |          1000 | 2018-04-01 | 2018-04-01 |             5 |
|         0 |          1000 | 2018-04-02 | 2018-04-03 |            17 |
|         0 |          1000 | 2018-06-01 | 2018-06-03 |            22 |
|         3 |          5000 | 2018-05-07 | 2018-05-09 |            90 |
|         3 |          5000 | 2018-05-10 | 2018-05-11 |            26 |
|         3 |          5000 | 2018-06-07 | 2018-06-11 |           116 |
+-----------+---------------+------------+------------+---------------+

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

2 голосов
/ 10 мая 2019

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

Из вопроса не ясно, какова рольDestinationID.Пример данных здесь не поможет.Я предполагаю, что нам нужно объединить и разделить как ProductID, так и DestinationID.

. Следующий запрос возвращает Price для каждой строки из History.Вам нужно добавить индекс в таблицу PriceChange

CREATE NONCLUSTERED INDEX [IX] ON [dbo].[PriceChange]
(
    [ProductId] ASC,
    [DestinationId] ASC,
    [EffectiveDate] DESC
)
INCLUDE ([Price])

, чтобы этот запрос работал эффективно.

Запрос цен

SELECT
    History.ProductId
    ,History.DestinationId
    ,History.ScheduledDate
    ,History.Quantity
    ,A.Price
FROM
    History
    OUTER APPLY
    (
        SELECT TOP(1)
            PriceChange.Price
        FROM
            PriceChange
        WHERE
            PriceChange.ProductID = History.ProductID
            AND PriceChange.DestinationId = History.DestinationId
            AND PriceChange.EffectiveDate <= History.ScheduledDate
        ORDER BY
            PriceChange.EffectiveDate DESC
    ) AS A
ORDER BY ProductID, ScheduledDate;

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

Этот запрос возвращает:

Цены

+-----------+---------------+---------------+----------+-------+
| ProductId | DestinationId | ScheduledDate | Quantity | Price |
+-----------+---------------+---------------+----------+-------+
|         0 |          1000 | 2018-04-01    |        5 |     1 |
|         0 |          1000 | 2018-04-02    |       10 |     2 |
|         0 |          1000 | 2018-04-03    |        7 |     2 |
|         3 |          5000 | 2018-05-07    |       15 |     5 |
|         3 |          5000 | 2018-05-08    |       23 |     5 |
|         3 |          5000 | 2018-05-09    |       52 |     5 |
|         3 |          5000 | 2018-05-10    |       12 |    20 |
|         3 |          5000 | 2018-05-11    |       14 |    20 |
+-----------+---------------+---------------+----------+-------+

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

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

INSERT INTO History (ProductId, DestinationId, ScheduledDate, Quantity)
VALUES
  (0, 1000, '20180601', 5),
  (0, 1000, '20180602', 10),
  (0, 1000, '20180603', 7),
  (3, 5000, '20180607', 15),
  (3, 5000, '20180608', 23),
  (3, 5000, '20180609', 52),
  (3, 5000, '20180610', 12),
  (3, 5000, '20180611', 14);

Если выЗапустите этот промежуточный запрос, и вы увидите, как он работает:

WITH
CTE_Prices
AS
(
    SELECT
        History.ProductId
        ,History.DestinationId
        ,History.ScheduledDate
        ,History.Quantity
        ,A.Price
    FROM
        History
        OUTER APPLY
        (
            SELECT TOP(1)
                PriceChange.Price
            FROM
                PriceChange
            WHERE
                PriceChange.ProductID = History.ProductID
                AND PriceChange.DestinationId = History.DestinationId
                AND PriceChange.EffectiveDate <= History.ScheduledDate
            ORDER BY
                PriceChange.EffectiveDate DESC
        ) AS A
)
,CTE_rn
AS
(
    SELECT
        ProductId
        ,DestinationId
        ,ScheduledDate
        ,Quantity
        ,Price
        ,ROW_NUMBER() OVER (PARTITION BY ProductId, DestinationId, Price ORDER BY ScheduledDate) AS rn1
        ,DATEDIFF(day, '20000101', ScheduledDate) AS rn2
    FROM
        CTE_Prices
)
SELECT *
    ,rn2-rn1 AS Diff
FROM CTE_rn

Промежуточный результат

+-----------+---------------+---------------+----------+-------+-----+------+------+
| ProductId | DestinationId | ScheduledDate | Quantity | Price | rn1 | rn2  | Diff |
+-----------+---------------+---------------+----------+-------+-----+------+------+
|         0 |          1000 | 2018-04-01    |        5 |     1 |   1 | 6665 | 6664 |
|         0 |          1000 | 2018-04-02    |       10 |     2 |   1 | 6666 | 6665 |
|         0 |          1000 | 2018-04-03    |        7 |     2 |   2 | 6667 | 6665 |
|         0 |          1000 | 2018-06-01    |        5 |     2 |   3 | 6726 | 6723 |
|         0 |          1000 | 2018-06-02    |       10 |     2 |   4 | 6727 | 6723 |
|         0 |          1000 | 2018-06-03    |        7 |     2 |   5 | 6728 | 6723 |
|         3 |          5000 | 2018-05-07    |       15 |     5 |   1 | 6701 | 6700 |
|         3 |          5000 | 2018-05-08    |       23 |     5 |   2 | 6702 | 6700 |
|         3 |          5000 | 2018-05-09    |       52 |     5 |   3 | 6703 | 6700 |
|         3 |          5000 | 2018-05-10    |       12 |    20 |   1 | 6704 | 6703 |
|         3 |          5000 | 2018-05-11    |       14 |    20 |   2 | 6705 | 6703 |
|         3 |          5000 | 2018-06-07    |       15 |    20 |   3 | 6732 | 6729 |
|         3 |          5000 | 2018-06-08    |       23 |    20 |   4 | 6733 | 6729 |
|         3 |          5000 | 2018-06-09    |       52 |    20 |   5 | 6734 | 6729 |
|         3 |          5000 | 2018-06-10    |       12 |    20 |   6 | 6735 | 6729 |
|         3 |          5000 | 2018-06-11    |       14 |    20 |   7 | 6736 | 6729 |
+-----------+---------------+---------------+----------+-------+-----+------+------+

Теперь просто сгруппируйте по Diff, чтобы получить одну строкуза интервал.

Окончательный запрос

WITH
CTE_Prices
AS
(
    SELECT
        History.ProductId
        ,History.DestinationId
        ,History.ScheduledDate
        ,History.Quantity
        ,A.Price
    FROM
        History
        OUTER APPLY
        (
            SELECT TOP(1)
                PriceChange.Price
            FROM
                PriceChange
            WHERE
                PriceChange.ProductID = History.ProductID
                AND PriceChange.DestinationId = History.DestinationId
                AND PriceChange.EffectiveDate <= History.ScheduledDate
            ORDER BY
                PriceChange.EffectiveDate DESC
        ) AS A
)
,CTE_rn
AS
(
    SELECT
        ProductId
        ,DestinationId
        ,ScheduledDate
        ,Quantity
        ,Price
        ,ROW_NUMBER() OVER (PARTITION BY ProductId, DestinationId, Price ORDER BY ScheduledDate) AS rn1
        ,DATEDIFF(day, '20000101', ScheduledDate) AS rn2
    FROM
        CTE_Prices
)
SELECT
    ProductId
    ,DestinationId
    ,MIN(ScheduledDate) AS StartDate
    ,MAX(ScheduledDate) AS EndDate
    ,SUM(Quantity) AS TotalQuantity
    ,Price
FROM
    CTE_rn
GROUP BY
    ProductId
    ,DestinationId
    ,Price
    ,rn2-rn1
ORDER BY
    ProductID
    ,DestinationId
    ,StartDate
;

Окончательный результат

+-----------+---------------+------------+------------+---------------+-------+
| ProductId | DestinationId | StartDate  |  EndDate   | TotalQuantity | Price |
+-----------+---------------+------------+------------+---------------+-------+
|         0 |          1000 | 2018-04-01 | 2018-04-01 |             5 |     1 |
|         0 |          1000 | 2018-04-02 | 2018-04-03 |            17 |     2 |
|         0 |          1000 | 2018-06-01 | 2018-06-03 |            22 |     2 |
|         3 |          5000 | 2018-05-07 | 2018-05-09 |            90 |     5 |
|         3 |          5000 | 2018-05-10 | 2018-05-11 |            26 |    20 |
|         3 |          5000 | 2018-06-07 | 2018-06-11 |           116 |    20 |
+-----------+---------------+------------+------------+---------------+-------+
2 голосов
/ 02 мая 2019

Не уверен, что я правильно понимаю, но это только моя идея:

Select concat_ws(',',view2.StartDate,  string_agg(view1.splitDate, ','), 
 view2.EndDate), view2.productId, view2.DestinationId from (
 SELECT DENSE_RANK() OVER (ORDER BY EffectiveDate) as Rank, EffectiveDate as 
  SplitDate FROM PriceChange GROUP BY EffectiveDate) view1 join 
 (
     SELECT MIN(ScheduledDate) as StartDate, MAX(ScheduledDate) as 
       EndDate,ProductId, DestinationId, SUM(Quantity) as TotalQuantity
     FROM (
      SELECT ScheduledDate, DestinationId, ProductId, PartitionGroup = 
      DATEADD(DAY ,-1 * DENSE_RANK() OVER (ORDER BY ScheduledDate), 
       ScheduledDate), Quantity
       FROM History
   ) tmp
      GROUP BY PartitionGroup, DestinationId, ProductId
    ) view2 on view1.SplitDate >= view2.StartDate 
      and view1.SplitDate <=view2.EndDate 
      group by view2.startDate, view2.endDate, view2.productId, 
      view2.DestinationId

Результатом этого запроса будет:

| ranges                                      | productId | DestinationId |
|---------------------------------------------|-----------|---------------|
| 2018-04-01,2018-04-02,2018-04-03            | 0         | 1000          |
| 2018-05-07,2018-05-10,2018-05-11            | 3         | 5000          |

Затем на любом языке процедур для каждой строки вы можете разбить строку (с соответствующим включающим или исключающим правилом для каждой границы), чтобы узнать список условий (: from,: to,: productId,: destinationId) .

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

Select * from History where ScheduledDate >= '2018-04-01' and ScheduledDate <'2018-04-02' and productId = 0 and destinationId = 1000 
union all
Select * from History where ScheduledDate >= '2018-04-02' and ScheduledDate <'2018-04-03' and productId = 0 and destinationId = 1000

---- Обновление --------

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

 with view3 as 
(Select concat_ws(',',view2.StartDate,  string_agg(view1.splitDate, ','), 
 dateadd(day, 1, view2.EndDate)) dateRange, view2.productId, view2.DestinationId from (
 SELECT DENSE_RANK() OVER (ORDER BY EffectiveDate) as Rank, EffectiveDate as 
  SplitDate FROM PriceChange GROUP BY EffectiveDate) view1 join 
 (
     SELECT MIN(ScheduledDate) as StartDate, MAX(ScheduledDate) as 
       EndDate,ProductId, DestinationId, SUM(Quantity) as TotalQuantity
     FROM (
      SELECT ScheduledDate, DestinationId, ProductId, PartitionGroup = 
      DATEADD(DAY ,-1 * DENSE_RANK() OVER (ORDER BY ScheduledDate), 
       ScheduledDate), Quantity
       FROM History
   ) tmp
      GROUP BY PartitionGroup, DestinationId, ProductId
    ) view2 on view1.SplitDate >= view2.StartDate 
      and view1.SplitDate <=view2.EndDate 
      group by view2.startDate, view2.endDate, view2.productId, 
      view2.DestinationId
),
 view4 as
(
select productId, destinationId, value from view3 cross apply string_split(dateRange, ',')
 ),
 view5 as(
   select *, row_number() over(partition by productId, destinationId order by value) rn from view4
 ),
 view6 as (
   select v52.value fr, v51.value t, v51.productid, v51. destinationid from view5 v51 join view5 v52
 on v51.productid = v52.productid
 and v51.destinationid = v52.destinationid
 and v51.rn = v52.rn+1
 )
 select min(h.ScheduledDate) StartDate, max(h.ScheduledDate) EndDate, v6.productId, v6.destinationId, sum(h.quantity) TotalQuantity from view6 v6 join History h 
 on v6.destinationId = h.destinationId
 and v6.productId = h.productId
 and h.ScheduledDate >= v6.fr
 and h.ScheduledDate <v6.t
 group by v6.fr, v6.t, v6.productId, v6.destinationId

И результат в точности совпадает с тем, что вы дали.

| StartDate  | EndDate    | productId | destinationId | TotalQuantity |
|------------|------------|-----------|---------------|---------------|
| 2018-04-01 | 2018-04-01 | 0         | 1000          | 5             |
| 2018-04-02 | 2018-04-03 | 0         | 1000          | 17            |
| 2018-05-07 | 2018-05-09 | 3         | 5000          | 90            |
| 2018-05-10 | 2018-05-11 | 3         | 5000          | 26            |
1 голос
/ 10 мая 2019

Используйте outer apply для выбора ближайшей цены, затем выполните group by:

Тест в реальном времени: http://www.sqlfiddle.com/#!18/af568/65

select 
    StartDate = min(h.ScheduledDate),
    EndDate = max(h.ScheduledDate),
    h.ProductId,
    h.DestinationId,
    TotalQuantity = sum(h.Quantity)
from History h
outer apply
(
    select top 1 pc.*
    from PriceChange pc
    where 
        pc.ProductId = h.ProductId
        and pc.Effectivedate <= h.ScheduledDate
    order by pc.EffectiveDate desc
) UpToDate
group by UpToDate.EffectiveDate,
    h.ProductId,
    h.DestinationId
order by StartDate, EndDate, ProductId    

Выход:

|  StartDate |    EndDate | ProductId | DestinationId | TotalQuantity |
|------------|------------|-----------|---------------|---------------|
| 2018-04-01 | 2018-04-01 |         0 |          1000 |             5 |
| 2018-04-02 | 2018-04-03 |         0 |          1000 |            17 |
| 2018-05-07 | 2018-05-09 |         3 |          5000 |            90 |
| 2018-05-10 | 2018-05-11 |         3 |          5000 |            26 |
...