How to check group for containing certain elements in SQL? - sql

I have a simple table:
id num
1 7
1 5
1 4
2 5
2 4
2 7
3 4
3 7
How to select ids having num 5 as well as 7 and 4
For this example ids: 1, 2

SELECT `id` FROM `table`
WHERE `num` IN (4, 5, 7)
GROUP BY `id`
HAVING COUNT(*) = 3

This is a very slightly amended version of zerkms' answer:
SELECT `id` FROM `table`
WHERE `num` IN (4, 5, 7)
GROUP BY `id`
HAVING COUNT(DISTINCT `num`) = 3

How about
;WITH T AS
(
SELECT ID, NUM, COUNT(*) OVER(PARTITION BY ID) AS ROWNO,
SUM(NUM) OVER(PARTITION BY ID) AS SUMNUM
FROM TABLE
)
SELECT ID, NUM
FROM T
WHERE ROWNO = 3 AND SUMNUM = 16

Related

How to group the same values which is in sequence order

I'm trying to group data in sequence order. I have the following table:
id num
-------
1 1
2 1
3 1
4 2
5 1
6 2
7 2
8 4
9 4
10 4
I need the SQL query to output the following:
num count(num)
-------------------
1 3
2 1
1 1
2 2
4 3
Sample data:
select * into #temp
from (
select 1 as id, 1 as num union all
select 2, 1 union all
select 3, 1 union all
select 4, 2 union all
select 5, 1 union all
select 6, 2 union all
select 7, 2 union all
select 8, 4 union all
select 9, 4 union all
select 10, 4
) as abc
select * from #temp
select num, count(num)
from #temp
group by num
I need this :
num count(num)
-------------------
1 3
2 1
1 1
2 2
4 3
The actual output :
num count(num)
---------------------
1 4
2 3
4 3
This is a gaps and islands problem. Here is one way to solve it using lag() and a cumulative sum():
select min(num) num, count(*) count_num
from (
select t.*, sum(case when num = lag_num then 0 else 1 end) over(order by id) grp
from (
select t.*, lag(num) over(order by id) lag_num
from #temp t
) t
) t
group by grp
Demo on DB Fiddlde:
num | count_num
--: | --------:
1 | 3
2 | 1
1 | 1
2 | 2
3 | 3
Another Approach can be using row_number
select num, count(*)
from (select t.*,
(row_number() over (order by id) -
row_number() over (partition by num order by id)
) as grp
from #temp t
) t
group by grp, num;
DBFIDDLE
Gaps and islands problems are fun because there are so many different ways to address them. Here is one approach that does not require aggregation -- although it does require more use of window functions.
This is possible because the only information you are requesting is the count. If the id has no gaps and is sequential:
select num,
lead(id, 1, max_id + 1) over (order by id) - id
from (select t.*,
lag(num) over (order by id) as prev_num,
max(id) over () as max_id
from temp t
) t
where prev_num is null or prev_num <> num
order by id;
Otherwise, you can generate such a sequence easily:
select num,
lead(seqnum, 1, cnt + 1) over (order by id) - seqnum
from (select t.*,
lag(num) over (order by id) as prev_num,
row_number() over (order by id) as seqnum,
count(*) over () as cnt
from temp t
) t
where prev_num is null or prev_num <> num
order by id;
Here is a db<>fiddle.

How to group nodes with relationships in SQL

I have the following table which lists related nodes:
;WITH CTE AS
( SELECT *
FROM (VALUES (1,2)
,(2,1)
,(3,4)
,(3,5)
,(4,3)
,(4,5)
,(5,3)
,(5,4)
,(6,NULL)
,(7,NULL)
,(8,9)
,(9,8)
) AS ValuesTable(ID,RelatedID)
)
SELECT *
FROM CTE
How can I assign unique IDS (GUID or integer GroupID) to each group, So, 1 and 2 will be on the same group, 3, 4, 5 on a different group, 6 is alone in it's group and so is 7, and 8 and 9 are one more group?
My answer so far seems very cumbersome:
;WITH CTE AS
( SELECT *
FROM (VALUES (1,2)
,(2,1)
,(3,4)
,(3,5)
,(4,3)
,(4,5)
,(5,3)
,(5,4)
,(6,NULL)
,(7,NULL)
,(8,9)
,(9,8)
) AS ValuesTable(ID,RelatedID)
)
SELECT DENSE_RANK() OVER(ORDER BY CA.IDList) AS GroupID,
ID,
RelatedID
FROM CTE
CROSS APPLY (SELECT STUFF((SELECT ',' + CONVERT(NVARCHAR(255), ID)
FROM CTE AS CTEInner
WHERE CTEInner.ID = CTE.ID
OR CTEInner.ID = CTE.RelatedID
OR CTEInner.RelatedID = CTE.RelatedID
OR CTEInner.RelatedID = CTE.ID
FOR XML PATH(''),TYPE).value('(./text())[1]','NVARCHAR(MAX)'),1,1,'') AS IDList) AS CA
But it provides the correct answer:
GroupID ID RelatedID
1 1 2
1 2 1
2 3 4
2 3 5
2 4 3
2 4 5
2 5 3
2 5 4
3 6 NULL
4 7 NULL
5 8 9
5 9 8
Adding a unique number for each group is not hard but it does require a few steps.
The first step would be to select unique values for the groups - so for instance the group with (1, 2) and (2, 1) will contain only a single record - (1, 2).
The next step is to get rid of the records that creates multiple paths for the same relationship - in this case - (3, 4), (4, 5), (3, 5) - means that 5 is the related to both 3 and 4, but for the recursive cte to work, we only need a single relationship path - so either (3, 4), (4, 5) or (3, 4), (3, 5) but not both.
The next step is to create a recursive cte based on these unique values, so that each group can get it's unique number.
After that, you can select from the original cte joined to the recursive cte and get the unique group numbers:
;WITH CTE AS
( SELECT *
FROM (VALUES (1,2)
,(2,1)
,(3,4)
,(3,5)
,(4,3)
,(4,5)
,(5,3)
,(5,4)
,(6,NULL)
,(7,NULL)
,(8,9)
,(9,8)
) AS ValuesTable(ID,RelatedID)
)
, PreUniqueValues AS
(
SELECT MIN(ID) AS ID,
MAX(RelatedID) As RelatedID
FROM CTE AS B
GROUP BY (ID + ISNULL(RelatedID, 0)) + (ID * ISNULL(RelatedID, 0))
)
, UniqueValues AS
(
SELECT ID, MIN(RelatedID) As RelatedID
FROM PreUniqueValues
GROUP BY ID
)
, Recursive AS
(
SELECT ID, RelatedId, DENSE_RANK() OVER(ORDER BY ID) As GroupID
FROM UniqueValues AS T0
WHERE NOT EXISTS
(
SELECT 1
FROM UniqueValues AS T1
WHERE T1.ID = T0.RelatedID
)
UNION ALL
SELECT UV.ID, UV.RelatedID, GroupID
FROM UniqueValues As UV
JOIN Recursive As Re
ON UV.ID = Re.RelatedId
)
SELECT CTE.ID, CTE.RelatedID, GroupID
FROM CTE
JOIN Recursive
ON CTE.ID = Recursive.ID OR CTE.ID = ISNULL(Recursive.RelatedID, 0)
ORDER BY ID
Results:
ID RelatedID GroupID
1 2 1
2 1 1
4 3 2
4 5 2
5 3 2
5 4 2
6 NULL 3
7 NULL 4
8 9 5
9 8 5
This is a graph-walking problem and you would seem to need recursive CTEs. The logic looks like this:
WITH t AS (
SELECT *
FROM (VALUES (1,2)
,(2,1)
,(3,4)
,(3,5)
,(4,3)
,(4,5)
,(5,3)
,(5,4)
,(6,NULL)
,(7,NULL)
,(8,9)
,(9,8)
) AS ValuesTable(ID,RelatedID)
),
cte as (
select distinct id, id as relatedId, ',' + convert(varchar(max), id) + ',' as relatedIds
from t
union all
select cte.id, t.relatedId, cte.relatedIds + convert(varchar(max), t.relatedId) + ','
from cte join
t
on cte.relatedId = t.id
where cte.relatedId is not null and
cte.relatedIds not like '%,' + convert(varchar(max), t.relatedId) + ',%'
)
SELECT id, min(relatedId) as grp,
dense_rank() over (order by min(relatedId)) as grp_number
FROM cte
GROUP BY id;
Here is a db<>fiddle.

sql server : count records

I have a tableA (ID int, Match varchar, tot int)
ID Match Tot
1 123
2 123
3 12
4 12
5 4
6 12
7 8
Now, I want to calculate Tot which is total number of match exists in the table. for example 123 occured twice, 12 exist thrice and so on. Also note that I want the count only at first match. here is the expected result.:
ID Match Tot
1 123 2
2 123
3 12 3
4 12
5 4 1
6 12
7 8 1
Another case:
ID Match Count Tot
1 123 2
2 123 1
3 12 10
4 12 10
5 4 3
6 12 5
7 8 7
Now I want to add the count for the same match. expected result:
ID Match Count Tot
1 123 2 3
2 123 1
3 12 10 25
4 12 10
5 4 3 3
6 12 5
7 8 7 7
Thanks
WITH tableA(ID, Match) AS
(
SELECT 1,123 UNION ALL
SELECT 2,123 UNION ALL
SELECT 3,12 UNION ALL
SELECT 4,12 UNION ALL
SELECT 5,4 UNION ALL
SELECT 6,12 UNION ALL
SELECT 7,8
)
SELECT *,
CASE
WHEN ROW_NUMBER() OVER (PARTITION BY Match ORDER BY ID) = 1
THEN COUNT(*) OVER (PARTITION BY Match)
END AS Tot
FROM tableA
ORDER BY ID
SELECT match, COUNT(match ) as Tot
FROM tableA
GROUP BY match
Solution 1:
DECLARE #MyTable TABLE
(
ID INT PRIMARY KEY
,Match VARCHAR(10) NOT NULL
,Tot INT NULL
);
INSERT #MyTable(ID, Match)
SELECT 1, 123
UNION ALL
SELECT 2, 123
UNION ALL
SELECT 3, 12
UNION ALL
SELECT 4, 12
UNION ALL
SELECT 5, 4
UNION ALL
SELECT 6, 12
UNION ALL
SELECT 7, 8;
--SELECT
SELECT *
,CASE
WHEN ROW_NUMBER()OVER(PARTITION BY a.Match ORDER BY a.ID ASC)=1
THEN COUNT(*)OVER(PARTITION BY a.Match)
END TotCalculated
FROM #MyTable a;
--UPDATE
WITH MyCTE
AS
(
SELECT a.Tot
,CASE
WHEN ROW_NUMBER()OVER(PARTITION BY a.Match ORDER BY a.ID ASC)=1
THEN COUNT(*)OVER(PARTITION BY a.Match)
END TotCalculated
FROM #MyTable a
)
UPDATE MyCTE
SET Tot = TotCalculated;
SELECT *
FROM #MyTable;
Solution 2:
UPDATE #MyTable
SET Tot = NULL;
SELECT x.ID, y.Num
FROM
(
SELECT b.Match, MIN(b.ID) ID
FROM #MyTable b
GROUP BY b.Match
) x INNER JOIN
(
SELECT a.Match, COUNT(*) AS Num
FROM #MyTable a
GROUP BY a.Match
) y ON x.Match = y.Match
ORDER BY x.ID
UPDATE #MyTable
SET Tot = t.Num
FROM #MyTable z
INNER JOIN
(
SELECT x.ID, y.Num
FROM
(
SELECT b.Match, MIN(b.ID) ID
FROM #MyTable b
GROUP BY b.Match
) x INNER JOIN
(
SELECT a.Match, COUNT(*) AS Num
FROM #MyTable a
GROUP BY a.Match
) y ON x.Match = y.Match
) t ON z.ID = t.ID;
SELECT *
FROM #MyTable;

How to get the max version records?

I have a table like the following:
------------------------------------
Id FId UId Version
1 1 1 1
2 1 2 1
3 1 3 1
4 1 2 2
5 1 3 2
6 1 3 2
7 1 4 2
8 2 1 1
9 2 2 1
then I want the result to be:
--------------------------
FId UId Version
1 2 2
1 3 2
1 4 2
2 1 1
2 2 1
How to write the query based on the max 'Version' of each FId-UId pair?
The following gives the output requested.
select distinct t2.FId, t2.UId, t2.Version
from
(
select FId, max(Version) as "Version"
from MyTable
group by FId
) t1
inner join MyTable t2 on (t1.FId = t2.FId and t1.Version = t2.Version)
order by t2.FId, t2.UId
This will work on SQL 2005 and later:
DECLARE #t TABLE
(Id INT,
Fid INT,
[uid] INT,
[VERSION] INT
)
INSERT #t
SELECT 1,1,1,1
UNION ALL SELECT 2,1,2,1
UNION ALL SELECT 3,1,3,1
UNION ALL SELECT 4,1,2,2
UNION ALL SELECT 5,1,3,2
UNION ALL SELECT 6,1,3,2
UNION ALL SELECT 7,1,4,2
UNION ALL SELECT 8,2,1,1
UNION ALL SELECT 9,2,2,1
;WITH myCTE
AS
(
SELECT *,
RANK() OVER (PARTITION BY Fid
ORDER BY [VERSION] DESC
) AS rnk
FROM #t
)
SELECT DISTINCT Fid, [uid],[VERSION]
FROM myCTE
WHERE rnk = 1
ORDER BY Fid, [uid]
select FId, UId, Version
from MyTable
join (select Fid, Max(Version) as MaxVersion group by Fid) x
on x.FId = MyTable.FId and x.MaxVersion = MyTable.Version
Is the result you show correct- 1,3,2 should appear twice.If you need only once use select distinct
The foll query is working
with t as(
select 1 as id, 1 as fid , 1 as uid, 1 as version union all
select 2 , 1 , 2 , 1 union all
select 3 , 1 , 3 , 1 union all
select 4 , 1 , 2 , 2 union all
select 5 , 1 , 3 , 2 union all
select 6 , 1 , 3 , 2 union all
select 7 , 1 , 4 , 2 union all
select 8 , 2 , 1 , 1 union all
select 9 , 2 , 2 , 1)
select distinct t.fid,t.uid,t.version from t
inner join(
select fid,max(version) as maxversion from t
group by fid)as grp
on t.fid=grp.fid
and t.version=grp.maxversion

Need sql query for matching with three values

I have a table like below
CAccountID CID NetworkID
1 1 1
2 1 2
3 2 1
4 2 2
5 2 3
6 3 1
7 3 2
8 3 3
9 4 1
10 4 2
I need a query to select all CID having all 3 NetworkID(1,2,3) and don't need to display only 1 and 2 NetworkID.
Output should be like below,
CAccountID CID NetworkID
3 2 1
4 2 2
5 2 3
6 3 1
7 3 2
8 3 3
You can use GROUP BY with JOIN :
select t.*
from table t inner join
( select cid
from table
where NetworkID in (1,2,3)
group by cid
having count(distinct NetworkID) = 3
) tt
on tt.cid = t.cid;
Try this:
select * from my_table t
where exists(select 1 from my_table
where CID = t.CID and NetworkID in (1,2,3)
group by CID
having count(*) = 3)
Try this:
select * from <<tablename>> where cid in(select cid from <<tablename>> group by cid having count(*)=3).
Here the subquery will return you all thouse cid which have 3 rows in your table.
Or if you have more network ids then use of INTERSECT operator can be helpful:
select * from <<tablename>> where cid in (
select cid from <<tablename>> where NetworkID=1
INTERSECT
select cid from <<tablename>> where NetworkID=2
INTERSECT
select cid from <<tablename>> where NetworkID=3
);
INTERSECT operator basically returns all the rows common in the queries. Thus, your data unpredicatbility can be handled in this way
Try xml path.
SELECT *
FROM Table_Name B
WHERE (SELECT [text()] = A.Network FROM Table_Name A WHERE A.CID = B.CID
ORDER BY CID, CAAccount FOR XML PATH('')) = 123
CTE Demo:
; WITH CTE(CAAccount, CID, Network) AS
(
SELECT 1 , 1, 1 UNION ALL
SELECT 2 , 1, 2 UNION ALL
SELECT 3 , 2, 1 UNION ALL
SELECT 4 , 2, 2 UNION ALL
SELECT 5 , 2, 3 UNION ALL
SELECT 6 , 3, 1 UNION ALL
SELECT 7 , 3, 2 UNION ALL
SELECT 8 , 3, 3 UNION ALL
SELECT 9 , 4, 1 UNION ALL
SELECT 10, 4, 2
) SELECT *
FROM CTE B
WHERE (SELECT [text()] = A.Network FROM CTE A WHERE A.CID = B.CID ORDER BY CID, CAAccount FOR XML PATH('')) = 123
Output:
CAAccount CID Network
3 2 1
4 2 2
5 2 3
6 3 1
7 3 2
8 3 3