SQL优化脚本使用cte

时间:2015-07-08 03:43:56

标签: sql-server query-optimization

我想优化我的脚本,因为如果有数十万条记录,我会在获得结果时遇到响应缓慢。其目标如下:

  • 获取具有相同altid的多条记录的所有记录
  • 具有0或false值的Producttype,必须使用producttype = 1或true
  • 指定买方值
  • 使用producttype = 1或true value排除具有两个以上不同买方值的所有记录。

这是我的表格结构,示例记录和我的查询:

CREATE TABLE Products
  (
     AltId       VARCHAR(10),
     ItemID      VARCHAR(10),
     ProductType BIT,
     Buyer       VARCHAR(6)
  )

CREATE UNIQUE NONCLUSTERED INDEX idx_product
  ON Products (AltId, ItemID)

INSERT INTO Products
            (Altid,ItemId,ProductType,Buyer)
VALUES      ('A01','ItemA0101',0,'216')

INSERT INTO Products
            (Altid,ItemId,ProductType,Buyer)
VALUES      ('A01','ItemA0102',0,NULL)

INSERT INTO Products
            (Altid,ItemId,ProductType,Buyer)
VALUES      ('A01','ItemA0103',1,'264')

INSERT INTO Products
            (Altid,ItemId,ProductType,Buyer)
VALUES      ('A01','ItemA0104',1,NULL)

INSERT INTO Products
            (Altid,ItemId,ProductType,Buyer)
VALUES      ('A02','ItemA0201',0,'215')

INSERT INTO Products
            (Altid,ItemId,ProductType,Buyer)
VALUES      ('A02','ItemA0202',1,'217')

INSERT INTO Products
            (Altid,ItemId,ProductType,Buyer)
VALUES      ('A03','ItemA0301',0,'215')

INSERT INTO Products
            (Altid,ItemId,ProductType,Buyer)
VALUES      ('A03','ItemA0302',1,'216')

INSERT INTO Products
            (Altid,ItemId,ProductType,Buyer)
VALUES      ('A03','ItemA0303',1,'264')

INSERT INTO Products
            (Altid,ItemId,ProductType,Buyer)
VALUES      ('A04','ItemA0401',1,'216')

INSERT INTO Products
            (Altid,ItemId,ProductType,Buyer)
VALUES      ('A05','ItemA0501',1,'218')

INSERT INTO Products
            (Altid,ItemId,ProductType,Buyer)
VALUES      ('A05','ItemA0502',0,'216')

INSERT INTO Products
            (Altid,ItemId,ProductType,Buyer)
VALUES     ('A05','ItemA0503',1,NULL);

WITH original_query
     AS (
        --GET ALL ALTID FROM PRODUCTS THAT HAVE MORE THAN 1 BUYER
        --JOIN MANUF TABLE TO GET ALL COLUMNS NEEDED FOR BUYERS
        SELECT b.altid,
               p.itemid,
               p.Buyer,
               p.ProductType--p.manufid
         FROM   (
                --GET ALL ALTID THAT HAS MORE THAN 1 BUYER
                SELECT a.altid
                 FROM   (
                        --GET ALL ALT ID AND GROUP IT BY ALTID AND BUYER
                        SELECT p.altid
                         FROM   products p
                         --WHERE p.BUYER IS NOT NULL
                         GROUP  BY p.altid,
                                   p.Buyer) a
                 GROUP  BY a.altid
                 HAVING Count(*) > 1)b
                JOIN products p
                  ON b.altid = p.altid
        --JOIN manuf m ON p.manufid = m.manufid
        --WHERE p.BUYER  IS NOT NULL
        ), -- Get all Null value Buyer
     GetAllNullBuyer
     AS (SELECT oq.altid,
                oq.itemid,
                oq.Buyer,
                oq.ProductType
         FROM   original_query oq
         WHERE  oq.Buyer IS NULL), --Get all buyer that has no null value
     GetAllNotNullBuyer
     AS (SELECT oq.altid,
                oq.itemid,
                oq.Buyer,
                oq.ProductType
         FROM   original_query oq --JOIN  Products p --Result
         --ON oq.AltID=p.AltID
         WHERE  oq.Buyer IS NOT NULL
        --Group by oq.altid,oq.Buyer,p.ProductType
        ), --Count the Buyer per altid and producttype
     Count_AltidperBuyer
     AS (SELECT a.altid,
                a.Buyer,
                Count(a.Buyer) BuyerCnt,
                a.ProductType
         FROM   GetAllNotNullBuyer a
         GROUP  BY a.altid,
                   a.Buyer,
                   a.ProductType), --Get list of buyer with producttype=1 and not more than 1 buyer
     exclude_rec
     AS (SELECT altid,
                Count(Buyer) BuyerCnt
         FROM   Count_AltidperBuyer
         WHERE  ProductType = 1
         GROUP  BY altid
         HAVING Count(Buyer) > 1), --Combine all buyer with value and null value but did not inlcude buyer in the exclude_rec
     CombineNullBuyer
     AS (SELECT altid,
                itemid,
                Buyer,
                producttype
         FROM   GetAllNotNullBuyer
         WHERE  altid NOT IN (SELECT altid
                              FROM   exclude_rec)
         UNION
         SELECT altid,
                itemid,
                Buyer,
                producttype
         FROM   GetAllNullBuyer
         WHERE  altid NOT IN (SELECT altid
                              FROM   exclude_rec)), --GET all altid with producttype=1 and buyer is null
     GetProductTypeBuyer
     AS (SELECT a.altid,
                a.itemid,
                a.producttype,
                a.Buyer
         FROM   CombineNullBuyer a
                JOIN products p
                  ON a.altid = p.altid
                     AND a.itemid = p.itemid
         WHERE  p.producttype = 1
                AND p.buyer IS NULL), --Combine records with producttype=1 and buyer is null and CombineNullBuyer records
     CombineALL
     AS (SELECT altid,
                itemid,
                Buyer,
                producttype
         FROM   CombineNullBuyer
         UNION
         SELECT altid,
                itemid,
                Buyer,
                producttype
         FROM   GetProductTypeBuyer), --Assign new Buyer ID
     Assign_Buyer
     AS (SELECT r.altid,
                r.itemid,
                r.Buyer,
                r.producttype,
                NewBuyer=Isnull((SELECT TOP 1 x.Buyer
                                 FROM   Products x
                                 WHERE  x.altid = r.altid
                                        AND x.producttype = 1), r.Buyer)
         FROM   CombineALL r), --This will assign new buyer ID to buyer is null and producttype=1
     RevisedBuyer
     AS (SELECT *,
                ( Dense_rank()
                    OVER(
                      PARTITION BY altid
                      ORDER BY Buyer DESC) ) AS SeqNo
         FROM   Assign_Buyer)
SELECT p.AltID,
       p.ItemID,
       ab.Buyer,
       ab.ProductType,
       ab.NewBuyer,
       ( CASE
           WHEN ab.NewBuyer IS NULL
                AND ab.ProductType = 1 THEN (SELECT TOP 1 x.Buyer
                                             FROM   RevisedBuyer x
                                             WHERE  x.altid = ab.altid
                                                    AND x.producttype = 1
                                                    AND x.SeqNo = 1)
           ELSE ab.NewBuyer
         END ) AS Buyer1
FROM   Products p
       JOIN Assign_Buyer ab
         ON p.AltID = ab.AltID
            AND p.Itemid = ab.ItemID
WHERE  Isnull(p.Buyer, '') <> Isnull(CASE
                                       WHEN ab.NewBuyer IS NULL
                                            AND ab.ProductType = 1 THEN (SELECT TOP 1 x.Buyer
                                                                         FROM   RevisedBuyer x
                                                                         WHERE  x.altid = ab.altid
                                                                                AND x.producttype = 1
                                                                                AND x.SeqNo = 1)
                                       ELSE ab.NewBuyer
                                     END, '')
ORDER  BY ab.altid,
          ab.itemid 

1 个答案:

答案 0 :(得分:0)

Here's my Solution to my problems:

    --Save to temporary product table
SELECT b.altid, p.itemid, p.Buyer,p.ProductType INTO #products
    FROM
    (
    --GET ALL ALTID THAT HAS MORE THAN 1 ALTID
    SELECT a.altid FROM
    (
        --GET ALL ALT ID AND GROUP IT BY ALTID AND BUYER
        SELECT p.altid FROM products p
        --WHERE p.BUYER IS NOT NULL
        GROUP BY p.altid,p.ProductType,p.Buyer
        ) a
        GROUP BY a.altid
        HAVING COUNT(*) > 1
    )b
JOIN products p ON b.altid = p.altid
CREATE UNIQUE NONCLUSTERED INDEX idx_product on #products(altid,itemid)
--- End save temp table

---Temporary table for excluded record wherein it has more than 2 buyer with prodcuttype=1
;WITH Count_AltidperBuyer AS
(    
    SELECT altid,Buyer,count(Buyer) Cnt,ProductType
    FROM #products
    Group by altid,Buyer,ProductType
),--List of altid that has more than 2 manufid with producttype=1
exclude_rec AS(
    Select altid,count(Buyer) BuyerCnt
    from Count_AltidperBuyer
    where ProductType=1
    group by altid
    having count(Buyer)>1
)
SELECT altid INTO #excluded_altid FROM exclude_rec
CREATE UNIQUE NONCLUSTERED INDEX idx_excluded_altid on #excluded_altid(altid)
---End of temporary excluded record
;WITH cte AS
(--Does not include records found in #excluded_altid table
    SELECT altid,itemid,Buyer,producttype
    FROM #products 
    WHERE altid NOT IN (SELECT altid FROM #excluded_altid )
), cte2 AS
(
    SELECT a.altid,a.itemid,a.producttype,a.Buyer
    FROM cte a JOIN products p
    ON a.altid=p.altid AND a.itemid=p.itemid
    WHERE p.producttype=1 and p.buyer is null
    UNION
    SELECT a.altid,a.itemid,a.producttype,a.Buyer
    FROM cte a
),Assign_Buyer AS
(
    SELECT  r.altid,r.itemid,r.Buyer,r.producttype,NewBuyer=ISNULL(
    isnull((SELECT Top 1 x.Buyer FROM Products x WHERE x.altid=r.altid and x.producttype=1),r.Buyer),
    (SELECT Top 1 x.Buyer FROM Products x WHERE x.altid=r.altid and x.producttype=1 AND x.Buyer IS NOT NULL))
    FROM cte2 r
)--,--This will assign new buyer ID to buyer is null and producttype=1
--Assign_BuyerToNewBuyer AS
--(
--  SELECT  ab.altid,ab.itemid,ab.Buyer,ab.producttype,NewBuyer, NewBuyer2=(CASE WHEN NewBuyer IS NULL
--      THEN (SELECT Top 1 x.Buyer FROM Products x WHERE x.altid=ab.altid and x.producttype=1 AND x.Buyer IS NOT NULL)
--      ELSE NewBuyer END)
--  FROM Assign_Buyer ab
--  --SELECT *, (DENSE_RANK() OVER(PARTITION BY altid order by Buyer DESC)) AS SeqNo
--  --FROM Assign_Buyer
--)
SELECT p.AltID,p.ItemID,ab.Buyer,ab.ProductType,ab.NewBuyer
FROM Products p JOIN Assign_Buyer ab
ON p.AltID=ab.AltID AND p.Itemid=ab.ItemID
WHERE isnull(p.Buyer,'') <> isnull(ab.NewBuyer,'')
ORDER by ab.altid, ab.itemid

DROP TABLE #products
DROP TABLE #excluded_altid

SQL performance improves well compare to my old queries. What I did, I create a temporary table with index and then use WITH cte. If you have any other solution for this problems, please post your answer.

Thanks to @JamesZ for the comments and idea. Here's the link to SQL Fiddler for reference.