Theory, Practice & Methodology of Relational Database Design and Programming Copyright © Ellis Cohen 2002-2007 Advanced Relational Algebra These slides are licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 2.5 License. For more information on how you may use them, please see http://www.openlineconsult.com/db 1 Overview of Lecture Cross Joins and Natural Joins Joins & Renaming Natural Outer Joins Un-Natural Outer Joins Views Collection Operators Assignment & Reassignment Relational Equivalence & Completeness Relational Division © Ellis Cohen 2001-2007 2 Cross Joins & Natural Joins © Ellis Cohen 2001-2007 3 Natural Join Classic Relational Algebra Emps OtherEmpData REAL Emps |X| OtherEmpData SQL-92 (also in Oracle 9i, but not 8i) SELECT * FROM (Emps NATURAL JOIN OtherEmpData) © Ellis Cohen 2001-2007 4 Projected Join of 1:M Relationship Emps |X| Depts empno ename addr deptno dname 7499 ALLEN ... 30 SALES 7654 MARTIN … 30 SALES 7698 BLAKE … 30 SALES 7839 KING … 10 ACCOUNTING 7844 TURNER … 30 SALES 7986 STERN … 50 SUPPORT To just get each employee's name & department name, do SELECT ename, dname FROM (Emps NATURAL JOIN Depts) (Emps |X| Depts){ ename, dname } © Ellis Cohen 2001-2007 ENAME -----ALLEN MARTIN BLAKE KING TURNER STERN DNAME -----SALES SALES SALES ACCOUNTING SALES SUPPORT 5 REAL Parentheses Matter Restriction and Projection bind more tightly than Join Emps |X| Depts{ ename, dname } means (and in this case, neither would work) Emps |X| ( Depts{ ename, dname } ) What we want is (Emps |X| Depts){ ename, dname } © Ellis Cohen 2001-2007 6 Cross Product Joins SELECT * FROM Cats SELECT * FROM Cats, Dogs catid ----missy buffy puff catid ----missy missy missy missy buffy buffy buffy buffy puff puff puff puff SELECT * FROM Dogs dogid ----rover spot ubu duke dogid ----rover spot ubu duke rover spot ubu duke rover spot ubu duke Cats X Dogs © Ellis Cohen 2001-2007 7 Restricted Joins SELECT * FROM Cats, Dogs catid ----missy missy missy missy buffy buffy buffy buffy puff puff puff puff dogid ----rover spot ubu duke rover spot ubu duke rover spot ubu duke Restricted joins restrict a cross product with a join condition relating the columns from the specified tables SELECT * FROM Cats, Dogs WHERE length(catid) = length(dogid) catid ----missy buffy puff puff dogid ----rover rover spot duke Join condition (Cats X Dogs)[ length(catid) = length(dogid)] © Ellis Cohen 2001-2007 8 Cross Joins and Natural Joins In REAL • You can only use a cross join when the joined relations have attribute names which are all different • You can use a natural join to join relations on the attributes whose names are the same (Note: a natural join between two relations whose names are different is a cross join) © Ellis Cohen 2001-2007 9 DISTINCT Join Exercise List the names of employees who manage projects with budgets over 100000 Emps Projs empno ename job mgr hiredate sal comm deptno pno pname pmgr persons budget pstart pend © Ellis Cohen 2001-2007 10 Answer: DISTINCT Join Exercise List the names of employees who manage projects with budgets over 100000 Emps empno ename job mgr hiredate sal comm deptno Projs pno pname pmgr persons budget pstart pend > 100000 (Emps X Projs)[empno = pmgr][budget > 100000]{ ename ! } SELECT DISTINCT ename FROM (Emps JOIN Projs ON empno = pmgr) WHERE budget > 100000 © Ellis Cohen 2001-2007 11 Advanced Join/Grouping Exercise For each named department, list the average salary of the project managers in that department Depts deptno dname loc Emps empno ename job mgr hiredate sal comm deptno © Ellis Cohen 2001-2007 Projs pno pname pmgr persons budget pstart pend 12 Advanced Join/Grouping Answer For each named department, list the average salary of the project managers in that department Depts deptno dname loc Emps empno ename job mgr hiredate sal comm deptno Projs avg() pno pname pmgr persons budget pstart pend (Depts |X| Emps X Projs)[ empno = pmgr ] { empno, sal, dname ! }{ dname ! avgsal:avg(sal) } or ( (Emps X Projs)[ empno = pmgr ]{empno !} |X| Emps |X| Depts ) { dname ! avgsal:avg(sal) } © Ellis Cohen 2001-2007 13 Joins & Renaming © Ellis Cohen 2001-2007 14 Using Natural Joins (Emps X Projs)[ empno = pmgr ]{ pname, ename } If you want to use a natural join, the joined attributes MUST have the same name. Use Renaming in the Relational Algebra Emps Join Diagram empno ename job mgr hiredate sal comm deptno Emps Projs pno pname pmgr persons budget pstart pend Join Diagram empno ename job mgr hiredate sal comm deptno Projs ep ep pno pname pmgr persons budget pstart pend (Emps{*, epempno} |X| Projs{*, eppmgr}) { pname, ename } © Ellis Cohen 2001-2007 15 Using Cross Joins (Emps |X| Depts){ ename, dname } If you want to use a cross join, all attributes MUST have different names. Use Renaming in the Relational Algebra Emps empno ename job mgr hiredate sal comm deptno Join Diagram Emps Depts deptno dname loc Join Diagram empno ename job mgr hiredate sal comm deptno Depts dd deptno dname loc ed (Emps{*, eddeptno} X Depts{*, dddeptno}) [ed = dd]{ ename, dname } © Ellis Cohen 2001-2007 16 Cross Joins and Qualified Names In the Relational Algebra, Cross Joined Relations MUST have different attribute names In SQL, this is not necessary, since qualified names can be used (Emps |X| Depts){ ename, dname } SELECT ename, dname FROM (Emps NATURAL JOIN Depts) (Emps{*, eddeptno} X Depts{*, dddeptno}) [ed = dd]{ ename, dname } SELECT ename, dname FROM Emps e, Depts d WHERE e.deptno = d.deptno © Ellis Cohen 2001-2007 17 Bulk Prefixing vs Qualified Names REAL does NOT have qualified names. But the bulk prefixing operator $$ adds a prefix to all attributes in a relation e$$Emps has attributes named e$empno, e$ename, …, e$deptno SELECT ename, dname FROM Emps e, Depts d WHERE e.deptno = d.deptno (e$$Emps X d$$Depts) [e$deptno = d$deptno]{ e$ename, d$dname } © Ellis Cohen 2001-2007 18 REAL Self Join Using Cross Join SELECT e.ename, m.ename as mname FROM (Emps e JOIN Emps m ON e.mgr = m.empno) To use a cross join in REAL, the attribute names in the joined tables cannot overlap. Join Diagram Emps e empno ename job mgr hiredate sal comm deptno Emps m empno ename job mgr hiredate sal comm deptno mname (Emps{ ename, mgr } X Emps{ empno, mname:ename }) [mgr = empno]{ ename, mname } (e$$Emps X m$Emps) [e$mgr = m$empno]{ e$ename, m$ename } © Ellis Cohen 2001-2007 19 REAL Self Join Using Natural Join SELECT e.ename, m.ename as mname FROM (Emps e JOIN Emps m ON e.mgr = m.empno) To use a Natural Join in REAL, the ONLY attribute names that overlap are the ones to be joined Join Diagram Emps e empno ename job mgr hiredate sal comm deptno Emps m em em empno ename job mgr hiredate sal comm deptno mname (Emps{ ename, em:mgr } |X| Emps{ em:empno, mname:ename }) { ename, mname } © Ellis Cohen 2001-2007 20 Natural Outer Joins © Ellis Cohen 2001-2007 21 Natural Outer Joins :X| NATURAL LEFT JOIN |X: NATURAL RIGHT JOIN :X: NATURAL FULL JOIN © Ellis Cohen 2001-2007 22 Natural Left Join Depts deptno * * dname … Emps empno ename … deptno 10 ACCOUNTING … 7782 CLARK … 10 20 RESEARCH … 7499 ALLEN … 30 30 SALES … 8214 JOJO … 40 OPERATIONS … 7698 BLAKE … DNAME -----------ACCOUNTING RESEARCH SALES SALES OPERATIONS ENAME -----CLARK ALLEN BLAKE 30 Show names and department names of the employees in each department. Include departments with no employees. SELECT dname, ename FROM Depts NATURAL LEFT JOIN Emps (Depts :X| Emps){ dname, ename } © Ellis Cohen 2001-2007 23 Natural Right Joins Depts deptno dname … Emps empno ename … deptno 10 ACCOUNTING … 7782 CLARK … 10 20 RESEARCH … 7499 ALLEN … 30 30 SALES … 8214 JOJO … 40 OPERATIONS … 7698 BLAKE … Show the names and department names of all employees. Include employees who are unassigned. ENAME ----CLARK ALLEN JOJO BLAKE * 30 DNAME ---------ACCOUNTING SALES SALES SELECT ename, dname FROM Depts NATURAL RIGHT JOIN Emps (Depts |X: Emps){ ename, dname } © Ellis Cohen 2001-2007 24 Depts deptno * * Natural Full Join dname … Emps empno ename … deptno 10 ACCOUNTING … 7782 CLARK … 10 20 RESEARCH … 7499 ALLEN … 30 30 SALES … 8214 JOJO … 40 OPERATIONS … 7698 BLAKE … * 30 DNAME ENAME ------------ -----JOJO ACCOUNTING CLARK RESEARCH SALES ALLEN SALES BLAKE OPERATIONS SELECT dname, ename FROM Depts NATURAL FULL JOIN Emps (Depts :X: Emps){ dname, ename } © Ellis Cohen 2001-2007 25 Un-Natural Outer Joins © Ellis Cohen 2001-2007 26 Un-Natural Outer Joins For each project, list its name, and the name of its manager (show NULL if there is no manager) SELECT pname, ename FROM (Projs LEFT JOIN Emp ON pmgr = empno) Projs pno pname pmgr persons budget pstart pend Emps pmgr empno ename job mgr hiredate sal comm deptno REAL only has natural outer joins. Renaming can be used so join attributes have the same name ( Projs :X| Emps{ pmgr:empno, ename } ){ pname, ename } © Ellis Cohen 2001-2007 27 Problem: Joining Cats & Dogs Given Cats( catid ) and Dogs( dogid ), for each cat list the dogs whose names are the same length, but make sure each cat is listed (will a NULL dog if necessary) SELECT catid, dogid FROM (Cats LEFT JOIN Dogs ON length(catid) = length(dogid)) Real only supports NATURAL OUTER JOINS. How can this be done in REAL? © Ellis Cohen 2001-2007 28 Outer Rejoin Idiom SELECT catid, dogid FROM (Cats LEFT JOIN Dogs ON length(catid) = length(dogid)) This requires an un-natural outer join, but in REAL, all outer joins are natural! There's a standard REAL outer rejoin idiom: 1. 2. 3. Perform a cross join Restrict it with the join condition Then, perform the natural outer join Cats :X| (Cats X Dogs) [length(catid) = length(dogid)] © Ellis Cohen 2001-2007 29 Outer Joins with Renaming For each project, list its name, and the name of its manager (show NULL if there is no manager) SELECT pname, ename FROM (Projs LEFT JOIN Emp ON pmgr = empno) Emps Projs pno pname pmgr persons budget pstart pend empno empno ename job mgr hiredate sal comm deptno ( Projs :X| Emps{ pmgr:empno, ename } ){ pname, ename } How can this be written in REAL without renaming? © Ellis Cohen 2001-2007 30 Using the Rejoin Idiom For each project, list its name, and the name of its manager (show NULL if there is no manager) SELECT pname, ename FROM (Projs LEFT JOIN Emp ON pmgr = empno) Projs pno pname pmgr persons budget pstart pend Emps empno ename job mgr hiredate sal comm deptno (Projs :X| (Projs X Emps)[pmgr = empno]){ pname, ename } What's the REAL to list the name of every employee, and (if they have a manager), their manager's name © Ellis Cohen 2001-2007 31 REAL Alternatives List the name of every employee, and, (if they have a manager), the name of their manager Join Diagram Emps e empno ename job mgr hiredate sal comm deptno Emps m em em empno ename job mgr hiredate sal comm deptno mname (Emps{ ename, em:mgr } :X| Emps{ em:empno, mname:ename }) { ename, mname } (e$$Emps :X| (e$$Emps X m$Emps)[e$mgr = m$empno]) { ename:e$ename, mname:m$ename } © Ellis Cohen 2001-2007 32 Views © Ellis Cohen 2001-2007 33 Creating Tables vs. Views HiEmps Emps[sal > 1500] CREATE TABLE HiEmps AS SELECT * FROM Emps WHERE sal > 1500 Creates a new table. Subsequent changes to Emps will not be reflected in HiEmps. HiEmps: Emps [sal > 1500] CREATE VIEW HiEmps AS SELECT * FROM Emps WHERE sal > 1500 Just creates a view based on Emps. Every use of HiEmps actually refers to its underlying base tables – in this case, Emps. © Ellis Cohen 2001-2007 34 Database Engines Expand based on Relational Algebra Suppose we define CREATE VIEW HiEmps AS SELECT * FROM Emps WHERE sal > 1500 HiEmps: Emps[ sal > 1500] and then execute the query SELECT ename, job FROM HiEmps HiEmps{ ename, job } The database engine automatically turns this into SELECT ename, job FROM Emps WHERE sal > 1500 Emps[ sal > 1500 ]{ ename, job } © Ellis Cohen 2001-2007 35 SQL & REAL Factoring WITH DeptMgrs AS (SELECT * FROM Emps WHERE job = 'DEPTMGR') SELECT pname, ename FROM (Projs p JOIN DeptMgrs m ON p.pmgr = m.empno) (DeptMgrs: Emps[job = 'DEPTMGR']) ( (Projs X DeptMgrs) [empno = pmgr]{ pname, ename } ) © Ellis Cohen 2001-2007 36 Collection Operators © Ellis Cohen 2001-2007 37 Unions SELECT ename AS event, hiredate AS evdate FROM Emps UNION SELECT pname AS event, pstart AS evdate FROM Projs event evdate ALLEN 20-FEB-81 MARTIN 28-SEP-81 BLAKE 01-MAY-81 KING 17-NOV-81 TURNER 08-SEP-81 STERN 23-NOV-99 Running Amuck 12-FEB-82 Cooling Off 01-JAN-05 Lifting Off 01-JAN-05 Emps{ event:ename, evdate:hiredate } U Projs{ event:pname, evdate:pstart } © Ellis Cohen 2001-2007 38 SQL & REAL Union Set Union eliminates duplicates Counted Union counts # of tuples SELECT * FROM A1 UNION ALL SELECT * FROM A2 A1 #U A2 SELECT * FROM A UNION SELECT * FROM B A1 U A2 (A1 #U A2){ * ! } Unlike SQL, in REAL, the names & types must conform Use if there are duplicates to be gotten rid of © Ellis Cohen 2001-2007 39 Counted Union vs. Set Union Emps[hiredate > '1-jan-82']{ job } Emps[sal ≥ 3000]{ job } ANALYST CLERK CLERK ANALYST PRESIDENT ANALYST Emps[sal ≥ 3000]{ job } U# Emps[hiredate > '1-jan-82']{ job } ANALYST PRESIDENT ANALYST ANALYST CLERK CLERK Counted Union counts # of tuples & includes all of them Emps[sal ≥ 3000]{ job } U Emps[hiredate > '1-jan-82']{ job } ANALYST CLERK PRESIDENT Set Union eliminates duplicates © Ellis Cohen 2001-2007 40 SQL & REAL Difference and Intersect SELECT * FROM A1 EXCEPT ALL SELECT * FROM A2 SELECT * FROM A EXCEPT SELECT * FROM B A1 — A2 A1 #— A2 A1{ * ! } #— A2{ * ! } SELECT * FROM A1 INTERSECT ALL SELECT * FROM A2 SELECT * FROM A INTERSECT SELECT * FROM B A1 # A2 A1 A2 A1{ * ! } # A2{ * ! } (A1 # A2){ * ! } © Ellis Cohen 2001-2007 41 Counted Difference vs. Set Difference Emps[sal ≥ 3000]{ job } ANALYST PRESIDENT ANALYST Emps[hiredate > '1-jan-82']{ job } Emps[sal ≥ 3000]{ job } —# Emps[hiredate > '1-jan-82']{ job } ANALYST PRESIDENT Emps[sal ≥ 3000]{ job } — Emps[hiredate > '1-jan-82']{ job } PRESIDENT Counted Difference counts # of tuples ANALYST CLERK CLERK Set Difference eliminates duplicates before taking the difference © Ellis Cohen 2001-2007 42 Counted Intersect vs. Set Intersect Emps[deptno = 20]{ job } CLERK MANAGER ANALYST CLERK ANALYST Emps[hiredate > '1-jan-82']{ job } Emps[deptno = 20]{ job } Emps[deptno = 20]{ job } Emps[hiredate > '1-jan-82']{ job } ANALYST CLERK CLERK Emps[hiredate > '1-jan-82']{ job } ANALYST CLERK ANALYST CLERK CLERK # Counted Intersect counts # of tuples & computes minimum # Set Intersect eliminates duplicates © Ellis Cohen 2001-2007 43 Representing Outer Joins using Collection Operators (Depts :X Emps){ dname, ename } (EmptyDepts: Depts{deptno} – Emps{deptno}) ( (Emps |X| Depts) { dname, ename } U (EmptyDepts |X| Depts) { dname, ename }) © Ellis Cohen 2001-2007 44 Assignment & Reassignment © Ellis Cohen 2001-2007 45 REAL Query & Assignment Two kinds of REAL statements: 1. Expression (query) e.g. Emps[ sal < 1000 ] 2. Assignment e.g. PoorEmps Emps[ sal < 1000 ] CREATE TABLE PoorEmps AS SELECT * FROM Emps WHERE sal < 1000 © Ellis Cohen 2001-2007 46 REAL Reassignment In REAL, you can also reassign to an existing relation Emps Emps[ sal IS NOT < 1000 ] corresponds to DELETE Emps WHERE sal < 1000 © Ellis Cohen 2001-2007 47 Insert as Reassignment INSERT INTO Emps SELECT * FROM OtherEmps WHERE sal < 1000 Emps Emps #U OtherEmps[ sal < 1000 ] © Ellis Cohen 2001-2007 48 Update as Reassignment UPDATE Emps SET sal = sal + 200 WHERE job = DEPTMGR' Emps Emps[ job IS NOT = 'DEPTMGR' ] #U Emps[ job = 'DEPTMGR' ]{ *, sal:(sal + 200) } © Ellis Cohen 2001-2007 49 Relational Equivalence & Completeness © Ellis Cohen 2001-2007 50 Primitive SQL Suppose we define Primitive SQL. It is exactly like standard SQL queries (SELECTs only), except we remove numeric/string operations (+, –, ||, etc.) ordinary or aggregate functions duplicates (no base relations have duplicates, and all SELECTs automatically remove duplicates). © Ellis Cohen 2001-2007 51 Primitive Relational Algebra Imagine a Relational Algebra which ONLY has comparison operators (<, =, etc.) logical operators (AND, OR, NOT) restriction and simple projection cross joins and natural inner joins set-oriented collection operators It does NOT have numeric/string operations (+, –, ||, etc.) ordinary or aggregate functions duplicates (no base relation has duplicates, and algebra operation automatically removes duplicate tuples) grouping outer joins This is the Primitive Relational Algebra © Ellis Cohen 2001-2007 52 Primitive Tuple Relational Calculus The Primitive Tuple Relational Calculus ONLY has tuple variables with qualified attributes (e.g. e.sal) comparison operators (<, =, etc.) tuple membership tests (e.g. e Emps) logical operators (AND, OR, NOT) quantification expressions SOME e1, e2, … SATISFIES … EACH e1, e2, … SATISFIES … EACH e1, e2, … WHERE … SATISFIES … simple SELECT (SELECT … WHERE …) at the outermost level only It does NOT have numeric/string operations (+, –, ||, etc.) ordinary or aggregate functions duplicates (no base relations have duplicates, and SELECT does not produce duplicates) any mechanism for grouping collection operations (UNION, INTERSECT, EXCEPT) join operators © Ellis Cohen 2001-2007 53 Primitive Domain Relational Calculus The Primitive Domain Relational Calculus ONLY has domain variables comparison operators (<, =, etc.) attribute matching tests (e.g. Emps( job: 'CLERK' )) logical operators (AND, OR, NOT) quantification expressions SOME e1, e2, … SATISFIES … EACH e1, e2, … SATISFIES … EACH e1, e2, … WHERE … SATISFIES … simple SELECT (SELECT … WHERE …) at the outermost level only It does NOT have numeric/string operations (+, –, ||, etc.) ordinary or aggregate functions duplicates (no base relations have duplicates, and SELECT does not produce duplicates) any mechanism for grouping collection operations (UNION, INTERSECT, EXCEPT) join operators © Ellis Cohen 2001-2007 54 Relational Equivalence Exactly the same set of queries can be expressed using • Primitive SQL • The Primitive Relational Algebra • The Safe Primitive Tuple Relational Calculus • The Safe Primitive Domain Relational Calculus © Ellis Cohen 2001-2007 55 Relational Completeness Any query language which can express a query equivalent to any query expressible in the Primitive Relational Algebra is relationally complete. So • Primitive SQL • The Safe Primitive Tuple Relational Calculus • The Safe Domain Relational Calculus are all relationally complete © Ellis Cohen 2001-2007 56 Relational Division © Ellis Cohen 2001-2007 57 Cool Projects Given Emps( empno, ename, job, … ) Projects( pno, pname, … ) Assigns( empno, pno ) • empno references Emps • pno references Projects CoolProjs( pno ) • pno references Projects Problem Use SQL or REAL to List the cool employees -those who are assigned to at least one of the cool projects © Ellis Cohen 2001-2007 58 Cool Employees SELECT DISTINCT empno FROM Assigns NATURAL JOIN CoolProjs (Assigns |X| CoolProjs){ empno ! } That's easy! How about the ultra-cool employees: those assigned to every one of the cool projects This is called a Relational Division problem © Ellis Cohen 2001-2007 59 Ultra Cool in the Primitive Domain Relational Calculus SELECT eno WHERE (EACH cpno WHERE ?CoolProjs( pno: cpno ) SATISIFIES ?Assigns( empno: eno, pno: cpno )) © Ellis Cohen 2001-2007 60 Ultra Cool in the Primitive Tuple Relational Calculus SELECT e.empno WHERE (e Emps) AND (EACH c WHERE (c CoolProjs) SATISFIES (SOME a SATISFIES (a Assigns) AND (a.empno = s.empno) AND (a.pno = c.pno))) © Ellis Cohen 2001-2007 61 Ultra Cool in Extended SQL SELECT empno FROM Emps e WHERE (EACH CoolProjs c SATISFIES SOME Assigns a SATISFIES a.empno = e.empno AND a.pno = c.pno) © Ellis Cohen 2001-2007 62 Ultra Cool in Extended SQL with Attribute Filtering SELECT empno FROM Emps e WHERE (EACH CoolProjs c SATISFIES (SOME Assigns( empno: e.empno, pno: c.pno ))) © Ellis Cohen 2001-2007 63 Ultra Cool with Extended SQL Mapped to Standard SQL SELECT empno FROM Emps e WHERE NOT exists( SELECT * FROM CoolProjs c WHERE NOT exists( SELECT * FROM Assigns a WHERE a.empno = e.empno AND a.pno = c.pno) ) SELECT empno FROM Emps e WHERE (SELECT count(*) FROM CoolProjs) = (SELECT count(*) FROM CoolProjs c WHERE exists( SELECT * FROM Assigns a WHERE a.empno = e.empno AND a.pno = c.pno) ) © Ellis Cohen 2001-2007 64 Ultra Cool without Aggregation (based on the Primitive Relational Algebra) REAL (without using Aggregation) ( AsnEmps: Assigns{ empno ! }, CoolAssigns: CoolProjs X AsnEmps, MissingAssigns: CoolAssigns – Assigns, UncoolEmps: MissingAssigns{ empno ! } ) AsnEmps – UncoolEmps CORRESPONDING SQL WITH AsnEmps AS (SELECT DISTINCT empno FROM Assigns) MissingAssigns AS (SELECT empno, pno FROM CoolProjs, AsnEmps EXCEPT SELECT empno, pno FROM Assigns) SELECT empno FROM AsnEmps EXCEPT SELECT empno FROM MissingAssigns © Ellis Cohen 2001-2007 65 Best Ultra Cool SQL BEST SQL SOLUTION SELECT empno FROM (Assigns NATURAL JOIN CoolProjs) GROUP BY empno HAVING count(*) = (SELECT count(*) FROM CoolProjs) CORRESPONDING REAL (EmpNumAssigns: (Assigns |X| CoolProjs){ empno ! epk:count(*) }, ProjKnt: CoolProjs{ ! pk:count(*) }) (EmpNumAssigns X CoolKnt)[pk = epk]{ empno } © Ellis Cohen 2001-2007 66 Best REAL Answer The ultra-cool employees: those assigned to every one of the cool projects Use the REAL Divide operator Assigns ÷ CoolProjs © Ellis Cohen 2001-2007 67 Why Division? Because it's the inverse of JOIN (which is like multiplication) Assigns (Assigns ÷ CoolProjs) X CoolProjs In general, RelA = (RelA X RelB) ÷ RelB RelC (RelC ÷ RelB) X RelB © Ellis Cohen 2001-2007 68