Function Split in SQL Server causing error - sql

I want to query a comma-separated list of values. But I get an error:
SELECT
nCmpID, cCompanyName
FROM
(SELECT *
FROM tbl_CompanyMaster
WHERE nCmpID IN (SELECT *
FROM dbo.fnsplit((SELECT can_AccessCompanyID
FROM tbl_UserMenuRelations
WHERE nUserID = 0
AND Is_Active = 1
AND Is_Available = 1), ',') a)
AND Is_Active = 1) t
My function FNSplit:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER FUNCTION [dbo].[fnSplit]
(#sInputList VARCHAR(8000), -- List of delimited items
#sDelimiter VARCHAR(8000) = ',' -- delimiter that separates items
)
RETURNS #List TABLE (item VARCHAR(8000))
BEGIN
DECLARE #sItem VARCHAR(8000)
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
I get these errors:
Msg 102, Level 15, State 1, Line 2
Incorrect syntax near '('.
Msg 102, Level 15, State 1, Line 3
Incorrect syntax near ','.

try this
SELECT
nCmpID, cCompanyName
FROM
(SELECT *
FROM tbl_CompanyMaster
WHERE nCmpID IN (SELECT *
FROM dbo.fnsplit((SELECT can_AccessCompanyID
FROM tbl_UserMenuRelations
WHERE nUserID = 0
AND Is_Active = 1
AND Is_Available = 1), ','))
AND Is_Active = 1) t
there's a useless a) in your code

Answer
Will require making use if dynamic Query
Declare #Query varchar(8000)
Declare #CommaSeparatedList varchar(8000)
-- Use a string variable to store the result of the split function.
Set #CommaSeparatedList =(SELECT *
FROM dbo.fnsplit((SELECT can_AccessCompanyID
FROM tbl_UserMenuRelations
WHERE nUserID = 0
AND Is_Active = 1
AND Is_Available = 1), ','))
SET #Query='SELECT
nCmpID, cCompanyName
FROM
(SELECT *
FROM tbl_CompanyMaster
WHERE nCmpID IN ('+#CommaSeparatedList+') a)
AND Is_Active = 1) t'
Exec(#Query)

your syntax is wrong where you are using user defined function dbo.fnsplit.
You should use cursor for rows by rows execution.
DECLARE #name VARCHAR(100)
DECLARE record CURSOR FOR SELECT name FROM emp
OPEN record
FETCH NEXT FROM record INTO #name
WHILE ##FETCH_STATUS = 0 BEGIN
select * from dbo.fnsplit(#name,',')
FETCH NEXT FROM record INTO #name
END
CLOSE record
DEALLOCATE record

Do not reinvent the wheel if you do not have to.
Example CSV string splitter table-valued function by Jeff Moden:
create function [dbo].[delimitedsplit8K] (
#pstring varchar(8000)
, #pdelimiter char(1)
)
returns table with schemabinding as
return
with e1(N) as (
select 1 union all select 1 union all select 1 union all
select 1 union all select 1 union all select 1 union all
select 1 union all select 1 union all select 1 union all select 1
)
, e2(N) as (select 1 from e1 a, e1 b)
, e4(N) as (select 1 from e2 a, e2 b)
, ctetally(N) as (
select top (isnull(datalength(#pstring),0))
row_number() over (order by (select null)) from e4
)
, ctestart(N1) as (
select 1 union all
select t.N+1 from ctetally t where substring(#pstring,t.N,1) = #pdelimiter
)
, ctelen(N1,L1) as (
select s.N1,
isnull(nullif(charindex(#pdelimiter,#pstring,s.N1),0)-s.N1,8000)
from ctestart s
)
select itemnumber = row_number() over(order by l.N1)
, item = substring(#pstring, l.N1, l.L1)
from ctelen l
;
go
And use like so:
select cm.nCmpID, cm.cCompanyName
from tbl_CompanyMaster cm
where cm.Is_Active = 1
and cm.nCmpId in (
select s.Item
from tbl_UserMenuRelations umr
cross apply dbo.delimitedsplit8K(umr.can_AccessCompanyId,',') s
where umr.nUserId = 0
and umr.Is_Active = 1
and umr.Is_Available = 1
)
splitting strings reference:
Tally OH! An Improved SQL 8K “CSV Splitter” Function - Jeff Moden
Splitting Strings : A Follow-Up - Aaron Bertrand
Split strings the right way – or the next best way - Aaron Bertrand
string_split() in SQL Server 2016 : Follow-Up #1 - Aaron Bertrand
Ordinal workaround for **string_split()** - Solomon Rutzky

Related

How to get only Capital letters from given value

I have a table it contains ID, Description and code columns. I need to fill code column using description column. Sample Description is "Investigations and Remedial Measures" so my code should be "IRM".
Note: Is there any words like "and/for/to/in" avoid it
This code may help you..
declare #input as varchar(1000) -- Choose the appropriate size
declare #output as varchar(1000) -- Choose the appropriate size
select #input = 'Investigations and Remedial Measures', #output = ''
declare #i int
select #i = 0
while #i < len(#input)
begin
select #i = #i + 1
select #output = #output + case when unicode(substring(#input, #i, 1))between 65
and 90 then substring(#input, #i, 1) else '' end
end
SELECT #output
Personally I would do this with an inline table-valued function
On SQL Server 2017 or better, or Azure SQL Database:
CREATE OR ALTER FUNCTION dbo.ExtractUpperCase(#s nvarchar(4000))
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN
(
WITH s(s) AS (SELECT 1 UNION ALL SELECT s+1 FROM s WHERE s < LEN(#s))
SELECT TOP (3) value = STRING_AGG(SUBSTRING(#s,s,1),'')
WITHIN GROUP (ORDER BY s.s)
FROM s WHERE ASCII(SUBSTRING(#s,s,1)) BETWEEN 65 AND 90
);
GO
On SQL Server 2016 or older:
CREATE FUNCTION dbo.ExtractUpperCase(#s nvarchar(4000))
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN
(
WITH s(s) AS (SELECT 1 UNION ALL SELECT s+1 FROM s WHERE s < LEN(#s))
SELECT value = (SELECT TOP (3) v = SUBSTRING(#s,s,1) FROM s
WHERE ASCII(SUBSTRING(#s,s,1)) BETWEEN 65 AND 90
ORDER BY s.s FOR XML PATH(''),
TYPE).value(N'./text()[1]',N'nvarchar(4000)')
);
GO
In either case:
CREATE TABLE #x(id int, name nvarchar(4000));
INSERT #x(id, name) VALUES
(1, N'Belo Horizonte Orange'),
(2, N'São Paulo Lala'),
(3, N'Ferraz de Vasconcelos Toranto');
SELECT id, f.value FROM #x AS x
CROSS APPLY dbo.ExtractUpperCase(x.name) AS f
ORDER BY id OPTION (MAXRECURSION 4000);
Results:
id name
---- ----
1 BHO
2 SPL
3 SVT
The OPTION (MAXRECURSION 4000) is only necessary if your strings can be longer than 100 characters.

SQL: How to get the element from a delimited string by its index?

I have this function (credit: searchsqlserver):
CREATE FUNCTION dbo.fnSplit(
#sInputList VARCHAR(8000) -- List of delimited items
, #sDelimiter VARCHAR(8000) = ',' -- delimiter that separates items
) RETURNS #List TABLE (item VARCHAR(8000))
BEGIN
DECLARE #sItem VARCHAR(8000)
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
GO
It takes as parameters a string and a delimiter and returns the delimited elements one by one.
select * from fnSplit('1,22,333', ',') -- returns 1 22 333
I'll confess that I`m new to SQL and I simply can't follow the whole logic behind this function. What I'm trying to achieve is a function that has a third parameter(an index) and returns the element on the position mention by the index. For example:
select * from fnSplit('1 22 333 444 5555 666', ' ' , 2 ) -- 333
select * from fnSplit('1 22 333 444 5555 666', ' ' , 0 ) -- 1
Using multistatment table-valued UDF with loop to parse string is very inefficient.
Better approaches: Split strings the right way – or the next best way
Anyway if you want to adapt your function you could set IDENTITY column for table variable and then filter based on third parameter:
CREATE FUNCTION dbo.fnSplit(
#sInputList VARCHAR(8000) -- List of delimited items
, #sDelimiter VARCHAR(8000) = ',' -- delimiter that separates items
,#num INT
) RETURNS #List TABLE ( item VARCHAR(8000))
BEGIN
DECLARE #ListHelper AS TABLE(id INT IDENTITY(1,1), item VARCHAR(8000));
DECLARE #sItem VARCHAR(8000)
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 #ListHelper SELECT #sItem
END
IF LEN(#sInputList)> 0
INSERT INTO #ListHelper SELECT #sInputList -- Put the last item in
INSERT INTO #List
SELECT item
FROM #ListHelper
WHERE id = #num
RETURN
END
GO
select * from fnSplit('1 22 333 444 5555 666', ' ' , 3 );
--333
LiveDemo
First off, looping splits are not efficient.
Example with an Index Filter
Select *
From [dbo].[udf-Str-Parse-8K]('1 22 333 444 5555 666', ' ' )
Where RetSeq=3
Returns
RetSeq RetVal
3 333
Example without an Index Filter
Select *
From [dbo].[udf-Str-Parse-8K]('1 22 333 444 5555 666', ' ' )
Returns
RetSeq RetVal
1 1
2 22
3 333
4 444
5 5555
6 666
The UDF used
CREATE FUNCTION [dbo].[udf-Str-Parse-8K] (#String varchar(max),#Delimiter varchar(25))
Returns Table
As
Return (
with cte1(N) As (Select 1 From (Values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N(N)),
cte2(N) As (Select Top (IsNull(DataLength(#String),0)) Row_Number() over (Order By (Select NULL)) From (Select N=1 From cte1 a,cte1 b,cte1 c,cte1 d) A ),
cte3(N) As (Select 1 Union All Select t.N+DataLength(#Delimiter) From cte2 t Where Substring(#String,t.N,DataLength(#Delimiter)) = #Delimiter),
cte4(N,L) As (Select S.N,IsNull(NullIf(CharIndex(#Delimiter,#String,s.N),0)-S.N,8000) From cte3 S)
Select RetSeq = Row_Number() over (Order By A.N)
,RetVal = LTrim(RTrim(Substring(#String, A.N, A.L)))
From cte4 A
);
--Orginal Source http://www.sqlservercentral.com/articles/Tally+Table/72993/
--Select * from [dbo].[udf-Str-Parse-8K]('Dog,Cat,House,Car',',')
--Select * from [dbo].[udf-Str-Parse-8K]('John||Cappelletti||was||here','||')
You could change your split string function to include a row number:
CREATE FUNCTION dbo.fnSplit2(
#sInputList VARCHAR(8000) -- List of delimited items
, #sDelimiter VARCHAR(8000) = ',' -- delimiter that separates items
) RETURNS #List TABLE (item VARCHAR(8000))
BEGIN
DECLARE #sItem VARCHAR(8000),
#RowNumber int
set #RowNumber = 0
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, #RowNumber
set #RowNumber = #RowNumber + 1
END
set #RowNumber = #RowNumber + 1
IF LEN(#sInputList)> 0
INSERT INTO #List SELECT #sInputList, #RowNumber -- Put the last item in
RETURN
END

SQL: Validate a quantity given a comma delimited set of ranges

I need to 'validate' a quantity. Given any number x, return true or false if the number is contained in a comma delimited set of ranges and numbers. Example: valid numbers "1,5-10,25-50,100,500", some valid numbers would be 1,5,6,7,8,9,10, but not 11,12,51, etc.
If you convert the string into a temporary table of min and max values, you can easily select the valid values.
Example in T-SQL (MS SQL Server):
declare #valid varchar(50) = '1,5-10,25-50,100,500'
declare #i int, #range varchar(10)
declare #t table(min int, max int)
while len(#valid) > 0 begin
set #i = charindex(',', #valid)
if #i = 0 begin
set #range = #valid
set #valid = ''
end else begin
set #range = left(#valid, #i - 1)
set #valid = right(#valid, len(#valid) - #i)
end
set #i = charindex('-', #range)
if #i = 0 begin
insert into #t (min, max) values (cast(#range as int), cast(#range as int))
end else begin
insert into #t (min, max) values (cast(left(#range, #i - 1) as int), cast(right(#range, len(#range) - #i) as int))
end
end
select
n
from
(values(1),(5),(6),(7),(8),(9),(10),(11),(12),(51)) as x (n)
inner join #t on n between min and max
Check this (SQL Server 2008)
CREATE FUNCTION [dbo].[RunningNumbers](#anzahl INT=1000000, #StartAt INT=0)
RETURNS TABLE
AS
RETURN
WITH E1(N) AS(SELECT 1 FROM(VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))t(N)), --10 ^ 1
E2(N) AS(SELECT 1 FROM E1 a CROSS JOIN E1 b), -- 10 ^ 2 = 100 rows
E4(N) AS(SELECT 1 FROM E2 a CROSS JOIN E2 b), -- 10 ^ 4 = 10,000 rows
E8(N) AS(SELECT 1 FROM E4 a CROSS JOIN E4 b), -- 10 ^ 8 = 10,000,000 rows
CteTally AS
(
SELECT TOP(ISNULL(#anzahl,1000000)) ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) -1 + ISNULL(#StartAt,0) As Nmbr
FROM E8
)
SELECT * FROM CteTally;
GO
CREATE FUNCTION dbo.CheckRange
(
#ValidTarget INT
,#rangeList VARCHAR(MAX)
)
RETURNS BIT
AS
BEGIN
DECLARE #rangeParts XML=CAST('<root><r>'+REPLACE(#rangeList,',','</r><r>') + '</r></root>' AS XML);
DECLARE #Count INT;
SELECT #Count= COUNT(*)
FROM
(
SELECT Valid.X
FROM #rangeParts.nodes('/root/r') AS rp(p)
CROSS APPLY
(
SELECT CASE WHEN CHARINDEX('-',p.value('.','varchar(max)'))>0
THEN CAST('<root><r>'+REPLACE(p.value('.','varchar(max)'),'-','</r><r>') + '</r></root>' AS XML)
ELSE '<root><r>' + p.value('.','varchar(max)') + '</r><r>' + p.value('.','varchar(max)') + '</r></root>' END AS pr
) AS partsResolved
CROSS APPLY
(
SELECT CASE WHEN #ValidTarget BETWEEN partsResolved.pr.value('(/root/r)[1]','int') AND partsResolved.pr.value('(/root/r)[2]','int') THEN 1 ELSE 0 END AS X
) AS Valid
) AS tbl
WHERE tbl.X=1
RETURN #Count;
END
GO
SELECT Nmbr,dbo.CheckRange(Nmbr,'1,5-10,25-50,100,500')
FROM dbo.RunningNumbers(70,-3)
DROP FUNCTION dbo.RunningNumbers;
GO
DROP FUNCTION dbo.CheckRange;

Replace with a regular expression

Please see the SQL statement below:
select * from person1
inner join person2 on person1.reference=person2.reference
where replace(person1.surname,' ','')<>replace(person2.surname,' ','')
I want to join on reference and then list all persons with a different surname in person1 and person2. However, I do not want whitespaces and certain other characters to be used in the matching, but I don't want lots of nested Replace statements like this:
replace(replace(replace(person1.surname,' ',''),char(39),''),'-','')<>replace(replace(replace(person2.surname,' ',''),char(39),''), '-','')
I am trying to design an SQL statements that replaces all characters that are not in the following list as a zero length string:
A-Z
a-z
Hyphen
I believe I could get around this using regular expressions.
Like I said, this will be about as fast as tree growth, but have fun...
CREATE FUNCTION dbo.StripBadCharacters
(
#input NVARCHAR(255)
)
RETURNS NVARCHAR(255)
WITH SCHEMABINDING
AS
BEGIN
DECLARE #s NVARCHAR(255), #i INT;
SELECT #s = N'', #i = 0;
WHILE #i <= LEN(#input)
BEGIN
IF SUBSTRING(#input, #i, 1) LIKE N'[A-Za-z-]'
BEGIN
SET #s = #s + SUBSTRING(#input, #i, 1);
END
SET #i = #i + 1;
END
RETURN (#s);
END
GO
Sample usage:
DECLARE #x TABLE(name1 NVARCHAR(255), name2 NVARCHAR(255));
INSERT #x VALUES('bob o''brien', 'bob obrien'); -- this will return
INSERT #x VALUES('bob obrien', 'bob o '' brien'); -- this will return
INSERT #x VALUES('bob o''brien', 'bob o''brian'); -- this will not
SELECT name1, name2 FROM #x
WHERE dbo.StripBadCharacters(name1) = dbo.StripBadCharacters(name2);
An inline table-valued function. It's relatively snappy.
CREATE FUNCTION dbo.StringCompareAlpha(
#str1 nvarchar(255),
#str2 nvarchar(255)
)
RETURNS TABLE AS
RETURN
(
WITH
t0 AS (SELECT 0 i UNION ALL SELECT 0),
t1 AS (SELECT 0 i FROM t0 a, t0 b),
t2 AS (SELECT 0 i FROM t1 a, t1 b),
t3 AS (SELECT 0 i FROM t2 a, t2 b),
n AS (SELECT ROW_NUMBER() OVER(ORDER BY (SELECT 0)) i FROM t3),
s1 AS (SELECT ROW_NUMBER() OVER(ORDER BY i) i, SUBSTRING(#str1,i,1) c FROM n WHERE SUBSTRING(#str1,i,1) LIKE '[A-Za-z-]' AND i <= LEN(#str1)),
s2 AS (SELECT ROW_NUMBER() OVER(ORDER BY i) i, SUBSTRING(#str2,i,1) c FROM n WHERE SUBSTRING(#str2,i,1) LIKE '[A-Za-z-]' AND i <= LEN(#str2))
SELECT 1 i WHERE NOT EXISTS(
(SELECT * FROM s1 EXCEPT SELECT * FROM s2)
UNION ALL
(SELECT * FROM s2 EXCEPT SELECT * FROM s1)
)
)
GO
DECLARE #x TABLE(name1 NVARCHAR(255), name2 NVARCHAR(255));
INSERT #x VALUES('bob o''brien', 'bob obrien'); -- this will return
INSERT #x VALUES('bob obrien', 'bob o '' brien'); -- this will return
INSERT #x VALUES('bob o''brien', 'bob o''brian'); -- this will not
SELECT * FROM #x CROSS APPLY dbo.StringCompareAlpha(name1,name2)

Change lower case to upper (title) case using sql query

i want to change case using sql query
e.g if text is : My nAme is iShAn halaRNkar (text is jumbled i.e it may contain Lower case or Upper case anywhere in the senetence)
than i want the output to be : My Name Is Ishan Halarnkar
i have not worked on sql queries much. Kindly help.
Here is another Microsoft SQL function:
CREATE FUNCTION PROPERCASE(#TEXT AS VARCHAR(MAX))
RETURNS VARCHAR(MAX)
AS
BEGIN
DECLARE #RESET BIT;
DECLARE #STR VARCHAR(MAX);
DECLARE #I INT;
DECLARE #C CHAR(1);
SELECT #RESET = 1, #I=1, #STR = '';
WHILE (#I <= LEN(#TEXT))
SELECT #C= SUBSTRING(#TEXT,#I,1),
#STR = #STR + CASE WHEN #RESET=1 THEN UPPER(#C) ELSE LOWER(#C) END,
#RESET = CASE WHEN #C LIKE '[A-ZA-Z]' THEN 0 ELSE 1 END,
#I = #I +1
RETURN #STR
END
There's no such function in any database which do this for you. You've to write a function which actually performs the check on each word in a sentence. Please check the solutions below:
MySql:
DELIMITER //
CREATE FUNCTION CAP_FIRST (input VARCHAR(255))
RETURNS VARCHAR(255)
DETERMINISTIC
BEGIN
DECLARE len INT;
DECLARE i INT;
SET len = CHAR_LENGTH(input);
SET input = LOWER(input);
SET i = 0;
WHILE (i < len) DO
IF (MID(input,i,1) = ' ' OR i = 0) THEN
IF (i < len) THEN
SET input = CONCAT(
LEFT(input,i),
UPPER(MID(input,i + 1,1)),
RIGHT(input,len - i - 1)
);
END IF;
END IF;
SET i = i + 1;
END WHILE;
RETURN input;
END//
DELIMITER ;
Example:
SELECT CAP_FIRST('this is exACtly tHe same!')
Output:
This Is Exactly The Same!
Copyrights:
http://joezack.com/2008/10/20/mysql-capitalize-function/
Hope this helps!
This SQL should work.
SELECT UPPER(LEFT(<ColumnName>, 1)) + LOWER(RIGHT(<ColumnName>,LEN(<ColumnName>)-1)) FROM {YourTableName}
First you need to create function
CREATE FUNCTION ProperCase(#OriginalText VARCHAR(8000))
RETURNS VARCHAR(8000)
BEGIN
DECLARE #CleanedText VARCHAR(8000)
;with
a1 as (select 1 as N union all select 1 union all
select 1 union all select 1 union all
select 1 union all select 1 union all
select 1 union all select 1 union all
select 1 union all select 1),
a2 as (select 1 as N from a1 as a cross join a1 as b),
a3 as (select 1 as N from a2 as a cross join a2 as b),
a4 as (select 1 as N from a3 as a cross join a2 as b),
Tally as (select top (len(#OriginalText)) row_number() over (order by N) as N from a4)
SELECT #CleanedText = ISNULL(#CleanedText,'') +
--first char is always capitalized?
CASE WHEN Tally.N = 1 THEN UPPER(SUBSTRING(#OriginalText,Tally.N,1))
WHEN SUBSTRING(#OriginalText,Tally.N -1,1) = ' ' THEN UPPER(SUBSTRING(#OriginalText,Tally.N,1))
ELSE LOWER(SUBSTRING(#OriginalText,Tally.N,1))
END
FROM Tally WHERE Tally.N
Now you just use this function
select dbo.ProperCase('My nAme is iShAn halaRNkar')