Eu tenho uma tabela que pode ser criada e preenchida com o seguinte código:
CREATE TABLE dbo.Example(GroupKey int NOT NULL, RecordKey varchar(12) NOT NULL);
ALTER TABLE dbo.Example
ADD CONSTRAINT iExample PRIMARY KEY CLUSTERED(GroupKey ASC, RecordKey ASC);
INSERT INTO dbo.Example(GroupKey, RecordKey)
VALUES (1, 'Archimedes'), (1, 'Newton'), (1, 'Euler'), (2, 'Euler'), (2, 'Gauss'),
(3, 'Gauss'), (3, 'Poincaré'), (4, 'Ramanujan'), (5, 'Neumann'),
(5, 'Grothendieck'), (6, 'Grothendieck'), (6, 'Tao');
Para todas as linhas que têm uma distância de colaboração finita com base em RecordKey
outra linha, gostaria de atribuir um valor exclusivo - não me importa como ou qual tipo de dados é o valor exclusivo.
Um conjunto de resultados correto que atende ao que estou pedindo pode ser gerado com a seguinte consulta:
SELECT 1 AS SupergroupKey, GroupKey, RecordKey
FROM dbo.Example
WHERE GroupKey IN(1, 2, 3)
UNION ALL
SELECT 2 AS SupergroupKey, GroupKey, RecordKey
FROM dbo.Example
WHERE GroupKey = 4
UNION ALL
SELECT 3 AS SupergroupKey, GroupKey, RecordKey
FROM dbo.Example
WHERE GroupKey IN(5, 6)
ORDER BY SupergroupKey ASC, GroupKey ASC, RecordKey ASC;
Para ajudar melhor no que estou perguntando, explicarei por que GroupKey
os s 1–3 têm o mesmo SupergroupKey
:
GroupKey
1 contém oRecordKey
Euler que por sua vez está contido emGroupKey
2; assimGroupKey
s 1 e 2 devem ter o mesmoSupergroupKey
.- Como Gauss está contido em
GroupKey
s 2 e 3, eles também devem ter o mesmoSupergroupKey
. Isso faz com queGroupKey
s 1–3 tenha o mesmoSupergroupKey
. - Como
GroupKey
s 1–3 não compartilham nenhumRecordKey
s com os s restantesGroupKey
, eles são os únicos comSupergroupKey
valor 1.
Devo acrescentar que a solução precisa ser genérica. A tabela acima e o conjunto de resultados foram apenas um exemplo.
Termo aditivo
Eu removi o requisito de que a solução não fosse iterativa. Embora eu prefira essa solução, acredito que seja uma restrição irracional. Infelizmente, não consigo usar nenhuma solução baseada em CLR; mas se você quiser incluir essa solução, fique à vontade. Eu provavelmente não vou aceitá-lo como uma resposta embora.
O número de linhas na minha tabela real é tão grande quanto 5 milhões, mas há dias em que o número de linhas será "apenas" em torno de dez mil. Em média, há 8 RecordKey
s por GroupKey
e 4 GroupKey
s por RecordKey
. Imagino que uma solução terá uma complexidade de tempo exponencial, mas mesmo assim estou interessado em uma solução.
Este problema é sobre seguir links entre itens. Isso o coloca no reino dos gráficos e do processamento de gráficos. Especificamente, todo o conjunto de dados forma um gráfico e estamos procurando por componentes desse gráfico. Isso pode ser ilustrado por um gráfico dos dados da amostra da pergunta.
A pergunta diz que podemos seguir GroupKey ou RecordKey para encontrar outras linhas que compartilham esse valor. Assim, podemos tratar ambos como vértices em um grafo. A questão continua explicando como GroupKeys 1–3 têm a mesma SupergroupKey. Isso pode ser visto como o cluster à esquerda unido por linhas finas. A figura também mostra os outros dois componentes (SupergroupKey) formados pelos dados originais.
O SQL Server tem alguma capacidade de processamento gráfico incorporada ao T-SQL. No momento, é bastante escasso, no entanto, e não ajuda com esse problema. O SQL Server também tem a capacidade de chamar R e Python e o conjunto rico e robusto de pacotes disponíveis para eles. Um desses é o igraph . Ele foi escrito para "manuseio rápido de grafos grandes, com milhões de vértices e arestas ( link )."
Usando R e igraph, consegui processar um milhão de linhas em 2 minutos e 22 segundos em testes locais 1 . É assim que se compara com a melhor solução atual:
Ao processar 1 milhão de linhas, 1m40s foram usados para carregar e processar o gráfico e atualizar a tabela. 42s foram necessários para preencher uma tabela de resultados do SSMS com a saída.
A observação do Gerenciador de Tarefas enquanto 1 milhão de linhas eram processadas sugere que cerca de 3 GB de memória de trabalho foram necessários. Isso estava disponível neste sistema sem paginação.
Posso confirmar a avaliação de Ypercube da abordagem CTE recursiva. Com algumas centenas de chaves de registro, consumia 100% da CPU e toda a RAM disponível. Eventualmente, o tempdb cresceu para mais de 80 GB e o SPID travou.
Eu usei a tabela de Paul com a coluna SupergroupKey para que haja uma comparação justa entre as soluções.
Por alguma razão R se opôs ao acento em Poincaré. Alterá-lo para um "e" simples permitiu que ele fosse executado. Eu não investiguei, pois não é pertinente ao problema em questão. Tenho certeza que há uma solução.
Aqui está o código
Isso é o que o código R faz
@input_data_1
é como o SQL Server transfere dados de uma tabela para o código R e os converte em um dataframe R chamado InputDataSet.library(igraph)
importa a biblioteca para o ambiente de execução do R.df.g <- graph.data.frame(d = InputDataSet, directed = FALSE)
carregar os dados em um objeto igraph. Este é um gráfico não direcionado, pois podemos seguir links de grupo para registro ou registro para grupo. InputDataSet é o nome padrão do SQL Server para o conjunto de dados enviado para R.cpts <- components(df.g, mode = c("weak"))
processe o gráfico para encontrar subgráficos discretos (componentes) e outras medidas.OutputDataSet <- data.frame(cpts$membership)
SQL Server expects a data frame back from R. Its default name is OutputDataSet. The components are stored in a vector called "membership". This statement translates the vector to a data frame.OutputDataSet$VertexName <- V(df.g)$name
V() is a vector of vertices in the graph - a list of GroupKeys and RecordKeys. This copies them into the ouput data frame, creating a new column called VertexName. This is the key used to match to the source table for updating SupergroupKey.I'm not an R expert. Likely this could be optimised.
Test Data
The OP's data was used for validation. For scale tests I used the following script.
I've just now realised I've gotten the ratios the wrong way around from the OP's definition. I don't believe this will affect the timings. Records & Groups are symmetrical to this process. To the algorithm they're all just nodes in a graph.
In testing the data invariably formed a single component. I believe this is due to the uniform distribution of the data. If instead of the static 1:8 ratio hard-coded into the generation routine I had allowed the ratio to vary there would more likely have been further components.
1 Especificação da máquina: Microsoft SQL Server 2017 (RTM-CU12), Developer Edition (64 bits), Windows 10 Home. 16 GB de RAM, SSD, i7 hyperthreaded de 4 núcleos, nominal de 2,8 GHz. Os testes eram os únicos itens em execução no momento, além da atividade normal do sistema (cerca de 4% da CPU).
Esta é uma solução T-SQL iterativa para comparação de desempenho.
Ele assume que uma coluna extra pode ser adicionada à tabela para armazenar a chave do supergrupo e a indexação pode ser alterada:
Configurar
Se você conseguir reverter a ordem da chave primária atual, o índice exclusivo extra não será necessário.
Contorno
A abordagem desta solução é:
Implementação
Comentários em linha:
Plano de execução
Para a atualização da chave:
Resultado
O estado final da tabela é:
Demonstração: db<>fiddle
Testes de performance
Usando o conjunto de dados de teste expandido fornecido na resposta de Michael Green , os horários no meu laptop * são:
* Microsoft SQL Server 2017 (RTM-CU13), Developer Edition (64 bits), Windows 10 Pro, 16 GB de RAM, SSD, i7 hyperthread de 4 núcleos, 2,4 GHz nominal.
Um método CTE recursivo - que provavelmente será terrivelmente ineficiente em tabelas grandes:
Testado em dbfiddle.uk