{"id":519,"date":"2009-02-17T00:00:00","date_gmt":"2009-02-17T00:00:00","guid":{"rendered":"https:\/\/test.simple-talk.com\/uncategorized\/divided-we-stand-the-sql-of-relational-division\/"},"modified":"2021-09-29T16:22:07","modified_gmt":"2021-09-29T16:22:07","slug":"divided-we-stand-the-sql-of-relational-division","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/t-sql-programming-sql-server\/divided-we-stand-the-sql-of-relational-division\/","title":{"rendered":"Divided We Stand: The SQL of Relational Division"},"content":{"rendered":"<div id=\"pretty\">\n<p class=\"start\">I&#8217;ve just got a copy of THE MANGA GUIDE TO DATABASES (ISBN 978-1-59327-190-9); it is one of a series of &#8220;Manga Guides&#8221; (the others are Calculus and Statistics).&#160; Since I consider myself a long time comic book fan, rather than a Dummy or an Idiot, I prefer these titles over their competition on the book shelf.&#160; But beside the risk that the title poses to my personal dignity, the book is a really good introductory book.&#160; You just have to get over the Japanese &#8216;database fairy&#8217; Tico in the Kingdom of Kod.&#160; <\/p>\n<p>The book has a section on basic RDBMS in which they mention Codd&#8217;s eight original operations on tables.&#160; If you don&#8217;t remember them, they were the three classic set operators: UNION, INTERSECTION and DIFFERENCE and five relationship operators: JOIN, PROJECTION, SELECTION, CARTESIAN PRODUCT and DIVISION.&#160; Since RDBMS is based on sets, you can see why the classic set operators are there.&#160; What is hard to figure out is why it took so long to get INTERSECTION and DIFFERENCE into SQL.&#160; We had JOIN, PROJECTION, SELECTION and CARTESIAN PRODUCT right away in SQL because you cannot do anything without them in an RDBMS. <\/p>\n<p>But the one in the collection that seems weird on first glance is relational division.&#160; It can be expressed in terms of other operators, but the other seven are relatively primitive operations.&#160; The idea is that a divisor table is used to partition a dividend table and produce a quotient or results table.&#160; The quotient table is made up of those values of one column for which a second column had all of the values in the divisor. <\/p>\n<p>The name &#8220;relational division&#8221; comes from the symbol for a Cartesian product (aka CROSS JOIN), which is X or multiplication.&#160; If you take the quotient table cross joined with the divisor table you get the dividend table.&#160; In notation we have (quotient CROSS JOIN divisor = dividend) is like (a\/b = c) implies (b * c = a) in integer maths.&#160;<\/p>\n<p>This is easier to explain with an example.&#160; We have a table of pilots and the planes they can fly (dividend); we have a table of planes in the hangar (divisor); we want the names of the pilots who can fly every plane (quotient) in the hangar.&#160; To get this result, we divide the PilotSkills table by the planes in the hangar.<\/p>\n<pre>CREATE TABLE PilotSkills\n(pilot_name CHAR(15) NOT NULL,\nplane_name CHAR(15) NOT NULL,\nPRIMARY KEY (pilot_name, plane_name));\n\n\/*\nPilotSkills\npilot_name&#160;&#160;&#160;&#160;plane_name\n=========================\n'Celko'&#160;&#160;&#160;&#160;'Piper Cub'\n'Higgins'&#160;&#160;'B-52 Bomber'\n'Higgins'&#160;&#160;'F-14 Fighter'\n'Higgins'&#160;&#160;'Piper Cub'\n'Jones'&#160;&#160;&#160;&#160;'B-52 Bomber'\n'Jones'&#160;&#160;&#160;&#160;'F-14 Fighter'\n'Smith'&#160;&#160;&#160;&#160;'B-1 Bomber'\n'Smith'&#160;&#160;&#160;&#160;'B-52 Bomber'\n'Smith'&#160;&#160;&#160;&#160;'F-14 Fighter'\n'Wilson'&#160;&#160; 'B-1 Bomber'\n'Wilson'&#160;&#160; 'B-52 Bomber'\n'Wilson'&#160;&#160; 'F-14 Fighter'\n'Wilson'&#160;&#160; 'F-17 Fighter' *\/\nCREATE TABLE Hangar\n(plane_name CHAR(15) NOT NULL PRIMARY KEY);\n\/*\nHangar\nplane_name\n=============\n'B-1 Bomber'\n'B-52 Bomber'\n'F-14 Fighter'\n\nPilotSkills DIVIDED BY Hangar\npilot_name\n=============================\n'Smith'\n'Wilson'\n*\/<\/pre>\n<p>In this example, Smith and Wilson are the two pilots who can fly everything in the hangar.&#160; Notice that both Higgins and Celko know how to fly a Piper Cub, but we don&#8217;t have one right now.&#160; In Codd&#8217;s original definition of relational division, it is not a problem to have more rows than are called for.<\/p>\n<h2><b>Division with a Remainder<\/b><\/h2>\n<p>There are two kinds of relational division.&#160; Division with a remainder allows the dividend table to have more values than the divisor, which was Dr. Codd&#8217;s original definition.&#160; For example, if a pilot can fly more planes than just those we have in the hangar, this is fine with us.&#160; The query can be written as &#8230;<\/p>\n<pre>SELECT DISTINCT pilot_name\n&#160;&#160;FROM PilotSkills AS PS1 \n&#160;&#160;WHERE NOT EXISTS\n&#160;&#160;&#160;&#160;&#160;&#160; (SELECT *\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;FROM Hangar\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; WHERE NOT EXISTS\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; (SELECT *\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;FROM PilotSkills AS PS2\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; WHERE (PS1.pilot_name = PS2.pilot_name)\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; AND (PS2.plane_name = Hangar.plane_name)));\n<\/pre>\n<p>The quickest way to explain what is happening in this query is to imagine a World War II movie where a cocky pilot has just walked into the hangar, looked over the fleet, and announced, &#8220;There ain&#8217;t no planes in this hangar that I can&#8217;t fly!&#8221;&#160; We want to find pilots for whom no plane exists in the hangar for which they have no skills.&#160; You might want to read that double negative again &#8211; it is ugly English, but good logic. <\/p>\n<p>This query for relational division was made popular by Chris Date in his textbooks, but it is neither the only method nor always the fastest.&#160; Another version of the division can be written so as to avoid three levels of nesting.&#160; While it is not original with me, I have made it popular in my books.<\/p>\n<pre>&#160;SELECT PS1.pilot_name\n&#160;&#160; FROM PilotSkills AS PS1, Hangar AS H1\n&#160;&#160;WHERE PS1.plane_name = H1.plane_name\n&#160;&#160;GROUP BY PS1.pilot_name \n&#160;&#160;HAVING COUNT(PS1.plane_name) = (SELECT COUNT(plane_name) FROM Hangar);\n\n<\/pre>\n<p>There is a serious difference in the two methods.&#160; Burn down the hangar, so that the divisor is empty.&#160; Because of the <code>NOT EXISTS() <\/code>predicates in Date&#8217;s query, all pilots are returned from a division by an empty set.&#160; Because of the<code> COUNT() <\/code>functions in my query, no pilots are returned from a division by an empty set.&#160; <\/p>\n<p>Now we are getting philosophical as to how we want to maintain the &#8220;integer division&#8221; analogy.&#160; Is an empty set &#8220;kind of like a zero&#8221; or not?&#160; If so, then dividing by zero would be undefined (or infinity, depending on your math book and your age).&#160; And dividing zero by anything is always zero.&#160; <\/p>\n<p>In the sixth edition of his book, INTRODUCTION TO DATABASE SYSTEMS (Addison-Wesley; 1995; ISBN 0-191-82458-2), Chris Date defined another operator (DIVIDEBY &#8230;&#160; PER) which produces the same results as my query, but with more complexity.<\/p>\n<h2><b>Exact Division<\/b><\/h2>\n<p>The second kind of relational division is exact relational division.&#160; The dividend table must match exactly to the values of the divisor without any extra values &#8211; i.e. a remainder, if you remember grade school math.<\/p>\n<pre>SELECT PS1.pilot_name\n&#160;&#160;FROM PilotSkills AS PS1\n&#160;&#160;&#160;&#160;&#160;&#160; LEFT OUTER JOIN\n&#160;&#160;&#160;&#160;&#160;&#160; Hangar AS H1\n&#160;&#160;&#160;&#160;&#160;&#160; ON PS1.plane_name = H1.plane_name \n&#160;&#160;GROUP BY PS1.pilot_name\nHAVING COUNT(PS1.plane_name) = (SELECT COUNT(plane_name) FROM Hangar)\n&#160;&#160; AND COUNT(H1.plane_name) = (SELECT COUNT(plane_name) FROM Hangar);<\/pre>\n<p>This says that a pilot must have the same number of certificates as there planes in the hangar and these certificates all match to a plane in the hangar, not something else.&#160; The &#8220;something else&#8221; is shown by a created NULL from the LEFT OUTER JOIN.<\/p>\n<p>Please do not make the mistake of trying to reduce the HAVING clause with a little false relational algebra to:<\/p>\n<pre>HAVING COUNT(PS1.plane_name) = COUNT(H1.plane_name) <\/pre>\n<p>because it does not work; it will tell you that the hangar has (n) planes in it and the <b>pilot_name<\/b> is certified for (n) planes, but not that those two sets of planes are equal to each other.<\/p>\n<h2><b>Todd&#8217;s Division<\/b><\/h2>\n<p>A relational division operator proposed by Stephen Todd is defined on two tables with common columns that are joined together, dropping the JOIN column and retaining only those non-JOIN columns that meet a criterion.&#160; Again, this is easier to explain with an example.&#160; <\/p>\n<p>We are given a table, <b>JobParts(job_nbr, part_nbr)<\/b>, and another table, <b>SupParts(sup_nbr, part_nbr),<\/b> of suppliers and the parts that they provide.&#160; We want to get the supplier-and-job pairs such that supplier &#8216;sn&#8217; supplies all of the parts needed for &#8216;jn&#8217;.&#160; This is not quite the same thing as getting the supplier-and-job pairs such that job_nbr &#8216;jn&#8217; requires all of the parts provided by supplier sn.<\/p>\n<p>You want to divide the <b>JobParts<\/b> table by the <b>SupParts<\/b> table.&#160; A rule of thumb: The remainder comes from the dividend, but all values in the divisor are present.<\/p>\n<pre>&#160;JobParts&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; SupParts&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; Result AS JobSups\n&#160;job_nbr part_nbr&#160;&#160; sup_nbr&#160; part_nbr&#160;&#160;&#160; job_nbr sup_nbr\n&#160;================&#160;&#160; =================&#160;&#160;&#160; ==============\n&#160;'j1'&#160; 'p1'&#160;&#160;&#160;&#160;&#160; &#160;&#160;&#160;'s1'&#160;&#160;&#160; 'p1'&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; 'j1'&#160; 's1'\n&#160;'j1'&#160; 'p2'&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; 's1'&#160;&#160;&#160; 'p2'&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; 'j1'&#160; 's2'\n&#160;'j2'&#160; 'p2'&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; 's1'&#160;&#160;&#160; 'p3'&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; 'j2'&#160; 's1'\n&#160;'j2'&#160; 'p4'&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; 's1'&#160;&#160;&#160; 'p4'&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; 'j2'&#160; 's4'\n&#160;'j2'&#160; 'p5'&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; 's1'&#160;&#160;&#160; 'p5'&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; 'j3'&#160; 's1'\n&#160;'j3'&#160; 'p2'&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; 's1'&#160;&#160;&#160; 'p6'&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; 'j3'&#160; 's2'\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; 's2'&#160;&#160;&#160; 'p1'&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; 'j3'&#160; 's3'\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; 's2'&#160;&#160;&#160; 'p2'&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; 'j3'&#160; 's4'\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; 's3'&#160;&#160;&#160; 'p2'\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; 's4'&#160;&#160;&#160; 'p2'\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; 's4'&#160;&#160;&#160; 'p4'\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; 's4'&#160;&#160;&#160; 'p5'\n<\/pre>\n<p>Pierre Mullin submitted the following query to carry out the Todd division:<\/p>\n<pre>SELECT DISTINCT JP1.job_nbr, SP1.sup_nbr_nbr\n&#160;&#160;FROM JobParts AS JP1, SupParts AS SP1\n&#160;WHERE NOT EXISTS\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;(SELECT *\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; FROM JobParts AS JP2\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;WHERE JP2.job_nbr = JP1.job_nbr\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;AND JP2.part_nbr\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;NOT IN (SELECT SP2.part_nbr\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;FROM SupParts AS SP2\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; WHERE SP2.sup_nbr_nbr = SP1.sup_nbr_nbr));<\/pre>\n<p>This is really a modification of the query for Codd&#8217;s division, extended to use a JOIN on both tables in the outermost SELECT statement.&#160; The IN predicate for the second subquery can be replaced with a NOT EXISTS predicate; it might run a bit faster, depending on the optimizer.<\/p>\n<p>Another related query is finding the pairs of suppliers who sell the same parts.&#160; In this data, that would be the pairs (&#8216;s1&#8217;, &#8216;p2&#8217;), (&#8216;s3&#8217;, &#8216;p1&#8217;), (&#8216;s4&#8217;, &#8216;p1&#8217;), (&#8216;s5&#8217;, &#8216;p1&#8217;)<\/p>\n<pre>SELECT S1.sup_nbr, S2.sup_nbr\n&#160;&#160;FROM SupParts AS S1, SupParts AS S2\n&#160;WHERE S1.sup_nbr &lt; S2.sup_nbr -- different suppliers\n&#160;&#160; AND S1.part_nbr = S2.part_nbr -- same parts\n&#160;GROUP BY S1.sup_nbr, S2.sup_nbr\nHAVING COUNT(*) = (SELECT COUNT (*)&#160;&#160;-- same count of parts\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; FROM SupParts AS S3\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &#160;&#160;&#160;&#160;&#160;&#160;&#160;WHERE S3.sup_nbr = S1.sup_nbr)\n&#160;&#160; AND COUNT(*) = (SELECT COUNT (*)\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; FROM SupParts AS S4\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;WHERE S4.sup_nbr = S2.sup_nbr);<\/pre>\n<p>This can be modified into Todd&#8217;s division easily be adding the restriction that the parts must also belong to a common job_nbr.&#160; <\/p>\n<h2><b>Division with JOINs<\/b><\/h2>\n<p>Standard SQL has several JOIN operators that can be used to perform a relational division.&#160; Going back to my World War II movie, to find the pilots, who can fly the same planes as Higgins, use this query:<\/p>\n<pre>SELECT SP1.Pilot_name\n&#160;&#160;FROM (((SELECT plane_name FROM Hangar) AS H1\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; INNER JOIN\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; (SELECT pilot_name, plane_name FROM PilotSkills) AS SP1\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; ON H1.plane_name = SP1.plane_name)\n&#160;&#160;&#160;&#160;&#160;&#160; INNER JOIN (SELECT *\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; FROM PilotSkills\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;WHERE pilot_name = 'Higgins') AS H2\n&#160;&#160;&#160;&#160;&#160;&#160; ON H2.plane_name = H1.plane_name)\n&#160;&#160;&#160;&#160;&#160;&#160; GROUP BY SP1.Pilot_name\nHAVING COUNT(*) &gt;= (SELECT COUNT(*)\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;FROM PilotSkills\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; WHERE pilot_name = 'Higgins');<\/pre>\n<p>The first JOIN finds all of the planes in the hangar for which we have a pilot.&#160; The next JOIN takes that set and finds which of those match up with <code>(SELECT * FROM PilotSkills WHERE pilot_name = 'Higgins')<\/code> skills.&#160; The<code> GROUP BY<\/code> clause will then see that the intersection we have formed with the joins has at least as many elements as Higgins has planes.&#160; The <code>GROUP BY<\/code> also means that the <code>SELECT DISTINCT<\/code> can be replaced with a simple <code>SELECT<\/code>.&#160; If the theta operator in the GROUP BY clause is changed from &gt;= to =, the query finds an exact division.&#160; If the theta operator in the GROUP BY clause is changed from &gt;= to &amp;l t;= or &lt;, the query finds those pilots whose skills are a superset or a strict superset of the planes that Higgins flies.&#160; This idea is useful when we get to Romley&#8217;s Davison at the end of this article. <\/p>\n<p>It might be a good idea to put the divisor into a VIEW or CTE for readability in this query and as a clue to the optimizer to calculate it once.&#160; Some products will execute this form of the division query faster than the nested subquery version, because they will use the PRIMARY KEY information to pre-compute the joins between tables.<\/p>\n<h2><b>Division with Set Operators<\/b><\/h2>\n<p>The Standard SQL set difference operator, EXCEPT, can be used to write a very compact version of Dr. Codd&#8217;s relational division.&#160; The EXCEPT operator removes the divisor set from the dividend set.&#160; If the result is empty, we have a match; if there is anything left over, it has failed.&#160; Using the pilots-and-hangar-tables example, we would write<\/p>\n<pre>SELECT DISTINCT Pilot_name FROM PilotSkills AS P1\nWHERE (SELECT plane_name FROM Hangar\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;EXCEPT\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;SELECT plane_name\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;FROM PilotSkills AS P2\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; WHERE P1.pilot_name = P2.pilot_name) IS NULL;<\/pre>\n<p>Again, informally, you can imagine that we got a skill list from each pilot, walked over to the hangar, and crossed off each plane he could fly.&#160; If we marked off all the planes in the hangar, we would keep this guy.&#160; Another trick is that an empty subquery expression returns a NULL, which is how we can test for an empty set.&#160; The <code>WHERE<\/code> clause could just as well have used a <code>NOT EXISTS() <\/code>predicate instead of the <code>IS NULL<\/code> predicate.<\/p>\n<h2><b>Romley&#8217;s Division<\/b><\/h2>\n<p>This somewhat complicated relational division is due to Richard Romley at Salomon Smith Barney.&#160; The original problem deals with two tables.&#160; The first table has a list of managers and the projects they can manage.&#160; The second table has a list of Personnel, their departments and the project to which they are assigned.&#160; Each employee is assigned to one and only one department and each employee works on one and only one project at a time.&#160; But a department can have several different projects at the same time, so a single project can span several departments.<\/p>\n<pre>CREATE TABLE MgrProjects\n(mgr_name CHAR(10) NOT NULL,\n&#160;project_id CHAR(2) NOT NULL,\n&#160;PRIMARY KEY(mgr_name, project_id));\nINSERT INTO MgrProject \nVALUES ('M1', 'P1'), ('M1', 'P3'),\n&#160;&#160;&#160;&#160;&#160;&#160; ('M2', 'P2'), ('M2', 'P3'),\n&#160;&#160;&#160;&#160;&#160;&#160; ('M3', 'P2'),\n&#160;&#160;&#160;&#160;&#160;&#160; ('M4', 'P1'), ('M4', 'P2'), ('M4', 'P3');\nCREATE TABLE Personnel\n(emp_id CHAR(10) NOT NULL,\n&#160;dept_id CHAR(2) NOT NULL,\n&#160;project_id CHAR(2) NOT NULL,\n&#160;UNIQUE (emp_id, project_id),\n&#160;UNIQUE (emp_id, dept),\n&#160;PRIMARY KEY (emp_id, dept_id, project_id));\n-- load department #1 data\nINSERT INTO Personnel \nVALUES ('Albert', 'D1', 'P1'),\n&#160;&#160;&#160;&#160;&#160;&#160; ('Bob', 'D1', 'P1'),\n&#160;&#160;&#160;&#160;&#160;&#160; ('Carl', 'D1', 'P1'),\n&#160;&#160;&#160;&#160;&#160;&#160; ('Don', 'D1', 'P2'),\n&#160;&#160;&#160;&#160;&#160;&#160; ('Ed', 'D1', 'P2'),\n&#160;&#160;&#160;&#160;&#160;&#160; ('Frank', 'D1', 'P2'),\n&#160;&#160;&#160;&#160;&#160;&#160; ('George', 'D1', 'P2');\n-- load department #2 data\nINSERT INTO Personnel\nVALUES ('Harry', 'D2', 'P2'),\n&#160;&#160;&#160;&#160;&#160;&#160; ('Jack', 'D2', 'P2'),\n&#160;&#160;&#160;&#160;&#160;&#160; ('Larry', 'D2', 'P2'),\n&#160;&#160;&#160;&#160;&#160;&#160; ('Mike', 'D2', 'P2'),\n&#160;&#160;&#160;&#160;&#160;&#160; ('Nat', 'D2', 'P2');\n-- load department #3 data\nINSERT INTO Personnel \nVALUES ('Oscar', 'D3', 'P2'),\n&#160;&#160;&#160;&#160;&#160;&#160; ('Pat', 'D3', 'P2'),\n&#160;&#160;&#160;&#160;&#160;&#160; ('Rich', 'D3', 'P3');<\/pre>\n<p>The problem is to generate a report showing for each manager each department whether he is qualified to manage none, some or all of the projects being worked on within the department.&#160; To find who can manage some, but not all, of the projects, use a version of relational division.<\/p>\n<pre>SELECT M1.mgr_name, P1.dept_name\n&#160;&#160;FROM MgrProjects AS M1\n&#160;&#160;&#160;&#160;&#160;&#160; CROSS JOIN\n&#160;&#160;&#160;&#160;&#160;&#160; Personnel AS P1\n&#160;WHERE M1.project_id = P1.project_id\n&#160;GROUP BY M1.mgr_name, P1.dept_name\nHAVING COUNT(*) &lt;&gt; (SELECT COUNT(emp_id)\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;FROM Personnel AS P2\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; WHERE P2.dept_name = P1.dept_name);<\/pre>\n<p>The query is simply a relational division with a &lt;&gt; instead of an = in the HAVING clause.&#160; Richard came back with a modification of my answer that uses a characteristic function inside a single aggregate function.<\/p>\n<pre>SELECT DISTINCT M1.mgr_name, P1.dept_name\n&#160;&#160;FROM (MgrProjects AS M1\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;INNER JOININ\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;Personnel AS P1\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;ON M1.project_id = P1.project_id)\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; INNER JOIN\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; Personnel AS P2\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; ON P1.dept_name = P2.dept_name\n&#160;GROUP BY M1.mgr_name, P1.dept_name, P2.project_id\nHAVING MAX (CASE WHEN M1.project_id = P2.project_id\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;THEN 1 ELSE 0 END) = 0;<\/pre>\n<p>This query uses a characteristic function while my original version compares a count of Personnel under each manager to a count of Personnel under each project_id.&#160; The use of &#8220;GROUP BY M1.mgr_name, P1.dept_name, P2.project_id&#8221; with the &#8220;SELECT DISTINCT M1.mgr_name, P1.dept_name&#8221; is really the tricky part in this new query.&#160; What we have is a three-dimensional space with the (x, y, z) axis representing (mgr_name, dept_name, project_id) and then we reduce it to two dimensions (mgr_name, dept_id) by seeing if Personnel on shared project_ids cover the department or not.<\/p>\n<p>That observation leads to the next changes.&#160; We can build a table that shows each combination of manager, department and the level of authority they have over the projects they have in common.&#160; That is the derived table T1 in the following query; (authority = 1) means the manager is not on the project and authority = 2 means that he is on the project_id:<\/p>\n<pre>SELECT T1.mgr_name, T1.dept_name,\n&#160;&#160;&#160;&#160;&#160;&#160; CASE SUM(T1.authority)\n&#160;&#160;&#160;&#160;&#160;&#160; WHEN 1 THEN 'None'\n&#160;&#160;&#160;&#160;&#160;&#160; WHEN 2 THEN 'All'\n&#160;&#160;&#160;&#160;&#160;&#160; WHEN 3 THEN 'Some'\n&#160;&#160;&#160;&#160;&#160;&#160; ELSE NULL END AS POWER\n&#160;&#160;FROM (SELECT DISTINCT M1.mgr_name, P1.dept_name,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; MAX (CASE WHEN M1.project_id = P1.project_id\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;THEN 2 ELSE 1 END) AS authority\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;FROM MgrProjects AS M1\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; CROSS JOIN\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; Personnel AS P1\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; GROUP BY m.mgr_name, P1.dept_name, P1.project_id) AS T1\n&#160;GROUP BY T1.mgr_name, T1.dept_name;<\/pre>\n<p>Another version, using the airplane hangar example:<\/p>\n<pre>SELECT PS1.pilot_name,\n&#160;&#160;&#160;&#160;&#160;&#160; CASE WHEN COUNT(PS1.plane_name) \n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&gt; (SELECT COUNT(plane_name) FROM Hanger)\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; AND COUNT(H1.plane_name) = (SELECT COUNT(plane_name)FROM Hanger)\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;THEN 'more than all'\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;WHEN COUNT(PS1.plane_name) \n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;= (SELECT COUNT(plane_name) FROM Hanger)\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; AND COUNT(H1.plane_name) ) \n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; = (SELECT COUNT(plane_name) FROM Hanger)\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;THEN 'exactly all '\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;WHEN MIN(H1.plane_name) IS NULL\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;THEN 'none'\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;ELSE 'some' END AS skill_level\n&#160;&#160;FROM PilotSkills AS PS1\n&#160;&#160;&#160;&#160;&#160;&#160; LEFT OUTER JOIN\n&#160;&#160;&#160;&#160;&#160;&#160; Hanger AS H1\n&#160;&#160;&#160;&#160;&#160;&#160; ON PS1.plane_name = H1.plane_name\n&#160;&#160;GROUP BY PS1.pilot_name;<\/pre>\n<p>We can now sum the authority numbers for all the projects within a department to determine the power this manager has over the department as a whole.&#160; If he had a total of one, he has no authority over Personnel on any project in the department.&#160; If he had a total of two, he has power over all Personnel on all projects in the department.&#160; If he had a total of three, he has both a 1 and a 2 authority total on some projects within the department.&#160; Here is the final answer.<\/p>\n<pre>&#160; Results\n&#160; mgr_name dept_power\n=====================\n&#160; M1&#160; D1&#160;&#160; 'Some'\n&#160; M1&#160; D2&#160;&#160; 'None'\n&#160; M1&#160; D3&#160;&#160; 'Some'\n&#160; M2&#160; D1&#160;&#160; 'Some'\n&#160; M2&#160; D2&#160;&#160; 'All '\n&#160; M2&#160; D3&#160;&#160; 'All '\n&#160; M3&#160; D1&#160;&#160; 'Some'\n&#160; M3&#160; D2&#160;&#160; 'All '\n&#160; M3&#160; D3&#160;&#160; 'Some'\n&#160; M4&#160; D1&#160;&#160; 'All '\n&#160; M4&#160; D2&#160;&#160; 'All '\n&#160; M4&#160; D3&#160;&#160; 'All '\n&#160;\n<\/pre>\n<p>At the end of THE MANGA GUIDE TO DATABASES, Tico the Japanese database fairy has left the Kingdom of Kod to spread Basic RDBMS and SQL across the world.&#160; I guess I get to be the SQL ogre who comes behind Tico and tells them that they just started, and that they need more than comic books introduction to do real work.&#160; I just wonder how I would look drawn in Manga Style &#8211; I am creepy enough in the Western SF novels and comics that I have been in.<\/p>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Businesses often require reports that require more than the classic set operators. Surprisingly, a business requirement can often be expressed neatly in terms of the DIVISION relationship operator: How can this be done with SQL Server? Joe Celko opens up the &#8216;Manga Guide to Databases&#8217;, meets the Database Fairy, and is inspired to explain DIVISION.&hellip;<\/p>\n","protected":false},"author":96214,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143531],"tags":[4168,4150,4151,4252,4926],"coauthors":[],"class_list":["post-519","post","type-post","status-publish","format-standard","hentry","category-t-sql-programming-sql-server","tag-database","tag-sql","tag-sql-server","tag-t-sql-programming","tag-tsql-sql-example-relational-division"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/519","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/96214"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=519"}],"version-history":[{"count":4,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/519\/revisions"}],"predecessor-version":[{"id":40051,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/519\/revisions\/40051"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=519"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=519"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=519"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=519"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}