2012-02-09 10 views
11

Supongamos que tenemos una tabla de "Cars" con cientos de miles de filas, y que quería hacer un GROUP BY:grupo T-SQL con el Conde, y después incluir MAX a partir del recuento

SELECT CarID 
     , CarName 
     , COUNT(*) AS Total 
FROM  dbo.tbl_Cars 
GROUP BY CarID 
     , CarName 

La agrupación le deja con un resultado parecido a:

CarID  CarName Total 
1872  Olds  202,121 
547841  BMW  175,298 
9877  Ford  10,241 

Todo bien y bien. Mi pregunta, sin embargo, es lo que es la mejor manera de conseguir el total y el total máximo en una tabla, en términos de rendimiento y codificación limpia, por lo que tiene un resultado como:

CarID  CarName Total  Max Total 
1872  Olds  202,121 202,121 
547841  BMW  175,298 202,121 
9877  Ford  10,241 202,121 

Un enfoque sería poner el resultado del GRUPO en una tabla temporal, y luego obtener el MAX de la tabla temporal en una variable local. Pero me pregunto cuál sería la mejor manera de hacerlo.


ACTUALIZACIÓN

La expresión de tabla común parece ser la más elegante para escribir, sin embargo similar a @EBarr, mi prueba limitada indica un rendimiento significativamente más lento. Así que no iré con el CTE.

Como el enlace @EBarr tiene para la opción COMPUTE indica que la característica está obsoleta, esa tampoco parece ser la mejor ruta.

La opción de una variable local para el valor MAX y el uso de una tabla temporal es probable que sea la ruta que baje, ya que no soy consciente de los problemas de rendimiento con él.

Un poco más de detalle sobre mi caso de uso: probablemente podría terminar siendo una serie de otras preguntas de SO. Pero basta con decir que estoy cargando un gran subconjunto de datos en una tabla temporal (por lo que un subconjunto de tbl_Cars es entrando en #tbl_Cars, e incluso #tbl_Cars pueden filtrarse adicionalmente y tener agregaciones realizadas en él), porque Tengo que realizar múltiples filtraciones y consultas de agregación en él dentro de un único proceso almacenado que devuelve múltiples conjuntos de resultados.


ACTUALIZACIÓN 2

@ utilización de EBarr de una función de ventana es agradable y corto. Nota a uno mismo: si usa un RIGHT JOIN en una tabla de referencia externa, la función COUNT() debe seleccionar una columna de tbl_Cars, no '*'.

SELECT  M.MachineID 
      , M.MachineType 
      , COUNT(C.CarID) AS Total 
      , MAX(COUNT(C.CarID)) OVER() as MaxTotal 
FROM   dbo.tbl_Cars C 
RIGHT JOIN dbo.tbl_Machines M 
     ON  C.CarID = M.CarID 
GROUP BY  M.MachineID 
      , M.MachineType 

En términos de velocidad, parece muy bien, pero ¿en qué punto hay que ser preocupado por el número de lecturas?

Respuesta

13

Mecánicamente hay algunas formas de hacerlo. Puede usar tablas temporales/variables de tabla. Otra forma es con consultas anidadas y/o un CTE como @Aaron_Bertrand mostró. Una tercera forma es usar FUNCIONES DE VENTANA tales como ...

SELECT CarName, 
      COUNT(*) as theCount, 
      MAX(Count(*)) OVER(PARTITION BY 'foo') as MaxPerGroup 
FROM  dbo.tbl_Cars 
GROUP BY CarName 

Un desfavorecidos (leer depricated) cuarta forma es utilizar la palabra clave COMPUTE como tal ...

SELECT CarID, CarName, Count(*) 
FROM  dbo.tbl_Cars 
GROUP BY CarID, CarName 
COMPUTE MAX(Count(*)) 

La palabra clave COMPUTE genera los totales que aparecen como columnas de resumen adicionales al final de la conjunto de resultados (see this). En la consulta anterior, verá dos conjuntos de registros.

más rápido

Ahora, la siguiente cuestión es ¿cuál es la "mejor/más rápido/más fácil." Inmediatamente pienso en un indexed view. Como @Aaron suavemente me recordó, las vistas indexadas tienen todo tipo de restricciones. La estrategia anterior, sin embargo, le permite crear una vista indizada en SELECT ... FROM..GROUP BY. Luego, seleccionando desde la vista indexada, aplica la cláusula FUNCIÓN DE WINDOWED.

Sin saber más, sin embargo, sobre su diseño va a ser difícil para cualquiera decir lo mejor. Obtendrá consultas rápidas de iluminación desde una vista indexada. Sin embargo, ese rendimiento tiene un precio. El precio es costos de mantenimiento. Si la tabla subyacente es el objetivo de una gran cantidad de operaciones de inserción/actualización/eliminación, el mantenimiento de la vista indexada reducirá el rendimiento en otras áreas.

Si comparte un poco más acerca de su caso de uso y los patrones de acceso a los datos, las personas podrán compartir más información.


la Micro PRUEBA

Así que genera un pequeño script de datos y miraron a los números del Analizador de SQL para el cumplimiento CTE vs Las funciones de división. Esta es una prueba micro, así que intente con algunos números reales en su sistema bajo carga real.

generación de datos:

Create table Cars (CarID int identity (1,1) primary key, 
        CarName varchar(20), 
        value int) 
GO 
insert into Cars (CarName, value) 
values ('Buick', 100), 
     ('Ford', 10), 
     ('Buick', 300),  
     ('Buick', 100), 
     ('Pontiac', 300),  
     ('Bmw', 100), 
     ('Mecedes', 300),  
     ('Chevy', 300),  
     ('Buick', 100), 
     ('Ford', 200); 
GO 1000 

Este script genera 10.000 filas. Entonces corrió cada uno de los cuatro siguientes consultas varias veces:

--just group by 
select CarName,COUNT(*) countThis 
FROM Cars 
GROUP BY CarName   

--group by with compute (BAD BAD DEVELOPER!) 
select CarName,COUNT(*) countThis 
FROM Cars 
GROUP BY CarName   
COMPUTE MAX(Count(*)); 

-- windowed aggregates... 
SELECT CarName, 
     COUNT(*) as theCount, 
     MAX(Count(*)) OVER(PARTITION BY 'foo') as MaxInAnyGroup 
FROM Cars 
GROUP BY CarName   

--CTE version 
;WITH x AS (
    SELECT CarName, 
      COUNT(*) AS Total 
    FROM  Cars 
    GROUP BY CarName 
) 
SELECT x.CarName, x.Total, x2.[Max Total] 
FROM x CROSS JOIN (
    SELECT [Max Total] = MAX(Total) FROM x 
) AS x2; 

Después de ejecutar las consultas anteriores, he creado una vista indizada en el "grupo simplemente por" consulta anterior. Luego ejecuté una consulta en la vista indizada que realizó un MAX(Count(*)) OVER(PARTITION BY 'foo'.

PROMEDIO RESULTADOS

Query      CPU  Reads  Duration 
-------------------------------------------------------- 
Group By     15  31  7 ms 
Group & Compute   15  31  7 ms 
Windowed Functions   14  56  8 ms 
Common Table Exp.   16  62  15 ms 
Windowed on Indexed View 0  24  0 ms 

Obviamente, esto es un micro-punto de referencia y sólo ligeramente instructiva, por lo tome por lo que vale la pena.

+0

no puede utilizar 'MAX' en una vista indizada (he estado pidiéndolo durante 5 años - http://connect.microsoft.com/SQLServer/feedback/details/267516/expand-aggregate-support-in-indexed-views-min-max). Además 'theFieldBeingSearchedForMax' no está en la tabla, es parte del resultado (es el recuento más alto). –

+0

Solo vuelve a leer la pregunta. Lo malinterpreté Actualizando SQL. – EBarr

+0

- lo siento, no pude agregar el GROUP BY en la primera consulta; mi error. – mg1075

8

Aquí hay una manera:

;WITH x AS 
(
    SELECT CarID 
     , CarName 
     , COUNT(*) AS Total 
    FROM  dbo.tbl_Cars 
    GROUP BY CarID, CarName 
) 
SELECT x.CarID, x.CarName, x.Total, x2.[Max Total] 
FROM x CROSS JOIN 
(
    SELECT [Max Total] = MAX(Total) FROM x 
) AS x2; 
0

SQL Server 2008 R2 y las versiones más recientes, puede utilizar:

GROUP BY CarID, CarName WITH ROLLUP 
Cuestiones relacionadas