Возможно ли, чтобы предложение вывода SQL возвращало столбец, который не вставлен?


124

Я внес некоторые изменения в свою базу данных, и мне нужно перенести старые данные в новые таблицы. Для этого мне нужно заполнить таблицу (ReportOptions), взяв данные из исходной таблицы (практика), и заполнить вторую промежуточную таблицу (PracticeReportOption).

ReportOption (ReportOptionId int PK, field1, field2...)
Practice (PracticeId int PK, field1, field2...)
PracticeReportOption (PracticeReportOptionId int PK, PracticeId int FK, ReportOptionId int FK, field1, field2...)

Я сделал запрос, чтобы получить все данные, которые мне нужны для перехода от практики к ReportOptions, но мне не удается заполнить промежуточную таблицу

--Auxiliary tables
DECLARE @ReportOption TABLE (PracticeId int /*This field is not on the actual ReportOption table*/, field1, field2...)
DECLARE @PracticeReportOption TABLE (PracticeId int, ReportOptionId int, field1, field2)

--First I get all the data I need to move
INSERT INTO @ReportOption
SELECT P.practiceId, field1, field2...
  FROM Practice P

--I insert it into the new table, but somehow I need to have the repation PracticeId / ReportOptionId
INSERT INTO ReportOption (field1, field2...)
OUTPUT @ReportOption.PracticeId, --> this is the field I don't know how to get
       inserted.ReportOptionId
  INTO @PracticeReportOption (PracticeId, ReportOptionId)
SELECT field1, field2
  FROM @ReportOption

--This would insert the relationship, If I knew how to get it!
INSERT INTO @PracticeReportOption (PracticeId, ReportOptionId)
SELECT PracticeId, ReportOptionId
  FROM @ReportOption

Если бы я мог ссылаться на поле, которого нет в таблице назначения в предложении OUTPUT, это было бы здорово (я думаю, что не могу, но я не знаю точно). Есть идеи о том, как удовлетворить мою потребность?


1
Вы можете вернуть любой из столбцов таблицы, в которую вы вставили строку, в своем OUTPUTпредложении. Поэтому, даже если вы не указываете значение для данного столбца в своем INSERTоператоре, вы все равно можете указать этот столбец в OUTPUTпредложении. Однако вы не можете возвращать переменные SQL или столбцы из других таблиц.
marc_s

2
@marc_s, спасибо за ответ, но у меня нет нужного мне поля в целевой таблице (мне нужен PracticeId, которого нет в ReportOption)
Алехандро Б.

Ответы:


192

Вы можете сделать это, используя MERGEвместо вставки:

так что замените это

INSERT INTO ReportOption (field1, field2...)
OUTPUT @ReportOption.PracticeId, --> this is the field I don't know how to get
       inserted.ReportOptionId
  INTO @PracticeReportOption (PracticeId, ReportOptionId)
SELECT field1, field2
  FROM @ReportOption

с участием

MERGE INTO ReportOption USING @ReportOption AS temp ON 1 = 0
WHEN NOT MATCHED THEN
    INSERT (field1, field2)
    VALUES (temp.Field1, temp.Field2)
    OUTPUT temp.PracticeId, inserted.ReportOptionId, inserted.Field1, inserted.Field2
    INTO @PracticeReportOption (PracticeId, ReportOptionId, Field1, Field2);

Ключ состоит в том, чтобы использовать предикат, который никогда не будет истинным (1 = 0) в условии поиска слияния, поэтому вы всегда будете выполнять вставку, но будете иметь доступ к полям как в исходной, так и в целевой таблицах.


Вот весь код, который я использовал для его тестирования:

CREATE TABLE ReportOption (ReportOptionID INT IDENTITY(1, 1), Field1 INT, Field2 INT)
CREATE TABLE Practice (PracticeID INT IDENTITY(1, 1), Field1 INT, Field2 INT)
CREATE TABLE PracticeReportOption (PracticeReportOptionID INT IDENTITY(1, 1), PracticeID INT, ReportOptionID INT, Field1 INT, Field2 INT)

INSERT INTO Practice VALUES (1, 1), (2, 2), (3, 3), (4, 4)


MERGE INTO ReportOption r USING Practice p ON 1 = 0
WHEN NOT MATCHED THEN
    INSERT (field1, field2)
    VALUES (p.Field1, p.Field2)
    OUTPUT p.PracticeId, inserted.ReportOptionId, inserted.Field1, inserted.Field2
    INTO PracticeReportOption (PracticeId, ReportOptionId, Field1, Field2);

SELECT  *
FROM    PracticeReportOption

DROP TABLE ReportOption
DROP TABLE Practice
DROP TABLE PracticeReportOption 

Дополнительная литература и источник всего, что я знаю по этой теме, находятся здесь.


2
Спасибо, это помогло! Я собирался использовать поддельное временное поле, но это намного элегантнее.
Alejandro B.

1
Превосходно! Этот трюк - золотая крупинка! Добавлен в первую строку коллекции!
Вадим Лобода

1
Suweet! Хотелось бы, чтобы в нем не использовалась иногда ошибочная команда MERGE, но в остальном она совершенно элегантна.
Tab Alleman

4
Имейте в виду. Я использовал оператор слияния, который за последний год вырос по мере использования. У нас начались тайм-ауты во время сохранения, и оказалось, что, поскольку оператор слияния всегда блокирует таблицы, у нас было 35-160 секунд блокировки таблиц каждые 4 минуты. Мне нужно реконструировать несколько операторов слияния, чтобы использовать вставку / обновления и ограничить количество строк, которые они обновляют, до 500 на вставку / обновление, чтобы избежать блокировки таблицы. Я считаю, что эта очень важная таблица держалась заблокированной почти 2 1/2 часа в день, что приводило ко всему, от медленных сохранений до тайм-аутов.
CubeRoot

3
Кроме того, для многих преградой является то, что MERGE содержит множество не исправленных ошибок, которые проявляются в странных условиях. Например, см. Эту статью Аарона Бертрана. Microsoft отказывается исправлять некоторые из них, и мое секретное подозрение заключается в том, что MS отказалась от всей своей службы MS Connect, чтобы попытаться заставить нас забыть обо всех этих ошибках в заявлении MERGE, которое они не хотят исправлять.
Обратный инженер

14

Может быть, тот, кто использует MS SQL Server 2005 или ниже , найдет этот ответ полезным.


MERGE будет работать только для SQL Server 2008 или выше. В остальном я нашел другой обходной путь, который даст вам возможность создавать таблицы сопоставления.

Вот как будет выглядеть Resolution для SQL 2005:

DECLARE @ReportOption TABLE (ReportOptionID INT IDENTITY(1, 1), Field1 INT, Field2 INT)
DECLARE @Practice TABLE(PracticeID INT IDENTITY(1, 1), Field1 INT, Field2 INT)
DECLARE @PracticeReportOption TABLE(PracticeReportOptionID INT IDENTITY(1, 1), PracticeID INT, ReportOptionID INT, Field1 INT, Field2 INT)

INSERT INTO @Practice (Field1, Field2) VALUES (1, 1)
INSERT INTO @Practice (Field1, Field2) VALUES (2, 2)
INSERT INTO @Practice (Field1, Field2) VALUES (3, 3)
INSERT INTO @Practice (Field1, Field2) VALUES (4, 4)

INSERT INTO @ReportOption (field1, field2)
    OUTPUT INSERTED.ReportOptionID, INSERTED.Field1, INSERTED.Field2 INTO @PracticeReportOption (ReportOptionID, Field1, Field2)
    SELECT Field1, Field2 FROM @Practice ORDER BY PracticeID ASC;


WITH CTE AS ( SELECT PracticeID, ROW_NUMBER() OVER ( ORDER BY PracticeID ASC ) AS ROW FROM @Practice )
UPDATE M SET M.PracticeID = S.PracticeID 
    FROM @PracticeReportOption AS M
    JOIN CTE AS S ON S.ROW = M.PracticeReportOptionID

    SELECT * FROM @PracticeReportOption

Основная хитрость заключается в том, что мы дважды заполняем таблицу сопоставления упорядоченными данными из исходной и целевой таблицы. Дополнительные сведения см. Здесь: слияние вставленных данных с помощью OUTPUT в SQL Server 2005.


1
Это не решит мою проблему. Мне нужно, чтобы Outputмои Insertвыходные данные Tableвключали Identityзначение из цели Tableс Insertнеактивным значением (PK) из источника Table(кстати, поэтому я мог бы (в другом пакете) использовать этот вывод Tableдля заполнения Columnв источник Tableсо Identityзначением). Без a Merge, я полагаю, мне пришлось бы: a) начать Transaction, b) перейти к следующему Identity, c) вставить в temp Tableс вычисленным Identity, d) установить Identity_Insert, e) Insertв цель Tableиз temp Table, f) очистить Identity_Insert.
Том
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.