Create Procedure [dbo].[spGenerateID]
(
#sFieldName NVARCHAR(100),
#sTableName NVARCHAR(100)
)
AS
BEGIN
SELECT ISNULL(MAX(ISNULL(#sFieldName, 0)), 0) + 1 FROM #sTableName
END
In the above procedure I supply the field name and table name and I want the max number of this field .Why this not work?I also want to check if those fields are null than it's not work.. This procedure must have a return parameter of the field that I supplied which contain the max number.Please help me to fixed it.
Why does this not work.
How to check input parameter are not null.
How to set output parameter
You can't have field names and table names as parameters without wrapping the entire SELECT statement in an EXEC statement:
EXEC ('select isnull(max(isnull([' + #sFieldName + '],0)),0)+1
from [' + #sTableName + '] ')
You cannot supply the tablename and fieldname as parameters to a stored procedure.
You need to create a dynamic query and execute using sp_executesql.
You should read The Curse and Blessings of Dynamic SQL
If this is always to be used for identity columns you can use a variable
SELECT ISNULL(IDENT_CURRENT(#sTableName),0)+1
Otherwise you need to use dynamic SQL (The usual caveats about SQL injection apply.)
Additionally I'm somewhat dubious about the reasons behind this anyway unless you don't have any concurrency to worry about.
I've changed the type of your parameters to sysname as this is more appropriate.
CREATE PROCEDURE [dbo].[spGenerateID]
(
#sFieldName sysname,
#sTableName sysname,
#id int output
)
AS
BEGIN
DECLARE #dynsql NVARCHAR(1000)
SET #dynsql = 'select #id =isnull(max([' + #sFieldName + ']),0)+1 from [' + #sTableName + '];'
EXEC sp_executesql #dynsql, N'#id int output', #id OUTPUT
END
Example Usage
DECLARE #id int
EXECUTE [dbo].[spGenerateID]
'id'
,'MYTABLE'
,#id OUTPUT
SELECT #id
1) This won't work because of the way the table name was passed.
2) You only have to check for ISNULL one time, you have a redundant number of calls there.
3) You need not necessarily declare an output, just catch the return value when you execute the stored procedure.
If you're trying to generate a unique Id this is not the best way to do it because you could run into race conditions and generate a duplicate ID for one of the calls. Ideally the ID is already declared as an IDENTITY column, but if you can't do it that way then it's better to create a special table that just returns an ID as an IDENTITY column. Then you can access that table to get the latest version with assurance that you will get a unique ID.
Here is how your stored procedure could work without the redundant IsNull().
Create Procedure [dbo].[spGenerateID]
#sFieldName NVARCHAR(100),
#sTableName NVARCHAR(100)
AS
BEGIN
Exec ( 'SELECT max(isnull(' + #sFieldName + ',0))+1 FROM ' + #sTableName)
END
Related
I have a stored procedure which has several kind of parameters, INT, VARCHAR, DateTime, etc... This sp inserts a record into a log table with the parameters passed in. There are differents log tables, three exactly, which are called for example, LogTbl1, LogTbl2 and LogTbl3. This sp writes to LogTbl1, LogTbl2 or LogTbl3 depending on a sp parameter that indicates where to write to. I have set this parameter as tinyint and this takes as values 0, 1 or 2. Then depending on the value passed in, I build a dynamic query to write to the appropiate Log Table as below:
CREATE PROCEDURE [dbo].[spTraceLog]
#LogId int,
#param2 int,
#param3 int,
#param4 varchar(100),
#DateSent datetime,
#TargetTable tinyint = 0
AS
BEGIN
DECLARE #sqlCommand nvarchar(max)
DECLARE #tblName nvarchar(100)
SET #tblName = CASE #TargetTable
WHEN 0 THEN '[dbo].[LogTbl1]'
WHEN 1 THEN '[dbo].[LogTbl2]'
WHEN 2 THEN '[dbo].[LogTbl3]'
ELSE ''
END
IF #tblName <> ''
BEGIN
SET #sqlCommand =
'INSERT INTO ' + #tblName +
'([LogId]' +
',[param2]' +
',[param3]' +
',[param4]' +
',[Date]) ' +
'VALUES' +
'(#LogId' +
',#param2' +
',#param3' +
',#param4' +
',#DateSent)'
EXECUTE sp_executesql #sqlCommand
END
END
So is there any other better elegant way to do it? Not possible using an enumeration in the sp parameter #TargetTable?
Building up dynamic SQL then executing it means that Sql Server can't do a very good job of building an execution plan. This may impact efficiency and will matter most if the SP is called very frequently, which it looks like this SP will be.
Although the code would be less elegant I think it would be more efficient if your code had an if #TargetTable statement which contained three separate inserts which are all identical except for the table name you're inserting into.
But that doesn't answer the question. There is no enum and I don't think there is a problem with the type you've used to identify the log you want to write to. If you want the code to be more readable you could split it into three SPs and call them spTraceLog1, spTraceLog2 etc. and not pass in the TargetTable. I would avoid the dynamic SQL if possible.
As I have seen so far, people suggested using dynamic SQL.
For example:
How to pass schema as parameter to a stored procedure in sql server?
How to pass schema name as parameter in stored procedure
However, dynamic SQL has the risk of SQL injection. Hence, I want to know if there are any other safe alternatives?
Basically, this stored procedure that I am creating will be called at runtime. There will be 2 possible schemas to be passed in. And the table name will be passed in as well.
Something like below: (It does not work)
CREATE PROCEDURE [EFM].[usp_readApexTable]
#SCHEMANAME VARCHAR(20) = NULL,
#TABLENAME VARCHAR(100) = NULL
AS
BEGIN
SET NOCOUNT ON;
SELECT *
FROM [#SCHEMANAME].[#TABLENAME];
END
GO
This is just an example of READ action. My plan is to create for CRUD, which requires 4 different stored procedures.
You can use QUOTENAME to avoid any SQL injection and build your dynamic query like the following:
CREATE PROCEDURE [EFM].[usp_readApexTable]
#SCHEMANAME VARCHAR(20) = NULL,
#TABLENAME VARCHAR(100) = NULL
AS
BEGIN
SET NOCOUNT ON;
DECLARE #SQL VARCHAR(MAX)=N'SELECT * FROM '
+ QUOTENAME(#SCHEMANAME) + '.' + QUOTENAME(#TABLENAME)
EXEC (#SQL)
END
GO
Note: If you have any plan to add parameters also for your WHERE clause, in that case QUOTENAME will not help much, I suggest to to use sp_executesql by passing appropriate parameters used in WHERE clause.
Still you need to use QUOTENAME for schema and table name as SQL excepts it only as literal, you can't use variable names for table and schema.
For example.
declare #sql nvarchar(max)
set #sql = N'select * from ' + quotename(#SCHEMANAME ) + '.' + quotename(#TABLENAME )
+ ' where (City = #City)'
exec sp_executesql
#sql,
N'#City nvarchar(50)',
#City
You can find more details here
You need to use dynamic sql to do this operation
CREATE PROCEDURE [EFM].[usp_readApexTable]
#SCHEMANAME VARCHAR(20) = NULL,
#TABLENAME VARCHAR(100) = NULL
AS
BEGIN
SET NOCOUNT ON;
DECLARE #sqlCommand nvarchar(MAX)
SET #sqlCommand='SELECT * FROM ['+#SCHEMANAME+'].['+#TABLENAME+'];'
--Create Your Temp Table where you can set the records after executing the dynamic query
CREATE TABLE #tmpTbl(
Column1 [datatype]
Column2 [datatype]
.
.
ColumnN
)
INSERT INTO #tmpTbl EXEC sp_executesql #sqlCommand --Copy data to #tmpTbl table
SELECT * FROM #tmpTbl
DROP TABLE #tmpTbl
END
GO
I am trying to code a stored procedure in SQL that does the following
Takes 2 inputs (BatchType and "Column Name").
Searches database and gives the batchdate and the data in the column = "Column name"
Code is as give below
ALTER PROCEDURE [dbo].[chartmilldata]
-- Add the parameters for the stored procedure here
(#BatchType nvarchar (50),
#Data nvarchar(50))
AS
BEGIN
-- Insert statements for procedure here
SELECT BatchDate,#Data FROM --Database-- WHERE BatchType = #BatchType
END
I am trying to select column from the database based on operator input. But I am not getting the output. It would be great if someone can give me a direction.
You may want to build out your SELECT statement as a string then execute it using sp_executesql.
See this page for more info:
https://msdn.microsoft.com/en-us/library/ms188001.aspx
This will allow you to set your query to substitute in your column name via your variable and then execute the statement. Be sure to sanitize your inputs though!
You'd need to use dynamic SQL, HOWEVER I would not recommend this solution, I don't think there is anything I can add as to why I wouldn't recommend it that isn't explained better in Erland Sommarskog in The Curse and Blessings of Dynamic SQL.
Nonetheless, if you had to do it in a stored procedure you could use something like:
ALTER PROCEDURE [dbo].[chartmilldata]
-- Add the parameters for the stored procedure here
(#BatchType nvarchar (50),
#Data nvarchar(50))
AS
BEGIN
-- DECLARE AND SET SQL TO EXECUTE
DECLARE #SQL NVARCHAR(MAX) = N'SELECT BatchDate = NULL, ' +
QUOTENAME(#Data) + N' = NULL;';
-- CHECK COLUMN IS VALID IN THE TABLE
IF EXISTS
( SELECT 1
FROM sys.columns
WHERE name = #Data
AND object_id = OBJECT_ID('dbo.YourTable', 'U')
)
BEGIN
SET #SQL = 'SELECT BatchDate, ' + QUOTENAME(#Data) +
' FROM dbo.YourTable WHERE BatchType = #BatchType;';
END
EXECUTE sp_executesql #SQL, N'#BatchType NVARCHAR(50)', #BatchType;
END
It would probably be advisable to change your input parameter #Data to be NVARCHAR(128) (or the alias SYSNAME) though, since this is the maximum for column names.
When I run the following code, I get an "invalid object name" error, any idea why?
I need to create a dynamically named temp table to be used in a stored procedure.
DECLARE #SQL NVARCHAR(MAX)
DECLARE #SessionID NVARCHAR(50)
SET #SessionID = 'tmp5l7g9q3l1h1n5s4k9k7e'
;
SET
#SQL = N' CREATE TABLE #' + #SessionID + ' ' +
N' (' +
N' CustomerNo NVARCHAR(5), ' +
N' Product NVARCHAR(3), ' +
N' Gross DECIMAL(18,8) ' +
N' )'
;
EXECUTE sp_executesql #SQL
;
SET
#SQL = N' SELECT * FROM #' + #SessionID
;
EXECUTE sp_executesql #SQL
Thanks!
WHY MESS WITH THE NAMES? Let SQL Server will manage this for you:
Temporary Tables in SQL Server
from the above link:
If the same routine is executed simultaneously by several processes,
the Database Engine needs to be able to distinguish between the
identically-named local temporary tables created by the different
processes. It does this by adding a numeric string to each local
temporary table name left-padded by underscore characters. Although
you specify the short name such as #MyTempTable, what is actually
stored in TempDB is made up of the table name specified in the CREATE
TABLE statement and the suffix. Because of this suffix, local
temporary table names must be 116 characters or less.
If you’re interested in seeing what is going on, you can view the
tables in TempDB just the same way you would any other table. You can
even use sp_help work on temporary tables only if you invoke them from
TempDB.
USE TempDB
go
execute sp_Help #mytemp
or you can find them in the system views of TempDB without swithching
databases.
SELECT name, create_date FROM TempDB.sys.tables WHERE name LIKE '#%'
You are doing it wrong!
Try:
exec(#SQL)
instead of:
EXECUTE sp_executesql #SQL
To use sp_executesql the variable must be inside #SessionID the quotes and it must be provided has input parameter. Check this for a full example!
You've to be aware that Dynamic SQL is a good port for SQL injections!
This syntax works
CREATE TABLE #SessionID (CustomerNo NVARCHAR(5), Product NVARCHAR(3), Gross DECIMAL(18,8));
Select COUNT(*) from #SessionID;
Drop Table #SessionID;
The following code generates the primaey key for the new record to be inserted and inserts the record into a table, whose name and the values to be inserted are given as parameters to the stored procedure. I am getting a runtime error. I am using Visual Studio 2005 to work with SQL Server 2005 Express Edition
ALTER PROCEDURE spGenericInsert
(
#insValueStr nvarchar(300),
#tblName nvarchar(10)
)
AS
DECLARE #sql nvarchar(400)
DECLARE #params nvarchar(200)
DECLARE #insPrimaryKey nvarchar(10)
DECLARE #rowCountVal integer
DECLARE #prefix nvarchar(5)
--following gets the rowcount of the table--
SELECT #rowCountVal = ISNULL(SUM(spart.rows), 0)
FROM sys.partitions spart
WHERE spart.object_id = object_id(#tblName) AND spart.index_id < 2
SET #rowCountVal = #rowCountVal+1
--Following Creates the Primary Key--
IF #tblName = 'DEFECT_LOG'
SET #prefix='DEF_'
ELSE IF #tblName='INV_Allocation_DB'
SET #prefix='INV_'
ELSE IF #tblName='REQ_Master_DB'
SET #prefix='REQ_'
ELSE IF #tblName='SW_Master_DB'
SET #prefix='SWI_'
ELSE IF #tblName='HW_Master_DB'
SET #prefix='HWI_'
SET #insPrimaryKey= #prefix + RIGHT(replicate('0',5)+ convert(varchar(5),#rowCountVal),5) -- returns somethin like 'DEF_00005'
-- Following is for inserting into the table --
SELECT #sql = N' INSERT INTO #tableName VALUES ' +
N' ( #PrimaryKey , #ValueStr )'
SELECT #params = N'#tableName nvarchar(10), ' +
N'#PrimaryKey nvarchar(10), ' +
N'#ValueStr nvarchar(300)'
EXEC sp_executesql #sql, #params, #tableName=#tblName, #PrimaryKey=#insPrimaryKey, #ValueStr=#insValueStr
Output Message:
Running [dbo].[spGenericInsert] ( #insValueStr = 2,"Hi",1/1/1987, #tblName = DEFECT_LOG ).
Must declare the table variable "#tableName".
No rows affected.
(0 row(s) returned)
#RETURN_VALUE = 0
Finished running [dbo].[spGenericInsert].
You are going to have to concatenate the table name directly into the string, as this cannot be parameterized:
SELECT #sql = N' INSERT INTO [' + #tblName + '] VALUES ' +
N' ( #PrimaryKey , #ValueStr )'
SELECT #params = N'#PrimaryKey nvarchar(10), ' +
N'#ValueStr nvarchar(300)'
To prevent injection attacks, you should white-list this table name. This also isn't robust if the table has other non-nullable columns, etc.
note: Personally, though, I don't think this is a good use of TSQL; it might be more appropriate to construct the command in the client (C# or whatever), and execute it as a parameterized command. There are use-cases for dynamic-SQL, but I'm not sure this is a good example of one.
Better yet, use your preferred ORM tool (LINQ-to-SQL, NHibernate, LLBLGen, Entity Framework, etc) to do all this for you, and concentrate on your actual problem domain.
White list essentially means make sure that the table being passed in is a valid table that you want them to be able to insert into. Let's just say for arguments sake that table name is user provided, the user could then start inserting records into system tables.
You can do a white list check by bouncing the table name of the sysobjects table:
select * from sysobjects where name=#tblname and xType='U'
However as Marc suggested this is not a good use of TSQL, and your better off handling this in the app tier as a paramatized query.
Agree with Marc- overall this is an extremely poor idea. Generic inserts/updates or deletes cause problems for the database eventually.
Another point is that this process will have problems when two users run simulutaneously against the same table as they will try to insert the same Primary Key.