Select all child record but need values of parent or grandparent record - sql

I'm working on a project that uses a MariaDB v5.5 database to keep track of employees in a tree based higherarchy. Each person in this tree can have various 'flags' associated with them. In this case, these flags are stored using a bitmask.
My objects look like the following
Employee Table description
+--------------+-------------+--------------------------------------+
| Name | Field | Description |
+--------------+-------------+--------------------------------------+
| employee_id | INT | Unique key |
| name | VARCHAR(45) | Employees name |
| flags | INT(4) | Bitmask of employee attributes |
| parent_id | INT | the employee_id of the parent record |
+--------------+-------------+--------------------------------------+
'flag' bitmap description
+-----+--------------+
| Bit | Flag |
+-----+--------------+
| 0 | CEO |
| 1 | MANAGER |
| 2 | PROJECT_LEAD |
| 3 | SALES_PERSON |
| 4 | MAINTANCE |
+-----+--------------+
Employee Table Data
+----+--------+------------+---------------------------+
| id | name | parent_id | flags |
+----+--------+------------+---------------------------+
| 1 | Lisa | NULL | CEO |
| 2 | Steve | 1 | MANAGER |
| 3 | Pat | 1 | MANAGER |
| 4 | Mary | 2 | SALES_PERSON,PROJECT_LEAD |
| 5 | Phil | 4 | SALES_PERSON,MAINTANCE |
| 6 | Jim | 3 | SALES_PERSON,MAINTANCE |
| 7 | Anna | 3 | SALES_PERSON,MAINTANCE |
+----+--------+------------+---------------------------+
Let's say I want to query all employees who have the "MAINTANCE" flag BUT, I need to return the id of the parent record that has the "MANAGER" flag. So my result should look like this.
> SELECT id, name, manager_id, manager_name FROM ...
+----+--------+------------+--------------+
| ID | Name | manager_id | manager_name |
+----+--------+------------+--------------+
| 5 | Phil | 2 | Steve |
| 6 | Jim | 3 | Pat |
| 7 | Anna | 3 | Pat |
+----+--------+------------+--------------+
So how do I build my query to give me what I want?
Note, that this tree can be more than just 3 levels deep and the query still needs to work.

Select
a.ID
,a.Name
,a.manager_ID
,b.Manager_Name
From
EmployeeTable As a
Left Join
EmployeeTable As b
On
a.Mangager_ID = b.ID
Where
a.Flag = 'Maintenance'
And
b.Flag = 'Manager'
This should give you what you are looking for.

Related

How to select 2 data from 1 column based on other relations in other table

Say, I have a table like the following called "name":
| nid | name |
----------------
| 1 | john |
| 2 | mike |
| 3 | tom |
| 4 | jack |
| 5 | will |
| 6 | david | ...
and another table like the following called "relation_father_son":
| rid | fnid | snid |
---------------------
| 1 | 1 | 2 |
| 2 | 1 | 3 |
| 3 | 4 | 5 |
| 4 | 2 | 6 | ...
then I would like a result like the following:
| father | son |
------------------
| john | mike |
| john | tom |
| jack | will |
| mike | david | ...
What the should the SQL query be?
The query would be:
SELECT
f.name AS father,
s.name AS son
FROM relation_father_son
INNER JOIN name AS f
ON (nid = fnid)
INNER JOIN name AS s
ON (nid = snid)
First of all, it is confusing that the first table is named as name. You should rename it to a more distinguished name, such as family_names. Read more at: Is name a reserved word in MySQL?
For the desired result, you can use the following query:
SELECT
(SELECT `name` FROM `family_names` WHERE nid=fnid) AS father,
(SELECT `name` FROM `family_names` WHERE nid=snid) AS son
FROM relation_father_son

Aggregate values by parents recursively in Oracle

Consider the the following example structure:
DEPARTMENT
ID
PARENT_ID
NAME
DEPTH
PROJECT
ID
NAME
COST
DEPARTMENT_ID
Some data, just for the sake of the examples bellow:
| ID | PARENT_ID | NAME | DEPTH |
|----|-----------|-------|-------|
| 1 | NULL | DEPT1 | 1 |
| 2 | 1 | DEPT2 | 2 |
| 3 | 1 | DEPT3 | 2 |
| 4 | 2 | DEPT4 | 3 |
| 5 | 3 | DEPT5 | 3 |
| 6 | NULL | DEPT6 | 1 |
| 7 | 6 | DEPT7 | 2 |
| ID | NAME | COST | DEPARTMENT_ID |
|------|--------|-------|---------------|
| 1 | PRJ1 | 100 | 1 |
| 2 | PRJ2 | 200 | 2 |
| 3 | PRJ3 | 300 | 3 |
| 4 | PRJ4 | 400 | 4 |
| 5 | PRJ5 | 500 | 5 |
| 6 | PRJ6 | 600 | 6 |
| 7 | PRJ7 | 700 | 7 |
Now, I need to somehow aggregate the costs of the, projects by one department and then by its direct children.
If the choosen filter is DEPT1, the intented result is:
| LINE | DEPARTMENT_ID | PARENT_ID | NAME | AGGREGATE_COST |
|------|----------------|-----------|--------|----------------|
| 1 | 1 | NULL | DEPT1 | 1500 |
| 2 | 2 | 1 | DEPT2 | 600 |
| 3 | 3 | 1 | DEPT3 | 800 |
Where:
Line 3 aggregate is PRJ5 (of DEPT5, which is child of DEPT3) + PRJ3 (of DEPT3) cost
Line 2 aggregate is PRJ4 (of DEPT4, which is child of DEPT2) + PRJ2 (of DEPT2) cost
Line 1 aggregate is the sum of his childrens aggregates.
PRJ6 and PRJ7 costs are ignored because the are from DEPT6 and DEPT7, and those are not in the hierachy of DEPT1 (DEPT6 would be his sibling, not child)
EDIT:
| ID | NAME | COST | DEPARTMENT_ID |
|------|--------|-------|---------------|
| 1 | PRJ1 | 1 | 1 |
| 2 | PRJ2 | 1 | 1 |
| 3 | PRJ3 | 1 | 2 |
| 4 | PRJ4 | 1 | 2 |
| 5 | PRJ5 | 1 | 4 |
In this scenario, the solution ivanzg presented, doesn't seem to work.
I get doubled results for the projects in the highers ranks
If I get the aggregate for DEPT1, it returns something similar to this:
| LINE | DEPARTMENT_ID | PARENT_ID | NAME | AGGREGATE_COST |
|------|----------------|-----------|--------|----------------|
| 1 | 1 | NULL | DEPT1 | 8 |
| 2 | 2 | NULL | DEPT1 | 4 |
You can tag rows in a hierarchy query (to later create groups) by using CONNECT_BY_ROOT hierarchy operator. In the hierarchy query, by making all rows root rows you create every hierarchy combination, later only specified combinations are taken and aggregated. For your test data this returns what you specified.
SELECT ROOT_DEPT AS DEPARTMENT_ID
,ROOT_PARENT AS PARENT_ID
,ROOT_NAME AS NAME
,SUM(COST) AS AGGREGATE_COST
FROM (SELECT COST
,CONNECT_BY_ROOT DEPARTMENT_ID ROOT_DEPT
,CONNECT_BY_ROOT PARENT_ID ROOT_PARENT
,CONNECT_BY_ROOT NAME ROOT_NAME
FROM (SELECT B.DEPARTMENT_ID
,NVL(A.PARENT_ID,'0') PARENT_ID
,A.NAME
,SUM(B.COST) COST
FROM DEPARTMENT A
JOIN PROJECT B
ON A.ID = B.DEPARTMENT_ID
--> GROUP COST OF PROJECTS IN THE SAME DEPARTMENT IF THERE ARE ANY
GROUP BY B.DEPARTMENT_ID
,NVL(A.PARENT_ID,'0')
,A.NAME
)
--> MAKE ALL ROWS ROOT ROWS
CONNECT BY PRIOR DEPARTMENT_ID = PARENT_ID
)
WHERE ROOT_DEPT = 1 OR ROOT_PARENT = 1
GROUP BY ROOT_DEPT
,ROOT_PARENT
,ROOT_NAME

SQL compare multiple rows or partitions to find matches

The database I'm working on is DB2 and I have a problem similar to the following scenario:
Table Structure
-------------------------------
| Teacher Seating Arrangement |
-------------------------------
| PK | seat_argmt_id |
| | teacher_id |
-------------------------------
-----------------------------
| Seating Arrangement |
-----------------------------
|PK FK | seat_argmt_id |
|PK | Row_num |
|PK | seat_num |
|PK | child_name |
-----------------------------
Table Data
------------------------------
| Teacher Seating Arrangement|
------------------------------
| seat_argmt_id | teacher_id |
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
| 4 | 1 |
| 5 | 2 |
------------------------------
---------------------------------------------------
| Seating Arrangement |
---------------------------------------------------
| seat_argmt_id | row_num | seat_num | child_name |
| 1 | 1 | 1 | Abe |
| 1 | 1 | 2 | Bob |
| 1 | 1 | 3 | Cat |
| | | | |
| 2 | 1 | 1 | Abe |
| 2 | 1 | 2 | Bob |
| 2 | 1 | 3 | Cat |
| | | | |
| 3 | 1 | 1 | Abe |
| 3 | 1 | 2 | Cat |
| 3 | 1 | 3 | Bob |
| | | | |
| 4 | 1 | 1 | Abe |
| 4 | 1 | 2 | Bob |
| 4 | 1 | 3 | Cat |
| 4 | 2 | 2 | Dan |
---------------------------------------------------
I want to see where there are duplicate seating arrangements for a teacher. And by duplicates I mean where the row_num, seat_num, and child_name are the same among different seat_argmt_id for one teacher_id. So with the data provided above, only seat id 1 and 2 are what I would want to pull back, as they are duplicates on everything but the seat id. If all the children on the 2nd table are exact (sans the primary & foreign key, which is seat_argmt_id in this case), I want to see that.
My initial thought was to do a count(*) group by row#, seat#, and child. Everything with a count of > 1 would mean it's a dupe and = 1 would mean it's unique. That logic only works if you are comparing single rows though. I need to compare multiple rows. I cannot figure out a way to do it via SQL. The solution I have involves going outside of SQL and works (probably). I'm just wondering if there is a way to do it in DB2.
Does this do what you want?
select d.teacher_id, sa.row_num, sa.seat_num, sa.child_name
from seatingarrangement sa join
data d
on sa.seat_argmt_id = d.seat_argmt_id
group by d.teacher_id, sa.row_num, sa.seat_num, sa.child_name
having count(*) > 1;
EDIT:
If you want to find two arrangements that are the same:
select sa1.seat_argmt_id, sa2.seat_argmt_id
from seatingarrangement sa1 join
seatingarrangement sa2
on sa1.seat_argmt_id < sa2.seat_argmt_id and
sa1.row_num = sa2.row_num and
sa1.seat_num = sa2.seat_num and
sa1.child_name = sa2.child_name
group by sa1.seat_argmt_id, sa2.seat_argmt_id
having count(*) = (select count(*) from seatingarrangement sa where sa.seat_argmt_id = sa1.seat_argmt_id) and
count(*) = (select count(*) from seatingarrangement sa where sa.seat_argmt_id = sa2.seat_argmt_id);
This finds the matches between two arrangements and then verifies that the counts are correct.

sort a table while keeping the hierarchy of rows

I have a table which represents the hierarchy of departments:
+-----------+--------------+--------------+--------------+-----------+-------+
| Top Dept. | 2-tier Dept. | 3-tire Dept. | 4-tier Dept. | name | tier |
+-----------+--------------+--------------+--------------+-----------+-------+
| 00 | | | | abc | 0 |
| | 00-01 | | | bcd | 1 |
| | | 00-01-01 | | cde | 2 |
| | | 00-01-02 | | abc | 2 |
| | 00-02 | | | aef | 1 |
| | | 00-02-01 | | qwe | 2 |
| | | 00-02-03 | | abc | 2 |
| | | | 00-02-03-01 | abc | 3 |
+-----------+--------------+--------------+--------------+-----------+-------+
now I want to sort the rows which are in the same tier by their names while keeping the hierarchy overall, That's what I expect:
+-----------+--------------+--------------+--------------+-----------+-------+
| Top Dept. | 2-tier Dept. | 3-tire Dept. | 4-tier Dept. | name | tier |
+-----------+--------------+--------------+--------------+-----------+-------+
| 00 | | | | abc | 0 |
| | 00-02 | | | aef | 1 |
| | | 00-02-03 | | abc | 2 |
| | | 00-02-01 | | qwe | 2 |
| | 00-01 | | | def | 1 |
| | | 00-01-02 | | abc | 2 |
| | | 00-01-01 | | cde | 2 |
| | | | 00-02-03-01 | abc | 3 |
+-----------+--------------+--------------+--------------+-----------+-------+
the missing data means null, I'm using Oracle DB, can anyone help me?
EDIT: Actually, it's a simple version of this sql, I've tried to add a new column which concats the values of the first four columns and then order by it and by name, but it did't work.
Update: This appears to be working... SQL Fiddle
All that was really needed from my original comment was to amend name to department in that order in both selects. This allows the engine to sort by name first, while maintaining the hierarchy.
WITH cte(Dept, superiorDept, name, depth, sort)AS (
SELECT
Dept,
superiorDept,
name,
0,
name|| dept
FROM hierarchy h
WHERE superiorDept IS NULL
UNION ALL
SELECT
h2.Dept,
h2.superiorDept,
h2.name,
cte.depth + 1,
cte.sort || h2.name ||h2.dept
FROM hierarchy h2
INNER JOIN cte ON h2.superiorDept = cte.Dept
)
SELECT
CASE WHEN depth = 0 THEN Dept END AS 一级部门,
CASE WHEN depth = 1 THEN Dept END AS 二级部门,
CASE WHEN depth = 2 THEN Dept END AS 三级部门,
CASE WHEN depth = 3 THEN Dept END AS 四级部门,
name,
depth,
sort
FROM cte
ORDER BY sort, name

how to undo a wrongly performed query which affected the records in SQL

I added a new column to already existed Table
alter table Employee
add Emp_dept varchar(50)
try to insert columns in to table now but output was not as i was expected
insert into Employee(Emp_dept)
values ('Accounting'),
('Accounting'),
('Technical'),
('Technical'),
('Managing'),
('Managing');
Assuming you began with a table:
Employee
+----+-------+
| ID | Name |
+----+-------+
| 1 | Joe |
| 2 | Bob |
| 3 | Mary |
+----+-------+
Then applied the ALTER TABLE, you probably obtained:
Employee
+----+-------+----------+
| ID | Name | Emp_dept |
+----+-------+----------+
| 1 | Joe | NULL |
| 2 | Bob | NULL |
| 3 | Mary | NULL |
+----+-------+----------+
If you then performed the insert, and it worked at all, I expect you would then have something like:
Employee
+----+-------+------------+
| ID | Name | Emp_dept |
+----+-------+------------+
| 1 | Joe | NULL |
| 2 | Bob | NULL |
| 3 | Mary | NULL |
| 4 | NULL | Accounting |
| 5 | NULL | Accounting |
| 6 | NULL | Technical |
| 7 | NULL | Technical |
| 8 | NULL | Managing |
| 9 | NULL | Managing |
+----+-------+------------+
Here I've assumed that ID is an autoincrement column and Name is NULLable.
To reverse this, you could try:
DELETE FROM Employee WHERE Emp_dept IS NOT NULL
Followed by:
ALTER TABLE Employee DROP COLUMN Emp_dept
This should return you to where you were prior to adding the column, which appears to be what you want.