SELECT Query for a Bill of Materials - sql

Our Bill of Materials table has 3 main columns: Part Number, Parent Part, and Level Code (0 being no parent, 3 being a child of a child of a child, etc).
I'd am trying to query based on a single part and get all of its children and their children, so on.
Its seems so simple but I have been struggling with this one. Anyone have a solution they can offer?
As always, I appreciate the help.
Per Randy's request, here is some more info:
PARPRT = Parent
COMPRT = Component
In the screen shot, all the component parts are part of the Bill of Materials for '101002'.
Later down the table, you will see each of those components in COMPRT listed in the PARPRT column with the components that make each of them up.
I want to query the BOM for '101002' and not only get the 4 parts in COMPRT where PARPRT = '101002', but also their COMPRTs and so on.

Thank you for those who suggested CTE's. That was exactly what I was looking for.
Here is the query I ended up with after some trial and error.
USE PartDatabase
GO
DECLARE #TheSinglePart CHAR(30) = '100001';
WITH BOM (PARPRT_02, COMPRT_02, QTYPER_02)
AS
(
-- Anchor member definition
SELECT e.PARPRT_02, e.COMPRT_02, e.QTYPER_02
FROM dbo.Product_Structure AS e
WHERE e.PARPRT_02 = #TheSinglePart
UNION ALL
-- Recursive member definition
SELECT e.PARPRT_02, e.COMPRT_02, e.QTYPER_02
FROM dbo.Product_Structure AS e
INNER JOIN BOM AS d
ON e.PARPRT_02 = d.COMPRT_02
)
SELECT *
FROM BOM;

Related

T-SQL: Single Table contains Permissions Tree

I have a SQL Server Table that contains a 'Hierarchy/Tree' of User Permissions.
Each Individual Permission can have values: 1 [Allowed], Blank [Not Allowed] & 0 [specifically Cancelled].
Each Individual Permission can be in one or more 'Permission Groups' & a User can be assigned all the Individual Permissions in one or more Permission Groups.
Each of the 'Permission Groups', in turn, can be in one or more higher level permission groups ... and eventually, all Permissions Groups are under a Master Group named 'Main Menu'.
This SQL Code:
Select
'Main Menu' Base,
Description Level1,
ParentId,
SecurityNodesId,
ListOrder,
Category,
LastModified
From SecurityNodes
Where ParentId = 1
Order By Description
Produces the following Output:
'Main Menu' has a ParentId of NULL [Not Shown in screenshot].
The 'Level1' 'Folders' contain other folders or Individual Permissions which are 'Referenced' by the Values under SecurityNodesId.
For instance, a search for SecurityNodesId 102 [Level1 - Administration] in the ParentId column returns this list of Sub Folders under 'Level2':
So ... I can access each of these sub folders by writing separate queries.
But what I want is to have an end result that displays every Node of this Permissions Tree in Table form like this:
Main Menu Level1 Level2 Level3 Level4 PermissionName PermissionValue
I have never had to do something this complex before, though I have done plenty of self-joins.
I am currently thinking that I would need to do a self join to each self join ... to get to successive Levels of the Tree ... but I believe there may be a 'recursive' approach to this that might be more efficient?
I would appreciate any help I can get with this.
Thanks in advance!
The way to solve this is with a Recursive CTE.
These are definitely more advanced than your usual SQL, but once you have your head wrapped around them, they are pretty easy to put together and VERY useful for hierarchical data (any table that stores a parent/child relationship).
A recursive CTE has two parts, separated by a UNION ALL.
The recursive seed which is ran only once and determines the starting result set for the recursion. For you, this is likely any record with a parentId of 1.
The recursive term (or member) which joins the cte (itself) to the table that holds the parent/child relationship. It will run over and over and over and over again until the Join or a WHERE filter causes it to return no new records.
In your case, it will look something like below. Note that I don't know what your starting table looks like. Namely the Level1 column from your original SQL isn't clear if that is the column name or an alias you call Level1. Furthermore it's not at all clear how you derive a "Permission Group" or "Permission Value" from this data. But... at any rate this should get you in the ballpark:
WITH reccte as (
/*
* To start the recursion we need a "Seed"... or a set of data
* that defines the starting point on which we iterate after
* the UNION ALL below.
*
* The seed here is all records with a parentid of 1
*/
SELECT Base,
ParentID,
SecurityNodesID,
Level as Level1,
NULL as Level2,
NULL as Level3,
NULL as Level4,
'?' as PermissionName,
Category as PermissionValue,
1 as depth, --track how deep we recurse
Base + '>' + Level as path --keep track of where we've been and what has led us to this point in recurssion
FROM SecurityNodes
UNION ALL
/*
* This section is the part that iterates. It continues to join
* all rows that have been collected up that point with the Security
* Nodes table until that join fails.
*/
SELECT
reccte.Base,
SecurityNodes.ParentID,
SecurityNodes.SecurityNodesID,
reccte.Level1,
/*
* Depending on how deep we are in the security hierarchy
* capture the level string to the appropriate column
*/
CASE WHEN depth = 1 THEN SecurityNodes.Level ELSE reccte.Level2,
CASE WHEN depth = 2 THEN SecurityNodes.Level ELSE reccte.Level3,
CASE WHEN depth = 3 THEN SecurityNodes.Level ELSE reccte.Level4,
'?' as PermissionName,
SecurityNodes.Category as PermissionValue,
reccte.depth + 1, --increment depth
reccte.path + '>' + SecurityNodes.Level --add to the path so we know how we got here
FROM reccte
INNER JOIN SecurityNodes
/*Join parent to child*/
ON reccte.SecurityNodesId = SecurityNodes.parentId
WHERE depth < 5 --Stop looking up if we go deeper than 4 levels.
)
SELECT *
FROM reccte
While we track depth here and stop the recursion if we hit a depth of 4, you could stop the recursion with the MAXRECURSIVE option/hint. That would just go at the end of your query:
SELECT *
FROM reccte
OPTION (MAXRECURSION 4);
It's important to add either/or to your recursive CTE otherwise you risk causing an infinite loop should a security node have a child that is also one of its ancestors which would cause it to cycle endlessly.
OPTION (MAXRECURSION 2);
I followed through on an idea I mentioned in my original post and it looks like I have achieved what I was wanting.
I don't think it is the best possible solution because I know how many total levels there currently are. If we suddenly add another level or two, the SQL will not capture everything and I'll manually have to add one or more Left Joins.
Select
'Main Menu' Base,
sn.Description Level1,
sn2.Description Level2,
sn3.Description Level3,
sn4.Description Level4,
sn.ParentId,
sn.SecurityNodesId,
sn.ListOrder,
sn.Category,
sn.LastModified
From
SecurityNodes sn
Left Join SecurityNodes sn2 On sn2.ParentId = sn.SecurityNodesId
Left Join SecurityNodes sn3 On sn3.ParentId = sn2.SecurityNodesId
Left Join SecurityNodes sn4 On sn3.ParentId = sn3.SecurityNodesId
Order By sn.ParentId, sn.Description
I would still appreciate any suggestions for a more elegant/dynamic way of achieving what I need ... but for now, the above SQL is doing the job.

Dynamic SQL query to loop through records and find correct category

I'm trying to write a 2 step process to determine material types for different components.
Step 1: this includes results from a different query containing RecordID and Component Name :
Sample records :
RecordID
Component
Material
BR39590
00000000000000564792
000000000002073757
BR39590
00000000000000567649
000000000002073757
BR39591
00000000000000567650
000000000002073758
Above RecordId's will contain several component numbers and corresponding Materials
Loop through the Material derived from above query result and join to a different table called 'Material' to determine Material type, if Material type belongs to 'A' or 'B' the process should exit out and insert the records into a new table. If Material type does not belong to 'A' or B then Query should go back to step 1 and fetch the next component to look for 'A' or 'B' or blank resultset.
Material
Category
000000000002073757
A
000000000002073758
B
Above 2 steps are repeated for all RecordID's
Final Result:
RecordId
Material
Component
Category
BR39590
000000000002073757
00000000000000564792
A
BR39590
000000000002073757
00000000000000567649
A
BR39591
000000000002073758
00000000000000567650
B
I did not understand what you mean, With dynamic SQL or coding in SQL environment, your wishes can be met, but there is a much, much easier way.
with a left join you can find the end result.
select
c.RecordID,
c.Material,
c.Component,
m.Category
from components c left join material m on c.Material = m.Material

Resulting data on last thread_date

As I fixed the previous question thanks to the answer, I am sticking now in the part to retrieve a thread based on the last thread_date.
The code itself seems to be working fine, but it is only printing one result out instead of others.
The thread has a threads.cat_id which is linked to thesubsubcategory.extra_cat_id.
SELECT
parent.subcat_id,
parent.subcat_name,
child.subsubcat_name,
child.subcat_id,
child.cat_id,
kid.thread_name,
kid.cat_id,
kid.thread_date
FROM
subcategories parent
JOIN subsubcategories child
ON child.cat_id = parent.cat_id
JOIN threads kid ON child.extra_cat_id = kid.cat_id
WHERE thread_date = (SELECT MAX(thread_date) FROM threads)
What I am expecting is this:
Category
Subcategory Latest thread
Subcategory Latest thread
What I am getting is this:
Category
Subcategory Latest thread
SQL fiddle: http://sqlfiddle.com/#!9/52e27/2
Any solutions to it?
Thanks!
If my guess is right, you want to edit the where clause to show more based on last date as your threaddate is a datetime, you have to convert it to a date format before comparing it.
SELECT parent.subcat_id,
parent.subcat_name,
child.subsubcat_name,
child.subcat_id,
child.cat_id,
kid.thread_name,
kid.cat_id,
kid.thread_date
FROM subcategories parent
INNER JOIN subsubcategories child
ON child.cat_id = parent.cat_id
INNER JOIN threads kid
ON child.extra_cat_id = kid.cat_id
WHERE convert(date,thread_date,108) = (
SELECT MAX(convert(date,thread_date,108))
FROM threads
)
Do you need to get a thread with maximal date for every subsubcategory, don't you?
SELECT
parent.subcat_id,
parent.subcat_name,
child.subsubcat_name,
child.subcat_id,
child.cat_id,
kid.thread_name,
kid.cat_id,
kid.thread_date
FROM subcategories parent
JOIN subsubcategories child ON child.cat_id=parent.cat_id
JOIN threads kid ON kid.cat_id=child.extra_cat_id
WHERE kid.thread_date=
(SELECT MAX(kid2.thread_date)
FROM threads kid2
WHERE kid2.cat_id=child.extra_cat_id)
I got this fixed by creating a PHP function to return the latest thread with a
SELECT * FROM threads WHERE subcat_id = variable SQL.

Selecting rows from Parent Table only if multiple rows in Child Table match

Im building a code that learns tic tac toe, by saving info in a database.
I have two tables, Games(ID,Winner) and Turns(ID,Turn,GameID,Place,Shape).
I want to find parent by multiple child infos.
For Example:
SELECT GameID FROM Turns WHERE
GameID IN (WHEN Turn = 1 THEN Place = 1) AND GameID IN (WHEN Turn = 2 THEN Place = 4);
Is something like this possible?
Im using ms-access.
Turm - Game turn GameID - Game ID Place - Place on matrix
1=top right, 9=bottom left Shape - X or circle
Thanks in advance
This very simple query will do the trick in a single scan, and doesn't require you to violate First Normal Form by storing multiple values in a string (shudder).
SELECT T.GameID
FROM Turns AS T
WHERE
(T.Turn = 1 AND T.Place = 1)
OR (T.Turn = 2 AND T.Place = 4)
GROUP BY T.GameID
HAVING Count(*) = 2;
There is no need to join to determine this information, as is suggested by other answers.
Please use proper database design principles in your database, and don't violate First Normal Form by storing multiple values together in a single string!
The general solution to your problem can be accomplished by using a sub-query that contains a self-join between two instances of the Turns table:
SELECT * FROM Games
WHERE GameID IN
(
SELECT Turns1.GameID
FROM Turns AS Turns1
INNER JOIN Turns AS Turns2
ON Turns1.GameID = Turns2.GameID
WHERE (
(Turns1.Turn=1 AND Turns1.Place = 1)
AND
(Turns2.Turn=2 AND Turns2.Place = 4))
);
The Self Join between Turns (aliased Turns1 and Turns2) is key, because if you just try to apply both sets of conditions at once like this:
WHERE (
(Turns.Turn=1 AND Turns.Place = 1)
AND
(Turns.Turn=2 AND Turns.Place = 4))
you will never get any rows back. This is because in your table there is no way for an individual row to satisfy both conditions at the same time.
My experience using Access is that to do a complex query like this you have to use the SQL View and type the query in on your own, rather than use the Query Designer. It may be possible to do in the Designer, but it's always been far easier for me to write the code myself.
select GameID from Games g where exists (select * from turns t where
t.gameid = g.gameId and ((turn =1 and place = 1) or (turn =2 and place =5)))
This will select all the games that have atleast one turn with the coresponding criteria.
More info on exist:
http://www.techonthenet.com/sql/exists.php
I bypassed this problem by adding a column which holds the turns as a string example : "154728" and i search for it instead. I think this solution is also less demanding on the database

SQL Modeling Pyramid or Binary Tree

I'm building a sql server project that have a pyramid or binary tree concept...
I gonna try to explain using some tables!
The first table is
TB_USER(ID, ID_FATHER, LEFT/RIGHT TREE POSITION)
User can sell producs! So when they sell they earn points. Then, the second table is
TB_SELL (ID_USER, ID_PRODUCT, POINT)
As a result I'd like to see in the report format of points of each client below me in the binary model tree. How can I design these tables to make my life easier in this kind of search ? I will always get my soons up to 9 levels down.
I know that with procedure I can solve this problem , however I would like to know an elegant and simple solution.
Thank you
I solve this using a with a recursive query:
with with_user_earns as (
-- get father information (start)
select father.id, father.str_name, father.id_father, father.ind_father_side_type, 1 as int_user_level from tb_user father where id = 9
union all
-- get all soons (stop condition)
select son.id, son.str_name, son.id_father, son.ind_father_side_type, WUE.int_user_level + 1 from tb_user as son inner join with_user_earns as WUE on son.id_father = WUE.id where son.id_father is not null /*and WUE.int_user_level < 9*/
)
-- show result
select with_user_earns.id, with_user_earns.str_name, with_user_earns.id_father, with_user_earns.ind_father_side_type, with_user_earns.int_user_level from with_user_earns order by with_user_earns.int_user_level, with_user_earns.id