362 CHAPTER 17: THE SELECT STATEMENT SELECT class_nbr, class_size, MIN(room_size) FROM Rooms, Classes WHERE Classes.class_size < Rooms.room_size GROUP BY class_nbr, class_size; This will give a result table with the desired room sizes, but not the room numbers. You cannot put the other columns in the SELECT list, since it would conflict with the GROUP BY clause. But also note that the classroom with 85 seats (‘r4’) is used twice, once by class ‘c1’ and then by class ‘c2’: Result class_nbr class_size MIN(room_size) ==================================== 'c1' 80 85 <== room r4 'c2' 70 85 <== room r4 'c3' 65 70 'c4' 55 65 'c5' 50 55 'c6' 40 50 Your best bet after this is to use the query in an EXISTS clause. SELECT * FROM Rooms, Classes WHERE EXISTS (SELECT class_nbr, class_size, MIN(room_size) FROM Rooms, Classes WHERE Classes.class_size < Rooms.room_size GROUP BY class_nbr, class_size); However, some versions of SQL will not allow a grouped subquery, and others will balk at an aggregate function in an EXISTS predicate. The only way I have found to rectify this is to save the results to a temporary table, then JOIN it back to the Cartesian product of Rooms and Classes. Putting the columns for Rooms into the SELECT list of the same query schema can do the second T-Join: SELECT room_nbr, room_size, MAX(class_size) FROM Rooms, Classes WHERE Classes.class_size < Rooms.room_size GROUP BY room_nbr, room_size; 17.8 Dr. Codd’s T-Join 363 This time, the results are the same as those Dr. Codd got with his procedural algorithm: Result room_nbr room_size MAX(class_size) ====================================== 'r4' 85 80 'r1' 70 65 'r6' 65 55 'r7' 55 50 'r3' 50 40 If you do a little arithmetic on the data, you find that we have 360 students and 395 seats, 6 classes and 7 rooms. This solution uses the fewest rooms, but note that the 70 students in class ‘c2’ are left out completely. Room ‘r2’ is left over, but it has only 40 seats. As it works out, the best fit of rooms to classes is given by changing the matching rule to “less than or equal.” This will leave the smallest room empty and pack the other rooms to capacity, thus: SELECT class_nbr, class_size, MIN(room_size) FROM Rooms, Classes WHERE Classes.class_size <= Rooms.room_size GROUP BY class_nbr, class_size; 17.8.1 The Croatian Solution I published this same problem in an article in DBMS magazine (Celko 1992a) and got an answer in QUEL from Miljenko Martinis of Croatia in our Letters column (Miljenko 1992). He then translated it from QUEL into SQL with two views, thus: CREATE VIEW Classrooms all possible legal pairs AS SELECT * FROM Classes, Rooms WHERE class_size < room_size; CREATE VIEW Classrooms1 smallest room for the class AS SELECT * FROM Classrooms AS CR1 WHERE room_size = (SELECT MIN(room_size) FROM Classrooms WHERE class_nbr = CR1.class_nbr); 364 CHAPTER 17: THE SELECT STATEMENT We find the answer with the simple query: SELECT class_nbr, class_size, room_size, room_nbr FROM Classrooms1 AS CR1 WHERE class_size = (SELECT MAX(class_size) FROM Classrooms1 WHERE room_nbr = CR1.room_nbr); class_nbr class_size room_size room_nbr ========================================== 'c6' 40 50 'r3' 'c5' 50 55 'r7' 'c4' 55 65 'r6' 'c3' 65 70 'r1' 'c1' 80 85 'r4' 17.8.2 The Swedish Solution I got another solution from Anders Karlsson of Mr. K Software AB in Stockholm, Sweden. Here is a version of that query: SELECT C1.class_nbr, C1.class_size, R1.room_size, R1.room_nbr FROM Classes AS C1, Rooms AS R1 WHERE C1.class_size = (SELECT MAX(C2.class_size) FROM Classes AS C2 WHERE R1.room_size > C2.class_size) AND NOT EXISTS (SELECT * FROM Rooms AS R2 WHERE R2.room_size > C1.class_size AND R2.room_size < R1.room_size); The first predicate says we have the largest class that will go into this room. The second predicate says there is no other room that would fit this class better (i.e., a room that is smaller than the candidate room and still larger than the class at which we are looking). 17.8.3 The Colombian Solution Francisco Moreno of the Department of Systems Engineering at the University of Antioquia in Colombia came up with another approach and data to demonstrate the problems in the T-Join. Clean out the existing tables and insert this data: 17.8 Dr. Codd’s T-Join 365 DELETE FROM Classes; INSERT INTO Classes VALUES ('c1', 106), ('c2', 105), ('c3', 104), ('c4', 100), ('c5', 99), ('c6', 90), ('c7', 89), ('c8', 88), ('c9', 83), ('c10', 82), ('c11', 81), ('c12', 65), ('c13', 50), ('c14', 49), ('c15', 30), ('c16', 29), ('c17', 28), ('c18', 20), ('c19', 19); DELETE FROM Rooms; INSERT INTO Rooms VALUES ('r1', 102), ('r2', 101), ('r3', 95), ('r4', 94), ('r5', 85), ('r6', 70), ('r7', 55), ('r8', 54), ('r9', 35), ('r10', 34), ('r11', 25), ('r12', 18); Using Codd’s T-Join algorithm for descending lists, you would have this mapping: 366 CHAPTER 17: THE SELECT STATEMENT 'c1' 106 'c2' 105 'c3' 104 'c4' 100 < > 'r1' 102 'c5' 99 < > 'r2' 101 'c6' 90 < > 'r3' 95 'c7' 89 < > 'r4' 94 'c8' 88 'c9' 83 < > 'r5' 85 'c10' 82 'c11' 81 'c12' 65 < > 'r6' 70 'c13' 50 < > 'r7' 55 'c14' 49 < > 'r8' 54 'c15' 30 < > 'r9' 35 'c16' 29 < > 'r10' 34 'c17' 28 'c18' 20 < > 'r11' 25 'c19' 19 'r12' 18 There are 1,317 students in classes, and 768 seats for them. You can see by inspection that some classes are too large for any room we have. If you started in ascending order, class ‘c19’ pairs with room ‘r11’ and you get another result set. This algorithm is not a best-fit answer, but a first fit answer. This is an important difference. To explain further, the first fit to class ‘c4’ is room ‘r1’, which has 102 seats; however, the best fit is room ‘r2, which has 101 seats. The algorithm would give us this result table:’ Results class_nbr class_size room_size room_nbr ========================================== 'c4' 100 102 'r1' 'c5' 99 101 'r2' 'c6' 90 95 'r3' 'c7' 89 94 'r4' 'c9' 83 85 'r5' 'c12' 65 70 'r6' 'c13' 50 55 'r7' 'c14' 49 54 'r8' 17.8 Dr. Codd’s T-Join 367 'c15' 30 35 'r9' 'c16' 29 34 'r10' 'c18' 20 25 'r11' 704 students served. If you use Swedish or Croatian solution on this data, the answer is: Swedish Result class_nbr class_size room_size room_nbr ============================================ 'c4' 100 101 'r2' 'c6' 90 94 'r4' 'c9' 83 85 'r5' 'c12' 65 70 'r6' 'c13' 50 54 'r8' 'c15' 30 34 'r10' 'c18' 20 25 'r11' 438 students served. At this point you have a result that is not complete but has the tightest mapping of each class into a room. There is another problem that was not mentioned: we have not had two classes or two rooms of the same size in the data. This will cause some other problems. Instead of trying to use a single static SQL query, we can use SQL to generate SQL code, then execute it dynamically. This solution is right but, of course, is horrible from a performance viewpoint. build a table of possible T-Join pairings DROP TABLE T-Join; CREATE TABLE T-Join AS SELECT * FROM Classes, Rooms WHERE room_size > class_size; create a temporary working table DROP TABLE Ins; CREATE TABLE Ins (class_nbr CHAR(3) NOT NULL, class_size INTEGER NOT NULL, 368 CHAPTER 17: THE SELECT STATEMENT room_nbr CHAR(3) NOT NULL, room_size INTEGER NOT NULL); create a table with the insertion code for each row SELECT 'INSERT INTO Ins SELECT class_nbr, class_size, room_nbr, room_size FROM T-Join AS T1 WHERE room_size = (SELECT MAX(room_size) FROM T-Join WHERE room_size NOT IN (SELECT room_size FROM Ins)) AND class_size = (SELECT MAX(class_size) FROM T-Join AS T2 WHERE class_size NOT IN (SELECT class_size FROM Ins) AND T2.class_size < T1.room_size);' FROM Rooms WHERE room_size > (SELECT MIN(class_size) FROM c); COMMIT; Now use "SELECT a, b, c FROM Ins;" query in a host program with dynamic SQL and execute each statement in the temporary table in order. This will give us the first answer at the start of Section 17.8.3, and it also works for the original data. Moreno’s second solution, which handles duplicates, is more complex, and I will not give it here. It uses the keys of the tables to make rows with duplicate values unique. CHAPTER 18 VIEWs, Derived Tables, Materialized Tables, and Temporary Tables V IEWS, DERIVED TABLES, MATERIALIZED tables and temporary tables are ways of putting a query into a named schema object. By that, I mean they hold the query, rather than the results of the query. A VIEW is also called a virtual table, to distinguish it from temporary and base tables. The definition of a VIEW requires that it act as if an actual physical table is created when its name is invoked. Whether or not the database system actually materializes the results or uses other mechanisms to get the same effect is implementation- defined. The definition of a VIEW is kept in the schema tables to be invoked by name wherever a table could be used. If the VIEW is updatable, then additional rules apply. The SQL Standard separates administrative (DBA) privileges from user privileges. Table creation is administrative and query execution is a user privilege, so users cannot create their own VIEW s or TEMPORARY TABLE s without having Administrative privileges granted to them. In the Standard SQL model, a temporary table acts very much like a base table. It is persistent in the schema, but it “cleans itself up” automatically so users do not have to bother, and it can be shared among several users. The temporary table has the same user privileges model as a base table. 370 CHAPTER 18: VIEWS, DERIVED TABLES, MATERIALIZED TABLES, AND TEMPORARY TABLES However, a user can build a derived table inside a query. This is like building a VIEW on the fly. With the AS operator, we can give names to the results of subquery expressions and use them. The syntax is very simple, but the scoping rules often confuse new users. (<query expression>) AS <table name> [(<column list>)] You can think of a VIEW ’s name being replaced by a derived table expression when it is invoked in a query. 18.1 VIEWs in Queries The Standard SQL syntax for the VIEW definition is CREATE VIEW <table name> [(<view column list>)] AS <query expression> [WITH [<levels clause>] CHECK OPTION] <levels clause> ::= CASCADED | LOCAL The <levels clause> option in the WITH CHECK OPTION did not exist in SQL-89, and it is still not widely implemented. Section 18.5 of this chapter will discuss this clause in detail. This clause has no effect on queries, but only on UPDATE , INSERT INTO , and DELETE FROM statements. A VIEW is different from a TEMPORARY TABLE , a derived table, or a base table. You cannot put constraints on a VIEW , as you can with base and TEMPORARY tables. A VIEW has no existence in the database until it is invoked, while a TEMPORARY TABLE is persistent. A derived table exists only in the query in which it is created. The name of the VIEW must be unique within the database schema, like a table name. The VIEW definition cannot reference itself, since it does not exist yet. Nor can the definition reference only other VIEW s; the nesting of VIEW s must eventually resolve to underlying base tables. This only makes sense if no base tables were involved, what would you be viewing? 18.2 Updatable and Read-Only VIEWs 371 18.2 Updatable and Read-Only VIEWs Unlike base tables, VIEW s are either updatable or read-only, but not both. INSERT , UPDATE , and DELETE operations are allowed on updatable VIEW s and base tables, subject to any other constraints. INSERT , UPDATE , and DELETE are not allowed on read-only VIEW s, but you can change their base tables, as you would expect. An updatable VIEW is one that can have each of its rows associated with exactly one row in an underlying base table. When the VIEW is changed, the changes pass unambiguously through the VIEW to that underlying base table. Updatable VIEW s in Standard SQL are defined only for queries that meet these criteria: 1. Built on only one table 2. No GROUP BY clause 3. No HAVING clause 4. No aggregate functions 5. No calculated columns 6. No UNION , INTERSECT , or EXCEPT 7. No SELECT DISTINCT clause 8. Any columns excluded from the VIEW must be NULL -able or have a DEFAULT in the base table, so that a whole row can be constructed for insertion By implication, the VIEW must also contain a key of the table. In short, we are absolutely sure that each row in the VIEW maps back to one and only one row in the base table. Some updating is handled by the CASCADE option in the referential integrity constraints on the base tables, not by the VIEW declaration. The definition of updatability in Standard SQL is actually fairly limited, but very safe. The database system could look at information it has in the referential integrity constraints to widen the set of allowed updatable VIEW s. You will find that some implementations are now doing just that, but it is not common yet. The SQL Standard definition of an updatable VIEW is actually a subset of the possible updatable VIEW s, and a very small subset at that. The major advantage of this definition is that it is based on syntax and not semantics. For example, these VIEW s are logically identical: . Rooms, Classes WHERE EXISTS (SELECT class_nbr, class_size, MIN(room_size) FROM Rooms, Classes WHERE Classes.class_size < Rooms.room_size GROUP BY class_nbr, class_size); However, some versions. THE SELECT STATEMENT SELECT class_nbr, class_size, MIN(room_size) FROM Rooms, Classes WHERE Classes.class_size < Rooms.room_size GROUP BY class_nbr, class_size; This will give a result. thus: SELECT class_nbr, class_size, MIN(room_size) FROM Rooms, Classes WHERE Classes.class_size <= Rooms.room_size GROUP BY class_nbr, class_size; 17.8.1 The Croatian Solution I published