Eu tenho uma tabela no SQL Server que se parece com isso:
Id |Version |Name |date |fieldA |fieldB ..|fieldZ
1 |1 |Foo |20120101|23 | ..|25334123
2 |2 |Foo |20120101|23 |NULL ..|NULL
3 |2 |Bar |20120303|24 |123......|NULL
4 |2 |Bee |20120303|34 |-34......|NULL
Estou trabalhando em um procedimento armazenado para diff, que leva dados de entrada e um número de versão. Os dados de entrada possuem colunas do Nome até o campoZ. Espera-se que a maioria das colunas de campo seja NULL, ou seja, cada linha geralmente possui dados apenas para os primeiros campos, o restante é NULL. O nome, a data e a versão formam uma restrição exclusiva na tabela.
Eu preciso diferenciar os dados que são inseridos em relação a esta tabela, para uma determinada versão. Cada linha precisa ser diferenciada - uma linha é identificada pelo nome, data e versão, e qualquer alteração em qualquer um dos valores nas colunas de campo precisará ser exibida na comparação.
Atualização: todos os campos não precisam ser do tipo decimal. Alguns deles podem ser nvarchars. Eu preferiria que o diff acontecesse sem converter o tipo, embora a saída do diff possa converter tudo em nvarchar, pois deve ser usada apenas para exibição proposta.
Suponha que a entrada seja a seguinte e a versão solicitada seja 2:
Name |date |fieldA |fieldB|..|fieldZ
Foo |20120101|25 |NULL |.. |NULL
Foo |20120102|26 |27 |.. |NULL
Bar |20120303|24 |126 |.. |NULL
Baz |20120101|15 |NULL |.. |NULL
O diff precisa estar no seguinte formato:
name |date |field |oldValue |newValue
Foo |20120101|FieldA |23 |25
Foo |20120102|FieldA |NULL |26
Foo |20120102|FieldB |NULL |27
Bar |20120303|FieldB |123 |126
Baz |20120101|FieldA |NULL |15
Minha solução até agora é primeiro gerar um diff, usando EXCEPT e UNION. Em seguida, converta o diff para o formato de saída desejado usando um JOIN e CROSS APPLY. Embora isso pareça estar funcionando, estou me perguntando se existe uma maneira mais limpa e eficiente de fazer isso. O número de campos é próximo de 100 e cada local no código que possui um ... é na verdade um grande número de linhas. Espera-se que a tabela de entrada e a tabela existente sejam bastante grandes ao longo do tempo. Eu sou novo no SQL e ainda estou tentando aprender o ajuste de desempenho.
Aqui está o SQL para isso:
CREATE TABLE #diff
( [change] [nvarchar](50) NOT NULL,
[name] [nvarchar](50) NOT NULL,
[date] [int] NOT NULL,
[FieldA] [decimal](38, 10) NULL,
[FieldB] [decimal](38, 10) NULL,
.....
[FieldZ] [decimal](38, 10) NULL
)
--Generate the diff in a temporary table
INSERT INTO #diff
SELECT * FROM
(
(
SELECT
'old' as change,
name,
date,
FieldA,
FieldB,
...,
FieldZ
FROM
myTable mt
WHERE
version = @version
AND mt.name + '_' + CAST(mt.date AS VARCHAR) IN (SELECT name + '_' + CAST(date AS VARCHAR) FROM @diffInput)
EXCEPT
SELECT 'old' as change,* FROM @diffInput
)
UNION
(
SELECT 'new' as change, * FROM @diffInput
EXCEPT
SELECT
'new' as change,
name,
date,
FieldA,
FieldB,
...,
FieldZ
FROM
myTable mt
WHERE
version = @version
AND mt.name + '_' + CAST(mt.date AS VARCHAR) IN (SELECT name + '_' + CAST(date AS VARCHAR) FROM @diffInput)
)
) AS myDiff
SELECT
d3.name, d3.date, CrossApplied.field, CrossApplied.oldValue, CrossApplied.newValue
FROM
(
SELECT
d2.name, d2.date,
d1.FieldA AS oldFieldA, d2.FieldA AS newFieldA,
d1.FieldB AS oldFieldB, d2.FieldB AS newFieldB,
...
d1.FieldZ AS oldFieldZ, d2.FieldZ AS newFieldZ,
FROM #diff AS d1
RIGHT OUTER JOIN #diff AS d2
ON
d1.name = d2.name
AND d1.date = d2.date
AND d1.change = 'old'
WHERE d2.change = 'new'
) AS d3
CROSS APPLY (VALUES ('FieldA', oldFieldA, newFieldA),
('FieldB', oldFieldB, newFieldB),
...
('FieldZ', oldFieldZ, newFieldZ))
CrossApplied (field, oldValue, newValue)
WHERE
crossApplied.oldValue != crossApplied.newValue
OR (crossApplied.oldValue IS NULL AND crossApplied.newValue IS NOT NULL)
OR (crossApplied.oldValue IS NOT NULL AND crossApplied.newValue IS NULL)
Obrigado!
Edite os campos com tipos diferentes, não apenas
decimal
.Você pode tentar usar o
sql_variant
tipo. Eu nunca o usei pessoalmente, mas pode ser uma boa solução para o seu caso. Para tentar, basta substituir todos[decimal](38, 10)
porsql_variant
no script SQL. A consulta em si permanece exatamente como está, nenhuma conversão explícita é necessária para realizar a comparação. O resultado final teria uma coluna com valores de tipos diferentes. Provavelmente, eventualmente, você precisará saber de alguma forma qual tipo está em qual campo processar os resultados em seu aplicativo, mas a consulta em si deve funcionar bem sem conversões.A propósito, é uma má idéia armazenar datas como
int
.Em vez de usar
EXCEPT
eUNION
calcular o diff, eu usariaFULL JOIN
. Para mim, pessoalmente, é difícil seguir a lógica por trásEXCEPT
e aUNION
abordagem.Eu começaria desviando os dados, em vez de fazê-lo por último (usando
CROSS APPLY(VALUES)
como você). Você pode se livrar da não articulação da entrada, se você fizer isso com antecedência, no lado do chamador.Você precisaria listar todas as 100 colunas apenas em
CROSS APPLY(VALUES)
.A consulta final é bem simples, portanto, a tabela temporária não é realmente necessária. Eu acho que é mais fácil escrever e manter do que a sua versão. Aqui está o SQL Fiddle .
Configurar dados de amostra
Consulta principal
CTE_Main
são dados originais não dinâmicos filtrados para o dadoVersion
.CTE_Input
é uma tabela de entrada, que já pode ser fornecida nesse formato. Utilizações de consulta principalFULL JOIN
, adicionadas às linhas de resultado comBee
. Eu acho que eles devem ser devolvidos, mas se você não quiser vê-los, é possível filtrá-los adicionandoAND CTE_Input.FieldValue IS NOT NULL
ou talvez usando emLEFT JOIN
vez deFULL JOIN
, não procurei detalhes lá, porque acho que eles deveriam ser devolvidos.Resultado
fonte