tree structure in different tables - sql

I have four tables,
Level1 (id, name, idFather, LevelFather)
Level2 (id, name, idFather, LevelFather)
Level3 (id, name, idFather, LevelFather)
Level4 (id, name, idFather, LevelFather)
This logic allow build a tree, where the leaves are the items in Level4, and his father can take level 1, 2 or 3. In the same way, the items in Level3, can have a father that is in the 2 o 1.
There are any query to obtain the tree below for a given an id and a level, until a given level?
For example, is we have the nextdata:
Level1 - 001, GroupEnterprise1, 001, 1
Level2 - 001-1, Enterprise1, 001, 1
Level2 - 001-2, Enterprise2,001, 1
Level3 - 002-1, Enterprise3, 001-1, 2
Level4 - 003-1, Office 1, 001-1,3
Level4 - 003-2, Office 2, 001-2,3
Level4 - 003-3, Office 3, 001-2,3
Level4 - 003-4, Office 4, 001-1,3
I can want consult all the offices (items in level 4), that are are daughters, granddaughters and great-granddaughters off the group GroupEnterprise1, or the offices that are daughters of Enterprise3, o the enterprises that are daughters of GroupEnterprise1.
The parameters for query are Id, Level and Level until I wish build the tree.

I'm not sure I quite understand what you're trying to do. If you need a hierarchy, you should use a single table called "Levels" and have all of your Level information in that one table. You don't need 4 tables for this. You can perform a self-join to easily return parent information.
The problem is that in order to move through the chain, you would probably have to use some sort of loop. This is usually to be avoided in SQL statements because the query optimizer would have to generate an execution plan for every iteration of the loop (which is inefficient). It is possible to do with only SQL, but I recommend pulling a table with all the information, and parsing through it with a non-SQL programming language that isn't geared towards set-based operations.
Below is some sample code using a single Table for all 4 levels. Once the tree is built in the table, you just need to move through a FOR loop to display it how you want.
--Creates Temp table (This table will expire upon connection close)
CREATE TABLE [#Levels] ([id] INT, [name] NVARCHAR(256), [level] INT, [idFather] INT);
--Populate Temp table with some sample data
INSERT INTO #Levels VALUES (1,'AbsoluteParent',1,null)
INSERT INTO #Levels VALUES (2,'ChildItem1',2,1)
INSERT INTO #Levels VALUES (3,'ChildItem2',2,1)
INSERT INTO #Levels VALUES (4,'GrandChild',3,2)
--Display populated table
SELECT * FROM [#Levels]
--Create 2 instances of our Temp table and join (id > idFather) so that we can return information about the parent table.
SELECT [T1].[name] AS 'Name'
, [T2].[name] AS 'Parent Name'
FROM [#Levels] AS T1
LEFT JOIN [#Levels] T2 ON [T1].[idFather] = [T2].[id]
--We can even link another instance of our Temp table and give information about grandparents!
SELECT [T1].[name] AS 'Name'
, [T2].[name] AS 'Parent Name'
, [T3].[name] AS 'Grand Parent Name'
FROM [#Levels] AS T1
LEFT JOIN [#Levels] T2 ON [T1].[idFather] = [T2].[id]
LEFT JOIN [#Levels] T3 ON [T2].[idFather] = [T3].[id]
Perhaps what you are looking for is a recursive common table expression that feeds the output back into the function to display all children recursively. Here is a microsoft example: https://technet.microsoft.com/en-us/library/ms186243(v=sql.105).aspx
I simplified the microsoft example a little:
-- Create a temp Employee table.
CREATE TABLE #MyEmployees
(
EmployeeID smallint NOT NULL,
FirstName nvarchar(30) NOT NULL,
LastName nvarchar(40) NOT NULL,
Title nvarchar(50) NOT NULL,
ManagerID int NULL,
);
-- Populate the table with values.
INSERT INTO #MyEmployees VALUES
(1, N'Ken', N'Sánchez', N'Chief Executive Officer',NULL)
,(273, N'Brian', N'Welcker', N'Vice President of Sales',1)
,(274, N'Stephen', N'Jiang', N'North American Sales Manager',273)
,(275, N'Michael', N'Blythe', N'Sales Representative',274)
,(276, N'Linda', N'Mitchell', N'Sales Representative',274)
,(285, N'Syed', N'Abbas', N'Pacific Sales Manager',273)
,(286, N'Lynn', N'Tsoflias', N'Sales Representative',285)
,(16, N'David',N'Bradley', N'Marketing Manager',273)
,(23, N'Mary', N'Gibson', N'Marketing Specialist',16);
WITH DirectReports (ManagerID, EmployeeID, Title, Level)
AS
(
SELECT e.ManagerID, e.EmployeeID, e.Title, 0 AS Level
FROM #MyEmployees AS e
WHERE e.ManagerID is null
UNION ALL
SELECT e.ManagerID, e.EmployeeID, e.Title, d.Level + 1
FROM #MyEmployees AS e
INNER JOIN DirectReports AS d
ON e.ManagerID = d.EmployeeID
)
SELECT ManagerID, EmployeeID, Title, Level
FROM DirectReports

Related

Accessing to total number in each second level(Postgres Hierarchical Query Practice)

I was practicing on Postgres and stuck on a point that I couldn't find a way to achieve. I have a simple database which are the attributes:
CREATE TABLE public.department
(
"deptId" integer NOT NULL PRIMARY KEY,
name character varying(30) COLLATE pg_catalog."default" NOT NULL,
"parentId" integer,
"numEmpl" integer NOT NULL,
CONSTRAINT "department_parentId_fkey" FOREIGN KEY ("parentId")
REFERENCES public.department ("deptId") MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION
)
and then I have some data in the table. Short example is
insert into department values (1, 'Headquarter', 1, 10);
insert into department values (2, 'Sales', 1, 15);
insert into department values (3, 'Logistics', 1, 25);
...
I was trying to get the total number of people who are employeed in each second level department.
I am able to get the total number of employeed people in each department but according to my search in the internet this is possible with "Hierarchical Queries". Currently, I am using
parentId=1
while querying.
Any solutions for this? Thank you.
Here is one option:
with recursive cte as (
select deptid as rootid, deptid from department where parentid = 1 and deptid <> 1
union all
select c.rootid, d.deptid
from cte c
inner join department d on d.parentid = c.deptid and d.deptid <> 1
)
select rootid, count(*) cnt from cte group by rootid

SQL 2 Table Query

Fairly new to the SQL area of coding and looking for some assistance.
Essentially have 2 tables 1 for employees and 1 for instore which is where their payment details are stored.
Looking to have a query which lists their name and payment rate which are stored across the two tables.
So far I have
SELECT paymentRate
FROM inStore
WHERE employeeID IN (SELECT employeeID
FROM employee
WHERE employeeID = 'C1234567')
Which gives me the result of the payment rate. I need to have the name displayed with it which is stored in the employee table. However after a while of troubleshooting I am having difficulty with something I am sure is quite simple. However when I try to change my query I keep getting assorted errors. Any assistance would be appreciated! Thanks in advance!
If I understood your query, you mean that you would like the employee name displayed when you do your query? If so you would want to use a Join as part of your sql query.
For example say that you want employee Bob Bobbins to show up you would need a query like the following:
SELECT i.paymentRate, e.FirstName, e.LastName FROM inStore as i
inner join employee as e on i.employeeID = e.employeeID
WHERE i.employeeID ='C1234567'
the as i and e are just ways to establish alias's so you wont need to keep retyping employee.firstname etc over and over again
hope that helps
edit: it would be good as well to have a foreign key relationship established as well for referential integrity purposes
Hope your InStore table contains multiple records against a single employee. Please refer the below sql query. This query is written in T-SQL.
DECLARE #Employee TABLE
(
Id INT,
Name VARCHAR(MAX)
)
DECLARE #InStore TABLE
(
Id INT,
Rate MONEY,
EmpId INT
)
INSERT INTO #Employee ( Id, Name )
VALUES ( 1,'ABC'),( 2,'DEF'),( 3,'GHI')
INSERT INTO #InStore( Id, Rate, EmpId )
VALUES ( 1, 10, 1 ), ( 2, 20, 2 ), ( 3, 30, 3 ),( 4, 40, 3 )
SELECT emp.Id,emp.Name,SUM(ins.Rate)
FROM #Employee emp
INNER JOIN #InStore ins ON ins.EmpId = emp.Id
GROUP BY emp.Id, emp.Name

SQL groupby multiple columns

Tablename: EntryTable
ID CharityName Title VoteCount
1 save the childrens save them 1
2 save the childrens saving childrens 3
3 cancer research support them 10
Tablename: ContestantTable
ID FirstName LastName EntryId
1 Neville Vyland 1
2 Abhishek Shukla 1
3 Raghu Nandan 2
Desired output
CharityName FullName
save the childrens Neville Vyland
Abhishek Shukla
cancer research Raghu Nandan
I tried
select LOWER(ET.CharityName) AS CharityName,COUNT(CT.FirstName) AS Total_No_Of_Contestant
from EntryTable ET
join ContestantTable CT
on ET.ID = CT.ID
group by LOWER(ET.CharityName)
Please advice.
Please have a look at this sqlfiddle.
Have a try with this query:
SELECT
e.CharityName,
c.FirstName,
c.LastName,
sq.my_count
FROM
EntryTable e
INNER JOIN ContestantTable c ON e.ID = c.EntryId
INNER JOIN (
SELECT EntryId, COUNT(*) AS my_count FROM ContestantTable GROUP BY EntryId
) sq ON e.ID = sq.EntryId
I assumed you actually wanted to join with ContestantTable's EntryId column. It made more sense to me. Either way (joining my way or yours) your sample data is faulty.
Apart from that, you didn't want repeating CharityNames. That's not the job of SQL. The database is just there to store and retrieve the data. Not to format it nicely. You want to work with the data on application layer anyways. Removing repeating data doesn't make this job easier, it makes it worse.
Most people do not realize that T-SQL has some cool ranking functions that can be used with grouping. Many things like reports can be done in T-SQL.
The first part of the code below creates two local temporary tables and loads them with data for testing.
The second part of the code creates the report. I use two common table expressions (CTE). I could have used two more local temporary tables or table variables. It really does not matter with this toy example.
The cte_RankData has two columns RowNum and RankNum. If RowNum = RankNum, we are on the first instance of charity. Print out the charity name and the total number of votes. Otherwise, print out blanks.
The name of the contestant and votes for that contestant are show on the detail lines. This is a typical report with sub totals show at the top.
I think this matches the report output that you wanted. I ordered the contestants by most votes descending.
Sincerely
John Miner
www.craftydba.com
--
-- Create the tables
--
-- Remove the tables
drop table #tbl_Entry;
drop table #tbl_Contestants;
-- The entries table
Create table #tbl_Entry
(
ID int,
CharityName varchar(25),
Title varchar(25),
VoteCount int
);
-- Add data
Insert Into #tbl_Entry values
(1, 'save the childrens', 'save them', 1),
(2, 'save the childrens', 'saving childrens', 3),
(3, 'cancer research', 'support them', 10)
-- The contestants table
Create table #tbl_Contestants
(
ID int,
FirstName varchar(25),
LastName varchar(25),
EntryId int
);
-- Add data
Insert Into #tbl_Contestants values
(1, 'Neville', 'Vyland', 1),
(2, 'Abhishek', 'Shukla', 1),
(3, 'Raghu', 'Nandan', 2);
--
-- Create the report
--
;
with cte_RankData
as
(
select
ROW_NUMBER() OVER (ORDER BY E.CharityName ASC, VoteCount Desc) as RowNum,
RANK() OVER (ORDER BY E.CharityName ASC) AS RankNum,
E.CharityName as CharityName,
C.FirstName + ' ' + C.LastName as FullName,
E.VoteCount
from #tbl_Entry E inner join #tbl_Contestants C on E.ID = C.ID
),
cte_SumData
as
(
select
E.CharityName,
sum(E.VoteCount) as TotalCount
from #tbl_Entry E
group by E.CharityName
)
select
case when RowNum = RankNum then
R.CharityName
else
''
end as rpt_CharityName,
case when RowNum = RankNum then
str(S.TotalCount, 5, 0)
else
''
end as rpt_TotalVotes,
FullName as rpt_ContestantName,
VoteCount as rpt_Votes4Contestant
from cte_RankData R join cte_SumData S
on R.CharityName = S.CharityName

Is there a better way to get the last level of groups from a table?

I have a Group table and within that table, there is a ParentId column that denotes a groups parent within the Group table. The purpose is to build a dynamic menu from these groups. I know I can loop and grab the last child and construct a result set, but I'm curious if there's a more SQL-y way of accomplishing this.
The table has Id, ParentId and Title fields of int, int and varchar.
Basically, a hierarchy may be constructed this way (People is the base group):
People -> Male -> Boy
-> Man
-> Female
I want to grab the last child(ren) of each branch. So, {Boy, Man, Female} in this case.
As I mentioned, getting that info isn't a problem. I'm just looking for a better way of getting it without having to write a bunch of unions and loops where I can basically change the base group and traverse the entire hierarchy outward, dynamically. I'm not really a Db guy, so I don't know if there's a slick way of doing this or not.
To get the leaf levels for one of many hierarchies, you can use a Recursive Common Table Expressions (CTEs) to enumerate the hierarchy, and then check which members are not the parent of another group to filter to the leaves:
Declare #RootID int = 1
;with cte as (
select
Id,
ParentId,
Title
From
Groups
Where
Id = #RootID
Union All
Select
g.Id,
g.ParentId,
g.Title
From
cte c
Inner Join
Groups g
On c.Id = g.ParentID
)
Select
*
From
cte g
Where
Not Exists (
Select
'x'
From
Groups g2
Where
g2.ParentID = g.Id
);
You can also do this with a left join rather than a not exists
http://sqlfiddle.com/#!6/8f1aa/9
Since you are using SQL Server 2012 you could take advantage of hierarchyid; here is an example following Laurence's schema:
CREATE TABLE Groups
(
Id INT NOT NULL
PRIMARY KEY
, Title VARCHAR(20)
, HID HIERARCHYID
)
INSERT INTO Groups
VALUES ( 1, 'People', '/' ),
( 2, 'Male', '/1/' ),
( 3, 'Female', '/2/' ),
( 4, 'Boy', '/1/1/' ),
( 5, 'Man', '/1/2/' );
SELECT Id
, Title
FROM Groups
WHERE HID NOT IN ( SELECT HID.GetAncestor(1)
FROM Groups
WHERE HID.GetAncestor(1) IS NOT NULL )
http://sqlfiddle.com/#!6/00330/1/0
Results:
ID TITLE
3 Female
4 Boy
5 Man

Recursively grab all data based on a parent id

We have a table where rows recursively link to another row. I want to pull data associated with a given parentId and all it's children. Where parentId is one from the root row.
I thought I have seen or done something like that before, but I am unable to find it now. Can this be done in SQL or is it better to do this in code?
I want the list to look like this when I'm done:
Parent
Child
Grandchild
This can be done in SQL Server 2005 and above using Common Table Expressions (CTEs). Here is a great link from MSDN describing recursive queries: Recursive Queries Using Common Table Expressions
Here is an example:
If you imagine a hierarchical line of people, this query will let you see the complete line of any person AND calculates their place in the hierarchy. It can be modified to find any child relationship.
Instead of the ID of the person, you swap in the ID of the row you are using as your parent.
--Create table of dummy data
create table #person (
personID integer IDENTITY(1,1) NOT NULL,
name varchar(255) not null,
dob date,
father integer
);
INSERT INTO #person(name,dob,father)Values('Pops','1900/1/1',NULL);
INSERT INTO #person(name,dob,father)Values('Grandma','1903/2/4',null);
INSERT INTO #person(name,dob,father)Values('Dad','1925/4/2',1);
INSERT INTO #person(name,dob,father)Values('Uncle Kev','1927/3/3',1);
INSERT INTO #person(name,dob,father)Values('Cuz Dave','1953/7/8',4);
INSERT INTO #person(name,dob,father)Values('Billy','1954/8/1',3);
DECLARE #OldestPerson INT;
SET #OldestPerson = 1; -- Set this value to the ID of the oldest person in the family
WITH PersonHierarchy (personID,Name,dob,father, HierarchyLevel) AS
(
SELECT
personID
,Name
,dob
,father,
1 as HierarchyLevel
FROM #person
WHERE personID = #OldestPerson
UNION ALL
SELECT
e.personID,
e.Name,
e.dob,
e.father,
eh.HierarchyLevel + 1 AS HierarchyLevel
FROM #person e
INNER JOIN PersonHierarchy eh ON
e.father = eh.personID
)
SELECT *
FROM PersonHierarchy
ORDER BY HierarchyLevel, father;
DROP TABLE #person;