I have one table with only one column. That one column contains strings with comma-delimited values. I am trying to select each string from the table > split the string by the comma's > and then insert the results into a different table.
My first table look like this:
DataString
abc,def,gh,i,jkl
mnop,qr,stu,v,wxyz
I would like the data to look like this:
Value1 Value2 Value3 Value4 Value5
abc def gh i jkl
mnop qr stu v wxyz
This is the code I have so far:
DECLARE #valueList varchar(100)
DECLARE #pos INT
DECLARE #len INT
DECLARE #value varchar(100)
Select #valueList = DataString from [dbo].[rawdata]
set #pos = 0
set #len = 0
WHILE CHARINDEX(',', #valueList, #pos+1)>0
BEGIN
set #len = CHARINDEX(',', #valueList, #pos+1) - #pos
set #value = SUBSTRING(#valueList, #pos, #len)
insert into [dbo].[tempTable] (Value1) Values (#value)
set #pos = CHARINDEX(',', #valueList, #pos+#len) +1
END
I am having two problems with this code.
Problem 1:
- my select statement to pull in the column DataString into the variable #valueList is only pulling the last row in the table instead of selecting all of them.
Problem 2:
- my insert into statement can only insert the values as new rows into the table. I cant figure out how to enter the values into their corresponding columns.
Any help on how to fix either of my problems would be greatly appreciated.
So your first issue is that assigning a variable to a column like that in a table is only going to pull one row from that column in table. If you want to loop through each row in a column like you're currently doing, you would need to use a CURSOR or some variant like that to go through each row.
The second issue you have is that your while statement is incorrect. You're missing the last value from it because you stop when there are no more commas in your datastring. There are no more commas at the last value, so it skips the last value. There's a way around this using your current method, but I would recommend an alternative method of splitting your string.
DECLARE #myTable TABLE (datastring VARCHAR(4000));
INSERT #myTable(datastring)
VALUES ('abc,def,gh,i,jkl'),('mnop,qr,stu,v,wxyz');
DECLARE #valueList VARCHAR(4000) = '', #i INT = 1, #pos INT = 0, #len INT = 0, #value VARCHAR(100) = '';
IF OBJECT_ID('tempTable', 'U') IS NOT NULL DROP TABLE tempTable;
CREATE TABLE tempTable (OriginalDataString VARCHAR(4000), stringVal VARCHAR(255), valNum INT);
DECLARE curs CURSOR FOR
SELECT datastring
FROM #myTable;
OPEN curs;
FETCH NEXT FROM curs INTO #valueList;
WHILE ##FETCH_STATUS = 0 BEGIN
SELECT #pos = 0, #len = 0, #i = 1;
WHILE 1 = 1
BEGIN
SET #len = ISNULL(NULLIF(CHARINDEX(',', #valueList, #pos+1), 0) - #pos, LEN(#valueList));
SET #value = SUBSTRING(#valueList, #pos, #len);
INSERT tempTable(OriginalDataString, stringVal, valNum)
VALUES (#valueList, #value, #i);
IF CHARINDEX(',', #valueList, #pos+1) = 0
BREAK;
SET #pos = CHARINDEX(',', #valueList, #pos+#len) + 1;
SET #i += 1;
END
FETCH NEXT FROM curs INTO #valueList;
END
CLOSE curs;
DEALLOCATE curs;
SELECT MAX(CASE WHEN valNum = 1 THEN stringVal END) val1
, MAX(CASE WHEN valNum = 2 THEN stringVal END) val2
, MAX(CASE WHEN valNum = 3 THEN stringVal END) val3
, MAX(CASE WHEN valNum = 4 THEN stringVal END) val4
, MAX(CASE WHEN valNum = 5 THEN stringVal END) val5
FROM tempTable
GROUP BY OriginalDataString;
This uses a cursor to get each datastring in your table, puts each value in a cursor, loops through them to get the value (the loop breaks when you reach the end of the string) and then selects val1, val2, val3, val4, val5 from the resulting table.
But, rather than using a cursor and a while loop, I would recommend the much simpler use of a recursive CTE (or even better, a split function you already have built in).
For example,
DECLARE #myTable TABLE (datastring VARCHAR(4000));
INSERT #myTable(datastring)
VALUES ('abc,def,gh,i,jkl'),('mnop,qr,stu,v,wxyz');
WITH CTE AS (
SELECT datastring
, SUBSTRING(datastring, 1, ISNULL(NULLIF(CHARINDEX(',', datastring), 0) - 1, LEN(datastring))) sString
, NULLIF(CHARINDEX(',', datastring), 0) cIndex
, 1 Lvl
FROM #myTable T
UNION ALL
SELECT datastring
, SUBSTRING(datastring, cIndex + 1, ISNULL(NULLIF(CHARINDEX(',', datastring, cIndex + 1), 0) - 1 - cIndex, LEN(datastring)))
, NULLIF(CHARINDEX(',', datastring, cIndex + 1), 0)
, Lvl + 1
FROM CTE
WHERE cIndex IS NOT NULL)
SELECT MAX(CASE WHEN Lvl = 1 THEN sString END) val1
, MAX(CASE WHEN Lvl = 2 THEN sString END) val2
, MAX(CASE WHEN Lvl = 3 THEN sString END) val3
, MAX(CASE WHEN Lvl = 4 THEN sString END) val4
, MAX(CASE WHEN Lvl = 5 THEN sString END) val5
--, datastring OriginalDataString
FROM CTE
GROUP BY datastring;
Declare #YourTable table (DataString varchar(250))
Insert Into #YourTable values
('abc,def,gh,i,jkl'),
('mnop,qr,stu,v,wxyz')
Select A.*
,Value1=B.Pos1
,Value2=B.Pos2
,Value3=B.Pos3
,Value4=B.Pos4
,Value5=B.Pos5
From #YourTable A
Cross Apply (Select * from [dbo].[udf-Str-Parse-Row](A.DataString,',')) B
Returns
DataString Value1 Value2 Value3 Value4 Value5
abc,def,gh,i,jkl abc def gh i jkl
mnop,qr,stu,v,wxyz mnop qr stu v wxyz
Then UDF if needed
CREATE FUNCTION [dbo].[udf-Str-Parse-Row] (#String varchar(max),#Delimeter varchar(10))
--Usage: Select * from [dbo].[udf-Str-Parse-Row]('Dog,Cat,House,Car',',')
-- Select * from [dbo].[udf-Str-Parse-Row]('John Cappelletti',' ')
-- Select * from [dbo].[udf-Str-Parse-Row]('id26,id46|id658,id967','|')
Returns Table
As
Return (
SELECT Pos1 = xDim.value('/x[1]','varchar(250)')
,Pos2 = xDim.value('/x[2]','varchar(250)')
,Pos3 = xDim.value('/x[3]','varchar(250)')
,Pos4 = xDim.value('/x[4]','varchar(250)')
,Pos5 = xDim.value('/x[5]','varchar(250)')
,Pos6 = xDim.value('/x[6]','varchar(250)')
,Pos7 = xDim.value('/x[7]','varchar(250)')
,Pos8 = xDim.value('/x[8]','varchar(250)')
,Pos9 = xDim.value('/x[9]','varchar(250)')
FROM (Select Cast('<x>' + Replace(#String,#Delimeter,'</x><x>')+'</x>' as XML) as xDim) A
)
Related
column of data in sqlserver like
numbers
1000-1050, 1054, 1090-1230, 1245
numbers
-------
1000-1050, 1054, 1090-1230, 1245
how to get the style like below:
numbers
-------
1000
1001
1002
1003
1004
...
1050
1054
1090
1091
1092
...
1245
You could use a split function and APPLY like this
DECLARE #SampleData AS TABLE
(
numbers varchar(200)
)
INSERT INTO #SampleData
VALUES ('1000-1050, 1054, 1090-1230, 1245')
;WITH temp AS
(
SELECT 1 AS Number
UNION ALL
SELECT t.Number + 1 FROM temp t
WHERE t.Number < 5000
) -- return table from 1 --> 5000
SELECT DISTINCT
ca2.*
FROM #SampleData sd
CROSS APPLY
(
SELECT pos, LTRIM(Value) AS Value
FROM dbo.SplitString(sd.Numbers,',')
) ca1
CROSS APPLY
(
SELECT *
FROM temp t WHERE t.Number BETWEEN LEFT(ca1.[Value], charindex('-', ca1.[Value] + '-') - 1) AND
CASE
WHEN len(ca1.[Value]) < charindex('-', ca1.[Value] + '-') THEN ca1.[Value]
ELSE RIGHT(ca1.[Value], len(ca1.[Value]) - charindex('-', ca1.[Value] + '-'))
END
) ca2
OPTION (MAXRECURSION 0)
Split function
CREATE FUNCTION [dbo].[SplitString] (#Text varchar(max),#Delimiter varchar(10))
Returns Table
As
Return (
Select Pos = Row_Number() over (Order By (Select null))
,Value = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>'+ Replace(#Text,#Delimiter,'</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
);
Demo link: http://rextester.com/GIPGR78132
First split the comma seperated values, then get the values using a recursive common table expression.
Declare #values nvarchar(max) = '1000-1050, 1054, 1090-1230, 1245'
;with ranges_cte as
(
select cast(case pos when 0 then ResultValue else left(ResultValue,pos-1) end as int) first, cast(case pos when 0 then ResultValue else substring(ResultValue,pos+1,len(ResultValue)-pos) end as int) Last
from (
select ResultValue, charINDEx('-',ResultValue) pos
from dbo.SplitString(#values,',')) x
)
, values_rte as
(
select first,last,first as active
from ranges_cte
union all
select first,last,active +1 as active
from values_rte
where active< last
)
select *
from values_rte
OPTION (MAXRECURSION 0)
Function to split the values:
Create FUNCTION [dbo].[SplitString] (#StringArray NVARCHAR(MAX), #Delimiter NVARCHAR(10))
RETURNS #ResultedValues table
(
nr int,
ResultValue nvarchar(max)
)
AS
--select * from dbo.splitstring ('123,456,789',',')
BEGIN
declare #string nvarchar(max)
declare #nr int = 1
set #string = #StringArray
WHILE (CHARINDEX(#Delimiter,#String)>0)
BEGIN
INSERT INTO #ResultedValues (nr,ResultValue) VALUES (#nr,LTRIM(RTRIM(SUBSTRING(#String,1,CHARINDEX(#Delimiter,#String)-1))))
SET #String = SUBSTRING(#String, CHARINDEX(#Delimiter,#String)+LEN(#Delimiter),LEN(#String))
set #nr = #nr +1
END
INSERT INTO #ResultedValues (nr,ResultValue ) VALUES ( #nr, LTRIM(RTRIM(#String)))
RETURN
END
Create a split function:
CREATE FUNCTION [dbo].[SplitIDsTest]
(
#idList varchar(4000)
)
RETURNS #parsedList TABLE
(
Id varchar(50),
Nr int
)
AS
BEGIN
DECLARE #id varchar(10), #pos int
DECLARE #nr int = 0;
SET #idList = LTRIM(RTRIM(#idList)) + ','
SET #Pos = CHARINDEX(',', #idList)
IF REPLACE(#idList, ',', '') <> ''
BEGIN
WHILE #Pos > 0
BEGIN
SET #id = LTRIM(RTRIM(LEFT(#idList, #pos - 1)))
IF #id <> ''
BEGIN
set #nr += 1;
INSERT INTO #ParsedList (Id, Nr)
VALUES (#id, #nr);
END
SET #idList = RIGHT(#idList, LEN(#idList) - #pos) -- 'WMPC,' (inklusive Komma) links vom Eingabeparameter abschneiden, weil jetzt der nächste Wert gesucht wird
SET #pos = CHARINDEX(',', #idList, 1) -- Nächste Position eines Kommas suchen und in der WHILE-Schleife weitermachen
END
END
RETURN
END
Then a tally table:
select top 1000000 N=identity(int, 1, 1)
into dbo.Tally
from master.dbo.syscolumns a cross join master.dbo.syscolumns b;
Then use:
select distinct Tally.N
from SplitIDsTest('10-12, 34, 9') splitted
join Tally on 1 = case
when CHARINDEX('-', splitted.Id) > 0 then
case
when Tally.N between cast(left(splitted.Id, CHARINDEX('-', splitted.Id) - 1) as int)
and cast(right(splitted.Id, len(splitted.Id) - CHARINDEX('-', splitted.Id)) as int) then 1
else 0
end
when Tally.N = cast(splitted.Id as int) then 1
else 0
end
order by Tally.N
I wanted to use UNION ALL on 3 different tables, merging them into one table by using SELECT INTO.
Table 1 ,2 and 3 has 15, 7 and 8 columns respectively.
So, was there a way for me to use UNION ALL and have table 2 and 3 default to a NULL for the missing columns, without individually classifying them as such?
For instance, I've been doing:
SELECT NULL as [Company_Code], NULL as [Doc_Code],
NULL as [Doc_Type], [H ID] as [Document_No] FROM [table_2]
INTO BIG_TABLE
UNION ALL
SELECT
[Document Company] as [Company_Code], [Document Company] as [Doc_Code],
[Doc Type] as [Doc_Type], NULL as [Document_No]
FROM [table_3]
In this way, the number of columns match up, and I can UNION them.
However, I was wondering if there was a way to avoid the tedious mechanism to avoid inserting NULL for each column that was missing, and have that done automatically in one go ?
Thanks.
In short, no. Unioning result sets together must have the same number / data type of columns. If you wanted to have the remaining sets populate null, the simplest way to do this would be to do something like so-
select col1
, col2
, col3
, col4
from tbl1
union all
select null as col1
, null as col2
, null as col3
, null as col4
from tbl2
Union should be inside another select
SELECT * FROM(
SELECT col1, col2 FROM test_table1
UNION ALL
SELECT col1, col2,col3 FROM test_table2
);
Result will be col1 and col2 the un matched columns are skipped
The best possible way to nullify the columns which were missing is to use null as column_name for which you want to see the null columns as like below :...
SELECT id as var1, mgr_id as var2,name as var3 into exp_test1 FROM
emp123 UNION ALL
SELECT null as employeeid, null as departmentid,null as lastname FROM employee
I still suggest to go with #iliketocode.
Dynamic Columns Generate as per your requirement
This is just another option, but purely dynamic as you want (only its need some time to execute rather than above answer). To achieve the result without define each column, you can create dynamic query which automatically add column as null via loop.
The same issue I faced and due to short time, I was done as above. But today I fill to give the proper solution (which I not done at that time) and I created a sample for you as you want, I hope this will help you.
--create table t1 (col1 int , col2 int, col3 int)
--create table t2 (col1 int , col2 int, col3 int, col4 int)
--insert into t1 values (1,11,111), (2,22,222)
--insert into t2 values (1,11,111,1111), (2,22,222,null)
--Step 1 - Declaration of variable
Declare #NoOfColumnForUnion int = 5
declare #T1TableColumnList nvarchar(max) ='' ,#T2TableColumnList nvarchar(max) ='' , #colName nvarchar(500) , #test cursor
--Step 2 - Get the column list of first table i.e. t1 and store into #T1TableColumnList variable
set #test = cursor for
select name from syscolumns
where id = object_id('t1')
open #test
fetch next from #test into #colName
while ##fetch_status = 0
begin
set #T1TableColumnList = #T1TableColumnList + #colName + ','
fetch next from #test into #colName
end
set #T1TableColumnList = left( #T1TableColumnList , len(#T1TableColumnList )-1)
close #test
deallocate #test
--Step 3 - Get the column list of Second table i.e. t2 and store into #T2TableColumnList variable
set #test = cursor for
select name from syscolumns
where id = object_id('t2')
open #test
fetch next from #test into #colName
while ##fetch_status = 0
begin
set #T2TableColumnList = #T2TableColumnList + #colName + ','
fetch next from #test into #colName
end
set #T2TableColumnList = left( #T2TableColumnList , len(#T2TableColumnList )-1)
close #test
deallocate #test
--Step 4 - Check the length of column list to add null columns or remove columns
--First table check
Declare #T1lengthofColumnList int
set #T1lengthofColumnList = (len(#T1TableColumnList) - len(replace(#T1TableColumnList, ',', '')) ) + 1
--add columns
if( #T1lengthofColumnList < #NoOfColumnForUnion)
Begin
While (#T1lengthofColumnList < #NoOfColumnForUnion)
Begin
set #T1lengthofColumnList = #T1lengthofColumnList + 1
Set #T1TableColumnList = #T1TableColumnList + ', null col' + cast( #T1lengthofColumnList as varchar(10))
End
End
--remove columns
Else if( #T1lengthofColumnList > #NoOfColumnForUnion)
Begin
While (#T1lengthofColumnList > #NoOfColumnForUnion)
Begin
set #T1lengthofColumnList = #T1lengthofColumnList - 1
Set #T1TableColumnList = LEFT(#T1TableColumnList, LEN(#T1TableColumnList) - CHARINDEX(',',REVERSE(#T1TableColumnList)))
End
End
--Second table check
Declare #T2lengthofColumnList int
set #T2lengthofColumnList = (len(#T2TableColumnList) - len(replace(#T2TableColumnList, ',', '')) ) + 1
--add columns
if( #T2lengthofColumnList < #NoOfColumnForUnion)
Begin
While (#T2lengthofColumnList < #NoOfColumnForUnion)
Begin
set #T2lengthofColumnList = #T2lengthofColumnList + 1
Set #T2TableColumnList = #T2TableColumnList + ', null col' + cast( #T2lengthofColumnList as varchar(10))
End
End
--remove columns
Else if( #T2lengthofColumnList > #NoOfColumnForUnion)
Begin
While (#T2lengthofColumnList > #NoOfColumnForUnion)
Begin
set #T2lengthofColumnList = #T2lengthofColumnList - 1
Set #T2TableColumnList = LEFT(#T2TableColumnList, LEN(#T2TableColumnList) - CHARINDEX(',',REVERSE(#T2TableColumnList)))
End
End
--Step 5 - create dynamic query and execute
DECLARE #template AS varchar(max)
SET #template = 'select ' + #T1TableColumnList + ' from t1 union all '
+ ' select ' + #T2TableColumnList + ' from t2 '
select #template
EXEC (#template)
--drop table t1
--drop table t2
I have a table with more than 150 columns. is there any way to dynamically count columns greater than 0 for each customer.
table view is like :
CustomerID (SomeColumns) Column1 Column2 ------------------ Column150
1 ----- 0 12 0 33 0 18 97
2 ----- 1 0 54 0 72 0 0
.
.
.
This table has 500K rows. values of column1 to column150 are either 0 or not. how can I count the number of columns greater than 0 ?
Query:
Update Table
set NumOfColumnsGreaterThanZero = (select Sum(case when Column1 to Column150 >0 then 1 else 0 end)
You can create a dynamic SQL based on sys.columns, something like:
declare #columns varchar(8000), #sql varchar(8000)
set #columns = ''
select #columns = #columns + 'case when [' + name + '] > 0 then 1 else 0 end+'
from sys.columns
where
object_id = object_id('TABLENAME') and
name not in ('not','wanted','columns') and
user_type_id in (select user_type_id from sys.types where name = 'int')
set #sql = 'select CustomerId, ' + #columns + '0 as VALUE from TABLENAME'
exec (#sql)
There is of course a risk with this approach that adding new columns to the table might cause unwanted results.
I doubt you have a valid reason for having 150 columns. However here is how you could count the values that are not 0 using Pivot:
DECLARE #table TABLE(customer_id int, col1 int, col2 int,
col3 int, col4 int, col5 int);
INSERT INTO #table
VALUES(1, 1, 2, 3, 4, 5) ,(2, 0, 2, 0, 4, 5)
SELECT count(CASE WHEN columns <> 0 THEN 1 END), customer_id
FROM #table as p
UNPIVOT
(columns FOR Seq IN
([col1], [col2], [col3], [col4], [col5]) ) AS unpvt
GROUP BY customer_id
Result:
5 1
3 2
If you want to select the columns dynamically:
CREATE TABLE
test_table(customer_id int, col1 int, col2 int,
col3 int, col4 int, col5 int);
INSERT INTO test_table
VALUES(1, 1, 2, 3, 4, 5) ,(2, 0, 2, 0, 4, 5) ;
DECLARE #columnnames varchar(max)
SELECT #columnnames = coalesce(#columnnames + ',['+ column_name + ']' , '['+ column_name + ']' )
FROM INFORMATION_SCHEMA.Columns
WHERE
table_name = 'test_table' and
column_name like 'col[0-9]%' and
table_schema = 'dbo'
ORDER BY column_name
DECLARE #sql varchar(max) =
'SELECT count(CASE WHEN columns <> 0 THEN 1 END), customer_id
FROM test_table as p
UNPIVOT
(columns FOR Seq IN
('+#columnnames+') ) AS unpvt
GROUP BY customer_id'
EXEC (#sql)
This is a basic example using 3 columns on a temporary table. You could adapt that for your structure using Dynamic SQL.
Sample data:
CREATE TABLE #Customers
(
CustomerID INT
, SomeColumn VARCHAR(100)
, Column1 INT
, Column2 INT
, Column3 INT
);
INSERT INTO #Customers
(CustomerID, SomeColumn, Column1, Column2, Column3)
VALUES
(1, 'aaa', 1, 0, 2)
, (2, 'bbb', 0, 0, 3)
, (3, 'ccc', 0, 0, 0)
Actual query:
SELECT CustomerID, SomeColumn, IIF(Column1 > 0, 1, 0) + IIF(Column2 > 0, 1, 0) + IIF(Column3 > 0, 1, 0) AS T
FROM #Customers
Results look like this:
CustomerID SomeColumn T
1 aaa 2
2 bbb 1
3 ccc 0
to avoid i misunderstand your question, i try to create sample table and data, basically what i understand from your questions is, each of the columns are integer type, whenever the record is more then 0 then set to 1, else 0; this is good if you able to provide sample data and expected output. :)
CREATE TABLE tblTEST
(
COLUMN1 INT,
COLUMN2 INT,
COLUMN3 INT,
COLUMN4 INT,
COLUMN5 INT
)
INSERT INTO tblTEST
SELECT 1,0,5,12,6
UNION ALL
SELECT 1,10,0,12,6
UNION ALL
SELECT 1,30,5,0,6
DECLARE #ColumnName NVARCHAR(MAX) = ''
DECLARE #Table_Name NVARCHAR(1000) = 'TBLTEST'
DECLARE #Query NVARCHAR(MAX) = ''
DECLARE #nStart INT = 1
DECLARE #nLast INt = (SELECT COUNT(1) FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = #Table_Name )
WHILE #nStart <=#nLast
BEGIN
SET #ColumnName = #ColumnName + ' CASE WHEN '+ (SELECT COLUMN_NAME FROM(SELECT COLUMN_NAME,ROW_NUMBER() OVER(ORDER BY COLUMN_NAME) RN FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = #Table_Name) T1 WHERE RN=#nStart) + ' >0 THEN 1 ELSE 0 END '+ (SELECT COLUMN_NAME FROM(SELECT COLUMN_NAME,ROW_NUMBER() OVER(ORDER BY COLUMN_NAME) RN FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = #Table_Name) T1 WHERE RN=#nStart) + ','
SET #nStart = #nStart + 1
END
SET #ColumnName = SUBSTRING(#ColumnName,1,LEN(#ColumnName)-1)
SET #Query = 'SELECT ' + #ColumnName + ' FROM ' + #Table_Name
EXECUTE SP_EXECUTESQL #Query
Another option is to use dynamic sql and while cycle
Sample data
-- Populate some sample data
IF OBJECT_ID('tempdb..#T','U') IS NOT NULL DROP TABLE #T;
CREATE TABLE #T
(Column1 INT, Column2 INT, Column3 INT, Column4 INT, Column5 INT);
INSERT INTO #T VALUES
(0,0,0,1,0),
(0,1,0,1,0),
(1,0,0,2,0),
(1,0,0,0,1);
Dynamic SQL with while cycle
DECLARE #ResultTable TABLE (HasZeroValue TINYINT);
-- Number of columns to search for zero values
DECLARE #ColumnsCount INT = 5;
-- Dynamic sql statement
DECLARE #SQL NVARCHAR(MAX);
DECLARE #i INT = 1;
WHILE #i <= #ColumnsCount
BEGIN
SET #SQL = 'SELECT CASE COUNT(*) WHEN 0 THEN 0 ELSE 1 END FROM #T WHERE Column' + CAST(#i AS VARCHAR) + ' > 0;';
INSERT #ResultTable
EXEC sp_executesql #SQL;
SET #i = #i + 1;
END
SELECT SUM(HasZeroValue) FROM #ResultTable;
I have the next table, how can I get substring before and after dot(.) special character?
MyTable
------------------------------
Id Description
------------------------------
1 [Hugo].[date].[Subtotal]
2 [Juan].[date].[Subtotal]
3 [7/23/2013].[SubTotal]
4 [7/25/2013].[Total]
I am looking for the following result
MyResultTable
------------------------
MyTableId Description depth
-----------------------
1 [Hugo] 1
1 [date] 2
1 [Subtotal] 3
2 [Juan] 1
2 [date] 2
2 [Subtotal] 3
3 [7/23/2013] 1
3 [SubTotal] 2
4 [7/25/2013] 1
4 [Total] 2
I want to separate the words after a dot(.) and list the words as the following table
How can I solve it?
You will want to split the data based on the .. You can use a recursive CTE to split the data and return the depth:
;with cte (id, DescriptionItem, Description, depth) as
(
select id,
cast(left(Description, charindex('.',Description+'.')-1) as varchar(50)) DescriptionItem,
stuff(Description, 1, charindex('.',Description+'.'), '') Description,
1 as depth
from MyTable
union all
select id,
cast(left(Description, charindex('.',Description+'.')-1) as varchar(50)) DescriptionItem,
stuff(Description, 1, charindex('.',Description+'.'), '') Description,
depth+1
from cte
where Description > ''
)
select id, DescriptionItem, depth
from cte
order by id, depth;
See SQL Fiddle with Demo
Or you can use a UDF function that splits the data:
create FUNCTION [dbo].[Split](#String varchar(MAX), #Delimiter char(1))
returns #temptable TABLE (items varchar(MAX), depth int)
as
begin
declare #idx int
declare #slice varchar(8000)
declare #depth int = 1
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, depth) values(#slice, #depth)
set #String = right(#String,len(#String) - #idx)
set #depth = #depth +1
if len(#String) = 0 break
end
return
end;
Then when you call the function, you will use CROSS APPLY similar to this:
select t.id, c.items description,
c.depth
from mytable t
cross apply dbo.split(t.description, '.') c
order by t.id, c.depth;
See SQL Fiddle with Demo
USE tempdb;
GO
IF OBJECT_ID('dbo.csv_split','U') IS NOT NULL DROP TABLE dbo.csv_split;
CREATE TABLE dbo.csv_split
(
Id INT NOT NULL PRIMARY KEY
,Description VARCHAR(100)
)
INSERT INTO dbo.csv_split(Id,Description)
VALUES
(1,'[Hugo].[date].[Subtotal]')
,(2,'[Juan].[date].[Subtotal]')
,(3,'[7/23/2013].[SubTotal]')
,(4,'[7/25/2013].[Total]');
WITH cte_xml AS
(
Select csv.Id
,CONVERT(XML,'<desc>'
+ REPLACE(csv.Description,'.','</desc><desc>')
+ '</desc>') AS xml_desc
From dbo.csv_split csv
)
,cte_shred_xml AS
(
Select t.Id
,xml_desc_nodes.value('(.)','varchar(50)') AS Description
,ROW_NUMBER() OVER(PARTITION BY t.Id ORDER BY t.Id ) AS Depth
From cte_xml t
CROSS APPLY t.xml_desc.nodes('/desc') AS t2(xml_desc_nodes)
)
Select *
From cte_shred_xml
Here is a simple example. I created your table as #test and used both a cursor and a loop within the cursor.
DECLARE #test TABLE ( id INT, NAME VARCHAR(MAX) )
INSERT #test
VALUES ( 1, '[Hugo].[date].[Subtotal]' )
INSERT #test
VALUES ( 2, '[Juan].[date].[Subtotal]' )
INSERT #test
VALUES ( 3, '[7/23/2013].[SubTotal]' )
INSERT #test
VALUES ( 4, '[7/25/2013].[Total]' )
DECLARE #id INT ,
#name VARCHAR(MAX)
DECLARE #values TABLE
(
MyTableId INT ,
Description VARCHAR(MAX) ,
Depth INT
)
DECLARE #v VARCHAR(2000) ,
#i INT ,
#depth INT
DECLARE #MyTableList CURSOR
SET
#MyTableList = CURSOR FOR SELECT id, name FROM #test
OPEN #MyTableList
FETCH NEXT FROM #MyTableList INTO #id, #name
WHILE ##FETCH_STATUS = 0
BEGIN
SET #depth = 1
SET #i = PATINDEX('%.%', #name)
WHILE #i > 0
BEGIN
INSERT #values
VALUES ( #id, SUBSTRING(#name, 1, #i - 1), #depth )
SET #name = SUBSTRING(#name, #i + 1, LEN(#name) - #i)
SET #i = PATINDEX('%.%', #name)
SET #depth = #depth + 1
END
INSERT #values
VALUES ( #id, #name, #depth )
FETCH NEXT FROM #MyTableList INTO #id, #name
END
SELECT MyTableId ,
Description ,
Depth
FROM #values
You output should look like this.
MyTableId Description Depth
1 [Hugo] 1
1 [date] 2
1 [Subtotal] 3
2 [Juan] 1
2 [date] 2
2 [Subtotal] 3
3 [7/23/2013] 1
3 [SubTotal] 2
4 [7/25/2013] 1
4 [Total] 2
I have two different tables as below and have specific requirement of a sql query.
Table1:
Name RuleNumber
Tom 1,2
Pete 1,3
Table2:
RuleNumber Description
1 Rule1
2 Rule2
3 Rule3
How can I get a sql query result something like below
Name Description
Tom Rule1, Rule2
Pete Rule1, Rule3
You will first need a custom split function to seperate the delimited list, and then use FOR XML PATH to combine the descriptions. Here is your final query
select t1.Name,
STUFF(( SELECT ',' + Description
FROM table2 AS t2
WHERE t2.ruleNumber in (select s from dbo.fn_split(t1.RuleNumber, ','))
ORDER BY ruleNumber
FOR XML PATH('')), 1, 1, '') as 'Description'
from table1 t1
Here is the code for the split function.
create function [dbo].[fn_Split]
(
#String varchar(8000) ,
#Delimiter varchar(10)
)
returns #tbl table (s varchar(1000))
as
begin
declare #i int ,
#j int
select #i = 1
while #i <= len(#String)
begin
select #j = charindex(#Delimiter, #String, #i)
if #j = 0
begin
select #j = len(#String) + 1
end
insert #tbl select substring(#String, #i, #j - #i)
select #i = #j + len(#Delimiter)
end
return
end
How the Table1's RuleNumber has more than one value? Is it a string? I will assume it is :
Name / RuleNumber
Tom / 1
Tom / 2
Then, the query would be:
select
Name,
(
select Description
from Table2 as t2
where t1.ruleNumber = t2.ruleNumber
) as Description
from
table1 as t1