Как скопировать таблицу с помощью SELECT INTO, но игнорировать свойство IDENTITY?

43

У меня есть таблица с идентификатором столбца сказать:

create table with_id (
 id int identity(1,1),
 val varchar(30)
);

Хорошо известно, что это

select * into copy_from_with_id_1 from with_id;

приводит к copy_from_with_id_1 с идентичностью на id тоже.

В следующем вопросе о переполнении стека упоминается явное перечисление всех столбцов.

Давай попробуем

select id, val into copy_from_with_id_2 from with_id;

Ой, даже в этом случае id - это столбец идентификаторов.

То, что я хочу, это как стол

create table without_id (
 id int,
 val varchar(30)
);
bernd_k
источник

Ответы:

55

Из книг онлайн

Формат new_table определяется путем оценки выражений в списке выбора. Столбцы в new_table создаются в порядке, указанном списком выбора. Каждый столбец в new_table имеет то же имя, тип данных, обнуляемость и значение, что и соответствующее выражение в списке выбора. Свойство IDENTITY столбца передается за исключением условий, определенных в «Работа со столбцами идентификаторов» в разделе «Примечания».

Вниз по странице:

Если в новой таблице выбран существующий столбец идентификаторов, новый столбец наследует свойство IDENTITY, если не выполняется одно из следующих условий:

  • Оператор SELECT содержит соединение, предложение GROUP BY или агрегатную функцию.
  • Несколько операторов SELECT объединяются с помощью UNION.
  • Столбец идентификаторов указан несколько раз в списке выбора.
  • Столбец идентичности является частью выражения.
  • Столбец идентификации из удаленного источника данных.

Если какое-либо из этих условий выполняется, столбец создается как NOT NULL, а не наследует свойство IDENTITY. Если в новой таблице требуется столбец идентификаторов, но такой столбец недоступен или требуется, чтобы начальное значение или значение приращения отличались от исходного столбца идентификаторов, определите столбец в списке выбора с помощью функции IDENTITY. См. «Создание столбца идентификаторов с помощью функции IDENTITY» в разделе «Примеры» ниже.

Итак ... теоретически вы можете сойти с рук:

select id, val 
into copy_from_with_id_2 
from with_id

union all

select 0, 'test_row' 
where 1 = 0;

Было бы важно прокомментировать этот код, чтобы объяснить его, чтобы он не был удален при следующем просмотре.

Эрик Хамфри - Лотсхелп
источник
30

Вдохновленный ответом Erics, я нашел следующее решение, которое зависит только от имен таблиц и не использует никакого конкретного имени столбца:

select * into without_id from with_id where 1 = 0
union all
select * from with_id where 1 = 0
;
insert into without_id select * from with_id;

редактировать

Можно даже улучшить это до

select * into without_id from with_id
union all
select * from with_id where 1 = 0
;
bernd_k
источник
13

Вы можете использовать объединение для создания и заполнения новой таблицы за один раз:

SELECT
  t.*
INTO
  dbo.NewTable
FROM
  dbo.TableWithIdentity AS t
  LEFT JOIN dbo.TableWithIdentity ON 1 = 0
;

Из-за этого 1 = 0условия правая сторона не будет иметь совпадений и, таким образом, предотвратит дублирование строк левой стороны, а поскольку это внешнее соединение, строки левой стороны также не будут удалены. Наконец, поскольку это соединение, свойство IDENTITY исключается.

Таким образом, выбор только левых боковых столбцов даст точную копию только для данных dbo.TableWithIdentity , т. Е. С удаленным свойством IDENTITY.

Все это, как говорится, Макс Вернон поднял обоснованный момент в комментарии, который стоит иметь в виду. Если вы посмотрите на план выполнения вышеуказанного запроса:

План выполнения

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

Таким образом, если оптимизатор может правильно установить, что правая сторона объединения не требуется в плане, разумно ожидать, что в будущей версии SQL Server он сможет определить, что свойство IDENTITY необязательно либо удален, так как больше нет другого столбца IDENTITY в исходной строке, установленной в соответствии с планом запроса. Это означает, что приведенный выше запрос может перестать работать, как ожидалось, в какой-то момент.

Но, как правильно заметил ypercubeᵀᴹ , до сих пор в руководстве явно указывалось, что при наличии соединения свойство IDENTITY не сохраняется:

Когда существующий столбец идентификаторов выбирается в новую таблицу, новый столбец наследует свойство IDENTITY, если только [...] [t] инструкция SELECT не содержит соединение.

Так что, пока руководство продолжает упоминать об этом, мы можем быть уверены, что поведение останется прежним.

Престижность Shaneis и ypercubeᵀᴹ за обсуждение связанной темы в чате.

Андрей М
источник
Будет JOIN (SELECT 1) AS dummy ON 1 = 1работать тоже?
ypercubeᵀᴹ
6

Попробуйте этот код ..

SELECT isnull(Tablename_old.IDENTITYCOL + 0, -1) AS 'New Identity Column'
INTO   dbo.TableName_new
FROM   dbo.TableName_old 

ISNULLВызов гарантирует , что новый столбец создается с NOT NULLдопустимостью пустым.

Саурав Гош
источник
1
Это ISNULL()или что +0это делает? Или оба нужны?
ypercubeᵀᴹ
Просто добавляю 0 работ. Это самое простое решение, если вы явно указываете столбцы и не используете select *.
Ян Хорвилл
3

Просто чтобы показать другой способ:

Вы можете использовать связанный сервер .

SELECT * 
INTO without_id 
FROM [linked_server].[source_db].dbo.[with_id];

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

DECLARE @LocalServer SYSNAME 
SET @LocalServer = @@SERVERNAME;
EXEC master.dbo.sp_addlinkedserver @server = N'localserver'
    , @srvproduct = ''
    , @provider = 'SQLNCLI'
    , @datasrc = @LocalServer;
EXEC master.dbo.sp_addlinkedsrvlogin @rmtsrvname=N'localserver'
    , @useself = N'True'
    , @locallogin = NULL
    , @rmtuser = NULL
    , @rmtpassword = NULL;

В этот момент вы запустите select * intoкод, ссылаясь на localserverчетырехчастное имя связанного сервера:

SELECT * 
INTO without_id 
FROM [localserver].[source_db].dbo.[with_id];

После этого очистите localserverсвязанный сервер с помощью этого:

EXEC sp_dropserver @server = 'localserver'
    , @droplogins = 'droplogins';

Или вы можете использовать OPENQUERYсинтаксис

SELECT * 
INTO without_id 
FROM OPENQUERY([linked_server], 'SELECT * FROM [source_db].dbo.[with_id]');
bernd_k
источник
1

Свойство identity не передается, если оператор select содержит соединение и т. Д.

select a.* into without_id from with_id a inner join with_id b on 1 = 0;

также даст желаемое поведение (скопированного idстолбца, чтобы не сохранять IDENTITYсвойство. Однако, это будет иметь побочный эффект - не копировать строки вообще! (как с некоторыми другими методами), поэтому вам нужно будет сделать:

insert into without_id select * from with_id;

(спасибо AakashM!)

Анон-99
источник
1

Самый простой способ - сделать столбец частью выражения.

Пример:
если таблица dbo.Employee имеет идентификатор для столбца ID, то в приведенном ниже примере временная таблица #t также будет иметь идентификатор IDENTITY для столбца ID.

--temp table has IDENTITY
select ID, Name 
into #t
from dbo.Employee

Измените это, чтобы применить выражение к ID, и у вас #t больше не будет идентификатора в столбце ID. В этом случае мы применяем простое дополнение к столбцу ID.

--no IDENTITY
select ID = ID + 0, Name 
into #t
from dbo.Employee

Другие примеры выражений для других типов данных могут включать: convert (), конкатенация строк или Isnull ()

Кулак ярости
источник
1
От docs.microsoft.com/en-us/sql/t-sql/queries/… «Когда существующий столбец идентификаторов выбран в новую таблицу, новый столбец наследует свойство IDENTITY, если не выполняется одно из следующих условий … Столбец идентификаторов является частью выражения… Столбец создается NOT NULL вместо того, чтобы наследовать свойство IDENTITY. »
Маннго,
1

Иногда вы хотите вставить из таблицы, где вы не знаете (или не заботитесь), был ли столбец создан с использованием IDENTITY или нет. Это может быть даже не целочисленный столбец, с которым вы работаете. В этом случае будет работать следующее:

SELECT TOP(0) ISNULL([col],NULL) AS [col], ... INTO [table2] FROM [table1]
ALTER TABLE [table2] REBUILD WITH (DATA_COMPRESSION=page)
INSERT INTO [table2] ...

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

Тони
источник
0
select convert(int, id) as id, val 
into copy_from_with_id_without_id 
from with_id;

удалит личность.

Недостатком является то, что это idстановится недействительным, но вы можете добавить это ограничение.

Джон Хантер
источник
1
Вы можете использовать ISNULL, чтобы обойти это.
Эрик Дарлинг
-2

Вы не select * intoсохраняет идентичность.

Младен Прайдич
источник
2
В вопросе не было необходимости использовать *.
Мартин Смит
2
И identityсобственность не всегда сохраняется, как указывали другие ответы.
ypercubeᵀᴹ