Recursive query in SQL Server - sql

I have a table with following structure
Table name: matches
That basically stores which product is matching which product. I need to process this table
And store in a groups table like below.
Table Name: groups
group_ID stores the MIN Product_ID of the Product_IDS that form a group. To give an example let's say
If A is matching B and B is Matching C then three rows should go to group table in format (A, A), (A, B), (A, C)
I have tried looking into co-related subqueries and CTE, but not getting this to implement.
I need to do this all in SQL.
Thanks for the help .

Try this:
;WITH CTE
AS
(
SELECT DISTINCT
M1.Product_ID Group_ID,
M1.Product_ID
FROM matches M1
LEFT JOIN matches M2
ON M1.Product_Id = M2.matching_Product_Id
WHERE M2.matching_Product_Id IS NULL
UNION ALL
SELECT
C.Group_ID,
M.matching_Product_Id
FROM CTE C
JOIN matches M
ON C.Product_ID = M.Product_ID
)
SELECT * FROM CTE ORDER BY Group_ID
You can use OPTION(MAXRECURSION n) to control recursion depth.
SQL FIDDLE DEMO

Something like this (not tested)
with match_groups as (
select product_id,
matching_product_id,
product_id as group_id
from matches
where product_id not in (select matching_product_id from matches)
union all
select m.product_id, m.matching_product_id, p.group_id
from matches m
join match_groups p on m.product_id = p.matching_product_id
)
select group_id, product_id
from match_groups
order by group_id;

Sample of the Recursive Level:
DECLARE #VALUE_CODE AS VARCHAR(5);
--SET #VALUE_CODE = 'A' -- Specify a level
WITH ViewValue AS
(
SELECT ValueCode
, ValueDesc
, PrecedingValueCode
FROM ValuesTable
WHERE PrecedingValueCode IS NULL
UNION ALL
SELECT A.ValueCode
, A.ValueDesc
, A.PrecedingValueCode
FROM ValuesTable A
INNER JOIN ViewValue V ON
V.ValueCode = A.PrecedingValueCode
)
SELECT ValueCode, ValueDesc, PrecedingValueCode
FROM ViewValue
--WHERE PrecedingValueCode = #VALUE_CODE -- Specific level
--WHERE PrecedingValueCode IS NULL -- Root

Related

How to create cases for multiple purposes and just showing the count of unique IDs for multiple cases

I wrote a CTE which helps determine a flag based off a certain client's ID and what kind of client they are. I am looking to test the counts of the Flags, and the counts are totaling out amazingly! However, I am looking to add additional columns to the very last section of my code to show the amount of IDs who have belonged in all 3 cases, or both PPP and R, PPP and RR, RR and PPP, or RR and R. Is there a way I could do this? I know SUM won't work. I'm thinking using CASE or an IF, however I am a novice to SQL and am unsure what to do.
WITH ids AS (
SELECT DISTINCT LOWER(r.entry_id) AS ID
FROM id_user AS r
UNION
SELECT DISTINCT LOWER(identifiervalue) AS ID
FROM account AS a
)
,PPP as (
SELECT DISTINCT
LOWER(accountid) as "ID"
FROM ppp
WHERE (date >= '2022-11-21')
)
,R as (
SELECT DISTINCT
LOWER(account_id) as "ID"
FROM user
)
, RR as (
SELECT DISTINCT
LOWER(id) AS "ID"
FROM program_member
)
, Joint as (
SELECT
r.ID
,CASE WHEN p.ID IS NULL THEN 0 ELSE 1 END AS "PPP Flag"
,CASE WHEN r.ID IS NULL THEN 0 ELSE 1 END AS "R Flag"
,CASE WHEN rr.raid IS NULL THEN 0 ELSE 1 END AS "RR Flag"
FROM ids i
LEFT JOIN PPP ppp ON i.RAID = ppp.RAID
LEFT JOIN R r ON i.RAID = r.RAID
LEFT JOIN RR rr on i.RAID = rr.RAID
----TESTING COUNTS
SELECT
COUNT(ID) AS "ID Count"
,sum("PPP Flag") AS "PPP Users"
,sum("R") AS "R Accounts"
,sum("RR Flag") AS "RR Users"
FROM Joint
if all you are trying to do is get distinct counts, and how many are within each of those given flag categories, dont try to repeat then join. They are either in the PPP, User or Program_Member table. Just get that
select
UnionCnt.IDAndRaidCnt,
P.PPPCnt,
R.RCnt,
RR.RRCnt
from
-- this outer query returns only 1 record
( select
count(*) IDAndRaidCnt
from
-- this inner query returns all distinct based on the UNION result
(SELECT DISTINCT
LOWER(r.entry_id) ID
FROM
id_user r
UNION
SELECT DISTINCT
LOWER(identifiervalue) ID
FROM
account a)
) UnionCnt
JOIN
(SELECT
count( DISTINCT LOWER(accountid)) PPPCnt
FROM
ppp
WHERE
date >= '2022-11-21' ) P
-- above will only return a single record anyhow
on 1=1
JOIN
(SELECT
count( DISTINCT LOWER(account_id) RCnt
FROM
user ) R
-- also returns single row with total qualifying distinct ID count
on 1=1
JOIN
(SELECT
count( DISTINCT LOWER(id)) RRCnt
FROM
program_member ) RR
-- same here, single record
on 1=1

SQL Server: Query for products with matching tags

I have been pondering over this for the past few hours but I cannot find a solution.
I have a products in a table, tags in another table and a product/tag link table.
Now I want to retrieve all products which have the same tags as a certain product.
Here are the tables (simplified):
PRODUCT:
id varchar(36) (primary key)
Name varchar(50)
TAG:
id varchar(36) (primary key)
Name varchar(50)
PRODUCTTAG:
id varchar(36) (primary key)
ProductID varchar(36)
TagID varchar(36)
I find quite a few answers here on Stackoverflow talking about returning full and partial matches. However I am looking for a query which only gives full matches.
Example:
Product A has tags 1, 2, 3
Product B has tags 1, 2
Product C has tags 1, 2, 3
Product D has tags 1, 2, 3, 4
If I query for product A, only product C should be found - as it is the only one having exactly the same tags.
Is this even possible?
Yes, yes, try this way:
with aa as (
select count(*) count
from [PRODUCTTAG]
where ProductID = '19A947C0-6A0F-4A6F-9675-48FBE30A877D'
), bb as
(
select ProductID, count(*) count
from [PRODUCTTAG]
group by ProductID
)
select distinct b.ProductID
from [dbo].[PRODUCTTAG] a join
[dbo].[PRODUCTTAG] b on a.TagID = b.TagID cross join
aa join
bb on aa.count = bb.count and b.ProductID = bb.ProductID
where a.ProductID = '19A947C0-6A0F-4A6F-9675-48FBE30A877D'
declare #PRODUCTTAG table(id int identity(1,1),ProductID int,TagID int)
insert into #PRODUCTTAG VALUES
(1,1),(1,2),(1,3)
,(2,1),(2,2)
,(3,1),(3,2),(3,3)
,(4,1),(4,2),(4,3),(4,4)
;With CTE as
(
select ProductID,count(*)smallCount
FROM #PRODUCTTAG
group by ProductID
)
,CTE1 as
(
select smallCount, count(smallCount)BigCount
from cte
group by smallCount
)
,CTE2 as
(
select * from cTE c
where exists(
select smallCount from cte1 c1
where BigCount>1 and c1.smallCount=c.smallCount
)
)
select * from cte2
--depending upon the output expected join this with #PRODUCTTAG,#Product,#Tag
--like this
--select * from #PRODUCTTAG PT
--where exists(
--select * from cte2 c2 where pt.productid=c2.productid
--)
Or Tell what is final output look like ?
This is a case where I find it simpler to combine all the tags into a single string and compare the strings. But, that is painful in SQL Server until 2016.
So, there is a set based solution:
with pt as (
select pt.*, count(*) over (partition by productid) as cnt
from producttag pt
)
select pt.productid
from pt join
pt pt2
on pt.cnt = pt2.cnt and
pt.productid <> pt2.productid and
pt.tagid = pt2.tagid
where pt2.productid = #x
group by pt.productid, pt.cnt
having count(*) = pt.cnt;
This matches every product to your given product based on the tags. The having clause then ensures that the number of matching tags is the same for the two products. Because the join only considers matching tags, all the tags are the same.

Proper use of NOT EXISTS

I have two tables -
member_master
-----------------------------
member_id, ( PK )
branch_id, ( PK )
name member_id,
member_photo
-----------------------------
member_id, ( FK )
branch_id, ( FK )
photo_index,
photo_file
Each entry in member_master has zero or more corresponding entries in member_photo table.
I have two requirements :
Get all the entries from member_master which have at least one entry in member_photo table. I am getting the correct result using the following SQL command
SELECT DISTINCT member_master.member_id,member_master.branch_id,name
FROM member_master, member_photo
WHERE member_master.branch_id=1
AND EXISTS
(
SELECT member_photo.member_id
WHERE member_master.member_id = member_photo.member_id
AND member_master.branch_id = member_photo.branch_id
)
;
Get all the entries from member_master which DO NOT have any entry in member_photo table. I am using the following SQL command
SELECT DISTINCT member_master.member_id,member_master.branch_id,name FROM member_master, member_photo
WHERE member_master.branch_id=1
AND NOT EXISTS
(
SELECT member_photo.member_id
WHERE member_master.member_id = member_photo.member_id
AND member_master.branch_id = member_photo.branch_id
)
;
The only difference is I have added a NOT before EXISTS command.
But unfortunately it does not give me the correct result. It simply returns all the rows in the table.
Please note that I am using SQL Server Express 2005.
You should do it without joining the tables, just select from the master table and add check for the photo table, like this:
SELECT
m.member_id,
m.branch_id,
m.name
FROM
member_master m
WHERE
m.branch_id=1 AND
EXISTS (SELECT 1 from member_photo p where
m.member_id = p.member_id AND m.branch_id = p.branch_id)
And similarly the other case:
SELECT
m.member_id,
m.branch_id,
m.name
FROM
member_master m
WHERE
m.branch_id=1 AND
not EXISTS (SELECT 1 from member_photo p where
m.member_id = p.member_id AND m.branch_id = p.branch_id)
Required SQL query is:
SELECT
member_id,
branch_id,
name
FROM member_master
WHERE member_master.branch_id=1
AND NOT EXISTS(SELECT member_photo.member_id
WHERE member_master.member_id = member_photo.member_id
AND member_master.branch_id = member_photo.branch_id)

To arrange the hierarchy in a table

I have a table dbo.Hierarchy that contains the following data:
Level1 Level2 Level3 Level4 Level5 Level6 Level7 Level8 Level9 Level10
-----------------------------------------------------------------------
a b c d e f g h i j
k l m n o
There are a total of 10 levels and any item can have hierarchy upto any level. In the above data a is the parent of b, b is the parent of c and so on. j and o are the last levels in their respective hierarchies. How can I get the output in the below format:
Name ParentName LevelID
-------------------------------
a NULL 1
b a 2
j i 10
k NULL 1
l k 2
o n 5
Something like (untested)
with t(L1,L2,L3,L4,L5,L6,L7,L8,L9,L10) as (
values ('a','b','c','d','e','f','g','h','i','j')
, ('k','l','m','n','o',null,null,null,null,null)
)
select x.*
from t
cross apply (
values (L1,null,1),(L2,L1,2),(L3,L2,3),(L4,L3,4),(L5,L4,5)
, (L6,L5,6),(L7,L6,7),(L8,L7,8),(L9,L8,9),(L10,L9,10)) x (name, parentname, levelid)
where name is not null
Try this:
;with base as
(select *, row_number() over (order by level1) rn from tbl),
src as
(
select
valname as name,
cast(substring(colname,6,len(colname)) as int) as level,
rn from
(select * from base) s
unpivot
(
valname
for colname in ([level1],[level2],[level3],[level4],[level5],[level6],[level7], [level8],[level9],[level10])
) u
),
cte as
(select * from src
where level = 1
union all
select s.* from src s
inner join cte c on s.level = c.level + 1 and s.rn = c.rn)
select distinct s.name, t.name parentname, s.level levelid from
cte s
left join cte t on s.rn = t.rn and s.level = t.level + 1
Breakdown:
CTE base is used to generate row number as a derived column. We will use this column to keep track of which values belong to which row. This will help in uniquely mapping children to their parents.
CTE src is where we transform the table from this denormalised structure to a normalised one. Using UNPIVOT, we reduce the data set to 3 columns - name, level and the row number rn.
CTE cte is a recursive CTE that we use to get all possible combinations of parents and children (including immediate parents as well as ancestors).
Finally, we LEFT JOIN cte to itself on the condition that row number is same on both sides of the join i.e. the values belong to same record from the base table, and also that the value from the right side is the immediate ancestor (parent) of the value on the left side.
Demo
The huge mass of code above can be avoided, if you were to choose a normalised structure for your table. I would suggest something like this:
CREATE TABLE tbl
(ID int, --Keep track of values that are related to each other
Name varchar(100), --Name
Level int --The level for a particular value
)
With this proposed structure, all you would need is the recursive CTE (cte from the above code) and the left join to get the parent-child data. The beauty of this approach is that you can extend it to any number of levels that you like, without having to hard-code the level numbers.
Demo

Find the top value for each parent

I'm sure this is a common request but I wouldn't know how to ask for it formally.
I encountered this a long time ago when I was in the Army. A soldier has multiple physical fitness tests but the primary test that counts in the most recent. The soldier also has multiple marksmanship qualifications but only the most recent qualification to the weapon assigned is significant.
How do you create a view that itemizes the most significant child of the parent?
Use:
SELECT p.*, x.*
FROM PARENT p
JOIN CHILD x ON x.parent_id = p.id
JOIN (SELECT c.id,
c.parent_id,
MAX(c.date_column) AS max_date
FROM CHILD c
GROUP BY c.id, c.parent_id) y ON y.id = x.id
AND y.parent_id = x.parent_id
AND y.max_date = x.date
Assuming SQL Server 2005+:
WITH summary AS (
SELECT p.*,
c.*,
ROW_NUMBER() OVER (PARTITION BY p.id
ORDER BY c.date DESC) AS rank
FROM PARENT p
JOIN CHILD c ON c.parent_id = p.id)
SELECT s.*
FROM summary s
WHERE s.rank = 1
Although I'm not quite sure what you are implying by "itemizing", you can do something like so:
Select ..
From Soldier
Left Join FitnessTest
On FitnessTest.SoldierId = Soldier.Id
And FitnessTest.TestDate = (
Select Max(FT1.TestDate)
From FitnessTest As FT1
Where FT1.SoldierId = FitnessTest.SoldierId
)
Left Join MarksmanshipTest
On MarksmanshipTest.SoldierId = Soldier.Id
And MarksmanshipTest.TestDate = (
Select Max(MT1.TestDate)
From MarksmanshipTest As MT1
Where MT1.SoldierId = MarksmanshipTest.SoldierId
)
This assumes that a solider cannot have two test datetime values for a fitness test or a marksmanship test.
No significant differnce from previous two answer but a little more detail perhaps:
create table soldier ( soldierId int primary key,
name varchar(100) )
create table fitnessTest ( soldierId int foreign key references soldier,
occurred datetime, result int )
create table marksmanshipTest ( soldierId int foreign key references soldier,
occurred datetime, result int )
;with
mostRecentFitnessTest as
(
select
fitnessTest.soldierId,
fitnessTest.result,
row_number() over (order by occurred desc) as row
from fitnessTest
),
mostRecentMarksmanshipTest as
(
select
marksmanshipTest.soldierId,
marksmanshipTest.result,
row_number() over (order by occurred desc) as row
from marksmanshipTest
)
select
soldier.soldierId,
soldier.name,
mostRecentFitnessTest.result,
mostRecentMarksmanshipTest.result
from soldier
left outer join mostRecentFitnessTest on
mostRecentFitnessTest.soldierId = soldier.soldierId
and mostRecentFitnessTest.row = 1
left outer join mostRecentMarksmanshipTest on
mostRecentMarksmanshipTest.soldierId = soldier.soldierId
and mostRecentMarksmanshipTest.row = 1