Split a string into individual characters in Sql Server 2005 - sql-server-2005

Hi I have an input as
ID data
1 hello
2 sql
The desired output being
ID RowID Chars
1 1 H
1 2 e
1 3 l
1 4 l
1 5 o
2 1 s
2 2 q
2 3 l
My approach so far being
Declare #t table(ID INT IDENTITY , data varchar(max))
Insert into #t Select 'hello' union all select 'sql'
--Select * from #t
;With CteMaxlen As(
Select MaxLength = max(len(data)) from #t)
, Num_Cte AS
(
SELECT 1 AS rn
UNION ALL
SELECT rn +1 AS rn
FROM Num_Cte
WHERE rn <(select MaxLength from CteMaxlen)
)
-- Shred into individual characters
, Get_Individual_Chars_Cte AS
(
SELECT
ID
,Row_ID =ROW_NUMBER() Over(PARTITION by ID Order by ID)
,chars
FROM #t,Num_Cte
CROSS APPLY( SELECT SUBSTRING((select data from #t),rn,1) AS chars) SplittedChars
)
Select * from Get_Individual_Chars_Cte
The query is not working at all with an exception being
Msg 512, Level 16, State 1, Line 4
Subquery returned more than 1 value.
This is not permitted when the
subquery follows =, !=, <, <= , >, >=
or when the subquery is used as an
expression.
Edit :
I found my answer
;with Get_Individual_Chars_Cte AS
(
SELECT
ID,
Row_ID =ROW_NUMBER() Over(PARTITION by ID Order by ID)
,SUBSTRING(Data,Number,1) AS [Char]--,
FROM #t
INNER JOIN master.dbo.spt_values ON
Number BETWEEN 1 AND LEN(Data)
AND type='P'
)
Select * from Get_Individual_Chars_Cte
Help needed

;with cte as
(
select ID,
substring(data, 1, 1) as Chars,
stuff(data, 1, 1, '') as data,
1 as RowID
from #t
union all
select ID,
substring(data, 1, 1) as Chars,
stuff(data, 1, 1, '') as data,
RowID + 1 as RowID
from cte
where len(data) > 0
)
select ID, RowID, Chars
from cte
order by ID, RowID

Old post but it's worth posting a purely set-based solution. Using NGrams8K you can do this:
Declare #t table(ID INT IDENTITY , data varchar(max))
Insert into #t Select 'hello' union all select 'sql';
SELECT ID, Row_ID = position, [char] = token
FROM #t
CROSS APPLY dbo.NGrams8k(data,1);
Returns:
ID Row_ID char
--- ------- --------
1 1 h
1 2 e
1 3 l
1 4 l
1 5 o
2 1 s
2 2 q
2 3 l

Related

SQL Grouping by first digit from sets of record

I need your help in SQL
I have a set of records of Cost center ID below.
what I want to do is to segregate/group them by inserting column to distinguish the category.
as you can see all digits start in 7 is belong to the bold digits.
my expected out is on below image also.
You can as the below:
DECLARE #Tbl TABLE (ID INT)
INSERT INTO #Tbl
VALUES
(735121201),
(735120001),
(5442244),
(735141094),
(735141097),
(4008060),
(735117603),
(40100000),
(735142902),
(735151199),
(4010070)
;WITH TableWithRowId
AS
(
SELECT
ROW_NUMBER() OVER (ORDER BY(SELECT NULL)) RowId,
ID
FROM
#Tbl
), TempTable
AS
(
SELECT T.RowId + 1 AS RowId FROM TableWithRowId T
WHERE
LEFT(T.ID, 1) != 7
), ResultTable
AS
(
SELECT
T.RowId ,
T.ID,
DENSE_RANK() OVER (ORDER BY (SELECT TOP 1 A.RowId FROM TempTable A WHERE A.RowId > T.RowId ORDER BY A.RowId)) AS Flag
FROM TableWithRowId T
)
SELECT * FROM ResultTable
Result:
RowId ID Flag
----------- ----------- ----------
1 735121201 1
2 735120001 1
3 5442244 1
4 735141094 2
5 735141097 2
6 4008060 2
7 735117603 3
8 40100000 3
9 735142902 4
10 735151199 4
11 4010070 4
The following query is similer with NEER's
;WITH test_table(CenterID)AS(
SELECT '735121201' UNION ALL
SELECT '735120001' UNION ALL
SELECT '5442244' UNION ALL
SELECT '735141094' UNION ALL
SELECT '735141097' UNION ALL
SELECT '4008060' UNION ALL
SELECT '735117603' UNION ALL
SELECT '40100000' UNION ALL
SELECT '735142902' UNION ALL
SELECT '735151199' UNION ALL
SELECT '4010070'
),t1 AS (
SELECT *,ROW_NUMBER()OVER(ORDER BY(SELECT 1)) AS rn,CASE WHEN LEFT(t.CenterID,1)='7' THEN 1 ELSE 0 END AS isSeven
FROM test_table AS t
),t2 AS(
SELECT t1.*,ROW_NUMBER()OVER(ORDER BY t1.rn) AS toFilter
FROM t1 LEFT JOIN t1 AS pt ON pt.rn=t1.rn-1
WHERE pt.CenterID IS NULL OR (t1.isSeven=1 AND pt.isSeven=0)
)
SELECT t1.CenterID,x.toFilter FROM t1
CROSS APPLY(SELECT TOP 1 t2.toFilter FROM t2 WHERE t2.rn<=t1.rn ORDER BY rn desc) x
CenterID toFilter
--------- --------------------
735121201 1
735120001 1
5442244 1
735141094 2
735141097 2
4008060 2
735117603 3
40100000 3
735142902 4
735151199 4
4010070 4

Recursive cte to repeat several integers

I'd like a column of numbers:
Seven occurances of the integer 1, followed by 7 occurances of 2, followed by 7 occurances of 3 .... , followed by 7 occurances of n-1, followed by 7 occurances of n. Like so
Num
1
1
1
1
1
1
1
2
2
2
2
2
2
2
...
...
n-1
n-1
n-1
n-1
n-1
n-1
n-1
n
n
n
n
n
n
n
Unfortunately I've not progressed too far. My current attempt is the following, where n=4:
WITH
one AS
(
SELECT num = 1,
cnt = 0
UNION ALL
SELECT num = num,
cnt = cnt + 1
FROM one
WHERE cnt < 7
),
x AS
(
SELECT num,
cnt = 0
FROM one
UNION ALL
SELECT num = num + 1,
cnt = cnt + 1
FROM one
WHERE cnt < 4
)
SELECT *
FROM x
;WITH Numbers AS
(
SELECT n = 1
UNION ALL
SELECT n + 1
FROM Numbers
WHERE n+1 <= 10
),
se7en AS
(
SELECT n = 1
UNION ALL
SELECT n + 1
FROM se7en
WHERE n+1 <= 7
)
SELECT Numbers.n
FROM Numbers CROSS JOIN se7en
with x as
(select 1 as id
union all
select 2 as id
union all
select 3 as id
union all
select 4 as id
union all
select 5 as id
union all
select 6 as id
union all
select 7 as id)
select x1.* from x cross join x x1
The cross join will work in your case.
No need to use recursive CTE for this you can try set based approach solution try something like this. Kind of integer division.
If you have access to master database then use this.
;with cte as
(
SELECT top 1000 [7_seq] = ( ( Row_number()OVER(ORDER BY (SELECT NULL)) - 1 ) / 7 ) + 1
FROM sys.columns
)
select * from cte where [7_seq] <= #n
or use tally table to generate the numbers. I will prefer this solution
DECLARE #n INT = 10;
WITH Tally (num)
AS (
-- 1000 rows
SELECT Row_number()OVER (ORDER BY (SELECT NULL))
FROM (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) a(n)
CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) b(n)
CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) c(n)),
seq
AS (SELECT [7_seq] = ( ( Row_number()
OVER(
ORDER BY (SELECT num)) - 1 ) / 7 ) + 1
FROM Tally)
SELECT [7_seq]
FROM seq
WHERE [7_seq] <= #n
WITH t1 AS (SELECT 0 as num UNION ALL SELECT 0)
,t2 AS (SELECT 0 as num FROM t1 as a CROSS JOIN t1 as b)
,t3 AS (SELECT 0 as num FROM t2 as a CROSS JOIN t2 as b)
,t4 AS (SELECT 0 as num FROM t3 as a CROSS JOIN t3 as b)
,Tally (number)
AS (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 1)) FROM t4)
SELECT t1.number
FROM Tally as t1 cross join Tally as t2
where t2.number <=7
ORDER BY t1.number;
You can do It in following:
DECLARE #num INT = 1,
#sub INT = 0,
#max INT = 10,
#timesToRepeat INT = 7
CREATE TABLE #Temp (num INT)
WHILE (#num < #max + 1)
BEGIN
SET #sub = 0;
WHILE (#sub < #timesToRepeat)
BEGIN
INSERT INTO #Temp
SELECT #num x
SET #sub = #sub +1
END
SET #num = #num +1
END
SELECT * FROM #Temp
DROP TABLE #Temp
Set #max variable to number what you want to reach for now It is 10 so It will return result set like:
1
1
1
1
1
1
1
2
2
2
2
2
2
2
.
.
.
10
10
10
10
10
10
10
DECLARE #MAX INTEGER
SET #MAX = 5;
with cte as
(SELECT 7 as num
UNION ALL
SELECT num-1 as num from cte where num>1
),cte2 AS
(SELECT #MAX as num
UNION ALL
SELECT num-1 as num from cte2 where num>1)
select C2.num from cte C1,cte2 C2 ORDER by C2.num asc
Change the value of #MAX to reflect the value of n
Here is a slightly different way to do it.
select null num into #a
union all
select null
union all
select null
union all
select null
union all
select null
union all
select null
union all
select null
select * into #b from
(select rn = row_number()over (order by (select null)) from sys.objects A cross join sys.objects B) A
where rn <=10
select #b.rn as numbers from #a cross join #b
order by 1

SQL count ids in fields

I have a table contains IDs in field. It looks like:
FieldName
-------------------------
1,8,2,3,4,10,5,9,6,7
-------------------------
1,8
-------------------------
1,8
I need to count these IDs to get result:
ID | Count
---|------
1 | 3
8 | 3
2 | 1
3 | 1
Any ideas?
Thanks!
Try this :
Declare #demo table(FieldName varchar(100))
insert into #demo values('1,8,2,3,4,10,5,9,6,7')
insert into #demo values('1,8')
insert into #demo values('1,8')
select ID, COUNT(id) ID_count from
(SELECT
CAST(Split.a.value('.', 'VARCHAR(100)') AS INT) AS ID
FROM
(
SELECT CAST ('<M>' + REPLACE(FieldName, ',', '</M><M>') + '</M>' AS XML) AS ID
FROM #demo
) AS A CROSS APPLY ID.nodes ('/M') AS Split(a)) q1
group by ID
I like Devart's answer because of the faster execution. Here is a modified earlier answer to suite your need :
Declare #col varchar(200)
SELECT
#col=(
SELECT FieldName + ','
FROM #demo c
FOR XML PATH('')
);
;with demo as(
select cast(substring(#col,1,charindex(',',#col,1)-1) AS INT) cou,charindex(',',#col,1) pos
union all
select cast(substring(#col,pos+1,charindex(',',#col,pos+1)-pos-1)AS INT) cou,charindex(',',#col,pos+1) pos
from demo where pos<LEN(#col))
select cou ID, COUNT(cou) id_count from demo
group by cou
Try this one -
Query:
SET NOCOUNT ON;
DECLARE #temp TABLE (txt VARCHAR(8000))
INSERT INTO #temp (txt)
VALUES ('1,8,2,3,4,10,5,9,6,7'), ('1,8'), ('1,8')
SELECT
t.ID
, [Count] = COUNT(1)
FROM (
SELECT
ID =
SUBSTRING(
t.string
, number + 1
, ABS(CHARINDEX(',', t.string, number + 1) - number - 1)
)
FROM (
SELECT string = (
SELECT ',' + txt
FROM #temp
FOR XML PATH(N''), TYPE, ROOT).value(N'root[1]', N'NVARCHAR(MAX)')
) t
CROSS JOIN [master].dbo.spt_values n
WHERE [type] = 'p'
AND number <= LEN(t.string) - 1
AND SUBSTRING(t.string, number, 1) = ','
) t
GROUP BY t.ID
ORDER BY [Count] DESC
Output:
ID Count
----- -----------
1 3
8 3
9 1
10 1
2 1
3 1
4 1
5 1
6 1
7 1
Query cost:

Problem in counting nulls and then merging them with the existing rows

Input:
ID groupId RowID Data
1 1 1 W
2 1 1 NULL
3 1 1 NULL
4 1 1 Z
5 1 2 NULL
6 1 2 NULL
7 1 2 X
8 1 2 NULL
9 1 3 NULL
10 1 3 NULL
11 1 3 Y
12 1 3 NULL
Expected Output
GroupId NewData
1 2Y1,2X1,W2Z
For every Null there will be a numeric count. That is if there are two nulls then the numeric value will be 2.
The ddl is as under
DECLARE #t TABLE(ID INT IDENTITY(1,1) , GroupId INT, RowID INT, Data VARCHAR(10))
INSERT INTO #t (GroupId, RowID,DATA)
SELECT 1,1,'W' UNION ALL SELECT 1,1,NULL UNION ALL SELECT 1,1,NULL UNION ALL SELECT 1,1,'Z' UNION ALL SELECT 1,2,NULL UNION ALL
SELECT 1,2,NULL UNION ALL SELECT 1,2,'X' UNION ALL SELECT 1,2,NULL UNION ALL SELECT 1,3,NULL UNION ALL SELECT 1,3,NULL UNION ALL
SELECT 1,3,'Y' UNION ALL SELECT 1,3,NULL
select * from #t
My version is as under but not the correct output
;with t as (
select GroupID, id, RowID, convert(varchar(25), case when Data is null then '' else Data end) Val,
case when Data is null then 1 else 0 end NullCount from #t where id = 1
union all
select t.GroupID, a.id,a.RowID, convert(varchar(25), Val +
case when Data is not null or (t.RowID <> a.RowID and NullCount > 0) then ltrim(NullCount) else '' end +
case when t.RowID <> a.RowID then ',' else '' end + isnull(Data, '')),
case when Data is null then NullCount + 1 else 0 end NullCount
from t inner join #t a on t.GroupID = a.GroupID and t.id + 1 = a.id
)
select GroupID, Data = Val + case when NullCount > 0 then ltrim(NullCount) else '' end from t
where id = (select max(id) from #t where GroupID = t.GroupId)
Is yielding the below output
GroupID Data
1 W2Z,2X1,3Y1
Please help me out
Thanks in advance
Kind of messy and most likely can be improved
;With RawData AS
(
select * from #t
)
,Ranked1 as
(
select *, RANK() OVER (PARTITION BY GroupId, RowID ORDER BY ID, GroupId, RowID) R from #t
)
,Ranked2 as
(
select *, R - RANK() OVER (PARTITION BY GroupId, RowID ORDER BY ID, GroupId, RowID) R2 from Ranked1
where Data is null
)
,Ranked3 as
(
select MIN(ID) as MinID, GroupId, RowID, R2, COUNT(*) C2 from Ranked2
group by GroupId, RowID, R2
)
,Ranked4 as
(
select RD.ID, RD.GroupId, RD.RowID, ISNULL(Data, C2) as C3 from RawData RD
left join Ranked3 R3 on RD.ID = R3.MinID and RD.GroupId = R3.GroupId and RD.RowID = R3.RowID
where ISNULL(Data, C2) is not null
)
,Grouped as
(
select GroupId, RowID,
(
select isnull(C3, '') from Ranked4 as R41
where R41.GroupId = R42.GroupId and R41.RowID = R42.RowID
order by GroupId, RowID for xml path('')
) as C4
from Ranked4 as R42
group by GroupId, RowID
)
select GroupId,
stuff((
select ',' + C4 from Grouped as G1
where G1.GroupId = G2.GroupId
order by GroupId for xml path('')
), 1, 1, '')
from Grouped G2
group by GroupId

SQL field = other field minus another row

Table has 2 cols: [nr] and [diff]
diff is empty (so far - need to fill)
nr has numbers:
1
2
45
677
43523452
on the diff column i need to add the differences between pairs
1 | 0
2 | 1
45 | 43
677 | 632
43523452 | 43522775
so basically something like :
update tbl set diff = #nr - #nrold where nr = #nr
but i don't want to use fetch next, because it's not cool, and it's slow (100.000) records
how can I do that with one update?
CREATE TABLE #T(nr INT,diff INT)
INSERT INTO #T (nr) SELECT 1
UNION SELECT 2
UNION SELECT 45
UNION SELECT 677
UNION SELECT 43523452
;WITH cte AS
(
SELECT nr,diff, ROW_NUMBER() OVER (ORDER BY nr) RN
FROM #T
)
UPDATE c1
SET diff = ISNULL(c1.nr - c2.nr,0)
FROM cte c1
LEFT OUTER JOIN cte c2 ON c2.RN+1= c1.RN
SELECT nr,diff FROM #T
DROP TABLE #T
Have a look at something like this (full example)
DECLARE #Table TABLE(
nr INT,
diff INT
)
INSERT INTO #Table (nr) SELECT 1 UNION ALL
SELECT 2 UNION ALL
SELECT 45 UNION ALL
SELECT 677 UNION ALL
SELECT 43523452
;WITH Vals AS (
SELECT nr,
diff,
ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) xID
FROM #Table
)
UPDATE c
SET diff = c.nr - ISNULL(p.nr, 0)
FROM Vals c LEFT JOIN
Vals p ON c.xID = p.xID + 1
SELECT *
FROM #Table
try this -
update tablename
set diff = cast(nr as INT) - cast((select nr from tablename where diff is not null and nr = a.nr) as INT)
from tablename a
where diff is null
This is assuming you only have one older row for nr old in the table. else the subquery will return more than one value