Что вы делаете в SQL Server для CREATE OR ALTER?

2009 год, и SQL Server не имеет CREATE OR ALTER/REPLACE. Это то, что я делаю вместо этого.

IF EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.ROUTINES 
           WHERE ROUTINE_NAME = 'SynchronizeRemoteCatalog' 
             AND ROUTINE_SCHEMA = 'dbo' 
             AND ROUTINE_TYPE = 'PROCEDURE')
 EXEC ('DROP PROCEDURE dbo.SynchronizeRemoteCatalog')

CREATE PROCEDURE dbo.SynchronizeRemoteCatalog
AS BEGIN
    -- body
END

Для триггеров, вы должны опираться на проприетарные системные представления.

Это самая принятая конвенция в то же время?

РЕДАКТИРОВАТЬ: Как n8wrl предложил, официальное слово предполагает, что эта функция не является высоким приоритетом. Отсюда и вопрос.

Ответ 1

Да, вот как выглядят все мои скрипты сборки. В конце я также установил разрешения.

Ответ 2

В этой статье рассказывается о потере разрешений при удалении объекта на SQL-сервере.

Итак, вот подход, который сохраняет права доступа:

IF OBJECT_ID('spCallSomething') IS NULL
    EXEC('CREATE PROCEDURE spCallSomething AS SET NOCOUNT ON;')
GO

ALTER PROCEDURE spCallSomething ... 
--instead of DROP/CREATE

Также работает для функций, просто замените PROCEDURE на FUNCTION в приведенном выше коде.

Еще одна причина подумать о том, чтобы сделать это таким образом - это терпимость к неудачам. Предположим, что ваш DROP успешен, но ваш CREATE завершился неудачей - у вас заканчивается битая база данных. Используя подход ALTER, вы получите более старую версию объекта.

Ответ 3

2009 год, и SQL Server не имеет CREATE OR ALTER/REPLACE.

Это 2016 год, и теперь в окончательной CREATE OR ALTER SQL Server 2016 он имеет DIE (исключение, если оно существует) и CREATE OR ALTER (представлен в пакете обновления 1 (SP1) 2016 года).

Принятие Drop If Exists предостережения о необходимости повторного применения разрешений с этим подходом все еще применяются. Пример синтаксиса

DROP PROCEDURE IF EXISTS dbo.SynchronizeRemoteCatalog

GO

CREATE PROCEDURE dbo.SynchronizeRemoteCatalog
AS
  BEGIN
      BODY:
  END 

GO

/*TODO: Reapply permissions*/

CREATE OR ALTER сохраняет права доступа. Пример синтаксиса

 CREATE OR ALTER PROCEDURE dbo.SynchronizeRemoteCatalog
 AS
 BEGIN
   BODY:
 END

Соответствующий пост в блоге MSSQL Tiger Team объясняет

CREATE OR ALTER может использоваться в объектах программируемости, таких как:

  • ХРАНЕННЫЕ ПРОЦЕДУРЫ (в том числе скомпилированные в исходном виде)
  • ФУНКЦИИ (Transact-SQL, включая нативно скомпилированный)
  • TRIGGERS
  • ПРОСМОТРЫ

Но не может быть использован в:

  • Объекты, которые требуют хранения (таблицы, индексы и индексированные представления)
  • Пользовательские функции CLR
  • Устаревшие объекты программируемости (RULE и DEFAULT)
  • Непрограммируемые объекты (такие как CREATE ASSEMBLY, CREATE TABLE или CREATE - SCHEMA). В этих объектах синтаксис CREATE и ALTER сильно отличается от синтаксиса и удобства использования.

Ответ 4

Каждый раз, когда разработчик пишет IF EXISTS(...) DROP, тюремный щенок сжимается. Вы должны точно знать, что в базе данных, а ваше обновление script должно делать CREATe или ALTER как соответствующие, исходя из текущей версии вашей схемы приложения: Version Control и вашей базы данных.

Ответ 5

Мы столкнулись с ситуацией, когда нам нужно было обновить удаленный сайт, но у нас не было разрешений DROP. До сих пор мы использовали "DROP и CREATE script, встроенные в SSMS 2008 R2, но теперь нам нужно было изменить. Мы создали три шаблона, которые мы бросаем над соответствующими сценариями ALTER, когда нам нужно обновить хранимую процедуру или функцию:

—- Stored Procedure
IF OBJECT_ID('[dbo].[<Name_Of_Routine, , >]') IS NULL
EXEC('CREATE PROCEDURE [dbo].[<Name_Of_Routine, , >] AS SET NOCOUNT ON;')
EXEC('GRANT EXECUTE ON [<Name_Of_Routine, , >] TO Public AS dbo;')
GO

—- Scalar Function
IF OBJECT_ID('[dbo].[<Name_Of_Routine, , >]') IS NULL
EXEC('CREATE FUNCTION [dbo].[<Name_Of_Routine, , >] (@i INT) RETURNS INT AS BEGIN RETURN 0 END;')
EXEC('GRANT EXECUTE ON [<Name_Of_Routine, , >] TO Public AS dbo;')
GO

—- Table-based Function
IF OBJECT_ID('[dbo].[<Name_Of_Routine, , >]') IS NULL
EXEC('CREATE FUNCTION [dbo].[<Name_Of_Routine, , >] (@i INT) RETURNS @O TABLE(i INT) AS BEGIN INSERT INTO @O SELECT 0 RETURN END;')
GO

Любые специальные разрешения получают скрипт после каждого CREATE (функции таблицы не могут быть назначены разрешениям). После этого ALTER не изменяет его, и если они добавляют или изменяют разрешения, они остаются. Выполняя это таким образом, его простая задача - скопировать имя функции или хранимой процедуры и использовать замену параметра шаблона для автоматизации завершения этих скриптлетов.

Теперь я надеюсь, что хорошие люди в Microsoft либо добавят это в свои списки "Script ___ as", либо дадут нам возможность создать нашу собственную, чтобы этот скрипт пришел "запеченный"

Возможно, вы захотите добавить некоторый вес к записи обратной связи SQL Server по адресу: https://connect.microsoft.com/SQLServer/feedback/details/344991/create-or-alter-statement. Кажется, что это один из немногих, которые все еще доступны публично, и заявляют, что они "приступили к технико-экономическому обоснованию для этого, чтобы решить, можем ли мы отправить это в ближайшем будущем". Чем больше голосов, тем более вероятно, что это произойдет!

(Обновление: теперь также используется следующий код для триггеров и просмотров)

-- Triggers
IF OBJECT_ID('[dbo].[<Name_Of_Trigger, , >]') IS NULL -- Check if Trigger Exists
    EXEC('CREATE TRIGGER [dbo].[<Name_Of_Trigger, , >] ON [<Name_Of_Table, , >] AFTER UPDATE AS SET NOCOUNT ON;') -- Create dummy/empty SP
GO

-- Views
IF OBJECT_ID('[dbo].[<Name_Of_View, , >]') IS NULL -- Check if View Exists
    EXEC('CREATE VIEW [dbo].[<Name_Of_View, , >] AS SELECT 1;') -- Create dummy/empty View
GO

Ответ 6

Я использую OBJECT_ID(...) IS NOT NULL перед DROP.

Идентификаторы объектов должны быть уникальными, поэтому он работает без использования системных таблиц:

CREATE TRIGGER dbo.ExistingTable ON dbo.AnotherTable FOR UPDATE
AS 
SET NOCOUNT ON
GO

дает

Msg 2714, Level 16, State 2, Procedure MetaClass, Line 3
There is already an object named ExistingTable ' in the database.

Я обычно использую ALTER из-за того, как мы работаем с контролем источника и т.д.

Ответ 7

Это в основном способ сделать это, да. Мне просто интересно, есть ли у вас конкретная причина использовать подход "EXEC":

IF EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_NAME = 'SynchronizeRemoteCatalog' AND ROUTINE_SCHEMA = 'dbo' AND ROUTINE_TYPE = 'PROCEDURE')
    EXEC ('DROP PROCEDURE dbo.SynchronizeRemoteCatalog')

Почему не просто:

IF EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_NAME = 'SynchronizeRemoteCatalog' AND ROUTINE_SCHEMA = 'dbo' AND ROUTINE_TYPE = 'PROCEDURE')
    DROP PROCEDURE dbo.SynchronizeRemoteCatalog

???

Для триггеров, там sys.triggers. Это представления системного каталога в схеме "sys" - на самом деле не строго или непосредственно таблицы.

Марк

Ответ 8

Я всегда alter мои объекты, потому что drop - это действительно плохая практика и может оставить вашу БД в плохом состоянии, если объект не создан (24/7 db!), а также то, что другие плакаты имеют упоминается о разрешениях на nuking.

Редакторы, такие как Sublime, Atom и VS Code, позволят вам создавать фрагменты кода в качестве шаблонов для быстрого создания вашего скелета script. SQL 2016 теперь, наконец, поддерживает конструкцию DROP IF EXISTS, но она по-прежнему приближается с неправильного направления - все это drop/create вместо одноразового create в далеком прошлом и alter с этого момента. Кроме того, я попытался сделать мои заголовки такими короткими, которые могут работать, поэтому я не становлюсь более привлекательным, чем create proc dbo.myproc as как заглушка create.

Просмотров:

if objectproperty(object_id('dbo.myview'), 'IsView') is null begin
    exec('create view dbo.myview as select 1 c')
end
go
alter view dbo.myview as
    -- select *
    -- from table
go

Procs:

if objectproperty(object_id('dbo.myproc'), 'IsProcedure') is null begin
    exec('create proc dbo.myproc as')
end
go
alter procedure dbo.myproc as
    set nocount on
    -- Add the stored proc contents here...
go

UDF (скаляр):

if objectproperty(object_id('dbo.myudf'), 'IsScalarFunction') is null begin
    exec('create function dbo.myudf returns int as begin return null end')
end
go
alter function dbo.myudf(@s varchar(100)) returns int as
begin
    -- return len(@s)
end
go

UDF (таблица):

if objectproperty(object_id('dbo.myudf'), 'IsTableFunction') is null begin
    exec('create function dbo.myudf returns @t table(x int) as begin return end')
end
go
alter function dbo.myudf(@s varchar(100))
    returns @result table (
        -- Columns returned by the function
        id int identity(1, 1) primary key not null
        ,result varchar(100) null
    )
begin
    return
end
go

Ответ 9

Похоже, что он выключен: текст ссылки

типичный script для меня:

IF EXISTS (SELECT name FROM sysobjects WHERE name = 'ig_InsertDealer' AND type = 'P')
    DROP PROC dbo.ig_InsertDealer
GO 
CREATE PROCEDURE dbo.ig_InsertDealer
...
GO
GRANT EXECUTE ON dbo.ig_InsertDealer TO ...
GO

Ответ 10

Я буду использовать либо в зависимости от контекста: мои сценарии начальной сборки или основного рефакторинга будут использовать check/drop/create, чистые сценарии обслуживания используют alter.

Ответ 11

Я предпочитаю подход CREATE-ALTER (не синтаксис) по сравнению с DROP-CREATE по двум причинам:

  • DROP-CREATE вы должны их воссоздать)
  • object_id (изменяющий объект не изменит его)

Пример DROP-CREATE:

--Initial creation:
CREATE PROCEDURE dbo.my_proc
AS
SELECT *
FROM dbo.a
WHERE i < 10;
GO

SELECT OBJECT_ID('dbo.my_proc');
GO


-- Recreating
DROP PROCEDURE IF EXISTS dbo.my_proc;
GO

CREATE PROCEDURE dbo.my_proc
AS
-- some meaningless comment
SELECT *
FROM dbo.a
WHERE i < 10;
GO

SELECT OBJECT_ID('dbo.my_proc');
GO

DB Fiddle

Как мы видим, object_id изменился.

Пример 2: CREATE-ALTER

-- Initial creation
CREATE PROCEDURE dbo.my_proc2
AS
SELECT *
FROM dbo.a
WHERE i < 10;
GO

SELECT OBJECT_ID('dbo.my_proc2');
GO

-- Altering
CREATE OR ALTER PROCEDURE dbo.my_proc2
AS
-- some meaningless comment
SELECT *
FROM dbo.a
WHERE i < 10;
GO

SELECT OBJECT_ID('dbo.my_proc2');
GO

DB Fiddle

В этом случае object_id остается неизменным.


Пример сценария, когда это может вызвать некоторые проблемы. Предположим, что мы используем SQL Server 2016 Query Store и вынуждены использовать конкретный план запроса для хранимой процедуры.

DROP-CREATE

USE T1;
GO
-- make sure that Query Store is READ_WRITE 
IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[a]') AND type in (N'U'))
BEGIN
CREATE TABLE [dbo].[a](
    [i] [int] IDENTITY(1,1) NOT NULL PRIMARY KEY,
    [g] [uniqueidentifier] NULL,
    [z] VARCHAR(10)
);
END
GO

-- populate table (15k records)
INSERT INTO dbo.a(g, z)
SELECT NEWID(), number
FROM (SELECT CAST([key] AS INT) AS number 
    FROM OPENJSON( '[1' + REPLICATE(',1',3000-1)+']')
    ) AS num
GO 5

-- initial creation
CREATE PROCEDURE dbo.my_proc
AS
SELECT *
FROM dbo.a
WHERE z LIKE '12%'
AND 1 = (SELECT 1);
GO

-- Clustered Index Scan
EXEC dbo.my_proc;

EXEC sp_query_store_flush_db; 

SELECT qsq.query_id,
    qsq.query_text_id,
    qsq.context_settings_id,
    qsq.[object_id],
    OBJECT_NAME(qsq.[object_id]) AS [object_name],
    qsp.is_forced_plan,
    qsqt.query_sql_text,
    qsrs.count_executions,
    CAST(qsp.query_plan AS XML) AS sql_query_plan
FROM sys.query_store_query qsq
JOIN sys.query_store_query_text qsqt
ON qsq.query_text_id = qsqt.query_text_id
JOIN sys.query_store_plan qsp
ON qsq.query_id= qsp.query_id
JOIN sys.query_store_runtime_stats qsrs
ON qsrs.plan_id = qsp.plan_id
WHERE query_sql_text LIKE '%dbo.a%'
AND qsq.[object_id] <> 0
ORDER BY qsq.query_id;
GO
--dc1

-- creating index
CREATE NONCLUSTERED INDEX IX_dbo_a_z
ON dbo.a([z] ASC) INCLUDE ([i], [g]);
GO

-- index seek
EXEC dbo.my_proc;

EXEC sp_query_store_flush_db; 

SELECT qsq.query_id,
    qsq.query_text_id,
    qsq.context_settings_id,
    qsq.[object_id],
    OBJECT_NAME(qsq.[object_id]) AS [object_name],
    qsp.is_forced_plan,
    qsqt.query_sql_text,
    qsrs.count_executions,
    CAST(qsp.query_plan AS XML) AS sql_query_plan
FROM sys.query_store_query qsq
JOIN sys.query_store_query_text qsqt
ON qsq.query_text_id = qsqt.query_text_id
JOIN sys.query_store_plan qsp
ON qsq.query_id= qsp.query_id
JOIN sys.query_store_runtime_stats qsrs
ON qsrs.plan_id = qsp.plan_id
WHERE query_sql_text LIKE '%dbo.a%'
AND qsq.[object_id] <> 0
ORDER BY qsq.query_id;

-- forcing plan GUI, clustered scan
-- dc3

EXEC sp_query_store_flush_db; 
SELECT qsq.query_id,
    qsq.query_text_id,
    qsq.context_settings_id,
    qsq.[object_id],
    OBJECT_NAME(qsq.[object_id]) AS [object_name],
    qsp.is_forced_plan,
    qsqt.query_sql_text,
    qsrs.count_executions,
    CAST(qsp.query_plan AS XML) AS sql_query_plan
FROM sys.query_store_query qsq
JOIN sys.query_store_query_text qsqt
ON qsq.query_text_id = qsqt.query_text_id
JOIN sys.query_store_plan qsp
ON qsq.query_id= qsp.query_id
JOIN sys.query_store_runtime_stats qsrs
ON qsrs.plan_id = qsp.plan_id
WHERE query_sql_text LIKE '%dbo.a%'
AND qsq.[object_id] <> 0
ORDER BY qsq.query_id;
-- dc4

-- Clustered Index Scan
EXEC dbo.my_proc;

EXEC sp_query_store_flush_db; 
SELECT qsq.query_id,
    qsq.query_text_id,
    qsq.context_settings_id,
    qsq.[object_id],
    OBJECT_NAME(qsq.[object_id]) AS [object_name],
    qsp.is_forced_plan,
    qsqt.query_sql_text,
    qsrs.count_executions,
    CAST(qsp.query_plan AS XML) AS sql_query_plan
FROM sys.query_store_query qsq
JOIN sys.query_store_query_text qsqt
ON qsq.query_text_id = qsqt.query_text_id
JOIN sys.query_store_plan qsp
ON qsq.query_id= qsp.query_id
JOIN sys.query_store_runtime_stats qsrs
ON qsrs.plan_id = qsp.plan_id
WHERE query_sql_text LIKE '%dbo.a%'
AND qsq.[object_id] <> 0
ORDER BY qsq.query_id;
-- dc5

/* MAIN PART  - DROP - RECREATE */
DROP PROCEDURE IF EXISTS dbo.my_proc;
GO

CREATE PROCEDURE dbo.my_proc
AS
-- some meaningless comment added by developer
SELECT *
FROM dbo.a
WHERE z LIKE '12%'
AND 1 = (SELECT 1);
GO

/* MAIN PART END */

-- Index Seek
EXEC dbo.my_proc;

EXEC sp_query_store_flush_db; 
SELECT qsq.query_id,
    qsq.query_text_id,
    qsq.context_settings_id,
    qsq.[object_id],
    OBJECT_NAME(qsq.[object_id]) AS [object_name],
    qsp.is_forced_plan,
    qsqt.query_sql_text,
    qsrs.count_executions,
    CAST(qsp.query_plan AS XML) AS sql_query_plan
FROM sys.query_store_query qsq
JOIN sys.query_store_query_text qsqt
ON qsq.query_text_id = qsqt.query_text_id
JOIN sys.query_store_plan qsp
ON qsq.query_id= qsp.query_id
JOIN sys.query_store_runtime_stats qsrs
ON qsrs.plan_id = qsp.plan_id
WHERE query_sql_text LIKE '%dbo.a%'
AND qsq.[object_id] <> 0
ORDER BY qsq.query_id;
-- object_id in query store is NULL
-- is_forced_plan flag is ignored !!!  

Первое исполнение:
DC1

Добавление индекса и выполнение: введите описание изображения здесь

План принуждения: введите описание изображения здесь введите описание изображения здесь

Другое исполнение: введите описание изображения здесь

После DROP-CREATE: введите описание изображения здесь


CREATE - ALTER

USE T2;
GO
-- make sure that Query Store is READ_WRITE 
IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[a]') AND type in (N'U'))
BEGIN
CREATE TABLE [dbo].[a](
    [i] [int] IDENTITY(1,1) NOT NULL PRIMARY KEY,
    [g] [uniqueidentifier] NULL,
    [z] VARCHAR(10)
);
END
GO

-- populate table (15k records)
INSERT INTO dbo.a(g, z)
SELECT NEWID(), number
FROM (SELECT CAST([key] AS INT) AS number 
    FROM OPENJSON( '[1' + REPLICATE(',1',3000-1)+']')
    ) AS num
GO 5

-- initial creation
CREATE PROCEDURE dbo.my_proc
AS
SELECT *
FROM dbo.a
WHERE z LIKE '12%'
AND 1 = (SELECT 1);
GO

-- Clustered Index Scan
EXEC dbo.my_proc;

EXEC sp_query_store_flush_db; 
SELECT qsq.query_id,
    qsq.query_text_id,
    qsq.context_settings_id,
    qsq.[object_id],
    OBJECT_NAME(qsq.[object_id]) AS [object_name],
    qsp.is_forced_plan,
    qsqt.query_sql_text,
    qsrs.count_executions,
    CAST(qsp.query_plan AS XML) AS sql_query_plan
FROM sys.query_store_query qsq
JOIN sys.query_store_query_text qsqt
ON qsq.query_text_id = qsqt.query_text_id
JOIN sys.query_store_plan qsp
ON qsq.query_id= qsp.query_id
JOIN sys.query_store_runtime_stats qsrs
ON qsrs.plan_id = qsp.plan_id
WHERE query_sql_text LIKE '%dbo.a%'
AND qsq.[object_id] <> 0
ORDER BY qsq.query_id;
-- ca1
GO

-- creating index
CREATE NONCLUSTERED INDEX IX_dbo_a_z
ON dbo.a([z] ASC) INCLUDE ([i], [g]);
GO

-- index seek
EXEC dbo.my_proc;

EXEC sp_query_store_flush_db; 
SELECT qsq.query_id,
    qsq.query_text_id,
    qsq.context_settings_id,
    qsq.[object_id],
    OBJECT_NAME(qsq.[object_id]) AS [object_name],
    qsp.is_forced_plan,
    qsqt.query_sql_text,
    qsrs.count_executions,
    CAST(qsp.query_plan AS XML) AS sql_query_plan
FROM sys.query_store_query qsq
JOIN sys.query_store_query_text qsqt
ON qsq.query_text_id = qsqt.query_text_id
JOIN sys.query_store_plan qsp
ON qsq.query_id= qsp.query_id
JOIN sys.query_store_runtime_stats qsrs
ON qsrs.plan_id = qsp.plan_id
WHERE query_sql_text LIKE '%dbo.a%'
AND qsq.[object_id] <> 0
ORDER BY qsq.query_id;
--ca2

-- forcing plan GUI
--ca3

EXEC sp_query_store_flush_db; 
SELECT qsq.query_id,
    qsq.query_text_id,
    qsq.context_settings_id,
    qsq.[object_id],
    OBJECT_NAME(qsq.[object_id]) AS [object_name],
    qsp.is_forced_plan,
    qsqt.query_sql_text,
    qsrs.count_executions,
    CAST(qsp.query_plan AS XML) AS sql_query_plan
FROM sys.query_store_query qsq
JOIN sys.query_store_query_text qsqt
ON qsq.query_text_id = qsqt.query_text_id
JOIN sys.query_store_plan qsp
ON qsq.query_id= qsp.query_id
JOIN sys.query_store_runtime_stats qsrs
ON qsrs.plan_id = qsp.plan_id
WHERE query_sql_text LIKE '%dbo.a%'
AND qsq.[object_id] <> 0
ORDER BY qsq.query_id;
--ca4

-- Clustered Index Scan
EXEC dbo.my_proc;

EXEC sp_query_store_flush_db; 
SELECT qsq.query_id,
    qsq.query_text_id,
    qsq.context_settings_id,
    qsq.[object_id],
    OBJECT_NAME(qsq.[object_id]) AS [object_name],
    qsp.is_forced_plan,
    qsqt.query_sql_text,
    qsrs.count_executions,
    CAST(qsp.query_plan AS XML) AS sql_query_plan
FROM sys.query_store_query qsq
JOIN sys.query_store_query_text qsqt
ON qsq.query_text_id = qsqt.query_text_id
JOIN sys.query_store_plan qsp
ON qsq.query_id= qsp.query_id
JOIN sys.query_store_runtime_stats qsrs
ON qsrs.plan_id = qsp.plan_id
WHERE query_sql_text LIKE '%dbo.a%'
AND qsq.[object_id] <> 0
ORDER BY qsq.query_id;
--ca5
GO

/* MAIN PART  - CREATE-ALTER */
CREATE OR ALTER PROCEDURE dbo.my_proc
AS
-- some meaningless comment added by developer
SELECT *
FROM dbo.a
WHERE z LIKE '12%'
AND 1 = (SELECT 1);
GO

/* MAIN PART END */

-- Clustered Index Scan
EXEC dbo.my_proc;

EXEC sp_query_store_flush_db; 
SELECT qsq.query_id,
    qsq.query_text_id,
    qsq.context_settings_id,
    qsq.[object_id],
    OBJECT_NAME(qsq.[object_id]) AS [object_name],
    qsp.is_forced_plan,
    qsqt.query_sql_text,
    qsrs.count_executions,
    CAST(qsp.query_plan AS XbML) AS sql_query_plan
FROM sys.query_store_query qsq
JOIN sys.query_store_query_text qsqt
ON qsq.query_text_id = qsqt.query_text_id
JOIN sys.query_store_plan qsp
ON qsq.query_id= qsp.query_id
JOIN sys.query_store_runtime_stats qsrs
ON qsrs.plan_id = qsp.plan_id
WHERE query_sql_text LIKE '%dbo.a%'
AND qsq.[object_id] <> 0
ORDER BY qsq.query_id;

-- is_forced_plan is valid

Первое исполнение:
введите описание изображения здесь

Добавление индекса и выполнение: введите описание изображения здесь

План принуждения: введите описание изображения здесь введите описание изображения здесь

Другое исполнение: введите описание изображения здесь

После CREATE-ALTER: введите описание изображения здесь

Результат

С Drop-Create мы потеряли принудительный план.

Ответ 12

У меня есть шаблон, который позволяет несколько раз выполнять script без ошибок.

IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[aaa_test]') AND type in (N'P', N'PC'))
    EXEC('CREATE PROCEDURE aaa_test AS')
    EXEC('GRANT EXECUTE ON aaa_test TO someone')
GO

ALTER PROCEDURE aaa_test 
     @PAR1 INT,
     @PAR2 INT=0
AS
BEGIN
    SELECT @PAR1 AS Par1, CASE @PAR2 WHEN 0 THEN 'Default' ELSE 'Other' END AS Par2
END
GO

Исполнение:

EXEC aaa_test 1
EXEC aaa_test 1,5

Ответ 13

Вы не должны бросать объект. Удаление объекта связано с двумя проблемами:

1) Если CREATE терпит неудачу, у вас больше нет объекта. (Вы можете использовать транзакции, чтобы избежать этого, за счет большого количества шаблонов)

2) Вы теряете права на объект, если вы их явно не создаете.


Я предпочитаю создавать пустой объект в условии "если не существует", а затем использовать ALTER и писать для этого вспомогательные процедуры.

Ответ 15

Только для моего продлить предыдущий ответ.

Другая причина, по которой я предпочитаю подход CREATE-ALTER over DROP-CREATE. Это может привести к потере свойств объекта. Например ExecIsStartup:

USE master
GO

CREATE TABLE dbo.silly_logging(id INT IDENTITY(1,1) PRIMARY KEY
                               ,created_date DATETIME DEFAULT GETDATE()
                               ,comment VARCHAR(100));
GO

CREATE PROCEDURE dbo.my_procedure 
AS
INSERT INTO dbo.silly_logging(comment)
VALUES ('SQL Server Startup');
GO

-- mark procedure to start at SQL Server instance startup
EXEC sp_procoption @ProcName = 'dbo.my_procedure'
    , @OptionName = 'startup'   
    , @OptionValue = 'on';


SELECT name, create_date, modify_date, is_auto_executed
FROM master.sys.procedures
WHERE is_auto_executed = 1;
--name  create_date modify_date is_auto_executed
--my_procedure  2017-07-28 06:36:21.743 2017-07-28 06:36:24.513 1

Теперь предположим, что кто-то хочет обновить эту процедуру, используя DROP-CREATE:

DROP PROCEDURE dbo.my_procedure;
GO

CREATE PROCEDURE dbo.my_procedure 
AS
-- adding meaningless comment
INSERT INTO dbo.silly_logging(comment)
VALUES ('SQL Server Startup');
GO

SELECT name, create_date, modify_date, is_auto_executed
FROM master.sys.procedures
WHERE is_auto_executed = 1;
-- empty

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