LEFT external join возвращает двойные строки даже после добавления отдельных - PullRequest
0 голосов
/ 25 июня 2019

Ограничение таблицы изменения цен для выбора конкретного товара

select * from price
where item = '13'

результаты запроса выше

item       Date_Changed     New     Old          START_DATE         end_DATE
13  01/11/2018 00:00    5.61    4.88    01/11/2018 00:00    30/11/2018 00:00
13  30/11/2018 00:00    2.84    5.61    01/11/2018 00:00    17/12/2018 00:00
13  17/12/2018 00:00    2.39    2.84    30/11/2018 00:00    17/12/2018 00:00

таблица продаж

Date    Item    Qty Amount
05/07/2018 00:00    13  3   14.64
05/07/2018 00:00    13  3   14.64
04/07/2018 00:00    13  3   14.64
02/07/2018 00:00    13  1   4.88
02/07/2018 00:00    13  6   29.28
06/07/2018 00:00    13  7   34.16
03/07/2018 00:00    13  4   19.52
12/07/2018 00:00    13  2   9.76
10/08/2018 00:00    13  1   4.88

Пример кода

SELECT distinct a.[Inv]
  , (CASE 
        WHEN a.Date <=  b.START_DATE  THEN  (b.Old * a.Qty)
        WHEN a.Date between  b.START_DATE  and b.dt_end_DATE  THEN  (b.New * a.Qty)
        ELSE 0
   END) as calc_amount
   ,(a.[amount] - (CASE 
        WHEN a.Date <=  b.START_DATE  THEN  (b.Old * a.Qty)
        WHEN a.Date between  b.START_DATE  and b.end_DATE  THEN  (b.New * a.Qty)
        ELSE 0
   END)) as variance
[sales] a 
left outer join price b
on a.[Item] = b.item 
where b.item = '13' 

Затем сценарий возвращает 27 строк вместо 9 строк.Может ли кто-нибудь помочь, как я могу улучшить свой сценарий, чтобы быть более точным

Ответы [ 3 ]

1 голос
/ 25 июня 2019

Используйте outer apply. Я предполагаю, что вы хотите самую последнюю дату начала с price, поэтому это выглядит так:

select s.*,
       (case when s.date <= p.start_date
             then p.Old * s.Qty
             else p.New * s.Qty
        end) as calc_amount
from sales s outer apply
     (select top (1) p.*
      from prices p
      where p.item = s.item and
            p.start_date <= s.date
      order by p.date desc
     ) p
0 голосов
/ 25 июня 2019

Ваш вопрос не ясен ... Вы добавили пример данных, но я сомневаюсь, что это правильно ...

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

Попробуй это. Я изменил даты таким образом, чтобы имитировать периодичность действия.

Сценарий

A макет (пожалуйста, в следующий раз для нас):

CREATE TABLE priceMock(item INT, Date_Changed DATE, New DECIMAL(10,4), Old DECIMAL(10,4), [START_DATE] DATE, end_DATE DATE);

SET DATEFORMAT dmy;
INSERT INTO priceMock VALUES
 (13,'01/11/2018 00:00',5.61,4.88,'01/07/2018 00:00','06/07/2018 00:00')
,(13,'30/11/2018 00:00',2.84,5.61,'07/07/2018 00:00','10/07/2018 00:00')
,(13,'17/12/2018 00:00',2.39,2.84,'11/07/2018 00:00','15/08/2018 00:00');
GO

CREATE TABLE salesMock ([Date] DATE, Item INT, Qty INT, Amount DECIMAL(10,4));

SET DATEFORMAT dmy;
INSERT INTO salesMock VALUES
 ('05/07/2018 00:00',13,3,14.64)
,('05/07/2018 00:00',13,3,14.64)
,('04/07/2018 00:00',13,3,14.64)
,('02/07/2018 00:00',13,1,4.88 )
,('02/07/2018 00:00',13,6,29.28)
,('06/07/2018 00:00',13,7,34.16)
,('03/07/2018 00:00',13,4,19.52)
,('10/07/2018 00:00',13,2,9.76 )
,('10/08/2018 00:00',13,1,4.88 );
GO

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

CREATE FUNCTION dbo.GetPriceForItemOnDate(@item INT,@ValidOn DATE)
RETURNS TABLE
AS
RETURN
    SELECT TOP 1 *
    FROM priceMock
    WHERE item=@item
    AND [START_DATE] <= @ValidOn
    ORDER BY [START_DATE] DESC
GO

- этот запрос объединит ваши данные о продажах с ценой, действующей на указанную дату

SELECT s.[Date]
      ,s.Item
      ,s.Qty
      ,p.New AS CurrentPrice
      ,s.Qty * p.New AS ComputedAmount
FROM salesMock s
OUTER APPLY dbo.GetPriceForItemOnDate(s.item,s.[Date]) p 
GO

- Очистить (осторожно с реальными данными)

DROP FUNCTION dbo.GetPriceForItemOnDate;
DROP TABLE priceMock;
DROP TABLE salesMock;

Идея вкратце:

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

Общее замечание: здесь используется validFrom -подход. Но вы можете повернуть все наоборот и использовать validTo -подход. Идея та же.

0 голосов
/ 25 июня 2019

Я не уверен, что это то, что вы ищете.но, возможно, вы можете пропустить объединение?

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


DECLARE @price table (item varchar(2),date_start date, new_price numeric(9,2))

Insert into @price (item , date_start,new_price) 
values
    ( '13', '20190101', '1.00'),
    ( '13', '20190102', '1.01'),
    ( '13', '20190103', '1.02')


DECLARE @sales table (item varchar(2),date_sales date,qty int)

Insert into @sales (item , date_sales,qty) 
values
    ( '13', '20190101', '5'),
    ( '13', '20190101', '2'),
    ( '13', '20190102', '5'),
    ( '13', '20190102', '2'),
    ( '13', '20190103', '5'),
    ( '13', '20190103', '2')


declare @item as varchar(2) = '13'
SELECT (select top (1) new_price from @price b where a.date_sales>=b.date_start and b.item = @item order by b.date_start desc ) * a.qty as 'new_price* qty'
from @sales a 
where a.item = @item

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

...