Tengo una tabla que incluye una columna de valores decimales, como este:
id value size
-- ----- ----
1 100 .02
2 99 .38
3 98 .13
4 97 .35
5 96 .15
6 95 .57
7 94 .25
8 93 .15
Lo que necesito lograr es un poco difícil de describir, así que tengan paciencia conmigo. Lo que intento hacer es crear un valor agregado de la size
columna que se incremente en 1 cada vez que las filas anteriores sumen 1, cuando están en orden descendente de acuerdo con value
. El resultado se vería así:
id value size bucket
-- ----- ---- ------
1 100 .02 1
2 99 .38 1
3 98 .13 1
4 97 .35 1
5 96 .15 2
6 95 .57 2
7 94 .25 2
8 93 .15 3
Mi primer intento ingenuo fue mantener un funcionamiento SUM
y luego CEILING
ese valor, sin embargo, no maneja el caso donde algunos registros size
terminan contribuyendo al total de dos cubos separados. El siguiente ejemplo podría aclarar esto:
id value size crude_sum crude_bucket distinct_sum bucket
-- ----- ---- --------- ------------ ------------ ------
1 100 .02 .02 1 .02 1
2 99 .38 .40 1 .40 1
3 98 .13 .53 1 .53 1
4 97 .35 .88 1 .88 1
5 96 .15 1.03 2 .15 2
6 95 .57 1.60 2 .72 2
7 94 .25 1.85 2 .97 2
8 93 .15 2.00 2 .15 3
Como puede ver, si simplemente usara CEILING
en el crude_sum
registro # 8 se asignaría al depósito 2. Esto se debe a que los size
registros # 5 y # 8 se dividen en dos depósitos. En cambio, la solución ideal es restablecer la suma cada vez que llega a 1, que luego incrementa la bucket
columna y comienza una nueva SUM
operación que comienza en el size
valor del registro actual. Debido a que el orden de los registros es importante para esta operación, he incluido la value
columna, que debe ordenarse en orden descendente.
Mis intentos iniciales han implicado hacer múltiples pases sobre los datos, una vez para realizar la SUM
operación, una vez más para CEILING
eso, etc. Aquí hay un ejemplo de lo que hice para crear la crude_sum
columna:
SELECT
id,
value,
size,
(SELECT TOP 1 SUM(size) FROM table t2 WHERE t2.value<=t1.value) as crude_sum
FROM
table t1
Que se utilizó en una UPDATE
operación para insertar el valor en una tabla para trabajar más tarde.
Editar: me gustaría dar otra puñalada para explicar esto, así que aquí va. Imagine que cada registro es un elemento físico. Ese artículo tiene un valor asociado y un tamaño físico menor que uno. Tengo una serie de cubos con una capacidad de volumen de exactamente 1, y necesito determinar cuántos de estos cubos necesitaré y en qué cubo entra cada artículo de acuerdo con el valor del artículo, ordenados de mayor a menor.
Un elemento físico no puede existir en dos lugares a la vez, por lo que debe estar en un cubo u otro. Es por eso que no puedo hacer una CEILING
solución total + , porque eso permitiría que los registros contribuyan con su tamaño a dos cubos.
distinct_count
complica las cosas. Aaron Bertrand tiene un excelente resumen de sus opciones en SQL Server para este tipo de trabajo de ventanas. He utilizado el método de "actualización peculiar" para calculardistinct_sum
, que puedes ver aquí en SQL Fiddle , pero esto no es confiable.Respuestas:
No estoy seguro de qué tipo de rendimiento está buscando, pero si CLR o la aplicación externa no son una opción, lo único que queda es un cursor. En mi vieja computadora portátil paso por 1,000,000 de filas en aproximadamente 100 segundos usando la siguiente solución. Lo bueno de esto es que se escala linealmente, por lo que estaría mirando unos 20 minutos para recorrer todo. Con un servidor decente, será más rápido, pero no un orden de magnitud, por lo que aún le tomará varios minutos completar esto. Si este es un proceso único, probablemente pueda permitirse la lentitud. Si necesita ejecutar esto como un informe o similar con regularidad, es posible que desee almacenar los valores en la misma tabla y actualizarlos a medida que se agregan nuevas filas, por ejemplo, en un desencadenante.
De todos modos, aquí está el código:
Cae y recrea la tabla MyTable, la llena con 1000000 filas y luego se pone a trabajar.
El cursor copia cada fila en una tabla temporal mientras ejecuta los cálculos. Al final, la selección devuelve los resultados calculados. Puede ser un poco más rápido si no copia los datos, sino que realiza una actualización en su lugar.
Si tiene una opción para actualizar a SQL 2012, puede ver los nuevos agregados de ventana móvil compatibles con spool de ventana, que deberían proporcionarle un mejor rendimiento.
En una nota al margen, si tiene un ensamblado instalado con permission_set = safe, puede hacer más cosas malas a un servidor con T-SQL estándar que con el ensamblaje, por lo que seguiría trabajando para eliminar esa barrera. Tiene un buen uso caso aquí donde CLR realmente te ayudaría.
fuente
En ausencia de las nuevas funciones de ventanas en SQL Server 2012, las ventanas complejas se pueden lograr con el uso de CTE recursivos. Me pregunto qué tan bien funcionará esto en millones de filas.
La siguiente solución cubre todos los casos que describió. Puede verlo en acción aquí en SQL Fiddle .
Ahora respira hondo. Aquí hay dos CTE clave, cada uno precedido por un breve comentario. El resto son solo CTE de "limpieza", por ejemplo, para extraer las filas correctas después de haberlas clasificado.
Esta solución supone que
id
es una secuencia sin espacios. De lo contrario, deberá generar su propia secuencia sin espacios agregando un CTE adicional al principio que numere las filasROW_NUMBER()
según el orden deseado (por ejemploROW_NUMBER() OVER (ORDER BY value DESC)
).Fankly, esto es bastante detallado.
fuente
crude_sum
condistinct_sum
susbucket
columnas asociadas para ver a qué me refiero.Esto se siente como una solución tonta, y probablemente no escalará bien, así que pruebe cuidadosamente si lo usa. Como el problema principal proviene del "espacio" que queda en el cubo, primero tuve que crear un registro de relleno para unir los datos.
http://sqlfiddle.com/#!3/72ad4/14/0
fuente
La siguiente es otra solución recursiva de CTE, aunque diría que es más sencillo que la sugerencia de @ Nick . En realidad, está más cerca del cursor de @ Sebastian , solo que utilicé diferencias en lugar de totales. (Al principio incluso pensé que la respuesta de @ Nick iba a estar en la línea de lo que estoy sugiriendo aquí, y es después de saber que la suya fue una pregunta muy diferente que decidí ofrecer la mía).
Nota: esta consulta supone que la
value
columna consta de valores únicos sin espacios. Si ese no es el caso, deberá introducir una columna de clasificación calculada en función del orden descendentevalue
y utilizarla en el CTE recursivo en lugar devalue
unir la parte recursiva con el ancla.Puede encontrar una demostración de SQL Fiddle para esta consulta aquí .
fuente
size
conroom_left
) en lugar de comparar un solo valor con una expresión (1
conrunning_size
+size
). Al principio no usé unais_new_bucket
bandera sino variasCASE WHEN t.size > r.room_left ...
("varias" porque también estaba calculando (y devolviendo) el tamaño total, pero luego pensé en contra de eso por simplicidad), así que pensé que sería más elegante de esa manera.