2016-05-26 7 views
1

У меня есть некоторые данные, пример реального мира. Чтобы отображать данные определенным образом, данные хранятся таким образом, что я должен манипулировать им, чтобы получить нужные результаты. По сути, существуют два разных типа счетов-фактур: предварительный счет-фактура (P), когда суммы выставлены счету перед обработкой заказа; и стандартный счет-фактура (I), где остаток выставляется после доставки. В обычных обстоятельствах вы ожидали, что стандартный счет-фактура будет иметь вычитаемую из него сумму перед выставлением счета, например, если предварительный счет был за 2 доллара, а заказ был равен 10 долларам США, что стандартный счет-фактура будет составлять 8 долларов США. Вместо этого стандартный счет-фактура хранится как 10 долларов США.Есть ли способ использования оконных функций для упрощения этого запроса?

Ниже приведен полный рабочий запрос (этот запрос работает! Это ловушка!), Который заполняет данные и возвращает результаты, которые я хочу. Цель состоит в том, чтобы принять сумму счета и, если это предварительный счет, вернуть эту сумму; но если это стандартный счет-фактура, «использовать» значение предварительного счета и вернуть новую сумму, минимальный ноль. Я включил шесть сценариев, поскольку предварительный счет-фактура может быть любой суммой и может быть технически обязательным в любое время.

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

DECLARE @PData TABLE (
    CustNum  INT 
    ,TransNum  INT 
    ,InvType  NVARCHAR(1) 
    ,InvAmt   DECIMAL(5,2) 
    ,CRank   INT 
    ,TRank   INT 
    ,ModInvAmt  DECIMAL(5,2) 
    ) 

INSERT INTO @PData (
    CustNum 
    ,TransNum 
    ,InvType 
    ,InvAmt 
    ) 

    VALUES 
    (124, 1,'P',2) 
    ,(124, 2,'I',10) 
    ,(124, 3,'I',10) 
    ,(153, 4,'I',10) 
    ,(153, 5,'P',2) 
    ,(153, 6,'I',10) 
    ,(324, 7,'I',10) 
    ,(324, 8,'I',10) 
    ,(324, 9,'P',2) 
    ,(441,10,'P',12) 
    ,(441,11,'I',10) 
    ,(441,12,'I',10) 
    ,(455,13,'I',10) 
    ,(455,14,'P',12) 
    ,(455,15,'I',10) 
    ,(667,16,'I',10) 
    ,(667,17,'I',10) 
    ,(667,18,'P',12) 

UPDATE pd1 
    SET CRank = pd2.CDR 
     FROM @PData pd1 
      JOIN (SELECT CustNum, TransNum, DENSE_RANK() OVER (ORDER BY CustNum) AS CDR 
        FROM @PData) pd2 
       ON pd1.TransNum = pd2.TransNum 

UPDATE pd1 
    SET TRank = pd2.TDR 
     FROM @PData pd1 
      JOIN (SELECT CustNum, TransNum, DENSE_RANK() OVER (PARTITION BY CustNum ORDER BY TransNum) AS TDR 
        FROM @PData) pd2 
       ON pd1.TransNum = pd2.TransNum 

DECLARE @Counter1  INT 
     ,@Counter2  INT 
     ,@CBal   DECIMAL(5,2) 
     ,@TNum   INT 
     ,@IAmt   DECIMAL(5,2) 

SET @Counter1 = 0 

WHILE @Counter1 < (SELECT MAX(CRank) FROM @PData) 
    BEGIN 
     SET @Counter1 += 1 
     SET @CBal = 0 
     SET @Counter2 = 0 
      WHILE @Counter2 < (SELECT MAX(TRank) FROM @PData WHERE CRank = @Counter1) 
       BEGIN 
        SET @Counter2 += 1 
        SET @TNum = (SELECT TransNum FROM @PData WHERE CRank = @Counter1 AND TRank = @Counter2) 
        SET @IAmt = (SELECT InvAmt FROM @PData WHERE TransNum = @TNum) 
        IF (SELECT InvType FROM @PData WHERE TransNum = @TNum) = 'P' 
         BEGIN 
          UPDATE @PData SET ModInvAmt = @IAmt WHERE TransNum = @TNum 
          SET @CBal += [email protected] 
         END 
        ELSE 
         BEGIN 
          UPDATE @PData SET ModInvAmt = (ABS(@[email protected])+(@[email protected]))/2 -- MINIMUM = 0 
           WHERE TransNum = @TNum 
          SET @CBal += (@IAmt - (ABS(@[email protected])+(@[email protected]))/2) 
         END     
       END 
    END 

SELECT CustNum 
     ,TransNum 
     ,InvType 
     ,InvAmt 
     ,ModInvAmt 
FROM @PData 

Вот результаты, которые я получаю: enter image description here

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

CustNum TransNum InvType InvAmt ModInvAmt 
124  1   P  2.00  2.00 
124  2   I 10.00  8.00 
124  3   I 10.00  10.00 
153  4   I 10.00  10.00 
153  5   P  2.00  2.00 
153  6   I 10.00  8.00 
324  7   I 10.00  10.00 
324  8   I 10.00  10.00 
324  9   P  2.00  2.00 
441  10   P 12.00  12.00 
441  11   I 10.00  0.00 
441  12   I 10.00  8.00 
455  13   I 10.00  10.00 
455  14   P 12.00  12.00 
455  15   I 10.00  0.00 
667  16   I 10.00  10.00 
667  17   I 10.00  10.00 
667  18   P 12.00  12.00 
+0

Мой образ в конце не отображается? – DaveX

+0

Полезно пометить вопросы базы данных как с помощью соответствующего программного обеспечения (MySQL, Oracle, DB2, ...) и версии, например. 'SQL-сервер-2014'. Различия в синтаксисе и особенностях часто влияют на ответы. – HABO

+0

Сделано, спасибо за идею. – DaveX

ответ

1

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

С наилучшими пожеланиями Питер

DECLARE @PData TABLE (
    CustNum  INT 
    ,TransNum  INT 
    ,InvType  NVARCHAR(1) 
    ,InvAmt   DECIMAL(5,2) 
    ) 

INSERT INTO @PData (
    CustNum 
    ,TransNum 
    ,InvType 
    ,InvAmt 
    ) 

    VALUES 
(124, 1,'P',2) 
,(124, 2,'I',10) 
,(124, 3,'I',10) 
,(153, 4,'I',10) 
,(153, 5,'P',2) 
,(153, 6,'I',10) 
,(324, 7,'I',10) 
,(324, 8,'I',10) 
,(324, 9,'P',2) 
,(441,10,'P',12) 
,(441,11,'I',10) 
,(441,12,'I',10) 
,(455,13,'I',10) 
,(455,14,'P',12) 
,(455,15,'I',10) 
,(455,19,'I',10) 
,(667,16,'I',10) 
,(667,17,'I',10) 
,(667,18,'P',12) 

;WITH Data as (
    SELECT 
     CustNum, 
     TransNum, 
     InvType, 
     InvAmt, 
     ROW_NUMBER() OVER (PARTITION BY custNum ORDER BY Transnum ASC) row, 
     CASE WHEN InvType = 'P' THEN cast(-1*InvAmt AS DECIMAL(5,2)) ELSE 0 END prepaidAmt 
    FROM 
     @PData 
), modified as(
    SELECT 
     CustNum, 
     TransNum, 
     InvType, 
     InvAmt, 
     prepaidAmt, 
     row, 
     InvAmt total 
    FROM Data d1 
     WHERE row = 1 
    UNION ALL 
    SELECT  
     d2.CustNum, 
     d2.TransNum, 
     d2.InvType, 
     d2.InvAmt, 
     CASE 
     WHEN 
      d2.InvAmt+m.prepaidAmt < 0 
     THEN 
      CAST (d2.InvAmt+m.prepaidAmt AS DECIMAL(5,2)) 
     ELSE 
      CASE 
      WHEN 
       d2.invtype = 'P' 
      THEN 
       CAST(-1*d2.invamt AS DECIMAL(5,2)) 
      ELSE 
       0 
      END 
     END , 
     d2.row, 
     CASE 
     WHEN 
      d2.InvAmt+m.prepaidAmt <0 
     THEN 
      0 
     ELSE 
      CAST(d2.InvAmt+m.prepaidAmt AS DECIMAL(5,2)) 
     END 
    FROM Data d2 
    JOIN modified m 
    ON 
     m.CustNum = d2.CustNum and 
     m.row = d2.row-1 
) 
SELECT 
    m.CustNum, 
    m.TransNum, 
    m.InvType, 
    m.InvAmt, 
    m.total 
FROM modified m 
ORDER BY 
    custnum, 
    transnum 
+0

Вау ... это довольно блестяще! Благодаря! – DaveX