2010-08-12 382 views
7

我需要使用SQL在下表中生成「required」列,而不使用循環和相關的子查詢。這在SQL 2008中可能嗎?SQL:如何填充具有上一行值的空單元格?

Date Customer Value Required Rule 
20100101  1  12   12 
20100101  2     0 If no value assign 0 
20100101  3  32   32 
20100101  4  42   42 
20100101  5  15   15 
20100102  1     12 Take last known value 
20100102  2     0 Take last known value 
20100102  3  39   39 
20100102  4     42 Take last known value 
20100102  5  16   16 
20100103  1  13   13 
20100103  2  24   24 
20100103  3     39 Take last known value 
20100103  4     42 Take last known value 
20100103  5  21   21 
20100104  1  14   14 
20100104  2     24 Take last known value 
20100104  3     39 Take last known value 
20100104  4  65   65 
20100104  5  23   23 

基本上我補虛「價值」細胞與該客戶最後一個已知值。請記住,最後一行可能沒有有效值,因此您必須先從行中選取具有有效值的行。

+1

作爲觸發器,還是作爲一般查詢? – Tobiasopdenbrouw 2010-08-12 08:26:19

+0

僅限一般查詢。 – Faiz 2010-08-12 09:45:03

回答

5

法伊茲,

下面的查詢怎麼樣,它根據我的理解做了你想要的。評論解釋了每一步。閱讀Books Online上的CTEs。這個例子甚至可以更改爲使用新的MERGE命令SQL 2008

/* Test Data & Table */ 
DECLARE @Customers TABLE 
    (Dates datetime, 
    Customer integer, 
    Value integer) 

    INSERT INTO @Customers 
    VALUES ('20100101', 1, 12), 
     ('20100101', 2, NULL), 
     ('20100101', 3, 32), 
     ('20100101', 4, 42), 
     ('20100101', 5, 15), 
     ('20100102', 1, NULL), 
     ('20100102', 2, NULL), 
     ('20100102', 3, 39), 
     ('20100102', 4, NULL), 
     ('20100102', 5, 16), 
     ('20100103', 1, 13), 
     ('20100103', 2, 24), 
     ('20100103', 3, NULL), 
     ('20100103', 4, NULL), 
     ('20100103', 5, 21), 
     ('20100104', 1, 14), 
     ('20100104', 2, NULL), 
     ('20100104', 3, NULL), 
     ('20100104', 4, 65), 
     ('20100104', 5, 23) ; 

/* CustCTE - This gives us a RowNum to allow us to build the recursive CTE CleanCust */ 
WITH CustCTE 
      AS (SELECT Customer, 
         Value, 
         Dates, 
         ROW_NUMBER() OVER (PARTITION BY Customer ORDER BY Dates) RowNum 
       FROM  @Customers), 

/* CleanCust - A recursive CTE. This runs down the list of values for each customer, checking the Value column, if it is null it gets the previous non NULL value.*/ 
     CleanCust 
      AS (SELECT Customer, 
         ISNULL(Value, 0) Value, /* Ensure we start with no NULL values for each customer */ 
         Dates, 
         RowNum 
       FROM  CustCte cur 
       WHERE  RowNum = 1 
       UNION ALL 
       SELECT Curr.Customer, 
         ISNULL(Curr.Value, prev.Value) Value, 
         Curr.Dates, 
         Curr.RowNum 
       FROM  CustCte curr 
       INNER JOIN CleanCust prev ON curr.Customer = prev.Customer 
              AND curr.RowNum = prev.RowNum + 1) 

/* Update the base table using the result set from the recursive CTE */ 
    UPDATE trg 
    SET Value = src.Value 
    FROM @Customers trg 
    INNER JOIN CleanCust src ON trg.Customer = src.Customer 
           AND trg.Dates = src.Dates 

/* Display the results */ 
SELECT * FROM @Customers 
+0

優秀的威廉,我幾乎在那裏。我知道如果我必須避免循環,CTE將是唯一的選擇(我討厭CTE),但我想看看是否有人可以用TSQL來想出其他一些技巧。做得好!我需要用現有的性能測試這個解決方案,看看它是如何執行的...... :) 我認爲我在這裏提出的問題是一個通用問題,並且經常在銀行項目中看到。希望這會幫助很多... – Faiz 2010-08-13 08:29:24

+0

我能問你爲什麼討厭CTE?它們非常有用,並且經常可以擊敗許多其他解決方案。遞歸CTE通常是有問題的和昂貴的,即遞歸的性質,而不是CTE的錯。 會有選擇使用古怪的更新(谷歌的),但它是一個有爭議的解決方案。 – 2010-08-13 09:47:21

2

我需要製作使用SQL不使用循環和 相關子查詢下表 在「要求」一欄 。在SQL 2008中可以使用 嗎?

不可能。點。不可能在任何基於SQL的服務器上,包括oracle。

這裏的主要問題是,你排除循環和相關的子查詢,以及在查詢時檢索值的任何方式將最終使用另一個查詢來查找有效值(實際上每個字段一個)。這就是SQL的工作原理。是的,你可以將它們隱藏在一個自定義的標量函數中,但是它們仍然會包含一個邏輯子查詢。

0

對於日期小於當前日期並且值非空的同一表上的左外部聯接如何,由date desc(limit 1)排序,當null時返回0? (目前沒有服務器可供測試)。除非這個計數作爲子查詢...

+0

任何人都可以想出這個查詢嗎? – Faiz 2010-08-12 08:59:51

+1

看起來像列文剛剛做的。 – pritaeas 2010-08-12 09:24:22

1

我不確定下面的計數是否考慮到你的約束條件,但是它可以完成工作。

測試數據

DECLARE @Customers TABLE (Date DATETIME, Customer INTEGER, Value INTEGER) 

INSERT INTO @Customers VALUES ('20100101', 1, 12 )  
INSERT INTO @Customers VALUES ('20100101', 2, NULL)   
INSERT INTO @Customers VALUES ('20100101', 3, 32 ) 
INSERT INTO @Customers VALUES ('20100101', 4, 42 ) 
INSERT INTO @Customers VALUES ('20100101', 5, 15 ) 
INSERT INTO @Customers VALUES ('20100102', 1, NULL) 
INSERT INTO @Customers VALUES ('20100102', 2, NULL) 
INSERT INTO @Customers VALUES ('20100102', 3, 39 ) 
INSERT INTO @Customers VALUES ('20100102', 4, NULL) 
INSERT INTO @Customers VALUES ('20100102', 5, 16 ) 
INSERT INTO @Customers VALUES ('20100103', 1, 13 ) 
INSERT INTO @Customers VALUES ('20100103', 2, 24 ) 
INSERT INTO @Customers VALUES ('20100103', 3, NULL) 
INSERT INTO @Customers VALUES ('20100103', 4, NULL) 
INSERT INTO @Customers VALUES ('20100103', 5, 21 ) 
INSERT INTO @Customers VALUES ('20100104', 1, 14 ) 
INSERT INTO @Customers VALUES ('20100104', 2, NULL) 
INSERT INTO @Customers VALUES ('20100104', 3, NULL) 
INSERT INTO @Customers VALUES ('20100104', 4, 65 ) 
INSERT INTO @Customers VALUES ('20100104', 5, 23 ) 

查詢

SELECT c.Date 
     , c.Customer 
     , Value = COALESCE(c.Value, cprevious.Value, 0) 
FROM @Customers c 
     INNER JOIN (
      SELECT c.Date 
        , c.Customer 
        , MaxDate = MAX(cdates.Date) 
      FROM @Customers c 
        LEFT OUTER JOIN (
        SELECT Date 
          , Customer 
        FROM @Customers 
       ) cdates ON cdates.Date < c.Date AND cdates.Customer = c.Customer 
      GROUP BY 
        c.Date, c.Customer 
     ) cmax ON cmax.Date = c.Date AND cmax.Customer = c.Customer     
     LEFT OUTER JOIN @Customers cprevious ON cprevious.Date = cmax.MaxDate AND cprevious.Customer = cmax.Customer 
ORDER BY 
     1, 2, 3   

Update語句

UPDATE @Customers 
SET  Value = c2.Value 
OUTPUT Inserted.* 
FROM @Customers c 
     INNER JOIN ( 
      SELECT c.Date 
        , c.Customer 
        , Value = COALESCE(c.Value, cprevious.Value, 0) 
      FROM @Customers c 
        INNER JOIN (
        SELECT c.Date 
          , c.Customer 
          , MaxDate = MAX(cdates.Date) 
        FROM @Customers c 
          LEFT OUTER JOIN (
           SELECT Date 
             , Customer 
           FROM @Customers 
          ) cdates ON cdates.Date < c.Date AND cdates.Customer = c.Customer 
        GROUP BY 
          c.Date, c.Customer 
       ) cmax ON cmax.Date = c.Date AND cmax.Customer = c.Customer     
        LEFT OUTER JOIN @Customers cprevious ON cprevious.Date = cmax.MaxDate AND cprevious.Customer = cmax.Customer 
     ) c2 ON c2.Date = c.Date 
       AND c2.Customer = c.Customer 
+0

謝謝你所有的努力。但是這隻能處理前一行或前一行有效值的情況,rigt?那麼有效的價值與目前的價值相差甚遠呢? – Faiz 2010-08-12 09:44:12

+0

@Faiz,對於這些情況,查詢變得更加複雜:)。它要求你加入max(c2.Date) 2010-08-12 09:59:16

+0

我想這將返回多個記錄,更新將只考慮最上面的記錄。現在,如何讓它們排序以便最後一個有效值的記錄位於最上面,是下一個問題:( – Faiz 2010-08-12 10:05:12

0

這是「Last non-null puzzle」,而這裏的幾個優雅的解決方案之一:

如果你的「疏」表是SparseTable與列日期,客戶,價值則:

with C as 
(select *, 
    max(case when Value is not null then [Date] end) 
     over (partition by Customer order by [Date] rows unbounded preceding) as grp 
from SparseTable 
) 
insert into FullTable 
select *, 
    max(Value) over (partition by Customer, grp order by [Date] rows unbounded preceding) as Required 
from C 

Value無法向前填補它仍然是NULL,這樣你就可以再

update FullTable set Required = 0 where Required is null 
相關問題