2

I am attempting to run some SQL queries if a table doesn't already exist, specifically:

  1. Create the table.
  2. Add some data.
  3. Create a stored procedure to access the data.

Step 1 and 2 work fine, but when I try to do the third part, I get an error. Here is my code (don't need to read thoroughly - here for reference, skip to problem area below):

USE [MyDB]
GO


IF NOT EXISTS (SELECT * FROM sysobjects WHERE name='fooTable' and xtype='U')
    BEGIN
        CREATE TABLE fooTable
        (
            ID INT NOT NULL IDENTITY(1,1),
            SomeNumber INT NOT NULL, 
            SomeOtherTable_ID INT NOT NULL, 
            PRIMARY KEY (ID),
            FOREIGN KEY (SomeOtherTable_ID) REFERENCES SomeOtherTable(SomeOtherTable_ID)
        );

        INSERT INTO fooTable (SomeNumber, SomeOtherTable_ID)
        VALUES (5, 1), (10, 1), (25, 1), (50, 1), (100, 1), (500, 1)

        CREATE PROCEDURE myProcedureName
        AS
        SELECT SomeNumber from [fooTable]
    END 

The relevant trouble line is the third one here, it works fine OUTSIDE of the if statement:

CREATE PROCEDURE myProcedureName
AS
SELECT SomeNumber from [fooTable]

but if I try to run it in the if block, SELECT gets underlined in red with the following:

Incorrect syntax near SELECT. Expecting EXTERNAL.

How do I run the stored procedure inside the if block? If it's relevant, this is in SQL Server using T-SQL.

Solomon Rutzky
  • 46,688
  • 9
  • 128
  • 171
VSO
  • 11,546
  • 25
  • 99
  • 187

3 Answers3

5

Stored procedures, triggers, functions, and several other object types need to be in their own batch. This can be achieved within an IF block by wrapping the CREATE statement in an EXEC(N'....'):

IF (OBJECT_ID(N'dbo.footTable') IS NULL)
BEGIN
   CREATE TABLE dbo.[fooTable]
   (
      ID INT NOT NULL IDENTITY(1, 1)
           CONSTRAINT [PK_fooTable] PRIMARY KEY,
      SomeNumber INT NOT NULL, 
      SomeOtherTable_ID INT NOT NULL
           CONSTRAINT [FK_fooTable_SomeOtherTable] FOREIGN KEY
           REFERENCES dbo.SomeOtherTable(SomeOtherTable_ID)
   );

   INSERT INTO dbo.fooTable (SomeNumber, SomeOtherTable_ID)
   VALUES (5, 1), (10, 1), (25, 1), (50, 1), (100, 1), (500, 1);

   EXEC(N'
        CREATE PROCEDURE dbo.myProcedureName
        AS
        SELECT SomeNumber FROM [fooTable];
   ');
END;

Notes:

  • Be sure to include the capital-N prefix on string literals containing dynamic SQL (as a best practice -- yes, not having it often works, but having it always works).

  • Don't use deprecated compatibility views such as sysobjects. Starting with the release of SQL Server 2005, the proper system catalog views are in the sys schema. In this case, it would be sys.objects.

  • You should schema-qualify the objects: dbo.fooTable instead of just fooTable

  • You should name your constraints: PK, FKs, etc.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Solomon Rutzky
  • 46,688
  • 9
  • 128
  • 171
  • Thanks! //comment length – VSO Oct 20 '16 at 00:13
  • 1
    @VSO Yer welcome. I made some updates to improve the code. Please take a look. – Solomon Rutzky Oct 20 '16 at 02:14
  • I appreciate it - I was actually going to ask for an explanation on N, but google answered it quickly. I was also wondering about the second point since Vince used sys.objects, but I have seen it recommended as sysobjects. – VSO Oct 20 '16 at 18:28
  • 1
    @VSO Actually, Vince used `sys.procedures` which is a subset of `sys.objects` but with some additional columns. Still, using `sys___` without any period in there has been a bad idea starting with SQL Server 2005, no matter how many examples of it are out there, and no matter how recent any of them are. – Solomon Rutzky Oct 20 '16 at 18:44
  • Was just pointing out that you hit the nail on the head with the notes you added, since you corrected me on the bad practiced I saw elsewhere. Thanks again. – VSO Oct 20 '16 at 19:25
1

CREATE PROCEDURE needs to be in its own block. Move it out and do something like:

IF EXISTS ( SELECT * FROM sys.procedures  WHERE NAME = 'myProcedureName' AND type = 'P')
     DROP PROCEDURE myProcedureName
GO
CREATE PROCEDURE myProcedureName
AS
SELECT SomeNumber from [fooTable]
Vince
  • 620
  • 1
  • 5
  • 9
  • Thanks for the answer. Is there no way to put it inside the block? – VSO Oct 19 '16 at 23:53
  • Not in the same block. "CREATE DEFAULT, CREATE FUNCTION, CREATE PROCEDURE, CREATE RULE, CREATE TRIGGER, and CREATE VIEW statements cannot be combined with other statements in a batch". See: http://stackoverflow.com/questions/37019512/why-shouldnt-transact-sql-statements-be-grouped-together-within-the-same-batch – Vince Oct 20 '16 at 00:32
  • P = SQL Stored Procedure. sys.procedures contains a row for each object that is a procedure of some kind, with sys.objects.type = P, X, RF, and PC. The description can be found here: https://learn.microsoft.com/en-us/sql/relational-databases/system-catalog-views/sys-objects-transact-sql?view=sql-server-2017 – Vince Dec 08 '18 at 10:05
0

I like the following idiom:

if object_id('dbo.yourProc') is not null --object exists
   set noexec on;
go
create procedure dbo.yourProc as
begin
   select 'not implemented' as [message]
end
go
set noexec off;
go
alter procedure dbo.yourProc as
begin
   «actual definition here»
end
go

What I like about this is that preserves any permissions that already exist on the procedure and is idempotent.

Ben Thul
  • 31,080
  • 4
  • 45
  • 68