I have a string for example 32,21C2L5N8C stored in one field. Now I want to expand this string into as follows:
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,CCCCCCCCCCCCCCCCCCCCCLLNNNNNCCCCCCCC
After getting the above string, I want to count number of commas,C's,L's and N's.
Can some one help me with this please?
you can extract the numbers and none numeric characters then then replicate each character, in SQL Server you can use patindex and replicate functions (explanations are in code):
--table variable for holding extracted numbers and none number characters
declare #t table(id int identity(1,1), num int, nonnum char(1))
declare #str1 varchar(50)='32,21C2L5N8C' -- your current given string
declare #int1 varchar(50)='' --for number
declare #str2 varchar(50)='' --for none numeric characters
declare #result varchar(max)=''
while len(#str1)>1 --for parsing the given string
begin
while (Select PatIndex('%[0-9]%', #str1))=1 --extract number
begin
set #int1=#int1+substring(#str1,1,1)
set #str1=substring(#str1,2,len(#str1)-1)
end
set #str2=substring(#str1,1,1) --extract none numeric character
set #str1=substring(#str1,2,len(#str1)-1)
insert into #t(num,nonnum)values (#int1,#str2)
set #int1=''
set #str2=''
end
select #result=#result+replicate(nonnum,num) from #t
select #result
Output:
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,CCCCCCCCCCCCCCCCCCCCCLLNNNNNCCCCCCCC
Edit 1: if you have characters with no number in front of it inside the given string and you want to print it once you can add an extra while loop in above code:
--table variable for holding extracted numbers and none number characters
declare #t table(id int identity(1,1), num int, nonnum char(1))
declare #str1 varchar(50)='32,21C2L5NC' -- your current given string
declare #int1 varchar(50)='' --for number
declare #str2 varchar(50)='' --for none numeric characters
declare #result varchar(max)=''
while len(#str1)>1 --for parsing the given string
begin
while (Select PatIndex('%[0-9]%', #str1))=1 --extract number
begin
set #int1=#int1+substring(#str1,1,1)
set #str1=substring(#str1,2,len(#str1)-1)
end
set #str2=substring(#str1,1,1) --extract none numeric character
set #str1=substring(#str1,2,len(#str1)-1)
insert into #t(num,nonnum)values (#int1,#str2)
set #int1=''
set #str2=''
while (isnumeric(substring(#str1,1,1))=0 and len(#str1)>=1)
begin
set #str2=substring(#str1,1,1) --extract none numeric character
set #str1=substring(#str1,2,len(#str1)-1)
insert into #t(num,nonnum)values (1,#str2)
set #int1=''
set #str2=''
end
end
select #result=#result+replicate(nonnum,num) from #t
select #result
Output:
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,CCCCCCCCCCCCCCCCCCCCCLLNNNNNC
Edit 2: if you want the number of repeats of each character, just query the #t table variable in above code, I mean at the end of above query say:
select nonnum [char],num [repeat] from #t
Output:
char repeat
, 32
C 21
L 2
N 5
C 1
You could do this by using a Pattern Splitter. Here is one taken from Dwain Camp's article. The function used, PatternSplitCM, is created by Chris Morris.
CREATE FUNCTION [dbo].[PatternSplitCM]
(
#List VARCHAR(8000) = NULL
,#Pattern VARCHAR(50)
) RETURNS TABLE WITH SCHEMABINDING
AS
RETURN
WITH numbers AS (
SELECT TOP(ISNULL(DATALENGTH(#List), 0))
n = ROW_NUMBER() OVER(ORDER BY (SELECT NULL))
FROM
(VALUES (0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) d (n),
(VALUES (0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) e (n),
(VALUES (0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) f (n),
(VALUES (0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) g (n))
SELECT
ItemNumber = ROW_NUMBER() OVER(ORDER BY MIN(n)),
Item = SUBSTRING(#List,MIN(n),1+MAX(n)-MIN(n)),
[Matched]
FROM (
SELECT n, y.[Matched], Grouper = n - ROW_NUMBER() OVER(ORDER BY y.[Matched],n)
FROM numbers
CROSS APPLY (
SELECT [Matched] = CASE WHEN SUBSTRING(#List,n,1) LIKE #Pattern THEN 1 ELSE 0 END
) y
) d
GROUP BY [Matched], Grouper
Using the function above, you would want to split your string using the pattern '[^0-9]', which means not numeric. You would then pivot the result so that the corresponding number and character will be on the same row. After that, you use REPLICATE to generate the strings and concatenate them at then end.
Your final query is:
DECLARE
#String VARCHAR(8000),
#Pattern VARCHAR(50),
#Result VARCHAR(MAX)
SELECT
#String = '32,21C2L5N8C',
#Pattern = '[^0-9]',
#Result = ''
;WITH Cte AS(
SELECT
ID = (s.ItemNumber + 1)/ 2,
Number = MAX(CASE WHEN s.ItemNumber % 2 = 1 THEN s.Item END),
Character = MAX(CASE WHEN s.ItemNumber % 2 = 0 THEN s.Item END)
FROM dbo.[PatternSplitCM](#String, #Pattern) s
GROUP BY (s.ItemNumber + 1)/ 2
)
SELECT #Result = #Result + REPLICATE(Character, Number) FROM Cte ORDER BY ID
SELECT #Result
SQL Fiddle
Here is the step by step explanation:
First, split the given string using the pattern '[^0-9]'.
SELECT * FROM dbo.[PatternSplitCM](#String, #Pattern) s
The result is:
ItemNumber Item Matched
-------------------- ---------- -----------
1 32 0
2 , 1
3 21 0
4 C 1
5 2 0
6 L 1
7 5 0
8 N 1
9 8 0
10 C 1
Second, pivot the result so that the corresponding number and character will be on the same row:
SELECT
ID = (s.ItemNumber + 1)/ 2,
Number = MAX(CASE WHEN s.ItemNumber % 2 = 1 THEN s.Item END),
Character = MAX(CASE WHEN s.ItemNumber % 2 = 0 THEN s.Item END)
FROM dbo.[PatternSplitCM](#String, #Pattern) s
GROUP BY (s.ItemNumber + 1)/ 2
The result is:
ID Number Character
------ ---------- ----------
1 32 ,
2 21 C
3 2 L
4 5 N
5 8 C
Last, use REPLICATE(Number, Character) to generate each string and concatenate them to get the final result:
SELECT #Result = #Result + REPLICATE(Character, Number) FROM Cte ORDER BY ID
SELECT #Result
The result is:
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,CCCCCCCCCCCCCCCCCCCCCLLNNNNNCCCCCCCC
I have a stored procedure where input is a comma separated string say '12341,34567,12446,12997' and it is not sure that the input string always carries numerical data. It may be '12341,34as67,12$46,1we97' so I need to validate them and use only the valid data in query.
Say my query is (Where the column OrderCode is int type)
select * from dbo.DataCollector where OrderCode in (12341,34567,12446,12997)
or only the valid data if other are invalid
select * from dbo.DataCollector where OrderCode in (12341)
For such situation what would be a good solution.
One way that works also in SQl-Server 2005 would be to create a split-function, then you can use ISNUMERIC to check if it's a number:
DECLARE #Input VARCHAR(MAX) = '12341,34as67,12$46,1we97'
SELECT i.Item FROM dbo.Split(#Input, ',')i
WHERE IsNumeric(i.Item) = 1
Demo
Your complete query:
select * from dbo.DataCollector
where OrderCode in ( SELECT i.Item FROM dbo.Split(#Input, ',')i
WHERE IsNumeric(i.Item) = 1 )
Here is the split-function which i use:
CREATE FUNCTION [dbo].[Split]
(
#ItemList NVARCHAR(MAX),
#delimiter CHAR(1)
)
RETURNS #ItemTable TABLE (Item VARCHAR(250))
AS
BEGIN
DECLARE #tempItemList NVARCHAR(MAX)
SET #tempItemList = #ItemList
DECLARE #i INT
DECLARE #Item NVARCHAR(4000)
SET #i = CHARINDEX(#delimiter, #tempItemList)
WHILE (LEN(#tempItemList) > 0)
BEGIN
IF #i = 0
SET #Item = #tempItemList
ELSE
SET #Item = LEFT(#tempItemList, #i - 1)
INSERT INTO #ItemTable(Item) VALUES(#Item)
IF #i = 0
SET #tempItemList = ''
ELSE
SET #tempItemList = RIGHT(#tempItemList, LEN(#tempItemList) - #i)
SET #i = CHARINDEX(#delimiter, #tempItemList)
END
RETURN
END
Edit according to the comment of Damien that ISNUMERIC has it's issues. You can use this function to check if it's a real integer:
CREATE FUNCTION dbo.IsInteger(#Value VarChar(18))
RETURNS Bit
AS
BEGIN
RETURN IsNull(
(Select Case When CharIndex('.', #Value) > 0
Then Case When Convert(int, ParseName(#Value, 1)) <> 0
Then 0
Else 1
End
Else 1
End
Where IsNumeric(#Value + 'e0') = 1), 0)
END
Here is another example with damien's "bad" input which contains £ and 0d0:
Demo
I have the following table with each row having comma-separated values:
ID
-----------------------------------------------------------------------------
10031,10042
10064,10023,10060,10065,10003,10011,10009,10012,10027,10004,10037,10039
10009
20011,10027,10032,10063,10023,10033,20060,10012,10020,10031,10011,20036,10041
I need to get a count for each ID (a groupby).
I am just trying to avoid cursor implementation and stumped on how to do this without cursors.
Any Help would be appreciated !
You will want to use a split function:
create FUNCTION [dbo].[Split](#String varchar(MAX), #Delimiter char(1))
returns #temptable TABLE (items varchar(MAX))
as
begin
declare #idx int
declare #slice varchar(8000)
select #idx = 1
if len(#String)<1 or #String is null return
while #idx!= 0
begin
set #idx = charindex(#Delimiter,#String)
if #idx!=0
set #slice = left(#String,#idx - 1)
else
set #slice = #String
if(len(#slice)>0)
insert into #temptable(Items) values(#slice)
set #String = right(#String,len(#String) - #idx)
if len(#String) = 0 break
end
return
end;
And then you can query the data in the following manner:
select items, count(items)
from table1 t1
cross apply dbo.split(t1.id, ',')
group by items
See SQL Fiddle With Demo
Well, the solution i always use, and probably there might be a better way, is to use a function that will split everything. No use for cursors, just a while loop.
if OBJECT_ID('splitValueByDelimiter') is not null
begin
drop function splitValueByDelimiter
end
go
create function splitValueByDelimiter (
#inputValue varchar(max)
, #delimiter varchar(1)
)
returns #results table (value varchar(max))
as
begin
declare #delimeterIndex int
, #tempValue varchar(max)
set #delimeterIndex = 1
while #delimeterIndex > 0 and len(isnull(#inputValue, '')) > 0
begin
set #delimeterIndex = charindex(#delimiter, #inputValue)
if #delimeterIndex > 0
set #tempValue = left(#inputValue, #delimeterIndex - 1)
else
set #tempValue = #inputValue
if(len(#tempValue)>0)
begin
insert
into #results
select #tempValue
end
set #inputValue = right(#inputValue, len(#inputValue) - #delimeterIndex)
end
return
end
After that you can call the output like this :
if object_id('test') is not null
begin
drop table test
end
go
create table test (
Id varchar(max)
)
insert
into test
select '10031,10042'
union all select '10064,10023,10060,10065,10003,10011,10009,10012,10027,10004,10037,10039'
union all select '10009'
union all select '20011,10027,10032,10063,10023,10033,20060,10012,10020,10031,10011,20036,10041'
select value
from test
cross apply splitValueByDelimiter(Id, ',')
Hope it helps, although i am still looping through everything
After reiterating the comment above about NOT putting multiple values into a single column (Use a separate child table with one value per row!),
Nevertheless, one possible approach: use a UDF to convert delimited string to a table. Once all the values have been converted to tables, combine all the tables into one table and do a group By on that table.
Create Function dbo.ParseTextString (#S Text, #delim VarChar(5))
Returns #tOut Table
(ValNum Integer Identity Primary Key,
sVal VarChar(8000))
As
Begin
Declare #dlLen TinyInt -- Length of delimiter
Declare #wind VarChar(8000) -- Will Contain Window into text string
Declare #winLen Integer -- Length of Window
Declare #isLastWin TinyInt -- Boolean to indicate processing Last Window
Declare #wPos Integer -- Start Position of Window within Text String
Declare #roVal VarChar(8000)-- String Data to insert into output Table
Declare #BtchSiz Integer -- Maximum Size of Window
Set #BtchSiz = 7900 -- (Reset to smaller values to test routine)
Declare #dlPos Integer -- Position within Window of next Delimiter
Declare #Strt Integer -- Start Position of each data value within Window
-- -------------------------------------------------------------------------
-- ---------------------------
If #delim is Null Set #delim = '|'
If DataLength(#S) = 0 Or
Substring(#S, 1, #BtchSiz) = #delim Return
-- --------------------------------------------
Select #dlLen = DataLength(#delim),
#Strt = 1, #wPos = 1,
#wind = Substring(#S, 1, #BtchSiz)
Select #winLen = DataLength(#wind),
#isLastWin = Case When DataLength(#wind) = #BtchSiz
Then 0 Else 1 End,
#dlPos = CharIndex(#delim, #wind, #Strt)
-- --------------------------------------------
While #Strt <= #winLen
Begin
If #dlPos = 0 Begin -- No More delimiters in window
If #isLastWin = 1 Set #dlPos = #winLen + 1
Else Begin
Set #wPos = #wPos + #Strt - 1
Set #wind = Substring(#S, #wPos, #BtchSiz)
-- ----------------------------------------
Select #winLen = DataLength(#wind), #Strt = 1,
#isLastWin = Case When DataLength(#wind) = #BtchSiz
Then 0 Else 1 End,
#dlPos = CharIndex(#delim, #wind, 1)
If #dlPos = 0 Set #dlPos = #winLen + 1
End
End
-- -------------------------------
Insert #tOut (sVal)
Select LTrim(Substring(#wind,
#Strt, #dlPos - #Strt))
-- -------------------------------
-- Move #Strt to char after last delimiter
Set #Strt = #dlPos + #dlLen
Set #dlPos = CharIndex(#delim, #wind, #Strt)
End
Return
End
Then write, (using your table schema),
Declare #AllVals VarChar(8000)
Select #AllVals = Coalesce(#allVals + ',', '') + ID
From Table Where ID Is Not null
-- -----------------------------------------
Select sVal, Count(*)
From dbo.ParseTextString(#AllVals, ',')
Group By sval