Agility – how to autogenerate stored procedures for CRUDs (SQL Server)?
One of the
most frequently tasks that we face every day as developers is to generate the
stored procedures for CRUDs operations over our tables.
I am
sharing a script taken from here. What exactly does it do? It generates the stored procedures for CRUD operations over one or all
tables of a database, in seconds. The main advantages are:
- It is a script, simple, clear :). It focuses on SP generation and period. It does what it promises very well.
-
It does not require to install
anything, as it is
only a script.
-
It
concentrates on the database, hence it
does not depend on any solution, framework or platform.
- Easily customizable, ie. If we want to change the naming convention. In fact, i could do that easliy :).
-
The generated code is easy to read and worked on SQL 2008 too.
-
It is
free :)
Although
there are some paid or free tools that allow doing this, the simplicity and portability
of this script makes it a strong candidate to use within a project.
Additionally, it does not compete with other tools that promise an ORM (Object Relational Mapping) model, i.e. Entity Framework. Besides, at the end of the day they can be (and it is a good practice) linked to stored procedures which need to be generated too. So, we can use this script for automating that.
Well, the idea
of this post is to contribute to agility, speed up our development. Any feedback as well as other proposals are
welcomed.
Thanks
PP
--
#########################################################
-- Author: www.sqlbook.com
--
Copyright: (c) www.sqlbook.com. You
are free to use and redistribute
-- this script as long as this comments
section with the
-- author and copyright details are not
altered.
-- Purpose: For a specified user defined table (or all user
defined
-- tables) in the database this script
generates 4 Stored
-- Procedure definitions with different
Procedure name
-- suffixes:
-- 1) List all records in the table
(suffix of _lst)
-- 2) Get a specific record from the
table (suffix of _sel)
-- 3) UPDATE or INSERT (UPSERT) - (suffix
of _ups)
-- 4) DELETE a specified row - (suffix of
_del)
-- e.g. For a table called location the
script will create
-- procedure definitions for the
following procedures:
-- dbo.udp_Location_lst
-- dbo.udp_Location_sel
-- dbo.udp_Location_ups
-- dbo.udp_Location_del
-- Notes: The stored procedure definitions can either be
printed
-- to the screen or executed using EXEC
sp_ExecuteSQL.
-- The stored proc names are prefixed
with udp_ to avoid
-- conflicts with system stored procs.
--
Assumptions: - This script assumes that
the primary key is the first
-- column in the table and that if the
primary key is
-- an integer then it is an IDENTITY
(autonumber) field.
-- - This script is not suitable for the
link tables
-- in the middle of a many to many
relationship.
-- - After the script has run you will
need to add
-- an ORDER BY clause into the '_lst'
procedures
-- according to your needs / required
sort order.
-- - Assumes you have set valid values
for the
-- config variables in the section
immediately below
--
#########################################################
--
##########################################################
/* SET
CONFIG VARIABLES THAT ARE USED IN SCRIPT */
--
##########################################################
-- Do we
want to generate the SP definitions for every user defined
-- table in
the database or just a single specified table?
-- Assign a
blank string - '' for all tables or the table name for
-- a single
table.
DECLARE @GenerateProcsFor varchar(100)
SET @GenerateProcsFor = 'Orders'
--SET
@GenerateProcsFor = ''
-- which
database do we want to create the procs for?
-- Change
both the USE and SET lines below to set the datbase name
-- to the
required database.
USE Northwind
DECLARE @DatabaseName varchar(100)
SET @DatabaseName = 'Northwind'
-- do we
want the script to print out the CREATE PROC statements
-- or do we
want to execute them to actually create the procs?
-- Assign a
value of either 'Print' or 'Execute'
DECLARE @PrintOrExecute varchar(10)
SET @PrintOrExecute = 'Print'
-- Is there
a table name prefix i.e. 'tbl_' which we don't want
-- to
include in our stored proc names?
DECLARE @TablePrefix varchar(10)
SET @TablePrefix = 'tbl_'
-- For our
'_lst' and '_sel' procedures do we want to
-- do
SELECT * or SELECT [ColumnName,]...
-- Assign a
value of either 1 or 0
DECLARE @UseSelectWildCard bit
SET @UseSelectWildCard = 0
--
##########################################################
/* END
SETTING OF CONFIG VARIABLE
-- do not
edit below this line */
--
##########################################################
-- DECLARE
CURSOR containing all columns from user defined tables
-- in the
database
DECLARE TableCol Cursor FOR
SELECT c.TABLE_SCHEMA, c.TABLE_NAME, c.COLUMN_NAME, c.DATA_TYPE, c.CHARACTER_MAXIMUM_LENGTH
FROM INFORMATION_SCHEMA.Columns c INNER JOIN
INFORMATION_SCHEMA.Tables t ON c.TABLE_NAME = t.TABLE_NAME
WHERE t.Table_Catalog = @DatabaseName
AND t.TABLE_TYPE
= 'BASE TABLE'
ORDER BY c.TABLE_NAME, c.ORDINAL_POSITION
-- Declare
variables which will hold values from cursor rows
DECLARE @TableSchema varchar(100), @TableName varchar(100)
DECLARE @ColumnName varchar(100), @DataType varchar(30)
DECLARE @CharLength int
DECLARE @ColumnNameCleaned varchar(100)
-- Declare
variables which will track what table we are
-- creating
Stored Procs for
DECLARE @CurrentTable varchar(100)
DECLARE @FirstTable bit
DECLARE @FirstColumnName varchar(100)
DECLARE @FirstColumnDataType varchar(30)
DECLARE @ObjectName varchar(100) -- this is the tablename with the
-- specified
tableprefix lopped off.
DECLARE @TablePrefixLength int
-- init
vars
SET @CurrentTable = ''
SET @FirstTable = 1
SET @TablePrefixLength = Len(@TablePrefix)
-- Declare
variables which will hold the queries we are building use unicode
-- data
types so that can execute using sp_ExecuteSQL
DECLARE @LIST nvarchar(4000), @UPSERT nvarchar(4000)
DECLARE @SELECT nvarchar(4000), @INSERT nvarchar(4000), @INSERTVALUES varchar(4000)
DECLARE @UPDATE nvarchar(4000), @DELETE nvarchar(4000)
-- open the
cursor
OPEN TableCol
-- get the
first row of cursor into variables
FETCH NEXT FROM TableCol INTO
@TableSchema, @TableName, @ColumnName, @DataType, @CharLength
-- loop
through the rows of the cursor
WHILE @@FETCH_STATUS = 0 BEGIN
SET @ColumnNameCleaned = Replace(@ColumnName, ' ', '')
-- is this a new table?
IF @TableName <> @CurrentTable BEGIN
-- if is the end of the last
table
IF @CurrentTable <> '' BEGIN
IF
@GenerateProcsFor = ''
OR @GenerateProcsFor =
@CurrentTable BEGIN
-- first add any
syntax to end the statement
-- _lst
SET @LIST = @List + Char(13) + 'FROM ' +
@CurrentTable + Char(13)
SET @LIST = @LIST + Char(13) + Char(13) + 'SET NOCOUNT OFF' + Char(13) + Char(13)
SET @LIST = @LIST + Char(13)
-- _sel
SET @SELECT = @SELECT + Char(13) + 'FROM ' +
@CurrentTable + Char(13)
SET @SELECT = @SELECT + 'WHERE [' +
@FirstColumnName + '] =
@' + Replace(@FirstColumnName, ' ', '') + Char(13)
SET @SELECT = @SELECT + Char(13) + Char(13) + 'SET NOCOUNT OFF' + Char(13) + Char(13)
SET @SELECT = @SELECT + Char(13)
-- UPDATE (remove
trailing comma and append the WHERE clause)
SET @UPDATE = SUBSTRING(@UPDATE, 0, LEN(@UPDATE)- 1) + Char(13) + Char(9) + 'WHERE [' +
@FirstColumnName + '] =
@' + Replace(@FirstColumnName, ' ', '') + Char(13)
-- INSERT
SET @INSERT = SUBSTRING(@INSERT, 0, LEN(@INSERT) - 1) + Char(13) + Char(9) + ')' + Char(13)
SET
@INSERTVALUES = SUBSTRING(@INSERTVALUES, 0, LEN(@INSERTVALUES) -1) + Char(13) + Char(9) + ')'
SET @INSERT = @INSERT +
@INSERTVALUES
-- _ups
SET @UPSERT = @UPSERT + Char(13) + 'AS' + Char(13)
SET @UPSERT = @UPSERT + 'SET NOCOUNT ON' + Char(13)
IF
@FirstColumnDataType IN
('int', 'bigint', 'smallint', 'tinyint', 'float', 'decimal')
BEGIN
SET
@UPSERT = @UPSERT +
'IF @' + Replace(@FirstColumnName, ' ', '') + ' = 0 BEGIN' + Char(13)
END ELSE BEGIN
SET
@UPSERT = @UPSERT +
'IF @' + Replace(@FirstColumnName, ' ', '') + ' = '''' BEGIN' + Char(13)
END
SET @UPSERT = @UPSERT + ISNULL(@INSERT, '') + Char(13)
SET @UPSERT = @UPSERT + Char(9) + 'SELECT SCOPE_IDENTITY() As InsertedID' + Char(13)
SET @UPSERT = @UPSERT + 'END' + Char(13)
SET @UPSERT = @UPSERT + 'ELSE BEGIN' + Char(13)
SET @UPSERT = @UPSERT + ISNULL(@UPDATE, '') + Char(13)
SET @UPSERT = @UPSERT + 'END' + Char(13) + Char(13)
SET @UPSERT = @UPSERT + 'SET NOCOUNT OFF' + Char(13) + Char(13)
SET @UPSERT = @UPSERT + Char(13)
-- _del
-- delete proc
completed already
--
--------------------------------------------------
-- now either print
the SP definitions or
-- execute the
statements to create the procs
--
--------------------------------------------------
IF
@PrintOrExecute <> 'Execute' BEGIN
PRINT
@LIST
PRINT
@SELECT
PRINT
@UPSERT
PRINT
@DELETE
END ELSE BEGIN
EXEC sp_Executesql @LIST
EXEC sp_Executesql @SELECT
EXEC sp_Executesql @UPSERT
EXEC sp_Executesql @DELETE
END
END -- end @GenerateProcsFor = '' OR @GenerateProcsFor =
@CurrentTable
END
-- update the value held in
@CurrentTable
SET @CurrentTable = @TableName
SET @FirstColumnName = @ColumnName
SET @FirstColumnDataType = @DataType
IF @TablePrefixLength > 0 BEGIN
IF SUBSTRING(@CurrentTable, 1,
@TablePrefixLength) =
@TablePrefix BEGIN
--PRINT Char(13) +
'DEBUG: OBJ NAME: ' + RIGHT(@CurrentTable, LEN(@CurrentTable) -
@TablePrefixLength)
SET
@ObjectName = RIGHT(@CurrentTable, LEN(@CurrentTable) - @TablePrefixLength)
END ELSE BEGIN
SET
@ObjectName = @CurrentTable
END
END ELSE BEGIN
SET @ObjectName = @CurrentTable
END
IF @GenerateProcsFor = '' OR @GenerateProcsFor =
@CurrentTable BEGIN
--
----------------------------------------------------
-- now start building the
procedures for the next table
--
----------------------------------------------------
-- _lst
SET @LIST = 'CREATE PROC [dbo].[udp_' +
@ObjectName + '_lst]'
+ Char(13)
SET @LIST = @LIST + 'AS' + Char(13)
SET @LIST = @LIST + 'SET NOCOUNT ON' + Char(13)
IF
@UseSelectWildcard = 1 BEGIN
SET @LIST = @LIST + Char(13) + 'SELECT * '
END
ELSE BEGIN
SET @LIST = @LIST + Char(13) + 'SELECT [' +
@ColumnName + ']'
END
-- _sel
SET @SELECT = 'CREATE PROC [dbo].[udp_'
+ @ObjectName +
'_sel]' + Char(13)
SET @SELECT = @SELECT + Char(9) + '@' +
@ColumnNameCleaned + '
' + @DataType
IF @DataType IN ('varchar', 'nvarchar', 'char', 'nchar') BEGIN
SET @SELECT = @SELECT + '(' + CAST(@CharLength As varchar(10)) + ')'
END
SET @SELECT = @SELECT + Char(13) + 'AS' + Char(13)
SET @SELECT = @SELECT + 'SET NOCOUNT ON' + Char(13)
IF @UseSelectWildcard = 1 BEGIN
SET @SELECT = @SELECT + Char(13) + 'SELECT * '
END
ELSE BEGIN
SET @SELECT = @SELECT + Char(13) + 'SELECT [' +
@ColumnName + ']'
END
-- _ups
SET @UPSERT = 'CREATE PROC [dbo].[udp_'
+ @ObjectName +
'_ups]' + Char(13)
SET
@UPSERT = @UPSERT +
Char(13) + Char(9) + '@' +
@ColumnNameCleaned + '
' + @DataType
IF @DataType IN ('varchar', 'nvarchar', 'char', 'nchar') BEGIN
SET @UPSERT = @UPSERT + '(' + CAST(@CharLength As Varchar(10)) + ')'
END
-- UPDATE
SET @UPDATE = Char(9) + 'UPDATE ' + @TableName + ' SET ' + Char(13)
-- INSERT -- don't add
first column to insert if it is an
-- integer (assume autonumber)
SET @INSERT = Char(9) + 'INSERT INTO ' + @TableName + ' (' + Char(13)
SET @INSERTVALUES = Char(9) + 'VALUES (' + Char(13)
IF
@FirstColumnDataType NOT IN ('int', 'bigint', 'smallint', 'tinyint')
BEGIN
SET @INSERT = @INSERT
+ Char(9) + Char(9) + '[' + @ColumnName + '],' + Char(13)
SET
@INSERTVALUES = @INSERTVALUES + Char(9) + Char(9) + '@' + @ColumnNameCleaned +
',' + Char(13)
END
-- _del
SET @DELETE = 'CREATE PROC [dbo].[udp_'
+ @ObjectName +
'_del]' + Char(13)
SET @DELETE = @DELETE + Char(9) + '@' +
@ColumnNameCleaned + '
' + @DataType
IF @DataType IN ('varchar', 'nvarchar', 'char', 'nchar') BEGIN
SET @DELETE = @DELETE + '(' + CAST(@CharLength As Varchar(10)) + ')'
END
SET @DELETE = @DELETE + Char(13) + 'AS' + Char(13)
SET @DELETE = @DELETE + 'SET NOCOUNT ON' + Char(13) + Char(13)
SET @DELETE = @DELETE + 'DELETE FROM ' +
@TableName + Char(13)
SET @DELETE = @DELETE + 'WHERE [' +
@ColumnName + '] = @'
+ @ColumnNameCleaned +
Char(13)
SET @DELETE = @DELETE + Char(13) + 'SET NOCOUNT OFF' + Char(13)
SET @DELETE = @DELETE + Char(13)
END -- end
@GenerateProcsFor = '' OR @GenerateProcsFor = @CurrentTable
END
ELSE BEGIN
IF @GenerateProcsFor = '' OR @GenerateProcsFor =
@CurrentTable BEGIN
-- is the same table as
the last row of the cursor
-- just append the column
-- _lst
IF
@UseSelectWildCard = 0 BEGIN
SET @LIST = @LIST + ', ' + Char(13) + Char(9) + '[' + @ColumnName + ']'
END
-- _sel
IF
@UseSelectWildCard = 0 BEGIN
SET @SELECT = @SELECT + ', ' + Char(13) + Char(9) + '[' + @ColumnName + ']'
END
-- _ups
SET @UPSERT = @UPSERT + ',' + Char(13) + Char(9) + '@' +
@ColumnNameCleaned + '
' + @DataType
IF @DataType IN ('varchar', 'nvarchar', 'char', 'nchar') BEGIN
SET @UPSERT = @UPSERT + '(' + CAST(@CharLength As varchar(10)) + ')'
END
-- UPDATE
SET @UPDATE = @UPDATE + Char(9) + Char(9) + '[' + @ColumnName + '] = @' + @ColumnNameCleaned +
',' + Char(13)
-- INSERT
SET @INSERT = @INSERT + Char(9) + Char(9) + '[' + @ColumnName + '],' + Char(13)
SET @INSERTVALUES = @INSERTVALUES + Char(9) + Char(9) + '@' +
@ColumnNameCleaned + ','
+ Char(13)
-- _del
-- delete proc completed
already
END -- end @GenerateProcsFor = '' OR @GenerateProcsFor =
@CurrentTable'
END
-- fetch next row of cursor into
variables
FETCH NEXT
FROM TableCol INTO
@TableSchema, @TableName, @ColumnName, @DataType, @CharLength
END
--
----------------
-- clean up
cursor
--
----------------
CLOSE TableCol
DEALLOCATE TableCol
--
------------------------------------------------
-- repeat
the block of code from within the cursor
-- So that
the last table has its procs completed
-- and printed
/ executed
--
------------------------------------------------
-- if is
the end of the last table
IF @CurrentTable <>
'' BEGIN
IF @GenerateProcsFor = '' OR
@GenerateProcsFor = @CurrentTable BEGIN
-- first add any syntax to end
the statement
-- _lst
SET @LIST = @List + Char(13) + 'FROM ' +
@CurrentTable + Char(13)
SET @LIST = @LIST + Char(13) + Char(13) + 'SET NOCOUNT OFF' + Char(13)
SET @LIST =
@LIST + Char(13)
-- _sel
SET @SELECT = @SELECT + Char(13) + 'FROM ' + @CurrentTable + Char(13)
SET @SELECT = @SELECT + 'WHERE [' +
@FirstColumnName + '] =
@' + Replace(@FirstColumnName, ' ', '') + Char(13)
SET @SELECT = @SELECT + Char(13) + Char(13) + 'SET NOCOUNT OFF' + Char(13)
SET @SELECT = @SELECT + Char(13)
-- UPDATE (remove trailing
comma and append the WHERE clause)
SET @UPDATE = SUBSTRING(@UPDATE, 0, LEN(@UPDATE)- 1) + Char(13) + Char(9) + 'WHERE [' +
@FirstColumnName + '] =
@' + Replace(@FirstColumnName, ' ', '') + Char(13)
-- INSERT
SET @INSERT = SUBSTRING(@INSERT, 0, LEN(@INSERT) - 1) + Char(13) + Char(9) + ')' + Char(13)
SET @INSERTVALUES = SUBSTRING(@INSERTVALUES, 0, LEN(@INSERTVALUES) -1) + Char(13) + Char(9) + ')'
SET @INSERT = @INSERT +
@INSERTVALUES
-- _ups
SET @UPSERT = @UPSERT + Char(13) + 'AS' + Char(13)
SET @UPSERT = @UPSERT + 'SET NOCOUNT ON' + Char(13)
IF @FirstColumnDataType IN ('int', 'bigint', 'smallint', 'tinyint', 'float', 'decimal')
BEGIN
SET @UPSERT = @UPSERT + 'IF @' + Replace(@FirstColumnName, ' ', '') + ' = 0 BEGIN' + Char(13)
END ELSE BEGIN
SET @UPSERT = @UPSERT + 'IF @' + Replace(@FirstColumnName, ' ', '') + ' = '''' BEGIN' + Char(13)
END
SET @UPSERT = @UPSERT + ISNULL(@INSERT, '') + Char(13)
SET @UPSERT = @UPSERT + Char(9) + 'SELECT SCOPE_IDENTITY() As InsertedID' + Char(13)
SET @UPSERT = @UPSERT + 'END' + Char(13)
SET @UPSERT = @UPSERT + 'ELSE BEGIN' + Char(13)
SET @UPSERT = @UPSERT + ISNULL(@UPDATE, '') + Char(13)
SET @UPSERT = @UPSERT + 'END' + Char(13) + Char(13)
SET @UPSERT = @UPSERT
+ 'SET NOCOUNT OFF'
+ Char(13)
SET @UPSERT = @UPSERT + Char(13)
-- _del
-- delete proc completed
already
--
--------------------------------------------------
-- now either print the SP
definitions or
-- execute the statements to
create the procs
--
--------------------------------------------------
IF @PrintOrExecute <> 'Execute' BEGIN
PRINT @LIST
PRINT @SELECT
PRINT @UPSERT
PRINT @DELETE
END ELSE BEGIN
EXEC sp_Executesql @LIST
EXEC sp_Executesql @SELECT
EXEC sp_Executesql @UPSERT
EXEC sp_Executesql @DELETE
END
END -- end @GenerateProcsFor = '' OR
@GenerateProcsFor = @CurrentTable
END