Query XML data in SQL Server using replace in criteria - sql-server

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.

Related

SQL Server : how can I find the specific value from all stored procedures in my database?

How can I find the specific value from all stored procedures in my SQL Server database?
To be more specific, I want to know the value that is inserted into the specified column on the specified table.
For example:
A database has 3 stored procedures; dbo.sp_1, dbo.sp_2, dbo.sp_3
And that database also has a [Log] table which has a [number] column
dbo.sp_1
INSERT INTO [dbo].[Log] ([number])
VALUES (1);
dbo.sp_2
INSERT INTO [dbo].[Log] ([number])
VALUES (2);
dbo.sp_3
INSERT INTO [dbo].[Log] ([number])
VALUES (4);
So, the query results I expect are as follows:
I found a snippet that was used for a somewhat-similar task, however, I did not have to parse values. This may get you close if you really have to parse the sql. Sorry, the rest of the parsing will be left up to you
DECLARE #NonEscapeTextToFindLike NVARCHAR(MAX) = 'INSERTINTO\[dbo\].\[LOG\](\[number\])VALUES('
DECLARE #NonEscapeTextToFind NVARCHAR(MAX) = 'INSERTINTO[dbo].[LOG]([number])VALUES('
;WITH Procs AS
(
SELECT
StoredProcedure = name,
StoredProcedureText = OBJECT_DEFINITION(object_id),
NoBlankText = REPLACE(REPLACE(REPLACE(REPLACE(OBJECT_DEFINITION(object_id),' ',''),CHAR(9),''),CHAR(10),''),CHAR(13),'')
FROM
sys.procedures
)
SELECT
*,
StartOfPossibleInt = CHARINDEX(#NonEscapeTextToFind, Procs.NoBlankText) + LEN(#NonEscapeTextToFind)
FROM
Procs
WHERE
Procs.NoBlankText LIKE '%'+#NonEscapeTextToFindLike+'%' ESCAPE '\'

Issue with SQL Server escape character for SSRS parameter

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, ",")

sql server where clause issue when passing multiple values

I have an issue in my where clause of my query. My query is as follows
select *
from languages
where languages.Language Like #languages
languages.Language is a comma separated string.
For example
languages.Language might be 'French, Spanish'
When #languages = 'french' it works correctly. However, on my webpage I want to be able to select multiple languages and then pass that into the stored procedure like so
#languages = 'French, Spanish'
This will work if languages.Language = 'French , Spanish' however this will not work because there might be another language in between French and Spanish and languages.Language might be something like 'French, German, Spanish' which will not return any results when searching for 'French, Spanish'.
I tried something like this
#languages = '%french% or languages.Language Like %Spanish%'
but this doesn't return any results.
I would consider the way you are storing the language field. You open the possibility of non-normalized data with the comma delimited approach. If that design is just so you can to filter a query then I would do that in a stored procedure instead.
CREATE PROCEDURE MyProcedure(
#LanguageList NVARCHAR(2000)
)
AS
SELECT
*
FROM
Languages
WHERE
Languages.Lanaguage IN (SELECT ID FROM dbo.MY_CreateStringTableFromCommaDelimitedString(#LanguageList))
You could use this with the helper table valued function below.
CREATE FUNCTION [dbo].[MY_CreateStringTableFromCommaDelimitedString](#IDList NVARCHAR(1000))
RETURNS #T TABLE (ID NVARCHAR(100))
AS BEGIN
SELECT #IDList = REPLACE(#IDList, CHAR(9),'')
SELECT #IDList = REPLACE(#IDList, CHAR(10),'')
SELECT #IDList = REPLACE(#IDList, CHAR(13),'')
WHILE(CHARINDEX(',',#IDList)>0)BEGIN
INSERT INTO #T
SELECT LTRIM(RTRIM(SUBSTRING(#IDList,1,CHARINDEX(',',#IDList)-1)))
SET #IDList = SUBSTRING(#IDList,CHARINDEX(',',#IDList)+LEN(','),LEN(#IDList))
END
INSERT INTO #T SELECT LTRIM(RTRIM(#IDList))
RETURN
END

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.

Execute a DELETE command whilst iterating through an XML input parameter

I have a stored procedure that receives 2 parameters.
#username VARCHAR(8),
#xmlShiftDays XML
I want to delete multiple rows from the database while iterating through the XML.
I have managed to do something similar for an INSERT (see below)
INSERT INTO table(username, date)
SELECT
username = #username,
CONVERT(DATETIME,shiftDate.date.value('.','VARCHAR(10)'),103)
FROM
#xmlShiftDays.nodes('/shiftDates/date') as shiftDate(date)
This will successfully insert "x" amount of rows into my table.
I now want to re-engineer the query to DELETE "x" amount of rows. If anyone knows how or could point me in the right direction I would greatly appreciate it.
An example of what I want to achieve is:
DECLARE #username VARCHAR(8)
DECLARE #xmlShiftDays XML
SET #xmlShiftDays = '<shiftDates><date>21/01/2012</date></shiftDates>'
SET #username = 'A0123456'
DELETE FROM table
WHERE username = #username
AND date = "<b>loop through the nodes in the XML string</b>"
Assuming you're using SQL Server 2008 (or newer) for this so I can use the DATE datatype (unfortunately, you didn't specify in your question which version of SQL Server you're using).....
I would strongly recommend you use a language-independent, regional-settings-independent date format in your XML - use the ISO-8601 format of YYYYMMDD for best results.
So try something like this:
DECLARE #xmlShiftDays XML
SET #xmlShiftDays = '<shiftDates><date>20120122</date><date>20120227</date></shiftDates>'
;WITH DatesToDelete AS
(
SELECT
DeletionDate = DT.value('(.)[1]', 'date')
FROM #XmlShiftDays.nodes('/shiftDates/date') AS SD(DT)
)
SELECT * FROM DatesToDelete
This should give you the two dates combined into XML string - right?
Now, you can use this to do the deletion from your table:
DECLARE #username VARCHAR(8)
DECLARE #xmlShiftDays XML
SET #xmlShiftDays = '<shiftDates><date>20120122</date><date>20120227</date></shiftDates>'
SET #username = 'A0123456'
;WITH DatesToDelete AS
(
SELECT
DeletionDate = DT.value('(.)[1]', 'date')
FROM #XmlShiftDays.nodes('/shiftDates/date') AS SD(DT)
)
DELETE FROM dbo.Table
WHERE username = #username
AND date IN (SELECT DeletionDate FROM DatesToDelete)
Does that work for you?
You can select the date from the Xml in the same way you did for the Insert:
DELETE FROM table
WHERE username = #username
AND date IN (SELECT
CONVERT(DATETIME,shiftDate.date.value('.','VARCHAR(10)'),103)
FROM
#xmlShiftDays.nodes('/shiftDates/date') as shiftDate(date))

Resources