2012-08-17 18 views
5

Digamos que tengo una tabla que contiene muchas filas de esta manera:fila TSQL Seleccionar Min & Max al agrupar

ID  Range   Range_begining  Profit 
---------------------------------------------------- 
1 (100-150)     100   -20 
2 (200-250)     200   40.2 
3 (100-150)     100   100 
4 (450-500)     450   -90 
... 

que estoy haciendo una consulta simple como esto:

SELECT max([Range]) AS 'Range' 
    , count(ID) AS 'Count' 
    , round(avg([Profit]), 2) AS 'AVG Profit' 
    FROM 
     Orders 
    GROUP BY 
     Range_begining 

Después de esto consulta se ejecuta consigo resultados como este:

Range  Count  AVG Profit 
------------------------------------ 
(100-150)  2    40 
(200-250)  1    40.2 
(450-500)  1    -90 
... 

bastante simple :)

Lo que hay que hacer ahora es seleccionar fila con mínimo y máximo beneficio en el recuento es mayor que 10 (este es un parámetro)

yo era capaz de obtener valor mínimo con esto:

SELECT TOP 1 [Range], [AVG Profit] FROM (
    SELECT max([Range]) AS 'Range' 
     , count(ID) AS 'Count' 
     , round(avg([Profit]), 2) AS 'AVG Profit' 
     FROM 
      Orders 
     GROUP BY 
      Range_begining) X 
WHERE 
    [Count]>10 
ORDER BY 
    [AVG Profit] ASC --or DESC if I want max profit 

Estaba pensando en hacer un UNION para la consulta anterior con ORDER BY DESC, pero no es la mejor solución.

lo que tengo que hacer:
Seleccionar 2 filas: una con mínimo, el segundo con el máximo beneficio cuando AVG agrupación por rango.

EDIT: Si agrego 2 columnas se mueven a mi mesa de datos principal de esta manera:

ID  Range   Range_begining  Profit  OrderDate  Company 
--------------------------------------------------------------------------------- 
1 (100-150)     100   -20  2012-01-02   1 
2 (200-250)     200   40.2  2012-03-22   0 
3 (100-150)     100   100  2012-02-05   0 
4 (450-500)     450   -90  2012-05-12   1 
... 

Y luego tratar de añadir 2 Condiciones de la misma familia:

; with ordering as (
    SELECT max([Range]) AS 'Range' 
    , count(ID) AS 'Count' 
    , round(avg([Profit]), 2) AS 'AVG Profit' 
    , row_number() over (order by avg([Profit])) rn_min 
    , row_number() over (order by avg([Profit]) desc) rn_max 
    FROM 
     Orders 
    GROUP BY 
     Range_begining 
    HAVING COUNT(ID) > 10 
    AND [Company][email protected] 
    AND (@from= '' OR [OrderDate]>[email protected]) 
    AND (@to= '' OR [OrderDate]<[email protected]) 
) 
select [range], [count], [avg profit] 
    from ordering 
where (rn_max = 1 or rn_min = 1) 

I Obtiene un error porque [Empresa] y [Fecha de pedido]

no es válido en la cláusula HAVING porque no está contenido en una función de agregado o la cláusula GROUP BY.

¿Cómo puedo reparar esto?

EDIT2 Got it working!

; with ordering as (
    SELECT max([Range]) AS 'Range' 
    , count(ID) AS 'Count' 
    , round(avg([Profit]), 2) AS 'AVG Profit' 
    , row_number() over (order by avg([Profit])) rn_min 
    , row_number() over (order by avg([Profit]) desc) rn_max 
    FROM 
     Orders 
    WHERE 
    [Company][email protected] 
    AND (@from= '' OR [OrderDate]>[email protected]) 
    AND (@to= '' OR [OrderDate]<[email protected]) 
    GROUP BY 
     Range_begining 
    HAVING COUNT(ID) > 10 
) 
select [range], [count], [avg profit] 
    from ordering 
where (rn_max = 1 or rn_min = 1) 

EDITAR 3 ¿Puedo devolver otra columna con la descripción de esta manera:

Range  AVG Profit    Description 
------------------------------------------------- 
(200-250)   40.2   Max profit here 
(450-500)   -90  Min profit, well done 

EDITAR 4 respuesta rápida (basado en @Nikola Markovinović respuesta):

; with ordering as (
    SELECT max([Range]) AS 'Range' 
    , count(ID) AS 'Count' 
    , round(avg([Profit]), 2) AS 'AVG Profit' 
    , row_number() over (order by avg([Profit])) rn_min 
    , row_number() over (order by avg([Profit]) desc) rn_max 
    FROM 
     Orders 
    WHERE 
    [Company][email protected] 
    AND (@from= '' OR [OrderDate]>[email protected]) 
    AND (@to= '' OR [OrderDate]<[email protected]) 
    GROUP BY 
     Range_begining 
    HAVING COUNT(ID) > 10 
) 
    SELECT 
    CASE WHEN rn_max=1 THEN 'This is max' ELSE 'Min' END AS 'Description' 
    ,[range] 
    ,[count] 
    ,[avg profit] 
    FROM ordering 
    WHERE (rn_max = 1 or rn_min = 1) 
+2

Asegúrese de agregar la etiqueta 'SQL' a sus preguntas para obtener más atención. –

Respuesta

7

Es posible hacerlo a la vez utilizando window functions:

; with ordering as (
    SELECT max([Range]) AS 'Range' 
    , count(ID) AS 'Count' 
    , round(avg([Profit]), 2) AS 'AVG Profit' 
    , row_number() over (order by avg([Profit])) rn_min 
    , row_number() over (order by avg([Profit]) desc) rn_max 
    FROM 
     Orders 
    GROUP BY 
     Range_begining 
    HAVING COUNT(ID) > 10 
) 
select [range], [count], [avg profit], 
     case when rn_max = 1 
      then 'Max profit' 
      else 'Min profit' 
     end Description 
    from ordering 
where (rn_max = 1 or rn_min = 1) 

And here is Sql Fiddle example.

+1

De nuevo, debo admitir que ERES EXCELENTE !!! :) – Misiu

+0

@Misiu Acabo de hacer algunas pruebas (limitadas) y en mi caso (700000 facturas, agrupadas por cliente, promediando el valor de factura) ambos enfoques funcionan rápido, pero row_number() es casi dos veces más rápido que union all. –

+0

Lo estoy probando. Parece que su solución es mucho más rápida, la agrupación se realiza solo una vez. Me gustaría saber SQL como lo hace;) – Misiu

2

Aquí hay un SQLFiddle example.En su última consulta pregunta puede utilizar HAVING en lugar de la consulta anidada:

select * from 
(SELECT TOP 1 
    max([Range]) AS 'Range' 
    , count(ID) AS 'Count' 
    , round(avg([Profit]), 2) AS 'AVG Profit' 
    FROM 
     Orders 
    GROUP BY 
     Range_begining 
    having count(id)>10 
    order by round(avg([Profit]), 2) ASC 
) a 
union all 
select * from 
(
SELECT TOP 1 
    max([Range]) AS 'Range' 
    , count(ID) AS 'Count' 
    , round(avg([Profit]), 2) AS 'AVG Profit' 
    FROM 
     Orders 
    GROUP BY 
     Range_begining 
    having count(id)>10 
    order by round(avg([Profit]), 2) desc 
)b 
+1

Gracias por una respuesta tan rápida :) Me pregunto si esto será rápido. Como puede ver, la parte superior e inferior es casi la misma (asc/desc está cambiando). ¿Esto se puede hacer? Una opción que me viene a la mente es seleccionar datos agrupados en una tabla temporal y luego seleccionar min & max row. Tal vez esto sea más rápido, además de que en tu (y la mía) solución tenemos que agrupar los datos 2 veces. Si tengo 100 filas, está bien, pero si tendré 10KK (10 millones), esto será un poco largo. – Misiu

Cuestiones relacionadas