CONNECT BY or hierarchical queries in RDBMS other than Oracle - sql

Oracle ships with a very handy feature. You can create hierarchical queries (recursive behaviour) using the following clause:
CONNECT BY [NOCYCLE] {condition [AND condition...]} [START WITH condition]
As documented here:
http://download.oracle.com/docs/cd/B28359_01/server.111/b28286/queries003.htm
I'm wondering, are there any other established RDBMS that support an equivalent or similar syntax? Or can recursive behaviour like this be generically simulated using regular SQL?
A good example that I'd like to be able to simulate is this (taken from the Oracle documentation):
SELECT LPAD(' ', 2 * (LEVEL-1)) || last_name org_chart,
employee_id, manager_id, job_id
FROM employees
START WITH job_id = 'AD_VP'
CONNECT BY PRIOR employee_id = manager_id;
Resulting in:
ORG_CHART EMPLOYEE_ID MANAGER_ID JOB_ID
------------------ ----------- ---------- ----------
Kochhar 101 100 AD_VP
Greenberg 108 101 FI_MGR
Faviet 109 108 FI_ACCOUNT
Chen 110 108 FI_ACCOUNT
Sciarra 111 108 FI_ACCOUNT
Urman 112 108 FI_ACCOUNT
Popp 113 108 FI_ACCOUNT
Whalen 200 101 AD_ASST
Mavris 203 101 HR_REP
Baer 204 101 PR_REP
Higgins 205 101 AC_MGR
Gietz 206 205 AC_ACCOUNT
De Haan 102 100 AD_VP
Hunold 103 102 IT_PROG
Ernst 104 103 IT_PROG
Austin 105 103 IT_PROG
Pataballa 106 103 IT_PROG
Lorentz 107 103 IT_PROG
The LEVEL pseudo column and the indentation achieved with it is not so important to me

SQL Server uses common table expressions (WITH statement) to achieve the same (see Recursive Queries Using Common Table Expressions).
This kind of query can also be used in Oracle (starting with 11g if I'm not mistaken).
The resulting query is more complex:
WITH emp(employee_id, manager_id, job_id, last_name, lvl)
AS (
SELECT e.employee_id, e.manager_id, e.job_id, e.last_name, 1 lvl
FROM employees e
WHERE job_id = 'AD_VP'
UNION ALL
SELECT e.employee_id, e.manager_id, e.job_id, e.last_name, r.lvl + 1 lvl
FROM employees e
JOIN emp r ON r.employee_id = e.manager_id
)
SELECT LPAD(' ', 2 * (lvl-1)) || last_name org_chart,
employee_id, manager_id, job_id
FROM emp;

There is an article on the developerworks site Port CONNECT BY to DB2 that does a nice conversion.
Also an interesting article on Explain Extended (Quassnoi's blog) that shows some difference between CONNECT BY and recursive CTE: Adjacency list vs. nested sets: Oracle, being row-based and set-based. He has also a nice article about "SQL Server: are the recursive CTE’s really set-based?". It seems that the "recursive CTE in Oracle is also not set based". I hope this helps with the conversion, recursion in JOOQ and understanding the difference of both implementations of recursion in SQL.
Regards,
JJ.

A trawl through SO showed the following questions and answers that deal with hierarchical queries over a variety of databases. The last of these refers to a MySql resource, that gives a generic SQL approach.
Building a Table Dependency Graph With A Recursive Query
Recursive select in SQL
SQL recursive query
Generating Depth based tree from Hierarchical Data in MySQL (no CTEs)

Related

How to view the column data based on another column data in the same table?

SQL> select employeeid, fname, salary, supervisor from employee;
EMPLOYEEID FNAME SALARY SUPERVISOR
---------- --------------- ---------- ----------
111 Heng 265000
246 Ananda 150000 111
231 Nagapan 85000 246
123 Vellu 105000 111
443 Fatimah 80000 111
433 Amin 66500 443
323 Kamuingat 76500 443
200 Jing 24500 135
135 Hong 45000 111
322 Derek 75000 123
128 Dong 38000 135
248 Fatt 30000 135
I want the supervisor column view as the supervisor name. the name already exists in the fname. How to make the supervisor name view based on the id in the employeeid column?
You probably want something like this:
select e.employeeid, e.fname, e.salary, s.fname
from employee e join employee s on (e.supervisor = s.employeeid)
You are joining the employee table to itself using the supervisor column as the id column. If that doesn't make sense, look up joins in the docs.
I think you need to self join using outer join otherwise the first record where the supervisor is null will be omitted from the result.
So I guess following is the query which you should try:
select e.employeeid, e.fname, e.salary, s.fname
from employee e left join employee s on (e.supervisor = s.employeeid)
Note that I have done left join.
Cheers!!

How to select unmatched records in Single Table In Self Join

EMPLOYEE_ID EMPLOYEE MANAGER_ID
100 Steven -
101 Neena 100
102 Lex 100
103 Alexander 102
104 Bruce 103
select the Employee Name & Manager Name select who not have manager
It could be much easier to do this without a join - just check the manager ID:
SELECT *
FROM employees
WHERE manager_id IS NULL

SQL with nested group function

I'm using Oracle db 11g
I have a table 'EMPLOYEES' like this...
ID JOB_ID SALARY
100 AD_PRES 24000
101 AD_VP 17000
102 AD_VP 17000
103 IT_PROG 9000
104 IT_PROG 6000
107 IT_PROG 4200
124 ST_MAN 5800
141 ST_CLERK 3500
142 ST_CLERK 3100
143 ST_CLERK 2600
144 ST_CLERK 2500
149 SA_MAN 10500
174 SA_REP 11000
176 SA_REP 8600
178 SA_REP 7000
200 AD_ASST 4400
201 MK_MAN 13000
202 MK_REP 6000
205 AC_MGR 12000
206 AC_ACCOUNT 8300
And I want to get the maximum of average salary of each job(job_id) and the job of it.
I firstly tried this and It resulted in error
SELECT MAX(AVG(salary)) AS max_avg_salary, job_id
FROM employees
GROUP BY job_id;
ORA-00937: not a single-group group function
00937. 00000 - "not a single-group group function"
*Cause:
*Action:
I finally made it with this code
WITH emp AS (SELECT AVG(salary) AS avg_salary, job_id FROM employees GROUP BY job_id)
SELECT e1.avg_salary AS max_avg_salary, e1.job_id
FROM emp e1 JOIN (SELECT MAX(avg_salary) AS max_avg_salary FROM emp) e2
ON e1.avg_salary = e2.max_avg_salary;
MAX_AVG_SALARY JOB_ID
24000 AD_PRES
What I want to know is..
Why my first code makes error?
Is there any better (more simple or easier) way than my code?
How about this?
select job_id, salary
from ( select job_id,
avg (salary) salary,
rank () over (order by avg (salary) desc) rnk
from employees
group by job_id)
where rnk = 1;
you can modify your query like this,
SELECT MAX(avg_salary)
FROM (SELECT AVG(salary) AS avg_salary, job_id
FROM employees
GROUP BY job_id);
or if you want to display the job id also,
SELECT avg_salary, job_id
FROM (SELECT AVG(salary) AS avg_salary, job_id
FROM employees
GROUP BY job_id
ORDER BY avg_salary DESC)
WHERE ROWNUM = 1;
[Edited] ... Unless you meant to get just a single answer, in which case the other respondent's solution should work and I'll offer a variation:
Non-Oracle:
SELECT TOP 1 *
FROM
(SELECT AVG(salary) AS avg_salary, job_id
FROM employees
GROUP BY job_id
) a
ORDER BY a.avg_salary DESC
Littlefoot pointed out, quite correctly, that this won't work on Oracle because of the TOP 1. You should pick his solution. I'm going to leave this here for any non-Oracle folks:
However, the asker himself, C Park, suggested an Oracle-compatible variation on this using ROWNUM. He's tested it and it worked for him so this will work in..
Oracle:
SELECT *
FROM
(SELECT AVG(salary) AS avg_salary, job_id
FROM employees
GROUP BY job_id
) a
ORDER BY a.avg_salary DESC
WHERE ROWNUM = 1
I hope this helps.

How to update values of a column based on other table in Oracle SQL syntax

I have an emp table with these columns
emp_id f_name l_name salary dept_id
----------------------------------------------------
100 Steven King 24000 90
101 Neena Kochhar 17000 50
102 Lex De Haan 17000 90
103 Alexander Hunold 9000 60
now I have t_emp table with these columns:
f_name l_name salary dept_id
-------------------------------------------
Steven King 24000 null
Neena Kochhar 17000 null
Lex De Haan 17000 null
Alexander Hunold 9000 null
Assume dept_id column was recently added here.
I want to update t_emp.dept_id column to be the same as emp.dept_id column.
How could I do that?
When I try below insert into query, I get error msg :
Cannot insert NULL into ("GAURAV"."T_EMP"."LAST_NAME")
insert into t_emp(dept_id)
select dept_id
from emp;
How could I do this single column update in t_emp table based on emp table?
Assuming the first and last names provide the match between the tables, then you can use a correlated subquery like this:
update t_emp te
set dept_id = (select e.dept_id
from emp e
where e.f_name = te.f_name and e.l_name = te.l_name
);
You can add salary equivalence in as well, if that is important.
Note that actually storing the column is not important. You could fetch the information using a join:
select . . ., e.dept_id
from t_emp te join
emp e
on e.f_name = te.f_name and e.l_name = te.l_name;
It is usually better to save such information in one place and use joins to get the right info.

Why cannot exclude 'IT_PROG' in SQL query?

The database data is in
http://www.w3resource.com/mysql-exercises/aggregate-function-exercises/write-a-query-to-get-the-average-salary-for-each-job-id-excluding-programmer.php
The result:
sqlite> SELECT job_id, AVG(salary)
...> FROM employees
...> WHERE job_id <> 'IT_PROG'
...> GROUP BY job_id;
JOB_ID AVG(salary)
------------ -----------
AC_ACCOUNT 8300.0
AC_MGR 12000.0
AD_ASST 4400.0
AD_PRES 24000.0
AD_VP 17000.0
FI_ACCOUNT 7920.0
FI_MGR 12000.0
HR_REP 6500.0
**IT_PROG 5760.0**
MK_MAN 13000.0
MK_REP 6000.0
PR_REP 10000.0
PU_CLERK 2780.0
PU_MAN 11000.0
SA_MAN 12200.0
SA_REP 8350.0
SH_CLERK 3215.0
ST_CLERK 2785.0
ST_MAN 7280.0
sqlite>
Above result still include IT_PROG, why?
It looks a correct query. You should look at database settings for the "string" type.
Try also to apply trim function that removes the blank spaces:
SELECT job_id, AVG(salary) FROM employees WHERE TRIM(job_id) <> 'IT_PROG' GROUP BY job_id;