Пытаемся перетащить бэкап SQL 2008 R2 на SQL 2008 — шаг 3

Продолжение…

Предыдущие страницы:

Бэкап SQL 2008 R2 не восстановить на SQL 2008, что делать?

Пытаемся перетащить бэкап SQL 2008 R2 на SQL 2008

Пытаемся перетащить бэкап SQL 2008 R2 на SQL 2008 — шаг 2

Теперь, когда база данных и все объекты в ней созданы, можно попытаться наполнить таблицы данными.

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

Сначала я пробовал создать скрипт с данными для отдельно взятой таблицы и запустить его на машине с SQL Server 2008: попытка оказалась неудачной.

Тогда я попробовал перенести данные через bulk copy (с помощью .bcp файлов) — увы, с тем же успехом.

Тогда я создал на исходной машине (где установлен SQL Server 2012 Express) скрипт создания таблицы, удалил таблицу на сервере с 2008 SQL’ем и заново её создал. Попробовал загрузить данные: всё отлично. Стало понятно, что при создании скрипта базы данных я что-то не учёл. Пришлось заново создать скрипт для базы данных целиком, при этом я внимательно прошёлся по всем параметрам и изменил некоторые значения, установленные по умолчанию:

В частности:
Сформировать скрипт для зависимых объектов — True
Внести в скрипт индексы — True

Не помню, что ещё было по умолчанию, смотрите на картинку внимательно!

Заново сформировал скрипт, он стал не 60 МБ, а 65 МБ, удалил базу данных на SQL Server’е 2008, откорректировал пути к файлам, убрал размеры (см. выше), заново создал базу данных и все объекты.

На сей раз всё получилось! 🙂

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

1. Если Вам доступны одновременно 2 сервера, то очень логичным выглядит вариант прилинковывания сервера с младшей версией к серверу со старшей версией SQL и вставлять данные в пустые таблицы со стороны сервера с более старшей версией.

Я так и сделал. Прилинковал SQL Server 2008 к SQL Server’у 2012 и попытался выполнить примерно такой запрос:

insert into [SQLSERVER2008].[Epicor905].[dbo].[QuoteMtl]
select * from [dbo].[QuoteMtl]

Однако столкнулся с такой проблемой, что вставить записи простой конструкцией insert into… select from… не получается, так как часть полей таблицы обозначена следующим образом:

[PROGRESS_RECID_IDENT_] [bigint] IDENTITY(1,1) NOT FOR REPLICATION NOT NULL

Для вставки информации в такие поля должна использоваться директива типа:

SET IDENTITY_INSERT [dbo].[QuoteMtl] ON

Попробовал такую конструкцию:

SET IDENTITY_INSERT [SQLSERVER2008].[Epicor905].[dbo].[QuoteMtl] ON
insert into [SQLSERVER2008].[Epicor905].[dbo].[QuoteMtl]
select * from [dbo].[QuoteMtl]

Увы, не проходит. Видимо с удалённого сервера включить IDENTITY_INSERT нельзя:

Сообщение 1088, уровень 16, состояние 11, строка 1
Не удалось найти объект "SQLSERVER2008.Epicor905.dbo.QuoteMtl", так как он не существует, или отсутствуют разрешения.

В то же время инструкция

select * from [SQLSERVER2008].[Epicor905].[dbo].[QuoteMtl]

выполняется успешно.

Как говаривал один мой знакомый: «Мы же консультанты!». Консультант отличается от других людей тем, что не будет искать единственную в своём роде истину, а попытается пойти другим путём, чем достигнет результата может быть менее красивым путём, зато быстрее. Под «быстрее» я в данном случае понимаю не быстрее в абсолютном смысле, а быстрее именно с точки зрения моих конкретных навыков и знаний. Мне важен результат, как его получить, в конце концов, не так важно. Иногда, если речь идёт о 2-х записях, проще «вколотить их руками», чем писать универсальную программу, которая пригодится в следующей такой же ситуации, иное дело, если ситуация будет повторяться достаточно часто.

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

2. Второй путь — использование утилиты sqlcmd и перенос данных с помощью скрипта, выполняемого с её помощью. Но я с ней работать пока не умею, поэтому — дальше! («Мы же консультанты!», нам некогда искать абсолютного совершенства. Когда разберусь с этим, обязательно напишу, но позднее.)

3. Создать несколько скриптов, чтобы их размер не превышал возможности системы сервера, где установлен мой SQL Server 2008, их загрузить. Почему бы нет, вот только как узнать, по сколько таблиц можно за раз выгружать-загружать? Дальше!

Хочу заметить, что этот путь работает, я его попробовал и, более того, использую в сценарии №4 🙂

4. С помощью запроса выгрузить данные таблиц в .bcp файлы, перенести их на другой сервер и там загрузить с помощью скрипта.

Мы же консультанты! Похожую задачу я уже решал. Правда для одного файла, но мы же консультанты 🙂

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

select sysobjects.name, rowcnt into TablesToRestore
from sysobjects (nolock)
join sysindexes (nolock)
on sysobjects.id=sysindexes.id and indid < 2 and rowcnt>0
where [uid]=USER_ID('dbo') and  xtype='U'
GO

После этого я посмотрел на эту таблицу и обнаружил, что есть то, что я переносить не хочу, в частности лог действий пользователей, размером почти 1 ГБ, который мне на моей тестовой виртуалке совсем не нужен. Не нужен, так удалим его из таблицы:

delete from TablesToRestore where [name]=N'SysActivityLog'

Вот теперь всё готово к выгрузке тех данных, что мне действительно нужны:

declare @objname sysname
declare @DataBase nvarchar(20),
@FolderName nvarchar(100),
@command nvarchar(500)
--Указать имя папки, куда сохранить файл
set @FolderName='C:\TEMP\Epicor905'
--Указать имя базы данных
set @DataBase='Epicor905'
--Указать название таблицы

DECLARE t_c CURSOR FOR

select [name]
from TablesToRestore (nolock)
order by [name]

OPEN t_c

FETCH NEXT FROM t_c
INTO @objname

WHILE @@FETCH_STATUS = 0
BEGIN
set @command = 'bcp '+@DataBase+'.dbo.'+@objname+' out '+@FolderName+'\'+@objname+'.bcp -n -T'
exec master..xp_cmdshell @command
FETCH NEXT FROM t_c
INTO @objname
END

CLOSE t_c
DEALLOCATE t_c

Здесь используется xp_cmdshell, которая по умолчанию запрещена. Её нужно разрешить с помощью sp_configure

Эту служебную таблицу надо как-то перенести на SQL Server 2008. Так воспользуемся сценарием №3:

Созданный таким образом скрипт запустим на сервере, где установлен SQL 2008:

Копируем .bcp файлы на сервер SQL 2008.

А теперь там же можно запустить следующий скрипт:

declare @objname sysname
declare @DataBase nvarchar(20),
@FolderName nvarchar(100),
@command nvarchar(500)
--Указать имя папки, откуда взять файл
set @FolderName='E:\Backup\Epicor905\'
--Указать имя базы данных
set @DataBase='Epicor905'
DECLARE t_c CURSOR FOR
select [name]
from TablesToRestore (nolock)
order by [name]
OPEN t_c

FETCH NEXT FROM t_c
INTO @objname

WHILE @@FETCH_STATUS = 0
BEGIN
--set @command = 'truncate table '+@DataBase+'.dbo.'+@objname
--exec(@command)
set @command = 'bcp '+@DataBase+'.dbo.'+@objname+' in '+@FolderName+'\'+@objname+'.bcp -n -T'
exec master..xp_cmdshell @command
FETCH NEXT FROM t_c
INTO @objname
END

CLOSE t_c
DEALLOCATE t_c
go

Ура! всё прошло успешно. На всякий случай делаю запрос к первой попавшейся таблице:

Да, всё получилось!

Итак, восстановить бэкап, сделанный на SQL Server 2008 R2, на сервере, где установлен SQL Server 2008, нельзя! …но если очень хочется и Вы готовы «поприседать», то можно 🙂

Дополнение от 13.07.2016 21:38:

Вчера пытался повторить фокус с восстановлением бэкапа базы данных SQL 2014 на виртуалке с 2012. Номер не прошёл 🙁
Точнее почти всё получилось, но что-то оказалось не совсем корректным из-за неряшливого стиля программирования в исходной базе данных, где из-за Collations CI (case insensitive) было неважно, как написать, «DimensionId» или «DimensionID», но у меня в итоге программа, использующая перетащенную таким образом базу данных в какой-то момент отказалась работать, сославшись на этот самый «DimensionId» (или что-то в этом роде), т.к. у моего сервера по умолчанию установлено «CS_AS». Я уже писал ранее про то, что программисты всегда должны всё делать при максимальных ограничениях, а не устраивать себе легкую жизнь: Case Insensitive Collation settings развращают программистов