Tải bản đầy đủ (.pdf) (40 trang)

Tài liệu SQL Puzzles & Answers- P8 pdf

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (368.69 KB, 40 trang )

262 PUZZLE 65 AGE RANGES FOR PRODUCTS
being, so we should be safe. A quick way is to use your auxiliary
Sequence table.
SELECT P.product_id
FROM PriceByAge AS P, Sequence AS S
WHERE S.seq BETWEEN P.low_age AND P.high_age
AND S.seq <= 150
GROUP BY P.product_id
HAVING COUNT(seq) = 150;
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
PUZZLE 66 SUDOKU 263
PUZZLE
66 SUDOKU
I thought that Sudoku, the current puzzle fad, would be a good SQL
programming problem. You start with a 9×9 grid that is further divided
into nine 3×3 regions. Some of the cells will have a digit from 1 to 9 in
them at the start of the puzzle. Your goal is to fill in all the cells with
more digits such that each row, column, and region contains one and
only one instance of each digit.
Strangely, the puzzle appeared in the United States in 1979, then
caught on in Japan in 1986 and became an international fad in 2005.
Most newspapers today carry a daily Sudoku.
How can we do this in SQL? Well we can start by modeling the grid as
an
(i, j) array with a value in the cell. The first attempt usually does
not have the region information as one of the columns. The regions do
not have names in the puzzle, so we need a way to give them names.
CREATE TABLE SudokuGrid
(i INTEGER NOT NULL
CHECK (i BETWEEN 1 AND 9),
j INTEGER NOT NULL


CHECK (j BETWEEN 1 AND 9),
val INTEGER NOT NULL
CHECK (val BETWEEN 1 AND 9),
region_nbr INTEGER NOT NULL,
PRIMARY KEY (i, j, val));
Now we need to fill it. Each (i, j) cell needs to start with all nine
digits, so we build a table of the digits 1 to 9 and do
CROSS JOINs. But
how do we get a region number?
An obvious name would be the position of the region by
(x, y)
coordinates, where
x = {1, 2, 3} and y = {1, 2, 3}. We can then make
them into one number by making x the tens place and y the units place,
so we get {11,12, 13, 21, 22, 23, 31, 32, 33} for the regions. The math
for this depends on integer arithmetic, but it is not really hard.
INSERT INTO SudokuGrid (i, j, val, region_nbr)
SELECT D1.d, D2.d, D3.d,
10*((D1.d+2)/3) + ((D2.d+2)/3) AS region_nbr
FROM Digits AS D1
CROSS JOIN Digits AS D2
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
264 PUZZLE 66 SUDOKU
CROSS JOIN Digits AS D3;
We will need a procedure to insert the known values and clear out
that value in the rows, columns, and regions. As we remove more and
more values, we hope to get a table with 81 cells that is the unique
solution for the puzzle.
Answer #1
The first attempt is usually to write three delete statements, one for rows,

one for columns, and one for the region.
BEGIN
DELETE FROM SudokuGrid rows
WHERE :my_i = i
AND :my_j <> j
AND :my_val = val;
DELETE FROM SudokuGrid columns
WHERE :my_i <> i
AND :my_j = j
AND :my_val = val;
DELETE FROM SudokuGrid region
WHERE i <> :my_i
AND j <> :my_j
AND region_nbr = 10*((:my_i+2)/3) + ((:my_j+2)/3)
AND :my_val = val);
END;
Answer #2
But this is a waste of execution time. Why use three statements when you
can write it in one? Let’s do a brute force code merge:
DELETE FROM SudokuGrid
WHERE (((:my_i = i AND j <> :my_j)
OR (:my_i <> i AND j = :my_j))
AND :my_val = val)
OR (i <> :my_i
AND j <> :my_j
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
PUZZLE 66 SUDOKU 265
AND region_nbr = 10*((:my_i+2)/3) + ((:my_j+2)/3)
AND :my_val = val);
Those nested ORs are ugly! The expression (:my_val = val) appears

twice. Get a drink, step back, and consider that the
(i, j) pairs can
relate to our input in one of four mutually exclusive ways, which require
that we remove a value from a cell or leave it. That implies a
CASE
expression instead of the nested
ANDs and ORs.
DELETE FROM SudokuGrid
WHERE CASE WHEN :my_i = i AND :my_j = j my cell
THEN 'Keep'
WHEN :my_i = i AND :my_j <> j row
THEN 'Delete'
WHEN :my_i <> i AND :my_j = j column
THEN 'Delete'
WHEN i <> :my_i AND j <> :my_j square
AND region_nbr
= 10*(:my_i+2)/3) + (:my_j+2)/3)
THEN 'Delete'
ELSE NULL END = 'Delete'
AND :my_val = val);
Answer #3
Test it and find out that this is wrong!! We need to pay special attention
to the cell where we know the value; that means two cases.
DELETE FROM SudokuGrid
WHERE CASE WHEN :my_i = i AND :my_j = j AND my_val = val
THEN 'Keep'
WHEN :my_i = i AND :my_j = j AND my_val <> val
THEN 'Delete'
WHEN :my_i = i AND :my_j <> j row
THEN 'Delete'

WHEN :my_i <> i AND :my_j = j column
THEN 'Delete'
WHEN i <> :my_i AND j <> :my_j square
AND region_nbr
= 10*(:my_i+2)/3) + (:my_j+2)/3)
THEN 'Delete'
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
266 PUZZLE 66 SUDOKU
ELSE NULL END = 'Delete'
AND :my_val = val);
The next improvement might be to put the known cells into their
own table so we have a history of the puzzle. But let’s leave that as a
problem for the reader.
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
PUZZLE 67 STABLE MARRIAGES PROBLEM 267
PUZZLE
67 STABLE MARRIAGES PROBLEM
This is a classic programming problem from procedural language classes.
The setup is fairly simple; you have a set of potential husbands and an
equally sized set of potential wives. We want to pair them up into stable
marriages.
What is a stable marriage? In 25 words or less, a marriage in which
neither partner can do better. You have a set of n men and a set of n
women. All the men have a preference scale for all the women that ranks
them from 1 to n without gaps or ties. The women have the same ranking
system for the men.
The goal is to pair off the men and women into n marriages such that
there is no pair in your final arrangement where Mr. X and Ms. Y are
matched to each other when they both would rather be matched to
someone else.

For example, let’s assume the husbands are (“Joe Celko,” “Brad Pitt”)
and the wives are (“Jackie Celko,” “Angelina Jolie”). If Jackers got
matched to Mr. Pitt, she would be quite happy. And I would enjoy Ms.
Jolie’s company. However, Mr. Pitt and Ms. Jolie can both do better than
us. Once they are paired up they will stay that way, leaving Jackers and I
still wed.
The classic Stable Marriage algorithms usually are based on
backtracking. These algorithms try a combination of couples, and then
attempt to fix any unhappy matches. When the algorithm hits on a
situation where nobody can improve their situation, they stop and give
an answer.
Two important things to know about this problem: (1) there is
always a solution, and (2) there is often more than one solution. Doing
this in SQL is really hard because SQL does not backtrack.
Remember that a stable marriage is not always a happy marriage. In
fact, in this problem, while there is always at least one arrangement of
stable marriages in any set, you most often find many different pairings
that produce a set of stable marriages. Each set of marriages will tend to
maximize either the happiness of the men or the women.
Let’s show a small example in SQL with four couples:
CREATE TABLE Husbands
(man CHAR(5) NOT NULL,
woman CHAR(5) NOT NULL,
ranking INTEGER NOT NULL CHECK (ranking > 0),
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
268 PUZZLE 67 STABLE MARRIAGES PROBLEM
PRIMARY KEY (man, woman));
INSERT INTO Husbands VALUES ('Abe', 'Joan', 1);
INSERT INTO Husbands VALUES ('Abe', 'Kathy', 2);
INSERT INTO Husbands VALUES ('Abe', 'Lynn', 3);

INSERT INTO Husbands VALUES ('Abe', 'Molly', 4);
INSERT INTO Husbands VALUES ('Bob', 'Joan', 3);
INSERT INTO Husbands VALUES ('Bob', 'Kathy', 4);
INSERT INTO Husbands VALUES ('Bob', 'Lynn', 2);
INSERT INTO Husbands VALUES ('Bob', 'Molly', 1);
INSERT INTO Husbands VALUES ('Chuck', 'Joan', 3);
INSERT INTO Husbands VALUES ('Chuck', 'Kathy', 4);
INSERT INTO Husbands VALUES ('Chuck', 'Lynn', 2);
INSERT INTO Husbands VALUES ('Chuck', 'Molly', 1);
INSERT INTO Husbands VALUES ('Dave', 'Joan', 2);
INSERT INTO Husbands VALUES ('Dave', 'Kathy', 1);
INSERT INTO Husbands VALUES ('Dave', 'Lynn', 3);
INSERT INTO Husbands VALUES ('Dave', 'Molly', 4);
CREATE TABLE Wives
(woman CHAR(5) NOT NULL,
man CHAR(5) NOT NULL,
ranking INTEGER NOT NULL CHECK (ranking > 0),
PRIMARY KEY (man, woman));
INSERT INTO Wives VALUES ('Joan', 'Abe', 1);
INSERT INTO Wives VALUES ('Joan', 'Bob', 3);
INSERT INTO Wives VALUES ('Joan', 'Chuck', 2);
INSERT INTO Wives VALUES ('Joan', 'Dave', 4);
INSERT INTO Wives VALUES ('Kathy', 'Abe', 4);
INSERT INTO Wives VALUES ('Kathy', 'Bob', 2);
INSERT INTO Wives VALUES ('Kathy', 'Chuck', 3);
INSERT INTO Wives VALUES ('Kathy', 'Dave', 1);
INSERT INTO Wives VALUES ('Lynn', 'Abe', 1);
INSERT INTO Wives VALUES ('Lynn', 'Bob', 3);
INSERT INTO Wives VALUES ('Lynn', 'Chuck', 4);
INSERT INTO Wives VALUES ('Lynn', 'Dave', 2);

INSERT INTO Wives VALUES ('Molly', 'Abe', 3);
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
PUZZLE 67 STABLE MARRIAGES PROBLEM 269
INSERT INTO Wives VALUES ('Molly', 'Bob', 4);
INSERT INTO Wives VALUES ('Molly', 'Chuck', 1);
INSERT INTO Wives VALUES ('Molly', 'Dave', 2);
The pairing of:
('Abe', 'Lynn')
('Bob', 'Joan')
('Chuck', 'Molly')
('Dave', 'Kathy')
does not work. There is a “blocking pair” in ('Abe', 'Joan'). Abe is
Joan’s first choice and he is her first choice, as shown by the rows:
Wives ('Joan', 'Abe', 1)
Husbands ('Abe', 'Joan', 1)
but they are matched to others. A simple swap will give us a stable
situation:
('Abe', 'Joan')
('Bob', 'Lynn')
('Chuck', 'Molly')
('Dave', 'Kathy')
If you use a backtracking algorithm, you do not have to generate all
possible marriage sets. Once you found a blocking pair, you would never
have to create it again. This is considerably faster than the combinatory
explosion that SQL must generate and filter. The only advantage with
SQL—and it is weak—is that the algorithms for this problem will usually
stop at the first success. They do not generate the full solution set, as
SQL does.
This answer is from Richard Romley. Simply explained, it generates
all possible marriages and filters out the failures. But there are some neat

little optimizing tricks in the code.
DROP TABLE Wife_perms;
CREATE TABLE Wife_perms
(wife CHAR(5) NOT NULL PRIMARY KEY,
tally INTEGER NOT NULL);
INSERT INTO Wife_perms VALUES ('Joan', 1);
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
270 PUZZLE 67 STABLE MARRIAGES PROBLEM
INSERT INTO Wife_perms VALUES ('Kathy', 2);
INSERT INTO Wife_perms VALUES ('Lynn', 4);
INSERT INTO Wife_perms VALUES ('Molly', 8);
Answer #1
The query for finding stable marriages is:
SELECT W1.wife AS abe_wife, W2.wife AS bob_wife,
W3.wife AS check_wife, W4.wife AS dave_wife
FROM Wife_perms AS W1, Wife_perms AS W2,
Wife_perms AS W3, Wife_perms AS W4
WHERE (W1.tally + W2.tally + W3.tally + W4.tally) = 15
AND NOT EXISTS
(SELECT *>
FROM Husbands AS H1, Husbands AS H2,
Wives AS W1, Wives AS W2
WHERE H1.man = H2.man
AND H1.ranking > H2.ranking
AND (H1.man || H1.woman) IN
('Abe' || W1.wife, 'Bob' || W2.wife,
'Chuck' || W3.wife, 'Dave' || W4.wife)
AND H2.woman = W1.woman
AND W1.woman = W2.woman
AND W1.ranking > W2.ranking

AND (W1.man || W1.woman) IN
('Abe' || W1.wife, 'Bob' || W2.wife,
'Chuck' || W3.wife, 'Dave' || W4.wife));
The first predicate generates all the permutations of wives in the
husband columns and the
EXISTS() checks for “blocking pairs” in the
row. This query will take some time to run, especially on a small
machine, and it will break down when you have a value of n too large for
the permutation trick.
The other optimization trick is the list of concatenated strings to see
that the blocking pair is in the row that was just constructed. A shorter
version of this trick is replacing
SELECT *
FROM Foo as F1, Bar as B1
WHERE F1.city = B1.city
AND F1.state = B1.state;
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
PUZZLE 67 STABLE MARRIAGES PROBLEM 271
with
SELECT *
FROM Foo as F1, Bar as B1
WHERE F1.city || F1.state = B1.city || B1.state;
to speed up a query. I will rationalize that the concatenated name is
atomic because it has meaning in itself that would be destroyed if it is
split apart. Things like (longitude, latitude) pairs are also atomic in this
sense.
There were only 4! (= 24) possible marriage collections, so this ran
pretty fast, even on a small machine. Now extend the problem to a set of
couples where (n = 8); you now have 8! (= 40,320) possible marriage
collections. And only a small number of the rows will be in the final

answer set.
Answer #2
Here is the code for the Stable Marriages problem with n = 8:
CREATE TABLE Husbands
(man CHAR(2) NOT NULL,
woman CHAR(2) NOT NULL,
ranking INTEGER NOT NULL CHECK (ranking > 0),
PRIMARY KEY (man, woman));
CREATE TABLE Wives
(woman CHAR(2) NOT NULL,
man CHAR(2) NOT NULL,
ranking INTEGER NOT NULL CHECK (ranking > 0),
PRIMARY KEY (woman, man));
INSERT INTO Husbands VALUES ('h1', 'w1', 5);
INSERT INTO Husbands VALUES ('h1', 'w2', 2);
INSERT INTO Husbands VALUES ('h1', 'w3', 6);
INSERT INTO Husbands VALUES ('h1', 'w4', 8);
INSERT INTO Husbands VALUES ('h1', 'w5', 4);
INSERT INTO Husbands VALUES ('h1', 'w6', 3);
INSERT INTO Husbands VALUES ('h1', 'w7', 1);
INSERT INTO Husbands VALUES ('h1', 'w8', 7);
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
272 PUZZLE 67 STABLE MARRIAGES PROBLEM
INSERT INTO Husbands VALUES ('h2', 'w1', 6);
INSERT INTO Husbands VALUES ('h2', 'w2', 3);
INSERT INTO Husbands VALUES ('h2', 'w3', 2);
INSERT INTO Husbands VALUES ('h2', 'w4', 1);
INSERT INTO Husbands VALUES ('h2', 'w5', 8);
INSERT INTO Husbands VALUES ('h2', 'w6', 4);
INSERT INTO Husbands VALUES ('h2', 'w7', 7);

INSERT INTO Husbands VALUES ('h2', 'w8', 5);
INSERT INTO Husbands VALUES ('h3', 'w1', 4);
INSERT INTO Husbands VALUES ('h3', 'w2', 2);
INSERT INTO Husbands VALUES ('h3', 'w3', 1);
INSERT INTO Husbands VALUES ('h3', 'w4', 3);
INSERT INTO Husbands VALUES ('h3', 'w5', 6);
INSERT INTO Husbands VALUES ('h3', 'w6', 8);
INSERT INTO Husbands VALUES ('h3', 'w7', 7);
INSERT INTO Husbands VALUES ('h3', 'w8', 5);
INSERT INTO Husbands VALUES ('h4', 'w1', 8);
INSERT INTO Husbands VALUES ('h4', 'w2', 4);
INSERT INTO Husbands VALUES ('h4', 'w3', 1);
INSERT INTO Husbands VALUES ('h4', 'w4', 3);
INSERT INTO Husbands VALUES ('h4', 'w5', 5);
INSERT INTO Husbands VALUES ('h4', 'w6', 6);
INSERT INTO Husbands VALUES ('h4', 'w7', 7);
INSERT INTO Husbands VALUES ('h4', 'w8', 2);
INSERT INTO Husbands VALUES ('h5', 'w1', 6);
INSERT INTO Husbands VALUES ('h5', 'w2', 8);
INSERT INTO Husbands VALUES ('h5', 'w3', 2);
INSERT INTO Husbands VALUES ('h5', 'w4', 3);
INSERT INTO Husbands VALUES ('h5', 'w5', 4);
INSERT INTO Husbands VALUES ('h5', 'w6', 5);
INSERT INTO Husbands VALUES ('h5', 'w7', 7);
INSERT INTO Husbands VALUES ('h5', 'w8', 1);
INSERT INTO Husbands VALUES ('h6', 'w1', 7);
INSERT INTO Husbands VALUES ('h6', 'w2', 4);
INSERT INTO Husbands VALUES ('h6', 'w3', 6);
INSERT INTO Husbands VALUES ('h6', 'w4', 5);
INSERT INTO Husbands VALUES ('h6', 'w5', 3);

INSERT INTO Husbands VALUES ('h6', 'w6', 8);
INSERT INTO Husbands VALUES ('h6', 'w7', 2);
INSERT INTO Husbands VALUES ('h6', 'w8', 1);
INSERT INTO Husbands VALUES ('h7', 'w1', 5);
INSERT INTO Husbands VALUES ('h7', 'w2', 1);
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
PUZZLE 67 STABLE MARRIAGES PROBLEM 273
INSERT INTO Husbands VALUES ('h7', 'w3', 4);
INSERT INTO Husbands VALUES ('h7', 'w4', 2);
INSERT INTO Husbands VALUES ('h7', 'w5', 7);
INSERT INTO Husbands VALUES ('h7', 'w6', 3);
INSERT INTO Husbands VALUES ('h7', 'w7', 6);
INSERT INTO Husbands VALUES ('h7', 'w8', 8);
INSERT INTO Husbands VALUES ('h8', 'w1', 2);
INSERT INTO Husbands VALUES ('h8', 'w2', 4);
INSERT INTO Husbands VALUES ('h8', 'w3', 7);
INSERT INTO Husbands VALUES ('h8', 'w4', 3);
INSERT INTO Husbands VALUES ('h8', 'w5', 6);
INSERT INTO Husbands VALUES ('h8', 'w6', 1);
INSERT INTO Husbands VALUES ('h8', 'w7', 5);
INSERT INTO Husbands VALUES ('h8', 'w8', 8);
INSERT INTO Wives VALUES ('w1', 'h1', 6);
INSERT INTO Wives VALUES ('w1', 'h2', 3);
INSERT INTO Wives VALUES ('w1', 'h3', 7);
INSERT INTO Wives VALUES ('w1', 'h4', 1);
INSERT INTO Wives VALUES ('w1', 'h5', 4);
INSERT INTO Wives VALUES ('w1', 'h6', 2);
INSERT INTO Wives VALUES ('w1', 'h7', 8);
INSERT INTO Wives VALUES ('w1', 'h8', 5);
INSERT INTO Wives VALUES ('w2', 'h1', 4);

INSERT INTO Wives VALUES ('w2', 'h2', 8);
INSERT INTO Wives VALUES ('w2', 'h3', 3);
INSERT INTO Wives VALUES ('w2', 'h4', 7);
INSERT INTO Wives VALUES ('w2', 'h5', 2);
INSERT INTO Wives VALUES ('w2', 'h6', 5);
INSERT INTO Wives VALUES ('w2', 'h7', 6);
INSERT INTO Wives VALUES ('w2', 'h8', 1);
INSERT INTO Wives VALUES ('w3', 'h1', 3);
INSERT INTO Wives VALUES ('w3', 'h2', 4);
INSERT INTO Wives VALUES ('w3', 'h3', 5);
INSERT INTO Wives VALUES ('w3', 'h4', 6);
INSERT INTO Wives VALUES ('w3', 'h5', 8);
INSERT INTO Wives VALUES ('w3', 'h6', 1);
INSERT INTO Wives VALUES ('w3', 'h7', 7);
INSERT INTO Wives VALUES ('w3', 'h8', 2);
INSERT INTO Wives VALUES ('w4', 'h1', 8);
INSERT INTO Wives VALUES ('w4', 'h2', 2);
INSERT INTO Wives VALUES ('w4', 'h3', 1);
INSERT INTO Wives VALUES ('w4', 'h4', 3);
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
274 PUZZLE 67 STABLE MARRIAGES PROBLEM
INSERT INTO Wives VALUES ('w4', 'h5', 7);
INSERT INTO Wives VALUES ('w4', 'h6', 5);
INSERT INTO Wives VALUES ('w4', 'h7', 4);
INSERT INTO Wives VALUES ('w4', 'h8', 6);
INSERT INTO Wives VALUES ('w5', 'h1', 3);
INSERT INTO Wives VALUES ('w5', 'h2', 7);
INSERT INTO Wives VALUES ('w5', 'h3', 2);
INSERT INTO Wives VALUES ('w5', 'h4', 4);
INSERT INTO Wives VALUES ('w5', 'h5', 5);

INSERT INTO Wives VALUES ('w5', 'h6', 1);
INSERT INTO Wives VALUES ('w5', 'h7', 6);
INSERT INTO Wives VALUES ('w5', 'h8', 8);
INSERT INTO Wives VALUES ('w6', 'h1', 2);
INSERT INTO Wives VALUES ('w6', 'h2', 1);
INSERT INTO Wives VALUES ('w6', 'h3', 3);
INSERT INTO Wives VALUES ('w6', 'h4', 6);
INSERT INTO Wives VALUES ('w6', 'h5', 8);
INSERT INTO Wives VALUES ('w6', 'h6', 7);
INSERT INTO Wives VALUES ('w6', 'h7', 5);
INSERT INTO Wives VALUES ('w6', 'h8', 4);
INSERT INTO Wives VALUES ('w7', 'h1', 6);
INSERT INTO Wives VALUES ('w7', 'h2', 4);
INSERT INTO Wives VALUES ('w7', 'h3', 1);
INSERT INTO Wives VALUES ('w7', 'h4', 5);
INSERT INTO Wives VALUES ('w7', 'h5', 2);
INSERT INTO Wives VALUES ('w7', 'h6', 8);
INSERT INTO Wives VALUES ('w7', 'h7', 3);
INSERT INTO Wives VALUES ('w7', 'h8', 7);
INSERT INTO Wives VALUES ('w8', 'h1', 8);
INSERT INTO Wives VALUES ('w8', 'h2', 2);
INSERT INTO Wives VALUES ('w8', 'h3', 7);
INSERT INTO Wives VALUES ('w8', 'h4', 4);
INSERT INTO Wives VALUES ('w8', 'h5', 5);
INSERT INTO Wives VALUES ('w8', 'h6', 6);
INSERT INTO Wives VALUES ('w8', 'h7', 1);
INSERT INTO Wives VALUES ('w8', 'h8', 3);
wife permutations
CREATE TABLE Wife_perms
(wife CHAR(2) NOT NULL PRIMARY KEY,

tally INTEGER NOT NULL);
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
PUZZLE 67 STABLE MARRIAGES PROBLEM 275
INSERT INTO Wife_perms VALUES ('w1', 1);
INSERT INTO Wife_perms VALUES ('w2', 2);
INSERT INTO Wife_perms VALUES ('w3', 3);
INSERT INTO Wife_perms VALUES ('w4', 4);
INSERT INTO Wife_perms VALUES ('w5', 5);
INSERT INTO Wife_perms VALUES ('w6', 6);
INSERT INTO Wife_perms VALUES ('w7', 7);
INSERT INTO Wife_perms VALUES ('w8', 8);
This query produces the correct results:
SELECT W1.wife AS h1, W2.wife AS h2, W3.wife AS h3,
W4.wife AS h4, W5.wife AS h5, W6.wife AS h6, W7.wife AS h7,
W8.wife AS h8
FROM Wife_perms AS W1, Wife_perms AS W2, Wife_perms AS W3,
Wife_perms AS W4, Wife_perms AS W5, Wife_perms AS W6,
Wife_perms AS W7 , Wife_perms AS W8
WHERE (W1.tally + W2.tally + W3.tally + W4.tally +
W5.tally + W6.tally + W7.tally + W8.tally) = 255
AND NOT EXISTS
(SELECT *
FROM Husbands AS H1, Husbands AS H2,
Wives AS W1, Wives AS W2
WHERE H1.man = H2.man
AND H1.ranking > H2.ranking
AND (H1.man || H1.woman) IN
('h1' || H1.wife, 'h2' || H2.wife,
'h3' || H3.wife, 'h4' || H4.wife,
'h5' || H5.wife, 'h6' || H6.wife,

'h7' || H7.wife, 'h8' || H8.wife)
AND H2.woman = W1.woman
AND W1.woman = W2.woman
AND W1.ranking > W2.ranking
AND (W1.man || W1.woman) IN
('h1' || H1.wife, 'h2' || H2.wife,
'h3' || H3.wife, 'h4' || H4.wife,
'h5' || H5.wife, 'h6' || H6.wife,
'h7' || H7.wife, 'h8' || H8.wife));
The final results are:
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
276 PUZZLE 67 STABLE MARRIAGES PROBLEM
h1 h2 h3 h4 h5 h6 h7 h8
================================
w2 w4 w3 w1 w7 w5 w8 w6
w2 w4 w3 w8 w1 w5 w7 w6
w3 w6 w4 w1 w7 w5 w8 w2
w3 w6 w4 w8 w1 w5 w7 w2
w6 w3 w4 w1 w7 w5 w8 w2
w6 w3 w4 w8 w1 w5 w7 w2
w6 w4 w3 w1 w7 w5 w8 w2
w6 w4 w3 w8 w1 w5 w7 w2
w7 w4 w3 w8 w1 w5 w2 w6
This example was taken from Niklaus Wirth’s book (see the references
list at the end of this puzzle).
Answer #3
The key was to maximize the performance of the NOT EXISTS test in the
final
SELECT. Unstable is a table of the unstable relationships—in a form
designed to be used as the object of the

LIKE clause in the final query.
CREATE TABLE Unstable
(bad_marriage CHAR(16) NOT NULL);
INSERT INTO Unstable
SELECT DISTINCT
CASE WHEN W.man = 'h1' THEN W.woman
WHEN Y.man = 'h1' THEN Y.woman
ELSE '__' END
|| CASE WHEN W.man = 'h2' THEN W.woman
WHEN Y.man = 'h2' THEN Y.woman
ELSE '__' END
|| CASE WHEN W.man = 'h3' THEN W.woman
WHEN Y.man = 'h3' THEN Y.woman
ELSE '__' END
|| CASE WHEN W.man = 'h4' THEN W.woman
WHEN Y.man = 'h4' THEN Y.woman
ELSE '__' END
|| CASE WHEN W.man = 'h5' THEN W.woman
WHEN Y.man = 'h5' THEN Y.woman
ELSE '__' END
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
PUZZLE 67 STABLE MARRIAGES PROBLEM 277
|| CASE WHEN W.man = 'h6' THEN W.woman
WHEN Y.man = 'h6' THEN Y.woman
ELSE '__' END
|| CASE WHEN W.man = 'h7' THEN W.woman
WHEN Y.man = 'h7' THEN Y.woman
ELSE '__' END
|| CASE WHEN W.man = 'h8' THEN W.woman
WHEN Y.man = 'h8' THEN Y.woman

ELSE '__' END
FROM Husbands AS W, Husbands AS X,
Wives AS Y, Wives AS Z
WHERE W.man = X.man
AND W.ranking > X.ranking
AND X.woman = Y.woman
AND Y.woman = Z.woman
AND Y.ranking > Z.ranking
AND Z.man = W.man
SELECT A.name AS h1, B.name AS h2, C.name AS h3, D.name AS
h4,
E.name AS h5, F.name AS h6, G.name AS h7, H.name AS
h8
FROM wife_hdr AS a, wife_hdr AS b, wife_hdr AS c, wife_hdr
AS d,
wife_hdr AS e, wife_hdr AS f, wife_hdr AS g, wife_hdr
AS h
WHERE B.name NOT IN (A.name)
AND C.name NOT IN (A.name, B.name)
AND D.name NOT IN (A.name, B.name, C.name)
AND E.name NOT IN (A.name, B.name, C.name, D.name)
AND F.name NOT IN (A.name, B.name, C.name, D.name,
E.name)
AND G.name NOT IN (A.name, B.name, C.name, D.name,
E.name, F.name)
AND H.name NOT IN (A.name, B.name, C.name, D.name,
E.name, F.name, G.name)
AND NOT EXISTS
(SELECT * FROM Unstable
WHERE A.name || B.name || C.name || D.name

|| E.name || F.name || G.name || H.name
LIKE bad_marriage)
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
278 PUZZLE 67 STABLE MARRIAGES PROBLEM
h1 h2 h3 h4 h5 h6 h7 h8

w3 w6 w4 w8 w1 w5 w7 w2
w6 w4 w3 w8 w1 w5 w7 w2
w6 w3 w4 w8 w1 w5 w7 w2
w3 w6 w4 w1 w7 w5 w8 w2
w6 w4 w3 w1 w7 w5 w8 w2
w6 w3 w4 w1 w7 w5 w8 w2
w7 w4 w3 w8 w1 w5 w2 w6
w2 w4 w3 w8 w1 w5 w7 w6
w2 w4 w3 w1 w7 w5 w8 w6
When Richard timed this, he got 40,000 rows in 4 seconds at an
average throughput of around 10,000 rows per second. I don’t think I’m
going to do any better than that.
The first predicate generates all the permutations of wives in the
husband columns, and the
EXISTS() checks for blocking pairs in the
row. This query will take some time to run, especially on a small
machine, and it will break down when you have a value of n too large for
the permutation trick.
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
PUZZLE 67 STABLE MARRIAGES PROBLEM 279
REFERENCES
Gusfierld, Dan, and Irving, Robert W., The Stable Marriage Problem:
Structure & Algorithms, ISBN 0-262-07118-5.
Knuth, Donald E., CRM Proceedings & Lecture Notes, Vol #10, “Stable

Marriage and Its Relation to Other Combinatorial Problems,” ISBN
0-8218-0603-3.
This booklet, which reproduces seven expository lectures given by
the author in November 1975, is a gentle introduction to the analysis of
algorithms using the beautiful theory of stable marriages as a vehicle to
explain the basic paradigms of that subject.
Wirth, Niklaus, Section 3.6, Algorithms + Data Structures = Programs,
ISBN 0-13-022418-9.
This section gives an answer in Pascal and a short analysis of the
algorithm. In particular, I used his data for my example. He gives
several answers that give a varying “degrees of happiness” for husbands
and wives.
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
280 PUZZLE 68 CATCHING THE NEXT BUS
PUZZLE
68 CATCHING THE NEXT BUS
Suppose a man randomly runs to the bus station and catches the next
bus leaving the station. Assume there are only two bus routes in this
town, and call them “A” and “B.” The schedule for each route spaces the
bus trips one hour apart. Your first thought may be that the random
traveler would spend approximately equal time on both routes, but the
truth is that he spends far more time on route A than on route B. Why?
The A bus leaves on the hour, and the B bus leaves at five minutes
after the hour. In order to catch the B bus, the traveler has to be in the
station between the hour and five after the hour. The rest of the time, he
will sit and wait for the A bus to arrive on the hour.
The problem of finding the next bus leaving the station is a fairly easy
bit of SQL. Here is a simple table for an imaginary bus line schedule. It
gives the route number and the departure and arrival times for one day,
without regard to the departure or destination points:

CREATE TABLE Schedule
(route_nbr INTEGER NOT NULL,
depart_time TIMESTAMP NOT NULL,
arrive_time TIMESTAMP NOT NULL,
CHECK (depart_time < arrive_time),
PRIMARY KEY (route_nbr, depart_time));
INSERT INTO Schedule
VALUES (3, '2006-02-09 10:00', '2006-02-09 14:00'),
(4, '2006-02-09 16:00', '2006-02-09 17:00'),
(5, '2006-02-09 18:00', '2006-02-09 19:00'),
(6, '2006-02-09 20:00', '2006-02-09 21:00'),
(7, '2006-02-09 11:00', '2006-02-09 13:00'),
(8, '2006-02-09 15:00', '2006-02-09 16:00'),
(9, '2006-02-09 18:00', '2006-02-09 20:00');
If you want to catch the next bus at “2006-02-09 15:30,” you should
return (
route_nbr = 4). If the time is “2006-02-09 16:30,” then you
should return route numbers 4 and 9, since both of them will depart at
the same time.
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
PUZZLE 68 CATCHING THE NEXT BUS 281
Answer #1
Okay, how can we solve the problem? The computational way to do so is
to find the departure times that occur on or after the time you arrived at
the station:
SELECT E1.event_id, E1.depart_time, E1.arrive_time
FROM Events AS E1
WHERE E1.depart_time
= (SELECT MIN(E2.depart_time)
FROM Events AS E2

WHERE :my_time <= E2.depart_time);
This will work fine because the primary key should give you fast
access to the departure time column for computing the
MIN().
Answer #2
The next question that comes to mind is: Can we find a way to do this
without computations?
Let’s try adding a column for the waiting period before the departure
time in a given day. Route 3 is the first bus out, so if you get to the
station any time between midnight and 10:00 hrs on February 9, that is
the next bus out. If you get to the station between 10:00 and 11:00 hrs,
you will take Route 7, and so forth.
CREATE TABLE Schedule
(route_nbr INTEGER NOT NULL,
wait_time TIMESTAMP NOT NULL,
depart_time TIMESTAMP NOT NULL,
arrive_time TIMESTAMP NOT NULL,
CHECK (depart_time < arrive_time),
PRIMARY KEY (route_nbr, depart_time));
Now the table looks like this:
INSERT INTO Schedule
VALUES (3, '2006-02-09 00:00', '2006-02-09 10:00', '2006-
02-09 14:00'),
(7, '2006-02-09 10:00', '2006-02-09 11:00', '2006-02-
09 13:00'),
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
282 PUZZLE 68 CATCHING THE NEXT BUS
(4, '2006-02-09 15:00', '2006-02-09 16:00', '2006-02-
09 17:00'),
(5, '2006-02-09 16:00', '2006-02-09 18:00', '2006-02-

09 19:00'),
(6, '2006-02-09 18:00', '2006-02-09 20:00', '2006-02-
09 21:00'),
(8, '2006-02-09 11:00', ‘2006-02-09 15:00', '2006-02-
09 16:00'),
(9, '2006-02-09 16:00', '2006-02-09 18:00', '2006-02-
09 20:00');
The query can now be done without a subquery:
SELECT E1.event_id, E1.depart_time, E1.arrive_time
FROM Events AS E1
WHERE :my_time BETWEEN E1.wait_time AND depart_time;
Will this run better? Well, it will require an extra timestamp for each
row in the schedule. That means (365 days per year * (n) routes in the
system * size of a timestamp) extra storage from the first version of the
table; this is not that bad. In the case of a bus schedule, you can assume
that it will remain constant for at least several months. The real question
is how much trouble is computing the wait time? It will be about the
same as running the first query once and using the data as part of an
UPDATE or INSERT.
It only takes a little adjusting of your own mindset to start seeing
table-driven answers for database problems.
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
PUZZLE 69 LIFO-FIFO INVENTORY 283
PUZZLE
69 LIFO-FIFO INVENTORY
Imagine a very simple inventory of one kind of item, widgets, into which
we add stock once a day. This inventory is then used to fill orders that
also come in once a day. Here is the table for this toy problem:
CREATE TABLE WidgetInventory
(receipt_nbr INTEGER NOT NULL PRIMARY KEY,

purchase_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT
NULL,
qty_on_hand INTEGER NOT NULL
CHECK (qty_on_hand >= 0),
unit_price DECIMAL (12,4) NOT NULL);
with the following data:
WidgetInventory
receipt_nbr purchase_date qty_on_hand unit_price
================================================
1 '2005-08-01' 15 10.00
2 '2005-08-02' 25 12.00
3 '2005-08-03' 40 13.00
4 '2005-08-04' 35 12.00
5 '2005-08-05' 45 10.00
The business now sells 100 units on 2005-08-05. How do you
calculate the value of the stock sold? There is not one right answer, but
here are some options:
1. Use the current replacement cost, which is $10.00 per unit as
of 2005-08-05. That would mean the sale cost us only
$1,000.00 because of a recent price break.
2. Use the current average price per unit. We have a total of 160
units, for which we paid a total of $1,840.00; that gives us an
average cost of $11.50 per unit, or $1,150.00 in total inventory
costs.
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
284 PUZZLE 69 LIFO-FIFO INVENTORY
3. Use LIFO, which stands for “last in, first out.” We start by look-
ing at the most recent purchases and work backward through
time
2005-08-05: 45 * $10.00 = $450.00 and 45 units

2005-08-04: 35 * $12.00 = $420.00 and 80 units
2005-08-03: 20 * $13.00 = $260.00 and 100 with 20 units
left over
for a total of $1,130.00 in inventory cost.
4. Use FIFO, which stands for “first in, first out.” We start by
looking at the earliest purchases and work forward through
time
2005-08-01: 15 * $10.00 = $150.00 and 15 units
2005-08-02: 25 * $12.00 = $300.00 and 40 units
2005-08-03: 40 * $13.00 = $520.00 and 80 units
2005-08-04: 20 * $12.00 = $240.00 with 15 units left over
for a total of $1,210.00 in inventory costs.
The first two scenarios are trivial to program.
CREATE VIEW (current_replacement_cost)
AS
SELECT unit_price
FROM WidgetInventory
WHERE purchase_date
= (SELECT MAX(purchase_date) FROM WidgetInventory);
CREATE VIEW (average_replacement_cost)
AS
SELECT SUM(unit_price * qty_on_hand)/SUM(qty_on_hand)
FROM WidgetInventory;
LIFO and FIFO are more interesting because they involve looking at
matching the order against blocks of inventory in a particular order.
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
PUZZLE 69 LIFO-FIFO INVENTORY 285
Answer #1
Consider this view:
CREATE VIEW LIFO (stock_date, unit_price, tot_qty_on_hand,

tot_cost)
AS
SELECT W1.purchase_date, W1.unit_price,
SUM(W2.qty_on_hand), SUM(W2.qty_on_hand *
W2.unit_price)
FROM WidgetInventory AS W1,
WidgetInventory AS W2
WHERE W2.purchase_date <= W1.purchase_date
GROUP BY W1.purchase_date, W1.unit_price;
A row in this view tells us the total quantity on hand, the total cost of
the goods in inventory, and what we were paying for items on each date.
The quantity on hand is a running total. We can get the LIFO cost with
this query:
SELECT (tot_cost - ((tot_qty_on_hand - :order_qty) *
unit_price))
AS cost
FROM LIFO AS L1
WHERE stock_date
= (SELECT MIN(stock_date)
FROM LIFO AS L2
WHERE tot_qty_on_hand >= :order_qty);
This is straight algebra and a little logic. You need to find the most
recent date when we had enough (or more) quantity on hand to meet the
order. If, by dumb blind luck, there is a day when the quantity on hand
exactly matched the order, return the total cost as the answer. If the
order was for more than we have in stock, then return nothing. If we go
back to a day when we had more in stock than the order was for, look at
the unit price on that day, multiply by the overage, and subtract it.
Answer #2
Alternatively, you can use a derived table and a CASE expression. The

CASE expression computes the cost of units that have a running total
quantity less than the
:order_qty and then performs algebra on the
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
286 PUZZLE 69 LIFO-FIFO INVENTORY
final block of inventory, which would put the running total over the
limit. The outer query does a sum on these blocks:
SELECT SUM(W3.v) AS cost
FROM (SELECT W1.unit_price
* CASE WHEN SUM(W2.qty_on_hand) <= :order_qty
THEN W1.qty_on_hand
ELSE :order_qty
- (SUM(W2.qty_on_hand) -
W1.qty_on_hand)
END
FROM WidgetInventory AS W1,
WidgetInventory AS W2
WHERE W1.purchase_date <= W2.purchase_date
GROUP BY W1.purchase_date, W1.qty_on_hand,
W1.unit_price
HAVING (SUM(W2.qty_on_hand) - W1.qty_on_hand) <=
:order_qty)
AS W3(v);
FIFO can be found with a similar VIEW or derived table:
CREATE VIEW FIFO (stock_date, unit_price, tot_qty_on_hand,
tot_cost)
AS
SELECT W1.purchase_date, W1.unit_price,
SUM(W2.qty_on_hand), SUM(W2.qty_on_hand *
W2.unit_price)

FROM WidgetInventory AS W1,
WidgetInventory AS W2
WHERE W2.purchase_date <= W1.purchase_date
GROUP BY W1.purchase_date, W1.unit_price;
with the corresponding query:
SELECT (tot_cost - ((tot_qty_on_hand - :order_qty) *
unit_price)) AS cost
FROM FIFO AS F1
WHERE stock_date
= (SELECT MIN (stock_date)
FROM FIFO AS F2
WHERE tot_qty_on_hand >= :order_qty);
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.

×