I have an SQL Server stored procedure that I'm trying to make an SSRS report from. The sp has a parameter in it. The parameter works in SSRS when calling a single value. However, when calling both values, only the first one is returned.
Here's something important- The 'Active' field is a bit data type, so this is where the problem most likely lies.
ALTER PROCEDURE [dbo].[USP_RptRC]
#Active VARCHAR(1)
AS
BEGIN
SELECT [Funding]
,[4thChar]
,REPLACE([Description], CHAR(13) + CHAR(10), '') [Description]
,[Comments]
,CAST(Active AS VARCHAR(1)) Active
,[IsDeleted]
,[LastModifiedBy]
,[LastModifiedDate]
FROM [RC]
WHERE Active IN (#Active)
END
First your parameter is only a length of 1. Second, since you are using 'Where In' you will need to do a little extra work. I had the same issue in the past and this article helped me get it working.
https://munishbansal.wordpress.com/2008/12/29/passing-multi-value-parameter-in-stored-procedure-ssrs-report/
Since #Active is a VARCHAR(1), which could be a CHAR or INT if you are always using 1 or 0, you can only pass in one of the values. If you want the ability to return where Active is 1 or 0, then do this:
#Active VARCHAR(1) = NULL
AS
BEGIN
SELECT [Funding]
,[4thChar]
,REPLACE([Description], CHAR(13) + CHAR(10), '') [Description]
,[Comments]
,CAST(Active AS VARCHAR(1)) Active
,[IsDeleted]
,[LastModifiedBy]
,[LastModifiedDate]
FROM [RC]
WHERE #Active IS NULL or Active = #Active
Then in your SSRS report, just set the default value of your parameter to NULL, and/or in the Parameter settings, allow NULL values along with your explicit choices of 1 and 0. When NULL is selected, both 1 and 0 will be returned. Of course you can label the parameter choice in SSRS to "All" or "Inactive & Active" and pass NULL as the actual value.
T-SQL wise - '0,1' is not two values, it's one and in the context you use it it's one string value. But since your parameter length is limited (by you) to 1, the engine trims everything after the first character to convert '0,1' into '0'. If you pass '1,whatever' you'll get '1' in you stored procedure. That's way "only the first one is returned".
But since the #Active is bit you should declare it as bit. Then change your logic to include appropriate subset of records when you receive NULL for #Active, like so:
ALTER PROCEDURE [dbo].[USP_RptRC]
#Active VARCHAR(1)
AS
BEGIN
SELECT [Funding]
,[4thChar]
,REPLACE([Description], CHAR(13) + CHAR(10), '') [Description]
,[Comments]
,CAST(Active AS VARCHAR(1)) Active
,[IsDeleted]
,[LastModifiedBy]
,[LastModifiedDate]
FROM [RC]
WHERE 1 = CASE WHEN #Active IS NULL THEN 1 ELSE
CASE WHEN Active = #Active THEN 1 ELSE -1
END
Related
So I've been working on a number of stored procedures for an SSRS report I'm building and have an odd error and need a pair of fresh eyes to see what I could be missing.
My procedure is pretty simple - SELECT various columns from some JOINed tables, INSERT them into a #temp table and SELECT all of the contents of the table to display as detail rows in my report.
My complete procedure is shown here:
USE [DB]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[rpt_select_ACHW_Ob]
#PR_ID INT
AS BEGIN
SET NOCOUNT ON;
CREATE TABLE #temp
(BuildingNum INT,
MTHD VARCHAR(50),
ObDescript NVARCHAR(50),
SizeRemarks VARCHAR(50),
ObLength NUMERIC,
ObWidth NUMERIC,
ObArea NUMERIC,
Stories NUMERIC,
Grade VARCHAR(50),
YearBuilt SMALLINT,
Condition VARCHAR(50),
Phys NUMERIC,
FC VARCHAR(50),
PercentDone NUMERIC,
TaxValue NUMERIC)
DECLARE #RowCounter INT, #h INT
SET #RowCounter = 11
INSERT INTO #temp(BuildingNum, MTHD, ObDescript, SizeRemarks, ObLength, ObWidth, ObArea, Stories, Grade, YearBuilt, Condition, Phys, FC, PercentDone, TaxValue)
SELECT ob.Ob_LineNumber AS BuildingNum,
CASE WHEN ob.SP IS NOT NULL THEN 'S' ELSE 'P' END AS MTHD,
som.Id AS ObDescript,
CASE WHEN ob.SF IS NULL THEN ob.CN ELSE CAST((ob.SF + '/' + ob.CN) AS VARCHAR) END AS SizeRemarks,
CASE WHEN ob.ob_Length IS NULL THEN 0 ELSE ob.Ob_Length END AS ObLength,
CASE WHEN ob.ob_Width IS NULL THEN 0 ELSE ob.Ob_Width END AS ObWidth,
CASE WHEN ob.ob_Length IS NULL WHEN ob.ob_Width IS NULL THEN 0 THEN 0 ELSE (ob.Ob_Length * ob.Ob_Width) END AS ObArea,
ob.Ob_NStories AS OBStories,
sovg.Grade AS ObGrade,
ob.Ob_YearBuilt As ObYearBuilt,
ob.Ob_ConditionCode AS ObConditionCode,
ob.DR AS phys,
ob.FC AS FC,
ob.Ob_PercentComplete AS ObPercentComplete,
ob.Ob_ValueTax AS TaxValue
FROM t_Ob ob WITH (NOLOCK)
LEFT JOIN t_ObToPR otpr WITH (NOLOCK) ON ob.Ob_ID=otpr.Ob_ID
LEFT JOIN t_PR pr WITH (NOLOCK) ON otpr.PR_ID=pr.PR_ID
LEFT JOIN t_S_Grade sovg WITH (NOLOCK) ON ob.S_Grade_ID=sovg.S_Grade_ID
LEFT JOIN t_SObD sod WITH (NOLOCK) ON ob.SObD_ID=sod.SObD_ID
LEFT JOIN t_SObM som WITH (NOLOCK) ON sod.SObM_ID=som.SObM_ID
WHERE pr.PR_Id = #PR_ID
SET #h = (SELECT COUNT(*) FROM #temp)
WHILE #h < #RowCounter OR #h % #RowCounter > 0
BEGIN
INSERT INTO #temp (BuildingNum) VALUES (NULL)
SET #h = #h + 1
END
SELECT * FROM #temp
ORDER BY CASE WHEN BuildingNum IS NULL THEN 1 ELSE 0 END, BuildingNum
END
As I said, I've been having a strange issue with this code. It's been working fine for the past two weeks for all test cases. I'm using EXEC to select the records based on the parameter #PR_ID and it was working fine. Yesterday, after not having touched ANYTHING with the code, I've begun generating an error code for only certain PR_ID values:
Msg 8114, Level 16, State 5, Procedure rpt_select_ACHW_Ob, Line 28 [Batch Start Line 2]
Error converting data type varchar to numeric.
Line 28 leads you to FC VARCHAR(50) which I've checked 10 times. All of the data types declared in the #temp table match up perfectly with the values being selected. Does anyone have any ideas as to why this has stopped working?
Here's a dbFiddle link with some sample data
Currently working with SQL Server 2012.
One of two things happened. The less likely is your original query was written correctly and someone changed an underlying table -- changing an integer column to a string column -- and then populated the string column with non-numeric data.
The more likely scenario is that your original query has implicit conversion. This is just a problem waiting to happen -- and now you know why. You have an error message from SQL Server and it doesn't specify the table, the row, or the column where the problem occurs. Arrggg!
My suggestion is to go through the query and check every expression and comparison to be sure the types are compatible (number/number, string/string, datetime/datetime is sufficient). If they are not, add explicit conversions. You can add the conversions using try_convert() or try_cast(), which will at least avoid the error (at the expense of producing NULLs).
I wish SQL Server had a "no-implicit conversions" mode where it would warn you that a query was using such conversions. Alas, no. So, get into the habit of writing your queries so all conversions are explicit.
EDIT:
For instance (based on the comments), this expression:
CAST((ob.SF + '/' + ob.CN) AS VARCHAR
should be:
CAST( (ob.SF as VARCHAR(255)) + '/' + ob.CN) AS VARCHAR(255))
Note that you should include lengths in all CHAR()/VARCHAR() references in SQL Server.
Per your comment on #Gordon's answer: If accurate, mark Gordon's response as your solve.
This will fail:
Declare #SF Int = 12
Declare #CN VarChar(10) = '2'
Select CAST((#SF + '/' + #CN) AS VARCHAR)
Unless you add a Cast:
Select CAST((Cast(#SF As VarChar(10)) + '/' + #CN) AS VARCHAR)
Result
12/2
I'm trying to construct a small query which will pull data from individual fields in a DB and print them in a human readable list format (it's what the operators are used to seeing). The code I have here is far from complete but It seems to me that it should work.
DECLARE #PSUCARD VARCHAR(20)
DECLARE #EQUIPMENT VARCHAR(50)
DECLARE #T1 VARCHAR
SET #PSUCARD = 'PSU-888'
SET #EQUIPMENT = '123_POUCH'
PRINT #PSUCARD + ':'
PRINT #EQUIPMENT
PRINT ''
IF (SELECT TEMPERATURE_MAIN FROM PSU WHERE PSU.PART_ID = #PSUCARD AND PSU.OPERATION_RESOURCE_ID = #EQUIPMENT)IS NOT NULL BEGIN
SET #T1 = (SELECT TEMPERATURE_MAIN FROM PSU WHERE PSU.PART_ID = #PSUCARD AND PSU.OPERATION_RESOURCE_ID = #EQUIPMENT)
PRINT 'Temperature: ' + #T1
--(SELECT TEMPERATURE_MAIN FROM PSU WHERE PSU.PART_ID = #PSUCARD AND PSU.OPERATION_RESOURCE_ID = #EQUIPMENT)
END
If I execute the code as is, #T1 returns a * rather than a value. If I remove comments from the line below I am reassured that there is indeed a value there.
I have other code very similar to this which works fine. Any ideas?
Also, I don't know if this helps in diagnosing the problem, but despite the temperature field in the DB being an INT, I get a conversion message if I try to treat #T1 an an INT.
This is because you declared #T1 as VARCHAR without a length. According to this:
When n is not specified in a data definition or variable declaration
statement, the default length is 1. When n is not specified when using
the CAST and CONVERT functions, the default length is 30.
You should always specify a length when declaring a VARCHAR variable:
DECLARE #T1 VARCHAR(50)
You need to give length for varchar datatype else it is going to take only one character
DECLARE #T1 VARCHAR(50)
I am having more controls (assume 10 controls with textbox, dropdown, radio buttons) in my Windows forms application for filtering data which all are not a mandatory, hence user may filter data with 1 control or more.
Now I have to create a stored procedure for filtering the data based on their inputs.
Ex: if user enters some text in 1 textbox control, and left remaining 9 controls with empty data, I have to filter data based on only that textbox which user entered.
If user enters some text in 1 textbox control and 1 dropdown, and left remaining 8 controls with empty data, I have to filter data based on only that textbox and dropdown which user entered.
What am I supposed to do?
In source code:
If user entered/selected text on any control, I am passing values as parameters else i am passing as "null" to remaining all other parameters .
In stored procedure:
I gave all 10 controls parameters to get value from Source Code,based on parameters I am filtering data.
if #Param1=null && #Param2=null && #Param3='SomeText'
begin
sELECT * FROM tABLE1 wHERE TableCOLUMN3=#Param3
END
if #Param1=null && #Param2='SomeText' && #Param3='SomeText'
begin
sELECT * FROM tABLE1 wHERE TableCOLUMN2=#Param2 AND TableCOLUMN3=#Param3
END
Note: I need to filter data with each table column to each parameter , Simply assume #Param1--TableCOLUMN1, #param2--TableCOLUMN2, filter varies depend on parameters text.
If I do like this my stored procedure will be more enormous and very big because I have 10 parameters to check (just for your reference I gave 3 parameters above sample).
What I want is :
Since I gave 10 parameters, based on parameters which having values (some text other than NULL) only I have to filter data by using where condition.
Is there any other way to do this, or does anyone have any other ways for this to do?
As long as you make your params default to null and either don't pass in a value for the params you dont need or pass in dbnull value then you can filter like this
CREATE PROC dbo.SAMPLE
(
#Param1 VARCHAR(255) = NULL,
#Param2 VARCHAR(255) = NULL,
#Param3 VARCHAR(255) = NULL,
#Param4 VARCHAR(255) = NULL,
#Param5 VARCHAR(255) = NULL,
#Param6 VARCHAR(255) = NULL
)
AS
BEGIN
SELECT *
FROM Table1
WHERE (#Param1 IS NULL OR TableCOLUMN1 = #Param1)
AND (#Param2 IS NULL OR TableCOLUMN2 = #Param2)
AND (#Param3 IS NULL OR TableCOLUMN3 = #Param3)
AND (#Param4 IS NULL OR TableCOLUMN4 = #Param4)
AND (#Param5 IS NULL OR TableCOLUMN5 = #Param5)
OPTION (RECOMPILE) -- as JamesZ suggested to prevent caching
END
EXEC dbo.SAMPLE #Param2 = 'SomeText' -- only filter where TableCOLUMN2 = #Param2
I would suggest something like that:
SELECT *
FROM TABLE1
WHERE TableCOLUMN1=ISNULL(#Param1,TableCOLUMN1)
AND TableCOLUMN2=ISNULL(#Param2,TableCOLUMN2)
AND TableCOLUMN3=ISNULL(#Param3,TableCOLUMN3)
AND TableCOLUMN4=ISNULL(#Param4,TableCOLUMN4)
... and so on...
This will filter column1 on a value if you specify param1 otherwise it will use the columnvalue itself which will always be true.
But this will only work if your #Param values were NULL in each case if you won't use them.
If the table is big / you need to use indexes for fetching the rows, the problem with this kind of logic is, that indexes can't really be used. There's basically two ways how you can do that:
Add option (recompile) to the end of the select statement by #Ionic or #user1221684. This will cause the statement to be recompiled every time it is executed, which might be a lot of CPU overhead if it's called often.
Create dynamic SQL and call it using sp_executesql
Example:
set #sql = 'SELECT * FROM TABLE1 WHERE '
if (#Param1 is not NULL) set #sql = #sql + 'TableCOLUMN1=#Param1 AND '
if (#Param2 is not NULL) set #sql = #sql + 'TableCOLUMN2=#Param2 AND '
if (#Param3 is not NULL) set #sql = #sql + 'TableCOLUMN3=#Param3 AND '
-- Note: You're not concatenating the value of the parameter, just it's name
set #sql = #sql + ' 1=1' -- This handles the last 'and'
EXEC sp_executesql #sql,
N'#Param1 varchar(10), #Param2 varchar(10), #Param3 varchar(10)',
#Param1, #Param2, #Param3
As an extra option, you could do some kind of mix between your original idea and totally dynamic one, so that it would have at least the most common search criteria handled so that in can be fetched efficiently.
Normally every parameter will have a default value, for example int will have the default value as zero. So using this you can have the condition. See the exam sql below.
create procedure [dbo].[sp_report_test](
#pParam1 int,
#pParam2 int ,
#pParam3 int,
#pParam4 varchar(50)
)
AS
SELECT
*
FROM [vw_report]
where
(#pParam1 <= 0 or Column1 = #pParam1) and
(#pParam2 <= 0 or Column2 = #pParam2) and
(#pParam3 <= 0 or Column3 = #pParam3) and
(#pParam4 is null or len(#pParam4) <= 0 or Column4 = #pParam4);
GO
I'm dealing with a table in which a bunch of arbitrary settings are stored as VARCHAR(255) values. The particular one I'm tasked with dealing with is a sequence number that needs to be incremented and returned to the caller. (Again, note that the sequence "number" is stored as VARCHAR, which is something I don't have any control over).
Because it's a sequence number, I don't really want to select and update in separate steps. When I've dealt with this sort of thing in the past with actual numeric fields, my method has been something like
UPDATE TABLE SET #SEQ_NUM = VALUE = VALUE + 1
which increments the value and gives me the updated value in one swell foop. I thought in this situation, I'd try the same basic thing with casts:
DECLARE #SEQ_NUM VARCHAR(255)
UPDATE SOME_TABLE
SET #SEQ_NUM = VALUE = CAST((CAST(VALUE AS INT) + 1) AS VARCHAR)
WHERE NAME = 'SOME_NAME'
The actual update works fine so long as I don't try to assign the result to the variable; as soon as I do, I receive the following error:
Msg 549, Level 16, State 1, Line 4 The collation
'SQL_Latin1_General_CP1_CI_AS' of receiving variable is not equal to
the collation 'Latin1_General_BIN' of column 'VALUE'.
I understand what that means, but I don't understand why it's happening, or by extension, how to remedy the issue.
As an aside to fixing the specific error, I'd welcome suggestions for alternative approaches to incrementing a char sequence "number".
From one of the comments, sounds like you may have already hit on this, but here's what I would recommend:
UPDATE TABLE
SET VALUE = CAST((CAST(VALUE AS INT) + 1) AS VARCHAR)
OUTPUT inserted.VALUE
WHERE NAME = 'SOME_NAME'
This will output the new value like a SELECT statement does. You can also cast inserted.VALUE to an int if you wanted to do that in the SQL.
If you wanted to put the value into #SEQ_NUM instead of outputing the value from the statement/stored procedure, you can't use a scalar variable, but you can pump it into a table variable, like so:
DECLARE #SEQ_NUM AS TABLE ( VALUE VARCHAR(255) );
UPDATE TABLE
SET VALUE = CAST((CAST(VALUE AS INT) + 1) AS VARCHAR)
OUTPUT inserted.VALUE INTO #SEQ_NUM ( VALUE )
WHERE NAME = 'SOME_NAME'
SELECT VALUE FROM #SEQ_NUM
Maintaining a sequential number manually is by no means a solution I'd like to work with, but I can understand there might be constraints around this.
If you break it down in to 2 steps, then you can work around the issue. Note I've replaced your WHERE clause for this example code to work:
CREATE TABLE #SOME_TABLE ( [VALUE] VARCHAR(255) )
INSERT INTO #SOME_TABLE
( VALUE )
VALUES ( '12345' )
DECLARE #SEQ_NUM VARCHAR(255)
UPDATE #SOME_TABLE
SET [VALUE] = CAST(( CAST([VALUE] AS INT) + 1 ) AS VARCHAR(255))
WHERE 1 = 1
SELECT *
FROM #SOME_TABLE
SELECT #SEQ_NUM = [VALUE]
FROM #SOME_TABLE
WHERE 1 = 1
SELECT #SEQ_NUM
DROP TABLE #SOME_TABLE
You can continue using the quirky update in OP but you have to split the triple assignment #Variable = Column = Expression in the UPDATE statement to two simple assignments of #Variable = Expression and Column = #Variable like this
CREATE TABLE #SOME_TABLE (
NAME VARCHAR(255)
, VALUE VARCHAR(255) COLLATE Latin1_General_BIN
)
INSERT #SOME_TABLE SELECT 'SOME_NAME', '42'
DECLARE #SEQ_NUM VARCHAR(255)
/*
-- this quirky update fails on COLLATION mismatch or data-type mismatch
UPDATE #SOME_TABLE
SET #SEQ_NUM = VALUE = CAST((CAST(VALUE AS INT) + 1) AS VARCHAR)
WHERE NAME = 'SOME_NAME'
*/
-- this quirky update works in all cases
UPDATE #SOME_TABLE
SET #SEQ_NUM = CAST((CAST(VALUE AS INT) + 1) AS VARCHAR)
, VALUE = #SEQ_NUM
WHERE NAME = 'SOME_NAME'
SELECT *, #SEQ_NUM FROM #SOME_TABLE
This simple rewrite prevents db-engine complaining on difference in data-type between #Variable and Column too (e.g. VARCHAR vs NVARCHAR) and seems like a more "portable" way of doing quirky updates (if there is such thing)
I am working on a stored procedure to get accounts for a case. If the #accounts parameter is NULL, I want to get all the accounts for the case. If #accounts is NOT NULL, then it will be a comma separated string of accountid's. I would like to have a single where clause that can handle this. Something that says if #accounts is NULL then grab all the accounts for the case. Otherwise, use the #accounts parameter and grab the accounts with the account id's specified. I would like to avoid a big IF statement that would require me to have the query twice with 2 different WHERE clauses.
DECLARE #caseId BIGINT,
DECLARE #accounts VARCHAR(255)
SELECT TOP 1 #userId = userId FROM TblTraceCur t
WHERE caseId = #caseid
ORDER BY processDate DESC
SELECT
.... (select logic) ...
WHERE
t.caseId = #caseID AND
t.userId = #userId AND
t.shortStock = 0 AND
... (where I need the new logic) ...
order by t.tracln ASC
Thanks a lot!
This worked for me. Add (#accounts IS NULL OR #accounts LIKE ('%,' + CAST(t.caseId AS VARCHAR(255)) + ',%')) where you indicated in your post. Of course you have to make sure that your first character and last character in #accounts are commas for this to work but it's the easiest I think. Not having the commas would return ids falsely for example: having #accounts = 12 would return ids 1, 2, and 12. Using commas and making sure they're the first and last characters prevent this from happening.