Issue with SQL Server escape character for SSRS parameter - sql-server

In SSRS I have a parameter of office manager names that is populated by a stored procedure:
SELECT
MANAGER.office_manager_name
FROM (SELECT
ISNULL(REPLACE(PRACT_LOC.officemanagername, ',', ''), '*N/A') AS office_manager_name
FROM PRACTICELOCATIONS PRACT_LOC
UNION
SELECT
'*N/A') MANAGER
ORDER BY
MANAGER.office_manager_name
The parameter is populated like this:
*N/A, Smith John, Sharp Alex, O'Toole Tom
In SQL Server I have another SP that joins the office manager name from a table using a function splitter:
CREATE PROCEDURE IP
(#office_manager_name VARCHAR(4000))
AS
Select * from Table A
where ISNULL(REPLACE(A.officemanagername, ',', ''), '*N/A') IN (SELECT item FROM DBO.FNSPLIT(#officemanagername, ','))
The function splitter code looks like this:
ALTER FUNCTION [dbo].[fnSplit]
(
#sInputList VARCHAR(MAX), -- List of delimited items
#sDelimiter VARCHAR(MAX) = ',' -- delimiter that separates items
)
RETURNS #List TABLE (item VARCHAR(MAX))
BEGIN
DECLARE #sItem VARCHAR(MAX)
WHILE CHARINDEX(#sDelimiter,#sInputList,0) <> 0
BEGIN
SELECT
#sItem=RTRIM(LTRIM(SUBSTRING(#sInputList,1,CHARINDEX (#sDelimiter,#sInputList,0)-1))),
#sInputList=RTRIM(LTRIM(SUBSTRING(#sInputList,CHARINDEX(#sDelimiter,#sInputList,0)+LEN(#sDelimiter),LEN(#sInputList))))
IF LEN(#sItem) > 0
INSERT INTO #List SELECT #sItem
END
IF LEN(#sInputList) > 0
INSERT INTO #List SELECT #sInputList -- Put the last item in
RETURN
END
The issue I'm having is that any records with office manager Tom O'Toole do not show up in the report if I select All managers from the parameter. He does show up if I just select O'Toole Tom from the parameter list. I'm thinking it has to do with the apostrophe in his last name as he is the only manager with an apostrophe. If this is the case does anyone know how to use an escape character in this code to get O'Toole to populate?

I've found the issue using SQL Server Profiler.
What reporting is sending to server is this:
exec rsp_m_initialprocessxxxx #practice_manager=N'*N/A,O''''Toole Tom,Sharp Alex,Smith John'
As you can see instead of 2 apostrophes it's sending 4: O''''Toole Tom.
When splitting this sting using DBO.FNSPLIT you've got O''Toole Tom instead of O'Toole Tom and this finds no matches in your table.
The workaroud is the following: replace 4 apostrophes with 2 in your stored procedure like this, adding additional variable #practice_manager1:
declare #practice_manager1 varchar(4000) = (select replace(#practice_manager, replicate(char(39), 2), replicate(char(39), 1)));
Select * from dbo.A
where ISNULL(REPLACE(A.office_manager_name, ',', ''), '*N/A') IN (SELECT item FROM DBO.FNSPLIT(#practice_manager1, ','));
Char(39) is apostrophe, I use its code instead of the symbol for not becoming crazy with '''''
P.S. you are using SSRS 2016, not 2012
UPDATE1
I just tried this but it still doesn't work. It also doesn't work if I
just select Tom O'Toole instead of All, which previously worked.
I've tested this code one more time for both cases, here is my repro:
declare #t table (test_num int, name varchar(4000));
insert into #t values (1, 'O''Toole Tom'),
(2, '*N/A,O''''Toole Tom,Sharp Alex,Smith John');
SELECT test_num, name, item
FROM #t cross apply DBO.FNSPLIT(name, ',');
SELECT test_num, name, item
FROM #t cross apply DBO.FNSPLIT(replace(name, replicate(char(39), 2), replicate(char(39), 1)), ',');
I've inserted 2 strings for both cases and applied DBO.FNSPLIT to that strings directly and after making a replace, the second way worked in both cases:
UPDATE2
How to find the first parameter passed to stored procedure.
First of all ALTER your procedure, that will invalidate the old plan.
Then run it from SSRS with the problematic parameter. This parameter will be sniffed as it's the first execution. Then you can see it as ParameterCompiledValue graphically or in xml of your procedure plan.
Here is the code to grab the proc's plan from the cache:
select qp.query_plan
from sys.dm_exec_procedure_stats ps
cross apply sys.dm_exec_query_plan(ps.plan_handle) qp
where object_id = object_id('dbo.sp_test'); -- put here your sp name

The problem is "'" symbol in the Ton's name, try to add the code the replace bellow
REPLACE(#officemanagername,'''','''''')
----
CREATE PROCEDURE ##IP
(#office_manager_name VARCHAR(4000))
AS
Select * from Table A
where ISNULL(REPLACE(A.officemanagername, ',', ''), '*N/A') IN (SELECT item FROM DBO.FNSPLIT(REPLACE(#officemanagername,'''',''''''), ','))

I had the same problem with my stored procedure.
Reducing quotes in my procedure REPLACE(#param,'''','''''') works. But, calling my procedure outside of SSRS won't work for real 2 single quotes.
There is a better solution. You can join report parameters before passing them to your procedure:
Join(Parameters!Value.Value, ",")

Related

Sql server full text search for a list of words

I am struggling with the expression syntax of sql server full-text-search feature.
I have a list of user "words" that need to be found in a column of my table:
SELECT DocumentNode, DocumentSummary
FROM Production.Document
WHERE CONTAINS(DocumentSummary, '"word1" OR "word2"')
Is there an efficiect way to do this for a list of x-number words?
Something better than:
'"word1" OR "word2" ... OR "wordx"'
UPDATE:
Let me clarify that my table (for the example here - Production.Document) has several milions of records, so I need the query to have the best performance possible. Also the query might execute some thousands of times per day. I am writting this to explain why the LIKE queries is not an option.
PS: If there is any alternative approach I am missing (other than full-text-search), please do tell.
There is no way to do it with CONTAINS as CONTAINS only takes a column as the first parameter and not the second. However, you could put your words in a table and then use dynamic sql to execute your query:
Declare #WordTable table
(Id int primary key,
Word varchar(25))
Insert #WordTable values (1, 'word1')
Insert #WordTable values (2, 'word2')
Insert #WordTable values (3, 'word3')
DECLARE #Words VARCHAR(MAX)
SELECT #Words = STUFF ((
SELECT '"' + Word + '" OR '
FROM #WordTable
FOR XML PATH('')
), 1, 0, '')
--drop the last OR
SELECT #Words = SUBSTRING(#Words, 0, LEN(#Words) - 2)
DECLARE #Query VARCHAR(MAX)
SELECT #Query = 'SELECT DocumentNode, DocumentSummary
FROM Production.Document
WHERE CONTAINS(DocumentSummary, ''' + #Words +''')'
EXEC(#Query)
If you need to find all the words in list, use 'LIKE' operator like this:
SELECT Document.Node, Document.Summary
FROM Production.Document
WHERE Document.Summary LIKE '%word1%'
AND Document.Summary LIKE '%word2%'
On the other hand, if you need to look for any word in list just change logical operation to OR. Hope, it will help :)

Table-valued parameter to be used in two stored procedures

I wrote a stored procedure to insert values into a table-valued parameter.
CREATE PROCEDURE insert_timezone_table
#TZ NVARCHAR(10)
AS
BEGIN
DECLARE #Table CT_sp;
IF type_id('[dbo].[COUNTRY_TIMEZONE_sp]') IS NOT NULL
DROP TYPE [dbo].[COUNTRY_TIMEZONE_sp];
CREATE TYPE COUNTRY_TIMEZONE_sp as TABLE (country_code NVARCHAR(2))
INSERT #Table (country_code)
SELECT country_code
FROM Timezones
WHERE fo_timezone = #TZ
END
Then I want to pass this as input for another stored procedure, using in the end in the WHERE clause like this:
WHERE country_code IN (SELECT country_code FROM #TimezoneTable)
This is not yielding what I want, so I have two questions:
Is it possible to make a simple select on the table-valued parameter to see exactly what fell inside there on the 1st procedure?
I reckon this can be a problem of missing quotes on the strings being passed in the IN clause.. How can I solve this problem? Anyway I can in the first stored procedure add the results of the other table concatenated with '' ?
WHERE country_code IN (SELECT country_code FROM #TimezoneTable)
I guess it is doing something like IN ( DE,PL) so it retrieves blanks, and it should be doing IN ('DE','PL') but I do not know how to put the country_codes in quotations
Thank you!

Compare the results of a bunch of SQL statements and review results

I have a SQL Server table with 2 columns. Column 1 is Query_Name and column 2 is Query_Text. This query_text column contains a semi-simple SQL Select statement. I have 34 rows like this. These Select statements are not necessarily operating on the same SQL server. I want a way where I can run these 34 queries, compare their result, and notify the user via email if certain conditions apply. The email part, I got that. It is a SQL server agent task. But I am lost as to how to run these 34 queries and compare them.
This has to be dynamic. For instance, a stored procedure with 34 variables won't work. This 34 might be 40 tomorrow. I want to run the queries contained in the query_text column every night and compare them and email the user.
Any ideas?
EDIT 1:
The comparison part: It cannot be significantly different. What is significantly different is determined by my boss. For the sake of this question, let us just assume it cannot vary by more than 100. The output of these 34 queries are numbers (Their data type might be nvarchar, but the output is definitely numbers).
EDIT 2:
RUN 1 query that in turn runs all the queries in a given column.
DECLARE #QUERYCOLUMN nvarchar(50)
SET #QUERYCOLUMN = (SELECT QUERY_TEXT FROM TABLE_NAME)
EXEC(#QUERYCOLUMN)
Something in that fashion.
This dumps your queries into a table variable and loops through them executing the text then deleting it from the table variable. You should definately be aware of the dangers of executing dynamic SQL before adopting this or any other approach though.
DECLARE #Name VARCHAR(50), #Query VARCHAR(MAX)
DECLARE #Queries TABLE (Query_Name VARCHAR(50), Query_Text VARCHAR(MAX))
INSERT INTO #Queries(Query_Name, Query_Text)
SELECT Query_Name, Query_Text FROM MY_SOURCE_TABLE
WHILE EXISTS (SELECT 1 FROM #Queries)
BEGIN
SELECT TOP 1 #Name = Query_Name, #Query = Query_Text FROM #Queries
EXEC(#Query)
DELETE FROM #Queries WHERE Query_Name = #Name
END
This method assumes Query_Name is unique in your source data or else the results could be skewed.
This will allow you to iterate through the table and execute the statements contained in each entry.
You will need to alter the statement contained in #sSQL if you wish to output this into a table, but this will work for testing in SSMS for you.
I would suggest you use a simple incrementing ID of sorts (Identity is the easiest) to allow you determine when all entries have been accounted for.
DECLARE #QID INT, #sSQL NVARCHAR(MAX) = ''
SELECT #QID = MIN(QID) FROM [dbo].[QueryComparison]
WHILE #QID IS NOT NULL
BEGIN
SELECT #sSQL = Query_Text FROM [dbo].[QueryComparison] WHERE QID = #QID
EXEC sp_executesql #sSQL
SELECT #QID = MIN(QID) FROM [dbo].[QueryComparison] WHERE QID > #QID
END

Query XML data in SQL Server using replace in criteria

I have an XML column in my SQL Server 2008 table. What I'm trying to do is for a given parameter to my stored procedure strip out any spaces (using REPLACE) in the param and use this in the WHERE criteria but then using the XQuery exist clause also use the REPLACE method on the xml data:
-- Add the parameters for the stored procedure here
#PostCode varchar(20) = ''
AS
BEGIN
-- strip out any spaces from the post code param
SET #PostCode = REPLACE(#PostCode, ' ','')
SELECT TOP 1 *
FROM sd_LocalAuthorities
WHERE PostCodes.exist(N'REPLACE(/PostCodes/PostCode/text(), '' '','''')[. = sql:variable("#PostCode")]') = 1
END
I'm getting the error at XQuery sd_LocalAuthorities.PostCodes.exist()
There is no function '{http://www.w3.org/2004/07/xpath-functions}:REPLACE()
when running the procedure. Is there any alternatives to REPLACE() I can use to strip out spaces just for this WHERE criteria, I don't want to be modifying the table itself.
There is an XQuery function 'replace' but it's not available in TSQL where you want to use it. As an alternative approach you could pull the postcodes out of the XML and do the replace on native values. Something like this;
declare #sd_LocalAuthorities table (id int, postcodes xml)
declare #PostCode varchar(20); set #PostCode = 'BB11BB'
insert #sd_LocalAuthorities values (1, N'<PostCodes><PostCode>AA1 1AA</PostCode></PostCodes>')
insert #sd_LocalAuthorities values (2, N'<PostCodes><PostCode>BB1 1BB</PostCode></PostCodes>')
insert #sd_LocalAuthorities values (3, N'<PostCodes><PostCode>CC1 1CC</PostCode></PostCodes>')
select top 1
la.*
from
#sd_LocalAuthorities la
cross apply la.postcodes.nodes('/PostCodes/PostCode') as t(c)
where
replace(t.c.value('.', 'varchar(20)'), ' ', '') = #PostCode
This approach is more precise than converting the whole XML document/fragment to varchar because it only performs the replace on the postcode values. Depending on your circumstances an XML index may help performance.

How to pass multiple values in a single parameter for a stored procedures?

I want to pass multiple values in a single parameter. SQL Server 2005
You can have your sproc take an xml typed input variable, then unpack the elements and grab them. For example:
DECLARE #XMLData xml
DECLARE
#Code varchar(10),
#Description varchar(10)
SET #XMLData =
'
<SomeCollection>
<SomeItem>
<Code>ABCD1234</Code>
<Description>Widget</Description>
</SomeItem>
</SomeCollection>
'
SELECT
#Code = SomeItems.SomeItem.value('Code[1]', 'varchar(10)'),
#Description = SomeItems.SomeItem.value('Description[1]', 'varchar(100)')
FROM #XMLDATA.nodes('//SomeItem') SomeItems (SomeItem)
SELECT #Code AS Code, #Description AS Description
Result:
Code Description
========== ===========
ABCD1234 Widget
You can make a function:
ALTER FUNCTION [dbo].[CSVStringsToTable_fn] ( #array VARCHAR(8000) )
RETURNS #Table TABLE ( value VARCHAR(100) )
AS
BEGIN
DECLARE #separator_position INTEGER,
#array_value VARCHAR(8000)
SET #array = #array + ','
WHILE PATINDEX('%,%', #array) <> 0
BEGIN
SELECT #separator_position = PATINDEX('%,%', #array)
SELECT #array_value = LEFT(#array, #separator_position - 1)
INSERT #Table
VALUES ( #array_value )
SELECT #array = STUFF(#array, 1, #separator_position, '')
END
RETURN
END
and select from it:
DECLARE #LocationList VARCHAR(1000)
SET #LocationList = '1,32'
SELECT Locations
FROM table
WHERE LocationID IN ( SELECT CAST(value AS INT)
FROM dbo.CSVStringsToTable_fn(#LocationList) )
OR
SELECT Locations
FROM table loc
INNER JOIN dbo.CSVStringsToTable_fn(#LocationList) list
ON CAST(list.value AS INT) = loc.LocationID
Which is extremely helpful when you attempt to send a multi-value list from SSRS to a PROC.
Edited: to show that you may need to CAST - However be careful to control what is sent in the CSV list
Just to suggest. You can't really do so in SQL Server 2005. At least there is no a straightforward way. You have to use CSV or XML or Base 64 or JSON. However I strongly discourage you to do so since all of them are error prone and generate really big problems.
If you are capable to switch to SQL Server 2008 you can use Table valued parameters (Reference1, Reference2).
If you cannot I'd suggest you to consider the necessity of doing it in stored procedure, i.e. do you really want (should/must) to perform the sql action using SP. If you are solving a problem just use Ad hoc query. If you want to do so in education purposes, you might try don't even try the above mentioned things.
There are multiple ways you can achieve this, by:
Passing CSV list of strings as an argument to a (N)VARCHAR parameter, then parsing it inside your SP, check here.
Create a XML string first of all, then pass it as an XML datatype param. You will need to parse the XML inside the SP, you may need APPLY operator for this, check here.
Create a temp table outside the SP, insert the multiple values as multiple rows, no param needed here. Then inside the SP use the temp table, check here.
If you are in 2008 and above try TVPs (Table Valued Parameters) and pass them as params, check here.

Resources