How to pass a date parameter into a stored procedure - sql

I have created a stored procedure for booking rooms that are not in the tblBooking table for the date which is passing in as a parameter.
Pdate is of type varchar
When I try to run this stored procedure with the command:
usr_sp_Search_Room_by_City_Date '','8','','DEC 13 2014'
It is throws this error:
Msg 102, Level 15, State 1, Line 10
Incorrect syntax near '%D%'.
Here is my procedure:
ALTER procedure usr_sp_Search_Room_by_City_Date
#RoomName varchar(100),
#CityId int = 0,
#LandMark varchar(100),
#BookingDate varchar
as
begin
Declare #Query as varchar(max)
set #Query = 'Select * from tblUser tblusr
join tblUserLogIn tblusrL on tblusr.UserId = tblusrL.UserId
join tblRoom tblP on tblP.RoomId = tblusr.UserId
join tblImage tblImg on tblImg.RoomId = tblusr.UserId
join tblCountry on tblP.RoomCountryId = tblCountry.CntryId
join tblState on tblState.StateId = tblP.RoomStateId
join tblCity on tblCity.CityId = tblP.RoomCityId
join tblRoomType on tblRoomType.RoomTypeId = tblP.RoomTypeId
where ''true''=tblImg.IsProfileImage and ''true''=tblusrL.Isconfirmed'
if(#BookingDate != ' ')
set #Query=#Query+'and tblP.RoomId not in (Select RoomId from tblBookings
where Pdate like ''%'+CAST(#BookingDate AS VARCHAR)+'%'''
exec(#Query)
end

If you printed out the query, you would probably find something like this:
where 'true'=tblImg.IsProfileImage and 'true'=tblusrL.Isconfirmedand tblP.RoomId not in (Select RoomId from tblBookings
Notice that the where statement runs into the and, because you have no spacer.
You don't intend for the comparison to be to '%D%', but that is not a syntax error. This is cause because you have omitted lengths on varchar() declarations.
Here are my recommendations:
Always use length with varchar().
If you want to pass a date into a stored procedure, use a date variable, not a varchar().
Don't use like with dates.
Debug this code by printing out #Query to see what is really being generated.

Is not about the date.
This is the output query from your dynamics query
Select * from tblUser tblusr
join tblUserLogIn tblusrL on tblusr.UserId=tblusrL.UserId
join tblRoom tblP on tblP.RoomId=tblusr.UserId join tblImage tblImg
on tblImg.RoomId=tblusr.UserId
join tblCountry on tblP.RoomCountryId=tblCountry.CntryId join tblState
on tblState.StateId=tblP.RoomStateId
join tblCity on tblCity.CityId=tblP.RoomCityId join tblRoomType
on tblRoomType.RoomTypeId=tblP.RoomTypeId
where 'true'=tblImg.IsProfileImage and 'true'=tblusrL.Isconfirmedand tblP.RoomId not in (Select RoomId from tblBookings
where Pdate like '%0%'
You miss the ")" at the end.
where Pdate like ''%'+CAST(#BookingDate AS VARCHAR)+'%'')'

The source of the actual error is due to you needing a final right parenthesis at the end of:
AS VARCHAR)+'%'')'
Also, you need a space at the beginning of the set #Query=#Query+'and statement. Currently the SQL will render as (partially):
'true'=tblusrL.Isconfirmedand tblP.RoomId not
Even better, you don't even need the CAST as the datatype is already VARCHAR, so the SET controlled by the IF should be:
SET #Query = #Query + ' AND tblP.RoomId not in (Select RoomId from tblBookings
where Pdate like ''%'+ #BookingDate + '%'')';
Always specify a size for variable length datatypes. The #BookingDate input parameter and CAST(#BookingDate AS VARCHAR) (which again, should be removed anyway) should both specify VARCHAR(20). The default size for VARCHAR / NVARCHAR / etc, in some situations, is 1. In other situations it is 30. In either case, do not rely upon defaults.
General notes:
What does the actual data in the Pdate field look like? Just wondering why you are using LIKE instead of = for the test.
You should probably use the BIT datatype for IsProfileImage and Isconfirmed, or at least TINYINT. Either one would be incredibly more efficient than a string field.

How do you pass a date parameter? First, specify the datatype as date, not varchar.
Second, you don't need dynamic sql. You can use a case construct for your conditional logic. Specifically, this:
if(#BookingDate!=' ')
set #Query=#Query+'and tblP.RoomId not in (Select RoomId from tblBookings
where Pdate like ''%'+CAST(#BookingDate AS VARCHAR)+'%'''
can be something like this.
where #BookingDate is null
or
(
#BookingDate is not null
and
tblP.RoomId not in
(Select RoomId from tblBookings
where Pdate = #BookingDate)
)

Related

Anything like template literals while writing a search query in SQL?

I am writing a stored procedure to get a particular value.
declare #num int
set #num = (SELECT Id
FROM [sometable]
WHERE Name like '%today%')
-- returns #num = 1
Select Value
FROM [anothertable]
where name like 'days1'
In the last line of the query I want to add "1" or any other number after 'days', depending on the variable #num.
How can I do it, sort of like how we use template literals in Javascript, using the ${} syntax but in SQL?
You can just use the first query as a sub-query of the second:
select [Value]
from anothertable
where [name] = Concat('days', (select Id from sometable where [Name] like '%today%'));

Stored Procedure was working fine but suddenly stopped for certain values

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

Make SQL SERVER evaluate clauses in a certain order

Take the following table as an instance:
CREATE TABLE TBL_Names(Name VARCHAR(32))
INSERT INTO TBL_Names
VALUES ('Ken'),('1965'),('Karen'),('2541')
sqlfiddle
Executing following query throws an exception:
SELECT [name]
FROM dbo.tblNames AS tn
WHERE [name] IN ( SELECT [name]
FROM dbo.tblNames
WHERE ISNUMERIC([name]) = 1 )
AND [name] = 2541
Msg 245, Level 16, State 1, Line 1 Conversion failed when converting
the varchar value 'Ken' to data type int.
While the following query executes without error:
SELECT [name]
FROM dbo.tblNames AS tn
WHERE ISNUMERIC([name]) = 1
AND [name] = 2541
I know that this is because of SQL Server Query Optimizer's decision. but I am wondering if there is any way to make sql server evaluate clauses in a certain order. this way, in the first query,the first clause filters out those Names that are not numeric so that the second clause will not fail at converting to a number.
Update: As you may noticed, the above query is just an instance to exemplify the problem. I know the risks of that implicit conversion and appreciate those who tried to warn me of that. However my main question is how to change Optimizer's behavior of evaluating clauses in a certain order.
There is no "direct" way of telling the engine to perform operations in order. SQL isn't an imperative language where you have complete control of how to do things, you simply tell what you need and the server decides how to do it itself.
For this particular case, as long as you have [name] = 2541, you are risking a potential conversion failure since you are comparing a VARCHAR column against an INT. Even if you use a subquery/CTE there is still room for the optimizer to evaluate this expression first and try to convert all varchar values to int (thus failing).
You can evade this with workarounds:
Correctly comparing matching data types:
[name] = '2541'
Casting [name] to INT beforehand and only whenever possible and on a different statement, do the comparison.
DECLARE #tblNamesInt TABLE (nameInt INT)
INSERT INTO #tblNamesInt (
nameInt)
SELECT
[nameInt] = CONVERT(INT, [name])
FROM
dbo.tblNames
WHERE
TRY_CAST([name] AS INT) IS NOT NULL -- TRY_CAST better than ISNUMERIC for INT
SELECT
*
FROM
#tblNamesInt AS T
WHERE
T.nameInt = 2351 -- data types match
Even an index hint won't force the optimizer to use an index (that's why it's called a hint), so we have little control on how it gets stuff done.
There are a few mechanics that we know are evaluated in order and we can use to our advantage, such as the HAVING expressions will always be computed after grouping values, and the grouping always after WHERE conditions. So we can "safely" do the following grouping:
DECLARE #Table TABLE (IntsAsVarchar VARCHAR(100))
INSERT INTO #Table (IntsAsVarchar)
VALUES
('1'),
('2'),
('20'),
('25'),
('30'),
('A') -- Not an INT!
SELECT
CASE WHEN T.IntsAsVarchar < 15 THEN 15 ELSE 30 END,
COUNT(*)
FROM
#Table AS T
WHERE
TRY_CAST(T.IntsAsVarchar AS INT) IS NOT NULL -- Will filter out non-INT values first
GROUP BY
CASE WHEN T.IntsAsVarchar < 15 THEN 15 ELSE 30 END
But you should always avoid writing code that implies implicit conversions (like T.IntsAsVarchar < 15).
Try like this
SELECT [name]
FROM #TBL_Names AS tn
WHERE [name] IN ( SELECT [name]
FROM #TBL_Names
WHERE ISNUMERIC([name]) = 1 )
AND [name] = '2541'
2)
AND [name] = convert(varchar,2541 )
Since You are storing name as varchar(32) varchar will accept integer datatype values also called precedence value
What about:
SELECT *
FROM dbo.tblNames AS tn
WHERE [name] = convert(varchar, 2541)
Why do you need ISNUMERIC([name]) = 1) since you only care about the value '2541'?
You can try this
SELECT [name]
FROM dbo.TBL_Names AS tn
WHERE [name] IN ( SELECT [name]
FROM dbo.TBL_Names
WHERE ISNUMERIC([name]) = 1 )
AND [name] = '2541'
You need to just [name] = 2541 to [name] = '2541'. You are missing ' (single quote) with name in where condition.
You can find the live demo Here.
Honestly, I wouldn't apply the implicit cast to your column [name], it'll make the query non-SARGable. Instead, convert the value of your input (or pass it as a string)
SELECT [name]
FROM dbo.TBL_Names tn
WHERE [name] = CONVERT(varchar(32),2541);
If you "must", however, wrap [name] (and suffer performance degradation) then use TRY_CONVERT:
SELECT [name]
FROM dbo.TBL_Names tn
WHERE TRY_CONVERT(int,[name]) = 2541;

Using column variables in my WHERE clause

I have two tables (tableA and tableB) both with a name column. tableA's name column might be called NAME, tableB's column might be called FULLNAME, but they both are supposed to have the same value.
I am to write a query that pulls member id's (from either table) where these two column values are not the same. However, I'd like to pass the column names I'm checking via parameter, as this will be going in an SSRS report and in the future i'd like to be able to use it to compare any other column between these two tables.
Something like this:
DECLARE #COLUMN_A VARCHAR(50), #COLUMN_B VARCHAR(50)
/* COLUMN PARAMS WILL BE PASSED IN VIA SSRS */
SELECT
DISTINCT(MEMBER_ID)
FROM
TABLE_A
JOIN TABLE_B
ON (TABLE_A.MEMBER_ID = TABLE_B.MEMBER_ID)
WHERE
#COLUMN_A <> #COLUMN_B
Is something like this possible?
edit:
Or might something like this work?
DECLARE
#column VARCHAR(50)
SELECT #column = 'FIRST_NAME';
SELECT DISTINCT
MEMBR_ID,
case
when #column='FIRST_NAME' then MEMBR_FIRST_NAME
when #column='LAST_NAME' then MEMBR_LAST_NAME
end TABLE_1,
case
when #column='FIRST_NAME' then FIRSTNAME
when #column='LAST_NAME' then LASTNAME
end TABLE_2,
#column
FROM
TABLE_1
JOIN TABLE_2
ON (TABLE_1.MEMBR_ID = TABLE_2.MEMBR_ID)
WHERE
TABLE_1.#column <> TABLE_2.#column
Is something like this possible?
Technically, the syntax is fine. The where will be comparing two constant strings. The results will be either all rows or no rows, depending on whether the two strings are the same.
Do these evaluate to the columns? No, they do not. You cannot pass parameters into a SQL statement for identifiers -- column names, table names, schema names, database names, function names, or operators (for example).
You can do this using dynamic SQL, but you have to plug the names in:
DECLARE #sql NVARCHAR(MAX);
SET #sql = '
SELECT DISTINCT A.MEMBER_ID
FROM TABLE_A A JOIN
TABLE_B B
ON A.MEMBER_ID = B.MEMBER_ID
WHERE A.#COLUMN_A <> B.#COLUMN_B
';
SET #sql = REPLACE(#sql '#COLUMN_A', COLUMN_A);
SET #sql = REPLACE(#sql '#COLUMN_B', COLUMN_B);
exec sp_executesql #sql;

Passing multiple value parameter to stored procedure

I have an existing stored procedure which accepts two single valued parameter. Now i have requirement to change one of its parameters to accept multiple values . I modified the stored procedure as below.
CREATE PROCEDURE [dbo].[GetSpecificationsM1]
#EntityType NVARCHAR(100)
,#EntityId BIGINT
AS
DECLARE #EntityTypeId AS BIGINT
SET #EntityTypeId=ISNULL((SELECT ID
FROM [ObjectTypes] WHERE [Type]=#EntityType),0)
SELECT ISNULL([Specifications].[Id],0) AS [SpecificationId]
,ISNULL([Specifications].[Measure],'') AS [Measure]
,ISNULL([Specifications].[Notes],'') AS [Notes]
,ISNULL([UOM].[UOM],'') As UOM
,ISNULL(SpecificationsTemplate.Name,'') As Specification
,ISNULL(SpecificationsTemplate.Id, 0) AS SpecificationTemplateId
,ISNULL(Specifications.EntityId, 0) AS EntityId
,(CASE WHEN ISNULL([SpecificationsTemplate].FieldTypeId,0)=0
THEN 8 ELSE [SpecificationsTemplate].FieldTypeId END) AS [FieldTypeId]
,ISNULL(SpecificationsTemplate.ListId,0) AS ListId
,ISNULL([Specifications].[ListItemId],0) AS [ListItemId]
FROM [SpecificationsTemplate] LEFT OUTER JOIN [Specifications]
ON [SpecificationsTemplate].[Id]=[Specifications].[SpecificationTemplateId]
AND [Specifications].[EntityTypeId]=#EntityTypeId
LEFT OUTER JOIN [UOM] ON [SpecificationsTemplate].[UOMId]=[UOM].[Id]
WHERE [Specifications].[EntityId] IN (#EntityId)
GO
In the above code I modified #EntityId to accept multiple values in the last line of the code. But I am getting an error as cannot convert Varchar to BigINt when i try to pass multiple values into the parameter as #EntityId=9,10,11.
Kindly help me out with the solution.
Thanks !
In your stored procedure change the parameter where condition something like below
Eg:
WHERE ([CostCentre]) collate database_default IN(SELECT Value FROM dbo.FnSplit(#CostCentre,','))
Complete code:
ALTER PROCEDURE [dbo].[SomeSP]
#CostCentre NVARCHAR(255)
AS
SELECT
[ProjectCode],[ProjectName], [ProjectManager],SUM([Hours]) AS [Hours MTD]
FROM dbo.Rpt_NRMA_CATS NC
INNER JOIN PeriodID P ON NC.PeriodID = P.PeriodID
WHERE
([CostCentre]) collate database_default IN (SELECT Value FROM dbo.FnSplit(#CostCentre, ','))
Just change your where condition to
WHERE [Specifications].[EntityId] IN (select item from dbo.fnSplit(#EntityId))
The code for fnSplit function is here