I have a method that is taking vales directly from the database, building a string of xml and then writing the xml to a file.
This is fine until I get special characters eg "'", "<", "&" etc.
Does anyone know of something in Sql Server that would allow me to encode the values as i select them; for example;
select encode(service_status) from myTable
Thanks.
Use FOR XML clause.
It can build XML from multiple values automatically:
WITH q AS (
SELECT 'Barnes & Noble' AS shop
UNION ALL
SELECT 'Marks & Spencer'
)
SELECT *
FROM q
FOR XML AUTO, TYPE
---
<q shop="Barnes & Noble" /><q shop="Marx & Spencer" />
If you just want to encode an existing value, use:
SELECT 'Barnes & Noble'
FOR XML PATH('')
---
Barnes & Noble
you don't need the CLR either just do it in sql...
create function [dbo].[fn_XMLEscape]( #s varchar(max)) returns varchar(max)
as
begin
declare #rs varchar(max)
set #rs = #s
set #rs = replace(#rs, '&', '&')
set #rs = replace(#rs, '''', ''')
set #rs = replace(#rs, '"', '"')
set #rs = replace(#rs, '>', '>')
set #rs = replace(#rs, '<', '<')
Return( #rs)
end
If you have >= sql 2005, I think it may be easiest to stuff your value into an xml element and then pull it back out. This will entitize anything that needs encoding.
declare #x xml, #str varchar(8000), #encStr varchar(8000)
set #x = '<a/>'
set #str = '><&'
set #x.modify(
'insert text{sql:variable("#str")}
as first into (/a)[1]')
set #encStr = CAST(#x.query('/a/text()') as varchar(8000))
select #encStr
--returns: ><&
If the calling application is building what you return into XML, then it is up to the calling application to encode the data. If you want to return XML from SQL Server, then it would be up to you, and the "FOR XML" answer by Quassnoi is correct.
If you are using SQL Server version 2005/2008, then you are in luck, as you can create your own ENCODE function using CLR Functions.
A really good article can be found here.
My variant:CREATE FUNCTION dbo.fn_XmlEncode (#STR varchar(200))
RETURNS varchar(200)
AS
BEGIN
IF CHARINDEX('&', #STR) > 0
BEGIN
DECLARE #POS1 int, #POS2 int
SET #POS1 = CHARINDEX('&', #STR)
WHILE #POS1 > 0 BEGIN
IF SUBSTRING(#STR, #POS1, 5) <> '&'
SET #STR = LEFT(#STR, #POS1 - 1) + '&'
+ case when #POS1 < LEN(#STR) THEN SUBSTRING(#STR, #POS1 + 1, LEN(#STR) - #POS1) ELSE '' END
SET #POS2 = CHARINDEX('&', SUBSTRING(#STR, #POS1 + 5, LEN(#STR)))
IF #POS2 = 0 BREAK
SET #POS1 = #POS1 + 4 + #POS2
END
END
WHILE CHARINDEX('<', #STR) > 0
SET #STR = REPLACE(#STR, '<', '<')
WHILE CHARINDEX('>', #STR) > 0
SET #STR = REPLACE(#STR, '>', '>')
RETURN #STR
END
GO
-- Tests
SELECT dbo.fn_XmlEncode('&&'), dbo.fn_XmlEncode('&<&>"&&')
Related
I'm trying to use a split function to add a range of values to a query. I'd also like to 'OR' the values together but first things first. I'm following an example I have found in a much larger query that uses the split function, so I've made a small query to try to figure out how it works. So far though, all I get is the error *"The name 'Select * from Country As sp WHERE (sp.CountryID in (SELECT [Value] FROM dbo.Split('2,22,', ',')))' is not a valid identifier."* I'm knew to DynamicSQL and I'm not quite sure how this split function is supposed to work.
DECLARE #Countries varchar(MAX);
DECLARE #FiltersOn bit;
DECLARE #Country int;
DECLARE #Query varchar(MAX);
Set #FiltersOn = 0;
Set #Query = 'Select * from Country As sp ';
Set #Countries ='2,22,'
IF ( #Countries IS NOT NULL )
BEGIN
IF ( #FiltersOn = 1 )
BEGIN
SET #Query = #Query + ' AND '
END
ELSE
BEGIN
SET #Query = #Query + ' WHERE '
SET #FiltersOn = 1
END
SET #Query = #Query
+ '(sp.CountryID in (SELECT [Value] FROM dbo.Split('''
+ #Countries + ''', '','')))'
END
EXEC #Query
This is the definition of the Country table:
CREATE TABLE [dbo].[Country](
[CountryID] [int] IDENTITY(1,1) NOT NULL,
[AgentID] [int] NULL,
[Name] [varchar](50) NULL,
[CountryLookupID] [int] NOT NULL
and this is the split function code:
CREATE FUNCTION [dbo].[Split]
(
#String varchar(8000),
#Delimiter varchar(10)
)
RETURNS #ValueTable table ([Value] varchar(255))
BEGIN
DECLARE #NextString varchar(4000)
DECLARE #Pos int
DECLARE #NextPos int
DECLARE #DelimiterCheck varchar(1)
-- initialise
SET #NextString = ''
SET #DelimiterCheck = RIGHT(#String, 1)
-- add trailing delimiter
IF (#DelimiterCheck <> #Delimiter)
SET #String = #String + #Delimiter
-- find position of first delimiter
SET #Pos = CHARINDEX(#Delimiter, #String)
SET #NextPos = 1
-- loop while there is a delimiter in the string
WHILE (#Pos <> 0)
BEGIN
SET #NextString = SUBSTRING(#String, 1, #Pos - 1)
INSERT INTO #ValueTable ([Value]) VALUES (#NextString)
SET #String = SUBSTRING(#String, #Pos + 1, LEN(#String))
SET #NextPos = #Pos
SET #Pos = CHARINDEX(#Delimiter, #String)
END
RETURN
END
You are not writing EXEC statement correctly.
use EXEC (#Query) instead of EXEC #Query
This is not a direct answer to your question, but something I can recomend reading through as I've seend these solutions create problems in systems before.
I would suggest not using the solution using the WHILE loop. While loops constantly create performance problems as SQL Server doesn't have the same ability to optimize loops as for example C# engines does.
Not sure why you are using dynamic SQL either. If you want to make sure that there is no SQL injection please use sp_executesql instead as it checks for harmful code but i would avoid using dynamic sql except when we don't know the structure of our underlying data (hence need for dynamic sql).
Just wrote a function for doing a string split using cte to keep perfomance up.
CREATE SCHEMA Util;
GO
CREATE FUNCTION Util.String_Split (
#Text varchar(MAX),
#SplitChar char
)
RETURNS TABLE
AS
RETURN(
WITH cte AS
(
SELECT
1 AS [RowNumber],
X.Text,
X.RemainingText
FROM
(
SELECT
SUBSTRING(#Text, 1, CHARINDEX(';', #Text) - 1) AS [Text],
SUBSTRING(#Text, CHARINDEX(';', #Text) + 1, LEN(#Text) - CHARINDEX(';', #Text)) AS [RemainingText]
) X
UNION ALL
SELECT
cte.RowNumber + 1,
X.Text,
X.RemainingText
FROM
cte
CROSS APPLY (
SELECT
SUBSTRING(cte.RemainingText, 1, ISNULL(NULLIF(CHARINDEX(';', cte.RemainingText) - 1, -1), LEN(cte.RemainingText))) AS [Text],
CASE
WHEN CHARINDEX(';', cte.RemainingText) = 0 THEN
''
ELSE
SUBSTRING(cte.RemainingText, CHARINDEX(';', cte.RemainingText) + 1, LEN(cte.RemainingText))
END AS [RemainingText]
) X
WHERE
X.Text ''
)
SELECT
cte.Text
FROM
cte
WHERE
cte.Text IS NOT NULL
);
You could then call make your query without the dynamic part using this call:
SELECT
*
FROM
Country sp
WHERE
#Countries IS NULL
OR
sp.CountryId IN (
SELECT * FROM Util.String_Split(#Countries, ',')
)
I have tried so many times but could not find the exact query yet.
The one I made works in few string but doesn't work in another(It is uncertain).
What i want is the word which has '.' in it like "abcde sfjhjk.dkjb sajb njdhf", what i want is "sfjhjk.dkjb" as result . This is just an example.
The query returns all letters in some cases while truncates few digits in other cases. You can check by providing different values.
I tried below :
This doesn't work:
DECLARE #QUERY VARCHAR(MAX)='
renewreque0_.amount AS AMOUNT48_,
renewreque0_.charge_type AS CHARGE3_48_,
renewreque0_.commission_rate AS COMMISSION4_48_
'
SET NOCOUNT ON;
DECLARE #TABLENAME TABLE(TABLE_NAME VARCHAR(MAX),ALIAS VARCHAR(MAX))
DECLARE #COLUMNS_JOIN TABLE(COL VARCHAR(MAX),COLUMN_NAME VARCHAR(MAX),ALIAS VARCHAR(MAX))
DECLARE #NAME VARCHAR(MAX),#ALIAS VARCHAR(MAX),#J_QUERY VARCHAR(MAX),#W_QUERY VARCHAR(MAX)
DECLARE #WHERE_JOIN TABLE(COL VARCHAR(MAX),COLUMN_NAME VARCHAR(MAX),ALIAS VARCHAR(MAX))
WHILE CHARINDEX('.',#QUERY)>1
BEGIN
SET #NAME = REVERSE( SUBSTRING(REVERSE(#QUERY),CHARINDEX('.',REVERSE(#QUERY))+1,CHARINDEX(' ',#QUERY)) )
SET #ALIAS= REVERSE(LEFT(REVERSE(#QUERY),CHARINDEX('.',REVERSE(#QUERY))))
SET #ALIAS=LEFT(#ALIAS,CHARINDEX(' ',#ALIAS))
SET #NAME=LTRIM(RTRIM(#NAME))
SET #ALIAS=LTRIM(RTRIM(#ALIAS))
INSERT INTO #COLUMNS_JOIN SELECT #NAME+#ALIAS,#NAME,REVERSE(LEFT(REVERSE(#ALIAS),LEN(#ALIAS)-1))
SET #QUERY=REPLACE(#QUERY,#NAME+#ALIAS,'')
END
SELECT * FROM #COLUMNS_JOIN
This works:
DECLARE #QUERY VARCHAR(MAX)='
AND t8_.username LIKE ?
AND t4_.branch_id = ?
AND t1_.account_no = ?
AND t0_.remarks = ?
AND t0_.collect_from = ?
'
SET NOCOUNT ON;
DECLARE #TABLENAME TABLE(TABLE_NAME VARCHAR(MAX),ALIAS VARCHAR(MAX))
DECLARE #COLUMNS_JOIN TABLE(COL VARCHAR(MAX),COLUMN_NAME VARCHAR(MAX),ALIAS VARCHAR(MAX))
DECLARE #NAME VARCHAR(MAX),#ALIAS VARCHAR(MAX),#J_QUERY VARCHAR(MAX),#W_QUERY VARCHAR(MAX)
DECLARE #WHERE_JOIN TABLE(COL VARCHAR(MAX),COLUMN_NAME VARCHAR(MAX),ALIAS VARCHAR(MAX))
WHILE CHARINDEX('.',#QUERY)>1
BEGIN
SET #NAME = REVERSE( SUBSTRING(REVERSE(#QUERY),CHARINDEX('.',REVERSE(#QUERY))+1,CHARINDEX(' ',#QUERY)) )
SET #ALIAS= REVERSE(LEFT(REVERSE(#QUERY),CHARINDEX('.',REVERSE(#QUERY))))
SET #ALIAS=LEFT(#ALIAS,CHARINDEX(' ',#ALIAS))
SET #NAME=LTRIM(RTRIM(#NAME))
SET #ALIAS=LTRIM(RTRIM(#ALIAS))
INSERT INTO #COLUMNS_JOIN SELECT #NAME+#ALIAS,#NAME,REVERSE(LEFT(REVERSE(#ALIAS),LEN(#ALIAS)-1))
SET #QUERY=REPLACE(#QUERY,#NAME+#ALIAS,'')
END
SELECT * FROM #COLUMNS_JOIN
Can anybody please help.
I would first use an SplitString function (passing a blank space as delimiter), which returns as rows each word on a string, and then filter it to return just the words having a dot.
SQL Server 2016 already has one https://learn.microsoft.com/en-us/sql/t-sql/functions/string-split-transact-sql and on older SQL Servers you can build your own : Splitting the string in sql server
set #SQLStr varchar(512) = 'abcde sfjhjk.dkjb sajb njdhf';
select value from string_split(#SQLStr, ' ')
where charindex('.', value) > 0
Alright just for fun
declare #str nvarchar(100) = 'abcde sfjhjk.dkjb sajb njdhf',
#pointIndex int
SET #pointIndex = (SELECT CHARINDEX('.',#str) )
SELECT RTRIM(LTRIM(SUBSTRING(#str, #pointIndex - CHARINDEX(' ',REVERSE(LEFT(#str,#pointIndex))) +1,CHARINDEX(' ',REVERSE(LEFT(#str,#pointIndex)))) -- left side
+SUBSTRING(#str,#pointIndex +1, CHARINDEX( ' ', SUBSTRING(#str,#pointIndex,len(#str) - #pointIndex) ) -1 )))
Needless to say i would not recommend this option because it is really hard to maintain. As Marc said your best option here is to split your string for blanks and find the ones with a '.'
Now if you dont have SQLServer 2016 here is a split function for you :
CREATE function [dbo].[Split]
(
#string nvarchar(max),
#delimiter nvarchar(20)
)
returns #table table
(
[Value] nvarchar(max)
)
begin
declare #nextString nvarchar(max)
declare #pos int, #nextPos int
set #nextString = ''
set #string = #string + #delimiter
set #pos = charindex(#delimiter, #string)
set #nextPos = 1
while (#pos <> 0)
begin
set #nextString = substring(#string, 1, #pos - 1)
insert into #table
(
[Value]
)
values
(
#nextString
)
set #string = substring(#string, #pos + len(#delimiter), len(#string))
set #nextPos = #pos
set #pos = charindex(#delimiter, #string)
end
return
end
And use it as such :
SELECT * FROM dbo.Split(REPLACE(#str,' ','/'),'/')
WHERE charindex('.', value) > 0
Note that i replace blanks by another value.
I have a stored procedure and it looks something like this:
SELECT * FROM empTable
WHERE empCode IN (#employeeCode)
The #employeeCode will accept an input from another application where I do NOT have the control of that application. It will give me a string something like this: 'M06', 'R02', 'B19', 'C10'
When I execute the procedure, it returns me 0 row. However, when I run the query manually in the SQL Server using the following query:
SELECT * FROM empTable
WHERE empCode IN ('M06', 'R02', 'B19', 'C10')
It returns me 15 rows.
I'm suspecting that when the employeeCode accepting the input, it assumes the entire 'M06', 'R02', 'B19', 'C10' as ONE string. Can anybody confirm this? If this is true, how am I going to fix it?
Edited: I'm using SQL Server 2000
Here's your savior! (5 years later)
Use a SQL Function!
What the following code do?
The function fn_split_string_to_column takes the list using a common separator and returns a table
CREATE FUNCTION [dbo].[fn_split_string_to_column] (
#string NVARCHAR(MAX),
#delimiter CHAR(1)
)
RETURNS #out_put TABLE (
[column_id] INT IDENTITY(1, 1) NOT NULL,
[value] NVARCHAR(MAX)
)
AS
BEGIN
DECLARE #value NVARCHAR(MAX),
#pos INT = 0,
#len INT = 0
SET #string = CASE
WHEN RIGHT(#string, 1) != #delimiter
THEN #string + #delimiter
ELSE #string
END
WHILE CHARINDEX(#delimiter, #string, #pos + 1) > 0
BEGIN
SET #len = CHARINDEX(#delimiter, #string, #pos + 1) - #pos
SET #value = SUBSTRING(#string, #pos, #len)
INSERT INTO #out_put ([value])
SELECT LTRIM(RTRIM(#value)) AS [column]
SET #pos = CHARINDEX(#delimiter, #string, #pos + #len) + 1
END
RETURN
END
SELECT * FROM empTable WHERE empCode IN (#employeeCode) -- this wont work!
SELECT * FROM empTable WHERE empCode IN (
select value from fn_split_string_to_column(#employeeCode,',') -- this will work!
)
Examples!
Using comma as separator
declare #txt varchar(100) = 'a,b,c, d, e'
select * from fn_split_string_to_column(#txt,',')
Query results:
Using space as separator
declare #txt varchar(100) = 'Did you hear about the guy who lost his left arm and left leg in an accident? He’s all right now.'
select * from fn_split_string_to_column(#txt,' ')
Query results:
Source
The function above is not mine. I borrowed from the answer of the following question:How to split a comma-separated value to columns
Go there and give him an upvote!
(Actually you should go there and read the comments about performance issues you should know about)
Or just use dynamic SQL. It would look something like this:
EXEC('SELECT * FROM empTable WHERE empCode IN (' + #employeeCode + ')')
I am trying to create a dynamic query in SQL Server.
Input: #value= abc,def,en,
Output: MAX(abc) as abc, MAX(def) as def, MAX(en) as en
My efforts so far took me no where.
With CONVERT() and REPLACE() I achieved a bit but finding it difficult. Need help!
Try this:
declare #value varchar(50) = 'abc,def,en'
declare #result varchar(100) = ''
select #result = replace(#value,'abc', 'MAX(''abc'') as abc')
select #result = replace(#result,'def', 'MAX(''def'') as def')
select #result = replace(#result,'en', 'MAX(''en'') as en')
select #result
You can also do the replacements in one line by nesting the expressions.
EDIT: If you have variable values in #value, you can take the below approach:
Use a splitter function to get the individual values in the string as a list. You can take a look at this article for implementations.
Insert this list to a temp table.
Update the temp table as shown above.
Concatenate the values into a single string using STUFF like so:
select stuff((select ',' + val from #temp for xml path('')),1,1,'')
Try this:
DECLARE #Value VARCHAR(200) = 'abc,def,en'
DECLARE #Template VARCHAR(100) = 'MAX(''##'') as ##'
DECLARE #Result VARCHAR(1000) = ''
DECLARE #Data VARCHAR(100) = ''
WHILE LEN(#Value) > 0
BEGIN
SET #Data = REPLACE(LEFT(#Value, ISNULL(NULLIF(CHARINDEX(',', #Value),0), LEN(#Value))),',','')
SET #Result = #Result + REPLACE(#Template, '##', #Data)
IF CHARINDEX(',', #Value) > 0
BEGIN
SET #Result = #Result + ','
SET #Value = REPLACE(#Value,#Data + ',','')
END
ELSE
SET #Value = REPLACE(#Value,#Data,'')
END
SELECT #Result
Have a look at SQL User Defined Function to Parse a Delimited String
So you can do like
Declare #Value varchar(200) = 'abc,def,en'
Declare #Item varchar(20) = null
declare #Str varchar(1000)=''
WHILE LEN(#Value) > 0
BEGIN
IF PATINDEX('%,%',#Value) > 0
BEGIN
SET #Item = SUBSTRING(#Value, 0, PATINDEX('%,%',#Value))
-- SELECT #Item
IF(LEN(#Str)>0)
SET #Str = #Str + ', SELECT MAX('+#Item+') as ' +#Item
ELSE
SET#Str = #Str + ' SELECT MAX('+#Item+') as ' +#Item
SET #Value = SUBSTRING(#Value, LEN(#Item + ',') + 1, LEN(#Value))
END
ELSE
BEGIN
SET #Item = #Value
SET #Value = NULL
SET #Str = #Str + 'SELECT MAX('+#Item+') as ' + #Item
END
END
select #Str
See the fiddle sample here
I have a variable #text varchar which has some values separated by a symbol, whatever I chose it to be. Ex:
declare #text varchar
set #text='John^Marry^Smith^Ane^Sue^'
I need to delete some data, but because it is a different server and database (a very long story), I must specify the in the WHERE clause, the values from my string, something like this:
Delete Employers where employer_name in ('John','Marry','Smith','Ane','Sue')
Can this be done? Most of all without any other objects, like procedures or functions?
Best regards, Bogdan
Simplest way: generate your SQL query as a string, using replace to form your in list, then execute it.
declare #sqlquery nvarchar(max)
set #sqlquery = 'Delete Employers where employer_name in (''' + replace(#text, '^', ''',''') + ''')'
EXEC sp_executesql #sqlquery
IF I understand your question correctly, then the answer is yes, just at you have stated it. You can use the following strip to turn a string into a table. If you declare the return table as a table variable, then you can roll it into your script as a
DELETE where EXISTS(....)
Create function [dbo].[atf_BarListToTable]
(#list ntext)
RETURNS #tbl TABLE (ListPosn int IDENTITY(1, 1) NOT NULL,
SString VARCHAR(1028) NOT NULL) AS
BEGIN
DECLARE #pos int
DECLARE #textpos int
DECLARE #ChunkLength smallint
DECLARE #str nvarchar(4000)
DECLARE #tmpstr nvarchar(4000)
DECLARE #leftover nvarchar(4000)
SET #textpos = 1
SET #leftover = ''
WHILE #textpos <= datalength(#list) / 2
BEGIN
SET #ChunkLength = 4000 - datalength(#leftover) / 2
SET #tmpstr = ltrim(#leftover + substring(#list, #textpos, #ChunkLength))
SET #textpos = #textpos + #ChunkLength
SET #pos = charindex('|', #tmpstr)
WHILE #pos > 0
BEGIN
SET #str = substring(#tmpstr, 1, #pos - 1)
INSERT #tbl (SString) VALUES( #str)
SET #tmpstr = ltrim(substring(#tmpstr, #pos + 1, len(#tmpstr)))
SET #pos = charindex('|', #tmpstr)
END
SET #leftover = #tmpstr
END
IF ltrim(rtrim(#leftover)) <> ''
INSERT #tbl (SString) VALUES(#leftover)
RETURN
END