SQL - Other Procedural Paradigms#

  • There are many other paradigms that have been developed over the years, some are quite archaic but may be part of legacy systems you may encounter. I provide them here as a reference.

Call-Level Interface Examples: OCCI#

  • OCCI is a C-library specific to Oracle, but it is designed to very closely resemble JDBC for Java which is a standard.

    #include <occi.h>
    using namespace oracle::occi;
    
    Environment* const env = Environment::createEnvironment(Environment::DEFAULT);
    
    Connection* const con = env->createConnection(user, pass, osid);
    
    Statement* const s =
           con->createStatement("SELECT a.stageName"
                                " FROM movies.actors a"
                                " WHERE a.stagename like 'A%'");
    

Initialization#

  • Each OCCI program must initialize an environment at the start of a program.

    static Environment* Environment::createEnvironment (Environment::DEFAULT)
    
  • The Environment object contains the memory allocator and thread-library configuration for OCI.

  • You should explicitly terminate an environment at the end of a program to release all memory.

    Environment::terminateEnvironment(env);
    

Connection#

  • After initialization, you must open a connection to the specific database instance you are going to query.

    Connection* Environment::createConnection (string username, string password, string sid)
    
  • It will authenticate the given user and password for the given instance id.

    Connection* con = env->createConnection("scott",  "tiger", "ora9i");
    
  • A single connection can be used to query the same database multiple times in parallel or sequentially.

  • You should terminate your connection at the end of your program to release all memory at the client and the database server.

    env->terminateConnection(con);
    

Querying#

  • Once you have established a connection to the database, you are ready to execute queries and updates.

  • To execute a query, you will need to:

    • Create an SQL statement and load it into a statement type object.

    • Execute your query which will return one or more tuples.

    • Create a resultset object that will allow you to iterate through the tuples returned by the query.

    • Close your resultset object so that the database and your program releases the necessary memory.

    • Close your statement if you will no longer use it. Note that you can use a single statement object repeatedly with different SQL queries.

Statements#

  • Create a statement for a specific connection:

    Statement* sel_all_stmt
    con->createStatement("SELECT attr1 FROM my_table");
    
    ..statements to execute this query here…
    
  • Change the query for this statement if necessary:

    sel_all_stmt->setSQL("SELECT attr2, attr3 FROM new_table");
    
  • When finished, release the statement object.

    con->terminateStatement(sel_all_stmt);
    

Parametrized Statements#

  • Very often statements are executed multiple times with different values.

  • For example, suppose a query that finds the name of a specific employee may be executed multiple times for different employees.

    Statement* sel_name con->createStatement("SELECT name FROM employee WHERE id = :1");
    
  • This means this query will need to be supplied by one value before it is executed.

    sel_name->setInt(1, 112223333);
    
  • The type used in the “set” method should set the type of the value being supplied.

  • This type of a query is UNPREPARED if the required value is not supplied by the program.

  • A prepared statement is optimized once and the query plan is used multiple times for each execution of the query saving execution time.

    Statement* sel_name con->createStatement("SELECT name FROM employee WHERE id = :1 AND Office = :2");
    sel_name->setInt(1, 112223333);
    sel_name->setString(2, “AE125”);
    
  • Or

    Statement* sel_name con->createStatement("SELECT name FROM employee WHERE id = :1 AND Office = :2");
    sel_name->setInt(1, ssnVar);
    sel_name->setString(2, officeVar);
    
  • Where ssnVar and officeVar are program variables of types integer and string respectively containing the necessary values.

Update statements#

  • All statements that change the database are executed using executeUpdate method.

  • Examples are insert, update, delete, create …, drop … statements

    stmt->executeUpdate(“CREATE TABLE basket_tab (fruit VARCHAR2(30), quantity NUMBER)”);
    
    statement* s1 =
    con->createStatement("INSERT INTO my_table (a, b) VALUES (1, 'A')");
    s1->executeUpdate();
    
  • Update statements return the total number of tuples effected which can be returned by getUpdateCount() method.

Select statements#

  • A SELECT query returns one or more tuples by the execution of the executeQuery method.

  • To process these tuples, you need a result set object which processes tuples in a similar way to a file.

  • You need to open, iterate through and close a result set to access the tuples.

    statement* s1 =
    con->createStatement("SELECT name FROM emp WHERE id < 1000");
    ResultSet r = s1->executeQuery();
    
  • Query is executed, the tuples are returned to the program and the result set is initialized to the before the first item in the results.

  • To find the first item, you need to execute “next” operation.

  • next returns false if the last tuple is already been read.

    statement* s1 = con->createStatement(
             "SELECT id, name FROM emp WHERE id < 1000");
    ResultSet r = s1->executeQuery();
    while (r->next()) {
        varId = r->getInt(1) ;
        varName = r->getString(2) ;
    }
    s->closeResultSet(r);
    
  • To read the columns of the current tuple pointed to by the result set, get methods are used.

  • The type of these methods much match the type of the column returned by the program. - getXXX(i) means attribute i of the query should have type XXX.

Errors and Status#

  • An SQLException is raised for many things that may go wrong for your program: - Connection cannot be established - The query cannot be executed, etc.

  • The exceptions can be caught and checked in the program.

    try{
       ... operations which throw SQLException ...
    }
    catch (SQLException e){
       cerr << e.what();
    }
    
  • Other useful methods: e.getMessage(), e.getErrorCode()

Errors and Status#

  • As statements are created dynamically and executed in the program, it may be necessary to check their status while the program is running.

  • This is accomplished with the status methods for each class.

  • Examples:

    • Check whether the statement is UNPREPARED, PREPARED, RESULT_SET_AVAILABLE, or UPDATE_COUNT_AVAILABLE.

    • Check whether the result set is END_OF_FETCH = 0, DATA_AVAILABLE.

  • Other methods exist to check for other status related information. For example, check whether the result set is empty (r->isNull).

Dynamic Conditions#

  • Suppose tableName is a program variable set to a specific constant by the user.

    sqlString = "SELECT * FROM " << tableName ;
    statement* s1 = con->createStatement(sqlString);
    ResultSet r = s1->executeQuery();
    while (r->next()) {
               // what attributes are in this result set?
    }
    s->closeResultSet(r);
    
  • The function:

    vector<MetaData> getColumnListMetaData() const;
    

    that returns number, types and properties of aResultSet’s columns.

Transactions#

  • A transaction starts with the first executeQuery, executeUpdate statement and ends until the first commit, abort and rollback statement executed for the given connection.

    con->commit();
    con->rollback();
    
  • After a rollback/commit, the next query/update will start a new transaction.

Call-Level Interface Examples: JDBC#

  • OCCI is an Oracle specific language for enabling communications between a C++ program and a database.

  • JDBC is a standard for any database product and a Java program for the same purpose.

  • JDBC and OCCI are very similar to each other and have almost identical set of classes and methods. In fact, OCCI is based on JDBC.

  • To accomplish the communication between a Java program and a database, a set of libraries called a “driver” is needed.

  • JDBC drivers are specific to the database server.

  • Example program:

    import java.sql.*;
    import oracle.sql.*;
    import oracle.jdbc.driver.*;
    class Employee
    {
    public static void main (String args []) throws SQLException
    {//Set your user name and the password
    String userName = "dummy" ;
    String passWord = "dummy" ;
    
    // Load the Oracle JDBC driver
    DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver());
    
       Connection conn = DriverManager.getConnection("jdbc:oracle:thin:@acadoracle.server.rpi.edu:1521:ora9",userName,passWord);
    // Create a statement which will return a cursor that
    // will allow you to scroll the result set using both
    // "next" and "previous" methods
    
    try {
          Statement stmt = conn.createStatement
               (ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
    
          ResultSet rset = stmt.executeQuery("SELECT name, oid FROM items ");
    
          // Iterate through the result and print the item names
          while (rset.next ()) {
              //Get item name, which is the first column
              System.out.println (rset.getString (1));
    
              PreparedStatement pstmt = conn.prepareStatement ("SELECT name FROM owners WHERE oid = ?") ;
    
              //Feed the owner id retrieved from rset into pstmt
              pstmt.setInt(1, rset.getInt (2));
              ResultSet dset = pstmt.executeQuery() ;
              if (dset.next())
                  System.out.println(dset.getString (1));
          } }
     }
     catch (SQLException) { error-handling-code } }  }
    

libpq: Postgresql C-language interface#

  • The C-language interface for Postgresql uses a number of function calls to commmunicate with the database.

  • Example:

    #include <stdio.h>
    #include <stdlib.h>
    #include "libpq-fe.h”
    static void
    exit_nicely(PGconn *conn)
    {
        PQfinish(conn);
        exit(1);
    }
    
    int
    main(int argc, char **argv)
    {
        const char *conninfo;
        PGconn     *conn;  PGresult   *res;
        int         nFields;
        int         i,  j;
    
        conninfo = "port=5432 dbname='sibel' host='localhost' user='sibel' ";
        conn = PQconnectdb(conninfo);
        if (PQstatus(conn) != CONNECTION_OK)    {
            fprintf(stderr, "Connection to database failed: %s",
            PQerrorMessage(conn));
            exit_nicely(conn);
        }
    
        /* Start a transaction block */
        res = PQexec(conn, "BEGIN");
    
        if (PQresultStatus(res) != PGRES_COMMAND_OK)
        {
             fprintf(stderr, "BEGIN command failed: %s", PQerrorMessage(conn));
             PQclear(res);
             exit_nicely(conn);
        }
        /* Should PQclear PGresult whenever it is no longer needed to avoid\
        memory leaks */
        PQclear(res);
    
        res = PQexec(conn, "DECLARE myportal CURSOR FOR select * from pg_database");
        if (PQresultStatus(res) != PGRES_COMMAND_OK)
        {
            fprintf(stderr, "DECLARE CURSOR failed: %s", PQerrorMessage(conn));
            PQclear(res);
            exit_nicely(conn);
        }
    
        res = PQexec(conn, "FETCH ALL in myportal");
        if (PQresultStatus(res) != PGRES_TUPLES_OK)
        {
            fprintf(stderr, "FETCH ALL failed: %s", PQerrorMessage(conn));
            PQclear(res);
            exit_nicely(conn);
        }
        /* first, print out the attribute names */
        nFields = PQnfields(res);
        for (i = 0; i < nFields; i++)
            printf("%-15s", PQfname(res, i));
        printf("\n\n");
        /* next, print out the rows */
        for (i = 0; i < PQntuples(res); i++)
        {
            for (j = 0; j < nFields; j++)
                printf("%-15s", PQgetvalue(res, i, j));
            printf("\n");
        }
    
        PQclear(res);
    
        /* close the portal ... we don't bother to check for errors ... */
        res = PQexec(conn, "CLOSE myportal");
        PQclear(res);
    
        /* end the transaction */
        res = PQexec(conn, "END");
        PQclear(res);
    
        /* close the connection to the database and cleanup */
        PQfinish(conn);
    
        return 0;
    }
    

SQL - Embedded SQL Programming#

  • In this section, we will look at a different paradigm for writing programs that incorporate SQL queries.

  • The main distinction between this model and others is that the program is written in a host language like C, but contains constructs that are foreign to the host language.

  • One thing you will notice that it requires programmers to work on very low level details of communication with the database.

  • To be able to compile these programs, we must first precompile using a special program, which will rewrite the program code by replacing pieces of it.

  • Once precompilation is finished, we compile the remaining code.

  • Embedded SQL, ESQL is an industry standard language.

Overview#

  • Embedded SQL is an SQL standard for writing a program in a host language (like C) with SQL statements starting with the string:

    EXEC SQL
    

    and ending with semicolon (;).

  • In addition, all variables to be used by the program as input/output to a query must be declared within a declare section.

  • Often type conversion for preliminary data types between the programming language and SQL is done by hand.

  • Proc in Oracle, ECPG in Postgresql implements the C embeddings for SQL.

  • Note that the following notes are based on Oracle embedded SQL language (slight differences are possible for postgresql)

  • Example program:

    #include <stdio.h>
    exec sql include sqlca;
    
    char user_prompt[] = "Please enter username and password:  ";
    char cid_prompt[] = "Please enter customer ID:  ";
    
    int main()
    {
        exec sql begin declare section;       /* declare SQL host variables    */
            char cust_id[5];
            char cust_name[14];
            float cust_discnt;                  /* host var for discnt value    */
            char user_name[20];
        exec sql end declare section;
    
        exec sql whenever sqlerror goto report_error; /* error trap condition     */
        exec sql whenever not found goto notfound; /* not found condition      */
    
        exec sql unix:postgresql://csc4380.cs.rpi.edu/sibel AS myconnection USER :user_name;
        /* ORACLE format: connect  */
    
        while (prompt(cid_prompt, 1, cust_id, 4) >= 0) {
            exec sql select cname, discnt
                    into :cust_name, :cust_discnt   /* retrieve cname, discnt   */
                    from customers where cid = :cust_id;
            exec sql commit work;                     /* release read lock on row */
    
            printf("CUSTOMER'S NAME IS  %s AND DISCNT IS  %5.1f\n",
                 cust_name, cust_discnt);            /* NOTE, (:) not used here  */
            continue;
        }
    }
    

ESQL#

  • Each ESQL statement starts with EXEC SQL keyword and ends with a semicolon ;

  • A pre-compiler will scan a program file and only read the statements enclosed within EXEC SQL statements and disregard everything else.

  • SQLCA is a specific data structure for storing status codes of all SQL operations

    /* always have this for error handling*/
    exec sql include sqlca ;
    

Connections#

  • To be able to perform any operations, we must open a connection to the database.

    EXEC SQL CONNECT TO target [AS connection-name] [USER user-name];
    
  • Many connection can be opened in a program, but generally one connection per database is sufficient.

  • Different databases can be used in a single program.

  • Close all connections before the program exists:

    EXEC SQL DISCONNECT [connection];
    
  • Change between multiple open connections with:

    EXEC SQL SET CONNECTION connection-name;
    

Variables in ESQL#

  • All variables that will be used in an SQL statement must be declared using an ESQL declaration and data type

    EXEC SQL BEGIN DECLARE SECTION ;
    VARCHAR    e_name[30], username[30] ;
    INTEGER     e_ssn, e_dept_id ;
    EXEC SQL END DECLARE SECTION ;
    
  • You can use almost any SQL command in ESQL as long as proper input to these commands are provided in the form of program variables.

Executing SQL commands#

  • Suppose we want to find the name of an employee given his/her SSN (input by the user of the program):

    EXEC SQL select name, dept_id into :e_name, :e_dept_id
    from employee
    where ssn = :e_ssn ;
    
    • Program variables are preceded by “:”, i.e. :e_ssn.

  • Read the value of the variable “e_ssn” and execute the SQL statement

    using this value, store the returned values of columns “name” and “dept_id” in the program variables “e_name” and “e_dept_id”.

  • Compare the above query with the expression below. What is the difference?

    EXEC SQL select name, dept_id
    from employee  where ssn = e_ssn ;
    

Executing SQL commands#

  • We are able to write:

    EXEC SQL select name, dept_id into :e_name, :e_dept_id
             from employee
             where ssn = :e_ssn ;
    
  • Since this query returns a single tuple. For this tuple, we read the returned values.

  • We will see how to handle queries that return multiple tuples in a minute.

Dealing with Strings#

  • There is a mismatch between the definition of a string in Oracle and in C/C++.

  • In C, the end of a string is identified by the null character ‘0’. Hence, “Sibel” would be stored as characters ‘S’,‘i’,‘b’,‘e’,‘l’,‘0’.

  • In Oracle, the length of a string is stored together with the string and there is no special end of string character.

  • If you convert a data string from Oracle to C, you must pad it with ‘0’ manually!

  • The data type VARCHAR e_name[30] is translated by the pre-compiler to the following structure:

    struct {
        unsigned short len
        unsigned char arr[30]
    } e_name ;
    
  • Putting the pieces together:

    strcpy(username.arr, “Sibel Adali") ;
    username.len = strlen(“Sibel Adali") ;
    strcpy(passwd.arr, “tweety-bird") ;
    passwd.len = strlen(“tweety-bird") ;
    exec sql   connect :username  identified by :passwd ;
    scanf(“%d", &e_ssn) ;
    exec sql   select name, dept_id into :e_name, :e_dept_id
               from employee where ssn = :e_ssn ;
    e_name.arr[e_name.len] = '\0' ;   /* so can use string in C*/
    printf(“%s", e_name.arr) ;
    exec sql commit work ;  /* make any changes permanent */
    exec sql disconnect ;     /* disconnect from the database */
    

Status Processing#

  • SQL Communications area is a data structure that contains information about

    • Error codes (might be more detailed than SQLSTATE)

    • Warning flags

    • Event information

    • Rows-processed count

    • Diagnostics for all processed SQL statements.

  • Included in the program using

    EXEC SQL INCLUDE SQLCA; or #include <sqlca.h>
    
  • It is possible to get the full text of error messages and other detailed status information.

  • Whenever an SQL statement is executed, its status is returned in a variable named "SQLSTATE"

  • This variable must be defined in the variable section, but it is populated with values automatically

    EXEC SQL BEGIN DECLARE SECTION;
      char    SQLSTATE[6] ;
    EXEC SQL END DECLARE SECTION;
    
  • Different errors and conditions have values that might be vendor specific.

Status processing#

  • sqlca covers both warnings and errors. If multiple warnings or errors occur during the execution of a statement, then sqlca will only contain information about the last one.

  • If no error occurred in the last SQL statement, sqlca.sqlcode will be 0 and sqlca.sqlstate will be “00000”.

  • If a warning or error occurred, then sqlca.sqlcode will be negative and sqlca.sqlstate will be different from “00000”.

  • If the last SQL statement was successful, then sqlca.sqlerrd[1] contains the OID of the processed row, if applicable, and sqlca.sqlerrd[2] contains the number of processed or returned rows, if applicable to the command.

  • The code can be checked after each statement and error handling code can be written

    • Commit, rollback

    • Exit program, etc.

    if (strcmp(SQLSTATE, "000000") != 0)
         rollback ;
    
  • It is also possible to use trap conditions that remain active throughout the program.

    EXEC SQL WHENEVER <condition> <action> ;
    
    • Conditions: SQLERROR, SQLWARNING, NOT FOUND

    • Actions: DO function, DO break, GOTO label, CONTINUE, STOP

  • Because WHENEVER is a declarative statement, its scope is positional, not logical. That is, it tests all executable SQL statements that physically follow it in the source file, not in the flow of program logic.

  • A WHENEVER directive stays in effect until superseded by another WHENEVER directive checking for the same condition.

Transactions#

  • Transactions start with the logically start with the first SQL statement and end with either a COMMIT or ROLLBACK statement

  • It is possible to set boundaries of transactions with the SQL statement:

    BEGIN ;
    SET TRANSACTION READ ONLY
        ISOLATION LEVEL READ COMMITTED
        DIAGNOSTICS SIZE 6 ;
    
  • Diagnostics size is the number of exception conditions that can be described at one time in the status.

  • READ ONLY, READ/WRITE transactions allow the programmer to define the type of the transaction

  • Isolation level allows the programmer to define the desired level of consistency

ESQL - Cursor Operations#

  • Declare a cursor using a regular SQL query (no “into”).

    EXEC SQL DECLARE emps_dept CURSOR FOR
          select ssn, name from employee
          where dept_id = :e_dept_id ;
    
  • Open a cursor: means the corresponding SQL query is executed, the results are written to a file (or a data structure) and the cursor is pointing to the first row.

    EXEC SQL OPEN emps_dept ;
    
  • Read the current row pointed to by the cursor using “fetch”. At the end of fetch, the cursor is moved to point to the next tuple.

    EXEC SQL FETCH emps_dept INTO :e_ssn, :e_name ;
    
  • How do we know when we reach the end of a cursor?

    • Check the “sqlcode” to see if the end of a cursor is reached (its expected value depends on the system).

      if (sqlca.sqlcode == -1) { … }
      
  • Error handling statements

    EXEC SQL WHENEVER NOT FOUND {}
    

Cursors and snapshots#

  • If a cursor is declared as “INSENSITIVE”, the contents of the cursor is computed once when the cursor is opened, and remains the same until the cursor is closed, even if the underlying data tables change during the this time.

    DECLARE cursor-name CURSOR INSENSITIVE CURSOR FOR table-expression
    
  • This type of cursor is a snapshot of the database, a view of it at a specific time.

    DECLARE cursor_name [INSENSITIVE][SCROLL] CURSOR FOR
      table_expression
      [ORDER BY order-item-comma-list]
      [ FOR [READ ONLY | UPDATE | OF column-commalist] ]
    

Cursors for update#

  • If a cursor is declared for update, then updates can be performed on the current tuple.

    DECLARE CURSOR cursor-name CURSOR FOR table-expression
                FOR UPDATE OF column-list
    
    UPDATE table-name SET assignment-list
               WHERE CURRENT OF cursor-name
    
    DELETE FROM table-name WHERE CURRENT OF cursor-name
    
  • For these updates to have an effect, the cursor must not be INSENSITIVE.

Constraints#

  • When constraints are violated, they cause an exception (or sqlerror) to be thrown.

  • When are constraints violated?

    • If constraint checking for a constraint is immediate, as soon as an SQL statement causes the constraint to become false, it is rolled back.

    • If a constraint is defined to be deferrable, then the constraint is not checked until a transaction tries to commit. Then, if it is violated, the whole transaction is rolled back.

      CONSTRAINT name CHECK … DEFERRABLE
      

Dynamic SQL#

  • In Dynamic SQL, embedded SQL statements are created on the fly using strings!

  • these strings are fed to an exec sql statement

    exec sql execute immediate :sql_string
    
  • Since dynamic SQL statements are not known to the pre-compiler at compile/initiation time, they must be optimized at run time!

  • Create a query once using a prepare statement and run it multiple times using the execute statement.

    strcopy(sqltext.arr, "delete from employee where ssn = ?") ;
    sqltext.len=str.len(sqltext.arr) ;
    exec sql prepare del_emp from :sqltext ;
    exec sql execute del_emp using :cust_id ;
    

SQLDA#

  • When we execute a dynamic SQL statement, we do not know which columns will be returned and how many columns will be returned.

  • The SQLDA descriptor definition allows us to find the number of columns and the value for each column.

    exec sql include sqlda ;
    exec sql declare sel_curs cursor for sel_emps ;
    exec sql prepare sel_emps from :sqltext ;
    exec sql describe sel_emps into sqlda ;
    
    exec sql open sel_curs ;
    exec sql fetch sel_curs using descriptor sqlda ;