Sql Server Stored Procedure dynamically select table name - sql

i have this stored procedure, I want to dynamically select table name based on the variable passing ie #Practice_Short_Name
Create procedure [dbo].[GetCompleteCPTDetails]
#Practice_Short_Name varchar(50) is Null,
#Uploaded_Date varchar(30) is Null
as
begin
DECLARE #CPTtablename varchar(100)
DECLARE #vQuery NVARCHAR(100)
--Dynamically select Table name based on #practice_short_name
set #CPTtablename ='ACER_CLAIMS_MASTER_DETAIL_Hist_'+#Practice_Short_Name+''
SET #vQuery = 'select Practice_Short_Name,Service_Date_From,Carrier_Name,
Location_Description,Patient_Number,Patient_First_Name,
Patient_Last_Name,Voucher_Number,Procedure_Code,Service_Fees,
Service_Payments,Service_Adjustments,Acer_Status,Acer_Allowed_Amount
from '+#CPTtablename+'
where Uploaded_Date ='+#Uploaded_Date+' and
Practice_Short_Name ='+#Practice_Short_Name+'
order by acer_status asc, Service_Date_From desc, Patient_First_Name asc'
EXEC #vQuery
end
GO
but while running this proc it is throwing error like
"Could not find stored procedure 'select Practice_Short_Name,Service_Date_From,Carrier_Name,
Location_Description,Patient_Numb'."
can anyone explains me what i am doing wrong..

This way you call a procedure
EXEC #vQuery
but this way you run dynamic sql
EXEC (#vQuery)
so your SP should look as below
Create procedure [dbo].[GetCompleteCPTDetails]
#Practice_Short_Name varchar(50) is Null,
#Uploaded_Date varchar(30) is Null
as
begin
DECLARE #CPTtablename varchar(100)
DECLARE #vQuery NVARCHAR(100)
--Dynamically select Table name based on #practice_short_name
set #CPTtablename ='ACER_CLAIMS_MASTER_DETAIL_Hist_'+#Practice_Short_Name+''
SET #vQuery = 'select Practice_Short_Name,Service_Date_From,Carrier_Name,
Location_Description,Patient_Number,Patient_First_Name,
Patient_Last_Name,Voucher_Number,Procedure_Code,Service_Fees,
Service_Payments,Service_Adjustments,Acer_Status,Acer_Allowed_Amount
from '+#CPTtablename+'
where Uploaded_Date ='+#Uploaded_Date+' and
Practice_Short_Name ='+#Practice_Short_Name+'
order by acer_status asc, Service_Date_From desc, Patient_First_Name asc'
EXEC (#vQuery)
end
GO

The variable you use to store the query is too short to store the entire query text.
This means that the query is truncated to fix the 100 characters limit of the #vQuery variable.
Use nvarchar(max) instead of nvarchar(100).
Also, using Dynamic SQL is usually a security hazard, leaving an opening to SQL Injection attacks. You might want to re-think your design and keep all the data in the same table, instead of keeping different tables with the same structure on your datadase.

2 things need to be corrected : length of your dynamic query variable that caused you this problem. use parameterized dynamic query to prevent sql injection.
DECLARE #CPTtablename varchar(100)
DECLARE #vQuery NVARCHAR(2000) -- increased length, you can also use nvarchar(max)
--Dynamically select Table name based on #practice_short_name
SET #CPTtablename ='ACER_CLAIMS_MASTER_DETAIL_Hist_' + #Practice_Short_Name + ''
SET #vQuery = 'select Practice_Short_Name,Service_Date_From,Carrier_Name,
Location_Description,Patient_Number,Patient_First_Name,
Patient_Last_Name,Voucher_Number,Procedure_Code,Service_Fees,
Service_Payments,Service_Adjustments,Acer_Status,Acer_Allowed_Amount
from ' + #CPTtablename + '
where Uploaded_Date = #Uploaded_Date and
Practice_Short_Name = #Practice_Short_Name
order by acer_status asc, Service_Date_From desc, Patient_First_Name asc'
--dynamic query with input params
EXEC sp_executesql
#vQuery,
N'#Uploaded_Date varchar(30), #Practice_Short_Name varchar(50)',
#Uploaded_Date = #Uploaded_Date,
#Practice_Short_Name = #Practice_Short_Name

Related

Cannot get output variable from stored procedure when procedure written in dynamic sql

I am writing a procedure to produce an int output variable, but I'm not sure how to do this using dynamic sql. If I execute the below procedure I get the #AnlyNum value displayed in the results screen, but I just want #AnlyNum variable set with a value so I can use it. Thank you.
Create procedure [dbo].[sp_test] #Db varchar(50), #RwNum int, #AnlyNum int output
As
Begin
Declare #Sql nvarchar(max) =
'Select ''#AnlyNum'' = (Select AnlyId From '+#Db+'..Test order by AnlyId desc OFFSET '+convert(varchar(10),#RwNum)+' rows fetch next 1 rows only)'
End
exec(#Sql)
This removes SQL injection concerns by properly escaping the database name and also dynamically executing against that database instead of embedding the database name in the command. Also, you don't need #RwNum to be dynamic.
CREATE PROCEDURE dbo.test
#Db sysname,
#RwNum int,
#AnlyNum int output
AS
BEGIN
SET NOCOUNT ON;
DECLARE #exec nvarchar(max) = QUOTENAME(#Db) + N'.sys.sp_executesql',
#sql nvarchar(max) = N'SELECT #AnlyNum = AnlyId
From dbo.Test order by AnlyId desc
OFFSET #RwNum rows fetch next 1 rows only);';
EXEC #exec #sql, N'#AnlyNum int output, #RwNum int',
#AnlyNum output, #RwNum;
END

Store the value of a query into a SQL Server variable

The objective of the code is to run a query dynamically and return 0 if there are no rows with data present in the columns and to return 1 if there are rows with data in the columns. This is my code for the stored procedure:
ALTER proc [dbo].[usp_ColumnFieldValidator]
(
#TblName nvarchar(30),
#ColumnName nvarchar(30),
#RetVal bit output
)
as
begin
declare #CountOfRowsQuery as nvarchar(300)
set #CountOfRowsQuery = 'select count('+quotename(#ColumnName)+') from '+quotename(#TblName)+' having count(' +quotename(#ColumnName)+') = nullif(count('+quotename(#ColumnName)+'),0)'
execute sp_executesql #CountOfRowsQuery
select #RetVal = dbo.fn_ColumnValidator(#CountOfRowsQuery)
end
As you can see, a user-defined function is being called to set the value of #RetVal. This is my code for the user-defined function.
ALTER function [dbo].[fn_ColumnValidator]
(
#NullChecker as nvarchar(max)
)
returns bit
as
begin
declare #returnVar as bit
if #NullChecker is null
set #returnVar = 0
else
set #returnVar = 1
return #returnVar
end
The output of #RetVal is always 1 and I have attributed this error to #CountOfRowsQuery storing the entire string rather than the value of the query ie: #CountOfRowsQuery = null if the count of rows is zero else, #CountOfRowsQuery = the number of rows present in the column. To make things clearer I am attaching screenshots of the output when I run the program.
Output of a table that contains rows with data
Output of a table that contains no rows with no data
As you can see in list item.2, the sp returns null but the function_returned_value is being set to 1 instead of 0.
The objective of the code is to run a query dynamically and return 0 if there are no rows with data present in the columns and to return 1 if there are rows with data in the columns.
Man, if this is not an over-complication I don't know what is.
Here's a much simpler (and more efficient) query that does the work:
SELECT CAST(IIF(EXISTS(
SELECT 1
FROM TableName
WHERE ColumnName IS NOT NULL
), 1, 0) As Bit)
Now, to change that to a procedure using dynamic SQL in a way that will not expose you to SQL Injection threats you can do this:
ALTER PROCEDURE [dbo].[usp_ColumnFieldValidator]
(
#TblName sysname,
#ColumnName sysname,
#RetVal bit output
)
AS
BEGIN
IF NOT EXISTS(
SELECT 1
FROM Information_Schema.Columns
WHERE Table_Name = #TblName
AND Column_Name = #ColumnName
)
RETURN;
DECLARE #Sql nvarchar(1000) =
N'SELECT #RetVal = CAST(IIF(EXISTS(
SELECT 1
FROM '+ QUOTENAME(#TblName) + N'
WHERE '+ QUOTENAME(#ColumnName) + N' IS NOT NULL
), 1, 0) As Bit)'
EXEC sp_executesql #Sql, N'#RetVal bit output', #RetVal OUTPUT;
END
Key notes:
I've changed the #TblName and #ColumnName variables to data type sysname instead of your original nvarchar(30) - since that is the data type SQL Server use internally to store identifiers.
Since identifiers can't be parameterized, I've white-listed them.
I'm using sp_executeSql to get back the value of the dynamic query directly into my output parameter.
For more tips and tricks on dynamic SQL, you can read my blog post entitled The do’s and don’ts of dynamic SQL for SQL Server

How to use table as variable in stored procedure

There is this query that I keep using over and over:
SELECT column_name, count(column_name) FROM table_name GROUP by column_name ORDER BY COUNT(column_name) DESC
I use this to check which different values there are in a column and how often they occur.
Because I use this query so often and it's repeating the same 4 times: column_name, I was like: why not make a stored procedure:
CREATE PROCEDURE countcv #table_name VARCHAR(50),#column_name VARCHAR(50)
AS
BEGIN
SELECT #column_name,COUNT(#column_name) FROM #table_name GROUP BY #column_name ORDER BY COUNT(#column_name)
END
Here is where I get stuck, I can not manage to get a variable tablename:
Must declare the table variable "#table_name"
I believe that #Julien Vavasseur and #Dark Knight has already addressed to your question.
However, I would like to add here that, Sql Server 2008 introduced Table-Valued Parameter by using which we can pass table type variable to the stored procedures. e.g.
Assuming you have a table by the name tblTest with the below columns
ID INT,
Name VARCHAR(50)
Step 1: Declare a new table User Defined Type
CREATE TYPE tblTestType AS TABLE
(
ID INT,
Name VARCHAR(50)
)
Step 2: Create a STORED PROCEDURE that has tblTestType as parameter
CREATE PROCEDURE countcv
(
#tblName tblTestType readonly
)
AS
INSERT INTO tblTest (ID, Name)
SELECT ID, Name
FROM
#tblName;
Then you can use DataTable (if you are using C#) and pass this data table as a parameter to the Stored Procedure.(you can find an example in the link I provided).
There is no way to do it directly. You need to use dynamicSQL approach. Assuming you pass correct table and column names. Below one should work.
CREATE PROCEDURE countcv #table_name VARCHAR(50),#column_name VARCHAR(50)
AS
BEGIN
declare #SQL nvarchar(max)
set #SQL = 'SELECT '+#column_name+',COUNT('+#column_name+')
FROM '+#table_name+'
GROUP BY '+#column_name+'
ORDER BY COUNT('+#column_name+')'
EXEC sp_executesql #SQL
END
If you want to do something like this, you must use dynamic SQL:
CREATE PROCEDURE countcv #table_name sysname, #column_name sysname
AS
BEGIN
Declare #sql nvarchar(max)
Set #sql = 'SELECT ' + QUOTENAME(#column_name)+', COUNT(' + QUOTENAME(#column_name)+')
FROM ' + QUOTENAME(#table_name)+'
GROUP BY ' + QUOTENAME(#column_name)+' ORDER BY COUNT(' + QUOTENAME(#column_name)+')'
EXEC sp_executesql #sql
END
Use sysname for data type for column and table names (buitin datatype for object names, alias to nvarchar(128))
Use QUOTENAME to add delimeter to column and table names

How can I spot in what database is a stored procedure with name 'myStoredProcedure'?

There are bunch of databases to the SQL server I am connected.
How should I query the sysobjects in order to spot in what database a stored procedure with name 'myStoredProcedure' is located ?
The query should return the database name.
Thanks
I know you are not asking for this, but I'd really download RedGate's Sql Search add-in for SSMS and use that. It allows you to find any object (proc, table, view, column, etc) on any database easily.
And it's free!
I'd give this a try:
CREATE TABLE ##DatabaseList
(
DatabaseName varchar(50)
)
EXECUTE SP_MSForEachDB 'USE [?]; INSERT INTO ##DatabaseList SELECT DB_NAME() FROM [sys].[objects] WHERE name = "MyStoredProcedure" AND type_desc = "SQL_STORED_PROCEDURE"'
SELECT * FROM ##DatabaseList
DROP TABLE ##DatabaseList
That's using the undocumented/ unsupported system stored procedure SP_MSForEachDb and writing any hits to a global temp table, then outputting the contents to the Results window before dropping the table. If you just need to know which database (or databases - there may of course be more than one) has an appropriately named SP, this should do it. If you want to use the output elsewhere as a parameter, it may take a little more work.
By the way, I'm only learning this stuff myself over the last few months so if anyone can critique the above and suggest a better way to go at it I'm happy to receive feedback. Equally, I can answer any further questions posted here to the best of my ability.
Cheers
So out of curiosity I decided to try write this myself, especially since ADG mentioned his solution was using an unsupported, undocumented procedure. This could also be expanded to take a 2nd parameter so where it checks the type = P (stored Proc) you could probably change it to look for other things like views / tables etc.
My solution is a bit long but here goes:
CREATE PROCEDURE spFindProceduresInDatabases
(
#ProcedureName NVARCHAR(99)
)
AS
BEGIN
-- Get all the database names and put them into a table
DECLARE #Db TABLE (DatabaseName Varchar(99))
INSERT INTO #Db SELECT name FROM Sys.databases
-- Declare a table to hold our results
DECLARE #results TABLE (DatabaseName VARCHAR(99))
-- Make a Loop
-- Declare a variable to be incremented
DECLARE #count INT
SET #count = 0
-- Declare the end condition
DECLARE #endCount INT
SELECT #endCount = COUNT(*) FROM #Db
-- Loop through the databases
WHILE (#count < #endCount )
BEGIN
-- Get the database we are going to look into
DECLARE #dbWeAreChecking VARCHAR(99)
SELECT TOP 1 #dbWeAreChecking = DatabaseName FROM #Db
DELETE FROM #Db WHERE DatabaseName = #dbWeAreChecking
-- Create and execute our query
DECLARE #Query NVARCHAR(3000)
SET #Query = N'SELECT #outParam = COUNT(*) FROM '+#dbWeAreChecking+'.sys.sysobjects WHERE type = ''P'' and name = #ProcedureName'
Declare #outParam INT
print (#Query)
DECLARE #ParmDefinition NVARCHAR(500)
DECLARE #IntVariable INT
SET #ParmDefinition = N'#ProcedureName VARCHAR(99),#outParam INT OUTPUT'
SET #IntVariable = 35
EXECUTE sp_executesql
#Query ,
#ParmDefinition,
#ProcedureName,
#outParam = #outParam OUTPUT
-- If we have a result insert it into the results table
If (#outParam > 0)
BEGIN
INSERT INTO #results(DatabaseName) VALUES(#dbWeAreChecking)
END
-- Increment the counter
SET #count = (#count + 1)
END
-- SELECT ALL OF THE THINGS!!!
SELECT * FROM #results
END

Is there a way to select a database from a variable?

Is there a way to select a database from a variable?
Declare #bob as varchar(50);
Set #bob = 'SweetDB';
GO
USE #bob
Unfortunately, no.
Unless you can execute the rest of your batch as dynamic SQL.
Using execute to dynamically execute SQL will change the context for the scope of the execute statement, but will not leave a lasting effect on the scope you execute the execute statement from.
In other words, this:
DECLARE #db VARCHAR(100)
SET #db = 'SweetDB'
EXECUTE('use ' + #db)
Will not set the current database permanently, but if you altered the above code like this:
DECLARE #db VARCHAR(100)
SET #db = 'SweetDB'
EXECUTE('use ' + #db + ';select * from sysobjects')
select * from sysobjects
Then the result of those two queries will be different (assuming you're not in SweetDB already), since the first select, executed inside execute is executing in SweetDB, but the second isn't.
declare #NewDB varchar(50)
set #NewDB = 'NewDB'
execute('use ' + #NewDB)
#TempTables will presist across GOs
you can create the table in the first batch, insert/select data as necessary in that or any following batch.
here is some sample syntax:
CREATE TABLE #YourTableName
(
col1 int not null primary key identity(1,1)
,col2 varchar(10)
)