Sunday, October 25, 2009

Lab 5.3 Nested IF Statements

Team-Fly
 

 

Oracle® PL/SQL® Interactive Workbook, Second Edition
By
Benjamin Rosenzweig, Elena Silvestrova
Table of Contents

Chapter 5. 
Conditional Control: IF Statements



Lab 5.3 Nested IF Statements



Lab Objectives


After this Lab, you will be able to:


  • Use Nested IF Statements


You have encountered different types of conditional controls: IF-THEN statement, IF-THEN-ELSE statement, and ELSIF statement. These types of conditional controls can be nested inside of anotherfor example, an IF statement can be nested inside an ELSIF and vice versa. Consider the following:





FOR EXAMPLE



DECLARE
v_num1 NUMBER := &sv_num1;
v_num2 NUMBER := &sv_num2;
v_total NUMBER;
BEGIN
IF v_num1 > v_num2 THEN
DBMS_OUTPUT.PUT_LINE ('IF part of the outer IF');
v_total := v_num1 - v_num2;
ELSE
DBMS_OUTPUT.PUT_LINE ('ELSE part of the outer IF');
v_total := v_num1 + v_num2;

IF v_total < 0 THEN
DBMS_OUTPUT.PUT_LINE ('Inner IF');
v_total := v_total * (-1);
END IF;

END IF;
DBMS_OUTPUT.PUT_LINE ('v_total = '||v_total);
END;

The IF-THEN-ELSE statement is called an outer IF statement because it encompasses the IF-THEN statement (shown in bold letters). The IF-THEN statement is called an inner IF statement because it is enclosed by the body of the IF-THEN-ELSE statement.


Assume that the value for v_num1 and v_num2 are -4 and 3 respectively. First, the condition



v_num1 > v_num2

of the outer IF statement is evaluated. Since -4 is not greater than 3, the ELSE part of the outer IF statement is executed. As a result, the message



ELSE part of the outer IF

is displayed, and the value of v_total is calculated. Next, the condition



v_total < 0

of the inner IF statement is evaluated. Since that value of v_total is equal -l, the condition yields TRUE, and message



Inner IF

is displayed. Next, the value of v_total is calculated again. This logic is demonstrated by the output produced by the example:



Enter value for sv_num1: -4
old 2: v_num1 NUMBER := &sv_num1;
new 2: v_num1 NUMBER := -4;
Enter value for sv_num2: 3
old 3: v_num2 NUMBER := &sv_num2;
new 3: v_num2 NUMBER := 3;
ELSE part of the outer IF
Inner IF
v_total = 1

PL/SQL procedure successfully completed.


Logical Operators


So far in this chapter, you have seen examples of different IF statements. All of these examples used test operators, such as >, <, and =, to test a condition. Logical operators can be used to evaluate a condition, as well. In addition, they allow a programmer to combine multiple conditions into a single condition if there is such a need.





FOR EXAMPLE



DECLARE
v_letter CHAR(1) := '&sv_letter';
BEGIN
IF (v_letter >= 'A' AND v_letter <= 'Z') OR
(v_letter >= 'a' AND v_letter <= 'z')
THEN
DBMS_OUTPUT.PUT_LINE ('This is a letter');
ELSE
DBMS_OUTPUT.PUT_LINE ('This is not a letter');

IF v_letter BETWEEN '0' and '9' THEN
DBMS_OUTPUT.PUT_LINE ('This is a number');
ELSE
DBMS_OUTPUT.PUT_LINE ('This is not a number');
END IF;

END IF;
END;

In this example, the condition



(v_letter >= 'A' AND v_letter <= 'Z') OR
(v_letter >= 'a' AND v_letter <= 'z')

uses logical operators AND and OR. There are two conditions



(v_letter >= 'A' AND v_letter <= 'Z')

and



(v_letter >= 'a' AND v_letter <= 'z')

combined into one with the help of the OR operator. It is also important for you to realize the purpose of the parentheses. In this example, they are used to improve readability only, because the operator AND takes precedence over the operator OR.


When the symbol "?" is entered at runtime, this example produces the following output:



Enter value for sv_letter: ?
old 2: v_letter CHAR(1) := '&sv_letter';
new 2: v_letter CHAR(1) := '?';
This is not a letter
This is not a number

PL/SQL procedure successfully completed.


Lab 5.3 Exercises



5.3.1 Use Nested IF Statements

In this exercise, you will use nested IF statements. This script will convert the value of a temperature from one system to another. If the temperature is supplied in Fahrenheit, it will be converted to Celsius, and vice versa.


Create the following PL/SQL script:



-- ch05_4a.sql, version 1.0
SET SERVEROUTPUT ON
DECLARE
v_temp_in NUMBER := &sv_temp_in;
v_scale_in CHAR := '&sv_scale_in';
v_temp_out NUMBER;
v_scale_out CHAR;
BEGIN
IF v_scale_in != 'C' AND v_scale_in != 'F' THEN
DBMS_OUTPUT.PUT_LINE ('This is not a valid scale');
ELSE
IF v_scale_in = 'C' THEN
v_temp_out := ( (9 * v_temp_in) / 5 ) + 32;
v_scale_out := 'F';
ELSE
v_temp_out := ( (v_temp_in 32) * 5 ) / 9;
v_scale_out := 'C';
END IF;
DBMS_OUTPUT.PUT_LINE ('New scale is: '||
v_scale_out);
DBMS_OUTPUT.PUT_LINE ('New temperature is: '||
v_temp_out);
END IF;
END;

Execute the script, and then answer the following questions:


a)

What output is printed on the screen if the value of 100 is entered for the temperature, and the letter "C" is entered for the scale?

b)

Try to run this script without providing a value for the temperature. What message will be displayed on the screen? Why?

c)

Try to run this script providing an invalid letter for the temperature scale, for example, letter "V." What message will be displayed on the screen? Why?

d)

Rewrite this script so that if an invalid letter is entered for the scale, v_temp_out is initialized to zero and v_scale_out is initialized to C.




Lab 5.3 Exercise Answers


This section gives you some suggested answers to the questions in Lab 5.3, with discussion related to how those answers resulted. The most important thing to realize is whether your answer works. You should figure out the implications of the answers here and what the effects are from any different answers you may come up with.



5.3.1 Answers

a)

What output is printed on the screen if the value of 100 is entered for the temperature, and the letter "C" is entered for the scale?

A1:

Answer: Your output should look like the following:


Enter value for sv_temp_in: 100
old 2: v_temp_in NUMBER := &sv_temp_in;
new 2: v_temp_in NUMBER := 100;
Enter value for sv_scale_in: C
old 3: v_scale_in CHAR := '&sv_scale_in';
new 3: v_scale_in CHAR := 'C';
New scale is: F
New temperature is: 212

PL/SQL procedure successfully completed.

Once the values for v_temp_in and v_scale_in have been entered, the condition


v_scale_in != 'C' AND v_scale_in != 'F'

of the outer IF statement evaluates to FALSE, and control is passed to the ELSE part of the outer IF statement. Next, the condition


v_scale_in = 'C'

of the inner IF statement evaluates to TRUE, and the values of the variables v_temp_out and v_scale_out are calculated. Control is then passed back to the outer IF statement, and the new value for the temperature and the scale are displayed on the screen.

b)

Try to run this script without providing a value for the temperature. What message will be displayed on the screen? Why?

A2:

Answer: If the value for the temperature is not entered, the script will not compile at all.

The compiler will try to assign a value to v_temp_in with the help of the substitution variable. Because the value for v_temp_in has not been entered, the assignment statement will fail, and the following error message will be displayed.


Enter value for sv_temp_in:
old 2: v_temp_in NUMBER := &sv_temp_in;
new 2: v_temp_in NUMBER := ;
Enter value for sv_scale_in: C
old 3: v_scale_in CHAR := '&sv_scale_in';
new 3: v_scale_in CHAR := 'C';
v_temp_in NUMBER := ;
*
ERROR at line 2:
ORA-06550: line 2, column 27:
PLS-00103: Encountered the symbol ";" when expecting one of
the following:
( - + mod not null <an identifier>
<a double-quoted delimited-identifier> <a bind variable> avg
count current exists max min prior sql stddev sum variance
cast <a string literal with character set specification>
<a number> <a single-quoted SQL string>
The symbol "null" was substituted for ";" to continue.

You have probably noticed that even though the mistake seems small and insignificant, the error message is fairly long and confusing.

c)

Try to run this script providing an invalid letter for the temperature scale, for example, letter "V." What message will be displayed on the screen? Why?

A3:

Answer: If an invalid letter is entered for the scale, the message "This is not a valid scale" will be displayed on the screen.

The condition of the outer IF statement will evaluate to TRUE. As a result, the inner IF statement will not be executed at all, and the message "This is not a valid scale" will be displayed on the screen.

Assume that letter "V" was typed by mistake. This example will produce the following output:


Enter value for sv_temp_in: 45
old 2: v_temp_in NUMBER := &sv_temp_in;
new 2: v_temp_in NUMBER := 45;
Enter value for sv_scale_in: V
old 3: v_scale_in CHAR := '&sv_scale_in';
new 3: v_scale_in CHAR := 'V';
This is not a valid scale

PL/SQL procedure successfully completed.
d)

Rewrite this script so that if an invalid letter is entered for the scale, v_temp_out is initialized to zero and v_scale_out is initialized to C.

A4:

Answer: Your script should look similar to the following script. Changes are shown in bold letters. Notice that the two last DBMS_OUTPUT.PUT_LINE statements have been moved from the body of the outer IF statement.


-- ch05_4b.sql, version 2.0
DECLARE
v_temp_in NUMBER := &sv_temp_in;
v_scale_in CHAR := '&sv_scale_in';
v_temp_out NUMBER;
v_scale_out CHAR;
BEGIN
IF v_scale_in != 'C' AND v_scale_in != 'F' THEN
DBMS_OUTPUT.PUT_LINE ('This is not a valid scale');
v_temp_out := 0;
v_scale_out := 'C';
ELSE
IF v_scale_in = 'C' THEN
v_temp_out := ( (9 * v_temp_in) / 5 ) + 32;
v_scale_out := 'F';
ELSE
v_temp_out := ( (v_temp_in - 32) * 5 ) / 9;
v_scale_out := 'C';
END IF;
END IF;
DBMS_OUTPUT.PUT_LINE ('New scale is: '||v_scale_out);
DBMS_OUTPUT.PUT_LINE ('New temperature is: '||
v_temp_out);
END;

The preceding script produces the following output:


Enter value for sv_temp_in: 100
old 2: v_temp_in NUMBER := &sv_temp_in;
new 2: v_temp_in NUMBER := 100;
Enter value for sv_scale_in: V
old 3: v_scale_in CHAR := '&sv_scale_in';
new 3: v_scale_in CHAR := 'V';
This is not a valid scale.
New scale is: C
New temperature is: 0

PL/SQL procedure successfully completed.





Lab 5.3 Self-Review Questions


In order to test your progress, you should be able to answer the following questions.


Answers appear in Appendix A, Section 5.3.


1)

What types of IF statements can be nested one inside another?


  1. _____ IF-THEN statement can only be nested inside ELSIF statement.

  2. _____ IF-THEN-ELSE statement cannot be nested at all.

  3. _____ Any IF statement can be nested inside another IF statement.

2)

How many IF statements can be nested one inside another?


  1. _____ One

  2. _____ Two

  3. _____ Any number

3)

Only a single logical operator can be used with a condition of an IF statement.


  1. _____ True

  2. _____ False

4)

When using nested IF statements, their conditions do not need to be mutually exclusive.


  1. _____ True

  2. _____ False

5)

When the condition of the outer IF statement evaluates to FALSE, which of the following happens?


  1. _____ Control is transferred to the inner IF statement.

  2. _____ The error message is generated.

  3. _____ Control is transferred to the first executable statement after the outer END IF statement.




    Team-Fly
     

     
    Top
     


    Section 9.3. Using PEAR










    9.3. Using PEAR







    PEAR is a framework and distribution system for reusable PHP components. Actually, PEAR is a collection of add-on functionality for PHP development. There are many modules available to handle everything from session management to shopping cart functionality. Modules that are currently available are listed in Table 9-1.


    Table 9-1. PEAR modules

    Authentication

    HTML

    Processing

    Benchmarking

    HTTP

    Science

    Caching

    Images

    Semantic Web

    Configuration

    Internationalization

    Streams

    Console

    Logging

    Structures

    Database

    Mail

    System

    Date/Time

    Math

    Test

    Encryption

    Networking

    Tools & Utilities

    Event

    Numbers

    Validate

    File Formats

    Payment

    Web Services

    File System

    PEAR

    XML

    GTK Components

    PHP

     



    Our list is not complete. Visit http://pear.php.net to find out all of the modules that are available for download.



    9.3.1. Installing


    PEAR uses a Package Manager to manage which PEAR features you install. Whether you need to install the Package Manager depends on which version of PHP you installed. If you're running PHP 4.3.0 or newer, it's already installed. If you're running PHP 5.0, PEAR has been split out into a separate package. The DB package that you're interested in is also installed by default with the Package Manager. So if you have the Package Manger, you're all set.



    9.3.1.1. Unix




    You can install the Package Manager on a Unix system by executing the following from the shell (command-line) prompt:



    lynx -source http://go-pear.org/ | php



    This takes the output of the go-pear.org site (which is actually the source PHP code) to install PEAR and passes it along to the php command for execution.




    9.3.1.2. Windows





    The PHP 5 installation includes the PEAR installation script as C:\php\go-pear.bat. In case you didn't install all the files in Chapter 2, go ahead and extract all the PHP files to C:/php from the command prompt, and execute the .bat file. Figure 9-5 shows the initial screen after executing the PEAR installer.



    Figure 9-5. The go-pear.bat install script




    You'll be asked a set of questions about paths. You can accept the defaults for all of them.



    The php.exe file must be in your path. Verify by typing php.exe from a command prompt. If it is not found, you'll need to add it to your PATH variable. To access your system path, navigate to Start Control Panel System Environment and add an entry to the end of the path with C:\php.




    The PEAR installer creates a file called C:\php\PEAR_ENV.reg. You need to double-click to set up the PEAR paths in the registry. This file is contingent on which PEAR version you installed. When the dialog box appears to verify your information, you will add this to the registry and click OK.


    You may have to edit the php.ini file after running this .bat file to add the PEAR directory to the include path. Line 447 of php.ini now looks like this:



    include_path = ".;c:\php\includes;c:\php\PEAR"



    Apache must be restarted before the DB package can be used.




    9.3.1.3. Hosted ISP

    Most ISPs have PEAR DB installed. Ask your ISP to install it if they haven't already. You can tell if PEAR DB has been installed by trying the PHP code in Example 9-7 to see whether the require_once ('DB.php'); line causes an error when the script is executed.





    9.3.2. Adding Additional Packages




    Once that's complete, you can access the PEAR Package Manger by entering pear at the command prompt. Adding new modules is as easy as executing pear packagename. You won't need to do anything, since the DB package was installed along with the install by default.


    However, if you're running Windows XP Home, you'll need to take these steps to install the PEAR DB:



    C:\>cd c:\php
    C:\>pear install DB
    C:\>pear list



    To find out what versions of PEAR packages
    are installed, execute pear list. That returns a listing such as the one shown in Figure 9-6.



    Figure 9-6. A listing of installed PEAR packages and versions




    Once you've got PEAR installed, you're ready to try it out.




    9.3.3. Rewriting the Books Example with PEAR


    When using the PEAR DB package, you follow the same steps. However, the function syntax is slightly different. We'll go line by line and explain the differences as they appear in Example 9-7.


    Example 9-7. Displaying the books table with PEAR DB




    1 <?php
    2
    3 include('db_login.php');
    4 require_once('DB.php');
    5
    6 $connection = DB::connect("mysql://$db_username:$db_password@$db_host/$db_database");
    7
    8 if (DB::isError($connection)){
    9 die("Could not connect to the database: <br />".DB::errorMessage($connection));
    10 }
    11
    12 $query = "SELECT * FROM `books` NATURAL JOIN `authors`";
    13 $result = $connection->query($query);
    14
    15 if (DB::isError($result)){
    16 die("Could not query the database:<br />".$query." ".DB::errorMessage($result));
    17 }
    18
    19 echo('<table border="1">');
    20 echo '<tr><th>Title</th><th>Author</th><th>Pages</th></tr>';
    21
    22 while ($result_row = $result->fetchRow()) {
    23 echo "<tr><td>";
    24 echo $result_row[1] . '</td><td>';
    25 echo $result_row[4] . '</td><td>';
    26 echo $result_row[2] . '</td></tr>';
    27 }
    28
    29 echo("</table>");
    30 $connection->disconnect();
    31
    32 ?>



    Example 9-7 displays the screen shown in Figure 9-7.



    Figure 9-7. Switching to the PEAR DB functions didn't change the output




    Notice that Figure 9-7 is identical to the output in Figure 9-4.


    Line 3 includes your database login information and remains unchanged:



    include('db_login.php');




    Line 4 has a new require statement:



    require_once( "DB.php" );



    This requires the file DB.php, which provides the PEAR DB functions. The require_once function errors out if the DB.php file is not found. It also will not include the file if it has been incorporated already. And, this would cause an error.



    The file DB.php is found in the /pear subdirectory of the PHP distribution. The PEAR install should have added that directory to the include_path in the php.ini file. If this file is not found, verify that PEAR DB is installed and that the paths are set up correctly.





    9.3.3.1. Creating a connect instance



    The DB.php file defines a class of type DB. Refer to Chapter 5 for more information on working with classes and objects. We'll principally be calling the methods in the class. The DB class has a connect method, which we'll use instead of our old connect function mysql_connect. The double colons (::) indicate that we're calling that function from the class in line 4:



    connection = DB::connect("mysql://$db_username:$db_password@$db_host/$db_database");



    When you call the connect function, it creates a new database connection that is stored in the variable $connection. The connect function attempts to connect to the database based on the connect string you passed to it.




    9.3.3.2. Connect string

    The connect string uses this new format to represent the login information that you already supplied in separate fields:



    dbtype://username:password@host/database



    This format may look familiar to you, as it's very similar to the connect string for a Windows share. The first part of the string is what really sets the PEAR functions apart from the plain PHP. The phptype field specifies the type of database to connect. Supported databases include ibase, msql, mssql, mysql, oci8, odbc, pgsql, and sybase. All that's required for your PHP page to work with a different type of database is changing the phptype!


    The username, password, host, and database should be familiar from the basic PHP connect. Only the type of connection is required. However, you'll usually want to specify all fields.


    After the values from dblogin.php are included, the connect string looks like the following:



    "mysql://test:test@localhost/test"



    If the connect method on line 6 was successful, a $DB object is created. It contains the methods to access the database as well as all of the information about the state of that database connection.




    9.3.3.3. Querying







    One of the methods it contains is called query. The query method works just like PHP's query function in that it takes a SQL statement. The difference is the hyphen and greater-than syntax (->) is used to call it from the object. It also returns the results as another object instead of a result set.



    $query = "SELECT * FROM `books`"
    $result = $connection->query($query);



    Based on the SQL query, this code calls the query function from the connection object and returns a result object named $result.




    9.3.3.4. Fetching

    Line 22 uses the result object to call the fetchRow method. It returns the rows one at a time, similar to mysql_fetch_row.



    while ($result_row = $result->fetchRow()) {
    echo 'Title: '.$result_row[1] . '<br />';
    echo 'Author: '.$result_row[4] . '<br /> ';
    echo 'Pages: '.$result_row[2] . '<br /><br />';
    }



    You use another while loop to go through each row from fetchRow until it returns FALSE. The code in the loop hasn't changed from the non-PEAR
    example.




    9.3.3.5. Closing

    In line 30, you're finished with the database connection, so you close it using the object method disconnect:



    $connection->disconnect();





    9.3.3.6. PEAR error reporting



    The function DB::isError will check to see whether the result that's been returned to you is an error or not. If it is an error, you can use DB::errorMessage to return a text description of the error that was generated. You need to pass DB::errorMessage the return value from your function as an argument.


    Here you rewrite the PEAR code to use error checking:



    <?php
    if ( DB::isError( $demoResult = $db->query( $sql)))
    {
    echo DB::errorMessage($demoResult);
    } else {
    while ($demoRow = $demoResult->fetchRow()) {
    echo $demoRow[2] . '<br />';
    }
    }
    ?>



    Now that you have a good handle on connecting to the database and the various functions of PEAR, we're going to talk about forms. Forms provide a way to send substantial data from the user to the server where it can be processed.














    Section 10.2. Subqueries







    10.2. Subqueries

    A subquery is a SELECT statement nested within another SQL statement. This feature became
    available as of version 4.1 of MySQL. Although the same results can be
    accomplished by using the JOIN clause or
    UNION, depending on the situation, subqueries are a
    cleaner approach that is sometimes easier to read. They make a complex
    query more modular, which makes it easier to create and to troubleshoot.
    Here is a simple example of a subquery:

    SELECT *
    FROM
    (SELECT col1, col2
    FROM table1
    WHERE col_id = 1000) AS derived1
    ORDER BY col2;


    In this example, the subquery or inner query
    is a SELECT statement specifying two column names. The other query is called the main or
    outer query. It doesn't have to be a
    SELECT. It can be an INSERT, a
    DELETE, a DO, an
    UPDATE, or even a SET statement. The
    outer query generally can't select data or modify data from the same table
    as an inner query, but this doesn't apply if the subquery is part of a
    FROM clause. A subquery can return a value (a scalar),
    a field, multiple fields containing values, or a full results set that
    serves as a derived table.

    You can encounter performance problems with subqueries if they are
    not well constructed. One problem occurs when a subquery is placed within
    an IN⁠(⁠ ⁠ ⁠) clause as part of a
    WHERE clause. It's generally better to use the
    = operator for each value, along with
    AND for each parameter/value pair.

    When you see a performance problem with a subquery, try
    reconstructing the SQL statement with JOIN and compare
    the differences using the BENCHMARK⁠(⁠ ⁠ ⁠) function.
    If the performance is better without a subquery, don't give up on
    subqueries. Only in some situations is performance poorer. For those
    situations where there is a performance drain, MySQL AB is working on
    improving MySQL subqueries. So performance problems you experience now may
    be resolved in future versions. You may just need to upgrade to the
    current release or watch for improvements in future releases.

    10.2.1. Single Field Subqueries

    The most basic subquery is one that returns a scalar or single
    value. This type of subquery is particularly useful in a WHERE clause in conjunction with an
    = operator, or in other instances where a single
    value from an expression is permitted.

    As an example of this situation, suppose that at our fictitious
    college one of the music teachers, Sonia Oram, has called us saying that
    she wants a list of students for one of her classes so that she can call
    them to invite them to a concert. She wants the names and telephone
    numbers for only the students in her first period Monday morning
    class.

    The way most databases store this data, the course number would be
    a unique key and would make it easy to retrieve the other data without a
    subquery. But Sonia doesn't know the course number, so we enter an SQL
    statement like this:

    SELECT CONCAT(name_first, ' ', name_last) AS student,
    phone_home, phone_dorm
    FROM students
    JOIN course_rosters USING (student_id)
    WHERE course_id =
    (SELECT course_id
    FROM course_schedule
    JOIN teachers USING (teacher_id)
    WHERE semester_code = '2007AU'
    AND class_time = 'monday_01'
    AND name_first = 'Sonia'
    AND name_last = 'Oram');


    Notice in the subquery that we're joining the
    course_schedule table with
    teachers so we can give the teacher's first and last
    name in the WHERE clause of the subquery. We're also
    indicating in the WHERE clause a specific semester
    (Autumn 2007) and time slot (Monday, first period). The results of these
    specifics should be one course identification number because a teacher
    won't teach more than one class during a particular class period. That
    single course number will be used by the WHERE clause
    of the main query to return the list of students on the class roster for
    the course, along with their telephone numbers.

    If by chance more than one value is returned by the subquery in
    the previous example, MySQL will
    return an error:

    ERROR 1242 (ER_SUBSELECT_NO_1_ROW)
    SQLSTATE = 21000
    Message = "Subquery returns more than 1 row"


    Despite our supposition, it is possible that a teacher might teach
    more than one class at a time: perhaps the teacher is teaching one
    course in violin and another in viola, but each class had so few
    students that the department head put them together. In such a
    situation, the teacher would want the data for both course numbers. To
    use multiple fields derived from a subquery in a
    WHERE clause like this, we would have to use
    something other than the = operator, such as
    IN. For this kind of situation, see the next section
    on Section 10.2.2."

    10.2.2. Multiple Fields Subqueries

    In the previous section, we discussed instances where one scalar
    value was obtained from a subquery in a WHERE clause.
    However, there are times when you may want to match multiple values. For
    those situations you will need to use the subquery in conjunction with
    an operator or a clause: ALL, ANY,
    EXISTS, IN, or
    SOME.

    As an example of a multiple fields subquery—and specifically of a
    subquery using IN (or using ANY or
    SOME)—let's adapt the example from the previous
    section to a situation where the teacher wants the contact information
    for students in all of her classes. To do this, we can enter the
    following SQL statement:

    SELECT CONCAT(name_first, ' ', name_last) AS student,
    phone_home, phone_dorm
    FROM students
    JOIN course_rosters USING (student_id)
    WHERE course_id IN
    (SELECT course_id
    FROM course_schedule
    JOIN teachers USING (teacher_id)
    WHERE semester_code = '2007AU'
    AND name_first = 'Sonia'
    AND name_last = 'Oram');


    In this example, notice that the subquery is contained within the
    parentheses of the IN clause. Subqueries are executed
    first, so the results will be available before the
    WHERE clause is executed. Although a comma-separated
    list isn't returned, MySQL still accepts the results so that they may be
    used by the outer query. The criteria of the WHERE
    clause here does not specify a specific time slot as the earlier example
    did, so multiple values are much more likely to be returned.

    Instead of IN, you can use ANY or SOME
    to obtain the same results by the same methods. (ANY
    and SOME are synonymous.) These two keywords must be
    preceded by a comparison operator (e.g., =,
    <, >). For example, we could
    replace the IN in the SQL previous statement with
    = ANY or with = SOME and the same
    results will be returned. IN can be preceded with
    NOT for negative comparisons: NOT
    IN(...)
    . This is the same as != ANY (...)
    and != SOME (...).

    Let's look at another subquery returning multiple values but using
    the ALL operator. The ALL operator must be preceded by a
    comparison operator (e.g., =,
    <, >). As an example of this
    usage, suppose one of the piano teachers provides weekend seminars for
    students. Suppose also that he heard a few students are enrolled in all
    of the seminars he has scheduled for the semester and he wants a list of
    their names and telephone numbers in advance. We should be able to get
    that data by entering an SQL statement like the following (though
    currently it doesn't work, for reasons to be explained shortly):

    SELECT DISTINCT student_id, 
    CONCAT(name_first, ' ', name_last) AS student
    FROM students
    JOIN seminar_rosters USING (student_id)
    WHERE seminar_id = ALL
    (SELECT seminar_id
    FROM seminar_schedule
    JOIN teachers ON (instructor_id = teacher_id)
    WHERE semester_code = '2007AU'
    AND name_first = 'Sam'
    AND name_last = 'Oram');


    In this example, a couple of the tables have different column
    names for the ID we want, and we have to join one of them with
    ON instead of USING, but that has
    nothing to do with the subquery. What's significant is that this
    subquery returns a list of seminar identification numbers and is used in
    the WHERE clause of the main query with =
    ALL
    . Unfortunately, although this statement is constructed
    correctly, it doesn't work with MySQL at the time of this writing and
    just returns an empty set. However, it should work in future releases of
    MySQL, so I've included it for future reference. For now, we would have
    to reorganize the SQL statement like so:

    SELECT student_id, student
    FROM
    (SELECT student_id, COUNT(*)
    AS nbr_seminars_registered,
    CONCAT(name_first, ' ', name_last)
    AS student
    FROM students
    JOIN seminar_rosters USING (student_id)
    WHERE seminar_id IN
    (SELECT seminar_id
    FROM seminar_schedule
    JOIN teachers
    ON (instructor_id = teacher_id)
    WHERE semester_code = '2007AU'
    AND name_first = 'Sam'
    AND name_last = 'Oram')
    GROUP BY student_id) AS students_registered
    WHERE nbr_seminars_registered =
    (SELECT COUNT(*) AS nbr_seminars
    FROM seminar_schedule
    JOIN teachers
    ON (instructor_id = teacher_id)
    WHERE semester_code = '2007AU'
    AND name_first = 'Sam'
    AND name_last = 'Oram');



    This is much more involved, but it does work with the latest
    release of MySQL.

    The first subquery is used to get the student's name. This
    subquery's WHERE clause uses another subquery to
    retrieve the list of seminars taught by the professor for the semester,
    to determine the results set from which the main query will draw its
    ultimate data. The third subquery
    counts the number of seminars that the same professor is teaching for
    the semester. This single value is used with the
    WHERE clause of the main query. In essence, we're
    determining the number of seminars the professor is teaching and which
    students are registered for all of them.

    The last possible method for using multiple fields in a subquery
    uses EXISTS. With
    EXISTS, in order for it to return meaningful or
    desired results, you need to stipulate in the WHERE
    clauses of the subquery a point in which it is joined to the outer
    query. Using the example from the previous section involving the teacher
    Sonia Oram, let's suppose that we want to retrieve a list of courses
    that she teaches:

    SELECT DISTINCT course_id, course_name 
    FROM courses
    WHERE EXISTS
    (SELECT course_id
    FROM course_schedule
    JOIN teachers USING (teacher_id)
    WHERE semester_code = '2007AU'
    AND name_first = 'Sonia'
    AND name_last = 'Oram'
    AND courses.course_id = course_schedule.course_id);


    As you can see here, we've added EXISTS to the
    WHERE clause with the subquery in parentheses,
    similar to using IN. The significant difference is
    that we added courses.course_id =
    course_schedule.course_id
    to the end. Without it, a list of
    all courses would be returned regardless of the criteria of the
    WHERE clause in the subquery. Incidentally, if we
    specified NOT EXISTS instead, we would get all
    courses except for the ones taught by the teacher
    given.

    10.2.3. Results Set Subqueries

    A subquery can be used to generate a results set, which is a table
    from which an outer query can select data. That is, a subquery can be
    used in a FROM clause as if it were another table in
    a database. It is a derived table. Along these
    lines, each derived table must be named. This is done with
    AS following the parentheses containing the subquery.
    A subquery contained in a FROM clause generally
    cannot be a correlated subquery—that is, it cannot reference the same
    table as the outer query. The exception is if it's constructed with a
    JOIN.

    In the following example, let's consider the subquery separately
    as though it were a plain query and not a subquery. It will generate a
    results set containing the student's ID and the student's average exam
    score for a specific course taught during a specific semester. The query
    uses AVG⁠(⁠ ⁠ ⁠), which requires a
    GROUP BY clause. The problem with GROUP BY is that it will order
    data only by the columns by which it's given to group data. In this
    case, it will order the data by student_id and not
    list the results by any other, more useful column. If we want to order
    the data so that the highest student average is first, descending in
    order to the lowest student average, we have to turn our query into a
    subquery and have the outer query re-sort the results:

    SELECT CONCAT(name_first, ' ', name_last) AS student,
    student_id, avg_grade
    FROM students
    JOIN
    (SELECT student_id,
    AVG(exam_grade) AS avg_grade
    FROM exams
    WHERE semester_code = '2007AU'
    AND course_id = 1489
    GROUP BY student_id) AS grade_averages
    USING(student_id)
    ORDER BY avg_grade DESC;


    The results set (the derived table generated by the subquery in
    the FROM clause) is named
    grade_averages. Notice that although the column
    student_id exists in the derived table, in the table
    from which it gets its data (i.e., exams) and in the
    primary table used in the main query (i.e.,
    students), there is no ambiguity. No error is
    generated. However, if we wanted to specify that the data be taken from
    the derived table, we could put
    grade_averages.student_id in the
    SELECT of the outer query.

    This subquery is a correlated subquery, which is generally not permitted in a
    FROM clause. It's allowed in this example because we
    are using a JOIN to join the results set to the table
    referenced in the outer query.








    Section 2.3. Use Case Overview Diagrams










    2.3. Use Case Overview Diagrams



    When you are trying to understand a system, it is sometimes useful to get a glimpse of the context within which it sits. For this purpose, UML provides the Use Case Overview diagram. Use Case Overview diagrams give you an opportunity to paint a broad picture of your system's context or domain (see Figure 2-16 for an example).



    Figure 2-16. The CMS's context as shown on a Use Case Overview diagram



    Unfortunately, Use Case Overviews are badly named as they don't usually contain any use cases. The use cases are not shown because the overview is designed to provide a context to your system; the system's internalscaptured by use casesare not normally visible.


    Use Case Overviews are a useful place to show any extra snippets of information when understanding your system's place within the world. Those snippets often include relationships and communication lines between actors. These contextual pieces of information do not usually contain a great deal of detail, they are more a placeholder and starting point to for the rest of your model's detail.












    Section 9.8. Pseudo File Systems










    9.8. Pseudo File Systems


    A number of file systems fall under the category of Pseudo File Systems in the kernel-configuration menu. Together they provide a range of facilities useful in a wide range of applications. For additional information, especially on the proc file system, spend an afternoon poking around this useful system facility. Where appropriate, references to additional reading material can be found in Section 9.11.1, at the end of this chapter.



    9.8.1. Proc File System


    The /proc file system took its name from its original purpose, an interface that allows the kernel to communicate information about each running process on a Linux system. Over the course of time, it has grown and matured to provide much more than process information. We introduce the highlights here; a complete tour of the /proc file system is left as an exercise for the reader.


    The /proc file system has become a virtual necessity for all but the simplest of Linux systems, even embedded ones. Many user-level functions rely on the contents of the /proc file system to do their job. For example, the mount command, issued without any parameters, lists all the currently active mount points on a running system, from the information delivered by /proc/mounts. If the /proc file system is not available, the mount command silently returns. Listing 9-14 illustrates this on the ADI Engineering Coyote board.


    Listing 9-14. Mount Dependency on /proc





    # mount
    rootfs on / type rootfs (rw)
    /dev/root on / type nfs
    (rw,v2,rsize=4096,wsize=4096,hard,udp,nolock,addr=192.168.1.19)
    tmpfs on /dev/shm type tmpfs (rw)
    /proc on /proc type proc (rw,nodiratime)

    < Now unmount proc and try again ...>

    # umount /proc
    # mount
    #




    Notice in Listing 9-14 that /proc itself is listed as a mounted file system, as type proc mounted on /proc. This is not doublespeak; your system must have a mount point called /proc at the top-level directory tree as a destination for the /proc file system to be mounted on.[6] To mount the /proc file system, use the mount command as with any other file system:

    [6] It is certainly possible to mount /proc anywhere you like on your file system, but all the utilities (including mount) that require proc expect to find it mounted on /proc.


    $ mount -t proc /proc /proc


    The general form of the mount command, from the man page, is mount [-t fstype] something somewhere. In the previous invocation, we could have substituted none for /proc, as follows:


    $ mount -t proc none /proc


    This looks somewhat less like doublespeak. The something parameter is not strictly necessary because /proc is a pseudo file system and not a real physical device. However, specifying /proc as in the earlier example helps remind us that we are mounting the /proc file system on the /proc directory (or, more appropriately, on the /proc mount point).


    Of course, by this time, it might be obvious that to get /proc file system functionality, it must be enabled in the kernel configuration. This kernel-configuration option can be found in the File Systems submenu under the category Pseudo File Systems.


    Each user process running in the kernel is represented by an entry in the /proc file system. For example, the init process introduced in Chapter 6 is always assigned the process id (PID) of 1. Processes in the /proc file system are represented by a directory that is given the PID number as its name. For example, the init process with a PID of 1 would be represented by a /proc/1 directory. Listing 9-15 shows the contents of this directory on our embedded Coyote board.


    Listing 9-15. init Process /proc EnTRies





    # ls -l /proc/1
    total 0
    -r-------- 1 root root 0 Jan 1 00:25 auxv
    -r--r--r-- 1 root root 0 Jan 1 00:21 cmdline
    lrwxrwxrwx 1 root root 0 Jan 1 00:25 cwd -> /
    -r-------- 1 root root 0 Jan 1 00:25 environ
    lrwxrwxrwx 1 root root 0 Jan 1 00:25 exe -> /sbin/init
    dr-x------ 2 root root 0 Jan 1 00:25 fd
    -r--r--r-- 1 root root 0 Jan 1 00:25 maps
    -rw------- 1 root root 0 Jan 1 00:25 mem
    -r--r--r-- 1 root root 0 Jan 1 00:25 mounts
    -rw-r--r-- 1 root root 0 Jan 1 00:25 oom_adj
    -r--r--r-- 1 root root 0 Jan 1 00:25 oom_score
    lrwxrwxrwx 1 root root 0 Jan 1 00:25 root -> /
    -r--r--r-- 1 root root 0 Jan 1 00:21 stat
    -r--r--r-- 1 root root 0 Jan 1 00:25 statm
    -r--r--r-- 1 root root 0 Jan 1 00:21 status
    dr-xr-xr-x 3 root root 0 Jan 1 00:25 task
    -r--r--r-- 1 root root 0 Jan 1 00:25 wchan




    These entries, which are present in the /proc file system for each running process, contain much useful information, especially for analyzing and debugging a process. For example, the cmdline entry contains the complete command line used to invoke the process, including any arguments. The cwd and root directories contain the processes' view of the current working directory and the current root directory.


    One of the more useful entries for system debugging is the maps entry. This contains a list of each virtual memory segment assigned to the program, along with attributes about each. Listing 9-16 is the output from /proc/1/maps in our example of the init process.


    Listing 9-16. init Process Memory Segments from /proc





    # cat /proc/1/maps
    00008000-0000f000 r-xp 00000000 00:0a 9537567 /sbin/init
    00016000-00017000 rw-p 00006000 00:0a 9537567 /sbin/init
    00017000-0001b000 rwxp 00017000 00:00 0
    40000000-40017000 r-xp 00000000 00:0a 9537183 /lib/ld-2.3.2.so
    40017000-40018000 rw-p 40017000 00:00 0
    4001f000-40020000 rw-p 00017000 00:0a 9537183 /lib/ld-2.3.2.so
    40020000-40141000 r-xp 00000000 00:0a 9537518 /lib/libc-2.3.2.so
    40141000-40148000 ---p 00121000 00:0a 9537518 /lib/libc-2.3.2.so
    40148000-4014d000 rw-p 00120000 00:0a 9537518 /lib/libc-2.3.2.so
    4014d000-4014f000 rw-p 4014d000 00:00 0
    befeb000-bf000000 rwxp befeb000 00:00 0
    #




    The usefulness of this information is readily apparent. You can see the program segments of the init process itself in the first two entries. You can also see the memory segments used by the shared library objects being used by the init process. The format is as follows:


    vmstart-vmend attr  pgoffset  devname inode filename


    Here, vmstart and vmend are the starting and ending virtual memory addresses, respectively; attr indicates memory region attributes, such as read, write, and execute, and tells whether this region is shareable; pgoffset is the page offset of the region (a kernel virtual memory parameter); and devname, displayed as xx:xx, is a kernel representation of the device ID associated with this memory region. The memory regions that are not associated with a file are also not associated with a device, thus the 00:00. The final two entries are the inode and file associated with the given memory region. Of course, if there is no file, there is no inode associated with it, and it displays with a zero. These are usually data segments.


    Other useful entries are listed for each process. The status entry contains useful status information about the running process, including items such as the parent PID, user and group IDs, virtual memory usage stats, signals, and capabilities. More details can be obtained from the references at the end of the chapter.


    Some frequently used /proc enTRies are cpuinfo, meminfo, and version. The cpuinfo enTRy lists attributes that the kernel discovers about the processor(s) running on the system. The meminfo enTRy provides statistics on the total system memory. The version entry mirrors the Linux kernel version string, together with information on what compiler and machine were used to build the kernel.


    Many more useful /proc entries are provided by the kernel; we have only scratched the surface of this useful subsystem. Many utilities have been designed for extracting and reporting information contained with the /proc file system. Two popular examples are top and ps, which every embedded Linux developer should be intimately familiar with. These are introduced in Chapter 13. Other utilities useful for interfacing with the /proc file system include free, pkill, pmap, and uptime. See the procps package for more details.




    9.8.2. sysfs


    Like the /proc file system, sysfs is not representative of an actual physical device. Instead, sysfs models specific kernel objects such as physical devices and provides a way to associate devices with device drivers. Some agents in a typical Linux distribution depend on the information on sysfs.


    We can get some idea of what kinds of objects are exported by looking directly at the directory structure exported by sysfs. Listing 9-17 shows the top-level /sys directory on our Coyote board.


    Listing 9-17. Top-Level /sys Directory Contents





    # dir /sys
    total 0
    drwxr-xr-x 21 root root 0 Jan 1 00:00 block
    drwxr-xr-x 6 root root 0 Jan 1 00:00 bus
    drwxr-xr-x 10 root root 0 Jan 1 00:00 class
    drwxr-xr-x 5 root root 0 Jan 1 00:00 devices
    drwxr-xr-x 2 root root 0 Jan 1 00:00 firmware
    drwxr-xr-x 2 root root 0 Jan 1 00:00 kernel
    drwxr-xr-x 5 root root 0 Jan 1 00:00 module
    drwxr-xr-x 2 root root 0 Jan 1 00:00 power
    #




    As you can see, sysfs provides a subdirectory for each major class of system device, including the system buses. For example, under the block subdirectory, each block device is represented by a subdirectory entry. The same holds true for the other directories at the top level.


    Most of the information stored by sysfs is in a format more suitable for machines than humans to read. For example, to discover the devices on the PCI bus, one could look directly at the /sys/bus/pci subdirectory. On our Coyote board, which has a single PCI device attached (an Ethernet card), the directory looks like this:


    # ls /sys/bus/pci/devices/
    0000:00:0f.0 -> ../../../devices/pci0000:00/0000:00:0f.0


    This entry is actually a symbolic link pointing to another node in the sysfs directory tree. We have formatted the output of ls here to illustrate this, while still fitting in a single line. The name of the symbolic link is the kernel's representation of the PCI bus, and it points to a devices subdirectory called pci0000:00 (the PCI bus representation), which contains a number of subdirectories and files representing attributes of this specific PCI device. As you can see, the data is rather difficult to discover and parse.


    A useful utility exists to browse the sysfs file system directory structure. Called systool, it comes from the sysfsutils package found on sourceforge.net. Here is how systool would display the PCI bus from the previous discussion:


    $ systool -b pci
    Bus = "pci"
    0000:00:0f.0 8086:1229


    Again we see the kernel's representation of the bus and device (0f), but this time the tool displays the vendor ID (8086 = Intel) and device ID (1229 = eepro100 Ethernet card) obtained from the /sys/devices/pci0000:00 branch of /sys where these attributes are kept. Executed with no parameters, systool displays the top-level system hierarchy. Listing 9-18 is an example from our Coyote board.



    Listing 9-18. Output from systool





    $ systool
    Supported sysfs buses:
    i2c
    ide
    pci
    platform
    Supported sysfs classes:
    block
    i2c-adapter
    i2c-dev
    input
    mem
    misc
    net
    pci_bus
    tty
    Supported sysfs devices:
    pci0000:00
    platform
    system




    You can see from this listing the variety of system information available from sysfs. Many utilities use this information to determine the characteristics of system devices or to enforce system policies, such as power management and hot-plug capability.













    2.5 Issuing Queries and Retrieving Results




    I l@ve RuBoard










    2.5 Issuing Queries and Retrieving Results




    2.5.1 Problem



    You
    want your program to send a query to the MySQL server and retrieve
    the result.





    2.5.2 Solution



    Some statements only return a status code, others return a result set
    (a set of rows). Most APIs provide different functions for each type
    of statement; if so, use the function that's
    appropriate for your query.





    2.5.3 Discussion



    This section is the longest of the chapter because there are two
    categories of
    queries you can execute. Some statements
    retrieve information from the database; others make changes to that
    information. These two types of queries are handled differently. In
    addition, some APIs provide several different functions for issuing
    queries, which complicates matters further. Before we get to the
    examples demonstrating how to issue queries from within each API,
    I'll show the table used for examples, then discuss
    the general statement categories and outline a strategy for
    processing them.



    In Chapter 1, we created a table named
    limbs to use for some sample queries. In this
    chapter, we'll use a different table named
    profile. It's based on the idea
    of a "buddy list," that is, the set
    of people we like to keep in touch with while we're
    online. To maintain a profile about each person, we can use the
    following
    table:



    CREATE TABLE profile
    (
    id INT UNSIGNED NOT NULL AUTO_INCREMENT,
    name CHAR(20) NOT NULL,
    birth DATE,
    color ENUM('blue','red','green','brown','black','white'),
    foods SET('lutefisk','burrito','curry','eggroll','fadge','pizza'),
    cats INT,
    PRIMARY KEY (id)
    );


    The profile table reflects that the things that
    are important to us are each buddy's name, age,
    favorite color, favorite foods, and number of cats�obviously
    one of those goofy tables that are used only for examples in a
    book![3] The table includes an id
    column containing unique values so that we can distinguish records
    from each other, even if two buddies have the same name.
    id and name are
    NOT NULL because
    they're each required to have a value. The other
    columns are allowed to be NULL because we might
    not know the value to put into them for any given individual.
    (We'll use NULL to signify
    "unknown.") Notice that although we
    want to keep track of age, there is no age column
    in the table. Instead, there is a birth column of
    DATE type. That's because ages
    change, but birthdays don't. If we recorded age
    values, we'd have to keep updating them. Storing the
    birth date is better because it's stable, and we can
    use it to calculate age at any given moment. (Age calculations are
    discussed in Recipe 5.20.) color
    is an ENUM column; color values can be any one of
    the listed values. foods is a
    SET, which allows the value to be chosen as any
    combination of the individual set members. That way we can record
    multiple favorite foods for any buddy.


    [3] Actually, it's not that goofy.
    The table uses several different data types for its columns, and
    these will come in handy later for illustrating how to solve
    particular kinds of problems that pertain to specific column
    types.



    To create the table, use the profile.sql script
    in the tables directory of the
    recipes distribution. Change location into that
    directory, then run the following command:



    % mysql cookbook < profile.sql


    Another way to create the table is to issue the
    CREATE TABLE statement manually
    from within the mysql program, but I recommend
    that you use the script, because it also loads sample data into the
    table. That way you can experiment with the table, then restore it
    after changing it by running the script again.[4]


    [4] See
    the note at the very end of this chapter on the importance of
    restoring the profile table.



    The initial contents of the profile table loaded
    by the profile.sql script look like this:



    mysql> SELECT * FROM profile;
    +----+---------+------------+-------+-----------------------+------+
    | id | name | birth | color | foods | cats |
    +----+---------+------------+-------+-----------------------+------+
    | 1 | Fred | 1970-04-13 | black | lutefisk,fadge,pizza | 0 |
    | 2 | Mort | 1969-09-30 | white | burrito,curry,eggroll | 3 |
    | 3 | Brit | 1957-12-01 | red | burrito,curry,pizza | 1 |
    | 4 | Carl | 1973-11-02 | red | eggroll,pizza | 4 |
    | 5 | Sean | 1963-07-04 | blue | burrito,curry | 5 |
    | 6 | Alan | 1965-02-14 | red | curry,fadge | 1 |
    | 7 | Mara | 1968-09-17 | green | lutefisk,fadge | 1 |
    | 8 | Shepard | 1975-09-02 | black | curry,pizza | 2 |
    | 9 | Dick | 1952-08-20 | green | lutefisk,fadge | 0 |
    | 10 | Tony | 1960-05-01 | white | burrito,pizza | 0 |
    +----+---------+------------+-------+-----------------------+------+


    Most of the columns in the profile table allow
    NULL values, but none of the rows in the sample
    dataset actually contain NULL yet. This is because
    NULL values complicate query processing a bit and
    I don't want to deal with those complications until
    we get to Recipe 2.8 and Recipe 2.9.





    2.5.4 SQL Statement Categories



    SQL
    statements can be divided into two broad categories:




    • Statements that do not return a result set (that is, a set of rows).
      This statement category includes INSERT,
      DELETE, and UPDATE. As a
      general rule, statements of this type generally change the database
      in some way. There are some exceptions, such as
      USE db_name, which
      changes the current (default) database for your session without
      making any changes to the database itself.


    • Statements that return a result set, such as
      SELECT, SHOW,
      EXPLAIN, and DESCRIBE. I refer
      to such statements generically as SELECT
      statements, but you should understand that category to include any
      statement that returns rows.



    The first step in processing a query is to send it to the MySQL
    server for execution. Some APIs (Perl and Java, for example)
    recognize a distinction between the two categories of statements and
    provide separate calls for executing them. Others (such as PHP and
    Python) do not and have a single call for issuing all statements.
    However, one thing all APIs have in
    common is that you don't use any special character
    to indicate the end of the query. No terminator is necessary because
    the end of the query string implicitly terminates the query. This
    differs from the way you issue queries in the
    mysql program, where you terminate statements
    using a semicolon ( ;) or \g.
    (It also differs from the way I normally show the syntax for SQL
    statements, because I include semicolons to make it clear where
    statements end.)



    After sending the query to the server, the next step is to check
    whether or not it executed successfully. Do not neglect
    this step
    . You'll regret it if you do. If
    a query fails and you proceed on the basis that it succeeded, your
    program won't work. If the query did execute, your
    next step depends on the type of query you issued. If
    it's one that returns no result set,
    there's nothing else to do (unless you want to check
    how many rows were affected by the query). If the query does return a
    result set, you can fetch its rows, then close the result set.




    Don't Shoot Yourself in the Foot: Check for Errors



    Apparently, the principle that you should
    check for errors is not so obvious or widely appreciated as one might
    hope. Many messages posted on MySQL-related mailing lists are
    requests for help with programs that fail for reasons unknown to the
    people that wrote them. In a surprising number of cases, the reason
    these developers are mystified by their programs is that they put in
    no error checking, and thus gave themselves no
    way to know that there was a problem or to find out what it was! You
    cannot help yourself this way. Plan for failure by checking for
    errors so that you can take appropriate action if they occur.




    Now we're ready to see how to issue queries in each
    API. Note that although the scripts check for errors as necessary,
    for brevity they just print a generic message that an error occurred.
    You can display more specific error messages using the techniques
    illustrated in Recipe 2.3.





    2.5.5 Perl



    The
    Perl DBI module provides two basic approaches to query execution,
    depending on whether or not you expect to get back a result set. To
    issue a query such as INSERT or
    UPDATE that returns no result set, use the
    do(
    )
    method. It executes the query and
    returns the number of rows affected by the query, or
    undef if an error occurs. For example, if Fred
    gets a new kitty, the following query can be used to increment his
    cats count by one:



    my $count = $dbh->do ("UPDATE profile SET cats = cats+1 WHERE name = 'Fred'");
    if ($count) # print row count if no error occurred
    {
    $count += 0;
    print "$count rows were updated\n";
    }


    If the query executes successfully but affects no rows, do(
    )
    returns a special value, the string
    "0E0" (that is, the value zero
    in scientific notation). "0E0" can be used for
    testing the execution status of a query because it is true in Boolean
    contexts (unlike undef). For successful queries,
    it can also be used when counting how many rows were affected,
    because it is treated as the number zero in numeric contexts. Of
    course, if you print that value as is, you'll print
    "0E0", which might look kind of weird to people
    who use your program. The preceding example shows one way to make
    sure this doesn't happen: adding zero to the value
    explicitly coerces it to numeric form so that it displays as
    0. You can also use printf with
    a %d format specifier to cause an implicit numeric
    conversion:



    my $count = $dbh->do ("UPDATE profile SET color = color WHERE name = 'Fred'");
    if ($count) # print row count if no error occurred
    {
    printf "%d rows were updated\n", $count;
    }


    If
    RaiseError is
    enabled, your script will terminate automatically if a DBI-related
    error occurs and you don't need to bother checking
    $count to see if do( ) failed:



    my $count = $dbh->do ("UPDATE profile SET color = color WHERE name = 'Fred'");
    printf "%d rows were updated\n", $count;


    To process
    queries such as
    SELECT that do return a result set, use a
    different approach that involves four steps:




    • Specify the query by calling prepare(
      )
      using the database handle.
      prepare( ) returns a statement handle to use with
      all subsequent operations on the query. (If an error occurs, the
      script terminates if RaiseError is enabled;
      otherwise, prepare( ) returns
      undef.)


    • Call execute( ) to execute the query and generate
      the result set.


    • Perform a loop to fetch the rows returned by the query. DBI provides
      several methods you can use in this loop, which
      we'll describe shortly.


    • Release resources associated with the result set by calling
      finish( ).



    The following example illustrates these steps, using
    fetchrow_array(
    )
    as the row-fetching method and assuming
    RaiseError is enabled:



    my $sth = $dbh->prepare ("SELECT id, name, cats FROM profile");
    $sth->execute ( );
    my $count = 0;
    while (my @val = $sth->fetchrow_array ( ))
    {
    print "id: $val[0], name: $val[1], cats: $val[2]\n";
    ++$count;
    }
    $sth->finish ( );
    print "$count rows were returned\n";


    The row-fetching loop just shown is followed by a call to
    finish( ), which closes the result set and tells
    the server that it can free any resources associated with it. You
    don't actually need to call finish(
    )
    if you fetch every row in the set, because DBI notices
    when you've reached the last row and releases the
    set for itself. Thus, the example could have omitted the
    finish( ) call without ill effect.
    It's more important to invoke finish(
    )
    explicitly if you fetch only part of a result set.



    The example illustrates that if you want to know how many rows a
    result set contains, you should count them yourself while
    you're fetching them. Do not use the DBI
    rows( ) method for this purpose; the DBI
    documentation discourages this practice. (The reason is that it is
    not necessarily reliable for SELECT
    statements�not because of some deficiency in DBI, but because
    of differences in the behavior of various database engines.)



    DBI has several functions that can be used to obtain a row at a time
    in a row-fetching loop. The one used in the previous example,
    fetchrow_array(
    )
    , returns an array containing the next
    row, or an empty list when there are no more rows. Elements of the
    array are accessed as $val[0],
    $val[1], ..., and are present in the array in the
    same order they are named in the SELECT statement.
    This function is most useful for queries that explicitly name columns
    to selected. (If you retrieve columns with SELECT
    *, there are no guarantees about the positions of
    columns within the array.)



    fetchrow_arrayref(
    )
    is like fetchrow_array(
    )
    , except that it returns a reference to the array, or
    undef when there are no more rows. Elements of the
    array are accessed as $ref->[0],
    $ref->[1], and so forth. As with
    fetchrow_array( ), the values are present in the
    order named in the query:



    my $sth = $dbh->prepare ("SELECT id, name, cats FROM profile");
    $sth->execute ( );
    my $count = 0;
    while (my $ref = $sth->fetchrow_arrayref ( ))
    {
    print "id: $ref->[0], name: $ref->[1], cats: $ref->[2]\n";
    ++$count;
    }
    print "$count rows were returned\n";


    fetchrow_hashref(
    )
    returns a reference to a hash structure,
    or undef when there are no more rows:



    my $sth = $dbh->prepare ("SELECT id, name, cats FROM profile");
    $sth->execute ( );
    my $count = 0;
    while (my $ref = $sth->fetchrow_hashref ( ))
    {
    print "id: $ref->{id}, name: $ref->{name}, cats: $ref->{cats}\n";
    ++$count;
    }
    print "$count rows were returned\n";


    The elements of the hash are accessed using the names of the columns
    that are selected by the query ($ref->{id},
    $ref->{name}, and so forth).
    fetchrow_hashref( ) is particularly useful for
    SELECT * queries, because you
    can access elements of rows without knowing anything about the order
    in which columns are returned. You just need to know their names. On
    the other hand, it's more expensive to set up a hash
    than an array, so fetchrow_hashref( ) is slower
    than fetchrow_array( ) or
    fetchrow_arrayref( ). It's also
    possible to "lose" row elements if
    they have the same name, because column names must be unique. The
    following query selects two values, but fetchrow_hashref(
    )
    would return a hash structure containing a single element
    named id:



    SELECT id, id FROM profile


    To avoid this problem, you can use
    column
    aliases to ensure that like-named columns have distinct names in the
    result set. The following query retrieves the same columns as the
    previous query, but gives them the distinct names
    id and id2:



    SELECT id, id AS id2 FROM profile


    Admittedly, this query is pretty silly, but if
    you're retrieving columns from multiple tables, you
    may very easily run into the problem of having columns in the result
    set that have the same name. An example where this occurs may be seen
    in Recipe 12.4.



    In addition to the methods for performing the query execution process
    just described, DBI
    provides several high-level retrieval methods that issue a query and
    return the result set in a single operation. These all are database
    handle methods that take care of creating and disposing of the
    statement handle internally before returning the result set. Where
    the methods differ is the form in which they return the result. Some
    return the entire result set, others return a single row or column of
    the set, as summarized in the following table:[5]


    [5] selectrow_arrayref( ) and
    selectall_hashref( ) require DBI 1.15 or newer.
    selectrow_hashref( ) requires DBI 1.20 or newer
    (it was present a few versions before that, but with a different
    behavior than it uses now).



































    Method



    Return value



    selectrow_array( )



    First row of result set as an array



    selectrow_arrayref( )



    First row of result set as a reference to an array



    selectrow_hashref( )



    First row of result set as a reference to a hash



    selectcol_arrayref( )



    First column of result set as a reference to an array



    selectall_arrayref( )



    Entire result set as a reference to an array of array references



    selectall_hashref( )



    Entire result set as a reference to a hash of hash references




    Most of these methods return a reference. The exception is
    selectrow_array(
    )
    , which selects the first row of the
    result set and returns an array or a scalar, depending on how you
    call it. In array context, selectrow_array( )
    returns the entire row as an array (or the empty list if no row was
    selected). This is useful for queries from which you expect to obtain
    only a single row:



    my @val = $dbh->selectrow_array (
    "SELECT name, birth, foods FROM profile WHERE id = 3");


    When selectrow_array( ) is called in array
    context, the return value can be used to determine the size of the
    result set. The column count is the number of elements in the array,
    and the row count is 1 or 0:



    my $ncols = @val;
    my $nrows = ($ncols ? 1 : 0);


    You can also invoke selectrow_array( ) in scalar
    context, in which case it returns only the first column from the row.
    This is especially convenient for queries that return a single value:



    my $buddy_count = $dbh->selectrow_array ("SELECT COUNT(*) FROM profile");


    If a query returns no result, selectrow_array( )
    returns an empty array or undef, depending on
    whether you call it in array or scalar context.



    selectrow_arrayref(
    )
    and selectrow_hashref(
    )
    select the first row of the result set
    and return a reference to it, or undef if no row
    was selected. To access the column values, treat the reference the
    same way you treat the return value from fetchrow_arrayref(
    )
    or fetchrow_hashref( ). You can also
    use the reference to get the row and column counts:



    my $ref = $dbh->selectrow_arrayref ($query);
    my $ncols = (defined ($ref) ? @{$ref} : 0);
    my $nrows = ($ncols ? 1 : 0);

    my $ref = $dbh->selectrow_hashref ($query);
    my $ncols = (defined ($ref) ? keys (%{$ref}) : 0);
    my $nrows = ($ncols ? 1 : 0);


    With selectcol_arrayref(
    )
    , a reference to a single-column array is
    returned, representing the first column of the result set. Assuming a
    non-undef return value, elements of the array are
    accessed as
    $ref->[i]
    for the value from row i. The number of
    rows is the number of elements in the array, and the column count is
    1 or 0:



    my $ref = $dbh->selectcol_arrayref ($query);
    my $nrows = (defined ($ref) ? @{$ref} : 0);
    my $ncols = ($nrows ? 1 : 0);


    selectall_arrayref(
    )
    returns a reference to an array, where
    the array contains an element for each row of the result. Each of
    these elements is a reference to an array. To access row
    i of the result set, use
    $ref->[i]
    to get a reference to the row. Then treat the row reference the same
    way as a return value from fetchrow_arrayref( ) to
    access individual column values in the row. The result set row and
    column counts are available as follows:



    my $ref = $dbh->selectall_arrayref ($query);
    my $nrows = (defined ($ref) ? @{$ref} : 0);
    my $ncols = ($nrows ? @{$ref->[0]} : 0);


    selectall_hashref(
    )
    is somewhat similar to
    selectall_arrayref( ), but returns a reference to
    a hash, each element of which is a hash reference to a row of the
    result. To call it, specify an argument that indicates which column
    to use for hash keys. For example, if you're
    retrieving rows from the profile table, the
    PRIMARY KEY is the
    id column:



    my $ref = $dbh->selectall_hashref ("SELECT * FROM profile", "id");


    Then access rows using the keys of the hash. For example, if one of
    the rows has a key column value of 12, the hash reference for the row
    is accessed as $ref->{12}. That value is keyed
    on column names, which you can use to access individual column
    elements (for example, $ref->{12}->{name}).
    The result set row and column counts are available as follows:



    my @keys = (defined ($ref) ? keys (%{$ref}) : ( ));
    my $nrows = scalar (@keys);
    my $ncols = ($nrows ? keys (%{$ref->{$keys[0]}}) : 0);


    The selectall_XXX(
    )
    methods are useful when you need to process a result set
    more than once, because DBI provides no way to
    "rewind" a result set. By assigning
    the entire result set to a variable, you can iterate through its
    elements as often as you please.



    Take care when using the high-level methods if you have
    RaiseError disabled. In that case, a
    method's return value may not always allow you to
    distinguish an error from an empty result set. For example, if you
    call selectrow_array( ) in scalar context to
    retrieve a single value, an undef return value is
    particularly ambiguous because it may indicate any of three things:
    an error, an empty result set, or a result set consisting of a single
    NULL value. If you need to test for an error, you
    can check the value of $DBI::errstr or
    $DBI::err.





    2.5.6 PHP



    PHP
    doesn't have separate functions for issuing queries
    that return result sets and those that do not. Instead, there is a
    single function mysql_query(
    )
    for all queries. mysql_query(
    )
    takes a query string and an optional connection
    identifier as arguments, and returns a result identifier. If you
    leave out the connection identifier argument, mysql_query(
    )
    uses the most recently opened connection by default. The
    first statement below uses an explicit identifier; the second uses
    the default connection:



    $result_id = mysql_query ($query, $conn_id);
    $result_id = mysql_query ($query);


    If the query fails, $result_id will be
    FALSE. This means that an error occurred because
    your query was bad: it was syntactically invalid, you
    didn't have permission to access a table named in
    the query, or some other problem prevented the query from executing.
    A FALSE return value does not
    mean that the query affected 0 rows (for a DELETE,
    INSERT, or UPDATE) or returned
    rows (for a SELECT).



    If $result_id is not FALSE, the
    query executed properly. What you do at that point depends on the
    type of query. For queries that don't return rows,
    $result_id will be TRUE, and
    the query has completed. If you want, you can call
    mysql_affected_rows(
    )
    to find out how many rows were changed:



    $result_id = mysql_query ("DELETE FROM profile WHERE cats = 0", $conn_id);
    if (!$result_id)
    die ("Oops, the query failed");
    print (mysql_affected_rows ($conn_id) . " rows were deleted\n");


    mysql_affected_rows( ) takes the connection
    identifier as its argument. If you omit the argument, the current
    connection is assumed.



    For queries that return a result set, mysql_query(
    )
    returns
    a nonzero result identifier. Generally, you use this identifier to
    call a row-fetching function in a loop, then call
    mysql_free_result( ) to release the result set.
    The result identifier is really nothing more than a number that tells
    PHP which result set you're using. This identifier
    is not a count of the number of rows selected,
    nor does it contain the contents of any of those rows. Many beginning
    PHP programmers make the mistake of thinking mysql_query(
    )
    returns a row count or a result set, but it
    doesn't. Make sure you're clear on
    this point and you'll save yourself a lot of
    trouble.



    Here's an example that shows how to run a
    SELECT query and use the result identifier to
    fetch the rows:



    $result_id = mysql_query ("SELECT id, name, cats FROM profile", $conn_id);
    if (!$result_id)
    die ("Oops, the query failed");
    while ($row = mysql_fetch_row ($result_id))
    print ("id: $row[0], name: $row[1], cats: $row[2]\n");
    print (mysql_num_rows ($result_id) . " rows were returned\n");
    mysql_free_result ($result_id);


    The example demonstrates that you obtain the rows in the result set
    by executing a loop in which you pass the result identifier to one of
    PHP's row-fetching functions. To obtain a count of
    the number of rows in a result set, pass the result identifier to
    mysql_num_rows( ). When there are no more rows,
    pass the identifier to mysql_free_result( ) to
    close the result set. (After you call mysql_free_result(
    )
    , don't try to fetch a
    row or get the row count, because at that point
    $result_id is no longer valid.)



    Each PHP row-fetching function returns the next row of the result set
    indicated by $result_id, or
    FALSE when there are no more rows. Where they
    differ is in the data type of the return value. The function shown in
    the preceding example, mysql_fetch_row( ), returns
    an array whose elements correspond to the columns selected by the
    query and are accessed using numeric subscripts.
    mysql_fetch_array( ) is like
    mysql_fetch_row( ), but the array it returns also
    contains elements that can be accessed using the names of the
    selected columns. In other words, you can access each column using
    either its numeric position or its name:



    $result_id = mysql_query ("SELECT id, name, cats FROM profile", $conn_id);
    if (!$result_id)
    die ("Oops, the query failed");
    while ($row = mysql_fetch_array ($result_id))
    {
    print ("id: $row[0], name: $row[1], cats: $row[2]\n");
    print ("id: $row[id], name: $row[name], cats: $row[cats]\n");
    }
    print (mysql_num_rows ($result_id) . " rows were returned\n");
    mysql_free_result ($result_id);


    Despite what you might expect, mysql_fetch_array(
    )
    is not appreciably slower than mysql_fetch_row(
    )
    , even though the array it returns contains more
    information.



    The previous example does not quote the non-numeric element names
    because they appear inside a quoted string. Should you refer to the
    elements outside of a string, the element names should be quoted:



    printf ("id: %s, name: %s, cats: %s\n",
    $row["id"], $row["name"], $row["cats"]);


    mysql_fetch_object(
    )
    returns an object having members that
    correspond to the columns selected by the query and that are accessed
    using the column names:



    $result_id = mysql_query ("SELECT id, name, cats FROM profile", $conn_id);
    if (!$result_id)
    die ("Oops, the query failed");
    while ($row = mysql_fetch_object ($result_id))
    print ("id: $row->id, name: $row->name, cats: $row->cats\n");
    print (mysql_num_rows ($result_id) . " rows were returned\n");
    mysql_free_result ($result_id);


    PHP 4.0.3 adds a fourth row-fetching function,
    mysql_fetch_assoc(
    )
    , that returns an array containing
    elements that are accessed by name. In other words, it is like
    mysql_fetch_array( ), except that the row does not
    contain the values accessed by numeric index.




    Don't Use count( ) to Get a Column Count in PHP 3



    PHP programmers
    sometimes fetch a result set row and then
    use count($row) to determine how many values the
    row contains. It's preferable to use
    mysql_num_fields( ) instead, as you can see for
    yourself by executing the following fragment of PHP code:



    if (!($result_id = mysql_query ("SELECT 1, 0, NULL", $conn_id)))
    die ("Cannot issue query\n");
    $count = mysql_num_fields ($result_id);
    print ("The row contains $count columns\n");
    if (!($row = mysql_fetch_row ($result_id)))
    die ("Cannot fetch row\n");
    $count = count ($row);
    print ("The row contains $count columns\n");


    If you run the code under PHP 3, you'll find that
    count( ) returns 2. With PHP 4, count(
    )
    returns 3. These differing results occur because
    count( ) counts array values that correspond to
    NULL values in PHP 4, but not in PHP 3. By
    contrast, mysql_field_count( ) uniformly returns 3
    for both versions of PHP. The moral is that count(
    )
    won't necessarily give you an accurate
    value. Use mysql_field_count(
    )
    if you want to know the true column
    count.






    2.5.7 Python



    The
    Python
    DB-API interface does not have distinct calls for queries that return
    a result set and those that do not. To process a query in Python, use
    your database connection object to get a
    cursor object.[6] Then use the cursor's execute(
    )
    method to send the query to the server. If there is no
    result set, the query is completed, and you can use the
    cursor's
    rowcount attribute to determine how many
    records were changed:[7]


    [6] If
    you're familiar with the term
    "cursor" as provided on
    the server side in some databases, MySQL doesn't
    really provide cursors the same way. Instead, the MySQLdb module
    emulates cursors on the client side of query execution.


    [7] Note that
    rowcount is an attribute, not a function. Refer to
    it as rowcount, not rowcount(
    )
    , or an exception will be raised.



    try:
    cursor = conn.cursor ( )
    cursor.execute ("UPDATE profile SET cats = cats+1 WHERE name = 'Fred'")
    print "%d rows were updated" % cursor.rowcount
    except MySQLdb.Error, e:
    print "Oops, the query failed"
    print e


    If the query does return a result set, fetch its rows and close the
    set. DB-API provides a couple of methods for retrieving rows.
    fetchone( ) returns the next row as a sequence (or
    None when there are no more rows):



    try:
    cursor = conn.cursor ( )
    cursor.execute ("SELECT id, name, cats FROM profile")
    while 1:
    row = cursor.fetchone ( )
    if row == None:
    break
    print "id: %s, name: %s, cats: %s" % (row[0], row[1], row[2])
    print "%d rows were returned" % cursor.rowcount
    cursor.close ( )
    except MySQLdb.Error, e:
    print "Oops, the query failed"
    print e


    As you can see from the preceding example, the
    rowcount attribute is useful for
    SELECT queries, too; it indicates the number of
    rows in the result set.



    Another row-fetching method, fetchall(
    )
    , returns the entire result set as a
    sequence of sequences. You can iterate through the sequence to access
    the rows:



    try:
    cursor = conn.cursor ( )
    cursor.execute ("SELECT id, name, cats FROM profile")
    rows = cursor.fetchall ( )
    for row in rows:
    print "id: %s, name: %s, cats: %s" % (row[0], row[1], row[2])
    print "%d rows were returned" % cursor.rowcount
    cursor.close ( )
    except MySQLdb.Error, e:
    print "Oops, the query failed"
    print e


    Like DBI, DB-API doesn't provide any way to rewind a
    result set, so fetchall( ) can be convenient when
    you need to iterate through the rows of the result set more than once
    or access individual values directly. For example, if
    rows holds the result set, you can access the
    value of the third column in the second row as
    rows[1][2] (indexes begin at 0, not 1).



    To access row values by column name, specify the
    DictCursor cursor type when you
    create the cursor object. This causes rows to be returned as Python
    dictionary objects with named elements:



    try:
    cursor = conn.cursor (MySQLdb.cursors.DictCursor)
    cursor.execute ("SELECT id, name, cats FROM profile")
    for row in cursor.fetchall ( ):
    print "id: %s, name: %s, cats: %s" \
    % (row["id"], row["name"], row["cats"])
    print "%d rows were returned" % cursor.rowcount
    cursor.close ( )
    except MySQLdb.Error, e:
    print "Oops, the query failed"
    print e




    2.5.8 Java



    The
    JDBC
    interface provides specific object types for the various phases of
    query processing. Queries are issued in JDBC by passing SQL strings
    to Java objects of one type. The results, if there are any, are
    returned as objects of another type. Problems that occur while
    accessing the database cause exceptions to be thrown.



    To issue a query, the first step is to get a
    Statement object by calling the
    createStatement( ) method of your
    Connection object:



    Statement s = conn.createStatement ( );


    Then use the Statement object to send the query to
    the server. JDBC provides several methods for doing this. Choose the
    one that's appropriate for the type of statement you
    want to issue: executeUpdate(
    )
    for statements that
    don't return a result set, executeQuery(
    )
    for statements that do, and execute( )
    when you don't know.



    The executeUpdate( ) method sends a query that
    generates no result set to the server and returns a count indicating
    the number of rows that were affected. When you're
    done with the statement object, close it. The following example
    illustrates this sequence of events:



    try
    {
    Statement s = conn.createStatement ( );
    int count = s.executeUpdate ("DELETE FROM profile WHERE cats = 0");
    s.close ( ); // close statement
    System.out.println (count + " rows were deleted");
    }
    catch (Exception e)
    {
    Cookbook.printErrorMessage (e);
    }


    For statements that return a result set, use executeQuery(
    )
    . Then get a result set object and use it to retrieve the
    row values. When you're done, close both the result
    set and statement objects:



    try
    {
    Statement s = conn.createStatement ( );
    s.executeQuery ("SELECT id, name, cats FROM profile");
    ResultSet rs = s.getResultSet ( );
    int count = 0;
    while (rs.next ( )) // loop through rows of result set
    {
    int id = rs.getInt (1); // extract columns 1, 2, and 3
    String name = rs.getString (2);
    int cats = rs.getInt (3);
    System.out.println ("id: " + id
    + ", name: " + name
    + ", cats: " + cats);
    ++count;
    }
    rs.close ( ); // close result set
    s.close ( ); // close statement
    System.out.println (count + " rows were returned");
    }
    catch (Exception e)
    {
    Cookbook.printErrorMessage (e);
    }


    The ResultSet object returned by the
    getResultSet( ) method of your
    Statement object has a number of methods of its
    own, such as next( ) to fetch rows and various
    getXXX(
    )
    methods that access columns of the current row. Initially
    the result set is positioned just before the first row of the set.
    Call next( ) to fetch each row in succession until
    it returns false, indicating that there are no more rows. To
    determine the number of rows in a result set, count them yourself, as
    shown in the preceding example.



    Column values are accessed
    using methods such as getInt( ),
    getString( ), getFloat( ), and
    getDate( ). To obtain the column value as a
    generic object, use getObject( ). The
    getXXX(
    )
    calls can be invoked with an argument indicating either
    column position (beginning at 1, not 0) or column name. The previous
    example shows how to retrieve the id,
    name, and cats columns by
    position. To access columns by name instead, the row-fetching loop of
    that example can be rewritten as follows:



    while (rs.next ( ))  // loop through rows of result set
    {
    int id = rs.getInt ("id");
    String name = rs.getString ("name");
    int cats = rs.getInt ("cats");
    System.out.println ("id: " + id
    + ", name: " + name
    + ", cats: " + cats);
    ++count;
    }


    You can retrieve a given column value using any
    getXXX(
    )
    call that makes sense for the column type. For example,
    you can use getString( ) to retrieve any column
    value as a string:



    String id = rs.getString ("id");
    String name = rs.getString ("name");
    String cats = rs.getString ("cats");
    System.out.println ("id: " + id
    + ", name: " + name
    + ", cats: " + cats);


    Or you can use getObject(
    )
    to retrieve values as generic objects
    and convert the values as necessary. The following code uses
    toString( ) to convert object values to printable
    form:



    Object id = rs.getObject ("id");
    Object name = rs.getObject ("name");
    Object cats = rs.getObject ("cats");
    System.out.println ("id: " + id.toString ( )
    + ", name: " + name.toString ( )
    + ", cats: " + cats.toString ( ));


    To find out how many columns are in each row, access the result
    set's metadata. The following code uses the column
    count to print each row's columns as a
    comma-separated list of values:



    try
    {
    Statement s = conn.createStatement ( );
    s.executeQuery ("SELECT * FROM profile");
    ResultSet rs = s.getResultSet ( );
    ResultSetMetaData md = rs.getMetaData ( ); // get result set metadata
    int ncols = md.getColumnCount ( ); // get column count from metadata
    int count = 0;
    while (rs.next ( )) // loop through rows of result set
    {
    for (int i = 0; i < ncols; i++) // loop through columns
    {
    String val = rs.getString (i+1);
    if (i > 0)
    System.out.print (", ");
    System.out.print (val);
    }
    System.out.println ( );
    ++count;
    }
    rs.close ( ); // close result set
    s.close ( ); // close statement
    System.out.println (count + " rows were returned");
    }
    catch (Exception e)
    {
    Cookbook.printErrorMessage (e);
    }


    The third JDBC query-executing method, execute(
    )
    , works for either type of query.
    It's particularly useful when you receive a query
    string from an external source and don't know
    whether or not it generates a result set. The return value from
    execute( ) indicates the query type so that you
    can process it appropriately: if execute( )
    returns true, there is a result set, otherwise not. Typically
    you'd use it something like this, where
    queryStr represents an arbitrary SQL statement:



    try
    {
    Statement s = conn.createStatement ( );
    if (s.execute (queryStr))
    {
    // there is a result set
    ResultSet rs = s.getResultSet ( );

    // ... process result set here ...

    rs.close ( ); // close result set
    }
    else
    {
    // there is no result set, just print the row count
    System.out.println (s.getUpdateCount ( )
    + " rows were affected");
    }
    s.close ( ); // close statement
    }
    catch (Exception e)
    {
    Cookbook.printErrorMessage (e);
    }



    Closing JDBC Statement and Result Set Objects



    The JDBC query-issuing
    examples in this section close the statement and result set objects
    explicitly when they are done with those objects. Some Java
    implementations close them automatically when you close the
    connection. However, buggy implementations may fail to do this
    properly, so it's best not to rely on that behavior.
    Close the objects yourself when you're done with
    them to avoid difficulties.











      I l@ve RuBoard