DB2 OData Tutorial

This tutorial will explain some of the features that are available in the IBM Data Server Gateway for OData Version 1.0.0. IBM Data Server Gateway for OData enables you to quickly create OData RESTful services to query and update data in IBM DB2 LUW.

An introduction to the OData gateway is found in the following developerWorks article:

https://www.ibm.com/developerworks/community/blogs/96960515-2ea1-4391-8170-b0515d08e4da/entry/IBM_Data_Server_Gateway_for_OData_Version_1_0_0?lang=en

The code can be obtained through the following link:

https://www-945.ibm.com/support/fixcentral/swg/selectFixes?parent=ibm~Information%2BManagement&product=ibm/Information+Management/IBM+Data+Server+Client+Packages&release=11.1.*&platform=Linux&function=fixId&fixids=*odata*FP001*&includeSupersedes=0&source=fc

OData Extensions for DB2

In order to help explain some of the features of the OData Gateway, a Jupyter notebook has been created that includes an %odata command that maps DB2 SQL into the equivalent OData syntax. The next command will load the extension and make the command available to this tutorial.


In [1]:
%run db2odata.ipynb


DB2 OData Extensions Loaded.

DB2 Extensions

Since we are connecting to a DB2 database, the following command will load the DB2 Jupyter notebook extension (%sql). The DB2 extension allows you to fully interact with the DB2 database, including the ability to drop and create objects. The OData gateway provides INSERT, UPDATE, DELETE, and SELECT capability to the database, but it doesn't have the ability to create or drop actual objects. The other option would be to use DB2 directly on the database server using utilities like CLP (Command Line Processor) or DSM (Data Server Manager).


In [2]:
%run db2.ipynb


DB2 Extensions Loaded.

An Brief Introduction to OData

Rather than paraphrase what OData does, here is the official statement from the OData home page:

http://www.odata.org/

OData (Open Data Protocol) is an ISO/IEC approved, OASIS standard that defines a set of best practices for building and consuming RESTful APIs. OData helps you focus on your business logic while building RESTful APIs without having to worry about the various approaches to define request and response headers, status codes, HTTP methods, URL conventions, media types, payload formats, query options, etc. OData also provides guidance for tracking changes, defining functions/actions for reusable procedures, and sending asynchronous/batch requests.

Why OData for DB2?

Customers have a wealth of data in their databases (not just DB2) and publishing it to different devices is often fraught with many challenges. DB2 requires the use of client code to communicate between the application and the database itself. Many of APIs that are used are well known: JDBC, .Net, ODBC, OLE-DB, CLI and so on. Most programming languages have some sort of connector that maps from the language syntax to the database driver. When a new language gets developed it always needs this driver code to talk to the database. For instance, this Python notebook is communicating to DB2 natively using the ibm_db package. Without some specialized coding, there would be no way to communicate with DB2.

OData tries to remove much of the complexity of communicating with the database. There are no drivers required, no configuration file, nor any administration required on the client that is communicating with the database. All communication is done using RESTful API calls, which are available on all browsers and all operating systems. The calls to the database are replaced with standard POST, GET, DELETE, PUT and PATCH requests.

OData goes one step further and removes the syntactical differences between SQL vendors. The INSERT, DELETE, UPDATE and SELECT statements are coverted to a canonical form that should be interpreted by all vendors. Of course, interoperability depends on how much of the standard a vendor has implemented.

The end result is that an Iphone, Andriod phone, tablet, browser or any application will be able to access the database without having any code installed locally. This simplifies the ability to access the database and makes development considerably easier.

The downside to this approach is that the richness of a particular SQL dialect will not be available through OData. Complex SQL with aggregation functions and moving result windows are not a good candidate to use with OData. However, OData covers much of the query spectrum that traditional applications will use, so it makes it a good choice for agile development.

OData to DB2 Extension

Writing OData calls to DB2 requires a knowledge of the OData syntax, the RESTful calling sequence, and an understanding of the level of support of OData that DB2 provides. This tutorial will take you through all of the functions that the OData gateway currently provides and show how these calls are implemented. Feel free to use the code and extensions in your own applications.

Connecting to OData and DB2

Both the DB2 client and OData calls need connection information. The way that you go about connecting to the database is completely different between these two protocols. Let first start with the DB2 connection.

DB2 requires a userid and password to connect to a database (along with the client code that talks to DB2 over the network). Assuming you have a DB2 database somewhere, the next command will ask you for the following information:

  • DATABASE name - The name of the DB2 database you want to connect to
  • HOST ipaddress - The IP address (or localhost) where the DB2 instance can be found
  • PORT portno - The PORT number that DB2 is listening to (usually 50000)
  • USER userid - The user that will be connecting to DB2
  • PASSWORD pwd - The password for the USER (use a "?" to prompt for the value)

You need to have this information available or the program won't be able to connect. For demonstration purposes, the standard SAMPLE database should be used but in the event you don't have that created, the %sql command will generate the necessary tables for you. It is also good to be a DBADM (database administrator) on the system you are connecting to. This will allow you to create the services requires by the OData gateway. If you don't, someone with that authority will be needed to give you access through OData.

When the next set of commands is issued, the system will prompt you for the information required as well as give you the details for each of the fields.


In [3]:
%sql connect reset
%sql connect to sample


Connection reset.
Connection successful.

If you connected to the SAMPLE database, you will have the EMPLOYEE and DEPARTMENT tables available to you. However, if you are connecting to a different database, you will need to execute the next command to populate the tables for you. Note, if you run this command and the two tables already exist, the tables will not be replaced. So don't worry if you execute this command by mistake.


In [4]:
%sql -sampledata


Sample tables [EMPLOYEE, DEPARTMENT] created.

Requesting data from DB2 using the standard %sql (ibm_db) interface is relatively straight-forward. We just need to place the SQL in the command and execute it to get the results.


In [5]:
%sql SELECT * FROM EMPLOYEE


EMPNO FIRSTNME MIDINIT LASTNAME WORKDEPT PHONENO HIREDATE JOB EDLEVEL SEX BIRTHDATE SALARY BONUS COMM
0 000010 CHRISTINE I HAAS A00 3978 1995-01-01 PRES 18 F 1963-08-24 152750.0 1000.0 4220.0
1 000020 MICHAEL L THOMPSON B01 3476 2003-10-10 MANAGER 18 M 1978-02-02 94250.0 800.0 3300.0
2 000030 SALLY A KWAN C01 4738 2005-04-05 MANAGER 20 F 1971-05-11 98250.0 800.0 3060.0
3 000050 JOHN B GEYER E01 6789 1979-08-17 MANAGER 16 M 1955-09-15 80175.0 800.0 3214.0
4 000060 IRVING F STERN D11 6423 2003-09-14 MANAGER 16 M 1975-07-07 72250.0 500.0 2580.0
... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
38 200280 EILEEN R SCHWARTZ E11 8997 1997-03-24 OPERATOR 17 F 1966-03-28 46250.0 500.0 2100.0
39 200310 MICHELLE F SPRINGER E11 3332 1994-09-12 OPERATOR 12 F 1961-04-21 35900.0 300.0 1272.0
40 200330 HELENA WONG E21 2103 2006-02-23 FIELDREP 14 F 1971-07-18 35370.0 500.0 2030.0
41 200340 ROY R ALONZO E21 5698 1997-07-05 FIELDREP 16 M 1956-05-17 31840.0 500.0 1907.0
42 999999 ROY R ALONZO E21 5698 1997-07-04 FIELDREP 16 M 1956-05-16 31840.0 500.0 1907.0

43 rows × 14 columns

Now that we have a working DB2 connection, we will need to set up an OData service to talk to DB2.

Connecting through OData

Connecting through OData requires a different approach than a DB2 client. We still need to ask a bunch of questions on how we connect to the database, but this doesn't create a connection from the client. Instead what we end up creating is a service URL. This URL gives us access to DB2 through the OData gateway server.

The OData Server take the URL request and maps it to a DB2 resource, which could be one or more tables. The RESTful API needs this URL to communicate with DB2 but nothing else (userids, passwords, etc...) are sent with the request.

The following %odata command will prompt you for the connection parameters, similar to what happened with the DB2 connect. There are a few differences however. The connection requires the userid and password of the user connecting to the database, and the userid and password of a user with administration (DBABM) privileges.

The administrative user creates the service connection that will be used to communicate through the OData gateway and DB2. The regular userid and password is for the actual user that will connect to the database to manipulate the tables. Finally we need to have the schema (or owner) of the tables that will be accessed. From a DB2 perspective, this is similar to connecting to a DATABASE (SAMPLE) as userid FRED. The EMPLOYEE table was created under the userid DB2INST1, so to access the table we need to use DB2INST1.EMPLOYEE. If we didn't include the schema (DB2INST1), the query would fail since FRED was not the owner of the table.

The %odata PROMPT command will request all of the connection parameters and explain what the various fields are. Note: If you have DBADM privileges (and you created the sample tables yourself), you can leave the USERID/PASSWORD/SCHEMA values blank and they will default to the administrative user values.


In [6]:
%odata prompt


SET Command Syntax

The set command is used to tell the program how to access a data source as well as give it credentials nessary to connect. The keywords that are allowed in the command include the following. Note, you do not need to specify all of these values, only the ones that you want to change. To get the current values (except passwords), use the SETTINGS command.

  • DATABASE name - The name of the DB2 database you want to connect to
  • SCHEMA userid - The SCHEMA that any request will use when accessing a table
  • HOST ipaddress - The IP address (or localhost) where the DB2 instance can be found
  • PORT portno - The PORT number that DB2 is listening to (usually 50000)
  • USER userid - The user that will be issuing the OData requests to the table
  • PASSWORD pwd - The password for the USER (use a "?" to prompt for the value)
  • ADMIN - The user that has administrative privileges for creating the service (perhaps the same as the user)
  • PASSWORD pwd - The password for the administrative user (use a ? to prompt for the value)
  • MAXROWS amount - By default 10 rows are displayed. A value of -1 will show all rows, while any other value will allow a maximum of that many rows to be displayed

Enter the DATABASE name: SAMPLE
Enter the HOST name: localhost
Enter the PORT number: 50000
Enter the Userid on the DB2 system: jlp
Userid Password: ········
Enter the Admin user on the DB2 system [Blank to default user/password/schema of Userid values]: 

Selecting Data from a Table

So far all we have done is set up the connection parameters, but no actual connection has been made to DB2, nor has an OData service been created. The creation of a service is done when the first SQL request is issued. The next statement will retrieve the values from our favorite EMPLOYEE table, but use OData to accomplish it.


In [7]:
%odata DROP TABLE EMPLOYEE
s = %odata -e SELECT lastname, salary from employee where salary > 50000


OData connection removed for JLP.EMPLOYEE in Database SAMPLE

Creating Service Request for OData Connection
Service URL: http://localhost:9080/ODataOne/createService
Request Header
{
    "Content-Type": "application/json",
    "Accept": "application/json"
}
Connection Parameters
{
    "database": "SAMPLE",
    "host": "localhost",
    "port": "50000",
    "db2AdminUser": "JLP",
    "db2AdminPassword": "********",
    "db2ServiceUser": "JLP",
    "db2ServicePassword": "********",
    "ssl": false,
    "schema": "JLP",
    "tablenames": [
        "EMPLOYEE"
    ]
}
SELECT Command
URL  : http://localhost:9080/ODataOne/ODataService/SAMPLE-43f3a26a657f448ba537289c494fb184
OData: /EMPLOYEES?$select=LASTNAME,SALARY&$filter=SALARY gt 50000&$format=json
LASTNAME SALARY
0 HAAS 152750
1 THOMPSON 94250
2 KWAN 98250
3 GEYER 80175
4 STERN 72250
... ... ...
15 BROWN 57740
16 JONES 68270
17 NATZ 68420
18 YAMAMOTO 64680
19 JOHN 69840

20 rows × 2 columns

Viewing the OData Command

Under the covers a number of things happened when running this command. The SELECT * FROM EMPLOYEE is not what is sent to OData. The syntax is converted to something that the RESTful API understands. To view the actual OData syntax, the -e option is used to echo back the commands.


In [8]:
s = %odata -e SELECT * FROM EMPLOYEE


SELECT Command
URL  : http://localhost:9080/ODataOne/ODataService/SAMPLE-43f3a26a657f448ba537289c494fb184
OData: /EMPLOYEES?$format=json
BIRTHDATE BONUS COMM EDLEVEL EMPNO FIRSTNME HIREDATE JOB LASTNAME MIDINIT PHONENO SALARY SEX WORKDEPT
0 1963-08-24 1000 4220 18 10 CHRISTINE 1995-01-01 PRES HAAS I 3978 152750 F A00
1 1978-02-02 800 3300 18 20 MICHAEL 2003-10-10 MANAGER THOMPSON L 3476 94250 M B01
2 1971-05-11 800 3060 20 30 SALLY 2005-04-05 MANAGER KWAN A 4738 98250 F C01
3 1955-09-15 800 3214 16 50 JOHN 1979-08-17 MANAGER GEYER B 6789 80175 M E01
4 1975-07-07 500 2580 16 60 IRVING 2003-09-14 MANAGER STERN F 6423 72250 M D11
... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
38 1966-03-28 500 2100 17 200280 EILEEN 1997-03-24 OPERATOR SCHWARTZ R 8997 46250 F E11
39 1961-04-21 300 1272 12 200310 MICHELLE 1994-09-12 OPERATOR SPRINGER F 3332 35900 F E11
40 1971-07-18 500 2030 14 200330 HELENA 2006-02-23 FIELDREP WONG 2103 35370 F E21
41 1956-05-17 500 1907 16 200340 ROY 1997-07-05 FIELDREP ALONZO R 5698 31840 M E21
42 1956-05-16 500 1907 16 999999 ROY 1997-07-04 FIELDREP ALONZO R 5698 31840 M E21

43 rows × 14 columns

The results will show the URL service command used (http:// followed by details of the host location and service ID) and the OData command. In this case the command should be /EMPLOYEES. This may seem like a spelling mistake, but the OData service creates a mapping from the database table (EMPLOYEE) to a service request. To give the service request a unique name, the letter "S" is appended to the table name. Do not confuse the service name with the table name. That can sometimes lead to coding errors!

If we tried to request a table that didn't exist in the database, we would get an error message instead.


In [9]:
%odata select * from unknown_table


SQLException: com.ibm.db2.jcc.am.SqlSyntaxErrorException: DB2 SQL Error: SQLCODE=-204, SQLSTATE=42704, SQLERRMC=JLP.UNKNOWN_TABLE, DRIVER=4.22.26

One drawback of OData is that we don't get the actual error text returned. We know that the error code is, but the message isn't that descriptive. Using the %sql (DB2) command, we can find out that the table doesn't exist.


In [10]:
%sql select * from unknown_table


SQL0204N "JLP.UNKNOWN_TABLE" is an undefined name. SQLSTATE=42704 SQLCODE=-204

Limiting Output Results

The results contain 43 rows. If you want to reduce the amount of rows being returned we can use the LIMIT clause on the SELECT statement. In addition, we can use the -j flag to return the data as JSON records.


In [11]:
s = %odata -e -j SELECT * FROM EMPLOYEE LIMIT 1


SELECT Command
URL  : http://localhost:9080/ODataOne/ODataService/SAMPLE-43f3a26a657f448ba537289c494fb184
OData: /EMPLOYEES?$top=1&$format=json
[
    {
        "BIRTHDATE": "1963-08-24",
        "BONUS": 1000.0,
        "COMM": 4220.0,
        "EDLEVEL": 18,
        "EMPNO": "000010",
        "FIRSTNME": "CHRISTINE",
        "HIREDATE": "1995-01-01",
        "JOB": "PRES    ",
        "LASTNAME": "HAAS",
        "MIDINIT": "I",
        "PHONENO": "3978",
        "SALARY": 152750.0,
        "SEX": "F",
        "WORKDEPT": "A00"
    }
]

To limit the results from a OData request, you must add the \$top=x modifier at the end of the service request. The format then becomes:

  \[service url\]/\[service name\]?$top=value
You will notice that the OData syntax requires that a "?" be placed after the name of the service. In our example, EMPLOYEES is the name of the service that accesses the EMPLOYEE table. We add the ? after the end of the service name and then add the $top modifier. If there were multiple modifiers, each one must be separated with an ampersand (&) symbol.

Persistent Connection Information

What you should have received when running the previous command was a single JSON record, the service URL and the OData command. The URL will be identical to the one in the previous %odata request. There is no need to recreate a service if you are using the same table. The program created a new service when you did a SELECT for the first time. After that it keeps the service information in a file called TABLE@SCHEMA@DATABASE.pickle in the directory where the Jupyter notebook is running. If you try this statement at another time, this service URL will be retrieved from this file rather than creating another service.

Dropping a Connection

If you want to delete the connection information, use the DROP command with the database, schema, and table name in it. This doesn't drop the object or anything associated with the table. All this does is remove the service information from your system. It also does not remove the service from the OData gateway.


In [12]:
%odata \
  DROP \
    DATABASE {settings['database']} \
    SCHEMA   {settings['schema']} \
    TABLE    EMPLOYEE


OData connection removed for JLP.EMPLOYEE in Database SAMPLE

The last example illustrates two additional features of the %odata command. First, you can span statements over multiple lines by using the backslash character ('\'). You could also use the %%odata command to do this without backslashes, but it unfortunately will not allow for variable substitution. The current settings being used by OData can be found by issuing the SETTINGS command.

You can specify the command with only the TABLE option and it will take the current DATABASE and SCHEMA names from any prior settings.


In [13]:
%odata settings


Database   : SAMPLE
Schema     : JLP
User       : JLP
Admin User : JLP
Host       : localhost
Port       : 50000
Format     : table

You can also refer to these values by using the settings['name'] variable. So the DROP statement just took the current DATABASE and SCHEMA settings and deleted the definition for the EMPLOYEE table. You could have done this directly with:

DROP DATABASE SAMPLE SCHEMA DB2INST1 TABLE EMPLOYEE
The list of settings and their variable names are listed below.

Setting Variable name
DATABASE settings['database']
SCHEMA settings['schema']
ADMIN settings['admin']
A_PWD settings['a_pwd']
USER settings['userid']
U_PWD settings['u_pwd']
HOST settings['host']
PORT settings['port']
MAXROWS settings['maxrows']

Variables in %OData Statements

To use local Jupyter/Python variables in a notebook, all you need to do is place braces {} around the name of the variable. Before we illustrate this, we need to create another connection (since we just dropped it in the last example). Fortunately, none of the settings have been removed, so we still have the connection information (DATABASE, SCHEMA, ...) available.

In the event you have closed the notebook and started up from scratch, there is no need to do a full connect command (or prompt). The settings are automatically written to disk and then restored when you start up another session. If you want to connect to another database then you will need to use the following SET statement.


In [14]:
%odata set DATABASE {settings['database']} SCHEMA {settings['schema']}

And this command will show the connection service being created for us.


In [15]:
u = %odata -e select * from employee limit 1


Creating Service Request for OData Connection
Service URL: http://localhost:9080/ODataOne/createService
Request Header
{
    "Content-Type": "application/json",
    "Accept": "application/json"
}
Connection Parameters
{
    "database": "SAMPLE",
    "host": "localhost",
    "port": "50000",
    "db2AdminUser": "JLP",
    "db2AdminPassword": "********",
    "db2ServiceUser": "JLP",
    "db2ServicePassword": "********",
    "ssl": false,
    "schema": "JLP",
    "tablenames": [
        "EMPLOYEE"
    ]
}
SELECT Command
URL  : http://localhost:9080/ODataOne/ODataService/SAMPLE-6a90088a5aef458d943c94780eca99d5
OData: /EMPLOYEES?$top=1&$format=json
BIRTHDATE BONUS COMM EDLEVEL EMPNO FIRSTNME HIREDATE JOB LASTNAME MIDINIT PHONENO SALARY SEX WORKDEPT
0 1963-08-24 1000 4220 18 10 CHRISTINE 1995-01-01 PRES HAAS I 3978 152750 F A00

Retrieving URL, OData Command, and Parameters

The %odata command will return the URL command for a select statement as part of the command:

<url> = %odata -e select * from employee limit 1
The variable "url" will contain the full URL required to retrieve data from the OData service. The next command illustrates how this works. You must use the echo (-e) option to get the URL returned. Note that you cannot use this syntax with the %%odata version of the command.


In [16]:
url = %odata -e select * from employee limit 1


SELECT Command
URL  : http://localhost:9080/ODataOne/ODataService/SAMPLE-6a90088a5aef458d943c94780eca99d5
OData: /EMPLOYEES?$top=1&$format=json
BIRTHDATE BONUS COMM EDLEVEL EMPNO FIRSTNME HIREDATE JOB LASTNAME MIDINIT PHONENO SALARY SEX WORKDEPT
0 1963-08-24 1000 4220 18 10 CHRISTINE 1995-01-01 PRES HAAS I 3978 152750 F A00

You can use this URL to directly access the results through a browser, or any application that can read the results returned by the OData gateway. The print statement below will display the URL as an active link. Click on that to see the results in another browser window.


In [17]:
print(url)


http://localhost:9080/ODataOne/ODataService/SAMPLE-6a90088a5aef458d943c94780eca99d5/EMPLOYEES?$top=1&$format=json

When a URL is generated, we need to append the \$format=json tag at the end to tell the OData service and the browser how to handle the results. When we run OData and RESTful calls from a programming language (like Python), we are able to send information in the header which tells the API how to handle the results and parameters. All of the RESTful calls to the OData gateway use the following header information:

  {
  "Content-Type":"application/json",
  "Accept":"application/json"
  }

When we send the URL to the OData gateway, it needs to be told how to return the information. We need to append the $format=json flag at the end of our query when sending the request via a browser. Note that the ampersand must be appended to the end of the existing URL since we already have one parameter in it.

JSON DIsplay in Firefox

Depending on what version of Firefox you have, you may not get the JSON to be displayed very nicely. To use the built-in JSON formatter, issue the following commands in a separate browser window:

about:config
Search for devtools.jsonview.enabled

Right click on the jsonview setting and enable it. This will result in the JSON being easier to view.

SQL Command Syntax

The %odata command has been designed to translate the SQL syntax for INSERT, DELETE, UPDATE, and SELECT into an equivalent OData format. There are very specific ways of requesting data from OData, so this ends up placing some limitations on what SQL you can use. This section will cover the four major SQL commands and how they can be used with OData. If you need the syntax for a particular SQL command, just enter the command name by itself on the %odata line and it will give you a brief summary of the syntax. Here is the DELETE help.


In [18]:
%odata delete


DELETE Command Syntax


The DELETE command will delete one record from the table (it does not do a searched delete). The keycolumn in the WHERE clause must have a primary key/index associated with it. If a primary index does not match this column, or the index does not exist, the DELETE will not work.

DELETE FROM <table> WHERE keycolumn=value
       

SELECT Statements

The SELECT statement is the most complicated of the four statements that are allowed in OData. There are generally two forms that can be used when accessing a record. The first method uses the primary key of the table and it requires no arguments. Note that the examples will not show the URL that points to the OData service.

/EMPLOYEES('000010')
The second method is to use the \$filter query option. \$filter allows us to compare any column against a value. The equivalent OData statement for retrieving an individual employee is:

/EMPLOYEES?$filter=EMPNO eq '000010'
The generated SELECT statements will always use this format, rather than relying on a primary key. This becomes more important when we deal with Views.

SELECT Syntax

The SELECT command will return data from one table. There is no ability to join tables with the current implementation of OData. If you do want to join tables, you may want to create a VIEW on the DB2 system and then use that as the TABLE. This will allow for SELECT, but no INSERT/DELETE/UPDATE.

You do not need to use the primary key in the WHERE clause to use this statement. By default, any results will be displayed in a table. If you want to retrieve the results as JSON records, use the -j option on the %odata command.

SELECT \[col1, col2, ... | count(\*)\] FROM <table> \[ WHERE logic\] \[ LIMIT rows \]
The column list can contain as many values as you want, or just COUNT(*). COUNT(*) will return the count of rows found. If you use the -r or -j flags to display everything in JSON format, you will also get the entire answer set along with the row count. This is the behavior of using count in OData.

The FROM clause must contain the name of the table you want to access.

The WHERE clause is optional, as is the LIMIT clause. The WHERE clause can contain comparisons between columns and constants (EMPNO='000010'), logic (AND, OR) as well as LIKE clauses (COLUMN LIKE 'xxx'). The current version cannot use arithmetic operators (+, -, *, /) or the NOT operator.

The LIMIT clause will restrict the results to "x" number of rows. So even if there are 500 rows that meet the answer set, only "x" rows will be returned to the client.

Example: Select statement with no logic

The following SELECT statement will retrieve all of the data from the EMPLOYEE table.


In [19]:
s = %odata -e SELECT * FROM EMPLOYEE


SELECT Command
URL  : http://localhost:9080/ODataOne/ODataService/SAMPLE-6a90088a5aef458d943c94780eca99d5
OData: /EMPLOYEES?$format=json
BIRTHDATE BONUS COMM EDLEVEL EMPNO FIRSTNME HIREDATE JOB LASTNAME MIDINIT PHONENO SALARY SEX WORKDEPT
0 1963-08-24 1000 4220 18 10 CHRISTINE 1995-01-01 PRES HAAS I 3978 152750 F A00
1 1978-02-02 800 3300 18 20 MICHAEL 2003-10-10 MANAGER THOMPSON L 3476 94250 M B01
2 1971-05-11 800 3060 20 30 SALLY 2005-04-05 MANAGER KWAN A 4738 98250 F C01
3 1955-09-15 800 3214 16 50 JOHN 1979-08-17 MANAGER GEYER B 6789 80175 M E01
4 1975-07-07 500 2580 16 60 IRVING 2003-09-14 MANAGER STERN F 6423 72250 M D11
... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
38 1966-03-28 500 2100 17 200280 EILEEN 1997-03-24 OPERATOR SCHWARTZ R 8997 46250 F E11
39 1961-04-21 300 1272 12 200310 MICHELLE 1994-09-12 OPERATOR SPRINGER F 3332 35900 F E11
40 1971-07-18 500 2030 14 200330 HELENA 2006-02-23 FIELDREP WONG 2103 35370 F E21
41 1956-05-17 500 1907 16 200340 ROY 1997-07-05 FIELDREP ALONZO R 5698 31840 M E21
42 1956-05-16 500 1907 16 999999 ROY 1997-07-04 FIELDREP ALONZO R 5698 31840 M E21

43 rows × 14 columns

You will notice that not all of the rows have been displayed. The output has been limited to 10 lines. 5 lines from the start of the answer set and 5 lines from the bottom of the answer set are displayed. If you want to change the maximum number of rows to be displayed, use the MAXROWS setting.


In [20]:
%odata set maxrows 10

If you want an unlimited number of rows returned, set maxrows to -1.


In [21]:
%odata set maxrows -1
%odata select * from employee


BIRTHDATE BONUS COMM EDLEVEL EMPNO FIRSTNME HIREDATE JOB LASTNAME MIDINIT PHONENO SALARY SEX WORKDEPT
0 1963-08-24 1000 4220 18 10 CHRISTINE 1995-01-01 PRES HAAS I 3978 152750 F A00
1 1978-02-02 800 3300 18 20 MICHAEL 2003-10-10 MANAGER THOMPSON L 3476 94250 M B01
2 1971-05-11 800 3060 20 30 SALLY 2005-04-05 MANAGER KWAN A 4738 98250 F C01
3 1955-09-15 800 3214 16 50 JOHN 1979-08-17 MANAGER GEYER B 6789 80175 M E01
4 1975-07-07 500 2580 16 60 IRVING 2003-09-14 MANAGER STERN F 6423 72250 M D11
5 2003-05-26 700 2893 16 70 EVA 2005-09-30 MANAGER PULASKI D 7831 96170 F D21
6 1971-05-15 600 2380 16 90 EILEEN 2000-08-15 MANAGER HENDERSON W 5498 89750 F E11
7 1980-12-18 500 2092 14 100 THEODORE 2000-06-19 MANAGER SPENSER Q 972 86150 M E21
8 1959-11-05 900 3720 19 110 VINCENZO 1988-05-16 SALESREP LUCCHESSI G 3490 66500 M A00
9 1972-10-18 600 2340 14 120 SEAN 1993-12-05 CLERK O`CONNELL 2167 49250 M A00
10 1955-09-15 500 1904 16 130 DELORES 2001-07-28 ANALYST QUINTANA M 4578 73800 F C01
11 1976-01-19 600 2274 18 140 HEATHER 2006-12-15 ANALYST NICHOLLS A 1793 68420 F C01
12 1977-05-17 500 2022 16 150 BRUCE 2002-02-12 DESIGNER ADAMSON 4510 55280 M D11
13 1980-04-12 400 1780 17 160 ELIZABETH 2006-10-11 DESIGNER PIANKA R 3782 62250 F D11
14 1981-01-05 500 1974 16 170 MASATOSHI 1999-09-15 DESIGNER YOSHIMURA J 2890 44680 M D11
15 1979-02-21 500 1707 17 180 MARILYN 2003-07-07 DESIGNER SCOUTTEN S 1682 51340 F D11
16 1982-06-25 400 1636 16 190 JAMES 2004-07-26 DESIGNER WALKER H 2986 50450 M D11
17 1971-05-29 600 2217 16 200 DAVID 2002-03-03 DESIGNER BROWN 4501 57740 M D11
18 2003-02-23 400 1462 17 210 WILLIAM 1998-04-11 DESIGNER JONES T 942 68270 M D11
19 1978-03-19 600 2387 18 220 JENNIFER 1998-08-29 DESIGNER LUTZ K 672 49840 F D11
20 1980-05-30 400 1774 14 230 JAMES 1996-11-21 CLERK JEFFERSON J 2094 42180 M D21
21 2002-03-31 600 2301 17 240 SALVATORE 2004-12-05 CLERK MARINO M 3780 48760 M D21
22 1969-11-12 400 1534 15 250 DANIEL 1999-10-30 CLERK SMITH S 961 49180 M D21
23 1976-10-05 300 1380 16 260 SYBIL 2005-09-11 CLERK JOHNSON P 8953 47250 F D21
24 2003-05-26 500 2190 15 270 MARIA 2006-09-30 CLERK PEREZ L 9001 37380 F D21
25 1976-03-28 500 2100 17 280 ETHEL 1997-03-24 OPERATOR SCHNEIDER R 8997 36250 F E11
26 1985-07-09 300 1227 12 290 JOHN 2006-05-30 OPERATOR PARKER R 4502 35340 M E11
27 1976-10-27 400 1420 14 300 PHILIP 2002-06-19 OPERATOR SMITH X 2095 37750 M E11
28 1961-04-21 300 1272 12 310 MAUDE 1994-09-12 OPERATOR SETRIGHT F 3332 35900 F E11
29 1962-08-11 400 1596 16 320 RAMLAL 1995-07-07 FIELDREP MEHTA V 9990 39950 M E21
30 1971-07-18 500 2030 14 330 WING 2006-02-23 FIELDREP LEE 2103 45370 M E21
31 1956-05-17 500 1907 16 340 JASON 1977-05-05 FIELDREP GOUNOT R 5698 43840 M E21
32 1973-08-14 1000 4220 18 200010 DIAN 1995-01-01 SALESREP HEMMINGER J 3978 46500 F A00
33 1972-10-18 600 2340 14 200120 GREG 2002-05-05 CLERK ORLANDO 2167 39250 M A00
34 1976-01-19 600 2274 18 200140 KIM 2006-12-15 ANALYST NATZ N 1793 68420 F C01
35 1981-01-05 500 1974 16 200170 KIYOSHI 2005-09-15 DESIGNER YAMAMOTO 2890 64680 M D11
36 1978-03-19 600 2387 18 200220 REBA 2005-08-29 DESIGNER JOHN K 672 69840 F D11
37 1984-03-31 600 2301 17 200240 ROBERT 2004-12-05 CLERK MONTEVERDE M 3780 37760 M D21
38 1966-03-28 500 2100 17 200280 EILEEN 1997-03-24 OPERATOR SCHWARTZ R 8997 46250 F E11
39 1961-04-21 300 1272 12 200310 MICHELLE 1994-09-12 OPERATOR SPRINGER F 3332 35900 F E11
40 1971-07-18 500 2030 14 200330 HELENA 2006-02-23 FIELDREP WONG 2103 35370 F E21
41 1956-05-17 500 1907 16 200340 ROY 1997-07-05 FIELDREP ALONZO R 5698 31840 M E21
42 1956-05-16 500 1907 16 999999 ROY 1997-07-04 FIELDREP ALONZO R 5698 31840 M E21

It is better to limit the results from the answer set by using the LIMIT clause in the SELECT statement. LIMIT will force DB2 to stop retrieving rows after "x" number have been read, while the MAXROWS setting will retrieve all rows and then only display a portion of them. The one advantage of MAXROWS is that you see the bottom 5 rows while you would only be able to do that with DB2 if you could reverse sort the output. The current OData implementation does not have the ability to $orderby, so sorting to reverse the output is not possible.


In [22]:
%odata set maxrows 10

Example: Select statement limiting output to 5 rows

This SELECT statement will limit output to 5 rows. If MAXROWS was set to a smaller value, it would still read all rows before displaying them.


In [23]:
s = %odata -e SELECT * FROM EMPLOYEE LIMIT 5


SELECT Command
URL  : http://localhost:9080/ODataOne/ODataService/SAMPLE-6a90088a5aef458d943c94780eca99d5
OData: /EMPLOYEES?$top=5&$format=json
BIRTHDATE BONUS COMM EDLEVEL EMPNO FIRSTNME HIREDATE JOB LASTNAME MIDINIT PHONENO SALARY SEX WORKDEPT
0 1963-08-24 1000 4220 18 10 CHRISTINE 1995-01-01 PRES HAAS I 3978 152750 F A00
1 1978-02-02 800 3300 18 20 MICHAEL 2003-10-10 MANAGER THOMPSON L 3476 94250 M B01
2 1971-05-11 800 3060 20 30 SALLY 2005-04-05 MANAGER KWAN A 4738 98250 F C01
3 1955-09-15 800 3214 16 50 JOHN 1979-08-17 MANAGER GEYER B 6789 80175 M E01
4 1975-07-07 500 2580 16 60 IRVING 2003-09-14 MANAGER STERN F 6423 72250 M D11

Selecting Columns to Display

OData allows you to select which columns to display as part of the output. The $select query option requires a list of columns to be passed to it. For instance, the following SQL will only display the first name and last name of the top five employees.

Example: Limiting the columns to display

The column list must only include columns from the table and cannot include any calculations like SALARY+BONUS.


In [24]:
s = %odata -e SELECT FIRSTNME, LASTNAME FROM EMPLOYEE LIMIT 5


SELECT Command
URL  : http://localhost:9080/ODataOne/ODataService/SAMPLE-6a90088a5aef458d943c94780eca99d5
OData: /EMPLOYEES?$select=FIRSTNME,LASTNAME&$top=5&$format=json
FIRSTNME LASTNAME
0 CHRISTINE HAAS
1 MICHAEL THOMPSON
2 SALLY KWAN
3 JOHN GEYER
4 IRVING STERN

The COUNT(*) function is available as part of a SELECT list and it cannot include any other column names. If you do include other column names they will be ignored.


In [25]:
s = %odata -e SELECT COUNT(*) FROM EMPLOYEE


SELECT Command
URL  : http://localhost:9080/ODataOne/ODataService/SAMPLE-6a90088a5aef458d943c94780eca99d5
OData: /EMPLOYEES?$count=true&$format=json
43 rows found.

One of the unusual behaviors of the COUNT(*) function is that will actually return the entire answer set under the covers. The %odata command strips the count out from the results and doesn't display the rows returned. That is probably not would you expect from this syntax! The COUNT function is better described as the count of physical rows returned. Here is the same example with 5 rows returned and the JSON records.


In [26]:
s = %odata -e -r SELECT COUNT(*) FROM EMPLOYEE LIMIT 5


SELECT Command
URL  : http://localhost:9080/ODataOne/ODataService/SAMPLE-6a90088a5aef458d943c94780eca99d5
OData: /EMPLOYEES?$top=5&$count=true&$format=json
{
    "@odata.context": "$metadata#EMPLOYEES",
    "@odata.count": 5,
    "value": [
        {
            "BIRTHDATE": "1963-08-24",
            "BONUS": 1000.0,
            "COMM": 4220.0,
            "EDLEVEL": 18,
            "EMPNO": "000010",
            "FIRSTNME": "CHRISTINE",
            "HIREDATE": "1995-01-01",
            "JOB": "PRES    ",
            "LASTNAME": "HAAS",
            "MIDINIT": "I",
            "PHONENO": "3978",
            "SALARY": 152750.0,
            "SEX": "F",
            "WORKDEPT": "A00"
        },
        {
            "BIRTHDATE": "1978-02-02",
            "BONUS": 800.0,
            "COMM": 3300.0,
            "EDLEVEL": 18,
            "EMPNO": "000020",
            "FIRSTNME": "MICHAEL",
            "HIREDATE": "2003-10-10",
            "JOB": "MANAGER ",
            "LASTNAME": "THOMPSON",
            "MIDINIT": "L",
            "PHONENO": "3476",
            "SALARY": 94250.0,
            "SEX": "M",
            "WORKDEPT": "B01"
        },
        {
            "BIRTHDATE": "1971-05-11",
            "BONUS": 800.0,
            "COMM": 3060.0,
            "EDLEVEL": 20,
            "EMPNO": "000030",
            "FIRSTNME": "SALLY",
            "HIREDATE": "2005-04-05",
            "JOB": "MANAGER ",
            "LASTNAME": "KWAN",
            "MIDINIT": "A",
            "PHONENO": "4738",
            "SALARY": 98250.0,
            "SEX": "F",
            "WORKDEPT": "C01"
        },
        {
            "BIRTHDATE": "1955-09-15",
            "BONUS": 800.0,
            "COMM": 3214.0,
            "EDLEVEL": 16,
            "EMPNO": "000050",
            "FIRSTNME": "JOHN",
            "HIREDATE": "1979-08-17",
            "JOB": "MANAGER ",
            "LASTNAME": "GEYER",
            "MIDINIT": "B",
            "PHONENO": "6789",
            "SALARY": 80175.0,
            "SEX": "M",
            "WORKDEPT": "E01"
        },
        {
            "BIRTHDATE": "1975-07-07",
            "BONUS": 500.0,
            "COMM": 2580.0,
            "EDLEVEL": 16,
            "EMPNO": "000060",
            "FIRSTNME": "IRVING",
            "HIREDATE": "2003-09-14",
            "JOB": "MANAGER ",
            "LASTNAME": "STERN",
            "MIDINIT": "F",
            "PHONENO": "6423",
            "SALARY": 72250.0,
            "SEX": "M",
            "WORKDEPT": "D11"
        }
    ]
}

One of the recommendations would be not to use the COUNT(*) function to determine the amount of rows that will be retrieved, especially if you expect there to a large of number rows. To minimize the data returned, you can use the form COUNT(column) which will modify the OData request to return the count and ONLY that column in the result set. This is a compromise in terms of the amount of data returned. This example using the -r (raw) flag which results in all of the JSON headers and data to be displayed. The JSON flag (-j) will not display any records.


In [27]:
s = %odata -e -r SELECT COUNT(EMPNO) FROM EMPLOYEE LIMIT 5


SELECT Command
URL  : http://localhost:9080/ODataOne/ODataService/SAMPLE-6a90088a5aef458d943c94780eca99d5
OData: /EMPLOYEES?$select=EMPNO&$top=5&$count=true&$format=json
{
    "@odata.context": "$metadata#EMPLOYEES(EMPNO)",
    "@odata.count": 5,
    "value": [
        {
            "EMPNO": "000010"
        },
        {
            "EMPNO": "000020"
        },
        {
            "EMPNO": "000030"
        },
        {
            "EMPNO": "000050"
        },
        {
            "EMPNO": "000060"
        }
    ]
}

FROM Clause

The FROM clause is mandatory in any SELECT statement. If an OData service has already been established, there will be no service request sent to OData. Instead, the URL information stored on disk will be used to establish the connection.

If a service has not been established, the %odata command will create the service and then build the OData select statement. If you want to see the command to establish the service as well as the SELECT command, use the -e flag to echo the results.

If the table does not exist in the database you will receive an error message.


In [28]:
%sql -q DROP TABLE UNKNOWN_TBL
%odata DROP TABLE UNKNOWN_TBL
s = %odata -e SELECT * FROM UNKNOWN_TBL


OData connection removed for JLP.UNKNOWN_TBL in Database SAMPLE

Creating Service Request for OData Connection
Service URL: http://localhost:9080/ODataOne/createService
Request Header
{
    "Content-Type": "application/json",
    "Accept": "application/json"
}
Connection Parameters
{
    "database": "SAMPLE",
    "host": "localhost",
    "port": "50000",
    "db2AdminUser": "JLP",
    "db2AdminPassword": "********",
    "db2ServiceUser": "JLP",
    "db2ServicePassword": "********",
    "ssl": false,
    "schema": "JLP",
    "tablenames": [
        "UNKNOWN_TBL"
    ]
}
SELECT Command
URL  : http://localhost:9080/ODataOne/ODataService/SAMPLE-d7d1e9bed03e4df99f2a6627be5baa8a
OData: /UNKNOWN_TBLS?$format=json

SQLException: com.ibm.db2.jcc.am.SqlSyntaxErrorException: DB2 SQL Error: SQLCODE=-204, SQLSTATE=42704, SQLERRMC=JLP.UNKNOWN_TBL, DRIVER=4.22.26

This actually can cause some issues if you try to reuse the connection information that was created with the UNKNOWN_TBL. Since the service could not determine the structure of the table, the service will not return any column information with a select statement. The next SQL statement will create the UNKNOWN_TBL.


In [29]:
%sql CREATE TABLE UNKNOWN_TBL AS (SELECT * FROM EMPLOYEE) WITH DATA


Command completed.

Retrying the SELECT statement will result in 43 rows with no columns returned!


In [30]:
s = %odata -e SELECT * FROM UNKNOWN_TBL


SELECT Command
URL  : http://localhost:9080/ODataOne/ODataService/SAMPLE-d7d1e9bed03e4df99f2a6627be5baa8a
OData: /UNKNOWN_TBLS?$format=json
0
1
2
3
4
...
38
39
40
41
42

43 rows × 0 columns

To correct this situation, you need to DROP the connect that the %odata program is using and reissue the SELECT statement.


In [31]:
%odata DROP TABLE UNKNOWN_TBL


OData connection removed for JLP.UNKNOWN_TBL in Database SAMPLE

Now you can try the SQL statement again.


In [32]:
s = %odata -e SELECT * FROM UNKNOWN_TBL


Creating Service Request for OData Connection
Service URL: http://localhost:9080/ODataOne/createService
Request Header
{
    "Content-Type": "application/json",
    "Accept": "application/json"
}
Connection Parameters
{
    "database": "SAMPLE",
    "host": "localhost",
    "port": "50000",
    "db2AdminUser": "JLP",
    "db2AdminPassword": "********",
    "db2ServiceUser": "JLP",
    "db2ServicePassword": "********",
    "ssl": false,
    "schema": "JLP",
    "tablenames": [
        "UNKNOWN_TBL"
    ]
}
SELECT Command
URL  : http://localhost:9080/ODataOne/ODataService/SAMPLE-98a8ed7eeffe42f0b57d87567d792875
OData: /UNKNOWN_TBLS?$format=json
BIRTHDATE BONUS COMM EDLEVEL EMPNO FIRSTNME HIREDATE JOB LASTNAME MIDINIT PHONENO SALARY SEX WORKDEPT
0 1963-08-24 1000 4220 18 10 CHRISTINE 1995-01-01 PRES HAAS I 3978 152750 F A00
1 1978-02-02 800 3300 18 20 MICHAEL 2003-10-10 MANAGER THOMPSON L 3476 94250 M B01
2 1971-05-11 800 3060 20 30 SALLY 2005-04-05 MANAGER KWAN A 4738 98250 F C01
3 1955-09-15 800 3214 16 50 JOHN 1979-08-17 MANAGER GEYER B 6789 80175 M E01
4 1975-07-07 500 2580 16 60 IRVING 2003-09-14 MANAGER STERN F 6423 72250 M D11
... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
38 1966-03-28 500 2100 17 200280 EILEEN 1997-03-24 OPERATOR SCHWARTZ R 8997 46250 F E11
39 1961-04-21 300 1272 12 200310 MICHELLE 1994-09-12 OPERATOR SPRINGER F 3332 35900 F E11
40 1971-07-18 500 2030 14 200330 HELENA 2006-02-23 FIELDREP WONG 2103 35370 F E21
41 1956-05-17 500 1907 16 200340 ROY 1997-07-05 FIELDREP ALONZO R 5698 31840 M E21
42 1956-05-16 500 1907 16 999999 ROY 1997-07-04 FIELDREP ALONZO R 5698 31840 M E21

43 rows × 14 columns

Describing the Table Contents

The SELECT statement needs to know what columns are going to be returned as part of the answer set. The asterix (*) returns all of the columns, but perhaps you only want a few of the columns. To determine what the columns are in the table along with the data types, you can use the DESCRIBE command. The following statement will show the structure of the EMPLOYEE table.


In [33]:
%odata DESCRIBE EMPLOYEE


(BIRTHDATE,BONUS,COMM,EDLEVEL,EMPNO,FIRSTNME,HIREDATE,JOB,LASTNAME,MIDINIT,PHONENO,SALARY,SEX,WORKDEPT)
COLUMN TYPE
0 BIRTHDATE Date
1 BONUS Decimal
2 COMM Decimal
3 EDLEVEL Int16
4 EMPNO String
... ... ...
9 MIDINIT String
10 PHONENO String
11 SALARY Decimal
12 SEX String
13 WORKDEPT String

14 rows × 2 columns

The datatypes are not the same as what one expect from a relational database. You get generic information on the character columns (String), and the numbers (Int16, Decimal). The Decimal specification actually contains the number of digits and decimal places but that isn't returned when using the table display.

Data Type Contents
Binary Binary data
Boolean Binary-valued logic
Byte Unsigned 8-bit integer
Date Date without a time-zone offset
Decimal Numeric values with fixed precision and scale
Double IEEE 754 binary64 floating-point number (15-17 decimal digits)
Duration Signed duration in days, hours, minutes, and (sub)seconds
Guid 16-byte (128-bit) unique identifier
Int16 Signed 16-bit integer
Int32 Signed 32-bit integer
Int64 Signed 64-bit integer
SByte Signed 8-bit integer
Single IEEE 754 binary32 floating-point number (6-9 decimal digits)
String Sequence of UTF-8 characters
TimeOfDay Clock time 00:00-23:59:59.999999999999

WHERE Clause

The WHERE clause is used to filter out the rows that you want to retrieve from the table. The WHERE clause allows the following operators:

  • >, =>, <, <=, =, !=, <>, LIKE
  • AND, OR
  • Parenthesis to override order () of operators

The WHERE clause does not allow for mathematical operators at this time (*, -, +, /) or the unary NOT or "-" operators.

The LIKE clause can contain the special % character, but the equivalent OData syntax always searches the entire string and does not anchor at the beginning of the string. What this means is that the LIKE clause will turn into a search of the entire string whether you use the % character in your search string or not.

Example: Single comparison

The following select statement will search for employees who have a salary less than 40000.


In [34]:
s = %odata -e SELECT EMPNO, WORKDEPT, SALARY FROM EMPLOYEE WHERE SALARY < 40000


SELECT Command
URL  : http://localhost:9080/ODataOne/ODataService/SAMPLE-6a90088a5aef458d943c94780eca99d5
OData: /EMPLOYEES?$select=EMPNO,WORKDEPT,SALARY&$filter=SALARY lt 40000&$format=json
EMPNO SALARY WORKDEPT
0 270 37380 D21
1 280 36250 E11
2 290 35340 E11
3 300 37750 E11
4 310 35900 E11
... ... ... ...
7 200240 37760 D21
8 200310 35900 E11
9 200330 35370 E21
10 200340 31840 E21
11 999999 31840 E21

12 rows × 3 columns

Example: Two comparisons in a WHERE clause

We add an additional comparison to our SQL to check for only employees in a particular department.


In [35]:
s = %odata -e SELECT EMPNO, WORKDEPT, SALARY FROM EMPLOYEE WHERE SALARY < 40000 AND WORKDEPT = 'E21'


SELECT Command
URL  : http://localhost:9080/ODataOne/ODataService/SAMPLE-6a90088a5aef458d943c94780eca99d5
OData: /EMPLOYEES?$select=EMPNO,WORKDEPT,SALARY&$filter=SALARY lt 40000 and WORKDEPT eq 'E21'&$format=json
EMPNO SALARY WORKDEPT
0 320 39950 E21
1 200330 35370 E21
2 200340 31840 E21
3 999999 31840 E21

Example: OR Logic in the WHERE clause

We add some additional complexity by requesting employees who are in department E11 as well as those who make less than 40000 and work in department E21.


In [36]:
s = %odata -e \
SELECT EMPNO, WORKDEPT, SALARY \
       FROM EMPLOYEE \
WHERE SALARY < 40000 AND WORKDEPT = 'E21' OR WORKDEPT = 'E11'


SELECT Command
URL  : http://localhost:9080/ODataOne/ODataService/SAMPLE-6a90088a5aef458d943c94780eca99d5
OData: /EMPLOYEES?$select=EMPNO,WORKDEPT,SALARY&$filter=SALARY lt 40000 and WORKDEPT eq 'E21' or WORKDEPT eq 'E11'&$format=json
EMPNO SALARY WORKDEPT
0 90 89750 E11
1 280 36250 E11
2 290 35340 E11
3 300 37750 E11
4 310 35900 E11
... ... ... ...
6 200280 46250 E11
7 200310 35900 E11
8 200330 35370 E21
9 200340 31840 E21
10 999999 31840 E21

11 rows × 3 columns

Example: Overriding the order of comparisons

You can override the order of comparisons in the WHERE clause by using parenthesis. Here we are asking for employees in department E21 or E11 and have a salary less than 40000.


In [37]:
s = %odata -e \
SELECT EMPNO, WORKDEPT, SALARY \
       FROM EMPLOYEE \
WHERE SALARY < 40000 AND (WORKDEPT = 'E21' OR WORKDEPT = 'E11')


SELECT Command
URL  : http://localhost:9080/ODataOne/ODataService/SAMPLE-6a90088a5aef458d943c94780eca99d5
OData: /EMPLOYEES?$select=EMPNO,WORKDEPT,SALARY&$filter=SALARY lt 40000 and (WORKDEPT eq 'E21' or WORKDEPT eq 'E11')&$format=json
EMPNO SALARY WORKDEPT
0 280 36250 E11
1 290 35340 E11
2 300 37750 E11
3 310 35900 E11
4 320 39950 E21
5 200310 35900 E11
6 200330 35370 E21
7 200340 31840 E21
8 999999 31840 E21

Example: Using a LIKE clause

The LIKE clause in DB2 will look for a string within a character column. Normally the LIKE statement will allow for the use of the % (wildcard) and _ (one character match) operators to look for patterns. These special characters do not exist in OData, so the %odata command will remove the % character and convert it to an equivalent OData statement. What this means is that the string search will look at the entire string for the pattern, while LIKE can be anchored to look only at the beginning of the string. This capability does not current exist with the current OData implementation.

Example: Search for a lastname that has 'AA' in it.

This SQL will look for a lastname that has the string 'AA' in it.


In [38]:
s = %odata -e SELECT LASTNAME FROM EMPLOYEE WHERE LASTNAME LIKE '%AA%'


SELECT Command
URL  : http://localhost:9080/ODataOne/ODataService/SAMPLE-6a90088a5aef458d943c94780eca99d5
OData: /EMPLOYEES?$select=LASTNAME&$filter=contains(LASTNAME,'AA')&$format=json
LASTNAME
0 HAAS

In SQL, you can search for a name that ends with the letters ON by using LIKE '%ON'


In [39]:
%sql SELECT LASTNAME FROM EMPLOYEE WHERE LASTNAME LIKE '%ON'


LASTNAME
0 THOMPSON
1 HENDERSON
2 ADAMSON
3 JEFFERSON
4 JOHNSON

Converting to OData will mean that the search will look across the entire string, not just the beginning.


In [40]:
s = %odata -e SELECT LASTNAME FROM EMPLOYEE WHERE LASTNAME LIKE '%ON'


SELECT Command
URL  : http://localhost:9080/ODataOne/ODataService/SAMPLE-6a90088a5aef458d943c94780eca99d5
OData: /EMPLOYEES?$select=LASTNAME&$filter=contains(LASTNAME,'ON')&$format=json
LASTNAME
0 THOMPSON
1 HENDERSON
2 O`CONNELL
3 ADAMSON
4 JONES
... ...
6 JOHNSON
7 MONTEVERDE
8 WONG
9 ALONZO
10 ALONZO

11 rows × 1 columns

Limit Clause

The LIMIT clause was discussed earlier in this notebook. LIMIT allows you to reduce the amount of rows that are returned in the answer set. The LIMIT clause is similar to FETCH FIRST x ROWS ONLY in DB2. The rows are always taken from the beginning of the answer set so there is no way to skip "x" rows before getting results. The facility does exist in the OData spaecification but has not been implemented in this release.

The LIMIT clause also works in conjunction with the %odata command. The default number of rows that are displayed in a table (result set) is set to 10 by default. So if you have 50 rows in your answer set, the first 5 are displayed and then the last 5 with the rows inbetween are hidden from view. If you want to see the entire answer set, you need to change the MAXROWS value to -1:

%odata SET MAXROWS -1
This will display all rows that are returned from the answer set. However, the number of rows actually returned in the anwer set will be determined by the LIMIT clause. If you set LIMIT 5 then only five rows will be returned no matter what MAXROWS is set to. On the other hand, if you set MAXROWS to 10 and LIMIT to 20, you will get 20 rows returned but only 10 will be displayed.

Example: Limit result to 5 rows

This SQL will retrieve only the top 5 rows of the EMPLOYEE table.


In [41]:
s = %odata -e SELECT * FROM EMPLOYEE LIMIT 5


SELECT Command
URL  : http://localhost:9080/ODataOne/ODataService/SAMPLE-6a90088a5aef458d943c94780eca99d5
OData: /EMPLOYEES?$top=5&$format=json
BIRTHDATE BONUS COMM EDLEVEL EMPNO FIRSTNME HIREDATE JOB LASTNAME MIDINIT PHONENO SALARY SEX WORKDEPT
0 1963-08-24 1000 4220 18 10 CHRISTINE 1995-01-01 PRES HAAS I 3978 152750 F A00
1 1978-02-02 800 3300 18 20 MICHAEL 2003-10-10 MANAGER THOMPSON L 3476 94250 M B01
2 1971-05-11 800 3060 20 30 SALLY 2005-04-05 MANAGER KWAN A 4738 98250 F C01
3 1955-09-15 800 3214 16 50 JOHN 1979-08-17 MANAGER GEYER B 6789 80175 M E01
4 1975-07-07 500 2580 16 60 IRVING 2003-09-14 MANAGER STERN F 6423 72250 M D11

INSERT Command

OData allows you to insert data into a table through the use of the RESTful POST command and a JSON document that contains the field names and contents of those fields.

The format of the INSERT command is:

INSERT INTO <table>(col1, col2, ...) VALUES (val1, val2, ...)
The TABLE must be defined before you can issue this statement. There is no requirement to have a primary key on the table, but this will prevent you from updating it with the OData interface because filtering (WHERE) is not allowed on UPDATEs or DELETEs. The column list and value list must match (i.e. there must be a value for every column name). If you do not supply the list of all columns in the table, the missing columns will have null values assigned to them. The insert will fail if any of these missing columns requires a value (NOT NULL).

Example: Insert into a table

In this example we will insert a single row into a table. We start by defining the table within DB2 and then doing a DESCRIBE to get the column definitions back with OData.


In [42]:
%%sql -q 
DROP TABLE TESTODATA; 
CREATE TABLE TESTODATA 
  ( 
  EMPNO INT NOT NULL, 
  LASTNAME VARCHAR(10) NOT NULL,
  SALARY INT NOT NULL,
  BONUS INT
  );


Command completed.

We also need to remove the connection information from the system in the event we've run this example before.


In [43]:
%odata DROP TABLE TESTODATA


OData connection removed for JLP.TESTODATA in Database SAMPLE

A couple of things about the table design. The salary is NOT NULL, while the BONUS allows for nulls. Unfortunately, the DESCRIBE command only tells us about the columns in the table and their OData data type, and no indication of whether table.


In [44]:
%odata DESCRIBE TESTODATA


(BONUS,EMPNO,LASTNAME,SALARY)
COLUMN TYPE
0 BONUS Int32
1 EMPNO Int32
2 LASTNAME String
3 SALARY Int32

The initial INSERT will populate the table with valid data. The echo option will show the json document that is sent via the POST command to OData to insert the row.


In [45]:
%odata -e INSERT INTO TESTODATA(EMPNO, LASTNAME, SALARY, BONUS) VALUES (1,'Fred',10000,1000)


INSERT Command
URL  : http://localhost:9080/ODataOne/ODataService/SAMPLE-4f7152a2349e4686a5e3fb620fe130e4
OData: {'EMPNO': 1, 'LASTNAME': 'Fred', 'SALARY': 10000, 'BONUS': 1000}
Parmeters
{
    "EMPNO": 1,
    "LASTNAME": "Fred",
    "SALARY": 10000,
    "BONUS": 1000
}

Record inserted.

Just to make sure things were inserted properly, we retrieve the contents of the table.


In [46]:
%odata SELECT * FROM TESTODATA


BONUS EMPNO LASTNAME SALARY
0 1000 1 Fred 10000

OData (and DB2) will return an error message about our missing SALARY column which requires a value.


In [47]:
%odata -e INSERT INTO TESTODATA(EMPNO, LASTNAME, BONUS) VALUES (2,'Wilma',50000)


INSERT Command
URL  : http://localhost:9080/ODataOne/ODataService/SAMPLE-4f7152a2349e4686a5e3fb620fe130e4
OData: {'EMPNO': 2, 'LASTNAME': 'Wilma', 'BONUS': 50000}
Parmeters
{
    "EMPNO": 2,
    "LASTNAME": "Wilma",
    "BONUS": 50000
}

SQLException: com.ibm.db2.jcc.am.SqlIntegrityConstraintViolationException: DB2 SQL Error: SQLCODE=-407, SQLSTATE=23502, SQLERRMC=TBSPACEID=2, TABLEID=72, COLNO=2, DRIVER=4.22.26

We can try this on the DB2 side as well to get the details of the error.


In [48]:
%sql INSERT INTO TESTODATA(EMPNO, LASTNAME, BONUS) VALUES (2,'Wilma',50000)


SQL0407N Assignment of a NULL value to a NOT NULL column "TBSPACEID=2, TABLEID=72, COLNO=2" is not allowed. SQLSTATE=23502 SQLCODE=-407

DELETE Command

The DELETE command only takes one parameter and that is the key value for the record that we want to delete from the table. The format of the command is:

DELETE FROM <table> WHERE KEY=VALUE
Key refers to the column that is the primary key in the table we are deleting from. Unless you have a primary key, the DELETE command will not work.


In [49]:
%odata -e DELETE FROM TESTODATA WHERE EMPNO=1


The TESTODATA table does not have a key that we can use to find a row.

A primary key is required to issue a DELETE command. You also need to make sure that the primary key column does not contain NULLs because a primary key must always contain a value. The following SQL tries to fix the primary key issue.


In [50]:
%sql ALTER TABLE TESTODATA ADD CONSTRAINT PKTD PRIMARY KEY (EMPNO)


Command completed.

Check to see if we can delete the row yet.


In [51]:
%odata -e DELETE FROM TESTODATA WHERE EMPNO=1


The TESTODATA table does not have a key that we can use to find a row.

Adding a primary key after the fact won't help because the service URL would have already recorded the information about the table (and the fact it didn't have a primary key at the time). We need to drop our SERVICE URL and generate another one.


In [52]:
%odata DROP TABLE TESTODATA


OData connection removed for JLP.TESTODATA in Database SAMPLE

We do a describe on the table and this will force another service URL to be generated for us.


In [53]:
%odata DESCRIBE TESTODATA


(EMPNO,EMPNO,LASTNAME,SALARY)
COLUMN TYPE
0 EMPNO Int32
1 EMPNO Int32
2 LASTNAME String
3 SALARY Int32

Trying the DELETE this time will work.


In [54]:
%odata -e DELETE FROM TESTODATA WHERE EMPNO=1


OData Delete
/TESTODATAS(1)

Record Deleted.

Deleting the record again still gives you a successful return code. The call always returns a successful status even if the record doesn't exist.


In [55]:
%odata -e DELETE FROM TESTODATA WHERE EMPNO=2


OData Delete
/TESTODATAS(2)

Record Deleted.

UPDATE Command

The update command requires both a primary key to update and the name of the field that you want changed. Note that you can only change one field at a time. There is no ability to specify multiple fields at this time.

The format of the UDPATE command is:

UPDATE <table> SET column=value WHERE key=keyvalue
You must have a primary key on the table if you want an update to work. The filtering (WHERE) is allowed only to specify the primary key for the row and no filtering is allowed. The primary can be changed in the statement, but the update will fail if the key already exists in another record. THe other restriction is that no calculations can be done as part of the SET clause. You can only pass atomic values to the UPDATE statement.

Example: Update a BONUS value of employee

This SQL will update employee number 1 bonus to 2000. The first step is to put the employee back into the table.


In [56]:
%odata -e \
INSERT INTO TESTODATA(EMPNO, LASTNAME, SALARY, BONUS) \
VALUES (1,'Fred',10000,1000)


INSERT Command
URL  : http://localhost:9080/ODataOne/ODataService/SAMPLE-81162ef4522a4015b8a8fb36085af308
OData: {'EMPNO': 1, 'LASTNAME': 'Fred', 'SALARY': 10000, 'BONUS': 1000}
Parmeters
{
    "EMPNO": 1,
    "LASTNAME": "Fred",
    "SALARY": 10000,
    "BONUS": 1000
}

Record inserted.

At this point we can update their salary.


In [57]:
%odata -e UPDATE TESTODATA SET BONUS=2000 WHERE EMPNO=1


UPDATE Command
URL  : http://localhost:9080/ODataOne/ODataService/SAMPLE-81162ef4522a4015b8a8fb36085af308
OData: /TESTODATAS(1)
Parmeters
{
    "BONUS": 2000
}

Record Updated.

We doublecheck the results to make sure we got it right!


In [58]:
%odata SELECT * FROM TESTODATA


BONUS EMPNO LASTNAME SALARY
0 2000 1 Fred 10000

Views

The OData implemented with DB2 doesn't allow for JOINS between tables. Sometimes you need to be able to look up information from another table in order to get the final result. One option you have to do this is to create a VIEW on the DB2 system.

The VIEW can contain almost any type of SQL so it allows for very complex queries to be created. For instance, the following view joins the EMPLOYEE table and the DEPARTMENT table to generate a row with the employee name and the name of the department that they work for.


In [59]:
%%sql
CREATE OR REPLACE VIEW EMPDEPT AS 
  (
  SELECT LASTNAME, DEPTNAME 
  FROM EMPLOYEE E, DEPARTMENT D
  WHERE E.WORKDEPT = D.DEPTNO
  )


Command completed.

We also need to drop any service connection you may have created in the past with this table name.


In [60]:
%odata DROP TABLE EMPDEPT


OData connection removed for JLP.EMPDEPT in Database SAMPLE

Now that we have created the view, we can retrieve rows from it just like a standard table.


In [61]:
%odata SELECT LASTNAME, DEPTNAME FROM EMPDEPT LIMIT 5


DEPTNAME LASTNAME
0 SPIFFY COMPUTER SERVICE DIV. HAAS
1 SPIFFY COMPUTER SERVICE DIV. ORLANDO
2 SPIFFY COMPUTER SERVICE DIV. HEMMINGER
3 SPIFFY COMPUTER SERVICE DIV. O`CONNELL
4 SPIFFY COMPUTER SERVICE DIV. LUCCHESSI

You can also create sophisticated VIEWS that can take parameters to adjust the results returned. For instance, consider the following SQL statement which gives me count of employees that work in SYSTEMS departments.


In [62]:
%%sql
SELECT 
  COUNT(*)
FROM 
  EMPLOYEE E, DEPARTMENT D
WHERE 
  E.WORKDEPT = D.DEPTNO
  AND D.DEPTNAME LIKE '%SYSTEMS%'


1
0 18

There are two departments with the name SYSTEMS in them, but there is no easy way to create a view for every possible combination of searches that you may want. Instead what we do is create a table that contains the pattern we want to look for and create the view so that it references this table.

The first step is to create our PATTERN table. Note we make sure it has a primary key so our OData update calls can change it!


In [63]:
%%sql -q
DROP TABLE PATTERN;
CREATE TABLE PATTERN
  (
  PATTERN_NUMBER INT NOT NULL PRIMARY KEY,
  SEARCH VARCHAR(16)
  );


Command completed.

Now we create a view that access this PATTERN table to do the actual search. Note that values that are inserted into the PATTERN table must have the SQL special characters like % to make sure patterns can be anywhere in the string.


In [64]:
%odata DROP TABLE EMPDEPT
%odata DROP TABLE PATTERN


OData connection removed for JLP.EMPDEPT in Database SAMPLE

OData connection removed for JLP.PATTERN in Database SAMPLE


In [65]:
%%sql
CREATE OR REPLACE VIEW EMPDEPT AS 
  (
  SELECT 
    COUNT(*) AS COUNT
  FROM 
    EMPLOYEE E, DEPARTMENT D
  WHERE 
    E.WORKDEPT = D.DEPTNO
    AND D.DEPTNAME LIKE
        (
        SELECT SEARCH FROM PATTERN WHERE PATTERN_NUMBER=1
        )
  );


Command completed.

In order for our view to work properly, we must populate our PATTERN table with a value. To test the view we will use %SYSTEMS% as our first example.


In [66]:
%sql INSERT INTO PATTERN VALUES(1,'%SYSTEMS%')


Command completed.

And now we can test our view by selecting from it.


In [67]:
%sql SELECT * FROM EMPDEPT


COUNT
0 18

Now that we have it working, we can try exactly the same thing but with OData. Our first transaction will update the search key to SERVICE.


In [68]:
%odata UPDATE PATTERN SET SEARCH = '%SERVICE%' WHERE PATTERN_NUMBER = 1


Record Updated.

The next OData statement should select the count of employees working in service departments.


In [69]:
%odata SELECT * FROM EMPDEPT


COUNT
0 6

Summary

The OData DB2 gateway removes much of the complexity of communicating with the database. There are no drivers required, no configuration file, nor any administration required on the client that is communicating with the database. All communication is done using RESTful API calls, which are available on all browsers and all operating systems. The calls to the database are replaced with standard POST, GET, DELETE, PUT and PATCH requests. Enabling OData support to DB2 opens up the type of applications that you can write and clients that you can connect to DB2 with.

Credits: IBM 2017, George Baklarz [baklarz@ca.ibm.com]