Data design & efficiency: get members from all child groups (nested) - sql

I have 2 tables which defined members and their groups like so:
[Table member]
memberID | groupID
1 | 101
2 | 104
... | ...
[Table group]
groupID | parentID
1 | NULL
101 | 1
102 | 1
103 | 101
104 | 103
... | ...
And in my page, if I want to get all members from a particular group (including their child groups), e.g. get all members from group 1, I have to:
step 1: get groupIDs for group 1 and its children and its children's children and ...
step 2: SELECT * FROM member WHERE groupID in (....."groupIDs grabbed in step 1"....)
In my database, there're about 3M members and 50K groups contained in each table, if a requested group is a top level node, it has thousands of child groups, and query may become very slow.
Is there a better way to re-design these table structures for member-group relation and make my query run faster ?
any alternative solutions like caching are also welcomed.

Even though you can store hierarchical data in sql server
The way you have done.
or by using sql server advance data type hierarchyid.
But SQL server isn't really a tool to handle the hierarchical data and it becomes really difficult to do the simplest task.
In your case the best option would be to have Two separate tables for Users and Groups e.g.
Users
UserID UserName
1 User1
2 User2
3 User3
Groups
GroupID GroupName
1 Group1
2 Group2
3 Group3
And then finally a Third table to store data For Users and Their Group Memberships. Where UserID column referrences back to User table and GroupID column referrences back to Group table.
UserGroup
UserID GroupID
1 1
1 2
2 1
2 2
2 3
3 3
So your tables would end up having the following relations
User --> One to Many --> UserGroup
Groups --> One to Many --> UserGroup
/-------> User
UserGroup --> Many to Many --/
\
\-------> Groups

Related

Returning table based on comparing list of values in one View to a list in another

I have two SQL Views that expose data relating to Entity / Classification pairings and another that exposes User Classification pairings.
In order for a user to have permission to access an entity the user must have ALL the categories assigned to that entity. So:
EntityID ClassificationID
1 1
1 2
2 1
2 2
2 3
UserID ClassificationID
100 1
100 2
100 4
101 1
101 2
101 3
In the above scenario User 100 has access to entity ID 1, but user 101 has access to both 1 and 2
I want to be able to return this data in a table like this, essentially a complete list of entitles and users that have access to them:
UserID EntityID
100 1
101 1
101 2
What is the best and most performant way of achieving this. I am using SQL Server 2019
This is a relational division problem. I would recommend a join to relate the users and entities, then aggregation, and filtering with a having clause to retain only "complete" groups.
Assuming that the tables are called entities and users:
select u.userid, e.entityid
from entities e
inner join users u on u.classificationid = e.classificationid
group by u.userid, e.entityid
having count(*) = (select count(*) from entities e1 where e1.entityid = e.entityid)
Demo on DB Fiddle:
userid | entityid
-----: | -------:
100 | 1
101 | 1
101 | 2

Combining related organisation records in SQL where there is a Parent-Child relationship between organisations

I am trying to build a table of data for use in Yellowfin BI reporting. One limitation of this is that no temporary tables can be created and then dropped in the database. I am pulling the data from an existing database, which i have no control over. I can only use SQL to query the existing data.
There are two tables in the source database i need to work with. I've simplified them for clarity. The first contains organisations. It has an ORG_ID column which contains a unique ID for each organisation and a PARENT_ORG_ID column indicating which organisation is the Parent Company of others in the list:
ORG_ID PARENT_ORG_ID
1 Null
2 1
3 5
4 5
5 Null
6 1
Using the table above i can see that there are the following relationships between organisations:
ORG_ID RELATED_ORGANISATIONS
1 2 and 6
2 1 and 6
3 5 and 4
4 5 and 3
5 4 and 3
6 1 and 2
I'm not sure the best way to represent these connections in a query as i need to use these relationships with a second table.
The second table i have is a list of organisations and money owed:
ORG_ID MONEY_OWED
1 5
2 10
3 0
4 15
5 20
6 5
What i need to achieve is a table that i can search for any single ORG_ID, and see the combined data for that Organisation and all related Organisations. In the case of my example, this could be a results table something like this:
ORG_ID MONEY_OWED_BY_ALL_RELATED_ORGS
1 20
2 20
3 35
4 35
5 35
6 20
I'm thinking i should use a CTE to handle the relationships between organisations but i can't get my head around it.
Any help would be greatly appreciated!
For your particular example, you can use:
select o.*,
sum(mo.money_owed) over (partition by coalesce(o.parent_org_id, o.org_id)) as parent_owed
from organizations o left join
money_owed mo
on mo.org_id = o.org_id;
This works because your organizations are only one level deep -- which is consistent with your sample data.

How to return only rows that do not exist in another query (Access 2013) using NOT EXISTS

I can't get this query to work. I'm trying to return every row that does NOT have a certain relationship in my tables.
group_id | group_name
--------------------------
1 group1
2 group2
3 group3
4 group4
5 group5
relationship_id | from | to | type
-----------------------------------------------------
1 1 2 Subgroup
3 1 5 Subgroup
4 2 3 Subgroup
5 4 2 Subgroup
These tables create a hierarchial structure, where [from] is the parent ID and [to] is the child ID.
--group1
--group2
--group3
-- group5
--group4
--group2
I want to select every group that is not a child of another group. Basically, I want to select top-level only groups, that is, all groups without a parent. This is all groups with their ID not under [TO]
I started by querying to find all of the children. This query works as expected.
SELECT groups.*
FROM relationships INNER JOIN groups ON relationships.to = groups.ID
WHERE (((relationships.Type)="SubGroup"))
(The type is related to something else don't worry about that).
This correctly return the following:
group_id | group_name
--------------------------
2 group2
3 group3
5 group5
So then, I figured I could use a NOT EXISTS to get the rows that I actually want.
SELECT groups.* FROM groups
WHERE NOT EXISTS (
SELECT groups.*
FROM relationships INNER JOIN groups ON relationships.to = groups.ID
WHERE (((relationships.Type)="SubGroup"))
)
However, this returns no rows. Am I using NOT EXISTS wrong?
What you're looking for is groups whereas the groupID does not exist in the 'to' column in your relationships table.
Try this:
SELECT groups.*
FROM groups
WHERE NOT EXISTS
(
SELECT 1
FROM relationships
WHERE groups.group_id = relationships.to
)

SQL Calculations With Multi-Group Affiliations

I'm attempting to have a function or view that is able to calculate and roll up various counts while being able to search on a many to many affiliation.
Here is an example data set:
Invoice Table:
InvoiceID LocationID StatusID
1 5 1
2 5 1
3 5 1
4 5 2
5 7 2
5 7 1
5 7 2
Group Table:
GroupID GroupName
1 Group 1
2 Group 2
GroupToLocation Table:
GroupToLocationID GroupID LocationID
1 1 5
2 2 5
3 2 7
I have gotten to the point where I could sum up the various statuses per location and get this:
LocationID Status1 Status2
5 3 1
7 1 2
Location 5 has 3 Invoices with a status of 1, and 1 invoice with a status of 2 while Location 7 has 1 status 1 and 2 status 2
There are two groups, and Location 5 is in both, while Location 7 is only in the second. I need to be able to set it up where I can append a where statement like this:
select * from vw_GroupCounts
where GroupName = 'Group 2'
or
select Invoice, SUM(*) from vw_GroupCounts
where GroupName = 'Group 2'
And that result in only getting Location 7. Whenever I do this, as I have to use left joins or something along those lines, the counts are duplicating for each group the the Location is affiliated with. I know I could do something along the lines of a subquery and pass in the GroupName into that, but the system I am working with uses a dynamic query builder that appends WHERE statements based on user input.
I don't mind using view, or functions, or any number of functions inside of functions, but I hope there is a way to do what I'm looking for.
Since locations 5 and 7 are in Group 2, if you search for group 2 in the where clause after joining all the tables, then you would get all records in this case, this isn't duplication, just the way the data is. A different join wouldn't change this, only changing the data. Let me know if I am misunderstanding something though.
Here is how you would join them to do that search.
Here it is with your first example of the location and status count.

Hierarchical Table and Query in SQL Server

I have a scenario as follows:
ManufacturerID Name
1 XXX
2 YYY
DeptID Name ManufacturerID
1 abc 1
2 bcd 1
3 efg 2
ProductID name deptid
1 dfdfg 1
2 dfdg 2
3 sdfsd 2
PartsID name productid
1 sdfs 1
2 sfdfs 2
3 sdd 1
I want the above table structure to be made as hierarchical using levels. How do I design the table?
I cannot understand what hierarchy you need to make, but if you're using sql server 2008 please take a look at hierachyId datatype.
And please provide more desdcription about you process. Now it looks straightforward:
Manufacturer has many departments, department has many products, product has many parts.