С одной стороны, когда человек открывает для себя что-то новое, повторение пройденного, в разумных пределах, позволяет ему лучше закрепить материал. Однако, в моей ситуации, ежедневно приходилось решать функционально схожие задачи. Закономерный результат — плавное снижение мотивации делать это вручную.
Найти выход из сложившейся ситуации мне помог динамический SQL, который позволил автоматизировать наиболее рутинные операции и повысить производительность труда.
Под катом приведено несколько жизненных примеров применения динамического SQL для решения реальных задач.
1. Автоматическое обслуживание индексов
То, что удовлетворительно работало на этапе проектирования, с течением времени, может вызывать существенное падение производительности при работе с базой данных.
Причин этому может быть много, поэтому, чтобы минимизировать вероятность возникновения самых очевидных из них, — формируют, так называемый, план обслуживания, в джентельменский набор которого входят задачи по перестройке (дефрагментации) индексов.
При разовом обслуживании можно перестроить индексы вручную, например, через пункт контекстного меню в SSMS — Rebuild Index.
Также, можно воспользоваться одним из специализированных инструментов – в своё время, я достаточно активно использовал бесплатный инструмент SQL Index Manager (очень жаль, что на момент написания статьи RedGate уже сделала его платным):
Однако этот факт не должен сильно нас печалить, поскольку основной функционал этого приложения легко реализовать посредством применения динамического SQL.
В первую очередь, необходимо получить список фрагментированных индексов, отсеяв при этом таблицы без первичного ключа (кучи):
SELECT <code>[object_name] = SCHEMA_NAME(o.[schema_id]) + '.' + o.name</code> , [object_type] = o.type_desc , index_name = i.name , index_type = i.type_desc , s.avg_fragmentation_in_percent , s.page_count FROM sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, 'DETAILED') s JOIN sys.indexes i ON s.[object_id] = i.[object_id] AND s.index_id = i.index_id JOIN sys.objects o ON o.[object_id] = s.[object_id] WHERE s.index_id > 0 AND avg_fragmentation_in_percent > 0
После этого мы сформируем динамический запрос, который, в зависимости от степени фрагментации, будет перестраивать либо реорганизовывать индексы:
DECLARE @SQL NVARCHAR(MAX) SELECT @SQL = ( SELECT 'ALTER INDEX [' + i.name + N'] ON [' + SCHEMA_NAME(o.[schema_id]) + '].[' + o.name + '] ' + CASE WHEN s.avg_fragmentation_in_percent > 50 THEN 'REBUILD WITH (SORT_IN_TEMPDB = ON' + CASE WHEN SERVERPROPERTY('Edition') IN ('Enterprise Edition', 'Developer Edition') THEN ', ONLINE = ON' ELSE '' END + ')' ELSE 'REORGANIZE' END + '; RAISERROR(''Processing ' + i.name + '...'', 0, 1) WITH NOWAIT;' FROM sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, 'DETAILED') s JOIN sys.indexes i ON s.[object_id] = i.[object_id] AND s.index_id = i.index_id JOIN sys.objects o ON o.[object_id] = s.[object_id] WHERE s.index_id > 0 AND page_count > 100 AND avg_fragmentation_in_percent > 10 FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)') EXEC sys.sp_executesql @SQL
Как показала практика, дефрагментирование индексов с низкой степенью фрагментации либо с небольшим количеством данных не приносит каких-либо заметных улучшений, способствующих повышению производительности при работе с ними, – по этой причине изменилось условие фильтрации в итоговом скрипте.
Дефрагментация индексов – очень ресорусоемкая операция, которая может занимать продолжительное время для таблиц, содержащих большие объемы данных.
Чтобы не блокировать работу пользователей, выполнять дефрагментацию индексов наиболее оптимально в ночное время, когда на базу оказывается минимальная нагрузка. Но не у каждого есть желание работать ночью, поэтому разумно воспользоваться возможностями SQL Agent.
Через SQL Agent был добавлен Job, который ежедневно выполнял скрипт.
2. Автоматическое добавление столбца к выбранным таблицам
На этапе внедрения, заказчик попросил реализовать возможность логирования изменений по всем имеющимся таблицам. В итоге потребовалось добавить 2 столбца для более чем 300 таблиц:
CreatedDate DATETIME ModifiedDate DATETIME
Сложно оценить время, в случае выполнения данной задачи вручную, с применением же динамического SQL, задача была выполнена в течении получаса.
В результате получили список всех таблиц, у которых не имелось указанных столбцов:
SELECT SCHEMA_NAME(o.[schema_id]) + '.' + o.name FROM sys.objects o LEFT JOIN ( SELECT * FROM ( SELECT c.[object_id], c.name FROM sys.columns c WHERE c.name IN ('ModifiedDate', 'CreatedDate') ) c PIVOT (MAX(name) FOR name IN (ModifiedDate, CreatedDate)) p ) c ON o.[object_id] = c.[object_id] WHERE o.[type] = 'U' AND (ModifiedDate IS NULL OR CreatedDate IS NULL)
Был сформировал и выполнен динамический запрос на изменение этих таблиц:
DECLARE @SQL NVARCHAR(MAX) SELECT @SQL = ( SELECT ' ALTER TABLE [' + SCHEMA_NAME(o.[schema_id]) + '].[' + o.name + '] ADD ' + CASE WHEN ModifiedDate IS NULL THEN '[ModifiedDate] DATETIME' ELSE '' END + CASE WHEN CreatedDate IS NULL THEN CASE WHEN ModifiedDate IS NULL THEN ', ' ELSE '' END + '[CreatedDate] DATETIME' ELSE '' END + ';' FROM sys.objects o LEFT JOIN ( SELECT * FROM ( SELECT c.[object_id], c.name FROM sys.columns c WHERE c.name IN ('ModifiedDate', 'CreatedDate') ) c PIVOT (MAX(name) FOR name IN (ModifiedDate, CreatedDate)) p ) c ON o.[object_id] = c.[object_id] WHERE o.[type] = 'U' AND (ModifiedDate IS NULL OR CreatedDate IS NULL) FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)') EXEC sys.sp_executesql @SQL
3. Создание консолидированных таблиц
Задачи по созданию сводных отчетов на предприятиях очень сильно распространены. Помнится, в свое время, для отдела Бухгалерии, я их создал великое множество. При этом возникало много проблем. Одна из них – затрата большого количества времени на реализацию конкретного отчета.
Чтобы частично оптимизировать этот процесс, было решено формировать некоторые отчеты динамически, через функциональность Pivot, реализованную в продукте dbForge for SQL Server (очень пригодился аргумент в пользу бесплатности этого продукта для рускоязычных пользователей, когда я убеждал начальство использовать его на постоянной основе).
Однако, не всегда создание консолидированных таблиц сводилось к необходимости создания отчетов. В некоторых сценариях, консолидированные таблицы были более эффективны, чем постоянное использование PIVOT запросов.
Подобные таблицы можно создать через табличный редактор SSMS или того же dbForge простым copy-paste столбцов.
Однако, данный вариант не является оптимальным, особенно, если есть возможность использовать динамический SQL. Всё, что требуется от пользователя, — указать количество столбцов, которое будет в таблице, префикс столбца, его тип, а далее выполняем следующий запрос:
IF OBJECT_ID ('dbo.temp') IS NOT NULL DROP TABLE dbo.temp DECLARE @SQL NVARCHAR(MAX) SELECT @SQL = 'CREATE TABLE dbo.temp (EmployeeID INT IDENTITY(1,1) PRIMARY KEY' + ( SELECT ', Day' + RIGHT('0' + CAST(sv.number AS VARCHAR(2)), 2) + ' INT' FROM [master].dbo.spt_values sv WHERE sv.[type] = 'p' AND sv.number BETWEEN 1 AND 31 FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)') + ')' PRINT @SQL EXEC sys.sp_executesql @SQL
Voila! После выполнения мы получим таблицу со следующей структурой:
CREATE TABLE dbo.temp ( EmployeeID INT IDENTITY (1, 1) PRIMARY KEY , Day01 INT , Day02 INT , Day03 INT , Day04 INT , Day05 INT , ... , Day30 INT , Day31 INT )
Вывод:
Стоит отметить, что применение динамического SQL не ограничивается описанными выше примерами. Надеюсь, что эта статья поможет взглянуть на Ваши ежедненые задачи с другой стороны.
ссылка на оригинал статьи http://habrahabr.ru/post/196920/
Добавить комментарий