SQL count ids in fields - sql

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:

Related

Split comma separated values of a multiple column in row in SQL query

I have data Like this
Code address phno
123 test1,test2,test3 123,456,789
And I want output
Code address phno
123 test1 123
123 test2 456
123 test3 789
My code is this
declare #address VARCHAR(500) = 'test1,test2,test3', #phoneno VARCHAR(500) = '123,456,789'
select *
from STRING_SPLIT(#address, ','), STRING_SPLIT(#phoneno, ',')
but it returns multiple values
You can use recursive CTEs for this:
with cte as (
select code, cast(NULL as varchar(max)) as address, cast(NULL as varchar(max)) as phno,
cast(address + ',' as varchar(max)) as rest_address, cast(phno + ',' as varchar(max)) as rest_phno, 0 as lev
from t
union all
select code, left(rest_address, charindex(',', rest_address) - 1), left(rest_phno, charindex(',', rest_phno) - 1),
stuff(rest_address, 1, charindex(',', rest_address), ''), stuff(rest_phno, 1, charindex(',', rest_phno), ''), lev + 1
from cte
where rest_address <> ''
)
select code, address, phno
from cte
where lev > 0;
Here is a db<>fiddle.
If you can have more than 100 elements in the lists, you'll need option (maxrecursion 0).
Please try the following solution.
SQL
DECLARE #tbl TABLE (Code CHAR(3), address VARCHAR(100), phno VARCHAR(100));
INSERT INTO #tbl (Code, address, phno) VALUES
('123', 'test1,test2,test3', '123,456,789');
;WITH cte1 AS
(
SELECT Code, value AS address
, ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS seq
FROM #tbl
CROSS APPLY STRING_SPLIT(address, ',')
), cte2 AS
(
SELECT Code, value AS phno
, ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS seq
FROM #tbl
CROSS APPLY STRING_SPLIT(phno, ',')
)
SELECT cte1.code, cte1.address, cte2.phno
FROM cte1 INNER JOIN cte2
ON cte2.seq = cte1.seq;
SQL #2, JSON based
;WITH rs AS
(
SELECT *
, ar1 = '["' + Replace(address, ',', '","') + '"]'
, ar2 = '["' + Replace(phno, ',', '","') + '"]'
FROM #tbl
)
SELECT Code, address.[value] AS [address], phno.[value] AS phno
FROM rs
CROSS APPLY OPENJSON (ar1, N'$') AS address
CROSS APPLY OPENJSON (ar2, N'$') AS phno
WHERE address.[key] = phno.[key];
Output
+------+---------+------+
| code | address | phno |
+------+---------+------+
| 123 | test1 | 123 |
| 123 | test2 | 456 |
| 123 | test3 | 789 |
+------+---------+------+

How to get active records for each date in SQL

I have a table like below:
ID FID MDate Active
--------------------------
1 1 2009-05-25 1
1 2 2009-05-25 1
1 1 2010-02-04 0
1 3 2010-02-04 1
1 1 2009-04-01 0
1 1 2009-03-01 1
How to get active FId for each date?
I was trying like below:
SELECT DISTINCT
ID, MDate,
STUFF ((SELECT DISTINCT ',' + CAST(FID AS VARCHAR)
FROM
(SELECT ID, MDate, FID
FROM Table1
WHERE IsActive = 1) t
WHERE t.MDate = a.MDate
FOR XML PATH('')), 1, 1, '') colb
FROM
(SELECT ID, MDate, FID
FROM Table1
WHERE IsActive = 1) a
Somehow, it is giving partial results. In last row of the result of above query FID 3 is getting selected, there should be two FIDs 2, 3 as FID 2 is active since 2009-05-25 to till date.
I want output like below:
ID MDate FID
1 2009-03-01 1
1 2009-05-25 1,2
1 2010-02-04 2,3
How to get this in SQL?
You need to specify names of fields correctly, and no need for that number of sub-query:
;WITH Table1 AS (
SELECT * FROM (VALUES
(1, 1, '2009-05-25', 1),
(1, 2, '2009-05-25', 1),
(1, 1, '2010-02-04', 0),
(1, 3, '2010-02-04', 1),
(1, 1, '2009-04-01', 0),
(1, 1, '2009-03-01', 1))
AS t(ID, FID, ModifyDate, IsActive)
)
SELECT DISTINCT ID,
ModifyDate,
STUFF(( SELECT DISTINCT ',' + CAST(FID AS VARCHAR)
FROM Table1 t
WHERE t.ModifyDate = a.ModifyDate
FOR XML PATH('')),1,1,'') colb
FROM Table1 a
WHERE IsActive=1
Results:
ID ModifyDate colb
----------- ---------- ----------
1 2009-03-01 1
1 2009-05-25 1,2
1 2010-02-04 1,3
(3 row(s) affected)
I would use a CTE to filter out the active row first
; WITH
CTE AS
(
SELECT *
FROM Table1
WHERE IsActive = 1
)
SELECT ID, ModifyDate,
colb = STUFF ( (
SELECT ',' + CAST(FID AS VARCHAR(10))
FROM CTE x
WHERE x.ModifyDate = c.ModifyDate
FOR XML PATH ('')
) , 1, 1, '')
FROM CTE c
GROUP BY ID, ModifyDate
SELECT distinct [ID],[MDate],STUFF((SELECT ',' + CAST(FID AS VARCHAR) FROM [dbo].[tableName] WHERE [MDate] = a.[MDate] and active =1 FOR XML PATH('')),1 ,1 ,'') as FID
FROM [dbo].[tableName] AS a
WHERE active = 1
ORDER BY [MDate]
Sample Output as below:
ID MDate FID
1 2009-03-01 1
1 2009-05-25 1,2
1 2010-02-04 3
I think this is what you want.

select columns from second row add to the end of the first row in sql group by ID

I have a table customer in the database as below.
ID UID Address1 Name code
10 5 A Jac 683501
11 5 B Joe 727272
13 6 C mat 373737
first two records (10,11) have a common uID -5 . These two records can be considered as a single unit.
fourth record is having a separate UID , so it is a separate unit
I need to produce an output in a csv file such a way that
ID UID Name code Address1 Name2 Code2
10 5 jac 683501 A Joe 727272
13 6 mat 373737 C
Name2 and code2 values are from the second row, since UID is same for first two records, we can considered it as a single unit.
Can anyone give hints to query for generating these records.
This process to transform data from rows into columns is known as a PIVOT. There are several ways that this can be done.
You can use a row_number() along with an aggregate function with a CASE expression:
select min(id),
uid,
max(case when seq = 1 then name end) Name,
max(case when seq = 1 then code end) Code,
max(case when seq = 1 then Address1 end) Address1,
max(case when seq = 2 then name end) Name2,
max(case when seq = 2 then code end) code2,
max(case when seq = 2 then Address1 end) Address1_2
from
(
select id, uid, address1, name, code,
row_number() over(partition by uid order by id) seq
from yourtable
) d
group by uid;
See SQL Fiddle with Demo.
You could use both the UNPIVOT and the PIVOT function:
select id, uid,
name1, code1, address1, name2, code2, address2
from
(
select id, uid, col+cast(seq as varchar(10)) col, value
from
(
select
(select min(id)
from yourtable t2
where t.uid = t2.uid) id,
uid,
cast(address1 as varchar(20)) address,
cast(name as varchar(20)) name,
cast(code as varchar(20)) code,
row_number() over(partition by uid order by id) seq
from yourtable t
) d
unpivot
(
value
for col in (address, name, code)
) unpiv
) src
pivot
(
max(value)
for col in (name1, code1, address1, name2, code2, address2)
) piv;
See SQL Fiddle with Demo.
Finally if you have an unknown number of values for each uid, then you can use dynamic SQL to get the result:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(col+(cast(seq as varchar(10))))
from
(
select row_number() over(partition by uid order by id) seq
from yourtable
) d
cross apply
(
select 'name', 1 union all
select 'code', 2 union all
select 'address', 3
) c (col, so)
group by seq, col, so
order by seq, so
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT id, uid,' + #cols + '
from
(
select id, uid, col+cast(seq as varchar(10)) col, value
from
(
select
(select min(id)
from yourtable t2
where t.uid = t2.uid) id,
uid,
cast(address1 as varchar(20)) address,
cast(name as varchar(20)) name,
cast(code as varchar(20)) code,
row_number() over(partition by uid order by id) seq
from yourtable t
) d
unpivot
(
value
for col in (address, name, code)
) unpiv
) x
pivot
(
max(value)
for col in (' + #cols + ')
) p '
execute(#query);
See SQL Fiddle with Demo. All versions will give a result:
| ID | UID | NAME1 | CODE1 | ADDRESS1 | NAME2 | CODE2 | ADDRESS2 |
---------------------------------------------------------------------
| 10 | 5 | Jac | 683501 | A | Joe | 727272 | B |
| 13 | 6 | mat | 373737 | C | (null) | (null) | (null) |
Try inner join.
select u2.firstname,u2.col2,
from users u
inner join users u2 on u.userid=u2.userid
where u.firstname=u2.lastname

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

Split a string into individual characters in 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