通过Min / Max / Avg操作对表中的值进行分组

时间:2011-04-05 07:20:57

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

以下是我的情景:

我有一个包含2列的单个表。 ID和价值。 ID为int,值为real。

ID    Value
1     6.7
2     8.9
3     4.5
5     3.2
8     2.5
9     2.1
10    1.0
15    2.3
18    2.4
19    4.0
20    3.2

我想编写一个接收分组编号(Group)和操作(Op)的SP,并按以下方式返回一个新表:

组= 2, Op = Max

IDstart   IDend    Value
1         2        8.9
3         5        4.5
8         9        2.5
10        15       2.3
18        19       4.0 
20        20       3.2

组= 3, Op = Min

IDstart   IDend    Value
1         3        4.5
5         9        2.1
10        18       1.0
19        20       3.2

组定义要合并为一行(在新表中)的行数,并且操作定义要对行组执行的操作,我需要的操作是最大,最小和平均。 最后一组可能包含的行数少于其他所有组。如果最后一个组具有单个值IDstart = IDEnd。 ID是唯一的,但可能有“差距”。

我正在寻找最快的方法,我们将不胜感激。

使用SQL Server 2008 R2

吉拉德。

3 个答案:

答案 0 :(得分:1)

您可能会觉得这很有用:

SET @idx = 0;
SET @grp_size = 3;
SELECT MIN(`temp1`.`id`) as `IDstart`, MAX(`temp1`.`id`) as `IDend`, AVG(`temp1`.`value`) as `agregate`
FROM (
    SELECT ID AS `id` , @idx := @idx +1 / @grp_size , FLOOR( @idx ) AS `grouper`, `value`
    FROM `test1`
) as `temp1`
GROUP BY `temp1`.`grouper`

它适用于MySQL,但对于SQL Server应该类似。

答案 1 :(得分:1)

推理就像这样

  • 使用ROW_NUMBER()函数和某些算法可以创建一个虚拟列,将每个ID放在指定大小的组中。
  • 可以对此语句的结果进行分组,并使用CASE语句应用您指定的运算符。如果您需要其他运算符,则只需展开此CASE语句。

<强>脚本

DECLARE @Group INTEGER
DECLARE @Op VARCHAR(3)

SET @Group = 3
SET @Op = 'MIN'

;WITH q(ID, Value) AS (
  SELECT 1,     6.7
  UNION ALL SELECT 2,     8.9
  UNION ALL SELECT 3,     4.5
  UNION ALL SELECT 5,     3.2
  UNION ALL SELECT 8,     2.5
  UNION ALL SELECT 9,     2.1
  UNION ALL SELECT 10,    1.0
  UNION ALL SELECT 15,    2.3
  UNION ALL SELECT 18,    2.4
  UNION ALL SELECT 19,    4.0
  UNION ALL SELECT 20,    3.2
)
SELECT  [IDStart] = MIN(ID)
        , [IDEnd] = MAX(ID)
        , [Value] = CASE  WHEN @Op = 'MAX' THEN MAX(Value)
                          WHEN @Op = 'MIN' THEN MIN(Value)
                          WHEN @Op = 'AVG' THEN AVG(Value)
                    END
FROM    (
          SELECT ID
                 , Value
                 , GroupRow = (ROW_NUMBER() OVER (ORDER BY ID) - 1) / @Group
          FROM    q
        ) q          
GROUP BY
        GroupRow

答案 2 :(得分:0)

这似乎符合您的要求。将@op参数的值更改为MIN,MAX或AVG,将@Group参数更改为组的大小。 NTILE排名函数用于对组进行分区,然后ROW_NUMBER以标识每个组的第一个/ lat成员。

DECLARE @t TABLE
(id INT,
 VALUE REAL
)

INSERT @t (id,VALUE)
VALUES 
(1,     6.7),
(2,     8.9),
(3,     4.5),
(5,     3.2),
(8,     2.5),
(9,     2.1),
(10,    1.0),
(15,    2.3),
(18,    2.4),
(19,    4.0),
(20,    3.2)

DECLARE @Group DECIMAL(5,1) = 3.0
DECLARE @Bucket INT
DECLARE @op char(3) = 'MIN' --MAX, AVG
SELECT @Bucket = CEILING(COUNT(1)/@Group)
FROM @t

;WITH bucketCTE
AS
(
    SELECT *,NTILE(@Bucket) OVER (ORDER BY id) bucket
    FROM @t
)
,rankCTE
AS
(
    SELECT *, ROW_NUMBER() OVER (PARTITION BY bucket
                                 ORDER BY id ASC
                                ) AS rn,
            ROW_NUMBER() OVER (PARTITION BY bucket
                                 ORDER BY id DESC
                                ) AS rn2
    FROM bucketCTE
)
,groupCTE
AS
(
    SELECT AVG(VALUE) average, MIN(VALUE) minimum, MAX(VALUE) maximum,  bucket
    FROM bucketCTE
    GROUP BY bucket

)
SELECT r1.id minId, r2.id maxId , CASE  WHEN @op = 'AVG' THEN g.average
                                        WHEN @op = 'MIN' THEN g.minimum
                                        WHEN @op = 'MAX' THEN g.maximum
                                        ELSE NULL
                                  END AS value
FROM rankCTE AS r1
JOIN rankCTE AS r2
ON   r2.bucket = r1.bucket
AND  r2.rn2 = 1
JOIN groupCTE AS g
ON   g.bucket = r1.bucket
WHERE r1.rn = 1
ORDER BY r1.bucket
相关问题