Parse series of strings into WHERE IN of stored procedure - sql-server

I'm trying to build an Excel tool that allows users to input a series of article IDs and query our SQL server for said IDs. Problem is that all of these IDs are strings, so I have to input them as '123', '456', '789' - which is obviously not working if I were to execute the SP via
exec SP_test #articleID = '123', '456', '789'
The SP itself is pretty basic:
ALTER PROCEDURE [dbo].[SP_test]
#articleID as VARCHAR
AS
BEGIN
SET NOCOUNT ON;
SELECT
ARTNR
, ARTBEZ
FROM table
WHERE
ARTNR IN
(#articleID)
END
GO
Like I said, this isn't working because it's not parsing '123' as '123' but rather 123.
I've then found a custom function to split strings in this thread: How to pass string parameter with `IN` operator in stored procedure SQL Server 2008
If I then just execute the query itself (not as a SP), it's actually giving me the proper results:
SELECT
ARTNR
, ARTBEZ
FROM table
WHERE
ARTNR IN
(select value from fn_split('123, 456, 789', ','))
But if I then try to turn that into my SP a la
ALTER PROCEDURE [dbo].[SP_test]
#articleID as VARCHAR
AS
BEGIN
SET NOCOUNT ON;
SELECT
ARTNR
, ARTBEZ
FROM table
WHERE
ARTNR IN
(select value from fn_split(#articleID, ','))
END
GO
it seems to again be parsing the articleIDs incorrectly.
Can anyone clue me in on how to get this to work properly? I've seen the string_split function being thrown around in this context, but m server's compatibility level isn't high enough for that.

Related

SQL Server stored procedure only works with locally re-declared variables; not variables passed to the procedure

I have edited my SQL code blocks to more accurately show what is going on
Say I have a simple stored procedure:
CREATE PROCEDURE [DBO].[FOO]
(#VARIABLE VARCHAR(500))
AS
BEGIN
SELECT AVG(BAR)
FROM MYTABLE
WHERE THING = #VARIABLE AND RTRIM(LTRIM(THING)) <> ''
END
When I call this stored procedure from my classic ASP page; which in this case would be with:
Set foo = Server.CreateObject("ADODB.RecordSet")
curCmd = "Foo 'MYVARIABLE'"
foo.Open curCmd, connectionString
I get this error (on the same line as the page opens the foo object):
Arithmetic overflow error converting varchar to data type numeric.
If I call the stored procedure manually in the terminal (IDE?); then it works fine.
Also if I recreate the stored procedure as the following:
CREATE PROCEDURE [DBO].[FOO]
(#VARIABLE VARCHAR(500))
AS
BEGIN
DECLARE #VARIABLE2 VARCHAR(500) = #VARIABLE
SELECT AVG(BAR)
FROM MYTABLE
WHERE THING = #VARIABLE2 AND RTRIM(LTRIM(THING)) <> ''
END
Then the stored procedure runs fine.
I have tried dropping and recreating the stored procedure (without using the re-declaration trick), but it does not fix the issue.
*As an aside; there is validation on the data being inserted into the table to ensure that only numbers (integers) are being entered for the THING field. The THING field can also be blank; hence the where clause.
I basically have two questions:
Why does re-declaring the same variable type with the same data fix the issue?
Is there a way I can fix my problem without using this silly "re-declaration" trick?
Thanks in advance for any help with this.
I think you can get the same error if you use begin/end:
CREATE PROCEDURE [DBO].[FOO] (
#VARIABLE VARCHAR(500)
)
AS
BEGIN
DECLARE #VARIABLE2 VARCHAR(500) = #VARIABLE;
SELECT AVG(BAR) FROM MYTABLE WHERE THING = #VARIABLE2;
END;
Then, both statements will be part of the stored procedure body and you can work on fixing the data so it will work.

Stored Procedure in sql for selecting columns based on input values

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.

How can I edit a stored procedure?

I have several stored procedures in my database, structured like this:
CREATE PROCEDURE MyProcedure (.....)
AS
DECLARE #myvar NVARCHAR(100);
SET #myvar = (SELECT .... FROM my_table WHERE ....)
GO
I was asked to replace the table my_table in the FROM clause with another one in every procedure that has it.
I went through a lot of researches, but I should create a script that works by itself, and I haven't found anything suitable. For example I found the sp_helpTetx that shows the source code of a stored procedure, but is there a way to put it into a variable in order to edit it?
You can use tool like REDGATE SqlRefactor that works perfectly or you can script all the stored procedures, replace CREATE command with ALTER and then apply the other REPLACE in text you need...
I do it lot of time, you have to pay attention but it works...
Find all stored procedures with a reference to that table (you can either use the dependencies stuff built into SQL Server or run a query looking for that table name see Search text in stored procedure in SQL Server)
Script them out with an "ALTER" instead of "CREATE" Press CTRL-H (find and replace)
Execute the script.
Here is an article outlining how to handle this using a cursor, and the sp_HelpText as mentioned above (including set as also mentioned).
http://www.ideosity.com/ourblog/post/ideosphere-blog/2013/06/14/how-to-find-and-replace-text-in-all-stored-procedures
-- set "Result to Text" mode by pressing Ctrl+T
SET NOCOUNT ON
DECLARE #sqlToRun VARCHAR(1000), #searchFor VARCHAR(100), #replaceWith VARCHAR(100)
-- text to search for
SET #searchFor = '[MY-SERVER]'
-- text to replace with
SET #replaceWith = '[MY-SERVER2]'
-- this will hold stored procedures text
DECLARE #temp TABLE (spText VARCHAR(MAX))
DECLARE curHelp CURSOR FAST_FORWARD
FOR
-- get text of all stored procedures that contain search string
-- I am using custom escape character here since i need to espape [ and ] in search string
SELECT DISTINCT 'sp_helptext '''+OBJECT_SCHEMA_NAME(id)+'.'+OBJECT_NAME(id)+''' '
FROM syscomments WHERE TEXT LIKE '%' + REPLACE(REPLACE(#searchFor,']','\]'),'[','\[') + '%' ESCAPE '\'
ORDER BY 'sp_helptext '''+OBJECT_SCHEMA_NAME(id)+'.'+OBJECT_NAME(id)+''' '
OPEN curHelp
FETCH next FROM curHelp INTO #sqlToRun
WHILE ##FETCH_STATUS = 0
BEGIN
--insert stored procedure text into a temporary table
INSERT INTO #temp
EXEC (#sqlToRun)
-- add GO after each stored procedure
INSERT INTO #temp
VALUES ('GO')
FETCH next FROM curHelp INTO #sqlToRun
END
CLOSE curHelp
DEALLOCATE curHelp
-- find and replace search string in stored procedures
-- also replace CREATE PROCEDURE with ALTER PROCEDURE
UPDATE #temp
SET spText = REPLACE(REPLACE(spText,'CREATE PROCEDURE', 'ALTER PROCEDURE'),#searchFor,#replaceWith)
SELECT spText FROM #temp
-- now copy and paste result into new window
-- then make sure everything looks good and run
GO
If sp_HelpText returns a table, why not you use a cursor to loop over the results and join the resulting strings together? It's nasty, but would do the trick.

Dynamic parameters for sql query with "IN" operator

I have an ASP.NET website (c#) and in the code-behind I want to use the IN operator in SQL http://www.w3schools.com/sql/sql_in.asp to get data from my database.
The syntax is:
SELECT column_name(s)
FROM table_name
WHERE column_name IN (value1,value2,...)
I use a stored procedure to get data, inside this procedure I run, in the end, the select.
My question is how to build dynamically the (value1, value2, ...) part of the query? How do I send as SqlParameter(s) the values that appear in the IN operator?
The stored procedure is defined like this:
CREATE PROCEDURE [dbo].[GetAllUsers]
#ORGANIZATION_ID int = null
AS
BEGIN
SET NOCOUNT ON;
begin
SELECT *
from USERS
where ORGANIZATION_ID = #ORGANIZATION_ID
end
END
I'd like to replace the WHERE clause with something like:
WHERE ORGANIZATION in (xxxxxxxxxxxxx)
How to do this?
Thank you.
You can use dynamic sql:
CREATE PROCEDURE [dbo].[GetAllUsers]
#ORGANIZATION_ID varchar(max) = null
AS
BEGIN
declare #sql varchar(max)
#sql = 'SELECT *
from USERS
where ORGANIZATION_ID in ('+#ORGANIZATION_ID+')'
SET NOCOUNT ON;
begin
exec(#sql)
end
END
You have to change type of #ORGANIZATION_ID to varchar and run the procedure with
one id:
exec '1'
or list of ids:
exec '1,2,3,4'

SQL Server - Stored Procedure Question

For the application I work on... we're creating a custom logging system. The user can view logs and apply "Tags" to them (Just like how you can apply tags to questions here!)
In this example, I'm trying to get a list of all the Logs given a "Tag." I realize I can accomplish this by using joins... but this is also an exercise for me to learn Stored Procedures a little better :)
I have a stored procedure that looks something like this to select a log by the PK
ALTER PROCEDURE [dbo].[getLogByLogId]
-- Add the parameters for the stored procedure here
#ID int
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
SELECT TOP 1
LOG_ID,
a.A,
a.B,
a.C
FROM dbo.LOG a
WHERE a.LOG_ID = #ID
Now I would like to call this Stored Procedure from another... something like this
ALTER PROCEDURE [dbo].[getLogsByTagName]
-- Add the parameters for the stored procedure here
#TAG nvarchar(50)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
SELECT TOP 1000
LOG_ID --somehow store this and execute the dbo.getLogByLogId procedure here
FROM dbo.LOG_TAG a
WHERE a.TAG = #TAG
Thanks
If you have complex logic in your logbyid SP which you are trying to avoid reproducing in multiple places in your system (choice of columns, derived columns, etc), I would recommend turning that into an inline table-valued function instead (potentially without taking the ID parameter, in which case, you can actually use an ordinary view).
Then you can either join to that ITVF/view in your other stored proc (or also make another udf) which does the search or use the OUTER APPLY functionality (not as efficient).
Inline table-valued functions are basically parameterized views and can be optimized fairly easily by the optimizer.
If you want to call another sproc from within a sproc just use:
CREATE PROCEDURE myTestProc
AS
BEGIN
--Do some work in this procedure
SELECT blah FROM foo
--now call another sproc
EXEC nameOfSecondSproc
END
The only way you can achive what you are attempting is by using a CURSOR.
If this is for your learning only, then by all means, give this a go, but I would not recomend this for production.
It would go something like this
DECLARE #Table TABLE(
ID INT
)
INSERT INTO #Table SELECT 1
INSERT INTO #Table SELECT 2
INSERT INTO #Table SELECT 3
INSERT INTO #Table SELECT 4
INSERT INTO #Table SELECT 5
INSERT INTO #Table SELECT 6
DECLARE Cur CURSOR FOR
SELECT ID
FROM #Table
OPEN Cur
DECLARE #ID INT
FETCH NEXT FROM Cur INTO #ID
WHILE ##FETCH_STATUS = 0
BEGIN
PRINT #ID
FETCH NEXT FROM Cur INTO #ID
END
CLOSE Cur
DEALLOCATE Cur
By using the #ID retrieved in the WHILE loop, you can then execute the sp you wish and insert the values into a table variable.
INSERT INTO #Table EXEC sp_MySP #ID
You can call a stored procedure from another using the following syntax:
ALTER PROCEDURE [dbo].[getLogsByTagName]
-- Add the parameters for the stored procedure here
#TAG nvarchar(50)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
SELECT TOP 1000
LOG_ID --somehow store this and execute the dbo.getLogByLogId procedure here
FROM dbo.LOG_TAG a
WHERE a.TAG = #TAG
-- Execute dbo.getLogByLogId stored procedure
DECLARE #logId INTEGER
SET #logId = <some value>
EXEC dbo.getLogByLogId #logId
END
However, the difficult part of your question is that your dbo.getLogByLogId procedure can only accept a single LogID parameter and therefore will only be able to return a single Log record. You need to return information for all Logs where the LogId has a corresponding record in the Tags table.
The correct way to do this would be to JOIN the Log and Tag tables together, like so:
SELECT *
FROM dbo.LOG_TAG a
INNER JOIN dbo.LOG b ON a.LOG_ID = b.LOG_ID
WHERE a.TAG = #TAG
If you are concerned about returning the same logId multiple times, you can use the DISTINCT keyword in the SELECT statement to filter out the duplicated logIds.
You may also be able to rewrite your dbo.getLogByLogId procedure as a user-defined function (UDF). UDFs can accept a table as a parameter and return a table result.
An introduction to user-defined functions can be found in this article.

Resources