我应该在这种情况下使用APPLY吗?

时间:2012-10-10 10:42:23

标签: sql sql-server sql-server-2008-r2

包含的代码是我们情况的简化版本;相当于#MyExample的生产表有20个字段,所有这些字段都需要中位数计算,因此脚本的第二部分变得非常长 - 这不是一个巨大的难题,但有更紧凑的解决方案吗?

我没有使用APPLY或自定义FUNCTION的经验,但是我们应该为中位数创建FUNCTION,然后使用APPLY I' m猜不是应用于每一行吗?

/*
DROP TABLE #MyExample
DROP TABLE #mediantable
*/

CREATE TABLE #MyExample
        (
        customer char(5),
        amountPeriodA numeric(36,8),
        amountPeriodB numeric(36,8),
        amountPeriodC numeric(36,8)
        )
INSERT INTO #MyExample
        values
        ('a',10,20,30),
        ('b',5,10,15),
        ('c',500,100,150),
        ('d',5,1,1),
        ('e',5,1,15),
        ('f',5,10,150),
        ('g',5,100,1500)




SELECT 
        [Period] = 'amountPeriodA',             
        [Median] = AVG(x.amountPeriodA)         
INTO    #mediantable
FROM (
        SELECT 
                r.customer,
                r.amountPeriodA,
                [RowASC] = ROW_NUMBER() OVER(ORDER BY r.amountPeriodA ASC, customer ASC),
                [RowDESC] = ROW_NUMBER() OVER(ORDER BY r.amountPeriodA DESC, customer DESC)
        FROM #MyExample r 
    ) x
WHERE RowASC IN (RowDESC, ROWDESC-1, ROWDESC+1)

union
SELECT 
        [Period] = 'amountPeriodB',             
        [Median] = AVG(x.amountPeriodB)         
FROM (
        SELECT 
                r.customer,
                r.amountPeriodB,
                [RowASC] = ROW_NUMBER() OVER(ORDER BY r.amountPeriodB ASC, customer ASC),
                [RowDESC] = ROW_NUMBER() OVER(ORDER BY r.amountPeriodB DESC, customer DESC)
        FROM #MyExample r 
    ) x
WHERE RowASC IN (RowDESC, ROWDESC-1, ROWDESC+1)

union
SELECT 
        [Period] = 'amountPeriodC',             
        [Median] = AVG(x.amountPeriodC)         
FROM (
        SELECT 
                r.customer,
                r.amountPeriodC,
                [RowASC] = ROW_NUMBER() OVER(ORDER BY r.amountPeriodC ASC, customer ASC),
                [RowDESC] = ROW_NUMBER() OVER(ORDER BY r.amountPeriodC DESC, customer DESC)
        FROM #MyExample r 
    ) x
WHERE RowASC IN (RowDESC, ROWDESC-1, ROWDESC+1)


SELECT * 
FROM #mediantable

2 个答案:

答案 0 :(得分:0)

我正在思考:

DECLARE @rowcount int
DECLARE @first int
DECLARE @last int

SELECT @rowcount = COUNT(*) FROM #MyExample

SELECT @first = (CASE WHEN @rowcount % 2 = 1 THEN (@rowcount + 1) / 2 ELSE (@rowcount / 2)     END),
       @last  = (CASE WHEN @rowcount % 2 = 1 THEN (@rowcount + 1) / 2 ELSE (@rowcount / 2) + 1 END)

SELECT [Period],  
       [Median] = AVG(Amount)
  FROM (SELECT [Period] = 'amountPeriodA',
               Amount   = amountPeriodA, 
               rownbr   = ROW_NUMBER() OVER(ORDER BY amountPeriodA ASC, customer ASC)
          FROM #MyExample

         UNION ALL

        SELECT [Period] = 'amountPeriodB',
               Amount   = amountPeriodB, 
               rownbr   = ROW_NUMBER() OVER(ORDER BY amountPeriodB ASC, customer ASC)
          FROM #MyExample

        UNION ALL

        SELECT [Period] = 'amountPeriodC',
               Amount   = amountPeriodC, 
               rownbr   = ROW_NUMBER() OVER(ORDER BY amountPeriodC ASC, customer ASC)
          FROM #MyExample

          ) r
 WHERE rownbr IN (@first, @last)
 GROUP BY [Period]

这看起来效果不错,输入的次数少了一点,而且速度也快了......但它仍然很“大”。

PS:使用UNION ALL而不是UNION,否则服务器将尝试将最终结果变为“不同”记录,在这种情况下不需要。 (期间无论如何都会让它变得独一无二!)

答案 1 :(得分:0)

基于我之前的回复,我得到了这个,这更容易(和更短)扩展列数,甚至运行更快(在20+列的情况下可能更快!)。但是,它会水平返回结果而不是垂直返回结果。这可以使用UNPIVOT再次“解决”。 我使用中间的#result表完成了两部分操作;但您可以使用子查询或CTE在单个语句中轻松完成。

DECLARE @rowcount int
DECLARE @first int
DECLARE @last int
DECLARE @divider numeric(36,8)

SELECT @rowcount = COUNT(*) FROM #MyExample

SELECT @first = (CASE WHEN @rowcount % 2 = 1 THEN (@rowcount + 1) / 2 ELSE (@rowcount / 2)     END),
       @last  = (CASE WHEN @rowcount % 2 = 1 THEN (@rowcount + 1) / 2 ELSE (@rowcount / 2) + 1 END),
       @divider = (CASE WHEN @rowcount % 2 = 1 THEN 1 ELSE 2 END)

 SELECT amountPeriodA = SUM(amountPeriodA) / @divider,
        amountPeriodB = SUM(amountPeriodB) / @divider,
        amountPeriodC = SUM(amountPeriodC) / @divider    
   INTO #result
   FROM
 (
 SELECT amountPeriodA = ((CASE WHEN ROW_NUMBER() OVER(ORDER BY amountPeriodA ASC, customer ASC) IN (@first, @last) THEN amountPeriodA ELSE 0.00 END)),
        amountPeriodB = ((CASE WHEN ROW_NUMBER() OVER(ORDER BY amountPeriodB ASC, customer ASC) IN (@first, @last) THEN amountPeriodB ELSE 0.00 END)),
        amountPeriodC = ((CASE WHEN ROW_NUMBER() OVER(ORDER BY amountPeriodC ASC, customer ASC) IN (@first, @last) THEN amountPeriodC ELSE 0.00 END)) 
   FROM #MyExample
  )t 

然后

  SELECT [Period], [Amount] 
    FROM #result as x
    UNPIVOT ( [Amount] FOR Period IN (amountPeriodA, amountPeriodB, amountPeriodC)) As unpvt