Multi-table queries Subqueries DBMAN 4 Multi-table queries Subqueries SzaboZs
SELECT Displayed order of suffixes INTO FROM WHERE GROUP BY HAVING UNION/MINUS INTERSECT ORDER BY SzaboZs
Multi-table queries Subqueries Examples DBMAN 4 Multi-table queries Subqueries Examples SzaboZs
SET OPERATORS [query] [set operator] [query] Important: two queries with same number of columns! Operators: UNION / UNION ALL, INTERSECT, MINUS / EXCEPT E.g.: SELECT min(sal) as ThreeValues FROM emp UNION SELECT avg(sal) FROM emp UNION SELECT max(sal) FROM emp; MySQL: MINUS = LEFT JOIN + WHERE … IS NULL SzaboZs
Table structure diagram SzaboZs
Querying multiple tables SELECT * FROM emp, dept; SELECT * FROM emp, emp; SELECT * FROM emp a, emp b; SELECT a.empno, a.ename, b.empno, b.ename FROM emp a, emp b; SELECT a.ename, a.deptno, b.deptno, b.dname FROM emp a, dept b; „Cross Join” Cartesian product EMP DEPT SzaboZs
"MANUAL JOIN" SELECT a.ename, a.deptno, b.deptno, b.dname FROM emp a, dept b WHERE a.deptno=b.deptno; Works – every record has a pair! SELECT a.empno, a.ename, a.mgr, b.empno, b.ename, b.mgr FROM emp a, emp b WHERE a.mgr=b.empno; Where is King??? no pair found! SzaboZs
JOIN Goes into the FROM part of a query SELECT * FROM table1, table2 SELECT * FROM table1 JOIN_EXPRESSION table2 JOIN_CONDITION Joining tables = connecting the foreign keys to the primary keys Joining indexed fields is fast, joining non-indexed (non-key) fields is very slow good-to-avoid, cannot-always-avoid SzaboZs
NATURAL / INNER JOIN Same result as with "MANUAL JOIN" Only shows the records with valid pairs SELECT * FROM emp NATURAL INNER JOIN dept; -- Not in MySQL/TSQL! SELECT * FROM emp NATURAL JOIN dept; -- Not in TSQL SELECT * FROM emp INNER JOIN dept using (deptno); -- Not in TSQL SELECT * FROM emp INNER JOIN dept on (emp.deptno=dept.deptno); -- Works everywhere! SzaboZs
LEFT JOIN … ON … It must be used if we want to display the records WHERE there is no matching primary key record The nonexistent records' fields will be filled with NULL values use IFNULL/ISNULL/NVL if needed SELECT a.ename, a.deptno, b.deptno, b.dname FROM emp a LEFT JOIN dept b ON a.deptno=b.deptno; -- No difference here, as every worker has a department SELECT a.empno, a.ename, a.mgr, b.empno, b.ename, b.mgr FROM emp a LEFT JOIN emp b ON a.mgr=b.empno; -- KING is shown! SzaboZs
LEFT/RIGHT JOIN … ON … SELECT a.ename, a.deptno, b.deptno, b.dname FROM emp a RIGHT JOIN dept b ON a.deptno=b.deptno; OPERATIONS (40) is shown as well! Show everyone as a boss, even those who are no bosses list unpaired records from the other side! SELECT a.empno, a.ename, a.mgr, b.empno, b.ename, b.mgr FROM emp a RIGHT JOIN emp b ON a.mgr=b.empno; SELECT a.empno, a.ename, a.mgr, b.empno, b.ename, b.mgr FROM emp b LEFT JOIN emp a ON a.mgr=b.empno; The LEFT JOIN is more used SzaboZs
JOIN / ORACLE ONLY SELECT * FROM emp d, emp f WHERE d.mgr=f.empno (+); SELECT * FROM emp d LEFT JOIN emp f ON (d.mgr=f.empno) SELECT * FROM emp d, emp f WHERE d.mgr(+)=f.empno; SELECT * FROM emp d RIGHT JOIN emp f ON (d.mgr=f.empno) SzaboZs
FULL JOIN LEFT JOIN + RIGHT JOIN SELECT a.empno, a.ename, a.mgr, b.empno, b.ename, b.mgr FROM emp a FULL JOIN emp b ON a.mgr=b.empno; [The standard names this type as UNION JOIN; unavailable in MySQL…] Use the "MANUAL" JOIN and the LEFT JOIN SzaboZs
BAD JOIN??? SELECT ename, sal, dname FROM emp, dept WHERE emp.deptno=dept.deptno; SELECT ename, sal, dname FROM emp, dept WHERE emp.deptno=dept.deptno AND ename like '%E%'; SELECT ename, sal, dname FROM emp, dept WHERE emp.deptno=dept.deptno AND ename like '%E%' OR sal<3000; SELECT ename, sal, dname FROM emp, dept WHERE emp.deptno=dept.deptno AND (ename like '%E%' OR sal<3000); SzaboZs
JOINING TABLES SzaboZs
JOINING TABLES SzaboZs
JOINING TABLES SzaboZs
JOINING TABLES SELECT * FROM emp worker, emp boss, dept work_dept, dept boss_dept WHERE worker.mgr=boss.empno AND worker.deptno=work_dept.deptno AND boss.deptno=boss_dept.deptno; Good, if we don't want to see the unpaired records SzaboZs
JOINING TABLES SELECT * FROM emp worker LEFT JOIN emp boss ON (worker.mgr=boss.empno), dept work_dept, dept boss_dept WHERE worker.deptno=work_dept.deptno AND boss.deptno=boss_dept.deptno; NOT CHANGES ANYTHING SzaboZs
JOINING TABLES SELECT * FROM emp worker LEFT JOIN emp boss ON (worker.mgr=boss.empno) LEFT JOIN dept boss_dept ON (boss.deptno=boss_dept.deptno) LEFT JOIN dept work_dept ON (worker.deptno=work_dept.deptno); PERFECT SzaboZs
JOINING TABLES After the join is made in the FROM/WHERE, the query can be built up just like as if it was a single-table query. Every suffix can be used, the result-table can be used the same way as with a single-table query PRACTICE and STRUCTURED QUERIES are required!!! SzaboZs
EXAMPLE Display every boss' name and the average salary of those sub-workers whose salary is bigger than 1000 USD. Only display those records where this average is smaller than 5000 USD. SzaboZs
EXAMPLE SELECT avg(a.sal) as AVERAGE, b.ename as BOSS FROM emp a, emp b WHERE (a.mgr=b.empno) AND (a.sal>1000) GROUP BY b.ename HAVING avg(a.sal)<5000 ORDER BY AVERAGE desc; SzaboZs
EXAMPLE SELECT avg(a.sal) as AVERAGE, b.ename as BOSS FROM emp a INNER JOIN emp b ON a.mgr=b.empno WHERE a.sal>1000 GROUP BY b.ename HAVING avg(a.sal)<5000 ORDER BY AVERAGE desc; SzaboZs
Multi-table queries Subqueries DBMAN 4 Multi-table queries Subqueries SzaboZs
SUB-QUERIES Basic principle: in some parts of the main query (field list, WHERE suffix, FROM suffix) there is another query instead of a simple field name/expression/constant/ table sub-query MySQL only supports it with the newer (>4.1) versions (with some angry limitations…) SzaboZs
Sub-queries in the field list You can put a constant in the field list you can put any sub-query there that returns with exactly one value SELECT sal, (SELECT max(sal) FROM emp) as maxSal, (SELECT max(sal) FROM emp)-sal as deltaSal FROM emp ORDER BY deltaSal desc; SzaboZs
Sub-queries in the FROM (inline view) You can put any table in the FROM every query returns a table almost any sub-query can be used in the FROM, that we can use as a table in that query The sub-query must have an alias and must be written between parentheses SELECT * FROM (SELECT a.empno, a.ename, a.mgr, b.empno, b.ename FROM emp a, emp b WHERE a.mgr=b.empno) sub ORDER BY ename asc; Field names must be unique – use an alias!!! SzaboZs
Sub-queries in the FROM SELECT bosses.work_name as Worker, bosses.boss_name as Boss FROM ( SELECT a.empno as work_id, a.ename as work_name, a.mgr as work_bossid, b.empno as boss_id, b.ename as boss_name FROM emp a, emp b WHERE a.mgr=b.empno) bosses ORDER BY Worker asc; SzaboZs
Sub-queries in the FROM SELECT emp.deptno, min, ename FROM ( Select deptno, min(sal) as min From emp Group by deptno ) minimums, emp WHERE emp.sal=minimums.min and emp.deptno=minimums.deptno; SzaboZs
Sub-query in the WHERE Use constant values in the WHERE use any sub-query that returns with exactly one value SELECT ename, sal FROM emp WHERE sal>(SELECT avg(sal) FROM emp); SELECT ename, sal FROM emp WHERE sal=(SELECT min(sal) FROM emp); SzaboZs
Sub-query in the WHERE ALWAYS VERY SLOW SUBQUERY IN THE FROM IS BETTER SELECT ename, sal FROM emp WHERE sal>(SELECT avg(sal) FROM emp); SELECT ename, sal FROM emp, (SELECT avg(sal) as avgSal FROM emp) sub WHERE sal>sub.avgsal; (No JOIN caresian product, but the subquery only has one row it simply adds the one avgSal field to emp) SzaboZs
Sub-query in the WHERE Use lists in the WHERE use any sub-query that returns with exactly one column Operators: [NOT] IN / EXISTS, ANY, ALL Usually used with a subset: SELECT sal FROM emp WHERE upper(job)='SALESMAN'; SELECT sal FROM emp WHERE deptno = 10; SzaboZs
Sub-query in the WHERE SELECT ename, sal FROM emp WHERE sal IN (SELECT sal FROM emp WHERE deptno = 10); SELECT ename, sal FROM emp WHERE sal NOT IN (SELECT sal FROM emp WHERE deptno = 10); Correlated sub-query STILL SLOW! Subquery into the FROM! IN INNER JOIN NOT IN LEFT JOIN + WHERE xxx IS NULL Optimization is not important this semester SzaboZs
Sub-query in the WHERE SELECT ename, sal FROM emp WHERE sal> ANY (SELECT sal FROM emp WHERE deptno = 10); SELECT ename, sal FROM emp WHERE sal>(SELECT min(sal) FROM emp WHERE deptno = 10); Same results Oracle does an internal sort with the ANY!!! STILL SLOW! Subquery into the FROM! SzaboZs
Sub-query in the WHERE SELECT ename, sal FROM emp WHERE sal> ALL (SELECT sal FROM emp WHERE deptno = 30); SELECT ename, sal FROM emp WHERE sal>(SELECT max(sal) FROM emp WHERE deptno = 30); Same results, No automatic sort Always use the ORDER BY, if required STILL SLOW! Subquery into the FROM! Subquery should always go to the FROM … But how to avoid loooooooooooooooong queries? SzaboZs
Fetch hierarchy in one query Oracle> SELECT level, empno, ename, mgr FROM emp START WITH empno=7839 CONNECT BY mgr=prior empno ORDER BY level desc; Standard, TSQL> no such way… TODO: recursive CTE … CTE = SUBSTITUTE a subquery with a name, usable only in the main query where the CTE is defined Similar object: views = STORE a subquery with a name, which is later usable in any main/subquery SzaboZs
CTE/VIEW CTE = Common Table Expression WITH xxx (columns) AS (SELECT …) SELECT * FROM xxx INNER JOIN otherTable … VIEW: GO CREATE VIEW xxx (columns) AS (SELECT …); GO SELECT * FROM xxx INNER JOIN otherTable … CREATE VIEW must be the only statement in the batch The column list is optional in both cases https://www.red-gate.com/simple-talk/sql/t-sql-programming/sql-server-cte-basics/ SzaboZs
CTE Example (AdventureWorks) Task: which are the orders with the biggest ratio compared to the monthlyTotal? Pre-solution: monthly totals SELECT SUM(TotalDue) as monthlyTotal, datepart(month, OrderDate) as month FROM Sales.SalesOrderHeader GROUP BY datepart(month, OrderDate) SzaboZs
Ratios with CTE WITH statsMonthly AS (SELECT SUM(TotalDue) as monthlyTotal, datepart(month, OrderDate) as month FROM Sales.SalesOrderHeader GROUP BY datepart(month, OrderDate)) SELECT sh.SalesOrderID, sh.OrderDate, sh.TotalDue, sm.monthlyTotal, sh.TotalDue/sm.monthlyTotal as ratio FROM Sales.SalesOrderHeader sh INNER JOIN statsMonthly sm ON (datepart(month,sh.OrderDate)=sm.month) ORDER BY ratio desc; SzaboZs
Ratios with VIEW IF object_id('statsMonthly', 'V') is not null DROP VIEW statsMonthly; GO CREATE VIEW statsMonthly AS (SELECT SUM(TotalDue) as monthlyTotal, datepart(month, OrderDate) as month FROM Sales.SalesOrderHeader GROUP BY datepart(month, OrderDate)); SzaboZs
Ratios with VIEW SELECT sh.SalesOrderID, sh.OrderDate, sh.TotalDue, sm.monthlyTotal, sh.TotalDue/sm.monthlyTotal as ratio FROM Sales.SalesOrderHeader sh INNER JOIN statsMonthly sm ON (datepart(month, sh.OrderDate)=sm.month) ORDER BY ratio desc; CTE or View? Used once vs used many times Recursive CTEs are allowed – Recursive views are not always allowed (Postgresql, Teradata) SzaboZs
Recursive CTE – Fetch whole hierarchy WITH recQ AS (SELECT empno, ename, mgr, 0 as wl FROM emp WHERE ename = 'KING' UNION ALL SELECT nplus1.empno, nplus1.ename, nplus1.mgr, recQ.wl+1 as wl FROM emp as nplus1, recQ WHERE recQ.empno = nplus1.mgr) SELECT ename, empno, mgr, wl FROM recQ SzaboZs
SzaboZs
SzaboZs