SQL dynamic query check for null, or empty string parameter - sql-server

I am writing a stored procedure that will be executed from an SSRS report. It receives 2 parameters: SourceID, and ConfirmationNumber but it is not working for all the tests that I am running
this returns recs:[dbo].[GetParkingPaymentInformation] 'PARC', ''
this does not return recs:[dbo].[GetParkingtPaymentInformation] 'PARC', NULL
this does not return recs:[dbo].[GetParkingPaymentInformation] '', '002077770'
this does not return recs:[dbo].[GetParkingPaymentInformation] NULL, '002077770'
this does return recs:[dbo].[GetParkingPaymentInformation] 'PARC', '002077770'
I want the sp to work when one or the other parameter are either null, or blank.
This is what I have so far for code:
SET #s_SQL = 'SELECT d.ID, d.TransactionNumber, h.SourceType, ' + #s_ColumnName + ', d.FirstName, d.LastName,' +
'LTRIM(RTRIM(d.FirstName)) + '' '' + LTRIM(RTRIM(d.LastName)) [Name], '+
'd.PaymentAmount, CONVERT(VARCHAR(10), CAST(d.InitiationDate AS DATE), 101) [InitiationDate]' +
', d.Fee, d.TotalAmount, d.PaymentStatus, d.PaymentType, d.CreditCardType, ' +
'CONVERT(VARCHAR(10), CAST(d.PaymentEffectiveDate AS DATE), 101) [PaymentEffectiveDate]' +
', CONVERT(VARCHAR(10), CAST(d.ProcessDate AS DATE), 101) [ProcessDate], CONVERT(VARCHAR(10), CAST(d.CreatedDate AS DATE), 101) [CreatedDate],' +
'd.CashCode, d.TransConfirmID' +
', d.Phone, d.StreetAddress1, d.StreetAddress2, ' +
'LTRIM(RTRIM(d.StreetAddress1)) + '' '' + CASE WHEN LEN(d.StreetAddress2) > 0 THEN LTRIM(RTRIM(d.StreetAddress2)) ELSE '''' END [Address]' +
', d.City, d.[State], d.ZipFive, d.ZipFour, d.Email ' +
'FROM '+ #s_TableHeader + ' h WITH (NOLOCK) ' +
'INNER JOIN ' + #s_TableDetail + ' d WITH (NOLOCK) ' +
'ON h.ID = d.headerID ' +
'WHERE' +
' ((h.sourcetype = ' + '''' + #s_Source_Type + '''' + ') OR ' + '''' + #s_Source_Type + '''' + ' IS NULL OR ' + '''' + #s_Source_Type + '''' + '= '''')' +
' AND ((d.transconfirmid = ' + '''' + #s_Confirmation_Number + '''' + ') OR ' + '''' + #s_Confirmation_Number + '''' + ' IS NULL OR ' + '''' + #s_Confirmation_Number + '''' + '= '''')'
Any help I can get to figure why my checks are not working, it would be great.

*Please note that your example sql code is incomplete and that you name the parameters slightly different from the variables which makes for difficulty understanding the question. *
It is not without reason that the use of dynamic sql is discouraged even in cases where the dangers of sql injection are negligible. One of the primary reasons for this is that dynamic sql is difficult to write, read and debug. That being said I have often found myself using it to solve for problems within poorly designed systems.
I assume that you have investigated alternatives appropriately.
To reduce the complexity of dynamic sql statements I have found that constructing the statement in a modular way to be a good strategy.
In your particular case the use of an 'if' statement (or some variation) may help reduce the complexity of your dynamic where clause and likely help resolve your problem.
Example:
`
set #sql = 'select....... your select statement..... where'
if (#a_Source_Type is not null and len(#a_Source_Type) > 0)
begin
set #sql += '''' + #a_Source_Type + ''' = some_field and '
end
else
begin
set #sql += ' len(isnull(some_field, '''')) = 0 and '
end
set #sql += ' 1 = 1 '
`
The above attempts to move the comparison operators out of the dynamic sql.
I suggest refactoring your query to use a strategy similar to this example as you may find it easier to identify erroneous code.
the final line of the example has proven useful in circumstances where the resulting dynamic sql statement may or may not have where clauses to append

Try changing your WHERE to something like this:
'where' +
' h.sourcetype = ' + isnull(quotename(nullif(#s_Source_Type, ''), ''''), 'h.sourcetype') +
' AND d.transconfirmid = ' + isnull(quotename(nullif(#s_Confirmation_Number, ''), ''''), 'd.transconfirmid')

Related

SQL Stored Procedure How to Modify and Return the Result of an Executed Dynamic Query

I have created a stored procedure that returns a single string of concatenated fields. The issue is that some of these fields may be empty strings resulting in a string much like the below:
, Mendip Road, Farnborough, Hampshire, GU14 9LS
or even
, , Farnborough, Hampshire, GU14 9LS
I really want to strip off any leading commas but I'll only know this once the query has been executed. Is there a way of executing the query, pattern-matching the commas and then removing them before finally returning the modified string?
The query itself is as follows:
SET #SQLQuery = 'SELECT TOP 1 REPLACE((ISNULL(POI,'''') + '', '' + ISNULL(Name,'''') + '', '''
+ ' + ISNULL(Settlement,'''') + '', '' + ISNULL(Cou_Unit,'''') + '', '' + ISNULL(Postcode,'''')),'', , '', '', '')'
+ ' AS ClosestAddress FROM [UKStreetsAndPlaces].[dbo].[OS_Locator] ORDER BY '
+ ' (Longitude ' + #LongitudeOperator + ' ' + CAST(ABS(#Longitude) AS VARCHAR(20)) + ')'
+ ' * (Longitude ' + #LongitudeOperator + ' ' + CAST(ABS(#Longitude) AS VARCHAR(20)) + ')'
+ ' + (Latitude - ' + CAST(#Latitude AS VARCHAR(20)) + ') * (Latitude - ' + CAST(#Latitude AS VARCHAR(20)) + ') ASC'
EXECUTE(#SQLQuery)
Concatenate the comma inside the ISNULL expression as follows:
ISNULL(POI + ', ','')
so your query will look like:
SET #SQLQuery = 'SELECT TOP 1 REPLACE((ISNULL(POI + '', '','''') + ISNULL(Name + '', '','''')'
+ ' + ISNULL(Settlement + '', '','''') + ISNULL(Cou_Unit + '', '','''') + ISNULL(Postcode,'''')),'', , '', '', '')'
+ ' AS ClosestAddress FROM [UKStreetsAndPlaces].[dbo].[OS_Locator] ORDER BY '
+ ' (Longitude ' + #LongitudeOperator + ' ' + CAST(ABS(#Longitude) AS VARCHAR(20)) + ')'
+ ' * (Longitude ' + #LongitudeOperator + ' ' + CAST(ABS(#Longitude) AS VARCHAR(20)) + ')'
+ ' + (Latitude - ' + CAST(#Latitude AS VARCHAR(20)) + ') * (Latitude - ' + CAST(#Latitude AS VARCHAR(20)) + ') ASC'
I don't know if you need dynamic SQL for some other reason, but I think something like this should work (with no Dynamic SQL); if you're really sure you need Dynamic SQL for some other reason, then just refactor this idea into your Dynanmic Statement:
DECLARE #ClosestAddress VARCHAR(1000)
SELECT TOP 1
#ClosestAddress = ISNULL(POI + ', ','')
+ ISNULL(Name + ', ','')
+ ISNULL(Settlement + ', ','')
+ ISNULL(Cou_Unit + ', ', '')
+ ISNULL(Postcode,'')
--AS ClosestAddress
FROM [UKStreetsAndPlaces].[dbo].[OS_Locator] ORDER BY (Longitude = 12.2132) * (Longitude = 12.2132) + (Latitude - 12.2132) * (Latitude - 12.2132) ASC
IF (RIGHT(#ClosestAddress, 2) = ', ')
RETURN SUBSTRING(#ClosestAddress, 0, LEN(#ClosestAddress))
ELSE
RETURN #ClosestAddress
Why this should work: Concatenating NULL + ', ' will result in an empty string. Then we check if the string ends with ', ', and if so we return everything but the last two characters.
You could do something like Replace all Comma with space and then do LTRIM and RTRIM and replace all space with comma.
Create table Data(name varchar(10),lastname varchar(10));
insert into Data values('','Doe');
insert into Data values('Jane','Doe');
insert into Data values('Jane','');
SELECT Replace(Rtrim(Ltrim(Replace(ISNULL(name,'') +',' + ISNULL(lastname,'') + ',',',',' '))),' ',',')
from Data
something like : http://sqlfiddle.com/#!3/6a6c6/1

Compile error - Dynamic SQL

I've tried to implement some dynamic SQL to create a cursor as an extension of a simple SELECT query. The cursor is used as a way to print the GROUPED values returned from the SELECT as a message in SQL Server Management Studio (kind of like a visual summary of the data) . The purpose of this approach is half task related and half to benefit my understanding of how dynamic SQL can be developed. Code reads:
DECLARE #Focus VARCHAR(10);
SET #Focus = 'Completed'; /* User input event focus {Started, Completed} */
DECLARE #PeriodStartDate DATE, #PeriodEndDate DATE;
SET #PeriodStartDate = '04/01/2014';
SET #PeriodEndDate = GETDATE();
DECLARE #sql VARCHAR(MAX);
SET #sql =
'SELECT ' +
'CASE DATEPART(M, ' + '[Event ' + CASE #Focus
WHEN 'Started' THEN 'Start'
WHEN 'Completed' THEN 'End'
END + ' Date]) ' +
' WHEN 1 THEN ''January'' ' +
' WHEN 2 THEN ''February'' ' +
' WHEN 3 THEN ''March'' ' +
' WHEN 4 THEN ''April'' ' +
' WHEN 5 THEN ''May'' ' +
' WHEN 6 THEN ''June'' ' +
' WHEN 7 THEN ''July'' ' +
' WHEN 8 THEN ''August'' ' +
' WHEN 9 THEN ''September'' ' +
' WHEN 10 THEN ''October'' ' +
' WHEN 11 THEN ''November'' ' +
' WHEN 12 THEN ''December'' ' +
' END AS [Event ' + #Focus + ' Month], ' +
' COUNT([Unique ID]) AS [Number of Events] ' +
' FROM [udf_Events](' + #Focus + ', ' + CAST(#PeriodStartDate AS VARCHAR) + ', ' + CAST(#PeriodEndDate AS VARCHAR) + ') ' +
' GROUP BY ' +
' DATEPART(M, ' + '[Event ' + CASE #Focus
WHEN 'Started' THEN 'Start'
WHEN 'Completed' THEN 'End'
END + ' Date]) ' +
' ORDER BY ' +
' DATEPART(M, ' + '[Event ' + CASE #Focus
WHEN 'Started' THEN 'Start'
WHEN 'Completed' THEN 'End'
END + ' Date]) '
;
DECLARE Results CURSOR
FOR
SELECT
#sql;
The error message I receive:
Msg 16924, Level 16, State 1, Line 71 Cursorfetch: The number of
variables declared in the INTO list must match that of selected
columns.
Through grappling with the problem and trying to execute the query as a SELECT statement (removing the complexity of the cursor) using EXEC(#sql) the error message reads:
Invalid column name 'Completed'.
..Which leads me to believe the problem lies with the CASE expression in the first field selected. udf_Events is an in-line table valued function with three arguments. Amongst others, it has columns [Event Start Date] and [Event End Date] which are the values the cursor is looking to do its work on.
Try this...few ' were missing in your query
DECLARE #Focus VARCHAR(10);
SET #Focus = 'Completed'; /* User input event focus {Started, Completed} */
DECLARE #temp VARCHAR(500);
IF(#Focus = 'Completed')
SET #temp = '[Event End Date]'
ELSE
SET #temp = '[Event Start Date]'
DECLARE #PeriodStartDate DATE, #PeriodEndDate DATE;
SET #PeriodStartDate = '04/01/2014';
SET #PeriodEndDate = GETDATE();
DECLARE #sql VARCHAR(MAX);
SET #sql =
'SELECT ' +
'CASE DATEPART(M, ' + #temp + ')' +
' WHEN 1 THEN ''January'' ' +
' WHEN 2 THEN ''February'' ' +
' WHEN 3 THEN ''March'' ' +
' WHEN 4 THEN ''April'' ' +
' WHEN 5 THEN ''May'' ' +
' WHEN 6 THEN ''June'' ' +
' WHEN 7 THEN ''July'' ' +
' WHEN 8 THEN ''August'' ' +
' WHEN 9 THEN ''September'' ' +
' WHEN 10 THEN ''October'' ' +
' WHEN 11 THEN ''November'' ' +
' WHEN 12 THEN ''December'' ' +
' END AS [Event ' + #Focus + ' Month], ' +
' COUNT([Unique ID]) AS [Number of Events] ' +
' FROM [udf_Events](''' + #Focus + ''', ''' + CAST(#PeriodStartDate AS VARCHAR) + ''', ''' + CAST(#PeriodEndDate AS VARCHAR) + ''') ' +
' GROUP BY ' +
' DATEPART(M, ' + #temp + ')' +
' ORDER BY ' +
' DATEPART(M, ' + #temp + ')';
print #sql
You're not quoting the dates in the call to udf_Events so you end up with
[udf_Events](Completed, 2014-04-01, 2014-09-19)
instead of
[udf_Events](Completed, '2014-04-01', '2014-09-19')
The fix is to change the line
' FROM [udf_Events](' + #Focus + ', ' + CAST(#PeriodStartDate AS VARCHAR) + ', ' + CAST(#PeriodEndDate AS VARCHAR) + ') ' +
to
' FROM [udf_Events](' + #Focus + ', ''' + CAST(#PeriodStartDate AS VARCHAR) + ''', ''' + CAST(#PeriodEndDate AS VARCHAR) + ''') ' +
That said, if the parameters to udf_Events are datetimes then I would consider changing the format to YYYYMMDD as this is unambiguous. To do this you can use CONVERT(char(8), <date>, 112) where <date> is the date to be converted.
i.e:
' FROM [udf_Events](' + #Focus + ', ''' + CONVERT(char(8), #PeriodStartDate, 112) + ''', ''' + CONVERT(char(8), #PeriodEndDate, 112) + ''') ' +

SQL Server : execute dynamic sql help please

EXEC SP_EXECUTESQL
#DynamicSQL
, N'#HostIDs VARCHAR(MAX) OUTPUT'
, #HostIDs OUTPUT;
PRINT #HostIDs;
SELECT #HostIDs AS HostIDs;
SET #UpdateSQL = '
EXECUTE [dbo].[usp_Win7_HostUpdater_NEW]
#HostID = ''' + #HostIDs + ''' ,
#PackageID = ''' + #PackageID + ''' ,
#MigrationFlag = ''' + #MigrationFlagID + ''' ,
#Manufacturer = ' + #Manufacturer + ' ,
#Product = ' + #Product + ' ,
#Version = ' + #Version + ' ,
#Reason = ' + #Reason + ' ,
#Contact = ' + #Contact + '
';
SELECT #UpdateSQL AS UpdateSQL;
PRINT #UpdateSQL;
EXEC( #UpdateSQL )
END
I have a stored procedure on both a SQL Server 2005 and 2008 in which the above code is the last part of
it returns a VARCHAR(MAX) of numbers separated by commas.
Now this returned value is large upwards of 600k characters. If I execute this on a SQL Server 2005 it works like 50% of the time, #HostIDs is populated always and #UpdateSQL gets generated with the correct values and is executed.
On SQL Server 2008, #HostIDs is populated but #UpdateSQL is always NULL
This is weirding me out tremendously
Can anyone maybe shed some light on my odd problem?
Check these out
SET CONCAT_NULL_YIELDS_NULL OFF
select 'abc' + null + 'def'
--- abcdef
SET CONCAT_NULL_YIELDS_NULL ON
select 'abc' + null + 'def'
--- NULL
That's one way to get around the problem, which is to set it off before your string building and back on after. Any NULL in the sequence of string concatenation renders the entire statement NULL, which explains it works like 50% of the time - these are when all of the variables are non-null.
Completely agree with freefaller though, unless the question's an abstraction of a larger puzzle, there's no reason to build a dynamic SQL and EXEC it when a direct execution will work for the particular snippet shown.
If any of the parameters are null, the entire statement will be null. You can work around it by doing something like this (and I don't know what the data types are, but sometimes you need to cast them to varchar from int/bool/etc. types to make the concatenation work):
SET #UpdateSQL = '
EXECUTE [dbo].[usp_Win7_HostUpdater_NEW]
#HostID = ' + ISNULL('''' + #HostIDs + '''', 'null') + ' ,
#PackageID = ' + ISNULL('''' + #PackageID + '''', 'null') + ' ,
#MigrationFlag = ' + ISNULL('''' + #MigrationFlagID + '''', 'null') + ' ,
#Manufacturer = ' + ISNULL(#Manufacturer, 'null') + ' ,
#Product = ' + ISNULL(#Product, 'null') + ' ,
#Version = ' + ISNULL(#Version, 'null') + ' ,
#Reason = ' + ISNULL(#Reason, 'null') + ' ,
#Contact = ' + ISNULL(#Contact, 'null') + '
';
It causes because you are not handling nulls
you can use sp_executesql instead of exec it has some benefits over exec

Stored Procedure with a conditional?

I trying to rewrite a stored procedure and my SQL is not very good. What i'm hoping to do is write it so that if ModuleID is 555 then select a custom date range (eg. 2012-01-01 2012-12-31). The Current SP is below.
set ANSI_NULLS ON
set QUOTED_IDENTIFIER ON
GO
/*** EventsGetByRange ***/
ALTER PROCEDURE [dbo].[EventsGetByRange]
(
#Filter nvarchar(500),
#BeginDate datetime,
#EndDate datetime
)
AS
SET DATEFORMAT mdy
Declare #sql nvarchar(4000)
Select #sql = 'SELECT E.PortalID, E.EventID, E.RecurMasterID, E.ModuleID, E.EventDateBegin, E.EventDateEnd, '
+ 'E.EventTimeBegin, E.Duration, E.EventName, E.EventDesc, '
+ 'E.Importance, E.CreatedDate, '
+ 'CreatedBy = U.DisplayName, '
+ 'CreatorID = E.CreatedBy, '
+ 'E.Every, '
+ 'E.Period, '
+ 'E.RepeatType, '
+ 'E.Notify, '
+ 'E.approved, '
+ 'E.Signups, '
+ 'E.MaxEnrollment, '
+ '(Select count(*) from dbo.EventsSignups WHERE EventID = E.EventID and E.Signups = 1) as Enrolled, '
+ 'E.EnrollRoleID, '
+ 'E.EnrollFee, '
+ 'E.EnrollType, '
+ 'E.PayPalAccount, '
+ 'E.PayPalPassword, '
+ 'E.Cancelled, '
+ 'E.DetailPage, '
+ 'E.DetailNewWin, '
+ 'E.DetailURL, '
+ 'E.ImageURL, '
+ 'E.ImageType, '
+ 'E.ImageWidth, '
+ 'E.ImageHeight, '
+ 'E.ImageDisplay, '
+ 'E.Location, '
+ 'c.LocationName, '
+ 'c.MapURL, '
+ 'E.Category, '
+ 'b.CategoryName, '
+ 'b.Color, '
+ 'b.FontColor, '
+ 'E.Reminder, '
+ 'E.TimezoneOffset, '
+ 'E.SendReminder, '
+ 'E.ReminderTime, '
+ 'E.ReminderTimeMeasurement, '
+ 'E.ReminderFrom, '
+ 'E.SearchSubmitted, '
+ 'E.CustomField1, '
+ 'E.CustomField2, '
+ 'E.EnrollListView, '
+ 'E.DisplayEndDate, '
+ 'E.AllDayEvent, '
+ 'E.OwnerID, '
+ 'OwnerName = O.DisplayName, '
+ 'E.LastUpdatedAt, '
+ 'LastUpdatedBy = L.DisplayName, '
+ 'E.LastUpdatedID, '
+ '(Select ModuleTitle from dbo.Modules WHERE ModuleID = E.ModuleID) as ModuleTitle, '
+ 'RMOwnerID = r.OwnerID, '
+ 'r.RRULE, '
+ 'E.OriginalDateBegin, '
+ 'E.NewEventEmailSent '
+ 'FROM dbo.Events E '
+ 'inner join dbo.EventsRecurMaster AS r on E.RecurMasterID = r.RecurMasterID '
+ 'left outer join dbo.Users U on E.CreatedBy = U.UserID '
+ 'left outer join dbo.Users O on E.OwnerID = O.UserID '
+ 'left outer join dbo.Users L on E.LastUpdatedID = L.UserID '
+ 'left join dbo.EventsCategory b on E.Category = b.Category '
+ 'left join dbo.EventsLocation c on E.Location = c.Location '
+ 'WHERE (E.ModuleID = 555 AND E.EventTimeBegin BETWEEN 2012-01-01 AND 2012-12-31) OR ((E.EventTimeBegin <= DATEADD(DAY,1,''' + convert(varchar, #EndDate) + ''') AND DATEADD(minute,E.Duration,E.EventTimeBegin) >= ''' + convert(varchar, #BeginDate) + ''') OR '
+ ' (E.EventTimeBegin BETWEEN ''' + convert(varchar, #BeginDate) + ''' AND DATEADD(DAY,1,''' + convert(varchar, #EndDate) + ''')))'
+ ' AND E.Approved = 1'
+ ' AND E.Cancelled = 0'
+ ' ' + #Filter + ' '
+ ' ORDER BY E.EventDateBegin, E.EventTimeBegin, E.EventDateEnd'
EXEC (#sql)
UPDATE: I used the where statemnnt that Diego recommended but that is not having the desired result. It does not act as and If\Else scanrio (which makes sense when I think about it).
I need to first identify if the module ID is 555 and if so only pull the dates from in the hard coded range otherwise execute it as written. Please let me know if more detail is required.
is the proc failing?
did you try adding E.ModuleID = 555 on the where clause?
Do you really want to hard code the value 555? how about passing it
on a parameter?
And most important question: why adding the sql statement to a
variable and execute it? why not just run the SQL? Is it because of
the #Filter variable?
also, sql server 2005 or 2008?
why nvarchar and not varchar on your variables ("n" occupies double
of space)
EDIT:
ok, you have a OR in there so it may be tricky. Do you want everything from code 555 despite the date range value, or everything within the date range and code 555?
I assume option 2 would make more sense so just add
E.ModuleID = 555
before the
+ ' AND E.Approved = 1'

Correct Amount of Spaces with concatenated person's name in SQL Server

Here is my SQL as I have it now. The first and last name should never be missing because they are required on the interface with validation. However, if the middle initial is missing, I don't want two spaces in the result; I just want one space. I could put a case statement in, but that seems like overkill if SQL already has a function for this purpose. Does anyone know if there is a function for this purpose? What would the code look like?
SELECT ISNULL(contact.firstname, '')
+ ' '
+ ISNULL(contact.middleinitial, '')
+ ' '
+ ISNULL(contact.lastname, '')
FROM dbo.contact
SELECT
ISNULL(contact.firstname, '') +
ISNULL(' ' + contact.middleinitial + ' ', ' ') +
ISNULL(contact.lastname, '')
However, you either should remove ISNULL for firstname, lastname (defined NOT NULL?) or add some trims
SELECT
LTRIM(RTRIM(
ISNULL(contact.firstname, '') +
ISNULL(' ' + contact.middleinitial + ' ', ' ') +
ISNULL(contact.lastname, '')
))
In SQL Server 2012 and up you can use CONCAT function:
SELECT CONCAT(contact.firstname, ' ' + contact.middleinitial, ' ' + contact.lastname)
FROM dbo.contact
If middleinitial is null then appending space to null value will result in null value and CONCAT will skip this null value during concatenation.
SELECT REPLACE(ISNULL(contact.firstname, '') + ' ' + ISNULL(contact.middleinitial, '') + ' ' + ISNULL(contact.lastname, '')
,' ',' ')
FROM dbo.contact

Resources