Eu tenho uma tabela que inclui uma coluna de valores decimais, como esta:
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
O que eu preciso realizar é um pouco difícil de descrever, por isso, tenha paciência comigo. O que estou tentando fazer é criar um valor agregado da size
coluna que incrementa em 1 cada vez que as linhas anteriores somam 1, quando em ordem decrescente de acordo com value
. O resultado seria algo parecido com isto:
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
Minha primeira tentativa ingênua foi manter um valor em execução SUM
e, em seguida, CEILING
esse valor, no entanto, ele não lida com o caso em que alguns registros size
acabam contribuindo para o total de dois depósitos separados. O exemplo abaixo pode esclarecer isso:
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 você pode ver, se eu fosse usar simplesmente CEILING
no crude_sum
registro # 8 seria atribuído a bucket 2. Isto é causado pelo size
de registros # 5 e # 8 sendo dividido em dois baldes. Em vez disso, a solução ideal é redefinir a soma cada vez que atingir 1, que depois incrementa a bucket
coluna e inicia uma nova SUM
operação começando com o size
valor do registro atual. Como a ordem dos registros é importante para esta operação, incluí a value
coluna, que deve ser classificada em ordem decrescente.
Minhas tentativas iniciais envolveram fazer várias passagens sobre os dados, uma vez para executar a SUM
operação, mais uma vez para CEILING
isso, etc. Aqui está um exemplo do que eu fiz para criar a crude_sum
coluna:
SELECT
id,
value,
size,
(SELECT TOP 1 SUM(size) FROM table t2 WHERE t2.value<=t1.value) as crude_sum
FROM
table t1
Que foi usado em um UPDATE
operação para inserir o valor em uma tabela para trabalhar posteriormente.
Edit: Eu gostaria de dar uma outra facada em explicar isso, então aqui vai. Imagine que cada registro é um item físico. Esse item tem um valor associado a ele e um tamanho físico menor que um. Eu tenho uma série de buckets com uma capacidade de volume de exatamente 1 e preciso determinar quantos desses buckets serão necessários e qual bucket cada item entra de acordo com o valor do item, classificado do maior para o menor.
Um item físico não pode existir em dois lugares ao mesmo tempo; portanto, ele deve estar em um balde ou no outro. É por isso que não consigo fazer uma CEILING
solução total + em execução , porque isso permitiria que os registros contribuíssem com seu tamanho para dois buckets.
distinct_count
complica as coisas. Aaron Bertrand tem um ótimo resumo de suas opções no SQL Server para esse tipo de trabalho de janelas. Usei o método "quirky update" para calculardistinct_sum
, que você pode ver aqui no SQL Fiddle , mas isso não é confiável.Respostas:
Não sei ao certo qual tipo de desempenho você está procurando, mas se o CLR ou o aplicativo externo não for uma opção, basta um cursor. No meu laptop antigo, recebo 1.000.000 de linhas em cerca de 100 segundos usando a solução a seguir. O bom disso é que ele é dimensionado linearmente, então eu ficaria olhando cerca de 20 minutos para percorrer a coisa toda. Com um servidor decente, você será mais rápido, mas não uma ordem de grandeza; portanto, ainda levará alguns minutos para concluir isso. Se esse é um processo único, você provavelmente pode pagar pela lentidão. Se você precisar executar isso como um relatório ou semelhante regularmente, convém armazenar os valores na mesma tabela e atualizá-los à medida que novas linhas forem adicionadas, por exemplo, em um gatilho.
Enfim, aqui está o código:
Ele solta e recria a tabela MyTable, preenche-a com 1000000 linhas e depois começa a trabalhar.
O cursor copia cada linha em uma tabela temporária enquanto executa os cálculos. No final, a seleção retorna os resultados calculados. Você pode ser um pouco mais rápido se não copiar os dados, mas fizer uma atualização no local.
Se você tiver a opção de atualizar para o SQL 2012, poderá olhar para os novos agregados de janelas móveis suportados pelo spool de janelas, que deverão oferecer melhor desempenho.
Em uma nota lateral, se você tiver um assembly instalado com permission_set = safe, poderá fazer mais coisas ruins com um servidor com T-SQL padrão do que com o assembly, por isso continuaria trabalhando para remover essa barreira - Você tem um bom uso caso em que o CLR realmente o ajudaria.
fonte
Na ausência das novas funções de janelas no SQL Server 2012, a janelas complexas podem ser realizadas com o uso de CTEs recursivas. Eu me pergunto o quão bem isso será executado em milhões de linhas.
A solução a seguir abrange todos os casos que você descreveu. Você pode vê-lo em ação aqui no SQL Fiddle .
Agora respire fundo. Existem duas CTEs principais aqui, cada uma precedida por um breve comentário. O restante são apenas CTEs de "limpeza", por exemplo, para puxar as linhas corretas depois que as classificamos.
Esta solução assume que
id
é uma sequência contínua. Caso contrário, você precisará gerar sua própria sequência sem intervalos, adicionando uma CTE adicional no início que numerar as linhas deROW_NUMBER()
acordo com a ordem desejada (por exemploROW_NUMBER() OVER (ORDER BY value DESC)
).Fankly, isso é bastante detalhado.
fonte
crude_sum
comdistinct_sum
asbucket
colunas associadas e para entender o que quero dizer.Parece uma solução boba, e provavelmente não será bem dimensionada; portanto, teste com cuidado se você a usar. Como o principal problema vem do "espaço" deixado no balde, primeiro tive que criar um registro de preenchimento para união nos dados.
http://sqlfiddle.com/#!3/72ad4/14/0
fonte
A seguir, é outra solução CTE recursiva, embora eu diria que é mais direta do que a sugestão de @ Nick . Na verdade, é mais próximo do cursor de @ Sebastian , apenas eu usei diferenças de execução em vez de totais de execução. (No começo, eu até pensei que a resposta de @ Nick seria na mesma linha do que estou sugerindo aqui, e foi depois de saber que a pergunta dele era de fato muito diferente que eu decidi oferecer a minha.)
Nota: esta consulta pressupõe que a
value
coluna consiste em valores exclusivos sem intervalos. Se não for esse o caso, você precisará introduzir uma coluna de classificação calculada com base na ordem decrescente devalue
e usá-la na CTE recursiva em vez devalue
unir a parte recursiva à âncora.Uma demonstração do SQL Fiddle para esta consulta pode ser encontrada aqui .
fonte
size
comroom_left
) em vez de comparar um único valor com uma expressão (1
comrunning_size
+size
). Eu não usei umais_new_bucket
bandeira no início, mas várias emCASE WHEN t.size > r.room_left ...
vez disso ("várias" porque também estava calculando (e retornando) o tamanho total, mas depois pensei contra isso por uma questão de simplicidade), então pensei que seria mais elegante dessa maneira.