Stored Procedure - Table Name as variable - sql

I am tasked with writing a stored procedure that will validate the input data against a few tables before inserting into a main table named CHANGES.
Here is what I am dealing with table wise:
There is a lookup table. This table basically gives the user the rules of data validation before it can be inserted into the MAIN table. The lookup table looks like this:
ID TABLECODE COLUMNAME ACCEPTEDDATATYPE
1 luDEPT DEPTCODE INT
2 luEMP GENDERCODE INT
3 luDEPT BLDGcode INT
So if a user is inserting an ID of 1, we know they are trying to make a correction to the DeptCode column and they must meet the requirements that only an Integer will be accepted before inserting into the CHANGES table (this is the main table that will hold the new values).
CHANGES table - Data is inserted into this table with the new value per column. Data will only be inserted into this table if it passes validation against the lookup table and explained in part 3.
Structure of CHANGES table
ID pkid NEWVALUE
1 67 01
1 84 09
2 56 03
This is the part I would like some help/input with to even see if it's doable. The column from the LOOKUP table name TABLECODE is the name of an actual table that exists in the database with codes and description for each column. So for example, all the DEPTCODE codes will be found in a lookup table named: luDEPT
Here is how the luDEPT that looks like this:
CODE DEPARTMENTNAME
01 BIOLOGY
02 CHEMISTRY
03 INFORMATION TECHNOLOGY
So another validation step I have to take is, make sure that the NEW VALUE being inserted into CHANGES table is a valid code found in the lookup table related to the COLUMNNAME.
This is what I have so far, which works
CREATE PROCEDURE [dbo].[NewValueData]
(
#ID int,
#pkid VARCHAR(40),
#value VARCHAR(50)
)
AS
Begin
declare #result bit = 0;
declare #result1 bit = 0;
declare #result2 bit = 0;
declare #result3 bit = 0;
declare #result4 bit = 0;
DECLARE #tablename varchar(50);
DECLARE #columndatatype varchar(30);
set #columndatatype=(select accepteddatatype from lookup where ID=#ID)
**set #tablename=(select TABLE_NAME from INFORMATION_SCHEMA.TABLES A, lookup b
where a.TABLE_NAME= b.lutablecode
and TABLE_SCHEMA = 'Test' and ID=#ID)**
--CHECK IF ID, pkid and VALUE are PROVIDED
if (#pkid IS NULL OR #pkid = '') or (#ID IS NULL OR #ID = '') or (#value IS NULL OR #value =
'')
begin
set #result = 1
PRINT 'PKID,ID or Value is missing'
end
--CHECK IF ID EXISTS IN LOOKUP TABLE
if #ID not in (select ID from lookup
where #ID=ID)
begin
set #result1=1
PRINT 'ID is not in lookup table'
end
--IF datatype is an integer, only accept a numeric value
if #columndatatype = 'INT'
begin
set #result3 = IIF(ISNUMERIC(#value)=1,0,1)
PRINT 'column type is INT '
end
**--ATTEMPT of trying to use #tablename
--CHECK IF VALUE IS AN ACCEPTED VALUE IN THE LOOKUP TABLE FOR THAT COLUMN
if #value not in (select code from #tablename where #value=code)
begin
set #result4=1
PRINT 'Not a valid code')
end**
if (#result = 0 and #result1 = 0 and #result2 = 0 and #result3 = 0 and #result4 = 0)
begin
BEGIN TRANSACTION;
begin try
INSERT INTO [CHANGES] (ID, pkid,newvalue) VALUES (#ID, #pkid, #value)
PRINT 'New Record Inserted'
COMMIT TRANSACTION
end TRY
begin catch
ROLLBACK TRANSACTION
PRINT 'id is not acceptable'
END
end
GO
The text in bold is my attempt at trying to derive the tablename dynamically but it doesn't work. Does anyone have suggestion on how to go about this issue? Any help will be welcomed.

Try something like:
DECLARE #tablename sysname = 'luDEPT'
DECLARE #columnname sysname = 'DEPTCODE'
DECLARE #value INT = 123
DECLARE #valid BIT
DECLARE #sql NVARCHAR(MAX) = '
SET #Valid = CASE
WHEN EXISTS(SELECT * FROM ' + QUOTENAME(#tablename) + ' WHERE ' + QUOTENAME(#columnname) + ' = #Value)
THEN 1
ELSE 0
END'
EXECUTE sp_executesql
#sql,
N'#value INT, #valid BIT OUTPUT',
#value = #value, #valid = #valid OUTPUT
SELECT Valid = #Valid
The data type could potentially also be parameterized if types other than INT are needed.

Related

Stored procedure table-valued variable without aliases in query string must declare scalar variable

I will pass a table-valued input parameter into a stored procedure, and also a variable that contains query string, so I made my sproc like this.
CREATE PROCEDURE [dbo].[SP_SelectData_View]
(
#Sort VARCHAR(MAX),
#CONDITION VARCHAR(MAX) = ''
#Values dbo.FlowStatus READONLY
)
AS
BEGIN
DECLARE #STRQUERY NVARCHAR(MAX)
IF #CONDITION IS NOT NULL AND #CONDITION != ''
BEGIN
SET #CONDITION = 'WHERE ' + #CONDITION
END
ELSE
BEGIN
SET #CONDITION = ''
END
IF #Sort IS NULL OR #Sort = ''
BEGIN
SET #Sort = 'Id Desc'
END
BEGIN
SET #STRQUERY = 'SELECT A.*
FROM ' + #Values + ' as FlowStatus'
JOIN Tbl_A as A
ON A.status = FlowStatus.StatusNowId AND B.flow = FlowStatus.FlowNowId
' + #CONDITION + '
Order By ' + #Sort
EXEC(#STRQUERY)
END
END
But in the code above, I got an error
must declare scalar variable #Values
I've searched for it and I think it is because the aliases is not detected because it's inside a string. But if I didn't put it in a string query, the #condition and #sort variable will be error. Is there a solution where I can do both calling the table-valued variable and query string variable together?
There are several things wrong with the approach you currently have, as I and others have commented, Brent Ozar has a good reference on dynamic SQL https://www.brentozar.com/sql/dynamic/
I would say don't pass in some SQL, construct it in the stored proc; passing in parameters such as name which is used in the where, hence I have put a full working example. This also shows how to pass the user defined table type into the stored proc and then also pass it into the dynamic SQL.
I hope this is a good enough example of the techniques, I had a bit of time so thought I would try and help as much as possible :)
/*
--------------------------------------------
Create a test table to run the stored proc against
*/
IF (NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'dbo' AND TABLE_NAME = 'MyTestTable'))
BEGIN
PRINT 'Creating table MyTestTable'
CREATE TABLE [dbo].[MyTestTable](
Id BIGINT NOT NULL IDENTITY(1,1) PRIMARY KEY,
[Name] NVARCHAR(50) NOT NULL
)
INSERT INTO dbo.MyTestTable ([Name])
VALUES ('Andrew'),
('Bob'),
('john')
-- SELECT * FROM MyTestTable
END
GO
/*
--------------------------------------------
Create the table type that we pass into the store proc
*/
IF NOT EXISTS (SELECT * FROM sys.types WHERE is_table_type = 1 AND name = 'FlowStatus')
BEGIN
PRINT 'Creating type [dbo].[FlowStatus]'
CREATE TYPE [dbo].FlowStatus AS TABLE (
MyId BIGINT PRIMARY KEY,
SomeText NVARCHAR(200)
)
END
GO
/*
--------------------------------------------
Create the stored proc with the User Defined table type
*/
CREATE OR ALTER PROCEDURE [dbo].[MyStoredProc]
(
#SortBy VARCHAR(50),
#SearchName VARCHAR(50),
#Values dbo.FlowStatus READONLY
)
AS
BEGIN
-- As your SQL gets more complex it is an idea to create seperate parts of the SQL
DECLARE #SqlToExecute NVARCHAR(MAX)
-- The primary data you want to get
SET #SqlToExecute = N'
SELECT T.Id, T.[Name], V.SomeText
FROM MyTestTable AS T
LEFT JOIN #Values AS V ON V.MyId = T.Id
WHERE 1 = 1' -- We do this so that we can have many AND statements which could be expanded upon
IF #SearchName IS NOT NULL
BEGIN
SET #SqlToExecute = #SqlToExecute + N'
AND T.[Name] LIKE ''%' + #SearchName + ''''
END
IF #SortBy IS NOT NULL
BEGIN
SET #SqlToExecute = #SqlToExecute + N'
ORDER BY ' +
CASE WHEN #SortBy LIKE 'Name%' THEN N'T.[Name]'
ELSE N'T.[Id]'
END
END
-- Print out the script that will be run, useful for debugging you code
PRINT #SqlToExecute
EXEC sp_executesql #SqlToExecute,
N'#Values dbo.FlowStatus READONLY', #Values
END
GO
/*
--------------------------------------------
Now lets test it
-- Test Andrew
*/
DECLARE #flowStatusType AS dbo.FlowStatus
INSERT INTO #flowStatusType(MyId, SomeText)
VALUES(1, 'Test1'),
(2, 'Test2')
EXEC [dbo].[MyStoredProc] #SearchName = 'Andrew', #SortBy = 'Name', #Values = #flowStatusType
GO
-- Test Bob
DECLARE #flowStatusType AS dbo.FlowStatus
INSERT INTO #flowStatusType(MyId, SomeText)
VALUES(1, 'Test1'),
(2, 'Test2')
EXEC [dbo].[MyStoredProc] #SearchName = 'Bob', #SortBy = 'Name', #Values = #flowStatusType
GO
Its also worth noting that if you can just join on the #Values without needing dynamic SQL then that is sure to be less work.

Procedure to rename a list of procedure, only renames the last procedure in the list

I created a procedure where I could pass a list of procedure names separated by a comma, and then I would like to rename all the procedures from the list by adding a custom suffix in the end.
But I am facing an issue where it would only rename the last item from the list and ignore all the rest. I don't know why it would not rename each one from table variable list since I am calling sp_rename on each item in the list.
Also, I was able to print each loop with raiseerror()
Here's the complete procedure that I written.
CREATE PROCEDURE [dbo].[Util_ProcRename]
#ProcNameListWithComma VARCHAR(MAX)
AS
BEGIN
DECLARE #ID INT
SET #ID = 1;
DECLARE #oldname varchar(200)
DECLARE #newname varchar(200);
DECLARE #NOTUSED VARCHAR(20)
SET #NOTUSED = '_NOTUSED_'+REPLACE(CONVERT(varchar(20), GETDATE(), 101),'/','')
DECLARE #ProList AS TABLE (
ID INT IDENTITY,
ProcName VARCHAR(200),
NewProcName VARCHAR(200)
);
--Get proc names from a list into a table with ID to use in While loop
INSERT INTO #ProList (ProcName)
SELECT P AS ProcName
FROM dbo.SplitText(#ProcNameListWithComma,',')
WHILE (#id <= (SELECT MAX(ID) FROM #ProList))
BEGIN
SET #oldname = (SELECT ProcName FROM #ProList WHERE ID = #ID)
SET #newname = #oldname + #NOTUSED
EXEC p_rename #oldname, #newname
SET #ID = #ID + 1
END
UPDATE p
SET NewProcName = p.ProcName+#NOTUSED
FROM #ProList p
WHERE ID = ID
--TO see the list that got renamed
SELECT
a.Name AS [NewName],
a.type
FROM
dbo.sysobjects a
WHERE
name IN (SELECT NewProcName FROM #ProList)
END
GO
Despite the indentation, you are doing the update after the loop. So, it is only called once.
Move the END to after the UPDATE.

SQL Server stored procedure: verify CRUD operation success/failure using output variable

I am trying to create a SQL Server stored procedure to handle updates to a table using some dynamic SQL. The table name required for the update is stored in a table that correlates a table id to a category id. Once the table name is retrieved and the table id is not null, I update the table using a dynamic SQL query as shown below:
CREATE PROCEDURE [dbo].[SP_EBS_CustomForms_SetCategoryData]
(#flag int output,
#cat_id int,
#sort int,
#value varchar(50),
#active int,
#enum int)
AS
BEGIN
DECLARE #tbl as varchar(50)
DECLARE #tbl_id as int
DECLARE #sql nvarchar(max)
BEGIN TRY
SET #tbl_id = (SELECT [tbl_id]
FROM [demodata].[dbo].[ebscustomforms_cattable]
WHERE cat_id = #cat_id)
IF #tbl_id IS NOT NULL
BEGIN
SET #tbl = (SELECT table_name
FROM ebscustomforms_enumtable
WHERE tbl_id = #tbl_id)
SET #sql = 'UPDATE ' + #tbl + ' SET [sort_order] = #sort, [value] = #value, [active] = #active WHERE [enum_id] = #enum'
EXECUTE sp_executesql #sql, N'#sort int, #value varchar(50), #active int, #enum int', #sort, #value, #active, #enum
SET #flag = 0
RETURN #flag
END
END TRY
BEGIN CATCH
IF ##ERROR <> 0
BEGIN
SET #flag = 1;
RETURN #flag
END
END CATCH
END
I want this stored procedure to return an int value indicating whether the stored procedure was successful (0) or failed (1) updating the table.
Points of error are as follows:
#tbl_id variable is null
#tbl is either null or an empty varchar
The table to be updated does not have a record where [enum_id] = #enum
I have noticed that when I try to update a record that does not exist, the procedure seems to return as successful i.e. #flag = 0. However, I would imagine that an error should be thrown because the record does not exist.

How to use a variable in an Sybase update statement

This query is to prove a concept that I will eventually use to locate all columns with a specific value and then create a name/value pair for export to JSON. But I'm stuck.
I query the list of all columns from the sql table. I would then like to go through the columns in Table1 row by row and update the values using the variable to construct the query. For example as it reads through the list if Col4 = "Old text" then I would like to set the value of Col 4 = "New Text"
DECLARE #c varCHAR(100)
DECLARE ReadData CURSOR
FOR SELECT cname FROM sys.syscolumns WHERE creator = 'dbserver' AND tname = 'Table1'
DECLARE #RowCount INT
SET #RowCount = (SELECT COUNT(cname) FROM sys.syscolumns WHERE creator = 'dbserver' AND tname = 'Table1')
OPEN ReadData
DECLARE #I INT // iterator
SET #I = 1 // initialize
WHILE (#I <= #RowCount)
BEGIN
FETCH NEXT ReadData INTO #c
INSERT INTO serverdb.Table2 (cname)VALUES(#c)// this works inserting all 100 columns in the cname column of Table 2
UPDATE serverdb.Table1 SET #c = 'New text' WHERE #c = 'Old text'// this fails with a syntax error. #c is not being interpreted for the query. Note: If I hard code the #c var (for testing)to a known column name, the query works as well
SET #I = #I + 1
END;
Why won't the update statement recognize the variable? What am I missing?
When you use varibale as mentioned below it is considered as a character string.
UPDATE serverdb.Table1 SET #c = 'New text' WHERE #c = 'Old text'
You need to create a dynamic query. use the execute method to execute your dynamic query
declare #sql varchar(999)
SELECT #sql = 'UPDATE serverdb.Table1 SET '+ #c + '= ''New text'' WHERE '+ #c+ ' = ''Old text'' '
execute(#sql)
Hope this helps

Using variable name to run query on multiple tables

what I am trying to do is run a query multiple times over multiple tables, so what I have here is a table of the table names that cycles through setting #tablename to the name of the table on each iteration that I want to run the query on.
As you can see below #tablename is the name of the table I want to run the queries on but how do i run these queries using #tablename as the table name?
CREATE TABLE [BusinessListings].[dbo].[temptablenames]
(id int,
name nvarchar(50),
)
INSERT INTO [BusinessListings].[dbo].[temptablenames] (id, name)
VALUES
(1,'MongoOrganisationsACT1'),
(2,'MongoOrganisationsNSW1'),
(3,'MongoOrganisationsNT1'),
(4,'MongoOrganisationsQLD1'),
(5,'MongoOrganisationsSA1'),
(6,'MongoOrganisationsTAS1'),
(7,'MongoOrganisationsVIC1'),
(8,'MongoOrganisationsWA1');
DECLARE #tablename sysname,
#id int
SET #id = 1
WHILE (#id < 9)
BEGIN
select #tablename = name from temptablenames where id = #id
select #tablename
select _key_out, sum(quality_score) as sumscore, count(*) as reccount, (sum(quality_score) / count(*)) as ave
into tempga0
from #tablename
group by _key_out
select _key_out, count(*) as reccount
into tempga3
from #tablename
where dedupe_result is null
group by _key_out
having count(*)>1
select a._key_out, max(quality_score) as maxdedupetotalscore
into tempga4
from
#tablename a
join
tempga3 b
on a._key_out = B._key_out
--where isdeleted is null
group by a._key_out
--- keep records
update #tablename
set dedupe_result = 'Keep'
from
#tablename a
join
tempga4 b
on a._key_out = B._key_out
where a.quality_score = b.maxdedupetotalscore
--and isdeleted is null
and dedupe_result is null
SET #id = #id + 1
END
GO
DROP TABLE [BusinessListings].[dbo].[temptablenames]
note: this is only part of the queries that I want run, I just want to figure out how to subsitute the variable in the query as the table name. Also I know this isnt good form but there is a reason I need to do it this way.
updated working code here:
DECLARE #tablename nvarchar(30),
#id int,
#SQLStr nvarchar(1000)
SET #id = 1
WHILE (#id < 9)
BEGIN
select #tablename = name from temptablenames where id = #id
IF OBJECT_ID('tempga0') IS NOT NULL
DROP TABLE tempga0
set #SQLStr = 'select _key_out, sum(quality_score) as sumscore, count(*) as reccount, (sum(quality_score) / count(*)) as ave
into tempga0
from ' + #tablename + ' group by _key_out'
exec(#SQLStr)
SET #id = #id + 1
END
GO
Use the Exec command. Write your query in a variable like and execute it
Declare #SQLStr = 'Select * into X from ' + #tablename
exec(#SQLStr)
You just have to be carefull. I see that you are using into statements. You will have to check that the table does not already exist because you will get an exception. You will need to drop the tables, or a better way would be to do this before you start your loop:
CREATE TABLE tempga0 (
_key_out int,
sumscore numeric(18,9),
reccount int,
ave numeric(18,9))
--rest of the tables to be created here...
Create all the tables, and when you start your While loop add a
WHILE (#id < 9)
BEGIN
TRUNCATE TABLE tempga0
--truncate the rest of the tables
--Do the rest of your stuff here
END
Hope it helps