У меня есть таблица на сервере SQL, которая выглядит следующим образом:
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
Я работаю над хранимой процедурой diff, которая принимает входные данные и номер версии. Входные данные имеют столбцы из Имя до поля Z. Ожидается, что большинство столбцов полей будут NULL, т. Е. Каждая строка обычно содержит данные только для первых нескольких полей, остальные - NULL. Имя, дата и версия образуют уникальное ограничение для таблицы.
Мне нужно различить данные, которые вводятся относительно этой таблицы, для данной версии. Каждая строка должна быть диффузной - строка идентифицируется по имени, дате и версии, и любое изменение в любом из значений в столбцах поля должно отображаться в diff.
Обновление: все поля не обязательно должны быть десятичного типа. Некоторые из них могут быть nvarchars. Я бы предпочел, чтобы diff происходил без преобразования типа, хотя вывод diff мог конвертировать все в nvarchar, поскольку он должен использоваться только для отображения.
Предположим, что ввод следующий, и запрашиваемая версия 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
Разница должна быть в следующем формате:
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
Мое решение до сих пор состоит в том, чтобы сначала сгенерировать diff, используя EXCEPT и UNION. Затем преобразуйте diff в желаемый формат вывода, используя JOIN и CROSS APPLY. Хотя это, кажется, работает, мне интересно, есть ли более чистый и эффективный способ сделать это. Количество полей близко к 100, и каждое место в коде, которое имеет ..., на самом деле представляет собой большое количество строк. Ожидается, что и входная таблица, и существующая таблица со временем будут достаточно большими. Я новичок в SQL и все еще пытаюсь научиться настройке производительности.
Вот SQL для этого:
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)
Спасибо!
Редактировать поля, имеющие разные типы, а не только
decimal
.Вы можете попробовать использовать
sql_variant
тип. Я никогда не использовал это лично, но это может быть хорошим решением для вашего случая. Чтобы попробовать это просто заменить все[decimal](38, 10)
сsql_variant
в сценарии SQL. Сам запрос остается в точности таким, какой он есть, для сравнения не требуется явного преобразования. Конечный результат будет иметь столбец со значениями разных типов. Скорее всего, в конечном итоге вам нужно будет каким-то образом узнать, какой тип находится в каком поле для обработки результатов в вашем приложении, но сам запрос должен работать без преобразований.Кстати, это плохая идея хранить даты как
int
.Вместо использования
EXCEPT
иUNION
для вычисления различий, я бы использовалFULL JOIN
. Лично мне сложно следовать логикеEXCEPT
иUNION
подходу.Я бы начал с удаления данных, а не делал это последним (используя,
CROSS APPLY(VALUES)
как вы). Вы можете избавиться от отмены ввода, если вы делаете это заранее, на стороне вызывающего абонента.Вы должны были бы перечислить все 100 столбцов только в
CROSS APPLY(VALUES)
.Последний запрос довольно прост, поэтому временная таблица на самом деле не нужна. Я думаю, что это легче написать и поддерживать, чем ваша версия. Вот SQL Fiddle .
Настройте пример данных
Основной запрос
CTE_Main
непивотированные исходные данные отфильтрованы по заданнымVersion
.CTE_Input
является входной таблицей, которая может быть предоставлена уже в этом формате. Основной запрос используетFULL JOIN
, который добавляет к строке результатов сBee
. Я думаю, что они должны быть возвращены, но если вы не хотите их видеть, вы можете отфильтровать их, добавивAND CTE_Input.FieldValue IS NOT NULL
или, возможно, используяLEFT JOIN
вместо этогоFULL JOIN
, я не стал вдаваться в подробности, потому что я думаю, что они должны быть возвращены.Результат
источник