I'm trying to write an SQL query (in Oracle DB) which performs the following:
Table:
Id | Name | Father_id
1 | John | 2
2 | Peter |
3 | Ann | 2
Expected result:
Name | Father_Name
John Peter
Ann Peter
I would like to list all the people who have a father in one row with the father's name. The user can have (of course) max. one father, but has not neccessarily one.
Which would be the best way to write such a query?
self join, if you want fatherless children, do a left outer join
select *
from table t_child join
table t_father on t_child.father_id = t_father.id
try this sql it will definatlly work
select child.MenuName as ChildName,parent.MenuName as ParentName from table_name as child
left join table_name as parent on child.parentId = parent.MenuId
you need to use left because you mention that not necessarily one
You do not need to use a self-join (and two table/index scans) and can instead do it with a hierarchical query:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE people ( Id, Name, Father_id ) AS
SELECT 1, 'John', 2 FROM DUAL UNION ALL
SELECT 2, 'Peter', NULL FROM DUAL UNION ALL
SELECT 3, 'Ann', 2 FROM DUAL
Query 1:
SELECT name,
PRIOR name AS father_name
FROM people
WHERE LEVEL > 1
OR CONNECT_BY_ISLEAF = 0 -- Comment out if you do not want Peter,NULL
CONNECT BY PRIOR id = father_id
Results:
| NAME | FATHER_NAME |
|-------|-------------|
| Peter | (null) |
| John | Peter |
| Ann | Peter |
Related
I'm relatively new to SQL and currently making some practical tasks to gain experience and got struggled with an update of my custom overview table with values from another table that contains join.
I have an overview table MyTable with column EmployeeID. AnotherTable contains data of employees with EmployeeID and their ManagerID.
I am able to retrieve ManagerName using different join methods, including:
SELECT m.first_name
FROM AnotherTable.employees e LEFT JOIN
AnotherTable.employees m
on m.EmployeeID = e.ManagerID
But I am getting stuck updating MyTable, as I usually receive errors such as "single row query returns more than one row" or "SQL command not properly ended". I've read that Oracle doesnt support joins for updating tables. How can I overcome this issue? A sample data would be:
MyTable
------------------------------
EmployeeID | SomeOtherColumns| ..
1 | SomeData |
2 | SomeData |
3 | SomeData |
4 | SomeData |
5 | SomeData |
------------------------------
OtherTable
-------------------------------------
EmployeeID | Name | ManagerID |
1 | Steve | - |
2 | John | 1 |
3 | Peter | 1 |
4 | Bob | 2 |
5 | Patrick | 3 |
6 | Connor | 1 |
-------------------------------------
And the result would be then:
MyTable
-------------------------------------------
EmployeeID | SomeOtherColumns |ManagerName|
1 | SomeData | - |
2 | SomeData | Steve |
3 | SomeData | Steve |
4 | SomeData | John |
5 | SomeData | Peter |
6 | SomeData | Steve |
-------------------------------------------
As one of the options I tried to use is:
update MyTable
set MyTable.ManagerName = (
SELECT
(m.name) ManagerName
FROM
OtherTable.employees e
LEFT JOIN OtherTable.employees m ON
m.EmployeeID = e.ManagerID
)
But there I get "single row query returns more than one row" error. How is it possible to solve this?
You can use a hierarchical query:
UPDATE mytable m
SET managername = (SELECT name
FROM othertable
WHERE LEVEL = 2
START WITH employeeid = m.employeeid
CONNECT BY PRIOR managerid = employeeid);
or a self-join:
UPDATE mytable m
SET managername = (SELECT om.name
FROM othertable o
INNER JOIN othertable om
ON (o.managerid = om.employeeid)
WHERE o.employeeid = m.employeeid);
Which, for the sample data:
CREATE TABLE MyTable (EmployeeID, SomeOtherColumns, ManagerName) AS
SELECT LEVEL, 'SomeData', CAST(NULL AS VARCHAR2(20))
FROM DUAL
CONNECT BY LEVEL <= 5;
CREATE TABLE OtherTable(EmployeeID, Name, ManagerID) AS
SELECT 1, 'Alice', NULL FROM DUAL UNION ALL
SELECT 2, 'Beryl', 1 FROM DUAL UNION ALL
SELECT 3, 'Carol', 1 FROM DUAL UNION ALL
SELECT 4, 'Debra', 2 FROM DUAL UNION ALL
SELECT 5, 'Emily', 3 FROM DUAL UNION ALL
SELECT 6, 'Fiona', 1 FROM DUAL;
Then after either update, MyTable contains:
EMPLOYEEID
SOMEOTHERCOLUMNS
MANAGERNAME
1
SomeData
null
2
SomeData
Alice
3
SomeData
Alice
4
SomeData
Beryl
5
SomeData
Carol
Note: Keeping this data violates third-normal form; instead, you should keep the employee name in the table with the other employee data and then when you want to display the manager's name use SELECT ... FROM ... LEFT OUTER JOIN with a hierarchical query to include the result. What you do not want to do is duplicate the data as then it has the potential to become out-of-sync when something changes.
db<>fiddle here
I have a table with columns id, forename, surname, created (date).
I have a table such as the following:
ID | Forename | Surname | Created
---------------------------------
1 | Tom | Smith | 2008-01-01
1 | Tom | Windsor | 2008-02-01
2 | Anne | Thorn | 2008-01-05
2 | Anne | Baker | 2008-03-01
3 | Bill | Sykes | 2008-01-20
Basically, I want this to return the most recent name for each ID, so it would return:
ID | Forename | Surname | Created
---------------------------------
1 | Tom | Windsor | 2008-02-01
2 | Anne | Baker | 2008-03-01
3 | Bill | Sykes | 2008-01-20
I get the desired result with this query.
SELECT id, forename, surname, created
FROM name n
WHERE created = (SELECT MAX(created)
FROM name
GROUP BY id
HAVING id = n.id);
I am getting the result I want but I fail to understand WHY THE IDS ARE NOT BEING REPEATED in the result set. What I understand about correlated subquery is it takes one row from the outer query table and run the inner subquery. Shouldn't it repeat "id" when ids repeat in the outer query? Can someone explain to me what exactly is happening behind the scenes?
First, your subquery does not need a GROUP BY. It is more commonly written as:
SELECT n.id, n.forename, n.surname, n.created
FROM name n
WHERE n.created = (SELECT MAX(n2.created)
FROM name n2
WHERE n2.id = n.id
);
You should get in the habit of qualifying all column references, especially when your query has multiple table references.
I think you are asking why this works. Well, each row in the outer query is tested for the condition. The condition is: "is my created the same as the maximum created for all rows in the name table with the same id". In your data, only one row per id matches that condition, so ids are not repeated.
You can also consider joining the tables by created vs max(created) column values :
SELECT n.id, n.forename, n.surname, n.created
FROM name n
RIGHT JOIN ( SELECT id, MAX(created) as created FROM name GROUP BY id ) t
ON n.created = t.created;
or using IN operator :
SELECT id, forename, surname, created
FROM name n
WHERE ( id, created ) IN (SELECT id, MAX(created)
FROM name
GROUP BY id );
or using EXISTS with HAVING clause in the subquery :
SELECT id, forename, surname, created
FROM name n
WHERE EXISTS (SELECT id
FROM name
GROUP BY id
HAVING MAX(created) = n.created
);
Demo
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 7 years ago.
Improve this question
I have a table that lists concurrent program IDs and names. However, the table allows more than one name for each program ID. I'm needing to report some data about the programs, but am having trouble getting it to display correctly, as the program name is what is being displayed and grouped by in Tableau.
So what I need is a way to pull the first name for each program ID from the table. Since I'm including the program name in my output, I can't use unique (it wouldn't filter the alternate names) & I can't use a join as there wouldn't be a unique match.
Several options (of varying efficiency) to get the (alphabetically) first name for each id.
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE PROGRAMS ( ID, NAME ) AS
SELECT 1, 'Charlie' FROM DUAL
UNION ALL SELECT 1, 'Bob' FROM DUAL
UNION ALL SELECT 1, 'Alice' FROM DUAL
UNION ALL SELECT 2, 'Ed' FROM DUAL
UNION ALL SELECT 2, 'Doris' FROM DUAL
UNION ALL SELECT 3, 'Fern' FROM DUAL
UNION ALL SELECT 3, 'Godfrey' FROM DUAL;
Query 1:
SELECT ID,
MIN( NAME ) AS NAME
FROM PROGRAMS
GROUP BY ID
ORDER BY ID
Results:
| ID | NAME |
|----|-------|
| 1 | Alice |
| 2 | Doris |
| 3 | Fern |
Query 2:
SELECT DISTINCT
ID,
FIRST_VALUE( NAME ) OVER ( PARTITION BY ID ORDER BY NULL ) AS NAME
FROM PROGRAMS
ORDER BY ID
Results:
| ID | NAME |
|----|-------|
| 1 | Alice |
| 2 | Doris |
| 3 | Fern |
Query 3:
SELECT ID,
NAME
FROM PROGRAMS p
WHERE NOT EXISTS ( SELECT 'X'
FROM PROGRAMS x
WHERE p.ID = x.ID
AND p.NAME > x.NAME )
ORDER BY ID
Results:
| ID | NAME |
|----|-------|
| 1 | Alice |
| 2 | Doris |
| 3 | Fern |
Say I have this table:
id | name
-------------
1 | john
2 | steve
3 | steve
4 | john
5 | steve
I only want the rows that are unique compared to the previous row, these:
id | name
-------------
1 | john
2 | steve
4 | john
5 | steve
I can partly achieve this by using this query:
SELECT *, (
SELECT `name` FROM demotable WHERE id=t.id-1
) AS prevName FROM demotable AS t GROUP BY prevName ORDER BY id ASC
But when I am using a query with multiple UNIONs and stuff, this gets way to complicated. Is there an easy way to do this (like GROUP BY, but than more specific)?
This should work, but I don't know if it's simpler :
select demotable.*
from demotable
left join demotable as prev on prev.id = demotable.id - 1
where demotable.name != prev.name
Imagine your typical manager / employee hierarchy, where you have an employee with a boss, who in turn has a boss, who in turn has a boss.
How would you write a query that would have a column with, say, all the boss's names as a varchar.
Given this data (leaving out the hierarchyid column but the id column here essentially describes that column):
id | name | bossid
1 | BigBoss | 0
2 | CTO | 1
3 | CTO Lackey | 2
4 | CIO | 1
5 | CIO Lackey | 4
End up with this resultset:
id | name | all boss names
1 | BigBoss |
2 | CTO |Big Boss
3 | CTO Lackey |Big Boss, CTO
4 | CIO |Big Boss
5 | CIO Lackey |Big Boss, CIO
Thanks!
Since you're on SQL 2008:
;WITH CTE_Tree AS
(
SELECT
id,
name,
'' AS boss_names
FROM
My_Table
WHERE
boss_id IS NULL
UNION ALL
SELECT
T.id,
T.name,
CTE.boss_names + ', ' + CTE.name
FROM
CTE_Tree CTE
INNER JOIN My_Table T ON
T.boss_id = CTE.id
)
SELECT
id,
name,
boss_names
FROM
CTE_Tree
This is off the top of my head, so you may need to tweak it. It will have an extra comma at the start of the boss_names. You can either remove that in your final query, or put in a check of CTE.boss_names in the CTE to decide whether or not to prepend the comma.