Database Systems: The Complete Book
Solutions for Chapter 8
Revised 11/4/01.

Solutions for Section 8.1
Solutions for Section 8.2
Solutions for Section 8.4
Solutions for Section 8.5
Solutions for Section 8.6
Solutions for Section 8.7

Solutions for Section 8.1

Exercise 8.1.1(a)

In the following, we use macro NO_MORE_TUPLES as defined in the section.
void closestMatchPC() {

    EXEC SQL BEGIN DECLARE SECTION;
        char manf[30], SQLSTATE[6];
        int targetPrice, /* holds price given by user */
            speedOfClosest, modelOfClosest, priceOfClosest,
                         /* for closest price found so far */
            tempSpeed, tempModel, tempPrice;
                         /* for tuple just read from PC */
    EXEC SQL END DECLARE SECTION;

    EXEC SQL DECLARE pcCursor CURSOR FOR
        SELECT model, price, speed FROM PC;

    EXEC SQL OPEN pcCursor;

    /* ask user for target price and read the answer into variable
       targetPrice */

    /* Initially, the first PC is the closest to the target price.
       If PC is empty, we cannot answer the question, and so abort. */

    EXEC SQL FETCH FROM pcCursor INTO :modelOfClosest, :priceOfClosest,
                                      :speedOfClosest;
    if(NO_MORE_TUPLES) /* print message and exit */ ;

    while(1) {
        EXEC SQL FETCH pcCursor INTO :tempModel, :tempPrice,
                                     :tempSpeed;
        if(NO_MORE_TUPLES) break;
        if( /* tempPrice closer to targetPrice than is priceOfClosest */) {
            modelOfClosest = tempModel;
            priceOfClosest = tempPrice;
            speedOfClosest = tempSpeed;
        }
    }
    /* Now, modelOfClosest is the model whose price is closest to
       target.  We must get its manufacturer with a single-row select */
    EXEC SQL SELECT maker
             INTO :manf
             FROM Product
             WHERE model = :modelOfClosest;
    printf("manf = %s, model = %d, speed = %d\n", :manf, :modelOfClosest,
                                                 :speedOfClosest);

    EXEC SQL CLOSE CURSOR pcCursor;
}

Exercise 8.1.1(f)

To make sure that we don't change the price of ``new'' PC's, we have only to make the cursor insensitive.

Again, the macro NO_MORE_TUPLES is used to test for the end of the relation.

void lowerPrices() {

    EXEC SQL BEGIN DECLARE SECTION;
        char SQLSTATE[6];
    EXEC SQL END DECLARE SECTION;

    EXEC SQL DECLARE pcCursor INSENSITIVE CURSOR FOR PC;

    EXEC SQL OPEN pcCursor;

    while(1) {
        EXEC SQL FETCH FROM pcCursor;
        if(NO_MORE_TUPLES) break;
        EXEC SQL UPDATE PC
                 SET price = price - 100
                 WHERE CURRENT OF execCursor;
    }

    EXEC SQL CLOSE pcCursor;
}

Exercise 8.1.3

void twoMoreExpensive() {

    EXEC SQL BEGIN DECLARE SECTION;
        char SQLSTATE[6], cd[5], cd1[5];
        int model, model1, speed, speed1, ram, ram1, price, price1;
        float hd, hd1; /* we use the variables cd1, model1, etc, to
            read the tuple 2 positions later, to see if the speed has
            not changed (in which case there are at least two PC's
            with the same speed and at least as high a price) */
    EXEC SQL END DECLARE SECTION;

    EXEC SQL DECLARE pcCursor SCROLL CURSOR FOR
        SELECT * FROM PC ORDER BY speed, price;

    EXEC SQL OPEN pcCursor;

    while(1) {
        EXEC SQL FETCH NEXT FROM pcCursor INTO :model, :speed, :ram,
                                               :hd, :cd, :price;
        if(NO_MORE_TUPLES) break;
        EXEC SQL FETCH RELATIVE +2 FROM pcCursor INTO :model1, :speed1, :ram1,
                                                      :hd1, :cd1, :price1;
        if(NO_MORE_TUPLES) break;
        if(speed1 == speed) /* print the tuple :model, :speed, etc. */ ;
        EXEC SQL FETCH RELATIVE -2 FROM pcCursor INTO :model1, :speed1, :ram1,
                                                      :hd1, :cd1, :price1;
        /* line above just to reset the cursor to where it was */
    }

    EXEC SQL CLOSE pcCursor;

Return to Top

Solutions for Section 8.2

Exercise 8.2.1(a)

CREATE FUNCTION PresNetWorth(IN studioName CHAR[15])

DECLARE presNetWorth INT;

BEGIN
    SELECT netWorth
    INTO presNetWorth
    FROM Studio, MovieExec
    WHERE Studio.name = studioName AND presC# = cert#;
    RETURN(presNetWorth);
END;

Exercise 8.2.1(b)

CREATE FUNCTION status(IN person, IN addr)

DECLARE isStar INT;
DECLARE isExec INT;

BEGIN
    SELECT COUNT(*)
    INTO isStar
    FROM MovieStar
    WHERE MovieStar.name = person AND MovieStar.address = addr;
    SELECT COUNT(*)
    INTO isExec
    FROM MovieExec
    WHERE MovieExec.name = person AND MovieExec.address = addr;
    IF isStar + isExec = 0 THEN RETURN(4)
    ELSE RETURN(isStar + 2*isExec)
    END IF;
END;

Exercise 8.2.1(c)

CREATE PROCEDURE twoLongest(
    IN studio CHAR(15),
    OUT longest VARCHAR(255),
    OUT second VARCHAR(255)
)

DECLARE longestLg INT;
DECLARE secondLg INT;
DECLARE t VARCHAR(255);
DECLARE l INT;
DECLARE Not_Found CONDITION FOR SQLSTATE = '02000';
DECLARE MovieCursor CURSOR FOR
    SELECT title, length FROM Movie WHERE studioName = studio;

BEGIN
    SET longest = NULL;
    SET second = NULL;
    SET longestLg = -1;
    SET secondLg = -1;
    OPEN MovieCursor;
    mainLoop: LOOP
        FETCH MovieCursor INTO t, l;
        IF Not_Found THEN LEAVE mainLoop END IF;
        IF l > longestLg THEN
            SET secondLg = longestLg;
            SET second = longest;
            SET longestLg = l;
            SET longest = t;
        ELSIF l > secongLg THEN
            SET secondLg = l;
            SET second = t;
        END IF;
    END LOOP;
    CLOSE MovieCursor;
END;

In explanation, as we run through movies, we need to remember not only the titles of the two longest movies seen so far, but their lengths. That way, when we see a new title and length, fetched into the pair of local variables (t, l), we can compare the length l with the two longest so far. The body of the loop first asks if l is longer than the longest; if so, the old longest becomes second, and the current movie becomes longest. If the current movie is not longest, then we next ask if it is longer than the second longest, and we replace the latter, if so.

Exercise 8.2.2(a)

One can actually write a tricky SQL query that will retrieve the model with the closest price, use this query in a single-row select, and return the selected model number. However, we can also scan the PC's and keep track of how close we have come, along with the number of the model whose price has so far been the closest. To simplify, we maintain the square of the difference between the closest price and the target price, so we don't have to worry about whether the closest price is above or below the target (you need that trick in the SQL SELECT-INTO statement as well).

Since we need to take the first model as ``closest'' regardless of its price, we use -1 as a value of the square of the price differences to indicate that there is no closest model yet selected.

CREATE FUNCTION closestMatchPC(IN targetPrice INT)

DECLARE closestModel INT;
DECLARE diffSq INT;
DECLARE currSq INT;
DECLARE m INT;
DECLARE p INT;
DECLARE Not_Found CONDITION FOR SQLSTATE '02000';
DECLARE PCCursor CURSOR FOR
    SELECT model, price FROM PC;

BEGIN
    SET closestModel = -1;
    SET diffSq = -1;
    OPEN PCCursor;
    mainLoop: LOOP
        FETCH PCCursor INTO m, p;
        IF Not_Found THEN LEAVE mainLoop END IF;
        SET currSq = (p - targetPrice)*(p - targetPrice);
        IF diffSq = -1 OR diffSq > currSq
            THEN BEGIN
                SET closestModel = m;
                SET diffSq = currSq;
        END IF;
    END LOOP;
    CLOSE PDCursor;
    RETURN(closestModel);
END;

Return to Top

Solutions for Section 8.4

Exercise 8.4.1(a)

#include sqlcli.h
int closestMatchPC(int targetPrice) {

    int diffSq, currSq, closestModel;
    SQLHENV myEnv;
    SQLHDBC myCon;
    SQLHSTMT pcStat;
    SQLINTEGER m, p, mInfo, pInfo;

    diffSq = closestModel = -1;
    SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &myEnv);
    SQLAllocHandle(SQL_HANDLE_DBC, myEnv, &myCon);
    SQLAllocHandle(SQL_HANDLE_STMT, myCon, &pcStat);
    SQLPrepare(pcStat, "SELECT model, price FROM PC", SQL_NTS);
    SQLExecute(pcStat);
    SQLBindCol(pcStat, 1, SQL_INTEGER, &m, size(m), &mInfo);
    SQLBindCol(pcStat, 2, SQL_INTEGER, &p, size(p), &pInfo);
    while(SQLFetch(pcStat) != SQL_NO_DATA) {
        currSq = (p - targetPrice)*(p - targetPrice);
        if(diffSq == -1 || diffSq > currSq) {
            diffSq = currSq;
            closestModel = m;
        }
    }
    return(m);
}

Exercise 8.4.1(f)

#include sqlcli.h
void lowerPrices() {
    SQLHENV myEnv;
    SQLHDBC myCon;
    SQLHSTMT pcStat;
    SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &myEnv);
    SQLAllocHandle(SQL_HANDLE_DBC, myEnv, &myCon);
    SQLAllocHandle(SQL_HANDLE_STMT, myCon, &pcStat);
    SQLPrepare(pcStat, "UPDATE PC SET price = price - 100", SQL_NTS);
    SQLExecute(pcStat);
}

Return to Top

Solutions for Section 8.5

Exercise 8.5.1(a)

int closestMatchPC(int targetPrice) {

    Connection myCon = DriverManager.getConnection(appropriate arguments);
    PreparedStatement PCStat = myCon.createStatement(
        "SELECT model, price FROM PC"
    );
    ResultSet PCresult = PCStat.executeQuery();
    int diffSq = -1;
    int closestmodel = -1;
    while(PCresult.next()) {
        int m = PCresult.getInt(1);
        int p = PCresult.getInt(2);
        int currSq = (p - targetPrice)*(p - targetPrice);
        if(diffSq == -1 || diffSq > currSq) {
            diffSq = currSq;
            closestModel = m;
        }
    }
    return(m);
}

Exercise 8.5.1(f)

void lowerPrices() {
    Connection myCon = DriverManager.getConnection(appropriate arguments);
    Statement PCStat = myCon.createStatement();
    PCStat.executeUpdate("UPDATE PC SET price = price - 100");
}

Return to Top

Solutions for Section 8.6

Exercise 8.6.1(b)

     EXEC SQL BEGIN DECLARE SECTION;
         int modelNo;
     EXEC SQL END DECLARE SECTION;

     void deleteModel() {
          /* get the model number from the user and store it in
             modelNo
          */

          EXEC SQL SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

          EXEC SQL DELETE FROM Product
                   WHERE model = :modelNo;

          EXEC SQL DELETE FROM PC
                   WHERE model = :modelNo;

          EXEC SQL COMMIT;
     }
We think that it is wise to set the isolation level to serializable, even though no tuples are written by this transaction. The potential problem is that another transaction could read one of the two tuples for a given model, either from Product or PC, while this transaction was deleting the other tuple. On the other hand, that transaction, if it really needed to be sure that either both tuples or none were present, could set its own isolation level to serializable. Since there is no harm that can come to our transaction if one and not the other tuple is present, we think it is also acceptable to replace the SET-TRANSACTION statement by:
         EXEC SQL SET TRANSACTION READ WRITE
                                  ISOLATION LEVEL READ UNCOMMITTED;

Exercise 8.6.4

It makes no sense to run T serializably. Once T starts, it would never see any changes to the database. In practice, a long-running transaction like T would have to be aborted by the system periodically. That would have the fortunate effect of allowing T to restart and see any new PC's that were added to the database.

For T's purposes, it is sufficent if it runs with isolation level repeatable-read. That will guarantee that whenever it sees a tuple, it will continue to see that tuple. However, it will also be allowed to see new tuples as they are added to the database. The potential problem with repeatable-read is that the transaction of Exercise 8.6.1(c), which lowers the price of a PC, might not be able to run while T is running, because it would change the tuple with the old price, which T is required to see. On the other hand, systems that support ``multiversion'' concurrency control would allow T to see the old tuple, while other transactions can see the new one.

T can also run at isolation level read-committed, and this choice is probably best. It will eventually see any new or updated tuple that the transactions of Exercise 8.6.1(c) or (d) produce, while not putting any constraints on these transactions.

Isolation level read-uncommitted for T is a possibility, although it would have the consequence that a new PC could be inserted into the database very briefly, while the person inserting the PC has a change of heart and aborts the change at the terminal, while T has already seen the PC's information.

Return to Top

Solutions for Section 8.7

Exercise 8.7.1(c)

We need:
  1. SELECT on Movie
  2. SELECT on Studio
  3. INSERT on Studio
The privilege INSERT(name) on Studio will suffice for (3).

Exercise 8.7.2

Here are the diagrams after Step 4; Step 5; Step 6.

Return to Top