split string in tsql from column - sql

I've got a column in my table with text (string) like 19.5 or 7.14 but as well with just 19 or 11.
I want to split the text in 2 columns 1 with all the text before the "." and 1 with all the text after the "." .
For the entries without a . in the string, the table must have 00 in the second column. Also all data after the . has to be 2 chars long(padded with 0).
e.g. 19.5 must give column1: 19 column2: 50 ; 11 must give column1: 11 column2: 00
Can anybody help me with the correct tsql-code?

Please try:
select
YourCol,
SUBSTRING(YourCol, 0, CHARINDEX('.', YourCol+'.')) Col1,
LEFT(SUBSTRING(YourCol, CHARINDEX('.', YourCol+'.')+1, 2)+'00', 2) Col2
from YourTable
or
select
YourCol,
CAST(YourCol AS INT) Col1,
RIGHT(PARSENAME(YourCol,1), 2) Col2
from
(
select
CONVERT(NUMERIC(18,2), YourCol) YourCol
from YourTable
)x
Sample:
declare #tbl as table(txt nvarchar(10))
insert into #tbl values ('19.5'), ('11'), ('7.14')
select
txt,
SUBSTRING(txt, 0, CHARINDEX('.', txt+'.')) Col1,
LEFT(SUBSTRING(txt, CHARINDEX('.', txt+'.')+1, 2)+'00', 2) Col2
from #tbl
or
select
txt,
CAST(txt AS INT) Col1,
RIGHT(PARSENAME(txt,1), 2) Col2
from
(
select
CONVERT(NUMERIC(18,2), txt) txt
from #tbl
)x

Try this,
Declare #InputList VARCHAR(8000), #Delimiter VARCHAR(8000);
Declare #1stvalue nvarchar(20), #2ndValue Int;
Set #InputList = '19.5';
Set #Delimiter = '.';
set #1stvalue = (select RTRIM(LTRIM(SUBSTRING(#InputList,1,CHARINDEX(#Delimiter,#InputList,0)-1))));
set #2ndValue = (Select RTRIM(LTRIM(SUBSTRING(#InputList,CHARINDEX(#Delimiter,#InputList,0)+LEN(#Delimiter),LEN(#InputList)))) )
set #2ndValue = (select case when #2ndValue < 10 then convert (varchar(10) ,convert (varchar(10),#2ndValue) + '0') else convert (varchar(10), #2ndValue) end);
SELECT #1stvalue AS Column1, #2ndValue AS Column2;
You can use this as function..

select cast(cast(number as float) as int) column1,
right(cast(number as numeric(9,2)), 2) column2
from
(VALUES ('19.5'),('7.14'),('19'),('11') ) t(number)

Related

How to force SQL to treat " - - " as a single delimiter?

I need to delimit #uid by "-". The issue is my data set has "--1" and I need it be treated as "-1"
I need #uid = '1585-1586--1-5417-2347-8865' to output this:
Instead of:
How can I achieve this in SQL?
The answer you have helps you a little here, however, with no definition of [dbo].[fnSplit] doesn't help any one else.
If we can assume that the data is well defined (has 6 columns), then we could "spam" some CHARINDEX functions to do this. You will, as shown in the answer, need to replace all the delimiters and then reinsert the value of - for the double delimiter:
DECLARE #UID varchar(30) = '1585-1586--1-5417-2347-8865';
DECLARE #Delimiter char(1) = '-';
SELECT SUBSTRING(ca.FixedUID,1,C1.I-1) AS Col1,
SUBSTRING(ca.FixedUID,C1.I+1, C2.I-C1.I-1) AS Col2,
SUBSTRING(ca.FixedUID,C2.I+1, C3.I-C2.I-1) AS Col3,
SUBSTRING(ca.FixedUID,C3.I+1, C4.I-C3.I-1) AS Col4,
SUBSTRING(ca.FixedUID,C4.I+1, C5.I-C4.I-1) AS Col5,
SUBSTRING(ca.FixedUID,C5.I+1, LEN(ca.FixedUID)-C5.I) AS Col6
FROM (VALUES(#UID))V([UID])
CROSS APPLY (VALUES(REPLACE(REPLACE(V.UID,#Delimiter,'|'),'||','|' + #Delimiter)))ca(FixedUID)
CROSS APPLY (VALUES(CHARINDEX('|',ca.FixedUID)))C1(I)
CROSS APPLY (VALUES(CHARINDEX('|',ca.FixedUID,C1.I+1)))C2(I)
CROSS APPLY (VALUES(CHARINDEX('|',ca.FixedUID,C2.I+1)))C3(I)
CROSS APPLY (VALUES(CHARINDEX('|',ca.FixedUID,C3.I+1)))C4(I)
CROSS APPLY (VALUES(CHARINDEX('|',ca.FixedUID,C4.I+1)))C5(I);
Of course, if you had a "empty" value, then this will fail:
DECLARE #UID varchar(30) = '1585--71-5417-2347-8865';
DECLARE #Delimiter char(1) = '-';
SELECT SUBSTRING(ca.FixedUID,1,C1.I-1) AS Col1,
SUBSTRING(ca.FixedUID,C1.I+1, C2.I-C1.I-1) AS Col2,
SUBSTRING(ca.FixedUID,C2.I+1, C3.I-C2.I-1) AS Col3,
SUBSTRING(ca.FixedUID,C3.I+1, C4.I-C3.I-1) AS Col4,
SUBSTRING(ca.FixedUID,C4.I+1, C5.I-C4.I-1) AS Col5,
SUBSTRING(ca.FixedUID,C5.I+1, LEN(ca.FixedUID)-C5.I) AS Col6
FROM (VALUES(#UID))V([UID])
CROSS APPLY (VALUES(REPLACE(REPLACE(V.UID,#Delimiter,'|'),'||','|' + #Delimiter)))ca(FixedUID)
CROSS APPLY (VALUES(CHARINDEX('|',ca.FixedUID)))C1(I)
CROSS APPLY (VALUES(CHARINDEX('|',ca.FixedUID,C1.I+1)))C2(I)
CROSS APPLY (VALUES(CHARINDEX('|',ca.FixedUID,C2.I+1)))C3(I)
CROSS APPLY (VALUES(CHARINDEX('|',ca.FixedUID,C3.I+1)))C4(I)
CROSS APPLY (VALUES(CHARINDEX('|',ca.FixedUID,C4.I+1)))C5(I);
Invalid length parameter passed to the LEFT or SUBSTRING function.
And hence why a delimiter than can appear in your data should never be used (however, one can hope that as these all appear to be integer values then a NULL value wouldn't exist and there would be a 0 instead: '1585-0-71-5417-2347-8865').
If you used a string splitter like DelimitedSpluit8K_LEAD then you could Pivot (and unpivot) the data fine, but the values would be in the wrong positions with the above example:
SELECT MAX(CASE DS.ItemNumber WHEN 1 THEN DS.Item END) AS Col1,
MAX(CASE DS.ItemNumber WHEN 2 THEN DS.Item END) AS Col2,
MAX(CASE DS.ItemNumber WHEN 3 THEN DS.Item END) AS Col3,
MAX(CASE DS.ItemNumber WHEN 4 THEN DS.Item END) AS Col4,
MAX(CASE DS.ItemNumber WHEN 5 THEN DS.Item END) AS Col5,
MAX(CASE DS.ItemNumber WHEN 6 THEN DS.Item END) AS Col6
FROM (VALUES(#UID))V([UID])
CROSS APPLY (VALUES(REPLACE(REPLACE(V.UID,#Delimiter,'|'),'||','|' + #Delimiter)))ca(FixedUID)
CROSS APPLY dbo.DelimitedSplit8K_LEAD(ca.FixedUID,'|') DS;
Which will result in the below:
Col1 Col2 Col3 Col4 Col5 Col6
---- ---- ---- ---- ---- ----
1585 -71 5417 2347 8865 NULL
Basically what I'm doing is a recursive cte from 6 to 1. each iteration I am removing the last delimited number and moving it to col_val column.
I decided to use reverse so that I could then use patindex to find the hyphen then the number. Doing that made it possible to get the negative values. In reverse the string looks like 1--6851-5851-0 then patindex('%-[0-9]%', <string>) returns 2 and because I used right function of the string 0-1585-1586--1 it will return -1
I added '0-' to the beginning of the delim_column because I want to use patindex without having to account for the last delimited column.
The column col_val is repeating all the above but instead of using #uid it is using delim_column
Here is what each iteration looks like:
col_num delim_column col_val loc
6 0-1585-1586--1-5417-2347 8865 4
5 0-1585-1586--1-5417 2347 4
4 0-1585-1586--1 5417 4
3 0-1585-1586 -1 2
2 0-1585 1586 4
1 0 1585 4
Then I'm pivoting the columns using a simple choose function. That will make the column names clean.
DECLARE
#uid VARCHAR(MAX) = '1585-1586--156-5417-2347-8865',
#delim_count INT = 0
--First, count the number of delimiters. We do this by temporarily replacing '--' with a single '-'
--and then count the difference in lengths of the two strings (one with '-' and one without)
SELECT #delim_count = LEN(REPLACE(#uid, '--', '-')) - LEN(REPLACE(REPLACE(#uid, '--', '-'), '-','')) - IIF(#uid LIKE '-%', 1, 0)
--next a recursive cte that will lop off the last number each iteration and move the last value to col_val
;WITH fnsplit(col_num, delim_column, col_val, loc)
AS
(
SELECT
#delim_count+1 --start with 6 and then go to 1. remove the +1 and replace col_num > 0 for a zero index
,'0-'+SUBSTRING(#uid,0, LEN(#uid) - LEN(RIGHT(#uid, PATINDEX('%-[0-9]%', reverse(#uid)) - 1)) )
,RIGHT(#uid, PATINDEX('%-[0-9]%', REVERSE(#uid)) - 1)
,PATINDEX('%-[0-9]%', REVERSE(#uid)) - 1
UNION ALL
SELECT
col_num - 1
,SUBSTRING(delim_column,0, LEN(delim_column) - LEN(RIGHT(delim_column, PATINDEX('%-[0-9]%', REVERSE(delim_column)) - 1)) )
,RIGHT(delim_column, PATINDEX('%-[0-9]%', REVERSE(delim_column)) - 1)
,PATINDEX('%-[0-9]%', REVERSE(delim_column)) - 1
FROM
fnsplit
WHERE
col_num > 1
)
--select * from fnsplit -- uncomment here and comment all below to see the recursion
SELECT
*
FROM
(
SELECT
column_name
,col_val
FROM
fnsplit
CROSS APPLY
(SELECT CHOOSE(col_num, 'Col_A','Col_B','Col_C', 'Col_D', 'Col_E', 'Col_F')) tbl(column_name)
)PVT
PIVOT
(
MAX(col_val)
FOR column_name IN ([Col_A], [Col_B], [Col_C], [Col_D], [Col_E], [Col_F])
) PVT1
The desired result can be achieve with the script below. The script relies on a User Defined Function called [fnSplit]. The [fnSplit] UDF is defined later in the post.
declare #uid nvarchar(100)
set #uid = '1585-1586--1-5417-2347-8865'
select
(select data from [dbo].[fnSplit] (REPLACE ((REPLACE (#uid, '-', '|')),'||', '|-'), '|') where id = 1) as [Col_A],
(select data from [dbo].[fnSplit] (REPLACE ((REPLACE (#uid, '-', '|')),'||', '|-'), '|') where id = 2) as [Col_B],
(select data from [dbo].[fnSplit] (REPLACE ((REPLACE (#uid, '-', '|')),'||', '|-'), '|') where id = 3) as [Col_C],
(select data from [dbo].[fnSplit] (REPLACE ((REPLACE (#uid, '-', '|')),'||', '|-'), '|') where id = 4) as [Col_D],
(select data from [dbo].[fnSplit] (REPLACE ((REPLACE (#uid, '-', '|')),'||', '|-'), '|') where id = 5) as [Col_E],
(select data from [dbo].[fnSplit] (REPLACE ((REPLACE (#uid, '-', '|')),'||', '|-'), '|') where id = 6) as [Col_F]
CREATE FUNCTION [dbo].[fnSplit]
(
#Line nvarchar(MAX),
#SplitOn nvarchar(5) = ','
)
RETURNS #RtnValue table
(
Id INT NOT NULL IDENTITY(1,1) PRIMARY KEY CLUSTERED,
Data nvarchar(1000) NOT NULL
)
AS
BEGIN
IF #Line IS NULL RETURN
DECLARE #split_on_len INT = LEN(#SplitOn)
DECLARE #start_at INT = 1
DECLARE #end_at INT
DECLARE #data_len INT
WHILE 1=1
BEGIN
SET #end_at = CHARINDEX(#SplitOn,#Line,#start_at)
SET #data_len = CASE #end_at WHEN 0 THEN LEN(#Line) ELSE #end_at-#start_at END
INSERT INTO #RtnValue (data) VALUES( SUBSTRING(#Line,#start_at,#data_len) );
IF #end_at = 0 BREAK;
SET #start_at = #end_at + #split_on_len
END
RETURN
END

How to separate a string and insert into table?

My question is that I have a string like this
Red,House|White,Car|Blue,Table
and I want insert this elements in different rows like this
- Col1 Col2
- -----------
- Red House
- White Car
- Blue Table
How can I do it?
maybe this is what are you looking for.
SELECT Substring(value, 1,Charindex(',', value)-1) as col1
, Substring(value, Charindex(',', value)+1, LEN(value)) as col2
FROM STRING_SPLIT('Red,House|White,Car|Blue,Table', '|')
works since SQL Server 2016
You can try this query.
DECLARE #str VARCHAR(500) = 'Red,House|White,Car|Blue,Table'
CREATE TABLE #Temp (tDay VARCHAR(100))
WHILE LEN(#str) > 0
BEGIN
DECLARE #TDay VARCHAR(100)
IF CHARINDEX('|',#str) > 0
SET #TDay = SUBSTRING(#str,0,CHARINDEX('|',#str))
ELSE
BEGIN
SET #TDay = #str
SET #str = ''
END
INSERT INTO #Temp VALUES (#TDay)
SET #str = REPLACE(#str,#TDay + '|' , '')
END
SELECT *
FROM #temp
SELECT tday,
PARSENAME(REPLACE(tday,',','.'),2) 'Col1' ,
PARSENAME(REPLACE(tday,',','.'),1) 'Col2'
FROM #temp
You can check the live demo Here.
I go with using string_split() or a similar string splitter function which you can add to your database. However, I would phrase the final extract logic as:
select left(s.value, v.split - 1),
stuff(s.value, 1, v.split, '')
from string_split('Red,House|White,Car|Blue,Table', '|') s cross apply
(values (charindex(',', s.value))) v(split);

Split string into words in columns

I am looking to split a string into words in columns in SQL Server 2014. I have found a few solutions but all of them are giving the results in rows. How can I break the below string into columns?
"First Second Third Fourth Fifth"
You can use XML and grab the elements by their position:
DECLARE #YourString VARCHAR(100)='First Second Third Fourth Fifth';
WITH StringAsXML AS
(
SELECT CAST('<x>' + REPLACE((SELECT #YourString AS [*] FOR XML PATH('')),' ','</x><x>') + '</x>' AS XML) TheXml
)
SELECT TheXml.value('x[1]/text()[1]','nvarchar(max)') AS FirstElement
,TheXml.value('x[2]/text()[1]','nvarchar(max)') AS SecondElement
,TheXml.value('x[3]/text()[1]','nvarchar(max)') AS ThirdElement
,TheXml.value('x[4]/text()[1]','nvarchar(max)') AS FourthElement
,TheXml.value('x[5]/text()[1]','nvarchar(max)') AS FifthElement
FROM StringAsXML;
Remark
You can use PIVOT, conditional aggregation, FROM(VALUES()) or the above. but any of these approaches will need a known set of columns (a known count of elements or at least a maximum count of elements).
If you cannot rely on such a knowledge, you can use dynamically created SQL. This would mean to create one of the working statements on string base and use EXEC for a dynamic execution.
UPDATE: A dynamic approach
This approach will deal with a variable number of elements
DECLARE #YourString VARCHAR(100)='First Second Third Fourth Fifth';
DECLARE #Delimiter CHAR(1)=' ';
DECLARE #countElements INT = LEN(#YourString)-LEN(REPLACE(#YourString,#Delimiter,''));
DECLARE #Statement VARCHAR(MAX)=
'WITH StringAsXML AS
(
SELECT CAST(''<x>'' + REPLACE((SELECT ''ReplaceYourString'' AS [*] FOR XML PATH('''')),'' '',''</x><x>'') + ''</x>'' AS XML) TheXml
)
SELECT ReplaceColumnList
FROM StringAsXML;';
DECLARE #columnList VARCHAR(MAX);
WITH cte AS
(
SELECT 1 AS ElementCounter
,CAST('TheXml.value(''x[1]/text()[1]'',''nvarchar(max)'') AS Element_01' AS VARCHAR(MAX)) AS ColStatement
UNION ALL
SELECT cte.ElementCounter+1
,cte.ColStatement + CAST(',TheXml.value(''x[' + CAST(cte.ElementCounter+1 AS VARCHAR(10)) + ']/text()[1]'',''nvarchar(max)'') AS Element_' + REPLACE(STR(cte.ElementCounter + 1,2),' ','0') AS VARCHAR(MAX))
FROM cte
WHERE cte.ElementCounter <= #countElements
)
SELECT #columnList=(SELECT TOP 1 cte.ColStatement FROM cte ORDER BY cte.ElementCounter DESC)
--replace the string you want to split
SET #Statement = REPLACE(#Statement,'ReplaceYourString',#YourString);
--replace the columnList
SET #Statement = REPLACE(#Statement,'ReplaceColumnList',#columnList);
EXEC(#Statement);
UPDATE 2: The smallest fully inlined and position-safe splitter I know of
Try this out:
DECLARE #inp VARCHAR(200) = 'First Second Third Fourth Fifth';
DECLARE #dlmt VARCHAR(100)=' ';
;WITH
a AS (SELECT n=0, i=-1, j=0 UNION ALL SELECT n+1, j, CHARINDEX(#dlmt, #inp, j+1) FROM a WHERE j > i),
b AS (SELECT n, SUBSTRING(#inp, i+1, IIF(j>0, j, LEN(#inp)+1)-i-1) s FROM a WHERE i >= 0)
SELECT * FROM b;
And just to get it complete: The above tiny splitter combined with PIVOT:
;WITH
a AS (SELECT n=0, i=-1, j=0 UNION ALL SELECT n+1, j, CHARINDEX(#dlmt, #inp, j+1) FROM a WHERE j > i),
b AS (SELECT n, SUBSTRING(#inp, i+1, IIF(j>0, j, LEN(#inp)+1)-i-1) s FROM a WHERE i >= 0)
SELECT p.*
FROM b
PIVOT(MAX(s) FOR n IN([1],[2],[3],[4],[5])) p;
You can use a SQL split string function to seperate the string into words and using the order of the word in the original string, you can use CASE statements like a PIVOT query and display as columns
Here is a sample
declare #string varchar(max) = 'First Second Third Fourth Fifth'
;with cte as (
select
case when id = 1 then val end as Col1,
case when id = 2 then val end as Col2,
case when id = 3 then val end as Col3,
case when id = 4 then val end as Col4,
case when id = 5 then val end as Col5
from dbo.split( #string,' ')
)
select
max(Col1) as Col1,
max(Col2) as Col2,
max(Col3) as Col3,
max(Col4) as Col4,
max(Col5) as Col5
from cte
If you cannot create a UDF, you can use the logic in your SQL code as follows
Please note that if you have your data in a database table column, you can simply replace column content in the first SQL CTE expression
declare #string varchar(max) = 'First Second Third Fourth Fifth'
;with cte1 as (
select convert(xml, N'<root><r>' + replace(#string,' ','</r><r>') + '</r></root>') as rawdata
), cte2 as (
select
ROW_NUMBER() over (order by getdate()) as id,
r.value('.','varchar(max)') as val
from cte1
cross apply rawdata.nodes('//root/r') as records(r)
)
select
max(Col1) as Col1,
max(Col2) as Col2,
max(Col3) as Col3,
max(Col4) as Col4,
max(Col5) as Col5
from (
select
case when id = 1 then val end as Col1,
case when id = 2 then val end as Col2,
case when id = 3 then val end as Col3,
case when id = 4 then val end as Col4,
case when id = 5 then val end as Col5
from cte2
) t
You may use parsename function as :
create table tab ( str varchar(100));
insert into tab values('First Second Third Fourth Fifth');
with t as
(
select replace(str,' ','.') as str
from tab
)
Select substring(str,1,charindex('.',str)-1) as col_first,
parsename(substring(str,charindex('.',str)+1,len(str)),4) as col_second,
parsename(substring(str,charindex('.',str)+1,len(str)),3) as col_third,
parsename(substring(str,charindex('.',str)+1,len(str)),2) as col_fourth,
parsename(substring(str,charindex('.',str)+1,len(str)),1) as col_fifth
from t;
col_first col_second col_third col_fourth col_fifth
--------- ---------- --------- ---------- ---------
First Second Third Fourth Fifth
P.S. firstly, need to split the main string into the parts with at most 3 three dot(.) character(otherwise the function doesn't work). It's a restriction for parsename.
Rextester Demo

How to achieve the following resultset using SQL

EID PID Metric Limit1 Limit2 Limit3
1 8 20 < 210 <
1 8 22 > 89 >=
I have the following requirement
Source
To be transformed to
EID PID 20-Limit1 20-Limit2 20-Limit3 22-Limit1 22-Limit2 22-Limit3
1 8 < 210 < > 89 >=
Can you please help in this.
Please try with this code
Create table #TempTable (EID INT,PID INT,Col1 VARCHAR(100),Col2 VARCHAR(100))
;WITH TestingCTE AS(
Select EID,PID,
Cast(Metric as varchar(50))+'-'+ColumnName AS Col1,
Columnvalue AS Col2,
Cast(Metric as varchar(50)) +'-'+ ColumnName1 AS Col3,
Columnvalue1 AS col4,
Cast(Metric as varchar(50)) +'-'+ ColumnName2 AS col5,
Columnvalue2 AS col6
from
(
select EID,PID,Metric,Limit1,Limit2,Limit3 from Testing
) src
unpivot
(
Columnvalue for ColumnName in (Limit1)
) un
unpivot
(
Columnvalue1 for ColumnName1 in (Limit2)
) un
unpivot
(
Columnvalue2 for ColumnName2 in (Limit3)
) un
)
INsert into #TempTable (EID,PID,Col1,Col2)
Select EID,PID,Col1,Col2 from TestingCTE
UNION ALL
Select EID,PID,Col3,Cast(Col4 as Varchar(100)) from TestingCTE
UNION ALL
Select EID,PID,Col5,Col6 from TestingCTE
Declare #var NVARCHAR(MAX)
Declare #query NVARCHAR(MAX)
Select #var= Stuff((SELECT ', ' + QUOTENAME(col1) from #TempTable
For XML PATH ('')),1,1,'')
SET #query='Select * from (Select * from #TempTable) a
PIVOT(MAX(col2) for col1 IN('+#var+'))Pivoted'
EXEC sp_executesql #query
Drop table #TempTable
Simple solution to get the required output would be as below:
create table E_Limit (EID int, PID int, Metric int, Limit1 varchar(10), Limit2 varchar(10), Limit3 varchar(10));
go
insert into E_LIMIT values(1, 8 , 20 , '<' , '210' , '<');
insert into E_LIMIT values(1, 8 , 22 , '>' , '89' , '>=');
Go
Select * From
(SELECT EID, PID, CONVERT(VARCHAR,METRIC) + '-'+ LIMIT_RANGE METRIC_LIMIT_RANGE,LIMIT
FROM
(SELECT EID, PID, METRIC, LIMIT1, LIMIT2, LIMIT3
FROM E_LIMIT) UP
UNPIVOT
(LIMIT FOR LIMIT_RANGE IN
(LIMIT1, LIMIT2, LIMIT3)
)AS unpvt) P
Pivot (MAX(LIMIT) FOR METRIC_LIMIT_RANGE IN
(
[20-LIMIT1]
,[20-LIMIT2]
,[20-LIMIT3]
,[22-LIMIT1]
,[22-LIMIT2]
,[22-LIMIT3]
)) PVT
In case you need more LIMIT columns or Metric values, you can try using information schema along with METRIC column values and generate dynamic pivot query to get the required result-set.
Write to me if you would like to have sample code for dynamic script to generate the required results.

SQL Query for Min and Max Values [duplicate]

This question already has answers here:
How to split a comma-separated value to columns
(38 answers)
Closed 8 years ago.
I have the following data in a table. The number of values in each row can vary and the number of rows could also vary.
The table has 1 column with csv formatted values. The values will always be numeric
Data
1,2
4
5,12, 10
6,7,8,9,10
15,17
I would like to end up with a temp table with the following
Data Lowest Highest
1,2 1 2
4 4 4
5,12, 10 5 12
6,7,8,9,10 6 10
15,17 15 17
Can anyone help with writing a sql query or function to achieve this
Instead of function, you can achieve by this
;WITH tmp
AS (SELECT A.rn,split.a.value('.', 'VARCHAR(100)') AS String
FROM (SELECT Row_number() OVER(ORDER BY (SELECT NULL)) AS RN,
Cast ('<M>' + Replace([data], ',', '</M><M>') + '</M>' AS XML) AS String
FROM table1) AS A
CROSS apply string.nodes ('/M') AS Split(a))
SELECT X.data,Tmp.lower,Tmp.higher
FROM (SELECT rn,Min(Cast(string AS INT)) AS Lower,Max(Cast(string AS INT)) AS Higher
FROM tmp
GROUP BY rn) Tmp
JOIN (SELECT Row_number() OVER(ORDER BY (SELECT NULL)) AS RN1,data
FROM table1) X
ON X.rn1 = Tmp.rn
FIDDLE DEMO
Output would be:
Data Lower Higher
1,2 1 2
4 4 4
5,12, 10 5 12
6,7,8,9,10 6 10
15,17 15 17
First create a user defined function to convert each row of 'DATA' column to a intermediate table as:
/****** Object: UserDefinedFunction [dbo].[CSVToTable]******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE FUNCTION [dbo].[CSVToTable] (#InStr VARCHAR(MAX))
RETURNS #TempTab TABLE
(id int not null)
AS
BEGIN
;-- Ensure input ends with comma
SET #InStr = REPLACE(#InStr + ',', ',,', ',')
DECLARE #SP INT
DECLARE #VALUE VARCHAR(1000)
WHILE PATINDEX('%,%', #INSTR ) <> 0
BEGIN
SELECT #SP = PATINDEX('%,%',#INSTR)
SELECT #VALUE = LEFT(#INSTR , #SP - 1)
SELECT #INSTR = STUFF(#INSTR, 1, #SP, '')
INSERT INTO #TempTab(id) VALUES (#VALUE)
END
RETURN
END
GO
Function is explained further here.
Then Using Cross Apply we can get the desired output as:
With CTE as
(
select
T.Data, Min(udf.Id) as [Lowest],Max(udf.Id) as [Highest]
from
Test T
CROSS APPLY dbo.CSVToTable(T.Data) udf
Group By Data
)
Select * from CTE
Sample Code here...
What a Cross Apply does is : it applies the right table expression to each row from the left table and produces a result table with the unified result sets.
Create table #temp1 (name varchar(100),value int )
Declare #len int
Select #len=(select max(LEN(name)-LEN(replace(name,',',''))) from table)
Declare #i int = 1
while (#i<=#len+1)
begin
insert into #temp1
select name,PARSENAME(REPLACE(name,',','.'),#i) from table t
set #i = #i+1
end
Select name,MIN(value) MINV,MAX(value) MAXV from #temp1 group by name
declare #Testdata table ( Data varchar(max))
insert #Testdata select '1,2'
insert #Testdata select '4'
insert #Testdata select '5,12, 10'
insert #Testdata select '6,7,8,9,10'
;with tmp( DataItem, Data, RN1) as (
select LEFT(Data, CHARINDEX(',',Data+',')-1),
STUFF(Data, 1, CHARINDEX(',',Data+','), ''),
ROW_NUMBER()OVER(ORDER BY (SELECT NULL))AS RN1
from #Testdata
union all
select LEFT(Data, CHARINDEX(',',Data+',')-1),
STUFF(Data, 1, CHARINDEX(',',Data+','), ''),RN1
from tmp
where Data > ''
)
Select x.data,t.Low,t.Up FROM
(Select RN1,MIN(Cast(DataItem AS INT)) As Low,
MAX(Cast(DataItem AS INT)) As Up
FROM tmp t GROUP BY t.RN1)t
JOIN (Select ROW_NUMBER()OVER(ORDER BY (SELECT NULL))AS RN,data from #Testdata)X
ON X.RN = t.RN1