254
RDM Server 8.4 SQL User's Guide

RDMs 8.4 SQL User's Guide

Embed Size (px)

Citation preview

Page 1: RDMs 8.4 SQL User's Guide

RDM Server 8.4

SQL User's Guide

Page 2: RDMs 8.4 SQL User's Guide

Trademarks

Raima®, Raima Database Manager®, RDM®, RDM Embedded® and RDM Server® are trademarks of Raima Inc. and may beregistered in the United States of America and/or other countries. All other names referenced herein may be trademarks oftheir respective owners.

This guide may contain links to third-party Web sites that are not under the control of Raima Inc. and Raima Inc. is notresponsible for the content on any linked site. If you access a third-party Web site mentioned in this guide, you do so at yourown risk. Inclusion of any links does not imply Raima Inc. endorsement or acceptance of the content of those third-party sites.

Page 3: RDMs 8.4 SQL User's Guide

Contents

SQL User Guide i

Contents

Contents i1. Introduction 11.1 Overview of Supported SQL Features 11.2 About This Manual 2

2. A Language for Describing a Language 33. A Simple Interactive SQL Scripting Utility 54. RDM Server SQL Language Elements 64.1 Identifiers 64. 2 Reserved Words 64. 3 Constants 8Numeric Constants 8String Constants 9Date, Time, and Timestamp Constants 9System Constants 10

5. Administrating an SQL Database 115.1 Device Administration 115.2 User Administration 125.3 Database and File Maintenance 135.3.1 Database Initialization 135.3.2 Extension Files 145.3.3 Flushing Inmemory Database Files 155.3.4 SQL Optimization Statistics 15

5.4 Security Logging 165.5 Miscellaneous Administrative Functions 165.5.1 Login/Logout Procedures 165.5.2 RDM Server Console Notifications 17

6. Defining a Database 196.1 Create Database 216.2 Create File 226.3 Create Table 246.3.1 Table Declarations 256.3.2 Table Column Declarations 26Data Types 26Default and Auto-Incremented Values 27Column Constraints 28

6.3.3 Table Constraint Declarations 306.3.4 Primary and Foreign Key Relationships 316.3.5 System-Assigned Primary Key Values 34

Page 4: RDMs 8.4 SQL User's Guide

Contents

SQL User Guide ii

6.4 Create Index 356.5 Create Join 386.6 Compiling an SQL DDL Specification 416.7 Modifying an SQL DDL Specification 426.7.1 Adding Tables, Indexes, Joins to a Database 426.7.2 Dropping Tables and Indexes from a Database 426.7.3 Altering Databases and Tables 436.7.4 Schema Versions 44

6.8 Example SQL DDL Specifications 456.8.1 Sales and Inventory Databases 456.8.2 Antiquarian Bookshop Database 486.8.3 National Science Foundation Awards Database 52

6.9 Database Instances 546.9.1 Creating a Database Instance 556.9.2 Using Database Instances 556.9.3 Stored Procedures and Views 566.9.4 Drop Database Instance 576.9.5 Restrictions 57

7. Retrieving Data from a Database 597.1 Simple Queries 597.2 Conditional Row Retrieval 607.2.1 Retrieving Data from a Range 627.2.2 Retrieving Data from a List 627.2.3 Retrieving Data by Wildcard Checking 637.2.4 Retrieving Rows by Rowid 63

7.3 Retrieving Data from Multiple Tables 657.3.1 Old Style Join Specifications 65Inner Joins 65Outer Joins 66Correlation Names 69Column Aliases 70

7.3.2 Extended Join Specifications 707.4 Sorting the Rows of the Result Set 757.5 Retrieving Computational Results 777.5.1 Simple Expressions 787.5.2 Built-in (Scalar) Functions 797.5.3 Conditional Column Selection 827.5.4 Formatting Column Expression Result Values 83

7.6 Performing Aggregate (Grouped) Calculations 857.7 String Expressions 927.8 Nested Queries (Subqueries) 93

Page 5: RDMs 8.4 SQL User's Guide

Contents

SQL User Guide iii

7.8.1 Single-Value Subqueries 947.8.2 Multi-Valued Subqueries 957.8.3 Correlated Subqueries 977.8.4 Existence Check Subqueries 98

7.9 Using Temporary Tables to Hold Intermediate Results 997.10 Other Select Statement Features 1007.11 Unions of Two or More Select Statements 1017.11.1 Specifying Unions 1027.11.2 Union Examples 102

8. Inserting, Updating, and Deleting Data in a Database 1058.1 Transactions 1058.1.1 Transaction Start 1058.1.2 Transaction Commit 1068.1.3 Transaction Savepoint 1068.1.4 Transaction Rollback 106

8.2 Inserting Data 1078.2.1 Insert Values 1078.2.2 Insert from Select 1088.2.3 Importing Data into a Table 1108.2.4 Exporting Data from a Table 112

8.3 Updating Data 1168.4 Deleting Data 117

9. Database Triggers 1199.1 Trigger Specification 1199.2 Trigger Execution 1209.3 Trigger Security 1219.4 Trigger Examples 1229.5 Accessing Trigger Definitions 126

10. Shared (Multi-User) Database Access 12810.1 Locking in SQL 12910.1.1 Row-Level Locking 12910.1.2 Table-Level Locking 13010.1.3 Lock Timeouts and Deadlock 130

10.2 Transaction Modes 13111. Stored Procedures and Views 13311.1 Stored Procedures 13311.1.1 Create a Stored Procedure 13311.1.2 Call (Execute) a Stored Procedure 134

11.2 Views 13511.2.1 Create View 13511.2.2 Retrieving Data from a View 136

Page 6: RDMs 8.4 SQL User's Guide

Contents

SQL User Guide iv

11.2.3 Updateable Views 13711.2.4 Drop View 13711.2.5 Views and Database Security 138

12. SQL Database Access Security 13912.1 Command Access Privileges 13912.1.1 Grant Command Access Privileges 13912.1.2 Revoke Command Access Privileges 140

12.2 Database Access Privileges 14112.2.1 Grant Table Access Privileges 14112.2.2 Revoke Table Access Privileges 142

13. Using SQL in a C Application Program 14413.1 Overview of the RDM Server SQL API 14513.2 Programming Guidelines 14813.3 ODBC API Usage Elements 15213.3.1 Header Files 15213.3.2 Data Types 15313.3.3 Use of Handles 15413.3.4 Buffer Arguments 154

13.4 SQL C Application Development 15413.4.1 RDM Server SQL and ODBC 15413.4.2 Connecting to RDM Server 15513.4.3 Basic SQL Statement Processing 15613.4.4 Using Parameter Markers 15613.4.4 Premature Statement Termination 15813.4.5 Retrieving Date/Time Values 15913.4.6 Retrieving Decimal Values 15913.4.7 Retrieving Decimal Data 16013.4.8 Status and Error Handling 16013.4.9 Select Statement Processing 16213.4.10 Positioned Update and Delete 166

13.5 Using Cursors and Bookmarks 16813.5.1 Using Cursors 168Rowset 168Types of Cursors 168

13.5.2 Static Cursors 168Using Static Cursors 169Limitations on Static Cursors 169

13.5.3 Using Bookmarks 170Activate a Bookmark 170Turn Off a Bookmark 170Retrieve a Bookmark 170

Page 7: RDMs 8.4 SQL User's Guide

Contents

SQL User Guide v

Return to a Bookmark 17113.5.4 Retrieving Blob Data 171

14. Developing SQL Server Extensions 17514.1 User-Defined Functions (UDF) 17514.1.1 UDF Implementation 177UDF Module Header Files 177Function udfDescribeFcns 178SQL Data VALUE Container Description 180Function udfInit 181Function udfCheck 182Function udfFunc 184Function udfReset 186Function udfCleanup 186

14.1.2 Using a UDF as a Trigger 18714.1.3 Invoking a UDF 195Calling an Aggregate UDF 196Calling a Scalar UDF 196

14.1.4 UDF Support Library 19714.2 User-Defined Procedures 19714.2.1 UDP Implementation 198Function udpDescribeFcns 198Function ModInit 200Function udpInit 201Function udpCheck 203Function udpExecute 203Function udpMoreResults 205Function udpColData 205Function udpCleanup 207Function ModCleanup 208

14.2.2 Calling a UDP 20814.3 Login or Logout UDP Example 20914.4 Transaction Triggers 21214.4.1 Transaction Trigger Registration 21214.4.2 Transaction Trigger Implementation 214

15. Query Optimization 218Overview of the Query Optimization Process 218Cost-Based Optimization 221Update Statistics 222Restriction Factors 222

Table Access Methods 224Sequential File Scan 224

Page 8: RDMs 8.4 SQL User's Guide

Contents

SQL User Guide vi

Direct Access Retrieval 225Indexed Access Retrieval 225Index Scan 226Primary To Foreign Key Join 227Foreign To Primary Key Join 227Foreign Thru Indexed/Rowid Primary Key Predefined Join 228

Optimizable Expressions 229How the Optimizer Determines the Access Plan 232Selecting Among Alternative Access Methods 232Selecting the Access Order 232

Sorting and Grouping 234Returning the Number of Rows in a Table 236Select * From Table 236

Query Construction Guidelines 236User Control Over Optimizer Behavior 237User-Specified Expression Restriction Factor 237User-Specified Index 237Optimizer Iteration Threshold (OptLimit) 238Enabling Automatic Insertion of Redundant Conditionals 238

Checking Optimizer Results 238Retrieving the Execution Plan (SQLShowPlan) 238Using the SqlDebug Configuration Parameter 240

Limitations 245Optimization of View References 245Merge-Scan Join Operation is Not Supported 246Subquery Transformation (Flattening) Unsupported 246

Page 9: RDMs 8.4 SQL User's Guide

1. Introduction

SQL User Guide 1

1. IntroductionThe RDM Server SQL User's Guide is provided in order to instruct application developers in how to build C applications thatuse the RDM Server SQL database language. Those developers that have SQL experience will find much information herewith which they are familiar. Moreover, while this guide is not intended to provide complete training on the use of SQL, itdoes give sufficient information for the novice SQL programmer to get a good start on RDM Server SQL programming.

Other SQL-related RDM Server documentation includes:

SQL Language Reference A complete description of the SQL language and statements provided in RDM Server.SQL C API Reference Descriptions of all SQL-related C application programming interface (API) functions.ODBC User's Guide Describes the use of ODBC with RDM Server SQL.JDBC User's Guide Describes the use of the RDM Server JDBC API.ADO.NET User's Guide Describes the use of the RDM Server ADO.NET API.

1.1 Overview of Supported SQL Features

RDM Server supports a subset of the ISO/IEC 9075 2003 SQL standard including referential integrity and column constraintchecks as wells as extensions that provide transparent network model database support and full relational access to combinedmodel databases. Specific RDM Server SQL features include the following.

l Full automatic referential integrity checking.l Automatic checking of column and table constraints that conform to the SQL standard column and table constraint fea-tures.

l Support for b-tree and hash indexes. Support for optional indexes that can be activated on-demand is also provided.l Ability to specify high-performance, pre-defined joins using the proprietary create join DDL statement. Used with for-eign and primary key specifications to indicate that direct access methods are to be used in maintaining inter-table rela-tionships.

l Support for the definition of standard SQL triggers.l Searched and positioned update and delete used in conjunction with the RDM Server SQL ODBC API.l Support for date, time, and timestamp data types.l A full complement of built-in scalar functions that include math, string, and date manipulation capabilities.l Support for null column values.l Data insertion statements. RDM Server SQL provides the insert values statement to insert a single row into a specifiedtable. Your application can use the insert from select statement to insert one or more rows from one table into another.The insert from file statement can be used to perform a bulk load from data contained in an ASCII text file.

l Support for select statements including group by, order by, subqueries, unions, and extended join syntax spe-cification.

l Support for the database security through standard grant and revoke statements.l Full transaction processing capabilities, including the capability for partial rollbacks.l Ability to create multiple instances of the same database schema.l Capability to define and access C structure and array columns manipulated using the RDM Server Core API (d_ prefixfunctions).

l A cost-based query optimizer that uses data distribution statistics to generate query execution plans based on use ofindexes, predefined joins, and direct access.

Page 10: RDMs 8.4 SQL User's Guide

1. Introduction

SQL User Guide 2

l Support for user-defined functions (UDF) that can be used in SQL statements. UDFs are extension modules that imple-ment scalar and/or aggregate functions. You can extend the SQL functionality of the server, for example, by writing afunction that does bitwise operations, or a function that performs an aggregate calculation (e.g., standard deviation) notprovided in the built-in functions.

l Support for stored procedures written in SQL and user-defined procedures (UDP) written in C that execute on the data-base server.

1.2 About This Manual

The RDM Server User's Guide is organized into the following sections.

l Chapter 2, "A Language for Describing a Language" describes the "meta-language" that is used to represent SQL state-ment syntax.

l Chapter 3, "A Simple Interactive SQL Scripting Utility" introduces a simple, command-line utility called "rsql" thatcan be used to interactively execute RDM Server SQL statements. We encourage you to use it to execute for yourselfmany of the SQL examples provided in this document.

l Chapter 4, "Administrating an SQL Database" provides descriptions of the SQL statements that can be used to performa variety of administration functions such as creating and dropping users and devices.

l Chapter 5, "Defining a Database" explains how to create an SQL database definition (called a schema) using SQL data-base definition language statements. Also described is how one goes about making changes to an existing databasedefinition that contains data. The SQL DDL specifications for the example databases used throughout this manual areprovided as well.

l Chapter 6, "Retrieving Data from a Database" provides descriptions all of the query capabilities available in the RDMServer SQL select statement as well as how to specify a union of two or more select statements. It also describes howyou can predefine a specific query using the create view statement.

l Chapter 7, "Inserting, Updating, and Deleting Data in a Database" explains the use of the SQL insert, update, anddelete statements.

l Chapter 8, "Database Triggers" provides a detailed description of how to implement database triggers in which pre-determined database actions can be automatically "triggered" whenever certain database modifications occur.

l Chapter 9, "Transactions and Concurrent Database Access" describes the important features of RDM Server SQL thatcan be controlled/used to manage concurrent access to a database from multiple users in order to balance high accessperformance with the need to guarantee the integrity of the database data (through transactions).

l Chapter 10, "Writing and Using Stored Procedures" shows you how to develop SQL stored procedures which encap-sulate one or more SQL statements in a single, parameterized procedure. Stored procedures are pre-compiled thus avoid-ing having to recompile the statements each time they need to be executed.

l Chapter 11, "Establishing SQL User Access Rights" explains the use of the SQL grant and revoke statements in orderto restrict access to portions of the database or restrict the use of certain SQL commands for specific users.

l Chapter 12, "Using SQL in a C Application Program" provides detailed guidelines on how to write an RDM ServerSQL application in the C programming language using the SQL C API functions (based on ODBC with several non-ODBC extensions also provided).

l Chapter 13, "Developing SQL Server Extensions" contains how-to guidelines for writing C-language server extensionsfor use by SQL. These include user-defined functions (UDF), user-defined procedures (UDP), user-defined import/-export filters (IEF), login/logout procedures, and transaction triggers.

l Chapter 14, "Query Optimization" provides a detailed description of how the RDM Server SQL query optimizerdetermines the "best" way to execute a particular query. Don't skip this chapter! Writing efficient and correct selectstatements is not always easy to do. Moreover, the "optimizer" is also not as smart as that particular designation maylead you to think. The more you understand how queries are optimized, the better able you will be to not only createquality queries but also to figure out why certain queries do not work quite the way you thought they should.

Page 11: RDMs 8.4 SQL User's Guide

2. A Language for Describing a Language

SQL User Guide 3

2. A Language for Describing a LanguageSQL stands for "Structured Query Language." You have probably seen many different methods used in programming manualsto show how to use a specific programming language. The two most common methods use syntax flow diagrams and what isknown as Backus-Naur Form (BNF) which is a formal language for describing a programming language. In this document weuse a simplified BNF method that seeks to represent the language in a way that closely matches the way you will code yourown SQL statements for your application.

For example, the following select statement:

select sale_name, company, city, statefrom salesperson natural join customer;

can be described by this syntax rule:

select_stmt:select identifier [, identifier]… from identifier [natural join identifier] ;

where "select_stmt" is the name of the rule (sometimes called a non-terminal); the bold-faced identifiers select, from, natural,and join are key words (sometimes called terminal symbols); identifier is like a function argument that stands in place of auser-specified value (technically, it too is the name of a rule that is matched by any user-specified value that begins with a let-ter followed by any sequence consisting of letters, digits, and the underscore ("_") character). Rule names are identifiers andtheir definitions are specified by giving the rule name beginning in column 1 and terminating the rule with a colon (":") asshown above.

There are also special meta-symbols that are part of the syntax descriptor language. Two are shown in the above select_stmtsyntax rule. The brackets ("[" and "]") enclose optional elements. The ellipsis ("…") specifies that the preceding item can berepeated zero or more times. Other meta-symbols include a vertical bar (i.e., an "or" symbol) that is used to separate alternativeelements and braces ("{" and "}") which enclose a set of alternatives from which one must always be matched. All other spe-cial characters (e.g., the "," and ";" in the select_stmt rule) are considered to be part of the language definition. Meta-symbolsthat are themselves part of the language will be enclosed in single quotes (e.g., '[') in the syntax rule.

Rule names can be used in other rules. For example, the syntax for a stored procedure that can contain multiple select state-ments could be described by the following rule:

create_proc:create procedure identifier as

select_stmt[; select_stmt]…end proc;

In order to make the syntax more readable, any non-bold, italicized name is considered to be matched as an identifier. Thus,the select_stmt rule can also be written as follows…

select_stmt:select colname [, colname]… from tabname [natural join tabname] ;

where colname represents identifiers that correspond to table column names and tabname represents identifiers that cor-respond to table names.

Some italicized terms are used to match specific text patterns. E.g., number matches any text pattern that can be used to rep-resent a number (either integer or decimal) and integer matches any pattern that represents an integer number.

These rules are summarized in the table below.

Page 12: RDMs 8.4 SQL User's Guide

2. A Language for Describing a Language

SQL User Guide 4

Syntax Element Description

keyword Bold-faced words that identify the special words used in the language that specify actions andusage. Sometimes called reserved words. Examples, select, insert, create, using.

identifier Italicized word corresponding to an identifier: sequences of letters, digits, and "_" that beginwith a letter.

number Any text that corresponds to an integer or decimal number.

integer Any text that corresponds to an integer.

[option1 | option2] A selection in which either nothing or option1 or option2 is specified.

{option1 | option2} Either option1 or option2 must be specified.

element… Repeat element zero or more times.

identifier Normal-faced identifiers correspond to the names of syntax rules. Syntax rules are defined by thename starting in column 1 and ending with a ":".

Table 2-1. Syntax Description Language Elements

Text for programming and SQL examples is shown in courier font in a shaded box as in the following example.

RSQL Utility - RDM Server 8.4.1 [22-Mar-2012]A Raima Database Manager UtilityCopyright (c) 1992-2012 Raima Inc.. All Rights Reserved.

Enter ? for list of interface commands.

001 rsql: .c 1 p admin secretConnected to RDM Server Version 8.4.1 [22-Mar-2012]*** using statement handle 1 of connection 1001 rsql: select * from salesperson;

sale_id sale_name dob commission regionBCK Kennedy, Bob 1956-10-29 0.075 0BNF Flores, Bob 1943-07-17 0.100 0BPS Stouffer, Bill 1952-11-21 0.080 2CMB Blades, Chris 1958-09-08 0.080 3DLL Lister, Dave 1999-08-30 0.075 3ERW Wyman, Eliska 1959-05-18 0.075 1GAP Porter, Greg 1949-03-03 0.080 1GSN Nash, Gail 1954-10-20 0.070 3JTK Kirk, James 2100-08-30 0.075 3SKM McGuire, Sidney 1947-12-02 0.070 1SSW Williams, Steve 1944-08-30 0.075 3SWR Robinson, Stephanie 1968-10-11 0.070 0WAJ Jones, Walter 1960-06-15 0.070 2WWW Warren, Wayne 1953-04-29 0.075 2002 rsql:

Page 13: RDMs 8.4 SQL User's Guide

3. A Simple Interactive SQL Scripting Utility

SQL User Guide 5

3. A Simple Interactive SQL Scripting UtilityOkay, we know that this is the world of point-and-click, easy-to-use applications. In fact, many abound for doing just thatwith SQL. So what value can there possibly be in providing a text-based, command-line-oriented, interactive SQL utility?Well, for one thing, you can keep both hands on the keyboard and never have to touch the mouse! Novel concept isn’t it? Italso has provided us here at Raima with something that was easy to write and is easily ported to any platform. Hence, theinterface works identically on all platforms. It also provides us (and, presumably, you as well) with the ability to generate testcases that can be easily and automatically executed. Since we also share the source code to the program, it allows you tomore easily see how to call the RDM Server SQL API functions without getting bogged down by object-oriented layers anduser-interface calls. There is an educational benefit as well. You will more effectively learn how to properly formulate SQLstatements by actually typing them in than by simply pointing to icons that do the job for you.

The name of this program is rsql (the standalone version is named rsqls). To start rsql, open an OS command window andenter a command that conforms to the following syntax. Note that an RDM Server that manages the SQL databases to beaccessed must be running and available.

rsql [-? | -h] [-B] [-V] [-e] [-u] [-c num] [-H num] [-s num] [-w num] [-l num][-b [@hostname:port]] startupfile [arg]…]

Table 3-1. RSQL Command Options

-h Display command usage information.-B Do not display program banner on startup.-V Display operating system version information.-e Do not echo commands contained in a script file.-u Display result set column headings in upper case.-c num Set maximum number of possible connections to num.-H num Set size of statement history list to num.-s num Set maximum number of statement handles per connection to num.-w num Set page width to num characters.-l num Set number of lines per display page to num.-o filename Output errors to filename.

startupfile [arg]… Name of text file containing startup rsql/SQL commands and any needed script file arguments (see .rcommand below).

Page 14: RDMs 8.4 SQL User's Guide

4. RDM Server SQL Language Elements

SQL User Guide 6

4. RDM Server SQL Language ElementsThis section defines all of the basic elements of RDM Server SQL that have been used throughout this User's Guide includingidentifiers, reserved words and constants.

4.1 Identifiers

Identifiers are used to name a wide variety of SQL language objects including databases, tables, columns, indexes, joins,devices, views, and stored procedures. An identifier is formed as a combination of letters, digits, and the underscore character('_'), always beginning with a letter or an underscore. An identifier in RDM server can be from 1 to 32 characters in length.Unless otherwise noted in the User's Guide, identifiers are case-insensitive (upper and lower case characters are indis-tinguishable). Thus, CUSTOMER, customer, and Customer all refer to the same item. Identifiers cannot be a reserved word(see below).

4. 2 Reserved Words

Reserved words are predefined identifiers that have special meaning in RDM Server SQL. As with identifiers, RDM ServerSQL does not distinguish between uppercase and lowercase letters in reserved words. Table 4-1 lists the RDM Server SQLreserved words. Some of the listed words are not described in this document but have been retained for compatibility withother SQL systems. Note none of the words listed in this table can be used in any context other than that indicated by the useof the word in the SQL grammar.

ABS COUNTS HAVING NAME SET

ACOS CREATE HEADINGS NATURAL SHARED

ACTONFAIL CROSS HOUR NEW SHORT

ADD CURDATE IF NEXT SHOW

ADMIN CURRENCY IFNULL NOINIT SIGN

ADMINISTRATOR CURRENT IGNORE NOINITIALIZE SIN

AFTER CURRENT_DATE IMPORT NON_VIRTUAL SMALLINT

AGE CURRENT_TIME IN NOSORT SOME

AGGREGATE CURRENT_TIMESTAMP

INDEX NOT SQRT

ALL CURTIME INIT NOTIFY START

ALTER C_DATA INITIALIZE NOW STATEMENT

AND DATA INMEMORY NULL SUBSTRING

ANY DATABASE INNER NULLIF SUM

AS DATABASES INSERT NUMERIC SWITCH

ASC DATE INSTANCE NUMRETRIES TABLE

ASCENDING DAYOFMONTH INT OBJECT TABLES

ASCII DAYOFWEEK INT16 OCTET_LENGTH TAN

ASIN DAYOFYEAR INT32 OF THEN

Table 4-1. RDM Server SQL Reserved Words

Page 15: RDMs 8.4 SQL User's Guide

4. RDM Server SQL Language Elements

SQL User Guide 7

ATAN DB_ADDR INT64 OFF THROUGH

ATAN2 DEACTIVATE INT8 OLD TIME

ATOMIC DEBUG INTEGER ON TIMESTAMP

AUTHORIZATION DEC INTO ONE TINYINT

AUTO DECIMAL IP ONLY TO

AUTOCOMMIT DEFAULT IPADDR OPEN TODAY

AUTOLOG DELETE IS OPTION TRAILING

AUTOSTART DESC ISOLATION OPTIONAL TRIGGER

AVG DESCENDING JOIN OPT_LIMIT TRIM

BEFORE DEVICE KEY OR TRUE

BEGIN DIAGNOSTICS LARGE ORDER TRUNCATE

BETWEEN DISABLE LAST OUTER TYPEOF

BIGINT DISPLAY LCASE OWNER UCASE

BINARY DISTINCT LEADING PAGESIZE UINT16

BIT DOUBLE LEFT PARAM UINT32

BLOB DROP LENGTH PARAMETER UINT64

BOOLEAN EACH LEVEL PI UINT8

BOTH ELSE LIKE POSITION UNICODE

BTREE ENABLE LN PRECISION UNION

BUT ENCRYPTION LOCALTIME PRIMARY UNIQUE

BY END LOCALTIMESTAMP PROC UNLOCK

BYTE ERRORS LOCATE PROCEDURE UNSIGNED

CALL ESCAPE LOCK PUBLIC UPDATE

CASCADE EXCLUSIVE LOG QUARTER UPPER

CASE EXEC LOGFILE RAND USE

CAST EXECUTE LOGGING REAL USER

CEIL EXISTS LOGIN REFERENCES USING

CEILING EXP LOGOUT REFERENCING VALUES

CHAR EXTENSION LONG REMOVE VARBINARY

CHARACTER FALSE LOWER REP VARBYTE

CHARACTER_LENGTH

FILE LTRIM REPEAT VARCHAR

CHAR_LENGTH FILTER MARK REPLACE VARYING

CHECK FIRST MASTER REVOKE VIRTUAL

CLOSE FLOAT MASTERALIAS RIGHT WAITSECONDS

COALESCE FLOOR MAX ROLLBACK WCHAR

COLUMN FLUSH MAXCACHESIZE ROUND WCHARACTER

COMMANDS FOR MAXPGS ROW WEEK

Page 16: RDMs 8.4 SQL User's Guide

4. RDM Server SQL Language Elements

SQL User Guide 8

COMMIT FOREIGN MAXTRANS ROWID WHEN

COMMITTED FROM MEMBER ROWS WHERE

COMPARE FULL MIN RTRIM WITH

CONCAT FUNCTION MINIMUM RUN WORK

CONVERT FUNCTIONS MINUTE SAVE WVARCHAR

COS GRANT MOD SAVEPOINT XML

COT GROUP MODE SECOND YEAR

COUNT HASH MONTH SELECT

4. 3 Constants

An RDM Server SQL constant is a number or string value that is used in a statement. The following sections describe how tospecify each type of constant value.

Numeric Constants

The RDM Server SQL numeric data types are smallint, integer, float, double, and decimal. Numeric constants are formed asspecified in the following syntax.

numeric_constant:[+|-]digits[.digits]

digits:d[d]...

d:0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9

If you specify a constant with a decimal portion (that is, [.digits]), RDM Server stores the constant as a decimal. If you do notuse the decimal part, the constant is stored as an integer.

The following examples show several types of numeric constants.

1021-503.14159453.75-81.75

Floating-point constants (data type real, float, or double) can be specified using as a numeric_constant or as an exponentialformed as specified below.

exponential_constant:[+|-]digits[.digits]{E|e}[+|-]ddd

Shown below are several examples of floating-point constants.

Page 17: RDMs 8.4 SQL User's Guide

4. RDM Server SQL Language Elements

SQL User Guide 9

6.02E231.8E5-3.776143e-12

String Constants

ASCII string constants are formed by enclosing the characters in the string inside single quotation marks ('string') or doublequotation marks ("string"). To form a wide character (Unicode) string constant, the initial quotation mark must be immediatelypreceded with "L". If the string itself contains quotation mark used to specify the string it must be immediately preceded by abackslash (\). To include a backslash character in the string, enter a double backslash (\\).

The following are examples of string constants.

"This is an ASCII string constant"L"This is a Unicode string constant""this string contains \"quotation\" marks"'this string contains "quotation" marks too''this string contains a backslash (\\)'

The default maximum length of an RDM Server SQL string constant is 256 characters. You can change this value by modi-fying the MaxString configuration parameter in the [SQL] section of rdmserver.ini. Refer to RDM Server Installation / Admin-istration Guide for more information.

Date, Time, and Timestamp Constants

The following syntax shows the formats for date, time, and timestamp constants.

date_constant:date "YYYY-MM-DD"

| @"[YY]YY-MM-DD"

time_constant:time "HH:MM[:SS[.dddd]]"

| @"HH:MM[:SS[.dddd]]"

timestamp_constant:timestamp "YYYY-MM-DD HH:MM[:SS[.dddd]]"

| @"YYYY-MM-DD [HH:MM[:SS[.dddd]]]"

The formats following the date, time, and timestamp keywords conform to the SQL standard. In the format for date constants,YYYY is the year (you must specify all four digits), MM is the month number (1 to 12), and DD is the day of the month (1 to31). The @ symbol represents a nonstandard alternative. When only two digits are specified for the year using the non-standard format, the century is assumed to be 1900 where YY is greater than or equal to 50; where YY is less than 50 in thisformat, the century is assumed to be 2000.

In the format for time constants, HH is hours (0 to 23), MM is minutes (0 to 59), SS is seconds (0 to 59), and .dddd is the frac-tional part of a second, with up to four decimal places of accuracy. If you specify more than four places, the value rounds tofour places. The format for timestamp constants simply combines the formats for date and time constants.

You can use three alternative characters as separators in declaring date, time, and timestamp constants. Besides hyphen ("-"),RDM Server accepts slash ("/") and period (".").

Page 18: RDMs 8.4 SQL User's Guide

4. RDM Server SQL Language Elements

SQL User Guide 10

The following are examples of the use of date, time, and timestamp constants.

insert into sales_order(ord_num, ord_date, amount)values(20001, @"93/9/23", 1550.00);

insert into notevalues("HI-PRI", timestamp "1993-9-23 15:22:00", "SKM", "SEA");

select * from sales_order where ord_date >= date "1993-9-1";

insert into event(event_id, event_time)values("Marathon", time "02:53:44.47");

The set date default statement, shown below, can be used to change the separator character and the order of month, day, andyear.

set_date_default:set {date default | default date} to {"MM-DD-YYYY" | "YYYY-MM-DD" | "DD-MM-YYYY"}

One of the three date format option must be specified exactly as shown except that the "-" separator can be any special char-acter you choose. This statement will set the date format for both input and output. Note that the specified separator characterwill be accepted for date constants as well as the built in characters hyphen, slash, and period.

System Constants

RDM Server SQL also recognizes three built-in literal constants as described in Table 4-2.

Constant Value

user The name of the user who is executing the state-ment.

today The current date at the execution time of the state-ment.

now The current timestamp at the execution time of thestatement.

Table 4-2. Literal System Constants

The following examples illustrate the use of the literal system constants.

.. a statement that could be executed from an extension module or

.. stored procedure that is always executed when a connection is made.insert into login_log(user_name, login_time) values(user, now);

.. check today's action itemsselect cust_id, note_text from action_items where tickle_date = today;

Page 19: RDMs 8.4 SQL User's Guide

5. Administrating an SQL Database

SQL User Guide 11

5. Administrating an SQL DatabaseThis chapter contains information pertinent to the administration of SQL databases. For complete RDM Server administrationdetails please refer to the RDM Server Installation and Administration Guide. Much of the capabilities described in thischapter have alternative methods. For example, users and devices can be defined outside of SQL through use of the rdsadminutility. However, it is often convenient (e.g., for regression testing, etc.) to be able to perform basic administrative actionsthrough SQL statements. Hence, RDM Server SQL includes a variety of administration related statements. Note that admin-istrator user privileges are required in order to use the SQL statements described below.

5.1 Device Administration

An RDM Server device specifies a logical name for a file system directory into which the server will manage database relatedfiles. A device can be created through SQL using the create device statement with the following syntax.

create_device:create [readonly] device devname as "directory_path"

This statement creates a device named devname with the specified directory_path which usually will be a fully-qualified pathname to an existing directory. Relative path names that are interpreted as being relative to the catalog directory as specifiedby the CATPATH environment variable can also be used as in the following (Windows) example.

create device importdev as ".\impdata";

It is important to repeat that the directory specified in the as clause must already exist. Otherwise the system will return error"invalid device: Illegal Physical Path for Device" error.

A readonly device is one in which the RDM Server managed files are only allowed to be read. Any attempt to write to a filecontained on a readonly device will result in an error.

Note that before any SQL DDL specification can be processed in order to define and use an SQL database, devices will needto be created for the directories that contain the DDL specification files and that will contain the created database files.

create device sqldev as "c:\rdms\sqlscripts";create device salesdb as ".\saledb";

Devices can be dropped but only when there are no RDM Server managed files contained in the directory associated with thedevice. The syntax for the drop device statement is very simple.

drop_device:drop device devname

Successful execution of this statement will drop the logical device named devname from the RDM Server system. The dir-ectory to which it refers, however, will remain as well as any, non RDM Server managed, files.

You can retrieve a list of all of the devices defined for the RDM server to which you are connected by executing the pre-defined stored procedure name ShowDevices as shown in the example below.

Page 20: RDMs 8.4 SQL User's Guide

5. Administrating an SQL Database

SQL User Guide 12

execute ShowDevices;

NAME TYPE PATHcatdev Read/Write .\emsamp Read/Write ..\examples\em\importdev Read/Write .\impdata\mlbdev Read/Write c:\aspen\mlbdb\mlbimpdev Read/Write c:\aspen\mlbdb\impfiles\rdsdll Read/Write ..\dll\nt_i\samples Read/Write ..\examples\tims.nt_i\sqldev Read/Write ..\sqldb.nt_i\sqlsamp Read/Write ..\examples\emsql\sysdev Read/Write ..\syslog.nt_i\

RDM Server manages disk space in such a way as to protect against a server shutdown in the event that needed external diskstorage requirements are not satisfied (i.e., the system runs out of disk space). When this happens, RDM Server will auto-matically switch into read-only mode until sufficient disk space is freed and made available to RDM Server. A minimum avail-able space attribute can be associated with all RDM Server devices that allows an administrator to have some control overthis low disk space system behavior. SQL provides the set device minimum statement in order to set the minimum number ofbytes of free space that must be available on every device in order for RDM Server to operate in its normal, read-write mode.The syntax for this statement is as follows.

set_device:set device minimum to nobytes

where nobytes is the number of free space that must be available on each RDM Server device.

Note that this overrides the MinFreeDiskSpace parameter in the [Engine] section of rdm-server.ini.

set device minimum to 100000000;

This example sets the device minimum free space threshold to 100 megabytes.

5.2 User Administration

The create user statement can be used to create a new RDM Server user that will allow a user with the specified name tologin into the RDM Server associated with the connection that is issuing the create user statement. The syntax for createuser is shown below.

create_user:create [admin[istrator]] user username password "password" [with encryption] on devname

The login name for the user is username and the login password is specified as "password" with devname as the user's defaultdevice (the device which will be used with SQL statements for which an optional on devname clause has been omitted). Thewith encryption option indicates that an encrypted form of the password is to be stored in the system catalog.

A username is a case-sensitive identifier so that "Sam", "SAM", and "sam" are three different user names.

Page 21: RDMs 8.4 SQL User's Guide

5. Administrating an SQL Database

SQL User Guide 13

Administrator users have full access rights to all databases and commands. The access rights for normal (non-administrator)users must be specified through use of the grant statement (see Chapter 11).

The create user statement can only be executed by administrator users.

The password for an existing user can be changed using the alter user statement.

alter_user:alter user username {authorization | password} "password"

Normal users can use this statement only to change their own password. Administrator users can use it to change the pass-word for any user.

Administrator users can remove a user from an RDM Server using the drop user statement:

drop_user:drop user username

IMPORTANT: RDM Server is delivered with some predefined users. Of particular importance is user"admin" with password "secret". We high recommend that this user be dropped (or at least the passwordchanged) once you have defined your own administrator users.

An administrator can get a list of the names of all users of an RDM Server by executing the pre-defined stored procedurenamed ShowUsers as shown in the following example.

create user randy password "RJ29j32r34s36k38" on sqldev;create admin user paul password "SaulOrPaul" with encryption on catdev;

exec ShowUsers;

USER_NAME RIGHTS HOME_DEVICEadmin Admin catdevguest Normal catdevpaul Admin catdevrandy Normal sqldevwayne Normal samples

5.3 Database and File Maintenance

5.3.1 Database Initialization

Administrators can initialize a database by issuing the following statement.

initialize_database:init[ialize] [database] dbname

Page 22: RDMs 8.4 SQL User's Guide

5. Administrating an SQL Database

SQL User Guide 14

Before initializing a database, the database must be closed by all users (including you) who currently have the databaseopened.

Execution of this statement is unrecoverable. The only way to restore the database is to restore from your last backup. If adatabase contains rows that are referenced from another database, initializing the referenced database will invalidate the ref-erential integrity between those databases.

5.3.2 Extension Files

Extension files can be created to allow database files to grow to sizes larger than can be accommodated in a single operatingsystem file. The feature was first added to RDM Server to overcome what was then the 2 gigabyte maximum size limitationfor files on some operating systems. As that may still be the case on some RDM Server OS installations, extension files arenecessary for those database files where the possible size limitation can be exceeded.

Extension files can also be used to partition the contents of a database file into multiple files contained on separate devices.The partitioning is defined based strictly on the specified maximum size for the data file (which can be set using the alter[extension] file statement) and the range of database addresses whose associated record occurrences are stored in a given file.

The syntax for the create extension file statement is given below.

create_extension_file:create extension file "extname" on extdev for "basename" on basedev

The name of the extension file is specified by extname and must be a legal, non-existent file name for the operating system onwhich RDM Server is installed. The extension file will be stored on the RDM Server device named extdev. This file will con-tain all data associated with the standard database file "basename" located on the device named basedev.

Device extdev can be the same as basedev as long as the extname is not the same as basename.

If more than one extension file is needed, you can issue as many create extension file statements on the basename as neces-sary. For example, the following statements create two extension files for file "sales.000" in the sales database.

create extension file "sales.0x0" on sqldev for "sales.000" on sqldev;create extension file "sales.0x1" on sqldev for "sales.000" on sqldev;

The alter [extension] file statement can be used to specify a variety of file sizing options for base and extension files as shownin the following syntax.

alter_file:alter [extension] file extno for "basename" on basedev

set {[maxsize=maxsize] | [cresize=cresize] | [extsize=extsize]}...

The specified file size settings apply to extension whose number is extno where an extno of 0 (zero) refers to the base fileitself. The maxsize option specifies the maximum file size in bytes. The cresize option specifies the initial size of the filewhen the file is first created. The extsize option specifies how much additional file space is to be allocated when data isadded to the end of the file. It is best that all of these values be integer multiples of the file's page size (see create file state-ment).

If maxsize is less than the current amount of allocated space on the file, or if the file is fully allocated to its current maximum,the request is denied. The new extsize value takes effect the next time the file is extended. The new cresize value is used thenext time the file is initialized.

Page 23: RDMs 8.4 SQL User's Guide

5. Administrating an SQL Database

SQL User Guide 15

You can use the maxsize value to control partitioning of the data among a set of extension files.

You can execute the ShowDBFiles predefined procedure to get a complete list of all of the files for a specified database as inthe example below.

exec ShowDBFiles("sales");

FILENO EXTNO DEVNAME FILENAME0 0 sqldev sales.0000 1 sqldev sales.0x00 2 sqldev sales.0x11 0 sqldev sales.0012 0 sqldev sales.0023 0 sqldev sales.0034 0 sqldev sales.0045 0 sqldev sales.0056 0 sqldev sales.006

5.3.3 Flushing Inmemory Database Files

RDM Server provides the ability for specific database files to be kept entirely in memory while a database is opened. This isparticularly important for files whose contents are accessed often and which need to have as fast a response time as possible.

RDM Server inmemory files can specified to be volatile (meaning that the file always starts empty), read (meaning that thedata is initially read from the file but no changes are ever written back), or persistent (meaning that the data file is completelyloaded when the database is first opened and all changes are written back to the database when the database is last closed).

For persistent (and even read) immemory files, it may be necessary for changes to those files to be written back to the data-base while the database remains open. The flush database statement provides the ability to do just that.

flush:flush [database] dbname[, dbname]...

This statement flushes the updated contents of the specified persistent or read inmemory files for the specified databases to thephysical database files. Note that use of the flush database statement is the only way in which changes made to inmemoryread files can be written to the database.

WARNING: The contents written to the database files with a flush database command are permanent andcannot be rolled back with a transaction rollback..

5.3.4 SQL Optimization Statistics

The RDM Server SQL query optimizer (see Chapter 14) utilizes data distribution statistics to assist its process of determiningthe best methods to use to efficiently execute a given select (or update/delete) statement. It is important that these statistics bekept up to date so that they provide a reasonable estimation of the characteristics of the data stored in the database. These stat-istics are generated from the current state of the database contents by executing the update statistics statement on the spe-cified database as follows.

update_stats:update {stats | statistics} on dbname

Page 24: RDMs 8.4 SQL User's Guide

5. Administrating an SQL Database

SQL User Guide 16

The statistics are collected and stored in the SQL system catalog by executing the update statistics statement. The histogramfor each column is collected from a sampling of the data files. The other statistics are maintained by the RDM Server runtimesystem.

The histogram for each column contains a sampling of values (25 by default, controlled by the rdmserver.ini fileOptHistoSize configuration parameter), and a count of the number of times that value was found from the sampled numberof rows (1000 by default, controlled by the rdmserver.ini file OptSampleSize configuration parameter). The sampledvalues are taken from rows evenly distributed throughout the table.

When update statistics has not been performed on a database, RDM Server SQL uses default values that assume each tablecontains 1000 rows. It is highly recommended that you always execute update statistics on every production database. Theexecution time for an update statistics statement is not excessive in RDM Server and does not vary significantly with the sizeof the database. Therefore, we suggest regular executions (possibly once per week or month, or following significant changesto the database).

5.4 Security Logging

RDM Server SQL provides the ability to log all grant and revoke statements that are issued. The SecurityLogging con-figuration parameter is used to activate (=1) or deactivate this feature (=0). SecurityLogging is by default disabled. Whenenabled, RDM Server SQL records the information associated with each grant and revoke statement that is successfullyexecuted in a row that is stored in the system catalog (syscat) table sysseclog along with a copy of the text of the commandbeing stored in table systext.

The following example shows the query can be used to display the security log showing when the command was issued, thename of the issuing user, and a copy of the grant/revoke statement.

select issued, user_name, txtln from sysseclog natural join systext;

ISSUED USER_NAME TXTLN2012-04-26 13:59:27.3160 admin grant all privileges on item to wayne;2012-04-26 13:59:16.5520 admin grant all privileges on product to wayne;2012-04-26 13:58:59.1110 admin grant all privileges on outlet to wayne;

5.5 Miscellaneous Administrative Functions

5.5.1 Login/Logout Procedures

Login/logout procedures are stored procedures that the SQL system calls automatically whenever a user connects to or dis-connects from a server. Two types of login/logout procedures are available:

l Public login/logout procedures are called whenever any user connects or disconnects with the server.l Private login/logout procedures are associated with particular users and are only called when those users connect or dis-connect.

If both a public and a private procedure have been defined for a user, both procedures are called; the public procedure iscalled before the private procedure.

Page 25: RDMs 8.4 SQL User's Guide

5. Administrating an SQL Database

SQL User Guide 17

Login/logout procedures cannot return a result set and cannot have arguments. They are typically used for setting user envir-onment values (e.g., display formats) or for performing specialized security functions. A login or logout procedure can be writ-ten either as a standard SQL stored procedure or as a C-based, user-defined procedure (UDP).

Login/logout procedures are registered using the set login procedure statement with the following syntax.

set_login_procedure:set {login | logout} proc[edure] for {public | username [, username ]...} to {procname | null}

The public option means that the login/logout procedure will be called whenever anyone logs in/out to/from the RDM Serverassociated with the connection on which this statement is executed. Otherwise, the username list identifies the specific usersto which the procedure applies.

Only one private login/logout procedure can be associated with a user. Hence, a subsequent set login/logout procedure callwill replace the previous one.

The following example creates a stored procedure called set_germany, which is to be used as a login procedure that definesthe user environment for German users.

create proc set_germany asset currency to "€";set date display(12, "yyyy mmm dd");set decimal to ",";set thousands to ".";set decimal display(20, "#.#,##' €'");

end proc;

The following statement registers the set_germany procedure as the login procedure for users Kurt, Wolfgang, Helmut, andWerner.

set login proc for "Kurt", "Wolfgang", "Helmut", "Werner" to set_germany;

The use of login/logout procedures can be enabled or disabled using the set login statement as follows.

set_login:set login [to | =] { on | off }

The effect of this statement is system-wide and will persist until the next set login is issued by this or another administratoruser. Use of login procedures is initially turned off.

5.5.2 RDM Server Console Notifications

The notify statement can be used to display a message on the RDM Server console. The syntax is shown below.

notify:notify {"message" | procvar | trigvar | ?}

A stored procedure variable (procvar) can be specified when the notify statement is executed within a stored procedure. A trig-ger variable (trigvar) that references an old or new row column value can be specified when the notify statement is executedwithin a trigger. If a parameter marker is specified, then the bound parameter value must be of type char or varchar.

The following example shows how the notify statement can be used in a trigger.

Page 26: RDMs 8.4 SQL User's Guide

5. Administrating an SQL Database

SQL User Guide 18

create trigger grade_watch before update of grade on coursereferencing old row as bc new row as ncfor each row

begin atomicnotify "Grade change made to: "notify bc.student_course

end;

Page 27: RDMs 8.4 SQL User's Guide

6. Defining a Database

SQL User Guide 19

6. Defining a DatabaseA poorly designed database can create all kinds of difficulties for the user of a database application. Unfortunately, the blamefor those difficulties are often laid at the feet of the database management system which, try as it might, simply cannot usenon-existent access paths to quickly get at the needed data. Good database design is as much of an art as it is engineering anda solid understanding of the application requirements is a necessary prerequisite. However, it is not the purpose of this doc-ument to teach you how to produce good database designs. But you do need to understand that designing a database is a com-plex task and that the quality of the application in which it is to be used is highly dependent on the quality of the databasedesign. If you are not experienced in designing databases then it is highly recommended that you first consult any number ofgood books on that subject before setting out to develop your RDM Server SQL database.

Information in a relational database is stored in tables. Each table is composed of columns that store a particular type ofinformation and rows that correspond to a particular record in the table. A simple but effective analogy can be made with afile cabinet as illustrated in Figure 6-1.

Figure 6-1. A File Cabinet is a Database

A file cabinet contains drawers. Each drawer contains a set of files organized around a common theme. For example, onedrawer might contain customer information while another drawer might contain vendor information. Each drawer holds indi-vidual file folders for each customer or vendor, sorted in customer or vendor name order. Each customer file contains specificinformation about the customer. The cabinet corresponds to a database, each drawer is like a table, and each folder is like arow in the table.

Typically, tables are viewed as shown in Figure 6-2, where the basic components of a database table are identified in anexample customer table. Each column of the table has a name that identifies the kind of information it contains. Each rowgives all of the information relating to a particular customer.

Figure 6-2. Definition of a "Table"

Suppose that you want to expand this example further and define a simple sales order database that, initially, keeps track ofsalespersons and their customers. Figure 6-3 shows how this information could be stored in the table.

Page 28: RDMs 8.4 SQL User's Guide

6. Defining a Database

SQL User Guide 20

Figure 6-3. Salesperson Accounts Table

There are columns for each salesperson's name and commission rate. Each salesperson has one or more customer accounts. Thecustomer's company name, city, and state are also stored with the data of the salesperson who services that customer's account.Note that the salesperson's name and commission are replicated in all of the rows that identify the salesperson's customers.Such duplicated data is called redundant data. One of the goals in designing a database is to minimize the amount of redund-ant data that must be stored in a database. A description on how this is done will be given below in section 6.3.4.

A database schema is the definition of what kind of data is to be stored and how that data is to be organized in the database.The Database Definition Language (DDL) consists of the SQL statements that are used to describe a particular databaseschema (also called the database definition). Five DDL statements are provided in RDM Server SQL: create database(schema), create file, create table, create index, and create join. The example below shows the RDM Server SQL DDL spe-cification that corresponds to the TIMS Core API database definition.

create database tims on sqldevdisable null valuesdisable references count;

create file tims_d1;create file tims_d2;create file tims_k1;create file tims_k2;

create table author(name char(31) primary key

) in tims_d2;create unique index author_key on author(name) in tims_k2;

create table info(id_code char(15) primary key,info_title char(79),publisher char(31),pub_date char(11),info_type smallint,name char(31) references author

) in tims_d2;create unique index info_key on info(id_code) in tims_k1;create join has_published order last on info(name);

create table borrower(myfriend char(31),date_borrowed date,date_returned date,id_code char(15) references info

) in tims_d2;create index borrower_key on borrower(myfriend) in tims_k2;create join loaned_books order last on borrower(id_code);

create table text(line char(79),id_code char(15) references info

) in tims_d2;

Page 29: RDMs 8.4 SQL User's Guide

6. Defining a Database

SQL User Guide 21

create join abstract order last on text(id_code);

create table keyword(word char(31) primary key

) in tims_d1;create unique index keyword_key on keyword(word) in tims_k2;

create table intersect(info_type smallint,id_code char(15) references info,word char(31) references keyword

) in tims_d1;create join key_to_info order last on intersect(word);create join info_to_key order last on intersect(id_code);

Detailed explanation for the use of each of the statements used in the above example are given in the flowing sections of thischapter. Section 6.1 explains the use of the create database (schema) statement which names the database that will bedefined by the DDL statements that follow it. The create file statement that can be used to define the files into which data-base data is stored is described in section 6.2. The create table statement, described in section 6.3, is used to define the char-acteristics of a table that will be stored in the database. The create index and create join statements are used to definemethods to quickly access database data and are described in section 6.4 and section 6.5, respectively. Instructions on how tocompile an SQL DDL specification follows in section 6.6. The kinds of changes that can be made to the schema of an exist-ing (and operational) database are described in section 6.7. Finally, the database definitions for the example databasesprovided with RDM Server are described in section 6.8.

6.1 Create Database

A complete DDL specification begins with a create database statement that conforms to the following syntax.

create_database:create {database | schema [authorization]} dbname db_attributes

| create {database | schema} dbname authorization username db_attributes

db_attributes:[pagesize bytes]

| slotsize {4 | 6 | 8}| on devname| [{enable | disable} null values]| [{enable | disable} reference count]

The name of the database to be created is specified by the dbname identifier which is case-insensitive meaning that "Sales","sales", and "SALES" all refer to the same database. The create schema form follows the SQL standard. If the authorizationusername clause is specified then the owner of the database will be the user named username. Otherwise the owner is the usersubmitting this statement.

The pagesize clause specifies that the default database file page size is to be set to the integer constant nobytes bytes. It isrecommended that this value be set to a multiple of the standard block size for the file system on which RDM Server is run-ning. The default page size is 1024.

The slotsize clause specifies the number of bytes to be used for the record (row) slot number used in an RDM Server databaseaddress. The slotsize defines the maximum number of rows that can be stored in a database file as the maximum unsigned 4,6, or 8 byte integer value. The default slotsize is 4.

Page 30: RDMs 8.4 SQL User's Guide

6. Defining a Database

SQL User Guide 22

The on clause is used to specify the default device on which the database files will be stored. The create file statement can beused to locate database files on separate devices if desired.

RDM Server SQL maintains in each table row a bitmap that keeps track of that row's null column values. The column value isnull when the bit in the bitmap associated with that column is set to 1. One byte is allocated for this bitmap for every 8columns that are declared in that row's table. These bitmaps are automatically allocated and invisibly maintained by RDMServer SQL. However, for some applications (e.g., those designed for Core API use) do not require the use of SQL nullcolumn values. Hence, the disable null values clause can be specified to disable the allocation and use of the null values bit-map for the database.

SQL requires that referential integrity be enforced for foreign and primary key relationships. This means that all rows in theprimary key table that are referenced by foreign key values in the rows of the referencing table exist. This is automaticallyhandled by SQL for those foreign keys on which a create join has been defined. For the other foreign keys, SQL maintains ineach referenced primary key table row a count of the number of current references to that row. RDM Server SQL enforces ref-erential integrity by only allowing primary key rows to be deleted or primary key values to be updated when its referencescount is zero. The references count value is automatically allocated and invisibly maintained by the SQL system for each rowin the referenced, primary key table. The allocation and use of the references count can be disabled by specifying the disablereferences count clause on the create database statement.

NOTE: When disable references count is specified, it will not be possible to delete rows (or update theprimary key value) from a primary key table that is referenced by a foreign key for which a create join hasnot been defined. .

The following example shows a create database statement for the bookshop database with a default page size of 4096 bytesand located on device booksdev.

create database bookshoppagesize 4096 on booksdev;

The create database for the RDM Server system catalog database is as follows.

create database syscat on catdevdisable references countdisable null values;

Note that this database is actually a Core API database as RDM Server SQL is itself a Core API application. Hence, the use ofboth null values and the references count is disabled.

6.2 Create File

The create file statement is used to define a logical file in which will be stored the contents of one or more table rows,indexes, or blob values. The table or index data which will be stored in the file is specified using the in clause of a sub-sequent create table or create index statement. The syntax for create file is as follows.

create_file:create {file | tablespace} filename [pagesize bytes] [on devname]

The filename is a case-insensitive identifier to be referenced in an in clause of a later DDL statement. The pagesize clause canbe used to specify the page size to be used for this particular file. If not specified, the default page size for the database will

Page 31: RDMs 8.4 SQL User's Guide

6. Defining a Database

SQL User Guide 23

be used. The on clause specifies the name of the RDM Server device on which the file will be located. If not specified, thefile is located on the default device for the database.

Use of the create file is not required. However, it must be used when a page size other than the database default is needed orwhen this file needs to be located on a device other than the database's default device.

Files referenced in an in clause but be created before the statement that references them is compiled.

Files can only contain the same kind of content. In other words, a file can either contain the rows of one or more tables (adata file), the occurrences of one or more index keys (a key file), the occurrences of a single hash index, or the occurrences ofone or more blob (e.g., long varchar) columns (a blob file).

A portion of the RDM Server system catalog SQL DDL specification is shown in the example below that illustrates the use ofthe create file statement.

create database syscat on catdevdisable references countdisable null values;

.../* index files*/create file sysnames; // all name indexescreate file syspfkeys; // primary and foreign key column indexes| .../* table files*/create file systabs; // systable, ...create file syspkeys; // syskeycreate file sysdbs; // sysparms, sysdb, sysindex

.../* blob files*/create file syscblobs pagesize 128; // long varchar data

...create table sysdb "database definition"(

name char(32) not null unique compare(nocase)"name of database",...

) in sysdbs;

create unique index db_name on sysdb(name) in sysnames;...

create table syskey "primary or unique key definition"() in syspkeys;create unique index pkey on syskey(cols) in syspfkeys;

...create table systable "table definition"(

table_addr db_addr primary key,name char(32) not null compare(nocase)

"table name",dbid integer not null

"database identifier",...defn long varchar in syscblobs

"definition string",...

Page 32: RDMs 8.4 SQL User's Guide

6. Defining a Database

SQL User Guide 24

) in systabs;create unique index tab_name on systable(name, dbid) in sysnames;

Note that RDM Server accepts both types of C-style comments to be embedded in an SQL script.

6.3 Create Table

An SQL table is the basic container for all data in a database. It consists of a set of rows each comprised of a fixed number ofcolumns. A simple example of a table declaration and the contents of a table is given below. The example shows the createtable declaration for the author table in the bookshop example database.

create table author(last_name char(13) primary key,full_name char(35),gender char(1),yr_born smallint,yr_died smallint,short_bio varchar(250)

);

The bookshop database contains 67 rows in the author table. Each row has values for each of the 6 columns declared in thetable. Some of the rows from this table are shown below. Note that the short_bio column values are truncated due to the sizeof the display window.

LAST_NAME FULL_NAME GENDER YR_BORN YR_DIED SHORT_BIOAlcottL Alcott, Louisa May M 1832 1888 American novelist. She is...AustenJ Austen, Jane F 1775 1817 English novelist whosewor ...BaconF Bacon, Francis M 1561 1626 English philosopher,state ...BarrieJ Barrie, J. M. (James Matthew) M 1860 1937 Scottish author anddramat ...BaumL Baum, L. Frank (Lyman Frank) M 1856 1919 American author of chil-dre ...BronteC Bronte, Charlotte F 1816 1855 English novelist andpoet, ...BronteE Bronte, Emily F 1818 1848 English novelist andpoet, ...BurnsR Burns, Robert M 1759 1796 Scottish poet and a lyr-ici ...BurroughsE Burroughs, Edgar Rice M 1875 1950 American author, bestknow ...CarlyleT Carlyle, Thomas M 1795 1881 Scottish satiricalwriter, ...CarrollL Carroll, Lewis M 1832 1898 (Charles Lutwidge Dodg-son) ...CatherW Cather, Willa F 1873 1947 a Pulitzer Prize-winningA ...

. . .TolstoyL Tolstoy, Leo M 1828 1910 Russian writer widelyrega ...TrollopeA Trollope, Anthony M 1815 1882 One of the most...re-specte ...

Page 33: RDMs 8.4 SQL User's Guide

6. Defining a Database

SQL User Guide 25

TwainM Twain, Mark M 1835 1910 (Samuel Clemens) American...VerneJ Verne, Jules M 1828 1905 French author who helpedp ...WellsH Wells, H. G. (Herbert George) M 1866 1946 English author, now bestk ...WhartonE Wharton, Edith F 1862 1937 Pulitzer Prize-winningAme ...WhitmanW Whitman, Walt M 1819 1892 American poet, essayist,j ...WildeO Wilde, Oscar M 1854 1900 Irish writer, poet, andpr ...WoolfV Woolf, Virginia F 1882 1941 English author, essayist,...

Details on how to properly define table using the create table statement are provided in the following sections of thischapter.

6.3.1 Table Declarations

The create table statement is used to define a table and must conform to the following syntax.

create_table:create table [dbname.]tabname ["description"]

(column_defn [, column_defn]... [, table_constraint]...)[in filename][inmemory [persistent | volatile | read] [maxpgs = maxpages]]

The table will be contained in the database defined by the most recently executed create database statement.

The name of the table is given by tabname which is an identifier of up to 32 characters in length. It is case-insensitive so that"salesperson" and "SALESPERSON" both refer to the same table. The table name must be unique—there can be no other tabledefined in the database with the same name. An optional "description" can be specified to provide additional descriptiveinformation about the table which will be stored in the system catalog entry for this table.

The infilename clause specifies a file, previously declared using create file, into which the rows of the table will be stored. Ifno in clause is specified, the system will automatically create a file for the table's rows using the database's default page sizeand storing it on the database's device.

The inmemory clause indicates that all of the rows in the table are to be maintained in the RDM Server computer's memorywhile the database containing the table is open. The read, persistent, and volatile options control whether the table's rows areread from disk when the database is opened (read, persistent), and whether they are written to the disk when the database isclosed (persistent). The default inmemory option is volatile which means that the table is always empty when the database isfirst opened. The read option means that all of the table's rows are read from the file when the database is opened; changes tothe data are allowed but are not written back to the file on closing. The persistent option means that the table's changes thatwere made while the database was open are written back to the file when the database is closed. The maxpgs parameter isused to specify the maximum number of database pages allowed for the table. (A database page is the basic unit of fileinput/output in RDM Server. A page contains one or more rows from a table. The number of rows per page is computed basedon the physical size of the table's row and the page size defined for the database file in which the table's rows are stored.)

Page 34: RDMs 8.4 SQL User's Guide

6. Defining a Database

SQL User Guide 26

6.3.2 Table Column Declarations

A table is comprised of one or more column definitions. Each column definition must follow the syntax shown below.

column_defn:colname basic_type [default {constant | null | auto}]

[not null][primary key | unique][references [dbname.]tabname [ (colname[, colname]...) ]][check(cond_expr)][compare({nocase | wnocase | cmdFcnId})]["description"]

| colname long {varchar | wvarchar | varbinary}[default {constant | null}] [not null] [in filename]

The name of the column is given by colname which is a case-insensitive identifier. There can only be one column declared inthe table with that name but it can be used in other tables. A good practice when naming columns is to use the same namesfor the primary and foreign key columns (except, of course, when the foreign key references the same table as in the sales-person table in the sales database example, see section 6.8 below). Keeping all other column names unique across all thetables in the database will allow you to use the natural join operation in your select statements.

Data Types

Table columns can be declared to contain values of one of the following data types as specified in the syntax below.

basic_type:{char | varchar |wchar | wvarchar } [( length )]

| {binary | varbinary} [( length )]| {double [precision] | float }| real| tinyint| {smallint | short}| {int | integer | long}| bigint| rowid [ '[' {4 | 6 | 8} ']' ]| decimal [(precision[, scale])]| date | time [(precision)] | timestamp [(precision)]

Descriptions for each of these data types are given in the following table.

Data Type Descriptionchar, varchar ASCII characters. The length specifies the maximum number of characters that can be stored

in the column which will be represented and stored as a null-terminated string. If no length isspecified (char only), a single character only is stored.

wchar, wvarchar Wide character data in which the storage format is operating system dependent. On Windows,wchar is stored as UTF-16 characters. On Linux, they are stored as UCS4 characters. Thelength specifies the maximum number of characters (not bytes) that can be stored in columnwhich will be represented and stored as a null-terminated string.

binary, varbinary Binary data where the length specifies the number of bytes that are stored in the column.

Table 6-1. RDM Server SQL Data Types

Page 35: RDMs 8.4 SQL User's Guide

6. Defining a Database

SQL User Guide 27

Data Type Descriptiondouble, float A 64-bit floating point number.real A 32-bit floating point number.tinyint An 8 bit, signed integer.smallint A 16 bit, signed integer.int, integer, long A 32 bit, signed integer.bigint A 64 bit, signed integer.rowid A 32-bit or 64-bit (depending on slotsize value) unsigned integer that holds the address of a

particular table row in the database.decimal A binary-coded decimal in which precision specifies the maximum number of significant

digits (default 32) and scale specifies the number of decimal digits (default 16).date Date values are stored as a 32 bit unsigned integer containing the number of elapsed days

since Jan 1, 1 A.D.time Time values are stored as a 32 bit unsigned integer contains the elapsed time since midnight

(to 4 decimal places => # seconds * 10000).timestamp A struct containing a data and time as defined above.long varchar A blob data column containing up to 2.1 gigabytes of ASCII character data which will be rep-

resented and stored as a null-terminated string.long wvarchar A blob data column containing up to 2.1 gigabytes of wide character data which will be rep-

resented and stored as a null-terminated string.long varbinary A blob data column containing up to 2.1 gigabytes of binary data.

Default and Auto-Incremented Values

column_defn:colname basic_type [default {constant | null | auto}]

The default clause can be used to specify a default value for a column when one has not been provided on an insert statementfor the table. The default is specified as a literal constant that is appropriate for that particular data type (see section 3.3) or itcan be set to null (the default).

A column of type integer is designated as an auto-increment column by specifying the default auto clause. This will causeSQL to automatically assign the next monotonically increasing non-negative integer value to the column when a value is notspecified for the column in an insert statement.

For example, the log_num column of the ship_log table is declared with default auto in the following create table statement.

create table ship_log(

LOG_NUM integer default auto primary key,ORD_DATE timestamp default now

"date/time when order was entered",ORD_NUM smallint not null

"order number",PROD_ID smallint not null

"product id number",LOC_ID char(3) not null

"outlet location id",QUANTITY integer not null

"quantity of item to be shipped from loc_id",BACKORDERED smallint default 0

Page 36: RDMs 8.4 SQL User's Guide

6. Defining a Database

SQL User Guide 28

"set to 1 when item is backordered",check(OKayToShip(ord_num, prod_id, loc_id, quantity, backordered) = 1)

);

When executing an insert statement, SQL automatically generates a value for log_num if no value has been specified. Forexample, in the insert statement below, SQL supplies the value for the log_num column.

insert into ship_logvalues(, date "1998-07-02", 3710, 17419, "SEA", 1, 0);

However, if you supply a value, then that value will be stored. In the example below, the loc_num value stored will be 12.

insert into ship_logvalues(12, date "1998-07-02", 3710, 17419, "SEA", 1, 0);

You should have little reason to assign your own values. But if you do, be sure to assign a value lower than the most recentlyauto-generated value.

The automatically generated integer values do not necessarily increase in strict monotonic order (that is, exactly by 1 eachtime). If a table's rows are stored in a file that also contains rows from other tables, the next number might exceed the currentnumber by more than 1.

Values from deleted rows are not reused.

The use of auto-increment default values does not incur any additional performance cost. RDM Server has implemented themas part of the standard file header, which uses special high-performance logging and recovery mechanisms.

Column Constraints

Column constraints restrict the values that can be legally stored in a column. The clauses used to do this are shown followingin the syntax portion.

column_defn:colname basic_type

[not null][primary key | unique][references [dbname.]tabname [ (colname[, colname]...) ]][check(cond_expr)]

Specifying not null indicates that the column cannot be assigned a null value. This means that either a default clause must bespecified for the column (of course, default null is not allowed) or a value for the column must always be specified in aninsert statement on the table.

A column that is declared to be a primary key or unique means that only one row in the table can have any specific value.SQL enforces this through creation of a unique index in which a copy each row's column value is contained. Error "integrityconstraint violation: unique" error is returned for any insert or update statement that attempts to assign a column value that isalready being used in another row of the table. Note that primary key and unique columns are automatically treated as notnull columns even when the not null clause is omitted in the column declaration.

Page 37: RDMs 8.4 SQL User's Guide

6. Defining a Database

SQL User Guide 29

A column that is declared with the references clause identifies it as a foreign key column referencing the primary key columnin the referenced table, tabname which can be in a separate database (dbname). This means that there must exist a row in thereferenced table with a primary key value that matches the column value being assigned by the insert or update statement.

The check clause is used to specify a conditional expression that must evaluate to true for every row that is stored in thetable. The specified conditional can only reference this column name and will typically check the value that it belongs to acertain range or set of values. Built-in or user-defined functions can be called from the conditional expression. Conditionalexpressions are specified in the usual way as given in the syntax below.

cond_expr:rel_expr [bool_oper rel_expr]...

rel_expr:expression [not] rel_oper {expression | [{any | some} | all] (subquery)}

| expression [not] between constant and constant| expression [not] in {(constant[, constant]...) | (subquery)}| [tabname.]colname is [not] null| string_expr [not] like "pattern"| not rel_expr| ( cond_expr )| [not] exists (subquery)| [tabname.]colname *= [tabname.]colname| [tabname.]colname =* [tabname.]colname

subquery:select {* | expression} from {table_list | path_spec} [where cond_expr]

expression:arith_expr | string_expr

arith_expr:arith_operand [arith_operator arith_operand]...

arith_operand:constant | [tabname.]colname | arith_function | ( arith_expr)

arith_operator:+ | - | * | /

arith_function:{sum | avg | max | min} (arith_expr)

| count ({* | [tabname.]colname})| if ( cond_expr, arith_expr, arith_expr)| numeric_function | datetime_function | system_function| user_defined_function

string_expr:string_operand [^ string_operand]

string_operand:"string" | [tabname.]colname

| if ( cond_expr, string_expr, string_expr)| string_function| user_defined_function

rel_oper:= | ==

| <| >

Page 38: RDMs 8.4 SQL User's Guide

6. Defining a Database

SQL User Guide 30

| <=| >=| <> | != | /=

bool_oper:& | && | and

| "|" | "||" | or

Descriptions of all supported SQL built-in functions can be found in Chapter 5 of the SQL Language Reference.

The following example gives the declaration of the salesperson table in the example sales database.

create table salesperson(sale_id char(3) primary key,sale_name char(30) not null,dob date,commission decimal(4,3) check(commission between 0.0 and 0.15),region smallint check(region in (0,1,2,3)),sales_tot double,office char(3) references invntory.outlet(loc_id),mgr_id char(3) references salesperson

);

This table contains a number of column constraint definitions. The sale_id column is defined as the table's primary key. Thesale_name column has the not null constraint meaning that a salesperson's name must always be specified on an insert state-ment. The commission column can only contain values in the range specified in its check clause. The region column must con-tain a value equal to 0,1,2 or 3. The office column value, if not null (null is okay), must be the same as the loc_id column ofa row from the outlet table in the invntory database. And the mgr_id column, if not null (null is also okay), must be the sameas a sale_id in another row of the same, salesperson table (this is a self-referencing table and is valid—note that it is not pos-sible for a row to reference itself).

6.3.3 Table Constraint Declarations

Following all column definitions, table constraints can be defined. Table constraints are similar to column constraints and areused to specify multi-column primary/unique and foreign key definitions and/or a check clause that can be used to specify aconditional expression involving multiple columns in the table that must be true for all rows in the table. The syntax for spe-cifying table constraints is as follows.

table_constraint:{primary key | unique} ( colname[, colname]... )

| foreign key ( colname[, colname]... )references [dbname.]tabname ( colname[, colname]... )

| check ( cond_expr )

The columns that comprise a unique or primary key cannot have null values.

The example below shows the create table statement for the note table in the sales database in which is declared a primarykey consisting of three of the table's columns.

create table note(note_id char(12) not null,

Page 39: RDMs 8.4 SQL User's Guide

6. Defining a Database

SQL User Guide 31

note_date date not null,sale_id char(3) not null references salesperson,cust_id char(3) references customer,primary key(sale_id, note_id, note_date)

);

The note_line table declaration in the example below contains a table constraint that declares a foreign key to the note tableshown above.

create table note_line( note_id char(12) not null,note_date date not null,sale_id char(3) not null,txtln char(81) not null,foreign key(sale_id, note_id, note_date) references note

);

Note that no column names are specified in the "references note" clause. The references clause usually references the primarykey of the referenced table but it could reference a unique column(s) declaration too. When the column names are notprovided, the references clause will always refer to the table's primary key.

NOTE: The number of data types of columns specified in a foreign key must match exactly with their cor-responding referenced primary key (unique) counterparts..

A portion of the sales_order table declaration is shown below which includes a check clause that ensures that the specifiedamount is greater than the tax.

create table sales_order(

cust_id char(3) not null references customer,...

amount double,tax real default 0.0,

...check(amount >= tax)

);

A side note needs to be mentioned here. The amount and tax columns are declared as floating point types which may be okayfor this simple example database but is not recommended for columns that are intended to contain monetary values. Floatingpoint arithmetic is too prone to computational errors to be used for monetary calculations. Instead, always use decimal types.

6.3.4 Primary and Foreign Key Relationships

Consider the create table statement below and its contents as shown in Table 6-2.

create table customer(sale_name char(30),comm decimal(4,3),office char(3),company varchar(30),city char(17),

Page 40: RDMs 8.4 SQL User's Guide

6. Defining a Database

SQL User Guide 32

state char(2),zip char(5)

);

Table 6-2. Example Un-normalized Customer Table

sale_name comm office company city state zipKennedy, Bob 0.075 DEN Broncos Air Express Denver CO 80239Kennedy, Bob 0.075 DEN Cardinals Bookmakers Phoenix AZ 85021Flores, Bob 0.100 SEA Seahawks Data Services Seattle WA 98121Flores, Bob 0.100 SEA Forty-niners Venture Group San Francisco CA 94127Stouffer, Bill 0.080 SEA Colts Nuts & Bolts, Inc. Baltimore IN 46219Blades, Chris 0.080 SEALister, Dave 0.075 ATLWyman, Eliska 0.075 NYC Browns Kennels Cleveland OH 44115Wyman, Eliska 0.075 NYC Jets Overnight Express New York NY 10021Wyman, Eliska 0.075 NYC Patriots Computer Corp. Foxboro MA 2131Wyman, Eliska 0.075 NYC 'Bills We Pay' Financial Corp. Buffalo NY 14216Wyman, Eliska 0.075 NYC Giants Garments, Inc. Jersey City NJ 7749Porter, Greg 0.080 SEA Lions Motor Company Detroit MI 48243Nash, Gail 0.070 DAL Saints Software Support New Orleans LA 70113Nash, Gail 0.070 DAL Oilers Gas and Light Co. Houston TX 77268Nash, Gail 0.070 DAL Cowboys Data Services Dallas TX 75230Kirk, James 0.075 ATLMcGuire, Sidney 0.070 WDC Steelers National Bank Pittsburgh PA 15234McGuire, Sidney 0.070 WDC Redskins Outdoor Supply Co. Arlington VA 22206McGuire, Sidney 0.070 WDC Eagles Electronics Corp. Philadelphia PA 19106Williams, Steve 0.075 ATL Dolphins Diving School Miami FL 33133Williams, Steve 0.075 ATL Falcons Microsystems, Inc. Atlanta GA 30359Williams, Steve 0.075 ATL Bucs Data Services Tampa FL 33601Robinson, Stephanie 0.070 LAX Raiders Development Co. Los Angeles CA 92717Robinson, Stephanie 0.070 LAX Chargers Credit Corp. San Diego CA 92126Robinson, Stephanie 0.070 LAX Rams Data Processing, Inc. Los Angeles CA 90075Jones, Walter 0.070 CHI Chiefs Management Corporation Kansas City MO 64141Jones, Walter 0.070 CHI Bengels Imports Cincinnati OH 45241Jones, Walter 0.070 CHI Bears Market Trends, Inc. Chicago IL 60603Warren, Wayne 0.075 MIN Vikings Athletic Equipment Minneapolis MN 55420Warren, Wayne 0.075 MIN Packers Van Lines Green Bay WI 54304

This table shows a customer list for a fictional company. Each customer entry contains information about the salesperson whoservices that company. Notice that there are duplicate salesperson (the sale_name, comm. and office columns) entries becausemost salespersons manage multiple customer accounts. Those duplicates comprise what is referred to as redundant data. Con-ceptually, an entire database can be viewed as a single table in which there is a great deal of redundant data among the rowsof that database. Hence, an important aspect of database design is the need to significantly reduce amount of redundant datain order to reduce disk space consumption which will also result in improved data access performance.

The database design technique that does this is called normalization. Normalization transfers the columns containing thesame redundant data into a separate table and then defines two new columns that will be used to associated the old data inthe new table with its original data in the old one. The new column in the new table is called the primary key. The newcolumn in the old table is called the foreign key.

For the example above, the create table declarations for the two tables would be as follows.

create table salesperson(sale_id char(3) primary key,sale_name char(30),

Page 41: RDMs 8.4 SQL User's Guide

6. Defining a Database

SQL User Guide 33

comm decimal(4,3),office char(3)

};create table customer(

company varchar(30),city char(17),state char(2),zip char(5),sale_id char(3) references salesperson

);

The sale_id column in the salesperson table is the primary key. Each row of the salesperson table must have a unique sale_idvalue. The sale_id column in the customer table is a foreign key that references the specific salesperson row that identifies thesalesperson who services that customer. The amount of redundant data per customer row has been reduced from about 40down to 3 bytes.

Table 6-3 Example Normalized Customer and Salesperson Tables

Table 6-3 shows the contents of the two tables after normalization. Each customer's salesperson is found from the row in thesalesperson table that has a matching sale_id column value. In order to see the name of the salesperson who services any par-ticular customer you must perform a join (specifically an equi-join) between the two tables. An example of a join between thesalesperson and customer tables is shown in the following select statement which displays the customers and their sales-persons for the companies located in California.

select sale_name, company, city, state from salesperson, customerwhere salesperson.sale_id = customer.sale_id and state = "CA";

sale_name company cityRobinson, Stephanie Raiders Development Co. Los AngelesRobinson, Stephanie Rams Data Processing, Inc. Los Angeles

Page 42: RDMs 8.4 SQL User's Guide

6. Defining a Database

SQL User Guide 34

Robinson, Stephanie Chargers Credit Corp. San DiegoFlores, Bob Forty-niners Venture Group San Francisco

A one-to-many relationship is formed between two tables through primary and foreign key column declarations in which for agiven row in the primary key table there can be many rows in the foreign key table with the same value. It is often very help-ful to refer to a graphical representation of a database schema in order to see all of the foreign and primary key relationshipsthat have been defined between the database tables. There are some very sophisticated standard ways to graphically depict adatabase design. We prefer, however, a simpler method using an arrow between the two related tables where the arrow startsat the primary key table (the "one" side of the one-to-many relationship and the arrow ends at the foreign key table (the"many" side of the one-to-many relationship). The arrow is labeled with the name of the foreign key column. The sales data-base example referred to in this documentation will be described in more detail later but a diagram of the schema showing allof the foreign and primary key relationships is shown in the figure below.

Figure 6-4. Sales and Inventory Database Schema Diagram

Note that the sales example is actually comprised of two databases named sales and invntory. As you can see in the aboveexample, foreign and primary key relationships can even be declared between tables defined in separate databases.

It is usually a good design practice for primary and foreign key columns to have the same name. Moreover, while it is pos-sible to declare multicolumn primary and foreign keys, it is better to define single column, unique primary keys. If there isalready data that uniquely identifies each row of a table (e.g., social security number, driver's or vehicle license number, etc.)then you should make that the primary key. If not, RDM Server provides two, easy-to-use methods that automatically assignprimary key values for you when rows are inserted into the table.

6.3.5 System-Assigned Primary Key Values

You can declare an integer column primary key to be auto-generated. The use of auto-generated integer column values wasdescribed earlier in the "Default and Auto-Incremented Values" paragraph in section 6.3.2. In the following create table state-ment the log_id column is declared to be an auto-generated, integer primary key. The insert statement which follows showshow.

Page 43: RDMs 8.4 SQL User's Guide

6. Defining a Database

SQL User Guide 35

create table activity_log(log_id integer default auto primary key,userid char(32),act_time timestamp,act_code tinyint,act_desc varchar(256)

);

The example below shows an insert statement into the above table and the select statement that shows the log_id value thatwas assigned.

insert into activity_log values(,user,now,1,"created auto-gen primary key example");select log_id, act_desc from activity_log;

log_id act_desc1 created auto-gen primary key example

Alternatively, you can declare a column to be a rowid primary key. A rowid primary key column uses a row's physical loc-ation in its data file to uniquely identify the row. Related tables would contain a rowid column foreign key referencing theprimary key row. This provides for the fastest possible method of locating rows based on the primary key value. Use of rowidprimary keys is much the same as auto-generated primary keys as shown in the example below.

create table activity_log(log_id rowid primary key,userid char(32),act_time timestamp,act_code tinyint,act_desc varchar(256)

);

The example below shows an insert statement into the above table and the select statement that shows the log_id value thatwas assigned.

insert into activity_log values(,user,now,1,"created auto-gen primary key example");select log_id, act_desc from activity_log;

log_id act_desc1 created auto-gen primary key example

A value can be assigned even to a rowid primary key but the value must be for a non-existent row in the database. Thisallows you to export a table (in rowid order) including the rowid values so that they can be imported into an empty tablekeeping the same rowid primary key column values. This is important because tables that have rowid foreign key referencesto the rowid primary key table must also maintain their values for export/import purposes.

The primary difference between the two methods is that an auto-generated integer primary key has an index whereas no indexis needed for the rowid.

6.4 Create Index

An index is a separate file containing the values of one or more table columns that can be used to quickly locate a row orrows in the table. Two indexing methods are supported in RDM Server. The standard indexing method is a Btree which

Page 44: RDMs 8.4 SQL User's Guide

6. Defining a Database

SQL User Guide 36

organizes the indexed column values so that they are stored in sorted order. This allows fast access to all the rows that matcha specified range of values. It also provides the ability to retrieve the table rows in the column order defined by the createindex statement avoiding the need to do a separate sort when a select statement includes an order by clause for thosecolumns. The time required to locate a specific row using a Btree access depends on factors such as the size of the index keyand the total number of rows in the database but typically will require from 3 to 5 disk reads.

The second supported index method is called hashing in which the location of a row a determined from performing a "hash"of the indexed column value. This method can often locate a particular row in 1 disk access and so can be used to providevery fast access to a row based on the indexed column value. However, the values are not sorted and, hence, hash indexes areonly used to find rows that match a specific value.

You do not directly use an index from SQL, but indexes are used by the RDM Server SQL optimizer in the selection of anaccess plan for retrieving data from the database. More indexes provide the optimizer with more alternatives and can greatlyimprove select execution performance. Unfortunately, the cost associated with a large number of indexes is a large amount ofrequired storage and a lower performance, incurred by insert, update, and delete statements.

Therefore, your selection of table columns to include in an index requires careful consideration. In general, create an index onthe columns through which the table's rows typically will be accessed or sorted. Do not create an index for every possible sortor query that may be of interest to a user. SQL can sort when the select statement is processed, so it is unnecessary to createall indexes in advance. Create indexes on the columns you expect will be used most often in order to speed access to therows or to order the result rows.

The syntax for create index is shown in the following syntax specification:

create_index:create [unique | optional] index [dbname.]ndxname ["description"]

[using {btree | hash {(hashsize) | for hashsize rows}}]on tabname ( colname [asc | desc] [, colname [asc | desc] ]... )[in filename][inmemory [maxpgs = maxpages] ]

Each index declared in the database has a unique name specified by the identifer ndxname. As with all table and columnnames, index names are case-insensitive. The dbname qualifier is only specified when the index is being added to a databasethat already exists. You can optionally include a "description" of the index which will be stored in the system catalog withthe other index information.

The create unique index statement is used to create an index that cannot contain any duplicate values. In addition, nullcolumn values are not allowed in columns that participate in a unique index.

The create optional index creates an index (always non-unique) that can be deactivated so that the overhead incurred duringthe execution of an insert statement can be avoided. Optional indexes that have been activated behave just like any otherindex. The values of the columns on which the index is based are stored in the index during the processing of an insert state-ment. Use of an activated optional index is also taken into consideration by the SQL query optimizer. When an optionalindex is deactivated, the index values are not created when new rows are inserted nor does the optimizer use the deactivatedoptional index. Note, however, that a delete or update of a row in which an index value has been stored in the optional indexwill properly maintain it (i.e., the index value will be deleted/updated) even when the index is deactivated. Hence, the activeor deactive state of an optional index only affects the use of the index in the processing of an insert or select statement.Optional indexes are useful when the use of the index by the optimizer is important only when executing queries that do notregularly occur. For example, an accounting system may activate optional indexes to improve the performance of month-endreporting requirements but then deactivate them at all other times to improve performance of transactions in which new rowsare being added. To enable or disable use of an optional index, use the activate index and deactivate index statements. Exe-cution of an activate index statement will read each row of the table and store an index value only for those rows that hadbeen inserted since the index was last deactivated. Initially, an optional index is deactivated.

Page 45: RDMs 8.4 SQL User's Guide

6. Defining a Database

SQL User Guide 37

You can specify the indexing method the using clause. The btree method is the default when the using clause is not spe-cified. For hash indexes the maximum number of rows that will (eventually) be stored in the table must be specified as thehashsize. Note that this does not need to be exact as it is unlikely that you can actually know this value in advance. The hashalgorithm relies on this information so it needs to be sufficiently large to minimize the average number of rows that hash tothe same value. Note that hash indexes must also be unique.

The on clause specifies the table and table columns on which the index is to be created. For btree indexes you can also spe-cify whether an indexed column is to be sorted in the index in either ascending (asc) or descending (desc) order.

Use the in clause to identify the file that contains the index. If not specified, the index will be maintained in a separate fileusing the default page size (1024 bytes). For hash indexes, the file specified in the in clause can only be used to store onehash index. For btree indexes, the file specified in the in clause can contain any number of other btree indexes. However, it isrecommended that each index be contained in its own file as this will generally produce better performance. Although, thereare some embedded operating systems with older (or simpler) file management capabilities in which having too many filescan also degrade performance.

The inmemory clause indicates that the index is to be maintained in the RDM Server computer's memory while the databasecontaining the table is open. The read, persistent, and volatile options control whether the index is read from disk when thedatabase is opened (read, persistent), and whether it is written to the disk when the database is closed (persistent). Thedefault inmemory option is volatile which means that the index is always empty when the database is first opened. The readoption means that entire index is read from the file when the database is opened; changes to the index are allowed but are notwritten back to the file on closing. The persistent option means that the index's changes that were made while the databasewas open are written back to the file when the database is closed. The maxpgs option is be used to specify the maximumnumber of database pages allowed for the index. (A database page is the basic unit of file input/output in RDM Server. Apage contains one or more keys in the index. The number of keys per page is computed based on the size of the indexedcolumns and the page size defined for the database file in which the index is stored.)

All unique and primary key columns (except those of the rowid data type) are indexed. If you do not specify a create indexfor a unique or primary key, SQL will automatically create one for you. You only need to specify a create unique index forunique or primary key table column(s) when 1) you want to use a hash index, 2) some of the columns in the btree indexneed to be in desc order, 3) you need to use the in clause to specify the index file where the create file was used to specify apage size other than the default page size, or 4) the index needs to be specified as an inmemory index.

In the following index example, the outlet table in our inventory database has two indexes. The loc_key index is aninmemory index for the primary key and loc_geo is an optional index.

create table outlet(

loc_id char(3) primary key,city char(17) not null,state char(2) not null,region smallint not null"regional U.S. sales area"

);create unique index loc_key on outlet(loc_id) inmemory persistent;create optional index loc_geo on outlet(state, city);

In the following index example, the outlet table in our inventory database has two indexes. The loc_key index is aninmemory index for the primary key and loc_geo is an optional index.

The create table for the sales_order table in the sales database example is shown below along with a multi-column createindex on the ord_date, amount, and ord_time columns.

Page 46: RDMs 8.4 SQL User's Guide

6. Defining a Database

SQL User Guide 38

create table sales_order(

cust_id char(3) not null references customer,ord_num smallint primary key,ord_date date default today,ord_time time default now,amount double,tax real default 0.0,ship_date timestamp default null,check(amount >= tax)

) in salesd0;create index order_ndx on sales_order(ord_date, amount desc, ord_time) in salek1;

6.5 Create Join

Using a create join statement, you can declare predefined joins that RDM Server SQL will automatically maintain on eachinsert, update, and delete statement issued by your application. Predefined joins are used to directly link together all of therows from a referencing table together with the referenced primary key row. Thus, queries that include a join (that is, an equi-join) between tables related through foreign and primary key relationships can directly access the related rows. This results inoptimal join performance. Like an index, a join is implicitly used by the RDM Server SQL optimizer in optimizing dataaccess. This means that no RDM Server SQL data manipulation statement refers directly to the predefined join.

A predefined join provides direct access from the primary key's row to all referencing foreign key rows, as well as from the for-eign key rows to the referenced primary key row. Thus, bi-directional direct access is available without the necessity of anindex on the foreign key. This bi-directional access also provides efficient outer-join processing.

Suppose that the salesperson table illustrated in Figure 6-8 contains rows for newly hired salespersons who do not yethave any customers. An "inner join" results in a virtual table that includes only the salespersons who have at least one cus-tomer (new hires are excluded). In this case, Figure 6-8 corresponds to an inner natural join of the salesperson and customertables. An "outer join" created for these tables results in a virtual table that includes all salespersons and their customers. Newhires appear in the table with empty (or null) customer column values, as illustrated in Figure 6-2.

Figure 6-5. Example of Outer Join Result

Access from the table row containing a foreign key to another table row containing the corresponding primary key entry isalways available through the primary key index. You can simply index the foreign key column to allow quick access to theforeign key row from the primary key table. However, doing so can use a large amount of disk storage because many foreignkeys can be associated with a single primary key. If you create a join instead, without indexing the foreign key, RDM Serveruses direct access methods to form the table relationship. This strategy results in better performance and saves considerabledisk storage.

The foreign key columns used in a create join are virtual columns (that is, columns for which RDM Server does not store thedata values). The application can access a value in a virtual column, just as it does any column. For virtual columns, RDMServer automatically extracts the data value from the primary key column of the referenced row through a pointer to that rowthat is associated with the predefined join and maintained by RDM Server.

Page 47: RDMs 8.4 SQL User's Guide

6. Defining a Database

SQL User Guide 39

Since values in a foreign key column come from the corresponding primary key column of the referenced table, no redundantdata is required. However, if an index uses one of the foreign key columns or if you have specified the non_virtual attributein your create join, the foreign key column values will be stored. In this case, redundant data is maintained in the referencing(foreign key) table.

When all foreign keys that reference a particular primary key are virtual, RDM Server allows the primarykey to be modified, even if there are still active foreign keys that reference it. This is the only case whereRDM Server allows a primary key column to be modified with references still active. Thus, changing theprimary key value will instantly change it in all the foreign key rows that reference it.

Using a join in your schema guarantees that only a single logical disk access is necessary to retrieve a row in the referencedtable. Thus, performance is optimal for referential integrity checking, for select statement processing, and for locating all rowsof the tables with a particular foreign key value. In addition, the database can use either one-to-many or many-to-one dataretrieval.

As with indexes, you should take care in deciding what foreign keys to use in predefined joins. Since a join is implementedby using database address links stored in each row, RDM Server must use resources to maintain the links during execution ofdatabase modification calls. Therefore, you should only use a join for situations in which the application needs to accesstables by going from primary key to foreign key, as well as from foreign key to primary key. When the access direction willonly be from the foreign key to the table containing the primary key, simply using the primary key index usually achievesacceptable performance.

The syntax for create join is shown in the following grammar specification:

create_join:create join joinname order {first | last | next | sorted}

on foreign_key [and foreign_key]...

foreign_key:[virtual | non_virtual] tabname [ ( colname [, colname]... ) ]

[by colname [asc | desc] [, colname [asc | desc]]... ]

The name of the join is specified by joinname which is a case-insensitive identifier. Even though the join is named, no otherSQL statement refers to a join by name as use of joins is handled automatically by RDM Server SQL.

A join on a foreign key declared in table tabname. If only one foreign key is declared then no colname list needs to be spe-cified. If specified, the colname list must exactly match the colname list in a foreign key declared in table tabname or, if onlyone column is specified, the colname column declaration in table tabname must itself have a references clause specified.

Columns of foreign keys on which the create join is specified are by default virtual—meaning that the column value is notstored in the foreign key table but is always retrieved from its referenced, primary key table row. This reduces the amount ofdata redundancy in the database. However, you can declare the join to be non_virtual indicating that the foreign key valuesare to also be stored in the foreign key table.

RDM Server implements a predefined join by maintaining all of the rows that have the same foreign key values (the ref-erencing rows) in a linked list connected to the referenced primary key row (the referenced row). The order clause specifiesthe order in which the referencing rows are maintained in this linked list as follows:

order first Newly inserted foreign key rows are placed at the front of the list.order last Newly inserted foreign key rows are placed at the end of the list.order next Newly inserted foreign key rows are placed following the current position in the list.order sorted Newly inserted foreign key rows are placed in the order specified in the foreign_key clause.

Page 48: RDMs 8.4 SQL User's Guide

6. Defining a Database

SQL User Guide 40

When you define a join as order sorted, you need to specify the by clause with either asc or desc to describe the sort orderfor each column as ascending or descending, respectively. Sort orders of mixed ascending and descending columns can be spe-cified but are not yet supported. Currently, ordering of all sort columns is based on the ordering of the first sort field.

The performance of an insert or update operation involving a joined foreign key will degrade when a largenumber of matching foreign key values exist. This is because the linked list implementation of predefinedjoins must be scanned to locate the proper insertion place. The larger the list, the longer the time of thescan.

The and operation allows multiple tables which contain foreign key declarations that reference the same primary key table toshare the same predefined join. This means that rows from each table that reference the same primary key row will be main-tained in the join's linked list. If the join is order sorted, then the type and length of the sort columns in each of the and'dtables must match exactly. Use of the and reduces the amount of space allocated to each row of the primary key table neededto maintain the predefined join lists. However, the cost of accessing related rows from one of the tables will be reduced as anaccess cost is incurred from any intervening rows from the other table(s) that are in the linked list.

The following example shows a portion of the salesperson and customer tables containing their respective primary and foreignkey declarations.

create table salesperson (sale_id char(3) primary key,sale_name char(30),...

);create table customer (

cust_id char(3) primary key,company varchar(30),...sale_id char(3) references salesperson

);create join salesperson_customers order last on customer(sale_id);

The order last specification will place a given salesperson's newly inserted rows at the end of the list so that a subsequenceselect statement that includes a join of the two tables will return the rows in the same order in which they were inserted.

insert into salesperson values "WHG", "Gates, Bill";

insert into customer values "IBM", "IBM Corporation", "WHG";insert into customer values "DLL", "Dell, Inc.", "WHG";insert into customer values "INT", "Intel Corporation", "WHG";insert into customer values "UW", "University of Washington", "WHG";commit;

select sale_name, cust_id, company from salesperson, customerwhere salesperson.sale_id = customer.sale_id

and sale_id = "WHG";

sale_name cust_id companyGates, Bill IBM IBM CorporationGates, Bill DLL Dell, Inc.Gates, Bill INT Intel CorporationGates, Bill UW University of Washington

Now consider, on the other hand, that the create join was specified with order sorted as follows.

Page 49: RDMs 8.4 SQL User's Guide

6. Defining a Database

SQL User Guide 41

create join salesperson_customers order sorted on customer(sale_id) by company;

Then the rows from that same select statement would be returned in company name order as shown below.

select sale_name, cust_id, company from salesperson, customerwhere salesperson.sale_id = customer.sale_id

and sale_id = "WHG";

sale_name cust_id companyGates, Bill DLL Dell, Inc.Gates, Bill IBM IBM CorporationGates, Bill INT Intel CorporationGates, Bill UW University of Washington

6.6 Compiling an SQL DDL Specification

There are several ways to compile an SQL DDL specification. Each DDL statement can be individually compiled andexecuted (of course in the correct sequence) through whatever method you would typically choose to use (e.g., the rdsad-min utility's SQL Browser). Usually, however, the SQL DDL specification will be contained in a text file as is the case withall of the RDM Server example database specifications.

The SQL DDL specification text can include C-style comments where the text between an opening "/*" up through a closing"*/" (can span multiple lines) is ignored as well as the text from a "//" to the end of the text line.

Two command-line utilities are provided that you can use to process an SQL DDL specification file. The sddlp utility isprovided just for that purpose. You can also use the rsql utility's ".r" command to process the DDL file as a script file. Ifyou do that, be sure to subsequently submit a commit statement (assuming, of course, there were no errors in the DDL spe-cification).

The sddlp utility is executed from a command-line according the usage shown below.

sddlp [-?|-h] [-B] [-V] [-2] [-6] [-f] [-L server ; user ; password ] ddlfile

Option Description-? Displays this usage information-B Do not display the start-up banner-V Display the version information-2 Align records like version RDM Server 2.1-6 Align BLOB files like version RDM Server 6.X-f Return database header file to client (for core API program use of SQL database).-L server ; user ; pass-word

Login in to RDM Server named server with user name user and password password. Each areseparated by a semi-colon (:) with no intervening spaces. If not specified, sddlp will attemptto use the values specified in the RDSLOGIN environment. variable and, failing that, willissue command-line prompts for the information.

ddlfile The name of the text file containing the SQL DDL specification.

Page 50: RDMs 8.4 SQL User's Guide

6. Defining a Database

SQL User Guide 42

6.7 Modifying an SQL DDL Specification

RDM Server allows the schema for an existing (i.e., populated) database to be modified by adding new tables or indexes, drop-ping existing tables or indexes, or changing the definition of a table. Each of these types of DDL modifications are describedin the following sections.

6.7.1 Adding Tables, Indexes, Joins to a Database

You can add a new table or index to a database simply by issuing a create table/index statement with the table/index namequalified by the database name as indicated in the earlier syntax specifications reproduced below.

create_table:create table [dbname.]tabname ["description"]

(column_defn [, column_defn]... [, table_constraint]...)[in filename][inmemory [persistent | volatile | read] [maxpgs = maxpages]]

create_index:create [unique | optional] index [dbname.]ndxname ["description"]

[using {btree | hash {(hashsize) | for hashsize rows}}]on tabname ( colname [asc | desc] [, colname [asc | desc] ]... )[in filename][inmemory [maxpgs = maxpages] ]

The table/index being created will be added to the database named dbname. If dbname is not specified, then the table/indexis added to the most recently opened/accessed database.

Note that the new table can contain a foreign key declaration that references an existing table, however, it is not possible toadd a create join on the foreign key. A create join can be added only when the join being defined is between two tables thatare also being added in the same transaction. The syntax for the create join statement is reproduced below.

create_join:create join joinname order {first | last | next | sorted}

on foreign_key [and foreign_key]...

foreign_key:[virtual | non_virtual] tabname [ ( colname [, colname]... ) ]

[by colname [asc | desc] [, colname [asc | desc]]... ]

6.7.2 Dropping Tables and Indexes from a Database

You can use the drop table statement to remove a table from a database as shown in the syntax below.

drop_table:drop table [dbname.tabname

The index will be dropped from database dbname. If dbname is not specified, then table tabname will be dropped from themost recently opened/accessed database that contains a table named tabname.

Page 51: RDMs 8.4 SQL User's Guide

6. Defining a Database

SQL User Guide 43

Tables to which foreign key references exist in other tables cannot be dropped. Nor can tables be dropped that have foreignkeys on which a create join has been declared.

Indexes can be dropped from a table using the drop index statement as follows:

drop_index:drop index [dbname.]ndxname

The index will be dropped from database dbname. If dbname is not specified, then index ndxname will be dropped from themost recently opened/accessed database that contains an index named ndxname.

Pre-defined joins (defined by the create join statement) cannot be dropped from a database.

Any create table, create index, drop table or drop index statements that are issued do not take effect until the next commitstatement is issued.

6.7.3 Altering Databases and Tables

If you will be making more than one change to a database schema, it is best to encapsulate the changes in an alter databasetransaction. The syntax for the alter database statement is shown below.

alter_database:alter {database | schema} dbname

The alter database statement is followed by a series of create file, create table, create index, drop table, drop index, oralter table statements that describe the changes you wish to make to the database schema. All the changes will be processedwhen a subsequent commit statement is submitted.

For example, the following alter database script will add an index on column contact in the customer table, drop the cust_order_key index in sales_order, and add a new table called sales_office.

alter database sales;

create file salesd4;create file salek3;

create index contact_key on customer(contact) in salek3;

drop index cust_order_key;

create table sales_office(office_id char(3) primary key,address char(30),city char(20),state char(2),zip char(10),phone char(12)

);create unique index office_key on sales_office(office_id) in salek3;

commit;

Page 52: RDMs 8.4 SQL User's Guide

6. Defining a Database

SQL User Guide 44

The alter table statement is used to change the definition of an existing table. It can be used to add, drop, or modify columndefinitions, rename the table or its columns, to modify the inmemory maxpgs value, drop a foreign key, or change the tabledescription string. The syntax for the alter table statement is as follows.

alter_table:alter table [dbname.]tabname alter_table_action

alter_table_action:add [column] column_defn [, column_defn]...

| alter [column] column_defn| drop column colname| inmemory maxpgs = maxpages| rename table tabname ["description"]| rename [column] oldname newname ["description"]| drop foreign key ( colname [, colname]... )| "description"

Execution of this statement will modify the definition of the table name tabname in database dbname. If dbname is not spe-cified then the table must be defined in the database identified in a previously submitted and active alter database statement.

The add column clause is used to add one or more columns to the table. A complete column_defn must be specified for eachone. The added columns will be added at the end of the table in the order specified in the list.

The alter column is used to change the definition of an existing column. The column_defn must be complete, that is, it muststill include all of the original column definition entities that are to be retained. If not null is added to the column definition,a default must be given. If the type or length of the column changes, any index that uses this column must have been pre-viously dropped. If the type or length of the column changes, any foreign key references including this column must havebeen previously dropped. The check and compare clauses of the column_defn cannot be changed, added, or removed whenaltering a column. Type conversion from double, float, real, numeric, decimal, date, time or timestamp into varchar, char,wvarchar or char will use the default display format for the type as defined by the user (i.e. "set date display(14, "mmm. d,yyyy")").

The drop column action removes the column from the table The rename action can be used to change the table name or thename of a column. The drop foreign key clause removes the foreign key table constraint with the specified column names.Foreign keys on which a create join has been declared cannot be dropped.

6.7.4 Schema Versions

All of the DDL statements that have been submitted after the alter database statement which initiated the schema modi-fication take effect upon execution of a commit statement. RDM Server assigns a new version number to the newly changeddatabase schema. Versioning allows DDL changes to take immediate effect without having to apply to those changes to theexisting database data.

All SQL statements that access the database which are submitted after the schema has been changed mustconform to the new DDL specification.

Any C applications or stored procedures that reference changed or dropped DDL tables or columns must bechanged and recompiled.

Database files that contained only tables, indexes, or blob column data that have been dropped are deleted.

Page 53: RDMs 8.4 SQL User's Guide

6. Defining a Database

SQL User Guide 45

Columns that have been added to tables will have null values returned for the table's rows that existed prior to the DDLchanges being put into effect. If the newly added column was specified as not null then the column's default value will bereturned.

If any tables are dropped or columns are changed or dropped by an alter database transaction, an updatestats should be submitted on the database after the DDL changes have been committed..

6.8 Example SQL DDL Specifications

Several example databases are provided with RDM Server. The two example databases that are primarily used in this doc-umentation to illustrate use of RDM Server SQL are for a hypothetical computer components sales company. (Since thisexample has been in use in the RDM Server documentation since 1992 perhaps we should call it an antique computer com-ponent sales company.) Also provided is an example database for a hypothetical bookshop that only sells high-end, rare anti-quarian books. The other example database contains actual data derived from over 130,000 National Science Foundation(USA) research grants that were awarded during the years 1990 through 2003.

6.8.1 Sales and Inventory Databases

The example inventory database is defined in the following schema. This database consists of three tables. The product tablecontains information about each product, including an identification code, description, pricing, and wholesale cost. The outlettable identifies all company office and warehouse locations. The on_hand table is used to link the other two tables by defin-ing the quantity of a specific product (through prod_id) located at a particular outlet (through loc_id). This specification isavailable in the text file named "invntory.sql".

create database invntory on sqldev;create table product(

prod_id smallint primary key "product identification code",prod_desc char(39) not null "product description",price float "retail price",cost float "wholesale cost"

);create unique index prod_key on product(prod_id);create index prod_pricing on product(price desc, prod_id);

create table outlet(

loc_id char(3) primary key,city char(17) not null,state char(2) not null,region smallint not null "regional U.S. sales area"

);create unique index loc_key on outlet(loc_id);create optional index loc_geo on outlet(state, city);

create table on_hand(

loc_id char(3) not null references outlet(loc_id)"warehouse containing this product",prod_id smallint not null references product

"id of product at this warehouse",quantity integer not null

"units of product on hand at this warehouse",primary key(loc_id, prod_id)

Page 54: RDMs 8.4 SQL User's Guide

6. Defining a Database

SQL User Guide 46

);create unique index on_hand_key on on_hand(loc_id, prod_id);create join inventory order last on on_hand(loc_id);create join distribution order last on on_hand(prod_id);

The example sales database definition given below, is more complex than the inventory database. The salesperson table con-tains specific information about a salesperson, including sales ID code, name, commission rate, etc. The customer table con-tains standard data identifying each customer. The sale_id column in this table is a foreign key for the salesperson whoservices the customer account. Note that sales orders made by a customer are identified through the cust_id foreign key in thesales_order table. This DDL specification is contained in text file "sales.sql".

create database sales on sqldev;create file salesd0;create file salesd1;create file salesd2;create file salesd3;create file salek0;create file salek1 pagesize 2048;create file salek2 pagesize 4096;

create table salesperson(

sale_id char(3) primary key,sale_name char(30) not null,dob date "date of birth",commission decimal(4,3) check(commission between 0.0 and 0.15)

"salesperson's commission rate",region smallint check(region in (0,1,2,3))

"regional U.S. sales area",office char(3) references invntory.outlet(loc_id)

"location where salesperson works",mgr_id char(3) references salesperson

"salesperson id of sales mgr") in salesd0;create unique index sale_key on salesperson(sale_id) in salek0;create optional index sales_regions on salesperson(region, office) in salek1;create optional index sales_comms

on salesperson(commission desc, sale_name) in salek2;create join manages order last on salesperson(mgr_id);

create table customer(

cust_id char(3) primary key,company varchar(30) not null,contact varchar(30),street char(30),city char(17),state char(2),zip char(5),sale_id char(3) not null references salesperson

"salesperson who services customer account") in salesd0;create unique index cust_key on customer(cust_id) in salek0;create optional index cust_geo on customer(state, city) in salek2;create join accounts order last on non_virtual customer;

create table sales_order(

Page 55: RDMs 8.4 SQL User's Guide

6. Defining a Database

SQL User Guide 47

cust_id char(3) not null references customer"customer who placed order",ord_num smallint primary key

"order number",ord_date date default today

"date order placed",ord_time time default now

"time order placed",amount double

"total base amount of order",tax real default 0.0"state/local taxes if appl.",ship_date timestamp default null,check(amount >= tax)

) in salesd0;create unique index order_key on sales_order(ord_num) in salek0;create index order_ndx on sales_order(ord_date, amount desc, ord_time) in salek1;create index cust_order_key on sales_order(cust_id) in salek0;create join purchases order last on sales_order;

create table item(

ord_num smallint not null references sales_order,prod_id smallint not null references invntory.product,loc_id char(3) not null references invntory.outlet,quantity integer not null

"number of units of product ordered",check( HaveProduct(ord_num, prod_id, loc_id, quantity) = 1 )

) in salesd1;create index item_ids on item(prod_id, quantity desc) in salek1;create join line_items order last on item(ord_num);

create table ship_log(

log_num integer default auto primary key,ord_date timestamp default now

"date/time when order was entered",ord_num smallint not null

"order number",prod_id smallint not null

"product id number",loc_id char(3) not null

"outlet location id",quantity integer not null

"quantity of item to be shipped from loc_id",backordered smallint default 0

"set to 1 when item is backordered",check(OKayToShip(ord_num,prod_id,loc_id,quantity,backordered) = 1)

) in salesd0;create index ship_order_key on ship_log(ord_num, prod_id, loc_id) in salek1;

create table note(

note_id char(12) not null,note_date date not null,sale_id char(3) not null references salesperson,cust_id char(3) references customer,primary key(sale_id, note_id, note_date)

) in salesd2;create unique index note_key on note(sale_id, note_id, note_date) in salek1;

Page 56: RDMs 8.4 SQL User's Guide

6. Defining a Database

SQL User Guide 48

create join tickler order sorted on note(sale_id) by note_date desc;create join actions order sorted on note(cust_id) by note_date desc;

create table note_line(

note_id char(12) not null,note_date date not null,sale_id char(3) not null,txtln char(81) not null,foreign key(sale_id, note_id, note_date) references note

) in salesd3;create join comments order last on note_line;

For the sales database, the item table contains product and quantity data pertaining to the sales order identified through theord_num foreign key. Notes that can serve as a tickler for the salesperson or that indicates actions to be performed for the cus-tomer are stored in the note table. Each line of the note text is stored as a row of the note_line table. An additional table,called ship_log, contains information about sales orders that have been booked but not yet shipped. Your application will cre-ate rows in this table through a trigger function, which is a special use of a user-defined function (UDF).

The schema diagram for the sales and inventory databases was given earlier in Figure 6-3 but is also shown in below. Recallthat the boxes represent tables. The arrow represents the foreign and primary key relationship between the two tables wherethe arrow starts at the primary key table (the "one" side of the one-to-many relationship and the arrow ends at the foreign keytable (the "many" side of the one-to-many relationship). The arrow is labeled with the name of the foreign key column.

Figure 6-6. Sales and Inventory Databases Schema Diagram

6.8.2 Antiquarian Bookshop Database

Our fictional bookshop is located in Hertford, England (a very real and charming town north of London). It is located in abuilding constructed around 1735 and has two rather smallish rooms on two floors with floor-to-ceiling bookshelves through-out. Upon entering, one is immediately transported to a much earlier era being quite overwhelmed by the wonderful sight and

Page 57: RDMs 8.4 SQL User's Guide

6. Defining a Database

SQL User Guide 49

odor of the ancient mahogany wood in which the entire interior is lined along with the rare and ancient books that reside onthem. There is a little bell that announces one’s entrance into the shop but it is not really needed, as the delightfully squeakyfloor boards quite clearly makes your presence known.

In spite of the ancient setting and very old and rare books, this bookshop has a very modern Internet storefront through whichit sells and auctions off its expensive inventory. A computer system contains a database describing the inventory and managesthe sales and auction processes. The database schema for our bookshop is given below. It is contained in text file "book-shop.sql".

create database bookshop on booksdev;

create table author(last_name char(13) primary key,full_name char(35),gender char(1),yr_born smallint,yr_died smallint,short_bio varchar(250)

);

create table genres(text char(31) primary key

);

create table subjects(text char(51) primary key

);

create table book(bookid char(14) primary key,last_name char(13) references author,title varchar(255),descr char(61),publisher char(136),publ_year smallint,lc_class char(33),date_acqd date,date_sold date,price double,cost double

);create join authors_books order last on book(last_name);create index year_ndx on book(publ_year);

create table related_name(bookid char(14) references book,name char(61)

);create join book_names order last on related_name(bookid);

create table genres_books(bookid char(14) references book,genre char(31) references genres

);create join genre_book_mm order last on genres_books(genre);create join book_genre_mm order last on genres_books(bookid);

create table subjects_books(bookid char(14) references book,

Page 58: RDMs 8.4 SQL User's Guide

6. Defining a Database

SQL User Guide 50

subject char(51) references subjects);create join subj_book_mm order last on subjects_books(subject);create join book_subj_mm order last on subjects_books(bookid);

create table acctmgr(mgrid char(7) primary key,name char(24),hire_date date,commission double

);

create table patron(patid char(3) primary key,name char(30),street char(30),city char(17),state char(2),country char(2),pc char(10),email char(63),phone char(15),mgrid char(7)

);create index patmgr on patron(mgrid);create index phone_ndx on patron(phone);

create table note(noteid integer primary key,bookid char(14) references book,patid char(3) references patron

);create join book_notes order last on note(bookid);create join patron_notes order last on note(patid);

create table note_line(noteid integer references note,text char(81)

);create join note_text order last on note_line(noteid);

create table sale(bookid char(14) references book,patid char(3) references patron

);create join book_sale order last on sale(bookid);create join book_buyer order last on sale(patid);

create table auction(aucid integer primary key,bookid char(14) references book,mgrid char(7) references acctmgr,start_date date,end_date date,reserve double,curr_bid double

);create join book_auction order last on auction(bookid);create join mgd_auctions order last on auction(mgrid);

Page 59: RDMs 8.4 SQL User's Guide

6. Defining a Database

SQL User Guide 51

create table bid(aucid integer references auction,patid char(3) references patron,offer double,bid_ts timestamp

);create join auction_bids order last on bid(aucid);create join patron_bids order last on bid(patid);

Descriptions for each of the above tables are given below.

Table Name Descriptionauthor Each row contains biographical information about a reknowned author.book Contains information about each book in the bookshop inventory. The last_name column

associates the book with its author. Books with a non null date_sold are no longer available.genres Table of genre names (e.g., "Historical fiction") with which particular books are associated via

the genres_books table.subjects Table of subject names (e.g., "Cape Cod") with which particular books are associated via the

subjects_books table.related_name Related names are names of individuals associated with a particular book. The names are usu-

ally hand-written in the book’s front matter or on separate pages that were included with thebook (e.g., letters) and identify the book’s provenance (owners). Only a few books haverelated names. However, their presence can significantly increase the value of the book.

genres_books Used to create a many-to-many relationship between genres and books.subjects_books Used to create a many-to-many relationship between subjects and books.note Connects each note_line to its associated book. Notes include edition info and other com-

ments (often coded) relating to its condition.note_line One row for each line of text in a particular note.acctmgr Account manager are the bookshop employees responsible for servicing the patrons and man-

aging auctions.patron Bookshop customers and their contact info. Connected to their purchases/bids through their

relationship with the sale and auction tables.sale Contains one row for each book that has been sold. Connects the book with the patron who

acquired through the bookid and patid columns.auction Some books are auctioned. Those that have been (or currently being) auctioned have a row in

this table that identifies the account manager who oversees the auction. The reserve columnspecifies the minimum acceptable bid, curr_bid contains the current amount bid.

bid Each row provides the bid history for a particular auction.

Table 6-5. Bookshop Database Table Descriptions

A schema diagram depicting the intertable relationships is shown below.

Page 60: RDMs 8.4 SQL User's Guide

6. Defining a Database

SQL User Guide 52

Figure 6-7. Bookshop Database Schema Diagram

6.8.3 National Science Foundation Awards Database

The data used in this example has been extracted from the University of California Irvine Knowledge Discovery in DatabasesArchive (http://kdd.ics.uci.edu/). The original source data can be found at http://k-dd.ics.uci.edu/databases/nsfabs/nsfawards.html. The data was processed by a Raima-developed RDM SQL program that, inaddition to pulling out the data from each award document, converted all personal names to a "last name, first name, ..."format and, where possible, identified each person’s gender from the first name. The complete DDL specification for the NSFawards database is shown below. It is contained in text file "nsfawards.sql".

create database nsfawards on sqldev;

create table person(name char(35) primary key,gender char(1),jobclass char(1)

);

create table sponsor(name char(27) primary key,addr char(35),city char(24),state char(2),zip char(5)

);create index geo_loc on sponsor(state, city);

create table nsforg(

Page 61: RDMs 8.4 SQL User's Guide

6. Defining a Database

SQL User Guide 53

orgid char(3) primary key,name char(40)

);

create table nsfprog(progid char(4) primary key,descr char(40)

);

create table nsfapp(appid char(10) primary key,descr char(30)

);

create table award(awardno integer primary key,title char(182),award_date date,instr char(3),start_date date,exp_date date,amount double,prgm_mgr char(35) references person,sponsor_nm char(27) references sponsor,orgid char(3) references nsforg

);create join manages order last on award(prgm_mgr);create join sponsor_awards order last on award(sponsor_nm);create join org_awards order last on award(orgid);create index award_date_ndx on award(award_date);create index exp_date_ndx on award(exp_date);create index amount_ndx on award(amount);

create table investigator(awardno integer references award,name char(35) references person

);create join award_invtgrs order last on investigator(awardno);create join invtgr_awards order last on investigator(name);

create table field_apps(awardno integer references award,appid char(10) references nsfapp

);create join award_apps order last on field_apps(awardno);create join app_awards order last on field_apps(appid);

create table progrefs(awardno integer references award,progid char(4) references nsfprog

);create join award_progs order last on progrefs(awardno);create join prog_awards order last on progrefs(progid);

Descriptions for each of the tables declared in the nsfawards database are given in the following table.

Table Name Descriptionperson Contains one row for each investigator or NSF program manager. An investigator (jobcclass =

"I") is a person who is doing the research. The NSF program manager (jobcclass = "P") over-

Table 6-6. NSF Awards Database Table Descriptions

Page 62: RDMs 8.4 SQL User's Guide

6. Defining a Database

SQL User Guide 54

Table Name Descriptionsees the research project on behalf of the NSF. An award can have more than one investigatorbut only one program manager. The gender column is derived from the first name but hasthree values "M", "F", and "U" for "unknown" when the gender based on the first name couldnot be determined (about 13%).

sponsor Institution that is sponsoring the research. Usually where the principal investigator isemployed. Each award has a single sponsor.

nsforg NSF organization. The highest level NSF division or office under which the grant is awarded.nsfprog Specific NSF programs responsible for funding research grants.nsfapp NSF application areas that the research impacts.award Specific data about the research grant. The columns are fairly self-explanatory. For clarity the

exp_data column contains the award expiration data (i.e., when the money runs out). Theamount column contains the total funding amount. The instr column is a code indicating theaward instrument (e.g., "CTG" = "continuing", "STD" = "standard", etc.).

investigator The specific investigators responsible for carrying out the research. This table is used to forma many-to-many relationship between the person and award tables.

field_apps NSF application areas for which the research is intended. This table is used to form a many-to-many relationship between the nsfapp and award tables.

progrefs Specific programs under which the research is funded. This table is used to form a many-to-many relationship between the nsfprog and award tables.

Note that the interpretations given in the above descriptions are Raima's and may not be completely accurate (e.g., it could bethat NSF programs are not actually responsible for funding research grants). However, our intent is to simply use this data forthe purpose of illustration. A schema diagram for the nsfawards database is shown below.

Figure 6-8. NSF Awards Database Schema Diagram

6.9 Database Instances

A database instance is a database that shares the schema (DDL specification) of another database. There can be any number ofdatabase instances that share the same schema definition. One principal use for database instancing is a situation where mutu-ally exclusive data exists that includes differing archiving requirements. For example, to retrieve and delete data from a data-base all database information related to a particular account or client record can be tedious to program and expensive to

Page 63: RDMs 8.4 SQL User's Guide

6. Defining a Database

SQL User Guide 55

process. However, if each client or account data is placed in a separate database instance, it is easy to both archive (simplycopy the database files), and delete (simply reinitialize the database or delete it altogether).

Time oriented applications also can benefit from database instancing. Consider the example of a company that uses a separateinstance for each day of the current year. In this setup, each day's transactions can simply be stored in the instance for thatday.

Instancing is also useful in some replication applications. For example, assume a large corporation has a mainframe computerthat stores all accounts from all its branch offices. Each branch office performs a daily download of the new and modifiedaccounts into separate database instances for each account. This allows each modified account to simply reinitialize the data-base before receiving the new account information or to create a new instance for the new accounts.

Database instancing requires that the database definition be considered as distinct from the database itself, since there can bemore than one instance of a schema and each instance has a different name. The original instance has the same name as theschema; subsequent instances have different names. Once a database instance has been created, it can be used in exactly thesame manner as any database.

6.9.1 Creating a Database Instance

When SQL processes an SQL DDL specification, the database name specified in the create database statement names boththe schema and the first instance of the schema, and automatically creates the first instance. Other instances can then be cre-ated and dropped.

A new instance of a database is created by a successful execution of the create database instance statement shown below.

create_database_instance:create database instance newdb from sourcedb

[with data | [no]init[ialize]] [on devname]

New database instances are created from existing databases. The name of the new database is given by newdb, which must beunique for all databases on the server. The existing database instance from which the new instance is created is sourcedb.

The database device name, devname, must be specified and must be a valid RDM Server database device. In addition, thatdevice cannot have been the device in which database sourcedb is contained nor any other instances of the same schema. Alldatabase files will be stored on that device and, since the file names for all instances are identical, they must be stored in sep-arate database devices. If specified, the with data option opens the source database for exclusive access and causes all data-base files and optimizer statistics from the source database to be copied into the new database. The init option (default) willensure that the database files for the instance are initialized. The noinit option can be specified to defer initialization to somelater time when an initialize database statement will be performed.

The create database instance statement can only be executed by administrators or the owner of the schema (that is, the userwho issued the original create database statement).

The initial instance of a database is created when a database definition is processed. The name of the instance is specified inthe create database statement. Other instances can then be created from the original database. All instances share the samedatabase definition information from the system catalog. However, database statistics used by the SQL query optimizer col-lected during execution of the update stats statement are maintained separately for each database instance.

6.9.2 Using Database Instances

Database instances are referenced just as you reference any database. You can explicitly open a database instance using theopen statement or implicitly open one through a qualified table name. For example, assume that wa_sales, ca_sales, and mi_

Page 64: RDMs 8.4 SQL User's Guide

6. Defining a Database

SQL User Guide 56

sales are each instances of the sales database, containing the sales for Washington, California, and Michigan, respectively. Thefollowing example shows how these instances can be created and populated.

create database instance wa_sales from sales on wadev;insert into wa_sales.customer from file "customrs.wa" on salesdev;

create database instance ca_sales from sales on cadev;insert into ca_sales.customer from file "customrs.ca" on salesdev;

create database instance mi_sales from sales on midev;insert into mi_sales.customer from file "customrs.mi" on salesdev;

update stats on wa_sales, ca_sales, mi_sales;

The next example returns the customers from the Michigan instance of sales.

open mi_sales;select * from customer;

This same query could have been executed using a single statement as follows.

select * from mi_sales.customer;

You can have any number of instances of the same schema opened at a time. An unqualified reference to a table in theschema will use the most recently opened instance by default. If you are not sure which instance is open, it is best to expli-citly qualify the table name with the database name.

An unqualified reference to a table from a schema on which there is more than one instance will use the oldest instance (usu-ally the original) when none have been opened.

6.9.3 Stored Procedures and Views

Views and stored procedure definitions are maintained based on the schema definition and are not dependent on a particulardatabase instance except when the database instance is explicitly referenced in the view or stored procedure declaration.However, the execution plan generated for the view or stored procedure is based on the optimization statistics associated withwhatever database instance was open at the time the view or stored procedure was compiled. Thus, if a view or stored pro-cedure will be used with more than one database instance, it is important that the instance used during compilation contain arepresentative set of data on which an update stats has been run.

The example below creates a view called in_state_by_zip that will list the customers in a database instance in zip code order.The mi_sales database was opened for the create view because it contained a large number of customers. Thus, the optimizerwould be sure to use the index on zip (assuming that in this example zip is indexed). The subsequent open on wa_sales fol-lowed by the select of in_state_by_zip will return the results from the wa_sales database.

-- Lot's of customers in Michigan, should provide good statsopen mi_sales;create view in_state_by_zip asselect * from customer order by zip;

open wa_sales;select * from in_state_by_zip;

Page 65: RDMs 8.4 SQL User's Guide

6. Defining a Database

SQL User Guide 57

Note that for views referenced in a select statement qualified with an instance name, the instance name is used to identify theschema to which the view is attached. It does not specify which instance to use with any unqualified table names in the viewdefinition itself. Thus, in the following example, the result set will contain Washington, not Michigan, customers.

open wa_sales;select * from mi_sales.in_state_by_zip;

6.9.4 Drop Database Instance

The drop database statement can be used to delete database instances. The syntax is shown below.

drop_database:drop database dbname

This statement can only be executed by administrators or the database owner. Also, database dbname must not be opened byany other users. The system drops the database instance by removing its instance-specific information from the system catalog.The database definition information associated with the schema is not deleted.

Dropping the original database after all other database instances based on it have been dropped will remove the database com-pletely from the system, including the schema definition.

6.9.5 Restrictions

A database instance cannot be created for any database which contain explicitly declared foreign key references to a differentdatabase. For example, the example sales database schema provided in RDM Server contains foreign references to the invntorydatabase. Any attempt to create an instance of either sales or inventory will return an error. This restriction exists because it isimpossible for RDM Server to reliably manage inter-database reference counts for multiple database instances. The reliabilityof such operations would be based on the correctness of the application's use of those databases, thus violating the veryconcept of DBMS-enforced referential integrity.

Inter-database relationships can still be maintained by the application program by using undeclared foreign keys. Shownbelow is an excerpt from sales.sql with the declared foreign keys to the invntory database highlighted. By simply removingthe indicated reference clauses, it is possible to create multiple instances of both sales and inventory. Referential integrity willnot be enforced by SQL but the inter-database relationships can still exist with no effect on how joins between the databasesare processed.

create table salesperson(

sale_id char(3) primary key,...office char(3) references invntory.outlet(loc_id),mgr_id char(3) references salesperson

);...create table item(

ord_num smallint not null references sales_order,prod_id smallint not null references invntory.product,loc_id char(3) not null references invntory.outlet,

Page 66: RDMs 8.4 SQL User's Guide

6. Defining a Database

SQL User Guide 58

...);

Page 67: RDMs 8.4 SQL User's Guide

7. Retrieving Data from a Database

SQL User Guide 59

7. Retrieving Data from a DatabaseThe reason data is stored in a database is so that it can be later retrieved and looked at. However, in order to do somethingintelligent with that data it must first intelligently be retrieved. This is often much easier to say than to do and that is par-ticularly true with a language like SQL.

Data is retrieved from RDM Server databases using the SQL select statement. A completely specified select statement is com-monly referred to as a query. The complete set of rows that are returned by a select statement is called the result set.

This chapter will explain how to properly formulate select statements to view data contained in one or more RDM Server data-bases. We will begin with the simplest and progress to more complex queries. The select statement syntax specification willbe incrementally developed throughout this chapter in order to show only the syntax that is relevant to the select statementfeature being explained.

7.1 Simple Queries

The most basic of queries is to retrieve all of the rows and columns of a table. The easiest way to do this is to use the fol-lowing statement:

select:select * from tabname

The "*" indicates that all of the columns declared in tabname are to be returned. Thus, you can enter the following statementto see all of the account managers in the acctmgr table in the bookshop database.

For example, the following statement retrieves data from the salesperson table in the example sales database. To choose allcolumns in the table, enter an asterisk (*).

select * from salesperson;

SALE_ID SALE_NAME DOB COMMISSION REGION SALES_TOT OFFICE MGR_IDBCK Kennedy, Bob 1957-10-29 0.075 0 736345.32 DEN BNFBNF Flores, Bob 1943-07-17 0.100 0 173102.02 SEA *NULL*BPS Stouffer, Bill 1952-11-21 0.080 2 29053.3 SEA *NULL*CMB Blades, Chris 1958-09-08 0.080 3 0 SEA *NULL*DLL Lister, Dave 1999-08-30 0.075 3 0 ATL *NULL*ERW Wyman, Eliska 1959-05-18 0.075 1 566817.01 NYC GAPGAP Porter, Greg 1949-03-03 0.080 1 439346.5 SEA *NULL*GSN Nash, Gail 1954-10-20 0.070 3 306807.26 DAL CMBJTK Kirk, James 2100-08-30 0.075 3 0 ATL *NULL*SKM McGuire, Sidney 1947-12-02 0.070 1 208432.11 WDC GAPSSW Williams, Steve 1944-08-30 0.075 3 247179.99 ATL CMBSWR Robinson, Stephanie 1968-10-11 0.070 0 374904.47 LAX BNFWAJ Jones, Walter 1960-07-15 0.070 2 422560.55 CHI BPSWWW Warren, Wayne 1953-04-29 0.075 2 212638.5 MIN BPS

Of course, if you only need to see some but not all of the columns in a table, those columns can be individually listed asindicated in the following syntax.

select:select colname[, colname]… from tabname

Page 68: RDMs 8.4 SQL User's Guide

7. Retrieving Data from a Database

SQL User Guide 60

Each specified colname must identify a column that is declared in tabname. The next example retrieves the salesperson name,sales total, commission, region code for each salesperson.

select sale_name, sales_tot, commission, region from salesperson;

SALE_NAME SALES_TOT COMMISSION REGIONKennedy, Bob 736345.32 0.075 0Robinson, Stephanie 374904.47 0.070 0Flores, Bob 173102.02 0.100 0Wyman, Eliska 566817.01 0.075 1Porter, Greg 439346.5 0.080 1McGuire, Sidney 208432.11 0.070 1Jones, Walter 422560.55 0.070 2Warren, Wayne 212638.5 0.075 2Stouffer, Bill 29053.3 0.080 2Williams, Steve 247179.99 0.075 3Kirk, James 0 0.075 3Lister, Dave 0 0.075 3Nash, Gail 306807.26 0.070 3Blades, Chris 0 0.080 3

7.2 Conditional Row Retrieval

If you need to retrieve only table rows that meet particular selection criteria, you can issue a select statement using the whereclause to specify a condition indicating just the rows you want. The where clause contains a conditional expression con-sisting of one or more relational expressions separated by operators as specified in the syntax given below.

select:select {* | colname[, colname]…} from tabname where cond_expr

cond_expr:rel_expr [bool_oper rel_expr]...

rel_expr:expression [not] rel_oper {expression | [{any | some} | all] (subquery)}

| expression [not] between constant and constant| expression [not] in {(constant[, constant]...) | (subquery)}| [tabname.]colname is [not] null| string_expr [not] like "pattern"| not rel_expr| ( cond_expr )| [not] exists (subquery)| [tabname.]colname *= [tabname.]colname| [tabname.]colname =* [tabname.]colname

expression:arith_expr | string_expr

arith_expr:arith_operand [arith_operator arith_operand]...

arith_operand:constant | [tabname.]colname | arith_function | ( arith_expr)

arith_operator:+ | - | * | /

Page 69: RDMs 8.4 SQL User's Guide

7. Retrieving Data from a Database

SQL User Guide 61

string_expr:string_operand [^ string_operand]

string_operand:"string" | [tabname.]colname

| if ( cond_expr, string_expr, string_expr)| string_function| user_defined_function

rel_oper:= | ==

| <| >| <=| >=| <> | != | /=

bool_oper:& | && | and

| "|" | "||" | or

For example, the following query chooses only customer accounts in the customer table (sales database) that are serviced bySidney McGuire (that is, accounts with sale_id equal to "SKM").

select sale_id, cust_id, company, city, state from customerwhere sale_id = "SKM";

SALE_ID CUST_ID COMPANY CITY STATESKM PHI Eagles Electronics Corp. Philadelphia PASKM PIT Steelers National Bank Pittsburgh PASKM WAS Redskins Outdoor Supply Co. Arlington VA

The next query example lists the sales_order rows for those orders that have not yet shipped (indicated by a null in the ship_date column) and where the amount is $50,000 or more.

select cust_id, ord_num, ord_date, amount from sales_orderwhere ship_date is null and amount > 50000.00;

CUST_ID ORD_NUM ORD_DATE AMOUNTBUF 2205 1997-01-03 150871.2DEN 2207 1997-01-06 274375GBP 2211 1997-01-10 53634.12NOS 2218 1997-01-24 81375DET 2219 1997-01-27 74034.9HOU 2226 1997-01-30 54875ATL 2230 1997-02-04 62340LAA 2234 1997-02-10 124660DEN 2237 1997-02-12 103874.8KCC 2241 1997-02-21 82315DET 2250 1997-03-06 82430.85PHO 2253 1997-03-16 143375CIN 2257 1997-03-23 62340NYJ 2270 1997-04-02 54875NEP 2281 1997-04-13 66341.5SFF 2284 1997-04-20 74315.16

Page 70: RDMs 8.4 SQL User's Guide

7. Retrieving Data from a Database

SQL User Guide 62

DET 2288 1997-04-24 252425GBP 2292 1997-04-30 77247.5NOS 2324 1997-07-30 104019.5

Note that the "ship_date is null" and not "ship_date != null" relational operator is required in order for the query to return thecorrect results. The SQL standard specifies that the result of a normal relational comparison with a null value is indeterminateand that only those rows in which the where clause evaluates to true are returned by a select statement. Since, "ship_date !=null' is, according to standard SQL, indeterminate, no rows would be returned from that select statement.

7.2.1 Retrieving Data from a Range

The between operator returns those rows where the left hand expression inclusively evaluates to a value between the two val-ues on the right. In the following example, the between operator will restrict the select result set to only those sales ordersmade from January 1 to January 31, 1997, inclusive.

select cust_id, ord_num, ord_date from sales_orderwhere ord_date between date "1997-1-1" and date "1997-1-31";

CUST_ID ORD_NUM ORD_DATECHI 2201 1997-01-02MIN 2202 1997-01-02KCC 2203 1997-01-02CIN 2204 1997-01-02BUF 2205 1997-01-03LAN 2206 1997-01-02DEN 2207 1997-01-06PHI 2208 1997-01-07PHO 2209 1997-01-07IND 2210 1997-01-09GBP 2211 1997-01-10ATL 2212 1997-01-15NYG 2213 1997-01-16LAA 2214 1997-01-16SEA 2215 1997-01-17KCC 2216 1997-01-21SDC 2217 1997-01-24NOS 2218 1997-01-24DET 2219 1997-01-27DEN 2220 1997-01-27NEP 2221 1997-01-27CLE 2222 1997-01-28MIN 2223 1997-01-28TBB 2224 1997-01-28SEA 2225 1997-01-29HOU 2226 1997-01-30IND 2227 1997-01-31

7.2.2 Retrieving Data from a List

You can use the in operator to choose only those rows that match one of the column values specified in the list. The exampleshows a select statement that retrieves all customers located in Pacific Coast states from the customer table.

Page 71: RDMs 8.4 SQL User's Guide

7. Retrieving Data from a Database

SQL User Guide 63

select cust_id, company, city, state from customerwhere state in ("CA", "OR", "WA");

CUST_ID COMPANY CITY STATESEA Seahawks Data Services Seattle WASFF Forty-Niners Venture Group San Francisco CALAA Raiders Development Co. Los Angeles CALAN Rams Data Processing, Inc. Los Angeles CASDC Chargers Credit Corp. San Diego CA

7.2.3 Retrieving Data by Wildcard Checking

The where clause can include a like operator to retrieve the rows where a character column's value match the wildcard patternspecified in the like string constant.

Two wildcard characters are defined in standard SQL.

Table Name Description

% (percent) Matches zero or more characters.

_ (underscore) Matches any single character.

Table 7-1. LIKE Operatior Wild Card Character Descriptions

The next example includes a select statement that retrieves from the customer table all customers who have "Data" as part oftheir company name.

select cust_id, company, city, state from customerwhere company like "%Data%";

CUST_ID COMPANY CITY STATESEA Seahawks Data Services Seattle WADAL Cowboys Data Services Dallas TXTBB Bucks Data Services Tampa FLLAN Rams Data Processing, Inc. Los Angeles CA

The application can change these match characters using the set wild statement.

7.2.4 Retrieving Rows by Rowid

RDM Server SQL provides a feature where rowid primary key columns can be declared in a table. The primary key value isautomatically assigned by the system to the row's location in the database file. This allows rows from that table to beaccessed directly through the primary key column. Even when no rowid primary key column has been declared in the table,RDM Server SQL exposes the rowid of each row of the table through use of the rowid keyword. All a user needs to is ref-erence a column called "rowid" in the select statement as shown in the example queries below.

select rowid, sale_id, sale_name, region, office, mgr_id from salesperson;

ROWID SALE_ID SALE_NAME REGION OFFICE MGR_ID6 BCK Kennedy, Bob 0 DEN BNF1 BNF Flores, Bob 0 SEA *NULL*

Page 72: RDMs 8.4 SQL User's Guide

7. Retrieving Data from a Database

SQL User Guide 64

3 BPS Stouffer, Bill 2 SEA *NULL*4 CMB Blades, Chris 3 SEA *NULL*14 DLL Lister, Dave 3 ATL *NULL*7 ERW Wyman, Eliska 1 NYC GAP2 GAP Porter, Greg 1 SEA *NULL*11 GSN Nash, Gail 3 DAL CMB13 JTK Kirk, James 3 ATL *NULL*8 SKM McGuire, Sidney 1 WDC GAP12 SSW Williams, Steve 3 ATL CMB5 SWR Robinson, Stephanie 0 LAX BNF9 WAJ Jones, Walter 2 CHI BPS10 WWW Warren, Wayne 2 MIN BPS

The rowid column should be qualified by a table name if there is more than one table listed in the from clause as shownbelow.

select salesperson.rowid, sale_name, customer.rowid, companyfrom salesperson, customer where salesperson.sale_id = customer.sale_id;

salesperson.rowid sale_name customer.rowid company6 Kennedy, Bob 17 Broncos Air Express6 Kennedy, Bob 34 Cardinals Bookmakers1 Flores, Bob 15 Seahawks Data Services1 Flores, Bob 31 Forty-niners Venture Group3 Stouffer, Bill 29 Colts Nuts & Bolts, Inc.7 Wyman, Eliska 23 Browns Kennels7 Wyman, Eliska 26 Jets Overnight Express7 Wyman, Eliska 27 Patriots Computer Corp.7 Wyman, Eliska 30 'Bills We Pay' Financial Corp.7 Wyman, Eliska 32 Giants Garments, Inc.2 Porter, Greg 39 Lions Motor Company11 Nash, Gail 19 Saints Software Support11 Nash, Gail 25 Oilers Gas and Light Co.11 Nash, Gail 33 Cowboys Data Services8 McGuire, Sidney 24 Steelers National Bank8 McGuire, Sidney 35 Redskins Outdoor Supply Co.8 McGuire, Sidney 42 Eagles Electronics Corp.12 Williams, Steve 28 Dolphins Diving School12 Williams, Steve 36 Falcons Microsystems, Inc.12 Williams, Steve 41 Bucs Data Services5 Robinson, Stephanie 16 Raiders Development Co.5 Robinson, Stephanie 18 Chargers Credit Corp.5 Robinson, Stephanie 20 Rams Data Processing, Inc.9 Jones, Walter 21 Chiefs Management Corporation9 Jones, Walter 22 Bengels Imports9 Jones, Walter 38 Bears Market Trends, Inc.10 Warren, Wayne 37 Vikings Athletic Equipment10 Warren, Wayne 40 Packers Van Lines

If more than one table is listed in the from clause and the rowid column is not qualified with a table name, the system willreturn the rowid from the first listed table. As with standard column references the qualifier name should be the correlationname when a correlation name as been specified, as shown in the example below.

select s.rowid, s.sale_name, c.rowid, c.city, c.statefrom salesperson s, customer c where s.sale_id = c.sale_id and s.region = 0;

Page 73: RDMs 8.4 SQL User's Guide

7. Retrieving Data from a Database

SQL User Guide 65

S.ROWID S.SALE_NAME C.ROWID C.CITY C.STATE6 Kennedy, Bob 17 Denver CO6 Kennedy, Bob 34 Phoenix AZ5 Robinson, Stephanie 16 Los Angeles CA5 Robinson, Stephanie 18 San Diego CA5 Robinson, Stephanie 20 Los Angeles CA1 Flores, Bob 15 Seattle WA1 Flores, Bob 31 San Francisco CA

Direct access retrieval will occur for queries of the following form:

select… from … where [tabname.]rowid = constant

select s.rowid, sale_name, company, city, statefrom salesperson s, customer c where s.sale_id = c.sale_id and s.rowid = 7;

S.ROWID SALE_NAME COMPANY CITY STATE7 Wyman, Eliska Browns Kennels Cleveland OH7 Wyman, Eliska Jets Overnight Express New York NY7 Wyman, Eliska Patriots Computer Corp. Foxboro MA7 Wyman, Eliska 'Bills We Pay' Financial Corp. Buffalo NY7 Wyman, Eliska Giants Garments, Inc. Jersey City NJ

7.3 Retrieving Data from Multiple Tables

A join associates two tables together common columns. Typically, but not always, the common columns will have the samenames. Join relationships can be explicitly defined between tables in the database definition through the specification ofprimary and foreign key clauses. But even where explicit joins have not been defined in the schema, joins between tableswith common columns can still be specified in a select statement.

RDM Server support two different methods for specifying joins. Old style join specifications are based on the 1989 ANSISQL standard in which all of the inter-table join relationships are specified in the select statement’s where clause. Extendedjoin specifications are based on the join enhancements originally introduced in the 1992 ANSI SQL standard in which thejoin relationships are specified in the from clause.

7.3.1 Old Style Join Specifications

Inner Joins

It is often necessary for an application to retrieve data from several related tables using a join. To form a join, issue a selectstatement that specifies each table name in the from clause. In the where clause, include an equality comparison of the asso-ciated columns (that is, the foreign and primary key columns) from the two tables. This comparison is called a join predicate.To differentiate between join columns of the same name in the two tables, the select statement must prefix the table names tothe column names in the comparison. An inner join is one in which only those rows from the two tables with matching valuesare returned. Join predicates are specified in the where clause as a relational expression according to the following syntax.

rel_expr:. . .

| [tabname.]colname = [tabname.]colname

Page 74: RDMs 8.4 SQL User's Guide

7. Retrieving Data from a Database

SQL User Guide 66

The example below retrieves and lists the customer accounts (customer table) for each salesperson (salesperson table).

select sale_name, company, city, state from salesperson, customerwhere salesperson.sale_id = customer.sale_id;

SALE_NAME COMPANY CITY STATEKennedy, Bob Broncos Air Express Denver COKennedy, Bob Cardinals BoOKmakers Phoenix AZFlores, Bob Seahawks Data Services Seattle WAFlores, Bob Forty-niners Venture Group San Francisco CAStouffer, Bill Colts Nuts & Bolts, Inc. Baltimore INWyman, Eliska Browns Kennels Cleveland OHWyman, Eliska Jets Overnight Express New York NYWyman, Eliska Patriots Computer Corp. Foxboro MAWyman, Eliska 'Bills We Pay' Financial Corp. Buffalo NYWyman, Eliska Giants Garments, Inc. Jersey City NJPorter, Greg Lions Motor Company Detroit MINash, Gail Saints Software Support New Orleans LANash, Gail Oilers Gas and Light Co. Houston TXNash, Gail Cowboys Data Services Dallas TXMcGuire, Sidney Steelers National Bank Pittsburgh PAMcGuire, Sidney Redskins Outdoor Supply Co. Arlington VAMcGuire, Sidney Eagles Electronics Corp. Philadelphia PAWilliams, Steve Dolphins Diving School Miami FLWilliams, Steve Falcons Microsystems, Inc. Atlanta GAWilliams, Steve Bucs Data Services Tampa FLRobinson, Stephanie Raiders Development Co. Los Angeles CARobinson, Stephanie Chargers Credit Corp. San Diego CARobinson, Stephanie Rams Data Processing, Inc. Los Angeles CAJones, Walter Chiefs Management Corporation Kansas City MOJones, Walter Bengels Imports Cincinnati OHJones, Walter Bears Market Trends, Inc. Chicago ILWarren, Wayne Vikings Athletic Equipment Minneapolis MNWarren, Wayne Packers Van Lines Green Bay WI

Your application can join any number of tables using the select statement. The next example illustrates a three-table join fromthe sales database that shows the January sales orders booked by Stephanie Robinson ("SWR").

select sale_name, cust_id, ord_date, ord_num, amountfrom salesperson, customer, sales_orderwhere salesperson.sale_id = "SWR" and

salesperson.sale_id = customer.sale_id andcustomer.cust_id = sales_order.cust_id andord_date between date "1997-1-1" and date "1997-1-31";

SALE_NAME CUST_ID ORD_DATE ORD_NUM AMOUNTRobinson,Stephanie LAN 1997-01-02 2206 15753.190000Robinson,Stephanie LAA 1997-01-16 2214 12614.340000Robinson,Stephanie SDC 1997-01-24 2217 705.980000

Outer Joins

An outer join between two tables includes those rows in one table that do not have any matching rows from the other table.A left outer join includes the rows for which the column on the left side of the join predicate do not have matching right-sidecolumn values. A right outer join does just the opposite. RDM Server SQL supports both left outer joins and right outer joinsas specified below.

Page 75: RDMs 8.4 SQL User's Guide

7. Retrieving Data from a Database

SQL User Guide 67

rel_expr:. . .

| [tabname.]colname *= [tabname.]colname| [tabname.]colname =* [tabname.]colname

Type of Join Operator

left outer join *=

right outer join =*

Table 7-2. Outer Join Relational Operators

The "outer" side column of an outer join predicate must be indexed or be a foreign key column on which acreate join has been declared in order for RDM Server SQL to be able to perform the outer join. Other-wise, a "No access path between outer joined tables" error will be returned by SQL.

The select statement in the following example uses a left outer join operator to retrieve the customers for each salesperson,whether or not that salesperson has any customers. The result set in this case will contain rows for all salespersons and nullfor the customer table columns for those salespersons who do not manage any customer accounts (e.g., salesperson managers).

select sale_name, company, city, state from salesperson, customerwhere salesperson.sale_id *= customer.sale_id;

SALE_NAME COMPANY CITYKennedy, Bob Broncos Air Express DenverKennedy, Bob Cardinals BoOKmakers PhoenixFlores, Bob Seahawks Data Services SeattleFlores, Bob Forty-niners Venture Group San FranciscoStouffer, Bill Colts Nuts & Bolts, Inc. BaltimoreBlades, Chris *NULL* *NULL*Lister, Dave *NULL* *NULL*Wyman, Eliska Browns Kennels ClevelandWyman, Eliska Jets Overnight Express New YorkWyman, Eliska Patriots Computer Corp. FoxboroWyman, Eliska 'Bills We Pay' Financial Corp. BuffaloWyman, Eliska Giants Garments, Inc. Jersey CityPorter, Greg Lions Motor Company DetroitNash, Gail Saints Software Support New OrleansNash, Gail Oilers Gas and Light Co. HoustonNash, Gail Cowboys Data Services DallasKirk, James *NULL* *NULL*McGuire, Sidney Steelers National Bank PittsburghMcGuire, Sidney Redskins Outdoor Supply Co. ArlingtonMcGuire, Sidney Eagles Electronics Corp. PhiladelphiaWilliams, Steve Dolphins Diving School MiamiWilliams, Steve Falcons Microsystems, Inc. AtlantaWilliams, Steve Bucs Data Services TampaRobinson, Stephanie Raiders Development Co. Los AngelesRobinson, Stephanie Chargers Credit Corp. San DiegoRobinson, Stephanie Rams Data Processing, Inc. Los AngelesJones, Walter Chiefs Management Corporation Kansas CityJones, Walter Bengels Imports CincinnatiJones, Walter Bears Market Trends, Inc. ChicagoWarren, Wayne Vikings Athletic Equipment MinneapolisWarren, Wayne Packers Van Lines Green Bay

Page 76: RDMs 8.4 SQL User's Guide

7. Retrieving Data from a Database

SQL User Guide 68

As you can see, the outer join result includes rows from the salesperson table that do not have any customers. This is exactlywhat the outer join does. Compare with the results from the earlier query.

As long as the table names are unique, you need do nothing different to perform a join between tables in different databases.The following example retrieves the descriptions of the specific products (product table in invntory database) ordered in theStephanie Robinson ("SWR") sales orders.

select cust_id, ord_num, prod_id, prod_descfrom customer, sales_order, item, productwhere sale_id = "SWR" and ord_date between @"97-1-1" and @"97-1-31" and

customer.cust_id = sales_order.cust_id andsales_order.ord_num = item.ord_num anditem.prod_id = product.prod_id;

CUST_ID ORD_NUM PROD_ID PROD_DESCLAA 2214 13016 RISC 16MB computerLAA 2214 17419 19in SVGA monitorLAA 2214 18060 60MB cartridge tape driveLAA 2214 19100 flat-bed plotterSDC 2217 22024 1200/2400 baud modemSDC 2217 23401 track ballLAN 2206 10450 486/50 computerLAN 2206 15750 750 MB hard disk driveLAN 2206 17214 14in VGA monitorLAN 2206 18120 120MB cartridge tape driveLAN 2206 18121 120MB tape cartridgeLAN 2206 23200 enhanced keyboardLAN 2206 23400 mouse

If both databases have a table with the same name the table names listed in the from clause will need to be qualified with thedatabase name as indicated by the syntax shown below.

from_clause:from [dbname.]tabname [, [dbname.]tabname ]...

For example, assume that both the sales database and the invntory database contain a table named "product". In the fromclause of the select statement, the name of the product table is prefixed with the database name "invntory". However, notethat the prod_id column in the select column list is not qualified. RDM Server assumes that an unqualified duplicate columnname is from the first table in the from list that contains a column of that name. Since the prod_id column values from bothtables is the same, it doesn't really matter which column is returned by the select statement.

select cust_id, ord_num, prod_id, prod_descfrom customer, sales_order, item, invntory.productwhere sale_id = "SWR" and ord_date between @"97-1-1" and @"97-1-31" and

customer.cust_id = sales_order.cust_id andsales_order.ord_num = item.ord_num anditem.prod_id = product.prod_id;

CUST_ID ORD_NUM PROD_ID PROD_DESCLAA 2214 13016 RISC 16MB computerLAA 2214 17419 19in SVGA monitorLAA 2214 18060 60MB cartridge tape driveLAA 2214 19100 flat-bed plotterSDC 2217 22024 1200/2400 baud modemSDC 2217 23401 track ballLAN 2206 10450 486/50 computer

Page 77: RDMs 8.4 SQL User's Guide

7. Retrieving Data from a Database

SQL User Guide 69

LAN 2206 15750 750 MB hard disk driveLAN 2206 17214 14in VGA monitorLAN 2206 18120 120MB cartridge tape driveLAN 2206 18121 120MB tape cartridgeLAN 2206 23200 enhanced keyboardLAN 2206 23400 mouse

Correlation Names

Sometimes an application must use the same select statement to reference two tables with the same name from separate data-bases. In that case, the from clause must include correlation names to distinguish between the two table references. Cor-relation names are aliased identifiers specified following the table name as shown in the following from clause syntax .

from_clause:from [dbname.]tabname [[as] corrname][, [dbname.]tabname [[as] corrname]]...

The correlation name, corrname, is an identifier defined as an alias for the table name that can be used to qualify columnnames in that table that are referenced in the select statement.

Suppose that the product table in the invntory database is named item instead of product. Then the information in theexample above would be specified as follows.

select cust_id, ord_num, prod_id, prod_descfrom customer, sales_order, sales.item s_item, invntory.item i_itemwhere sale_id = "SWR" and

ord_date between @"97-1-1" and @"97-1-31" andcustomer.cust_id = sales_order.cust_id andsales_order.ord_num = item.ord_num ands_item.prod_id = i_item.prod_id;

In this example, the correlation name for the sales database item table is s_item, and the correlation name for the invntory data-base item table is i_item.

Correlation names are required when processing a self-join. A self-join is a join of a table with itself. The mgr_id column inthe salesperson table is a foreign key to the salesperson table. A self-join can be used to list all salespersons along with theirmanagers as shown in the following example. Notice how correlation names are used to distinguish between the manager'srow and the salesperson's row.

select emp.sale_name, mgr.sale_namefrom salesperson emp, salesperson mgr where emp.mgr_id = mgr.sale_id;

EMP.SALE_NAME MGR.SALE_NAMEKennedy, Bob Flores, BobWarren, Wayne Stouffer, BillWilliams, Steve Blades, ChrisWyman, Eliska Porter, GregJones, Walter Stouffer, BillMcGuire, Sidney Porter, GregNash, Gail Blades, ChrisRobinson, Stephanie Flores, Bob

Page 78: RDMs 8.4 SQL User's Guide

7. Retrieving Data from a Database

SQL User Guide 70

Column Aliases

The columns specified in a select result column list can be assigned aliases as specified below.

select:select select_item [, select_item]... from_clause

[where cond_expr]select_item:

[tabname | corrname.]colname [ identifier | "headingstring" ]

The identifier or "headingstring" will be displayed in the result set heading instead of the column name. The last example isshown below but using the column aliases "employee" and "manager".

select emp.sale_name employee, mgr.sale_name managerfrom salesperson emp, salesperson mgr where emp.mgr_id = mgr.sale_id;

EMPLOYEE MANAGERKennedy, Bob Flores, BobWarren, Wayne Stouffer, BillWilliams, Steve Blades, ChrisWyman, Eliska Porter, GregJones, Walter Stouffer, BillMcGuire, Sidney Porter, GregNash, Gail Blades, ChrisRobinson, Stephanie Flores, Bob

7.3.2 Extended Join Specifications

The 1992 ANSI SQL standard introduced a new method by which joins between tables can be specified. This new methodseparates the information needed to form the joins from the where clause and places it in the from clause of the select state-ment. In addition, the 1992 standard also enhanced join handling to allow the specification of left and right outer joins (the1989 standard only allowed for inner joins, the "*=" and "=*" outer join operators described in the last section are non-stand-ard).

The enhanced syntax for the select statement from clause that incorporates join specifications is given below.

select:select {* | select_item [, select_item]...}

from table_ref [, table_ref]...

table_ref:table_primary | table_join

table_primary:table_name_spec | ( table_join )

table_name_spec:[dbname.]tabname [[as] corrname]

table_join:natural_join | qualified_join | cross_join

natural_join:table_ref natural [inner | {left | right} [outer]] join table_primary

Page 79: RDMs 8.4 SQL User's Guide

7. Retrieving Data from a Database

SQL User Guide 71

qualified_join:table_ref [inner | {left | right} [outer]] join table_primary

{using (colname[, colname]... ) on cond_expr}

cross_join:table_ref cross join table_primary

The natural join specification indicates that the join is to be performed based on the common columns (names and types)from the two tables. RDM Server will perform the join based on the columns from the table (or tables) specified on the leftside of "natural … join" with those columns from the table (or tables) on the right side that have the same name. The examplebelow gives a natural inner join between the salesperson and customer tables.

select sale_name, company from salesperson natural inner join customer;

SALE_NAME COMPANYKennedy, Bob Broncos Air ExpressKennedy, Bob Cardinals BookmakersFlores, Bob Seahawks Data ServicesFlores, Bob Forty-niners Venture GroupStouffer, Bill Colts Nuts & Bolts, Inc.Wyman, Eliska Browns KennelsWyman, Eliska Jets Overnight ExpressWyman, Eliska Patriots Computer Corp.Wyman, Eliska 'Bills We Pay' Financial Corp.Wyman, Eliska Giants Garments, Inc.Porter, Greg Lions Motor CompanyNash, Gail Saints Software SupportNash, Gail Oilers Gas and Light Co.Nash, Gail Cowboys Data ServicesMcGuire, Sidney Steelers National BankMcGuire, Sidney Redskins Outdoor Supply Co.McGuire, Sidney Eagles Electronics Corp.Williams, Steve Dolphins Diving SchoolWilliams, Steve Falcons Microsystems, Inc.Williams, Steve Bucs Data ServicesRobinson, Stephanie Raiders Development Co.Robinson, Stephanie Chargers Credit Corp.Robinson, Stephanie Rams Data Processing, Inc.Jones, Walter Chiefs Management CorporationJones, Walter Bengels ImportsJones, Walter Bears Market Trends, Inc.Warren, Wayne Vikings Athletic EquipmentWarren, Wayne Packers Van Lines

The common column between the two tables is sale_id so the above natural inner join example is equivalent to the fol-lowing old style join:

select sale_name, company from salesperson, customerwhere salesperson.sale_id = customer.sale_id;

A natural left (right) outer join includes the results of the inner join plus those rows of the left (right) table that do not havea corresponding matching row in the joined table. This is illustrated below where the last example is changed from a naturalinner join to a natural left outer join.

Page 80: RDMs 8.4 SQL User's Guide

7. Retrieving Data from a Database

SQL User Guide 72

select sale_name, company from salesperson natural left outer join customer;

SALE_NAME COMPANYKennedy, Bob Broncos Air ExpressKennedy, Bob Cardinals BookmakersFlores, Bob Seahawks Data ServicesFlores, Bob Forty-niners Venture GroupStouffer, Bill Colts Nuts & Bolts, Inc.Blades, Chris *NULL*Lister, Dave *NULL*Wyman, Eliska Browns KennelsWyman, Eliska Jets Overnight ExpressWyman, Eliska Patriots Computer Corp.Wyman, Eliska 'Bills We Pay' Financial Corp.Wyman, Eliska Giants Garments, Inc.Porter, Greg Lions Motor CompanyNash, Gail Saints Software SupportNash, Gail Oilers Gas and Light Co.Nash, Gail Cowboys Data ServicesKirk, James *NULL*McGuire, Sidney Steelers National BankMcGuire, Sidney Redskins Outdoor Supply Co.McGuire, Sidney Eagles Electronics Corp.Williams, Steve Dolphins Diving SchoolWilliams, Steve Falcons Microsystems, Inc.Williams, Steve Bucs Data ServicesRobinson, Stephanie Raiders Development Co.Robinson, Stephanie Chargers Credit Corp.Robinson, Stephanie Rams Data Processing, Inc.Jones, Walter Chiefs Management CorporationJones, Walter Bengels ImportsJones, Walter Bears Market Trends, Inc.Warren, Wayne Vikings Athletic EquipmentWarren, Wayne Packers Van Lines

This statement is equivalent to the old style outer join:

select sale_name, company from salesperson, customerwhere salesperson.sale_id *= customer.sale_id;

An inner join is the default so that the specification of "natural join" produces a natural inner join. For outer joins, "outer"does not need to be specified. The following example requests a natural inner join between salesperson and customer and anatural left outer join between customer and sales_order.

select sale_name, company, ord_num, ord_date, amountfrom salesperson natural join customer natural left join sales_order;

SALE_NAME COMPANY ORD_NUM ORD_DATE AMOUNTKennedy, Bob Broncos Air Express 2207 1997-01-06 274375Kennedy, Bob Broncos Air Express 2220 1997-01-27 49980Kennedy, Bob Broncos Air Express 2237 1997-02-12 103874.8Kennedy, Bob Broncos Air Express 2264 1997-04-01 21950Kennedy, Bob Broncos Air Express 2282 1997-04-14 21950Kennedy, Bob Broncos Air Express 2304 1997-05-26 19995Kennedy, Bob Broncos Air Express 2321 1997-06-24 6827.96Kennedy, Bob Cardinals Bookmakers 2209 1997-01-07 3715.83

Page 81: RDMs 8.4 SQL User's Guide

7. Retrieving Data from a Database

SQL User Guide 73

Kennedy, Bob Cardinals Bookmakers 2253 1997-03-16 143375Kennedy, Bob Cardinals Bookmakers 2269 1997-04-02 35119.46Kennedy, Bob Cardinals Bookmakers 2301 1997-05-14 16227.27Kennedy, Bob Cardinals Bookmakers 2313 1997-06-12 38955Flores, Bob Seahawks Data Services 2215 1997-01-17 16892Flores, Bob Seahawks Data Services 2225 1997-01-29 2987.5Flores, Bob Seahawks Data Services 2229 1997-02-04 8824.56...Jones, Walter Bears Market Trends, Inc. 2249 1997-03-04 28570Jones, Walter Bears Market Trends, Inc. 2271 1997-04-03 49584.65Jones, Walter Bears Market Trends, Inc. 2295 1997-05-06 31580Warren, Wayne Vikings Athletic Equipment 2202 1997-01-02 25915.86Warren, Wayne Vikings Athletic Equipment 2223 1997-01-28 408Warren, Wayne Vikings Athletic Equipment 2248 1997-02-28 3073.54Warren, Wayne Vikings Athletic Equipment 2266 1997-04-01 5190.42Warren, Wayne Vikings Athletic Equipment 2296 1997-05-07 2790.99Warren, Wayne Vikings Athletic Equipment 2315 1997-06-17 12082.39Warren, Wayne Packers Van Lines 2211 1997-01-10 53634.12Warren, Wayne Packers Van Lines 2235 1997-02-11 8192.38Warren, Wayne Packers Van Lines 2292 1997-04-30 77247.5Warren, Wayne Packers Van Lines 2327 1997-06-30 24103.3

Natural joins will form the join based on equal values from all columns in the joined tables that have the same name. In theexamples above, there is only one common column name between salesperson and customer, sale_id, and one between cus-tomer and sales_order, cust_id. The customer table in the sales database shares two common columns with the outlet table inthe invntory database: city and state. In the next example, a natural join between the customer and outlet tables will producethose customers that are located in the same city and state where there is a distribution outlet.

select company, city, state from customer natural join outlet;

COMPANY CITY STATESeahawks Data Services Seattle WARaiders Development Co. Los Angeles CARams Data Processing, Inc. Los Angeles CAChargers Credit Corp. San Diego CAForty-niners Venture Group San Francisco CACowboys Data Services Dallas TXOilers Gas and Light Co. Houston TXPatriots Computer Corp. Foxboro MABears Market Trends, Inc. Chicago ILChiefs Management Corporation Kansas City MOChiefs Management Corporation Kansas City MO'Bills We Pay' Financial Corp. Buffalo NYJets Overnight Express New York NYFalcons Microsystems, Inc. Atlanta GAVikings Athletic Equipment Minneapolis MNBroncos Air Express Denver CO

A qualified join is like a natural join except that it requires that the columns on which the join is to be formed be explicitlyspecified. Two specification methods are provided. With the using clause requires you name the common column namesbetween the joined tables which are to be used to form the join. With the on clause you specify the join predicates as con-ditional expressions exactly as they would be specified in the where clause under the old style joins. The on clause is neces-sary whenever the join is to be performed between columns that do not have the same name.

The using clause allows you to choose only the matching columns on which you want the join formed. So, for example, tolist those customers located in the same state, but not necessarily the same city, as a distribution outlet you would use the fol-lowing statement:

Page 82: RDMs 8.4 SQL User's Guide

7. Retrieving Data from a Database

SQL User Guide 74

select company, city, state from customer inner join outlet using(state);

COMPANY CITY STATESeahawks Data Services Seattle WARaiders Development Co. Los Angeles CARams Data Processing, Inc. Los Angeles CAChargers Credit Corp. San Diego CAForty-niners Venture Group San Francisco CACowboys Data Services Dallas TXOilers Gas and Light Co. Houston TXPatriots Computer Corp. Foxboro MABears Market Trends, Inc. Chicago ILChiefs Management Corporation Kansas City MOChiefs Management Corporation Kansas City MO'Bills We Pay' Financial Corp. Buffalo NYJets Overnight Express New York NYFalcons Microsystems, Inc. Atlanta GAVikings Athletic Equipment Minneapolis MNBroncos Air Express Denver CO

It is usually a good database design principle for the columns on which different tables could be joined to have the samenames. Doing so will greatly simplify the select statement join specifications. However, there are situations in which this isjust not possible and a join is needed in which the columns on which the join is to be made cannot have the same name. Onesuch situation occurs in a self-referencing join, a join that is performed on the same table. For example, the salesperson table’sprimary key is sale_id but salesperson also contains a column named mgr_id that is a foreign key reference to the row in thesalesperson table associated with that salesperson’s manager. The following example gives a select statement that lists all man-agers along with those sales persons that they manage. Note that correlation names must be specified for the two salespersonreferences in the from clause in order to differentiate the manager rows from the employee rows.

select mgr.sale_name, emp.sale_namefrom salesperson mgr join salesperson empon mgr.sale_id = emp.mgr_id;

MGR.SALE_NAME EMP.SALE_NAMEFlores, Bob Robinson, StephanieFlores, Bob Kennedy, BobStouffer, Bill Jones, WalterStouffer, Bill Warren, WayneBlades, Chris Nash, GailBlades, Chris Williams, StevePorter, Greg Wyman, EliskaPorter, Greg McGuire, Sidney

Parentheses are sometimes needed to be used to group joins when more than two tables are involved in the from clause. Theyare required when one table needs to be joined with two or more tables. For example, the statement below produces a list ofproduct orders for those customers who are located in cities where an distribution outlet is also located. A natural joinbetween the customer table and both the sales_order table (based on the cust_id column) and the outlet table (based on thecity and state columns) will accomplish this.

select company, city, prod_id, quantityfrom customer natural join(sales_order natural join item natural join outlet);

COMPANY CITY PROD_ID QUANTITYSeahawks Data Services Seattle 16311 20

Page 83: RDMs 8.4 SQL User's Guide

7. Retrieving Data from a Database

SQL User Guide 75

Seahawks Data Services Seattle 18061 200Seahawks Data Services Seattle 18121 500Seahawks Data Services Seattle 16511 250...Broncos Air Express Denver 15340 2Broncos Air Express Denver 17214 2Broncos Air Express Denver 20303 2Broncos Air Express Denver 23200 2Broncos Air Express Denver 23400 2

By grouping the natural joins between sales_order, item, and outlet together with parentheses, the group is treated like asingle table to which a natural join with customer is then formed. The common columns between customer and sales_order(cust_id), item (none), and outlet (city and state) becomes the basis on which the natural join is performed.

There can be no duplicate common column names between the table (or tables) on the left side of a join andthe table (or tables) on the right side of the join.

A cross join is simply a cross product of the two tables where each row of the left table is joined with each row of the righttable so that the cardinality of the result (i.e., the number of result rows) is equal to the product of the cardinalities of the twotables. An on clause cannot be specified with a cross join. However, there is nothing that restricts including join conditionsin the where clause. In practice, there are very few times when a cross join is needed and since it can be a very expensiveoperation that can potentially produce huge result sets, its use should be avoided.

7.4 Sorting the Rows of the Result Set

You can sort the result set produced by the select statement by using an order by clause that conforms to the following syn-tax.

select:select [first | all | distinct] {* | select_item [, select_item]...}

from table_ref [, table_ref]...[where cond_expr][order by {number | colname} [asc | desc] [, {number | colname} [asc | desc]]...]

The order by clause identifies the result set columns which are to be sorted and whether the column value is to be sorted inascending or descending order. The sort columns are identified either by the ordinal number it appears in the select resultcolumn list beginning with 1 or by the name (or alias) of the column.

For example, the statement shown below sorts the salesperson table in alphabetical order by salesperson name (sale_namecolumn).

select * from salesperson order by sale_name;

SALE_ID SALE_NAME DOB COMMISSION REGION OFFICE MGR_IDCMB Blades, Chris 1958-09-08 0.080 3 SEA *NULL*BNF Flores, Bob 1943-07-17 0.100 0 SEA *NULL*WAJ Jones, Walter 1960-06-15 0.070 2 CHI BPSBCK Kennedy, Bob 1956-10-29 0.075 0 DEN BNFJTK Kirk, James 2100-08-30 0.075 3 ATL *NULL*DLL Lister, Dave 1999-08-30 0.075 3 ATL *NULL*SKM McGuire, Sidney 1947-12-02 0.070 1 WDC GAPGSN Nash, Gail 1954-10-20 0.070 3 DAL CMB

Page 84: RDMs 8.4 SQL User's Guide

7. Retrieving Data from a Database

SQL User Guide 76

GAP Porter, Greg 1949-03-03 0.080 1 SEA *NULL*SWR Robinson, Stephanie 1968-10-11 0.070 0 LAX BNFBPS Stouffer, Bill 1952-11-21 0.080 2 SEA *NULL*WWW Warren, Wayne 1953-04-29 0.075 2 MIN BPSSSW Williams, Steve 1944-08-30 0.075 3 ATL CMBERW Wyman, Eliska 1959-05-18 0.075 1 NYC GAP

As noted above, you can specify columns listed in the order by clause by name or by number. The following lists the sales-person names and birth dates in birth date order.

select sale_name, dob from salesperson order by 2;

SALE_NAME DOBFlores, Bob 1943-07-17Porter, Greg 1949-03-03Stouffer, Bill 1952-11-21Kennedy, Bob 1956-10-29Blades, Chris 1958-09-08Robinson, Stephanie 1968-10-11

You can use the order by clause to sort on more than one column. Additionally, the clause can be used to specify whethereach column is in ascending (the default) or descending order. In the following example, column 1 is the primary sort column.

select commission, sale_name from salesperson order by 1 desc, 2 asc;

COMMISSION SALE_NAME0.100 Flores, Bob0.080 Blades, Chris0.080 Porter, Greg0.080 Stouffer, Bill0.075 Robinson, Stephanie0.070 Kennedy, Bob

The query below returns the sale total for the sales orders entered on or after 6-1-1997 where the "amount+tax" result columnis assigned alias sale_tot which is referenced in the order by clause.

select ord_num, amount+tax sale_tot from sales_orderwhere ord_date >= date "1997-06-01"order by sale_tot desc;

ORD_NUM SALE_TOT2324 104019.52310 51283.97002929692317 49778.76006835942313 389552323 35582.52308 32675.68999023442311 32589.60009765632319 31602.15009765622320 277822318 27239.10009765632322 25231.982327 24103.32326 22887.962325 21532.0899902344

Page 85: RDMs 8.4 SQL User's Guide

7. Retrieving Data from a Database

SQL User Guide 77

2314 207802309 17388.66003417972316 16986.992312 16598.00000488282315 12940.23997558592321 7251.289986572272307 4487.76

As you can see, the select statement result columns can be computational as described in the next section.

7.5 Retrieving Computational Results

Besides retrieving the values of individual columns, a select statement allows you to specify expressions that can performarithmetic operations on the columns in a table. The normal arithmetic operators (+, -, *, /) along with a wide range of built-infunctions can be included in a select column expression. The complete syntax for column expressions is given below.

select:select [first | all | distinct] {* | select_item [, select_item]...}

from table_ref [, table_ref]...[where cond_expr][order by col_ref [asc | desc] [, col_ref [asc | desc]]...]

select_item:{tabname | corrname}.* | expression} [identifier | "headingstring"]

expression:arith_expr | string_expr

arith_expr:arith_operand [arith_operator arith_operand]...

arith_operand:constant | [tabname.]colname | arith_function | ( arith_expr)

arith_operator:+ | - | * | /

arith_function:numeric_function | datetime_function | system_function

| user_defined_function

string_expr:string_operand [^ string_operand]

string_operand:"string" | [tabname.]colname

| if ( cond_expr, string_expr, string_expr)| string_function| user_defined_function

numeric_function: See Table 7-3.datetime_function: See Table 7-4.string_function: See Table 7-5.system_function: See Table 7-6.

Page 86: RDMs 8.4 SQL User's Guide

7. Retrieving Data from a Database

SQL User Guide 78

7.5.1 Simple Expressions

The query example below shows the salespersons' orders with the largest earned commissions. The select statement computesthe commission earned by multiplying the commission rate by the amount of the order. It accesses the name of a salespersonby using a three-table join and sorts the result in descending order by earned commission.

select sale_name, ord_num, amount*commissionfrom salesperson, customer, sales_orderwhere salesperson.sale_id = customer.sale_id

and customer.cust_id = sales_order.cust_idorder by 3 desc;

SALE_NAME ORD_NUM AMOUNT*COMMISSIONKennedy, Bob 2207 20578.125000Porter, Greg 2288 20194.000000Wyman, Eliska 2205 11315.340000Kennedy, Bob 2253 10753.125000Robinson, Stephanie 2234 8726.200000Kennedy, Bob 2237 7790.610000Flores, Bob 2284 7431.516000Nash, Gail 2324 7281.365000Porter, Greg 2250 6594.468000Porter, Greg 2219 5922.792000Warren, Wayne 2292 5793.562500Jones, Walter 2241 5762.050000

Note that, because column 3 contains an expression rather than a simple column name, the order by clause is needed in orderto use the column number. You could also use a column alias, as shown in the equivalent query below.

select sale_name, ord_num, amount*commission earnedfrom salesperson, customer, sales_orderwhere salesperson.sale_id = customer.sale_id

and customer.cust_id = sales_order.cust_idorder by earned desc;

SALE_NAME ORD_NUM EARNEDKennedy, Bob 2207 20578.125000Porter, Greg 2288 20194.000000Wyman, Eliska 2205 11315.340000...

In the next example, the select statement retrieves the amount that the company receives from each of the orders shown in theexample above. The amount to the company is simply the order amount minus the commission.

select sale_name, ord_num, amount-amount*commission "NET REVENUE"from salesperson, customer, sales_orderwhere salesperson.sale_id = customer.sale_id

and customer.cust_id = sales_order.cust_idorder by 3 desc;

SALE_NAME ORD_NUM NET REVENUEPorter, Greg 2288 232231.000451Kennedy, Bob 2207 255168.749918Kennedy, Bob 2253 133338.749957Robinson, Stephanie 2234 115933.799963Kennedy, Bob 2237 96603.563969

Page 87: RDMs 8.4 SQL User's Guide

7. Retrieving Data from a Database

SQL User Guide 79

Porter, Greg 2250 75836.382147Porter, Greg 2219 68112.108132

Arithmetic operators that are specified in an expression are evaluated based on the precedence given in the following table.

Priority Operator Use

Highest () Parenthetical expressions

High + Unary plus

High - Unary minus

Medium * Multiplication

Medium / Division

Lowest + Addition

Lowest - Subtraction

Table 7-2. Precedence of Arithmetic Operators

7.5.2 Built-in (Scalar) Functions

RDM Server SQL provides many built-in functions that can be used in select statement expressions. Four classes of built-infunctions are provided as noted in the select statement syntax shown above: numeric, datetime, system, and string functions.These functions are called scalar functions because for a given set of argument values they each return a single value. Func-tions described in the next section are called aggregate functions because they perform computations over a set (group) ofrows.

The built-in numeric functions provided in RDM Server SQL are described in the following table.

Function Descriptionabs(arith_expr) Returns the absolute value of an expression.acos(arith_expr) Returns the arccosine of an expression.asin(arith_expr) Returns the arcsine of an expression.atan(arith_expr) Returns the arctangent of an expression.atan2(arith_expr) Returns the arctangent of an x-y coordinate pair.{ceil | ceiling}(arith_expr)

Finds the upper bound for an expression.

cos(arith_expr) Returns the cosine of an angle.cot(arith_expr) Returns the cotangent of an angle.exp(arith_expr) Returns the value of an exponential function.floor(arith_expr) Finds the lower bound for an expression.{ln | log}(arith_expr) Returns the natural logarithm of an expression.mod(arith_expr1, arith_expr2)

Returns the remainder of arith_expr1/arith_expr2.

pi() Returns the value of pi.rand(num) Returns next random floating-point number. Non-zero num is seed.sign(arith_expr) Returns the sign of an expression (-1, 0, +1).sin(arith_expr) Returns the sine of an angle.sqrt(arith_expr) Returns the square root of an expression.tan(arith_expr) Returns the tangent of an angle.

Table 7-3. Built-in Numeric Functions

Page 88: RDMs 8.4 SQL User's Guide

7. Retrieving Data from a Database

SQL User Guide 80

The example below calls the floor function to truncate the cents portion from the amount column in the sales_order table forall orders made by Seahawks Data Services.

select ord_num, ord_date, floor(amount) from sales_orderwhere cust_id = "SEA";

ORD_NUM ORD_DATE FLOOR(AMOUNT)2215 1997-01-17 168922225 1997-01-29 29872229 1997-02-04 88242258 1997-03-23 13652273 1997-04-03 6502311 1997-06-05 30036

Function Descriptionage(dt_expr) Returns the age (in full years).{curdate | current_date}() Returns the current date.{curtime | current_time}() Returns the current time.current_timestamp() Returns the current date and timedayofmonth(dt_expr) Returns the day of the month.dayofweek(dt_expr) Returns the day of the week.dayofyear(dt_expr) Returns the day of the year.hour(dt_expr) Returns the hour.minute(dt_expr) Returns the minute.month(dt_expr) Returns the month.now() Returns the current date and time.quarter(dt_expr) Returns the quarter.second(dt_expr) Returns the second.week(dt_expr) Returns the week.year(dt_expr) Returns the year.

Table 7-4. Built-in Date/Time Functions

The next query returns the age for each salesperson on April 19, 2012. As you can see, it is an experienced sales staff exceptfor Dave Lister who is only 12 and James T. Kirk who will not be born for another 89 years!

select sale_name, dob, curdate(), age(dob) from salesperson;

SALE_NAME DOB CURDATE() AGE(DOB)Flores, Bob 1943-07-17 2012-04-19 68Blades, Chris 1958-09-08 2012-04-19 53Porter, Greg 1949-03-03 2012-04-19 63Stouffer, Bill 1952-11-21 2012-04-19 59Kennedy, Bob 1956-10-29 2012-04-19 55Kirk, James 2100-08-30 2012-04-19 -89Lister, Dave 1999-08-30 2012-04-19 12Warren, Wayne 1953-04-29 2012-04-19 58Williams, Steve 1944-08-30 2012-04-19 67Wyman, Eliska 1959-05-18 2012-04-19 52Jones, Walter 1960-06-15 2012-04-19 51McGuire, Sidney 1947-12-02 2012-04-19 64

Page 89: RDMs 8.4 SQL User's Guide

7. Retrieving Data from a Database

SQL User Guide 81

Nash, Gail 1954-10-20 2012-04-19 57Robinson, Stephanie 1968-10-11 2012-04-19 43

Function Descriptionascii(string_expr) Returns the numeric ASCII value of a characterchar(num) Returns the ASCII character with numeric value numconcat(string_expr1,string_expr2)

Concatenates two strings

insert(string_expr1,num1, num2, string_expr2)

Replace num2 chars from string_expr2 in string_expr1 beginning at position num1 (1st pos-ition is 1 not 0)

lcase(string_expr) Converts a string to lowercaseleft(string_expr, num) Returns the leftmost num characters from the stringlength(string_expr) Returns the length of the stringlocate(string_expr1,string_expr2, num)

Locate string_expr1 from position num in string_expr2

ltrim(string_expr) Removes all leading spaces from stringrepeat(string_expr, num) Repeats string num timesreplace(string_expr1,string_expr2, string_expr3)

Replace string_expr2 with string_expr3 in string_expr1

right(string_expr, num) Returns the rightmost num characters from stringrtrim(string_expr) Removes all trailing spaces from stringsubstring(string_expr1,num1, num2)

Returns num2 characters from string_expr beginning at position num1.

ucase(string_expr) Convert string to uppercaseunicode(string_expr) Returns the numeric Unicode value of a characterwchar(num) Returns a Unicode character with numeric value num.

Table 7-5. Built-in String Functions

The next query displays the customer company names and their lengths with the longest listed first.

select company, length(company) from customer order by 2 desc;

COMPANY LENGTH(COMPANY)'Bills We Pay' Financial Corp. 30Chiefs Management Corporation 29Redskins Outdoor Supply Co. 27Falcons Microsystems, Inc. 26Forty-niners Venture Group 26Rams Data Processing, Inc. 26

...Broncos Air Express 19Lions Motor Company 19Bucs Data Services 18Packers Van Lines 17Bengels Imports 15Browns Kennels 14

The built-in system functions provided in RDM Server SQL are described in the table below.

Page 90: RDMs 8.4 SQL User's Guide

7. Retrieving Data from a Database

SQL User Guide 82

Function Descriptionconvert(expression,type)

Converts expression result to specified data type.

convert(expression,{char | wchar}, width,format)

Converts expresions to string of no more than width characters according to the specifiedformat.

database() Returns string containing a comma-separated list of the currently opened databases.if(cond_expr, expres-sion1, expression2)

Returns result of expression1 if cond_expr is true , otherwise expression2.

ifnull(expression1,expression2)

Returns result of expression1 if not null, otherwise expression2.

user() Returns user name as a string.

Table 7-6. Built-in System Functions

One of the features of the RDM Server select statement is that you can use it as a simple calculator by not specifying a from(or any other) clause. For example, the user and database functions return values that do not derive from any particular data-base so the following select simply returns their current values.

select user(), database();

USER() DATABASE()admin invntory,sales

Use of the if and convert functions are described in detail in the next two sections.

7.5.3 Conditional Column Selection

The conditional if function allows you to select an expression result based on a specified condition applied to each result rowfor a select statement. The if function syntax is as follows.

if(cond_expr, expression, expression)For each row in which the conditional expression, cond_expr, evalutes is true, the function returns the result of evaluating thefirst expression. If the condition is false, the second expression is evaluated and its result is returned.

The following example uses the if function to identify which customers are located "In-state" or "Out-of-state" where the stateis the beautiful state of Washington located in the great Pacific Northwest of the USA!

select company, if(state = "WA", "In-state", "Out-of-state") location from customer;

COMPANY LOCATIONCardinals Bookmakers Out-of-stateRaiders Development Co. Out-of-stateRams Data Processing, Inc. Out-of-stateChargers Credit Corp. Out-of-stateForty-niners Venture Group Out-of-stateBroncos Air Express Out-of-stateDolphins Diving School Out-of-stateBucs Data Services Out-of-stateFalcons Microsystems, Inc. Out-of-stateBears Market Trends, Inc. Out-of-stateColts Nuts & Bolts, Inc. Out-of-stateSaints Software Support Out-of-state

Page 91: RDMs 8.4 SQL User's Guide

7. Retrieving Data from a Database

SQL User Guide 83

Patriots Computer Corp. Out-of-stateLions Motor Company Out-of-stateVikings Athletic Equipment Out-of-stateChiefs Management Corporation Out-of-stateGiants Garments, Inc. Out-of-state'Bills We Pay' Financial Corp. Out-of-stateJets Overnight Express Out-of-stateBengels Imports Out-of-stateBrowns Kennels Out-of-stateEagles Electronics Corp. Out-of-stateSteelers National Bank Out-of-stateCowboys Data Services Out-of-stateOilers Gas and Light Co. Out-of-stateRedskins Outdoor Supply Co. Out-of-stateSeahawks Data Services In-statePackers Van Lines Out-of-state

7.5.4 Formatting Column Expression Result Values

The convert function listed in Table 7-6 can be used to do simple type conversions or sophisticated formatting of expressionresult values into a char or wchar string. The syntax for the convert function is given below.

convert_function:convert(expression, type)

| convert(expression, {char | wchar}, width, format)

data_type:char | wchar | smallint | integer | real

| double | date | time | timestamp | tinyint | bigint

format_spec:numeric_format | datetime_format

numeric_format:"[<< | >> | ><]['text' | $][- | (][#,]#[.#[#]...][e | E]['text' | $ | %]"

datetime_format:"[<< | >> | ><]['text' | spchar | date_code | time_code]..."

date_code:m | mm | mmm | mon | mmmm | month

| d | dd | ddd | dddd | day| yy | yyyy

time_code:h | hh | m | mm | s | ss | .f[f]... | [a/p | am/pm | A/P | AM/PM]

The expression specifies the SQL expression to be converted. In the first convert function form, type specifies the data type tobe returned. It must be a type for which a legal conversion can be performed.

The second form of the convert function will convert the expression result into either a char or a wchar string. The maximumlength of the result string is specified by width which must be an integer constant greater than 1. The result is formatted asspecified by a format string that conforms to the syntax shown above.

Page 92: RDMs 8.4 SQL User's Guide

7. Retrieving Data from a Database

SQL User Guide 84

The format specifier for numeric values is represented as shown in the table below. The minimum specifier that must be usedfor a numeric format is "#". If the display field width is too small to contain a numeric value, the convert function formats thevalue in exponential format (for example, 1.759263e08).

Element Description

[ << | >> | >< ] The justification specifier. You can specify left-justified text (<<), right-justified text (>>), or centeredtext (><). The default for numeric values is right-justified.

[ 'text' | $ ] A text character or string to use as a prefix for the result string. You must enclose the character or textwith single quotation marks unless the prefix is one dollar sign. A set currency statement will changethe symbol that is accepted by convert for the $.

[ - | ( ] The display specifier for negative values. You can show negative values with a minus sign or with par-entheses around the value. If parentheses are used, positive values are shown with an ending space toensure alignment of the decimal point.

[#,]#[.#[#]...] The numeric format specifier. You can specify whether to show commas every third place before thedecimal point. Also, you can specify how many digits (if any) to show after the decimal point. A setthousands or set decimal will change the symbol that is accepted by convert for the "," or the ".".

[e | E] Whether to use exponential format to show numeric values. If this option is omitted, exponential formatis used only when the value is too large or small to be shown otherwise. You can specify display of anlowercase or uppercase exponent indicator.

['text' | $ | %] A text character or string to use as a suffix for the result string. You must enclose the character or textwith single quotation marks unless the suffix is one dollar or percent sign. A set currency statement willchange the symbol that is accepted by convert for the $.

Table 7-7. Numeric Format Specifiers

The format specifier elements for date/time values are described in the next table. The date/time format specifier can containany number of text items or special characters that are interspersed with the date or time codes. You can arrange these items inany order, but a time specifier must adhere to the ordering rules described in the syntax under "time_code". For the minutecodes to be interpreted as minutes (and not months) they must follow the hour codes. You cannot specify the minutes of atime value without also specifying the hour. You can specify the hour by itself. Similarly, you cannot specify the secondswithout having specified minutes and you cannot specify fractions of a second without specifying seconds. Thus, the order"hours, minutes, seconds, fractions" must be preserved.

Element General Formatting Elements Description

[ << | >> | >< ] The justification specifier. You can specify left-justified text (<<), right-justified text (>>), or centeredtext (><). The default for numeric values is right-justified.

[ 'text' | spchar ] A string or a special character (for example, "-", "/", or ".") to be copied into the result string. The specialcharacter is often useful in separating the entities within a date and time.

Element Date-Specific Formatting Elements Description

m Month number (1-12) without a leading zero.

mm Month number with a leading zero.

mmm Three-character month abbreviation (e.g., "Jan").

mon Same as mmm.

mmmm Fully spelled month name (e.g., "January").

month Same as mmmm.

Table 7-8. Date and Time Format Specifiers

Page 93: RDMs 8.4 SQL User's Guide

7. Retrieving Data from a Database

SQL User Guide 85

Element General Formatting Elements Description

d Day of month (1-31) without leading zero.

dd Day of month with leading zero.

ddd Three character day of week abbreviation (e.g., "Wed").

dddd Fully spelled day of week (e.g., "Wednesday").

day Same as dddd.

yy Two-digit year AD with leading zero if year between 1950 and 2049; otherwise same as yyyy.

yyyy Year AD up to four digits without leading zero.

Element Time-Specific Formatting Elements Description

h Hour of day (0-12 or 23) without leading zero.

hh Hour of day with leading zero.

m Minute of hour (0-59) without leading zero (only after h or hh).

mm Minute of hour with leading zero (only after h or hh).

s Second of minute (0-59) without leading zero (only after m or mm).

ss Second of minute with leading zero (only after m or mm).

.f[f]... Fraction of a second: four decimal place accuracy (only after s or ss).

a/p | am/pm | A/P |AM/PM

Hour of day is 0-12; AM or PM indicator will be output to result string (only after last time code ele-ment).

The following examples show numeric format specifiers and their results.

Function Resultconvert(14773.1234, char, 10, "#.#") " 14773.1"convert(736620.3795, char, 12, "#,#.###") "736,620.380"convert(736620.3795, char, 12, "$#,#.##") "$736,620.38"convert(736620.3795, char, 13, "<<#.######e") "7.366204e+005"convert(56.75, char, 10, "#.##%") " 56.75%"convert(56.75, char, 18, "#.##' percent'") " 56.75 percent"

The examples below show date/time format specifiers and corresponding results. These examples show how the constant"timestamp "1951-10-23 04:40:35" can be returned. The format specifier, rather than the entire function, is shown here in theleft column.

Format Spec. Resultmmm dd, yyyy Oct 23, 1951hh' hours on' ddd month dd, yyyy 04 hours on Tue October 23, 1951dd 'of' month 'of the year' yyyy 23 of October of the year 1951dddd hh.mm.ss.ffff mm-dd-yyyy Tuesday 04.42.27.1750 10-23-1951'date:'yyyy.mm.dd 'at' hh:mm A/P date:1951.10.23 at 04:42 A

7.6 Performing Aggregate (Grouped) Calculations

All of the select statements shown thus far have produced detail rows where each row of the result set corresponds to a singlerow from the table (a base table or table formed from the set of joined tables in the from clause). There are often times when

Page 94: RDMs 8.4 SQL User's Guide

7. Retrieving Data from a Database

SQL User Guide 86

you want to perform a calculation on one or more columns from a related set of rows returning only a summary row thatincludes the calculation result. The set of rows over which the calculations are performed is called the aggregate. The selectstatement group by clause is used to identify the column or columns that define each aggregate—those rows that haveidentical group by column values. The syntax for the select statement including group by is as follows.

select:select [first | all | distinct] {* | select_item [, select_item]...}

from table_ref [, table_ref]...[where cond_expr][group by col_ref [, col_ref]... [having cond_expr]][order by col_ref [asc | desc] [, col_ref [asc | desc]]...]

select_item:{tabname | corrname}.* | expression} [identifier | "headingstring"]

table_ref:table_primary | table_join

table_primary:table_name_spec | ( table_join )

table_name_spec:[dbname.]tabname [[as] corrname]

table_join:natural_join | qualified_join | cross_join

natural_join:table_ref natural [inner | {left | right} [outer]] join table_primary

qualified_join:table_ref [inner | {left | right} [outer]] join table_primary

{using (colname[, colname]... ) on cond_expr}

cross_join:table_ref cross join table_primary

cond_expr:rel_expr [bool_oper rel_expr]...

rel_expr:expression [not] rel_oper {expression | [{any | some} | all] (subquery)}

| expression [not] between constant and constant| expression [not] in {(constant[, constant]...) | (subquery)}| [tabname.]colname is [not] null| string_expr [not] like "pattern"| not rel_expr| ( cond_expr )| [not] exists (subquery)| [tabname.]colname *= [tabname.]colname| [tabname.]colname =* [tabname.]colname

subquery:select {* | expression} from {table_list | path_spec} [where cond_expr]

expression:arith_expr | string_expr

arith_expr:arith_operand [arith_operator arith_operand]...

Page 95: RDMs 8.4 SQL User's Guide

7. Retrieving Data from a Database

SQL User Guide 87

arith_operand:constant | [tabname.]colname | arith_function | ( arith_expr)

arith_operator:+ | - | * | /

arith_function:{sum | avg | max | min} (arith_expr)

| count ({* | [tabname.]colname})| if ( cond_expr, arith_expr, arith_expr)| numeric_function | datetime_function | system_function| user_defined_function

string_expr:string_operand [^ string_operand]

string_operand:"string" | [tabname.]colname

| if ( cond_expr, string_expr, string_expr)| string_function| user_defined_function

The five built-in aggregate functions shown in the arith_function syntax rule above are defined in the table below.

Function Descriptioncount( [distinct] {* |[tabname.]colname} )

Returns the number (distinct) of rows in the aggregate.

sum( [distinct] expres-sion )

Returns the sum of the (distinct) values of expression in the aggregate.

avg( [distinct] expres-sion )

Returns the average of the (distinct) values of expression in the aggregate.

min( expression ) Returns the minimum expression value in the aggregate.max( expression ) Returns the maximum expression value in the aggregate.

Table 6-9. Built-in Aggregate Function Descriptions

The following example shows how grouped calculations are used to formulate a select statement that produces the year-to-date earnings for each salesperson. All orders for each salesperson are summarized, the total amount of all orders is computed,and the total commissions are calculated.

select sale_name, sum(amount), sum(amount*commission)from salesperson, customer, sales_orderwhere salesperson.sale_id = customer.sale_id

and customer.cust_id = sales_order.cust_idgroup by sale_name;

SALE_NAME SUM(AMOUNT) SUM(AMOUNT*COMMISSION)Flores, Bob 173102.02 17310.202Jones, Walter 422560.55 29579.2385Kennedy, Bob 736345.32 55225.899McGuire, Sidney 208432.11 14590.2477Nash, Gail 306807.26 21476.5082Porter, Greg 439346.5 35147.72Robinson, Stephanie 374904.47 26243.3129Stouffer, Bill 29053.3 2324.264Warren, Wayne 212638.5 15947.8875

Page 96: RDMs 8.4 SQL User's Guide

7. Retrieving Data from a Database

SQL User Guide 88

Williams, Steve 247179.99 18538.49925Wyman, Eliska 566817.01 42511.27575

The set display statement lets you specify a default display format a data type. The example below uses the set display state-ment to specify a two decimal place, fixed-point format for all double and real type columns.

set double display(14, "#,#.##");set real display(14,"#,#.##");

Re-executing the previous query now produces the following results.

SALE_NAME SUM(AMOUNT) SUM(AMOUNT*COMMISSION)Flores, Bob 173,102.02 17,310.20Jones, Walter 422,560.55 29,579.24Kennedy, Bob 736,345.32 55,225.90McGuire, Sidney 208,432.11 14,590.25Nash, Gail 306,807.26 21,476.51Porter, Greg 439,346.50 35,147.72Robinson, Stephanie 374,904.47 26,243.31Stouffer, Bill 29,053.30 2,324.26Warren, Wayne 212,638.50 15,947.89Williams, Steve 247,179.99 18,538.50Wyman, Eliska 566,817.01 42,511.28

Most of the remaining examples use the above specified formats (the amount column is double and the tax column is real inthe example databases).

Figure 7-1 illustrates the retrieved aggregate function results. The sales orders for Bob Flores are totaled in the amount andamount*commission columns.

Figure 7-1. Group By Calculations

If the group by clause is omitted, calculations are performed on all rows as a single aggregate producing a single summary res-ult row. The following example illustrates a select statement that calls the count, min, max, and avg aggregate functionswithout the group by clause. The statement retrieves the total number of sales orders, along with the minimum, maximum,and average order amounts.

Page 97: RDMs 8.4 SQL User's Guide

7. Retrieving Data from a Database

SQL User Guide 89

select count(*), min(amount), max(amount), avg(amount) from sales_order;

COUNT(*) MIN(AMOUNT) MAX(AMOUNT) AVG(AMOUNT)127 68.750000 274375.000000 29269.189213

The next example illustrates use of the sum function. The function computes total year-to-date sales for all salespersons in thesales database.

select sum(amount) from sales_order;

SUM(AMOUNT)3,717,187.03

The count function is used to calculate the number of detail rows from which the aggregate is comprised. The next queryshows the number of orders placed by each salesperson sorted by the number of orders (most listed first).

select sale_name, count(ord_num) from salesperson, customer, sales_orderwhere salesperson.sale_id = customer.sale_id and

customer.cust_id = sales_order.cust_idgroup by 1 order by 2 desc;

SALE_NAME COUNT(ORD_NUM)Wyman, Eliska 24Jones, Walter 15Robinson, Stephanie 15Kennedy, Bob 12McGuire, Sidney 11Warren, Wayne 10Flores, Bob 9Nash, Gail 9Williams, Steve 9Stouffer, Bill 8Porter, Greg 5

The argument in count can be any of the column names. Or, since any column you choose will give the same result, you cansimply write "count(*)".

A special form of the count function can also retrieve the total number of rows in a table, as shown below.

select count(*) from on_hand;

COUNT(*)744

The result returned by "select count (*) from tablename" may include uncommitted records. However, if theselect query contains additional columns or clauses, the returned result set will not include uncommittedrecords.

RDM Server SQL maintains on-line statistics that include the total number of rows per table, allowing the above query toreturn the result instantly. However, if you did not specify "count(*)" but included a column in this query (as shown below),RDM Server scans the entire table counting each row, using much more time for the query.

Page 98: RDMs 8.4 SQL User's Guide

7. Retrieving Data from a Database

SQL User Guide 90

select count(quantity) from on_hand;

COUNT(quantity)744

If you do not want duplicates included in aggregate calculations, you can specify distinct in an avg, count, or sum function.Use of distinct is shown in the following query, which retrieves both the total number of items and the total number of dis-tinct products sold by each salesperson.

select sale_name, count(prod_id), count(distinct prod_id)from salesperson, customer, sales_order, itemwhere salesperson.sale_id = customer.sale_id and

customer.cust_id = sales_order.cust_id andsales_order.ord_num = item.ord_num

group by 1;

SALE_NAME COUNT(PROD_ID) COUNT(DISTINCT PROD_ID)Flores, Bob 2 24Jones, Walter 62 29Kennedy, Bob 40 27McGuire, Sidney 41 27Nash, Gail 20 16Porter, Greg 17 14Robinson, Stephanie 67 34Stouffer, Bill 19 15Warren, Wayne 59 38Williams, Steve 25 17Wyman, Eliska 79 41

SQL provides the having clause to restrict result rows based on aggregate functions. The next example uses the having clauseto limit the result set to only those companies with more than five orders for the year.

select company, count(ord_num), sum(amount) from customer natural join sales_ordergroup by company having count(ord_num) > 5;

COMPANY COUNT(ORD_NUM) SUM(AMOUNT)Broncos Air Express 7 $498,952.76Browns Kennels 7 $43,284.54Colts Nuts & Bolts, Inc. 8 $29,053.30Patriots Computer Corp. 6 $120,184.69Rams Data Processing, Inc. 8 $172,936.31Seahawks Data Services 6 $60,756.36Vikings Athletic Equipment 6 $49,461.20

Note that your application cannot use a where clause in place of the having clause. The where clause restricts detail rowsbefore they affect the summary calculations, while the having clause restricts aggregate result rows after the calculations areperformed. Consider the following query.

select sale_name, sum(amount) from salesperson, customer, sales_orderwhere salesperson.sale_id = customer.sale_id

and customer.cust_id = sales_order.cust_idand sale_id in ("BNF","GAP")and ord_date between date "1997-06-01" and date "1997-06-30"

Page 99: RDMs 8.4 SQL User's Guide

7. Retrieving Data from a Database

SQL User Guide 91

group by 1having sum(amount) > 50000.0;

Figure 7-2 shows the different application of the where and having clauses during the processing of the above query.

Figure 7-2. Use of Where and Having Clauses

The following example uses the ucase and substring functions to retrieve all customers who have a customer identifier (cust_id) equal to the first three characters of the company name.

select cust_id, company from customerwhere cust_id = ucase(substring(company,1,3));

CUST_ID COMPANYSEA Seahawks Data Services

Page 100: RDMs 8.4 SQL User's Guide

7. Retrieving Data from a Database

SQL User Guide 92

7.7 String Expressions

The concat function is used to concatenate strings. It can be called from the select statement as shown in the followingexample.

select sale_name, concat(city, concat(", ", concat(state, concat(" ", zip)))) localityfrom accounts;

SALE_NAME LOCALITYFlores, Bob Seattle, WA 98121Flores, Bob San Francisco, CA 94127Porter, Greg Detroit, MI 48243Stouffer, Bill Baltimore, MD 46219Robinson, Stephanie Los Angeles, CA 92717Robinson, Stephanie San Diego, CA 92126Robinson, Stephanie Los Angeles, CA 90075Kennedy, Bob Denver, CO 80239Kennedy, Bob Phoenix, AZ 85021

In the previous example, the concat function requires several recursive calls to construct the customer locality string. Altern-atively, your application can use the string concatenation operator ^ as shown below.

select sale_name, city ^ ", " ^ state ^ " " ^ zip from accounts;

The query example below uses the select statement with the dayofweek scalar function, which retrieves the day of the week(for example, 1 = Sunday, 7 = Saturday). It retrieves the distribution of sales orders, from our sales database, based on the dayof the week when the orders were placed.

select dayofweek(ord_date), count(*) from sales_order group by 1;

DAYOFWEEK(ORD_DATE) COUNT(*)2 223 254 255 296 26

In the next example, a select statement calls the month scalar function, which retrieves the month number from the order date.The sum aggregate function computes the sales totals for each month.

select month(ord_date), sum(amount) from sales_order group by 1;

MONTH(ORD_DATE) SUM(AMOUNT)1 $969,467.022 $529,401.193 $415,894.504 $953,985.825 $249,299.816 $599,138.69

You can use the convert scalar function to change an expression to a character string according to a specified format. Usingthis function overrides the default display format.

Page 101: RDMs 8.4 SQL User's Guide

7. Retrieving Data from a Database

SQL User Guide 93

In the next example, the select statement uses convert to compute the total amount of all orders and the total commissions forsalespersons in the example sales database. Note that identifiers (for example, total_amt) are used to rename the columns.

select sale_name, convert(sum(amount), char, 12, "$#,#.##") total_amt,convert(sum(amount*commission), char, 12, "$#,#.##") total_comm,

from acct_sale group by sale_name;

SALE_NAME TOTAL_AMT TOTAL_COMMFlores, Bob $173,102.02 $17,310.20Kennedy, Bob $736,345.32 $51,544.17Porter, Greg $439,346.50 $35,147.72Robinson, Stephanie $374,904.47 $26,243.31Stouffer, Bill $29,053.30 $2,324.26

The application can use the convert function to format a result so that it is easier for users to read. In the following example,the "dddd" format indicates that the full spelling of the day is to be retrieved.

select convert(ord_date, char, 10, "dddd") "DAY OF ORDER", count(*)from sales_order group by 1;

DAY OF ORDER COUNT(*)Monday 22Tuesday 25Wednesday 25Thursday 29Friday 26

7.8 Nested Queries (Subqueries)

Subqueries allow SQL statements to restrict where clause results based on the evaluated result of a select statement nestedwithin the SQL statement. Using its nested query capability, a single SQL select statement can perform a task that may takemany statements in procedural programming languages such as C. Subqueries are specified as a where clause relational expres-sion as defined by the syntax below.

rel_expr:expression [not] rel_oper {expression | [{any | some} | all] (subquery)}

| expression [not] in {(constant[, constant]...) | (subquery)}| not rel_expr| ( cond_expr )| [not] exists (subquery)

subquery:select {* | expression} from {table_list | path_spec} [where cond_expr]

rel_oper:= | ==

| <| >| <=| >=| <> | != | /=

RDM Server SQL can evaluate the following subquery classes.

Page 102: RDMs 8.4 SQL User's Guide

7. Retrieving Data from a Database

SQL User Guide 94

l Simple, single-value subqueryl Multi-value subqueryl Complex, correlated subqueryl Existence check subquery

Each of these types of subqueries are described in the following sections.

7.8.1 Single-Value Subqueries

A single-value subquery is the simplest and most often used subquery. This subquery retrieves a single value (often computedfrom an aggregate function). A single value subquery has the following form:

select ... from ... where expression rel_oper (select expression from ...)The subquery's select statement must return only one row.

The following example shows the use of a single value subquery in a select statement that retrieves customer orders withorder amounts larger than the average sales order. The subquery itself retrieves the average sales order amount.

select company, amount from customer, sales_orderwhere customer.cust_id = sales_order.cust_id and

amount > (select avg(amount) from sales_order);

COMPANY AMOUNTFalcons Microsystems, Inc. 62,340.00Falcons Microsystems, Inc. 38,750.00'Bills We Pay' Financial Corp. 150,871.20'Bills We Pay' Financial Corp. 46,091.44Bears Market Trends, Inc. 46,740.00Bears Market Trends, Inc. 49,584.65

. . .Eagles Electronics Corp. 37,408.52Eagles Electronics Corp. 47,370.00Cardinals Bookmakers 143,375.00Cardinals Bookmakers 35,119.46Cardinals Bookmakers 38,955.00Seahawks Data Services 30,036.50Forty-niners Venture Group 74,315.16Bucs Data Services 39,675.95Bucs Data Services 35,582.50Redskins Outdoor Supply Co. 47,309.94

You can nest subqueries within other subqueries. The next example uses two nested subqueries to retrieve the orders (by sales-person) larger than the largest order, closed after the date the final order closed from New Jersey.

select sale_name, amount, ord_date from salesperson, customer, sales_orderwhere salesperson.sale_id = customer.sale_id and

customer.cust_id = sales_order.cust_id andamount > (select avg(amount) from sales_orderwhere ord_date > (select max(ord_date) from

sales_order, customer where state = "NJ" andsales_order.cust_id = customer.cust_id));

SALE_NAME AMOUNT ORD_DATEKennedy, Bob 274,375.00 1997-01-06

Page 103: RDMs 8.4 SQL User's Guide

7. Retrieving Data from a Database

SQL User Guide 95

Kennedy, Bob 49,980.00 1997-01-27Kennedy, Bob 103,874.80 1997-02-12Kennedy, Bob 143,375.00 1997-03-16Kennedy, Bob 35,119.46 1997-04-02Kennedy, Bob 38,955.00 1997-06-12Flores, Bob 30,036.50 1997-06-05Flores, Bob 74,315.16 1997-04-20Wyman, Eliska 32,925.00 1997-02-06Wyman, Eliska 54,875.00 1997-04-02Wyman, Eliska 66,341.50 1997-04-13

. . .Jones, Walter 46,740.00 1997-01-02Jones, Walter 28,570.00 1997-03-04Jones, Walter 49,584.65 1997-04-03Jones, Walter 31,580.00 1997-05-06Warren, Wayne 53,634.12 1997-01-10Warren, Wayne 77,247.50 1997-04-30

7.8.2 Multi-Valued Subqueries

A multi-value subquery retrieves more than one value and has two forms of syntax, as shown below.

select ... from ... where expression rel_oper {{any | some} | all} (select expression from ...)or

select ... from ... where expression [not] in (select expression from ...)The any or some qualifier (they are synonyms) indicates that the relational operation is true if there is at least one row fromthe subquery's result set for which it is true. The all qualifier indicates that the relational operation is true only when it is truefor every row from the subquery's result set.

The in ( subquery ) relational operation is true if there is one row from the subquery result set that is equal to the value of theleft-side expression. If not in is specified the relational operation is true when the value of the left-side expression does notequal any of the subquery result row values.

Note that,

where expression in (select expression from ...)is the same as

where expression = some (select expression from ...)For example, the following query uses a subquery to retrieve the customer orders with amounts larger than all orders bookedin May.

select company, ord_num, ord_date, amount from customer, sales_orderwhere customer.cust_id = sales_order.cust_id and

amount > all (select amount from sales_order whereord_date between date "1997-05-01" and date "1997-05-31");

COMPANY ORD_NUM ORD_DATE AMOUNTFalcons Microsystems, Inc. 2230 1997-02-04 62,340.00'Bills We Pay' Financial Corp. 2205 1997-01-03 150,871.20'Bills We Pay' Financial Corp. 2317 1997-06-18 46,091.44Bears Market Trends, Inc. 2201 1997-01-02 46,740.00

Page 104: RDMs 8.4 SQL User's Guide

7. Retrieving Data from a Database

SQL User Guide 96

Bears Market Trends, Inc. 2271 1997-04-03 49,584.65Bengels Imports 2257 1997-03-23 62,340.00Broncos Air Express 2207 1997-01-06 274,375.00Broncos Air Express 2220 1997-01-27 49,980.00Broncos Air Express 2237 1997-02-12 103,874.80Lions Motor Company 2219 1997-01-27 74,034.90Lions Motor Company 2250 1997-03-06 82,430.85Lions Motor Company 2288 1997-04-24 252,425.00Packers Van Lines 2211 1997-01-10 53,634.12Packers Van Lines 2292 1997-04-30 77,247.50Oilers Gas and Light Co. 2226 1997-01-30 54,875.00Chiefs Management Corporation 2241 1997-02-21 82,315.00Raiders Development Co. 2234 1997-02-10 124,660.00Patriots Computer Corp. 2281 1997-04-13 66,341.50Saints Software Support 2218 1997-01-24 81,375.00Saints Software Support 2324 1997-06-30 104,019.50Jets Overnight Express 2270 1997-04-02 54,875.00Eagles Electronics Corp. 2290 1997-04-29 47,370.00Cardinals Bookmakers 2253 1997-03-16 143,375.00Forty-niners Venture Group 2284 1997-04-20 74,315.16Redskins Outdoor Supply Co. 2310 1997-06-04 47,309.94

The next example demonstrates two ways to use multi-value subqueries. The subqueries show customers who are located instates that also have a sales office.

select company, city, state from customerwhere state = any (select state from outlet);

.. or ..

select company, city, state from customerwhere state in (select state from outlet);

COMPANY CITY STATERaiders Development Co. Los Angeles CARams Data Processing, Inc. Los Angeles CAChargers Credit Corp. San Diego CAForty-niners Venture Group San Francisco CABroncos Air Express Denver COFalcons Microsystems, Inc. Atlanta GABears Market Trends, Inc. Chicago ILPatriots Computer Corp. Foxboro MAVikings Athletic Equipment Minneapolis MNChiefs Management Corporation Kansas City MO'Bills We Pay' Financial Corp. Buffalo NYJets Overnight Express New York NYCowboys Data Services Dallas TXOilers Gas and Light Co. Houston TXSeahawks Data Services Seattle WA

The following example illustrates a select statement using the first form of the multi-value subquery to retrieve companies loc-ated in states without a sales office.

select company, city, state from customerwhere state <> all (select state from outlet);

Page 105: RDMs 8.4 SQL User's Guide

7. Retrieving Data from a Database

SQL User Guide 97

COMPANY CITY STATECardinals Bookmakers Phoenix AZDolphins Diving School Miami FLBucs Data Services Tampa FLColts Nuts & Bolts, Inc. Baltimore INSaints Software Support New Orleans LALions Motor Company Detroit MIGiants Garments, Inc. Jersey City NJBengels Imports Cincinnati OHBrowns Kennels Cleveland OHEagles Electronics Corp. Philadelphia PASteelers National Bank Pittsburgh PARedskins Outdoor Supply Co. Arlington VAPackers Van Lines Green Bay WI

7.8.3 Correlated Subqueries

A correlated subquery is one that refers to a column from the outer query, called an outer reference. RDM Server SQL per-forms a correlated subquery by executing the inner query for each row of the outer query. Processing a subquery of this typecan take some time. An alternative is to create temporary tables and indexes that are then joined using the select statement toretrieve the desired information.

The following is an example of a correlated subquery used to retrieve the customers who are located in cities that also havean outlet. Note that the inner query references the state column from the outer query by including the table name shown inthe outer query.

select company, city, state from customerwhere city in (select city from outlet where outlet.state = customer.state);

COMPANY CITY STATERaiders Development Co. Los Angeles CARams Data Processing, Inc. Los Angeles CABroncos Air Express Denver COFalcons Microsystems, Inc. Atlanta GABears Market Trends, Inc. Chicago ILVikings Athletic Equipment Minneapolis MNChiefs Management Corporation Kansas City MOJets Overnight Express New York NYCowboys Data Services Dallas TXSeahawks Data Services Seattle WA

The query below retrieves the average sales order amounts for each sales manager's department.

select mgr_id, avg(amount)from salesperson join customer using(sale_id) natural join sales_orderwhere mgr_id is not nullgroup by 1;

MGR_ID AVG(AMOUNT)BNF 41,157.40BPS 25,407.96CMB 30,777.07GAP 22,149.97

Page 106: RDMs 8.4 SQL User's Guide

7. Retrieving Data from a Database

SQL User Guide 98

The next example retrieves salespersons' order amounts greater than the average order amount for the department. You cancompare the amounts in the result set with the averages shown above to confirm that the query returned the correct results.Also note the use of extended join syntax in the from clause.

select sale_name, mgr_id, ord_num, amountfrom salesperson sp1 join customer using(sale_id) natural join sales_order

where mgr_id is not nulland amount > (select avg(amount)

from salesperson sp2 join customer using(sale_id) natural join sales_orderwhere sp2.mgr_id = sp1.mgr_id);

SALE_NAME MGR_ID ORD_NUM AMOUNTKennedy, Bob BNF 2207 274,375.00Kennedy, Bob BNF 2220 49,980.00Kennedy, Bob BNF 2237 103,874.80Kennedy, Bob BNF 2253 143,375.00Wyman, Eliska GAP 2231 32,925.00Wyman, Eliska GAP 2270 54,875.00Wyman, Eliska GAP 2306 25,002.78Wyman, Eliska GAP 2281 66,341.50Wyman, Eliska GAP 2205 150,871.20Wyman, Eliska GAP 2259 24,990.00Wyman, Eliska GAP 2317 46,091.44

. . .Jones, Walter BPS 2241 82,315.00Jones, Walter BPS 2257 62,340.00Jones, Walter BPS 2201 46,740.00Jones, Walter BPS 2249 28,570.00Jones, Walter BPS 2271 49,584.65Jones, Walter BPS 2295 31,580.00Warren, Wayne BPS 2202 25,915.86Warren, Wayne BPS 2211 53,634.12Warren, Wayne BPS 2292 77,247.50

Since both queries reference different occurrences of the same table, correlation names must be specified (for example, "sp1"and "sp2") for each separate salesperson table. This is necessary in order for SQL to determine which of the two salespersontables (the inner or outer) the mgr_id refers to.

7.8.4 Existence Check Subqueries

A subquery can also be used to simply check whether a select statement retrieves any row at all. The format of the existencecheck subquery is as follows:

select ... from ... where [not] exists (select * from ...)The existence check subquery does not retrieve any result set; it just returns true if the subquery retrieves at least one row,and false otherwise.

The following example uses a correlated existence check subquery to return the list of outlets that are warehouses only andnot a sales office.

select * from outletwhere not exists (select * from salesperson where office = loc_id);

LOC_ID CITY STATE REGIONBOS Boston MA 1

Page 107: RDMs 8.4 SQL User's Guide

7. Retrieving Data from a Database

SQL User Guide 99

KCM Kansas City MO 2STL St. Louis MO 2

7.9 Using Temporary Tables to Hold Intermediate Results

It is sometimes just not possible to formulate a single select statement to perform a complex query. At those times, the com-plex query can sometimes be broken into separate, simpler queries in which intermediate results from those simpler queriescan be stored in temporary tables to be joined together in the final query to produce the originally desired results. RDMServer provides the create temporary table statement just for this purpose with the following syntax.

create_temporary_table:create temporary table tabname (temp_col_defn [, temp_col_defn]...)

temp_col_defn:colname type_spec [default {constant | null | auto}]

The tabname is a case-insensitive identifier that can be any name except for that of another temporary table already defined inthe same connection. The table is comprised of the specified columns which can be declared to be any standard RDM ServerSQL data type. The default clause can be used to specify a default value for the table.

You can use the create index statement to create an index on a temporary table.

A commit statement must be issued after the create temporary table and the create index statements associated with itbefore you can use the temporary table.

You can use initialize table to re-initialize the table to contain other intermediate results (this is much fast than delete fromtabname).

Temporary tables are visible only to the connection that creates them. They exist until the connection is terminated. Also, youmust have at least one database open in order to create a temporary table.

Temporary tables can be used as an alternative to use of a correlated subquery where the performance penalty incurred by thesubquery it too great. So, while that is not really the issue with the following query, the example below shows how this canbe done. Suppose you want a list of the salespersons' order amounts greater than the average order amount for the department(this example was given earlier in section 7.9.3). You could solve this by first storing the department averages in a temporarytable indexed on the mgr_id and then just do a join between the salesperson and that temporary table in order to get thedesired list. The following SQL script shows how to do this.

set double display as (12,"#,#.##"); // produces cleaner outputcreate temp table mgravg(mgr_id char(3), avgsale double);create index mgravgid on mgravg(mgr_id);commit;insert into mgravg select mgr_id, avg(amount)

from salesperson join customer using(sale_id) natural join sales_orderwhere mgr_id is not null group by 1;

select * from mgravg;

MGR_ID AVGSALEBNF 41,157.40BPS 25,407.96CMB 30,777.07GAP 22,149.97

Page 108: RDMs 8.4 SQL User's Guide

7. Retrieving Data from a Database

SQL User Guide 100

select sale_name, mgr_id, ord_num, amountfrom salesperson s, mgravg m, customer c, sales_order owhere s.mgr_id = m.mgr_id and s.sale_id = c.sale_id and c.cust_id = o.cust_id

and amount > avgsale;

SALE_NAME MGR_ID ORD_NUM AMOUNTKennedy, Bob BNF 2207 274,375.00Kennedy, Bob BNF 2220 49,980.00Kennedy, Bob BNF 2237 103,874.80Kennedy, Bob BNF 2253 143,375.00Wyman, Eliska GAP 2231 32,925.00Wyman, Eliska GAP 2270 54,875.00Wyman, Eliska GAP 2306 25,002.78Wyman, Eliska GAP 2281 66,341.50

. . .Jones, Walter BPS 2201 46,740.00Jones, Walter BPS 2249 28,570.00Jones, Walter BPS 2271 49,584.65Jones, Walter BPS 2295 31,580.00Warren, Wayne BPS 2202 25,915.86Warren, Wayne BPS 2211 53,634.12Warren, Wayne BPS 2292 77,247.50

7.10 Other Select Statement Features

There are a few other select statement features that need to be described. These are shown in the select statement syntax gram-mar below.

select:select [first | all | distinct] {* | select_item [, select_item]...}

from table_ref [, table_ref]...[where cond_expr][with exclusive lock][group by col_ref [, col_ref]... [having cond_expr]][order by col_ref [asc | desc] [, col_ref [asc | desc]]...]

select_item:{tabname | corrname}.* | expression} [identifier | "headingstring"]

You can indicate that a select statement is to return just the first row of the result set, all rows of the result set (which is thedefault), or only the distinct result set rows in which duplicate rows have been eliminated. Some examples are shown below.

select first * from salesperson;

SALE_ID SALE_NAME DOB COMMISSION REGION SALES_TOT OFFICE MGR_IDBCK Kennedy, Bob 1956-10-29 0.075 0 736,345.32 DEN BNF

The next query returns the list of salespersons who have a least one customer account. Try it without the distinct and seewhat you get.

select distinct sale_namefrom salesperson join customer using(sale_id);

Page 109: RDMs 8.4 SQL User's Guide

7. Retrieving Data from a Database

SQL User Guide 101

SALE_NAMEKennedy, BobFlores, BobStouffer, BillWyman, EliskaPorter, GregNash, GailMcGuire, SidneyWilliams, SteveRobinson, StephanieJones, Walter

Note that select distinct usually requires SQL to sort the rows of the result set. This can be expensive for large result sets somake sure that you really need the distinct rows before using this feature.

Like a select * from tabname you can specify tabname.* to have SQL include all of the columns declared in tabname in theselect column list. This is useful when more than on table is listed in the from clause. For example, the following select dis-plays all of the note table entries made by each salesperson.

select sale_name, note.* from salesperson join note using(sale_id);

SALE_NAME NOTE.NOTE_ID NOTE.NOTE_DATE NOTE.SALE_ID NOTE.CUST_IDKennedy, Bob FOLLCALL1 1996-12-27 BCK DENKennedy, Bob FOLLCALL1 1997-02-06 BCK DENKennedy, Bob FOLLCALL1 1997-03-05 BCK PHOKennedy, Bob FOLLCALL1 1997-03-18 BCK PHOKennedy, Bob FOLLCALL1 1997-04-03 BCK DENKennedy, Bob FOLLCALL1 1997-05-08 BCK PHO

. . .Warren, Wayne INITMEET 1996-11-11 WWW GBPWarren, Wayne INITMEET 1996-12-30 WWW MINWarren, Wayne QUOTE1 1996-12-09 WWW GBPWarren, Wayne QUOTE1 1997-04-08 WWW GBPWarren, Wayne SALESLIT1 1997-03-10 WWW MINWarren, Wayne SALESLIT1 1997-04-01 WWW GBPWarren, Wayne SALESLIT1 1997-05-01 WWW MINWarren, Wayne SALESLIT2 1997-03-23 WWW MIN

The with exclusive lock will cause SQL to place a write-lock (as opposed to its usual read-lock) on all of the select statementresult rows. It is not usually a good idea to do this but there can be certain processing requirements where exclusive access isneeded for all of the access rows even where only some may actually end up being changed.

7.11 Unions of Two or More Select Statements

Situations sometimes exist where needed information is stored in different tables or databases. It may be the case that data hasnot been normalized so that redundant data co-resides in those separate tables or databases and the easiest way to access thatinformation is to submit separate queries on each table/database. Ideally, one wants to have the result sets from those separatequeries grouped into a single result set. This can be done through use of temporary tables and using the insert from selectstatement to run the results of each query into a single table. But a much easier method is by using the union operator to haveSQL do that work for you. This section describes the use of the union operator to combine the results of separate select state-ments into a single result set.

Page 110: RDMs 8.4 SQL User's Guide

7. Retrieving Data from a Database

SQL User Guide 102

7.11.1 Specifying Unions

The result sets of two or more similar select statements can be combined into a single result set through use of the union oper-ator. The syntax for the union of multiple select statements is shown below.

union:query_expr [order by {colname | num} [asc | desc][, {colname | num} [asc | desc]]...]

query_expr:query_term | query_expr union [all] query_term

query_term:query_spec | ( query_expr )

query_spec:select [first | all | distinct] {* | select_item[, select_item]...}from tab_ref [, tab_ref]...[where cond_expr]

All select statements that are involved in each specified union must have the same number of result columns and each of thecorresponding columns must have compatible data types. The results of each of the select statements are combined into asingle result set.

The results from each pair of select statements that are unioned together will have any duplicate rows removed by default.You can specify union all in order to keep any duplicate rows in the result set. Because RDM Server SQL must maintain aseparate index in order to locate the duplicate rows to be removed, the best performance will result by always specifyingunion all.

The unions of more than two select statements are processed in left to right order but can be changed by using parentheses.The size of the final result set can be affected by how the unions are parenthesized if duplicate rows are being eliminated insome of the unions (i.e., all is specified in some but not all of the union operations).

Standard SQL assigns no column headings to the result columns. RDM Server SQL, however, by default assigns the resultcolumn headings based on the column names or headings specified in the first select statement. You can turn this feature onor off using the following set statement.

set_union_headings:set union headings {on | off}

7.11.2 Union Examples

The FBI’s National Crime Information Center maintains the national Integrated Automated Fingerprint Identification System(IAFIS) for use by law enforcement agencies throughout the United States. This database contains the fingerprint records forover 55 million criminal subjects as well as the civilian subjects many of whom are current or former employees of variouslocal, state, and federal law enforcement agencies. The FBI also manages the COmbined DNA Index System (CODIS) and theNational DNA Index System containing over 6.7 million offender DNA profiles and almost 260,000 forensic DNA profilesextracted from crime scenes (as of February, 2009). The CODIS database contains information on convicted felons, arrestees,and missing persons and their biologically related relatives.

In the investigation of a crime, fingerprints and DNA samples are often found that can lead to the identification and appre-hension of the perpetrators of the crime. In the following example, a set of fingerprints and human DNA samples that wereextracted from a hypothetical crime scene are submitted to these various databases in order to identify any individuals fromthose databases that match any of the provided fingerprint and DNA codes. All tables contain the name, date of birth (dob),

Page 111: RDMs 8.4 SQL User's Guide

7. Retrieving Data from a Database

SQL User Guide 103

gender, height, weight, race, hair color, eye color, and distinguishing scars and marks (dsm) of each person in their respectivedatabases. Each record also contains a unique NCIC identification number. The following query shows how the union oper-ator can be used to return a single result set from these databases containing a list of all persons who match at least one of thespecified fingerprint or DNA codes.

select name, dob, gender, height, weight, race, hair, eye, dsm, ncic, "IAFIS Criminal"from iafis.criminal where fpid in (fpcode1, fpcode2, ..., fpcodeN)

union allselect name, dob, gender, height, weight, race, hair, eye, dsm, ncic, "IAFIS Civilian"

from iafis.civil where fpid in (fpcode1, fpcode2, ..., fpcodeN)union all

select name, dob, gender, height, weight, race, hair, eye, dsm, ncic, "CODIS Felon"from codis.felon where dnacode in (dnacode1, dnacode2, ..., dnacodeN)

union allselect name, dob, gender, height, weight, race, hair, eye, dsm, ncic, "CODIS Arrestee"

from codis.arrestee where dnacode in (dnacode1, dnacode2, ..., dnacodeN)order by 1, 2;

The union all ensures that SQL will not have to do the extra work to check for duplicate rows. The character literal that isspecified as the last entry in each select column list simply identifies the source from which the matching row was found. Thefinal result set is sorted by name and date of birth. The ncic identification number is returned in the ncic column which canthen be used to retrieve the entire record for each result row if desired.

Unions can also be used to simplify the kind of select statement to be used to retrieve the desired result. Our sales databaseexample stores only one address for each customer. But there are often situations where the customer has one address forbilling and another for shipping. One way this can be implemented is to separate customer address data into a separate tableand maintain a billing address foreign key and a shipping address foreign key referencing the address table in the customertable. The DDL which implements this scheme is given below.

create table address(

addrid rowid primary key,address1 char(30),address2 char(30),city char(20),state char(2),zip char(5)

);create table customer(

cust_id char(3) primary key,company varchar(30) not null,contact varchar(30),billingaddr rowid not null references address,shippingaddr rowid references address

);

The billingaddr column contains the rowid of the address table row that contains the customer’s billing address information.The shippingaddr column contains the rowid of the address table row that contains the shipping address information. Whenthe billing and the shipping address are the same, shippingaddr is null. Now suppose that each customer is to be sent a pack-age of promotional material. A list of each customer’s shipping address is to be retrieved and used to produce mailing labels.One way to do this is shown in the example below.

Page 112: RDMs 8.4 SQL User's Guide

7. Retrieving Data from a Database

SQL User Guide 104

select company, contact,if (shippingaddr is null, b.address1, s.address1) address1,if (shippingaddr is null, b.address2, s.address2) address2,if (shippingaddr is null, b.city, s.city) city,if (shippingaddr is null, b.state, s.state) state,if (shippingaddr is null, b.zip, s.zip) zip

from (customer inner join address b on (billingaddr = b.addrid))left outer join address s on (shippingaddr = s.addrid)

order by 7;

This works well but is pretty complex. The same result, however, can be achieved using a union with a much simpler con-struction as follows.

select company, contact, address1, address2, city, state, zipfrom customer inner join address on (shippingaddr = addrid)

union allselect company, contact, address1, address2, city, state, zip

from customer inner join address on (billingaddr = addrid)where shippingaddr is null

order by 7;

Page 113: RDMs 8.4 SQL User's Guide

8. Inserting, Updating, and Deleting Data in a Database

SQL User Guide 105

8. Inserting, Updating, and Deleting Data in a DatabaseThe SQL insert statement is used to add new data into the database. Database data that already exists in the database can bechanged using the SQL update statement. You delete data from the database using the SQL delete statement. Use of thesethree statements are described in detail in this chapter. Changes made by one or more of these statements are not stored in thedatabase until a commit statement is executed. A commit causes all of the database changes made in the current transactionto be safely written to the database. Before describing the use of the SQL statements that you can use to change the datastored in the database, it is necessary to first describe the use of transactions.

8.1 Transactions

It is very important that any database management system (DBMS) ensures that the data that is stored in a database satisfiesthe ACID criteria: Atomicity, Consistency, Isolation, and Durability. Atomicity means that a set of interrelated database modi-fications all be made together at the same time. If one modification from the set fails then all fail. Consistency means that adatabase never contains errant data or relationships and that a transaction always transforms the database from one consistentstate into another. Consistency is something that is primarily the responsibility of the application because the database cannotbe certain that all of the necessary modifications have been properly included in any given transaction. In SQL, consistencyrules are specified through DDL foreign and primary key declarations and the check clause and RDM Server SQL does ensurethat all database data adheres to those rules. Isolation means that the changes that are being made during a transaction areonly visible to the user (connection) making them. Not until the transaction’s changes have been committed to the databaseare other users (connections) able to see them. Durability refers to the DBMS’s ability to ensure that the changes made by alltransactions that have committed survive any kind of system failure.

The work necessary to ensure that a DBMS supports "ACIDicity" makes it among the most complex of all system softwarecomponents. The challenge is to maintain ACIDicity and yet allow the database data to be easily accessed by as many usersas possible, as fast as possible. However, there is an unavoidable and severe negative performance impact caused by the needto maintain an ACID compliant database. When enforcement of these properties is relaxed, data can be updated and accessedmuch more quickly but the consistency and integrity of the data will certainly be impaired should a system failure occur.

A transaction is a group of related database modifications (i.e., a sequence of insert, update, and/or delete statements) that arewritten to the database during execution of a commit statement in such a way as to guarantee that either all of the modi-fications are successfully written to the database or none are in the event of a system failure while the commit is being pro-cessed. Should the application detect an error (e.g., invalid user input) or RDM Server SQL detect an integrity error prior tothe commit, a rollback statement can be executed to discard all of the changes made since the start of the transaction.

Transactions are controlled through the use of four SQL statements: start transaction, commit, rollback, and savepoint.

8.1.1 Transaction Start

The start transaction statement is used to mark the beginning of a new database transaction. Use of this statement is notstrictly necessary as a transaction is implicitly started by SQL on execution of the first insert, update or delete statement thatfollows the most recently executed commit (or call to SQLConnect). However, it is best to explicitly start each transactionas that will clearly delineate transaction boundaries in your application. The syntax for the start transaction is given below.

start_trans:start trans[action]

Page 114: RDMs 8.4 SQL User's Guide

8. Inserting, Updating, and Deleting Data in a Database

SQL User Guide 106

The start transaction initiates a new database modification transaction in which the changes made by any subsequent insert,update, or delete statements (as well as changes made by any triggers that have been defined on the modified tables, seeChapter 8) will be atomically written to the database as a unit upon execution of a commit statement.

In earlier versions of RDM Server the begin transaction statement was used to start (begin) a transaction.Its syntax is shown below and is still accepted by RDM Server SQL.begin [trans[action] | work] [trans_id]The optional trans_id is an identifier that can be used to label the transaction.

8.1.2 Transaction Commit

The commit statement is used to atomically write all of the changes made by insert, update and delete statements executedsince the most recently executed start transaction statement. The syntax for commit is as follows.

commit:commit

A simple transaction used to insert a single row into the salesperson table is shown in the following example.

start trans;insert into salesperson

values "MMB", "Bryant, Mike",date "1960-11-14",0.05,0,"SEA","BNF";commit;

8.1.3 Transaction Savepoint

The savepoint statement is used to mark a transaction savepoint identified by savepoint_id that can be the target of a sub-sequently executed rollback to savepointsavepoint_id statement which will cause all of the database modifications madeafter this savepoint to be discarded while keeping intact all changes made in the transaction prior to this savepoint. The syn-tax for the savepoint statement is shown below.

savepoint:savepoint savepoint_id

Of course, this statement requires that a transaction has been started.

Savepoints are discarded through execution of a rollback to a prior savepoint, or a rollback or commit of the transaction.

8.1.4 Transaction Rollback

The rollback statement is used to discard (undo) database modifications made during the current transaction. The syntax forrollback is shown below.

rollback:rollback [trans[action]]

| rollback to savepoint savepoint_id

Page 115: RDMs 8.4 SQL User's Guide

8. Inserting, Updating, and Deleting Data in a Database

SQL User Guide 107

The first form is used to terminate the transaction and discard all of the changes made by all insert, update and delete state-ments that were executed during the transaction.

The second form is used to discard all of the changes made by all insert, update and delete statements that were executedafter execution of the savepoint statement with a matching savepoint_id. Changes made during the transaction prior to thesavepoint remain in place.

The example below illustrates the use of savepoint and rollback.

start trans;insert into salesperson ... // new salespersonsavepoint new_customer;insert into customer... // new customer for new salespersoninsert into customer... // another for the new salesperson

... // discover problem with new customersrollback savepoint to new_customer;commit; // commit new salesperson to database

8.2 Inserting Data

The insert statement is used to insert new rows into a table. Three different methods for inserting rows into a table are sup-ported in RDM Server SQL. The insert values statement is the most common and is used to insert a single row into a table.The insert from select statement can be used to insert the results from a select statement into a table. Finally, the insert fromfile statement allows you to insert rows into a table from a comma-delimited text file or from a XML file. Use of each of thesemethods is described in the following sections.

8.2.1 Insert Values

The insert values statement is used to insert a row into a table. The syntax for the insert values statement is:

insert_values:insert into [ dbname.]tabname [ ( colname [, colname ]... ) ] values col_value [, col_value]...

col_value:constant | null |? | proc_arg

The insert values statement is used to insert a single row into the table tabname which must identify a table declared in adatabase managed by RDM Server. If more that one database has a table named tabname then dbname should be specified toidentify the database containing the desired table.

If a colname list is specified it must include every column which requires that a value be specified (a primary key column orone which does not have a default value but does have a not null declared). For each column, there must be a value specifiedin the same corresponding position in the values list. If no colname list is specified then there must be a value listed for eachcolumn declared in the table in the order in which the columns were declared in the create table statement for tabname.

The values specified in the values list will usually simply be a constant of a data type that is compatible with the data typeof its corresponding column, or null if allowed by the corresponding column definition. However, insert values can include aparameter marker references (designated by a "?") or, if the insert statement is contained within a create procedure statement,procedure argument names (proc_arg).

For example, the following statement inserts a new salesperson into the example salesperson table.

Page 116: RDMs 8.4 SQL User's Guide

8. Inserting, Updating, and Deleting Data in a Database

SQL User Guide 108

insert into salespersonvalues "MMB", "Bryant, Mike",date "1960-11-14",0.05,0,"SEA","BNF";

In the salesperson table, the mgr_id column is a foreign key to the row of the salesperson table of the manager. For thisexample, if RDM Server finds no salesperson row with "BNF" as the value of sale_id, it rejects the insert statement with a ref-erential integrity violation..

When using the insert values statement, the application does not have to specify all columns for the table. Only non-nullcolumns that do not have default values must be specified. For these columns, the insert statement specifies either the defaultvalue or null. If your application does not include a column list, it must specify values for all table columns, in the order inwhich the columns are declared in the create table (or create view) statement.

Any SQL program that inserts rows into a table with a rowid primary key must add a place-holder (",,")for the rowid primary key column in any values list, as well as in any text file that is used for importingrows into the table. This affects only the use of the insert statement. Rowid primary key values cannot bemodified by using the update statement.

The next example shows the insert statements needed to store a complete sales order in the database.

start trans;insert into sales_order

values("SEA",2311,date "1997-06-30",time "13:17:00",30036.50,2553.10);

insert into item values(2311,16311,30);insert into item values(2311,18061,200);insert into item values(2311,18121,1000);commit;

The columns in the sales_order table are cust_id, ord_num, ord_date, ord_time, amount, tax, and ship_date respectively. Thecolumns in the item table are ord_num, prod_id, loc_id and quantity. Note that this is a single transaction, which containsfour insert statements. Hence, there is a single commit statement.

The following example illustrates the use of RDM Server SQL system literal constants in insert statements.

.. a statement that could be executed from an extension module or

.. stored procedure that is always executed when a connection is made.insert into login_log(user_name, login_time) values(user, now);

.. check today's action itemsselect cust_id, note_text from action_items where tickle_date = today;

See RDM Server Language Reference for information about specifying constant values including date andtime constants and system literal constants, such as those in the example above.

8.2.2 Insert from Select

You can also insert new rows into a table from another table using insert from select statement. The syntax for the insertfrom select statement is given below. The select statement was described in detail in the last chapter and its use with theinsert statement will show the basics of how the two can be used together.

insert_from_select:

Page 117: RDMs 8.4 SQL User's Guide

8. Inserting, Updating, and Deleting Data in a Database

SQL User Guide 109

insert into [db_name.]tabname [(colname[, colname]...)] [from] select

The number of result columns returned from the select statement must equal the number of columns specified in the colnamelist or, if not specified, the number of columns declared in the table. The data type of each result column must also be com-patible with its corresponding table column.

Your application can create a temporary table to hold temporary results from which additional queries can be processed. Atemporary table is visible only to the application session that creates it. It can be queried just like any other table.

To create a temporary table, execute a create temporary table statement that conforms to he following syntax.

create_temporary_table:create temporary table tabname (temp_col_defn [, temp_col_defn]...)

temp_col_defn:colname type_spec [default {constant | null | auto}]

The basics of table creation were described earlier in section 6.3. However, no foreign or primary keys or check constraintscan be declared for a temporary table.

Before any rows are inserted into the temporary table, you can create one or more indexes on the temporary table using thecreate index statement. Note that the optional attribute and the in clause are not allowed in the create index statement for atemporary index. Use of the create index statement is described in section 6.4.

Once the temporary table has been created, rows can be inserted into it using any form of the insert statement and it can bereferenced just like any table in other select, update, and delete statements.

The following example uses an insert statement to fill a temporary table called sp_sales with the customer orders processedby Sidney McGuire ("SKM").

create temporary table sp_sales(company char(30),city char(17),state char(2),ord_date date,amount float

);create index skm_ndx on sp_sales(state);insert into sp_sales

select company, city, state, ord_date, amountfrom customer, sales_orderwhere customer.sale_id = "SKM" andcustomer.cust_id = sales_order.cust_idorder by 1, 4;

The select statement in the insert statement above contains an order by clause that causes the natural ordering of the rows insp_sales to be sorted in company, ord_date order. Any select statement issued for sp_sales that does not itself have an orderby clause specified reports its results in the same order.

A temporary table can be reinitialized using the initialize temporary table statement as shown below.

initialize_temporary_table:init[ialize] temp[orary] table tabname[, tabname]...

Page 118: RDMs 8.4 SQL User's Guide

8. Inserting, Updating, and Deleting Data in a Database

SQL User Guide 110

Each tabname must be the name of a previously created temporary table.

The following example shows an initialize statement used to reload the temporary table sp_sales with customers for BobFlores ("BNF").

init temp sp_sales;insert into sp_sales

select company, city, state, ord_date, amountfrom customer, sales_orderwhere customer.sale_id = "BNF" andcustomer.cust_id = sales_order.cust_idorder by 1, 4;

8.2.3 Importing Data into a Table

Your application can use a single insert from file statement to import multiple rows from a comma-delimited text file into atable. This statement can be used to perform a bulk load. If any of the rows in the text file violate any of the integrity con-straints defined for the table, the load terminates with an error. All rows inserted up to that point are rolled back auto-matically. The syntax for this form of the insert statement is as follows:

insert_from_file:insert [with auto commit] into [dbname.]tabname

[from] [ascii | unicode] file "filename" [, "delimiter"]... [on devname]| insert [with auto commit] into [db_name.]table_name

[from] xml file "filename"[, xml_option ...][on devname]

xml_option:"blobs={no | yes}"

| "tags={columnnames | numbers}"| "attribs={no | only}"| "tabname={no | yes}"| "nulltags={no | yes}"| "dateformat={y | m | d}"

The specified file must reside in device devname or, if no device is given, in the user's device. The first form of the insertfrom file statement stores each row in a text line with the column values delimited with a comma or the specified "delimiter"character would be in the values clause of an insert values statement.

The insert from xml file imports the data from an xml formated file. You can specify a variety of options that describe theformat of the xml file to be imported. Note that these options are specified in a string with no spaces allowed between theoption elements. Also note that the default setting is the first option setting specified in the list. Hence, the default blobsoption is no. You can also specify just "y" for "yes" or "n" for "no". The option string is case-insensitive. Each of theseoptions is described in the following table.

Option Description

blobs Set to "yes" to import the translation string specified for long varbinary columndata.

tags Set to "numbers" when column tags are identified by their ordinal position inthe result set rather than its name (e.g., <COLUMN-2>).

attribs Set to "only" when each result row output is a single text line where the

Table 8-1. XML Import Option Descriptions

Page 119: RDMs 8.4 SQL User's Guide

8. Inserting, Updating, and Deleting Data in a Database

SQL User Guide 111

Option Description

column values specified as attributes (e.g., <ROW> sale_id="BNF" name-e="Flores, Bob"<\ROW>).

tabname Set to "yes" when each result row is tagged with its table name (e.g., <sales-person>) rather than <ROW>.

nulltags Set to "yes" when an empty column entry is given for null-valued columns.

dateformat Set to "y" when dates are in "YYYY-MM-DD" format (default), to "m" fordates in "MM-DD-YYYY" format, and to "d" for dates in "DD-MM-YYYY"format.

Each text line (ending in a newline character, '\n') in file "filename" corresponds to one row in the table. Each value in thetext line is specified just as it would be in the values clause of an insert values statement. Each column value is separated bythe "delimiter" character which is by default a comma (",").

Any errors encountered during the processing of any of the insert will result in an appropriate error return and will discardany rows inserted prior to the occurrence of the error. The with auto commit clause can be specified to indicate that the sys-tem is to perform a commit on each row that it inserted into the table from the specified file which will preserve all rows thatwere inserted up to the one in which the error was detected.

If the number of rows to be inserted is very large, your application should either explicitly open the data-base in exclusive mode or issue an exclusive table lock on the table being accessed. Otherwise, the serveris forced to maintain a growing number of record locks for the table, which can cause severe performancedegradation on the server.

The following example lists the contents of file "outlet.txt" located in the catdev device.

"SEA", "Seattle", "WA", 0"LAX", "Los Angeles", "CA", 0"DAL", "Dallas", "TX", 3"BOS", "Boston", "MA", 1"CHI", "Chicago", "IL", 2"KCM", "Kansas City", "MO", 2"STL", "St. Louis", "MO", 2"NYC", "New York", "NY", 1"ATL", "Atlanta", "GA", 3"MIN", "Minneapolis", "MN", 2"DEN", "Denver", "CO", 0"WDC", "Washington", "DC", 1

All these values can be loaded into our example outlet table using the following insert statement.

insert into outlet file "outlet.txt" on catdev;commit;

You can ensure the fastest possible import processing by first opening the database in exclusive access mode (no locksrequired) with transaction logging turned off (see example below). Of course, the price paid for this performance is the loss ofrecoverability in case the server crashes (for example, in a power failure) while the insert statement is being processed. If anyintegrity constraints are violated, the insert statement terminates but the rows that have already been inserted cannot be rolledback. No rollback capability exists at all in this case, because the changes are not logged.

Page 120: RDMs 8.4 SQL User's Guide

8. Inserting, Updating, and Deleting Data in a Database

SQL User Guide 112

The following code illustrates insert from file statements issued for the product, outlet, and on_hand tables. Notice the use ofthe update stats statement following the bulk load. It is always a good practice to execute update stats after making sub-stantial modifications to a database, such as bulk loads. Executing this statement ensures that the SQL query optimizer is gen-erating access plans based on reasonable data usage statistics.

open invntory exclusive with trans off;insert into product from file "product.txt" on catdev;insert into outlet from file "outlet.txt" on catdev;insert into on_hand from file "on_hand.txt" on catdev;commit;update stats on invntory;

You use the insert from file statement to import XML data into a database table. The general format of an XML file is as fol-lows:

<?xml version="1.0" encoding="UTF-8"?><anytagname><anyrowname anyattributename="value" ...><anytagname>value</anytagname>...

</anyrowname>...

</anytagname>

The first level tags (anyrowname) are assumed to enclose row values. The tag name is ignored. When anytagname or any-attributename matches a column in the table named in the insert statement, the value will be assigned for that column. A rowwill be created if at least 1 column is specified, and the resulting insert of the row is valid according to SQL rules, such askey uniqueness and referential integrity.

Tags nested at deeper levels will be ignored.

If a column is missing, it will be inserted as a null column value. Note that if the column is not nullable, the insert will fail.

If a column is identified more than once in a single row element (one or more attributes with the same name, and/or one ormore elements with the same name), only the first value will be used. The remaining values will be ignored. In the followingexample, the sale_id column will have the value "one".

<?xml version="1.0" encoding="UTF-8"?><RAIMA-SQL><ROW sale_id="one" dob="1954-05-30" sale_id="two"><SALE_ID>three</SALE_ID><SALE_NAME>Flores, Bob</SALE_NAME><SALE_ID>four</SALE_ID>

</ROW></RAIMA-SQL>

8.2.4 Exporting Data from a Table

The insert into file statement can be used to export data from a table (or tables) into either a comma-delimented formatted fileor an XML formatted file. The syntax for this statement is given below.

insert_into_file:

Page 121: RDMs 8.4 SQL User's Guide

8. Inserting, Updating, and Deleting Data in a Database

SQL User Guide 113

insert into [ascii | unicode] file "filename" [ , "delimiter"] [on devname ] [from] select| insert into xml file "filename" [ , xml_option]... [on devname ] [from] select

xml_option:"blobs={no | yes}

| "header={noversion | version }"| "tags={columnnames | numbers}"| "attribs={no | only}"| "tabname={no | yes}"| "nulltags={no | yes}"| "dtd={no | yes}"| "schema={no | yes}"| "dateformat={y | m | d}"

This statement will export the result set returned from the specified select statement in the specified format (ascii, unicode, orxml) into a text file named "filename" which will be stored in the device named devname or in the home device for the userexecuting the statement.

The first form (non-xml) of the insert into file statement will store the result rows from the specified select statement in acomma-delimited file in which the data is stored either as ascii-coded (default) or Unicode-coded (UTF-8) characters. You canuse the "delimiter" clause to change the delimiter from a comma to some other special character (e.g., "|").

The xml form of the insert into file statement allows one or more xml control options to be specified. Note that these optionsare specified in a string with no spaces allowed between the option elements. Also note that the default setting is the firstoption setting specified in the list. Hence, the default blobs option is no. You can also specify just "y" for "yes" or "n" for"no". The option string is case-insensitive. Each of these options is described in the following table.

Option Description

blobs Set to "yes" to include a translation string for long varbinary column data.

header Set to "version" include in the generated xml file a <!-- --> header line con-taining the version of RDM Server that executed the statement.

tags Set to "numbers" to have each column tag identified by its ordinal position inthe result set rather than its name (e.g., <COLUMN-2>).

attribs Set to "only" to have each result row output as a single text line with thecolumn values specified as attributes (e.g., <ROW> sale_id="BNF" name-e="Flores, Bob"<\ROW>).

tabname Set to "yes" to have each result row tagged with its table name (e.g., <sales-person>) rather than <ROW>.

nulltags Set to "yes" to output an empty column entry for null-valued columns.

dtd Set to "yes" to output a DTD (Document Type Description) header for the xmlfile.

schema Set to "yes" to output a header containing the schema for the result xml table.

dateformat Set to "y" to output dates in "YYYY-MM-DD" format (default), to "m" to out-put dates in "MM-DD-YYYY" format, and to "d" to output dates in "DD-MM-YYYY" format.

Table 8-2. XML Export Option Descriptions

Examples of a variety of export options of the outlet table (invntory database) are provided below. The insert statement is fol-lowed by the contents of the generated file.

Page 122: RDMs 8.4 SQL User's Guide

8. Inserting, Updating, and Deleting Data in a Database

SQL User Guide 114

insert into file "outlet.txt" on catdev from select * from outlet;

"ATL","Atlanta","GA",3"BOS","Boston","MA",1"CHI","Chicago","IL",2"DAL","Dallas","TX",3"DEN","Denver","CO",0"KCM","Kansas City","MO",2"LAX","Los Angeles","CA",0"MIN","Minneapolis","MN",2"NYC","New York","NY",1"SEA","Seattle","WA",0"STL","St. Louis","MO",2"WDC","Washington","DC",1

insert into file "outlet.txt", "|" on catdev from select * from outlet;

"ATL"|"Atlanta"|"GA"|3"BOS"|"Boston"|"MA"|1"CHI"|"Chicago"|"IL"|2"DAL"|"Dallas"|"TX"|3"DEN"|"Denver"|"CO"|0"KCM"|"Kansas City"|"MO"|2"LAX"|"Los Angeles"|"CA"|0"MIN"|"Minneapolis"|"MN"|2"NYC"|"New York"|"NY"|1"SEA"|"Seattle"|"WA"|0"STL"|"St. Louis"|"MO"|2"WDC"|"Washington"|"DC"|1

insert into xml file "outlet.xml" on catdev from select * from outlet;

<?xml version="1.0" encoding="UTF-8"?><RAIMA-SQL>

<ROW><loc_id>ATL</loc_id><city>Atlanta</city><state>GA</state><region>3</region>

</ROW><ROW>

<loc_id>BOS</loc_id><city>Boston</city><state>MA</state><region>1</region>

</ROW><ROW>

<loc_id>CHI</loc_id><city>Chicago</city><state>IL</state><region>2</region>

</ROW><ROW>

<loc_id>DAL</loc_id><city>Dallas</city><state>TX</state><region>3</region>

</ROW><ROW>

<loc_id>DEN</loc_id>

Page 123: RDMs 8.4 SQL User's Guide

8. Inserting, Updating, and Deleting Data in a Database

SQL User Guide 115

<city>Denver</city><state>CO</state><region>0</region>

</ROW><ROW>

<loc_id>KCM</loc_id><city>Kansas City</city><state>MO</state><region>2</region>

</ROW><ROW>

<loc_id>LAX</loc_id><city>Los Angeles</city><state>CA</state><region>0</region>

</ROW><ROW>

<loc_id>MIN</loc_id><city>Minneapolis</city><state>MN</state><region>2</region>

</ROW><ROW>

<loc_id>NYC</loc_id><city>New York</city><state>NY</state><region>1</region>

</ROW><ROW>

<loc_id>SEA</loc_id><city>Seattle</city><state>WA</state><region>0</region>

</ROW><ROW>

<loc_id>STL</loc_id><city>St. Louis</city><state>MO</state><region>2</region>

</ROW><ROW>

<loc_id>WDC</loc_id><city>Washington</city><state>DC</state><region>1</region>

</ROW></RAIMA-SQL>

insert into xml file "outlet.xml","attribs=only","tabname=y" on catdev from select * from out-let;

<?xml version="1.0" encoding="UTF-8"?><RAIMA-SQL>

<outlet loc_id="ATL" city="Atlanta" state="GA" region="3" /><outlet loc_id="BOS" city="Boston" state="MA" region="1" /><outlet loc_id="CHI" city="Chicago" state="IL" region="2" /><outlet loc_id="DAL" city="Dallas" state="TX" region="3" /><outlet loc_id="DEN" city="Denver" state="CO" region="0" /><outlet loc_id="KCM" city="Kansas City" state="MO" region="2" /><outlet loc_id="LAX" city="Los Angeles" state="CA" region="0" />

Page 124: RDMs 8.4 SQL User's Guide

8. Inserting, Updating, and Deleting Data in a Database

SQL User Guide 116

<outlet loc_id="MIN" city="Minneapolis" state="MN" region="2" /><outlet loc_id="NYC" city="New York" state="NY" region="1" /><outlet loc_id="SEA" city="Seattle" state="WA" region="0" /><outlet loc_id="STL" city="St. Louis" state="MO" region="2" /><outlet loc_id="WDC" city="Washington" state="DC" region="1" />

</RAIMA-SQL>

The last example shown above is an insert into xml file that specified two xml options. Any number of the xml options canbe specified in an insert statement.

8.3 Updating Data

The update statement is used to modify the values of one or more columns of one or more rows in a table.

update:update [dbname.]tabname

set colname = {expression | null}[, colname = {expression | null}]...[where cond_expr ]

The value to which each named column in the set clause is assigned is the evaluated result of its specified expression. Thetable to be updated is named tabname which, if more than one database has a table of that name, should be qualified with itsdbname. The column values in table tabname referenced by the expressions are the pre-updated column values. The rows thatare updated are those for which the conditional expression is true. If no where clause is specified, every row in table tabnamewill be updated. If the update of any of the selected rows results in an integrity constraint violation, the update is aborted andthe changes to the rows that had already been modified are discarded.

Note that you can only update a primary key of those rows for which there are either no foreign key references or for onwhich a create join has been declared on all of the foreign keys that reference this primary key. Updates of foreign keycolumns will be checked to ensure that referential integrity is preserved (i.e., the referenced primary key row exists).

The following example shows a basic update statement that sets the commission to eight percent for the salesperson withsale_id "SWR" (Stephanie Robinson). This update modifies only a single row of a table.

start transaction;update salesperson set commission = 0.08 where sale_id = "SWR";commit;

The next example gives each non-manager salesperson a 10 percent increase in commission rate.

update salespersonset commission = commission + 0.10*commissionwhere mgr_id is null;

commit;

Assume that Rams Data Processing, Inc., has moved to a new address. The next statement modifies the relevant columns inthe customer table of our sales database.

update customerset address = "17512 SW 123rd St.", city = "Tustin", zip = "90121"

Page 125: RDMs 8.4 SQL User's Guide

8. Inserting, Updating, and Deleting Data in a Database

SQL User Guide 117

where cust_id = "LAN";commit;

The statements below illustrate another update example. Eliska Wyman ("ERW") has left the company. Until her replacementis hired, Eliska's New York and New Jersey customers are to be serviced by Greg Porter ("GAP"), and her other customers willbe handled by Sidney McGuire ("SKM").

start trans;update customer

set sale_id = "GAP"where sale_id = "ERW" and state in ("NY", "NJ");

update customerset sale_id = "SKM"where sale_id = "ERW" and state not in ("NY", "NJ");

commit;

The following example uses the if column selection function. This function allows the application to do in a single statementthe modifications requiring two update statements in the previous example.

start trans;update customer

set sale_id = if (state in ("NY","NJ"), "GAP", "SKM");where sale_id = "ERW";

commit;

8.4 Deleting Data

The delete statement is used to delete one or more rows from a table. The syntax for the delete statement is as follows.

delete:delete from [dbname.]tabname [where cond_expr ]

The table whose rows are to be updated is named tabname which, if more than one database has a table of that name, shouldbe qualified with its dbname. The rows to be deleted from tabname are those for which the conditional expression specifiedin the where clause returns true. If no where clause is specified the all of the rows in the table will be deleted. The deletestatement will fail and return an error if it attempts to delete a row which is referenced by another foreign key rows in whichcase no rows will be deleted.

The following example shows how the delete statement is used to try to delete the salesperson row with sale_id equal to"ERW".

delete from salesperson where sale_id = "ERW";

However, since there are five customers who are serviced by this salesperson which have not been deleted the system (in thiscase, the rsql utility) returns the following error.

****RSQL Diagnostic 3713: non-zero references on primary/unique key

Page 126: RDMs 8.4 SQL User's Guide

8. Inserting, Updating, and Deleting Data in a Database

SQL User Guide 118

In the next example, sales manager Chris Blades has left the company and his salespersons are to be reassigned to BillStouffer. Before deleting the salesperson row for Chris Blades (sale_id = "CMB"), an update statement must first be executingto reassign the salesperson rows with mgr_id = "CMB" to mgr_id "BPS".

start trans;update salesperson set mgr_id = "BPS" where mgr_id = "CMB";

*** 2 rows affecteddelete from salesperson where sale_id = "CMB"****RSQL Diagnostic 3713: non-zero references on primary/unique key

Oops. There are still some foreign keys somewhere that reference salesperson row with sale_id = "CMB" but there are no cus-tomers assigned to Blades since he is a manager. But there are notes. So, the statements below will successfully complete thetransaction. Note that the update salesperson statement is still active in the transaction even those the above delete statementfailed.

delete from note_line where sale_id = "CMB";

*** 29 rows affecteddelete from note where sale_id = "CMB";

*** 11 rows affecteddelete from salesperson where sale_id = "CMB"

*** 1 rows affectedcommit;

Page 127: RDMs 8.4 SQL User's Guide

9. Database Triggers

SQL User Guide 119

9. Database TriggersA trigger is procedure associated with a table that is executed (i.e., fired) whenever that table is modified by the execution ofan insert, update, or delete statement. A non-standard trigger mechanism has been available in RDM Server SQL through theuse of a User-Defined Function that gets called via the execution of a check condition that was specified in the create tablestatement. The SQL standard now provides the ability for triggers to be specified using SQL statements. This section describeshow standard SQL triggers are implemented in RDM Server SQL.

9.1 Trigger Specification

The create trigger statement is used to create a trigger on a specified database table. The syntax for this statement is givenbelow.

create_trigger:create trigger trigname ["description"]

{before | after} {insert | delete | update [of colname[, colname]...]} on tabname[referencing {old | new} [row] [as] corname [{new | old} [row] [as] corname]][for each {row [when (search_condition)] | statement]trigger_stmts

trigger_stmts:trig_stmt

| begin [atomic]trig_stmt...

end

trig_stmt:open | close | flush | initialize_database | insert | delete

| update | lock_table | call | initialize_table | notify

The trigname is the unique name of the trigger and must conform to a standard identifier. The tabname is the name of thetable with which the trigger is to be associated. If there is more than one database with a table named tabname then you canqualify tabname with the name of its database dbname.

An optional string containing a description or comment about the trigger can be specified. This string is stored along with thetrigger definition in the system catalog.

The trigger is defined to be fired either before or after the changes are made by the specified insert, update, or delete (calledthe trigger event). The firing of an update trigger can be restricted to occur only when the values of the column names spe-cified in the update of clause are updated. If no columns are specified, then an update trigger will be fired upon the executionof every update statement on tabname.

Two types of triggers can be created. A statement-level trigger is created by specifying for each statement in the triggerdeclaration. If no for each clause is specified, for each statement is the default. A statement-level trigger fires once for eachexecution of the insert, update, or delete statement as indicated in the specified trigger event. Thus, for example, an updatestatement that modifies 100 rows in a table will execute a statement-level update trigger on the table only once.

A row-level trigger is created by specifying for each row in the trigger declaration. Row-level triggers fire once for each tablerow that is changed by the insert, update, or delete statement. Row-level triggers are the more useful of the two types of trig-gers in that they can reference the old and/or new columns values for each row. The referencing clause is used to specify acorrelation name for either the old table row values or the new table row values.This clause can only be specified with row-

Page 128: RDMs 8.4 SQL User's Guide

9. Database Triggers

SQL User Guide 120

level triggers. The when clause can be used to specify a condition that must evaluate to true in order for the trigger to fire.Note that the only table values that can be referenced in the when conditional expression (cond_expr) are through the ref-erencing old and/or new row correlation names.

The new or old column values of each row can be referenced in the trigger’s SQL statements through the correlation namesspecified in the referencing clause. However, references to blob type columns (long varchar/varbinary/wvarchar) are notallowed. Note that insert triggers only have new column values, delete triggers only have old column values, while updatetriggers have both old and new column values.

The SQL statement to be executed when the trigger fires is specified last. If more than one statement is needed, it must beplaced within a begin [atomic] and end block. The SQL standard offers no explanation as to why it chose to include theword "atomic." It normally is used to mean that a sequence of statements are not interruptable. However, since the executionof a trigger can cause other data modifications to occur that also have triggers (they can be nested) this cannot be the casewith triggers. We have interpreted it to mean that either all of the SQL statements succeed or if any one fails then the state isrestored to its pre-trigger execution condition. Regardless of why they chose to include this term, it does tend to make onenot want to use triggers for fear of nuking the database!

There are some restrictions on the kinds of SQL statements that can be included in a trigger. No select, DDL, or create state-ments are allowed in a trigger. A trigger cannot create another trigger. A stored procedure cannot create a trigger. Also, sinceit is necessary that any database modifications made by a trigger be included as part of the user’s transaction, no transactionstatements are allowed in a trigger definition. While stored procedures and user-defined procedures can be executed within atrigger, great care must be exercised to ensure that no harmful side effects occur from the execution of these procedures insidea trigger.

A trigger begins to take effect immediately upon the successful execution of the create trigger statement. Thus, it is shouldbe considered more of a DDL than a DML statement since their creation should occur immediately after the DDL statementsare issued that define the database tables on which the triggers are associated. Triggers that are created on an existing data-base may require that the conditions and data relationships being maintained by the triggers be externally established at trig-ger creation time. See the "Summary Statistics" section below for an example.

9.2 Trigger Execution

A trigger that has been defined on a table will be executed based on the {before | after} trigger event specification. Anychanges that are made by the SQL statements specified in a before trigger will remain intact even when the triggering datamodification statement fails (e.g., with an integrity violation). The triggered SQL statements defined in an after trigger areonly executed when the triggering data modification statement succeeds.

A before statement-level trigger will execute before any changes are made by the associated (triggering) insert, update, ordelete statement. An after statement-level trigger will execute after all changes have been successfully made by the associatedinsert, update, or delete statement.

A before row-level trigger will execute prior to each row modification made by the triggering insert, update, or delete state-ment. An after row-level trigger executes after each row has been successfully modified by the triggering insert, update, ordelete statement. If a when clause has been specified with a row-level trigger, the trigger will only fire on those rows wherethe evaluation of the when's conditional expression (cond_expr) returns true.

All changes made by the SQL statement(s) defined by the trigger are included as part of the user’s transaction. Thus, thetriggered database modifications will be committed when the user subsequently issues a commit statement or they will berolled back should the user subsequently execute a rollback statement.

There is no limit to the number of triggers than can be defined on a table. There can even be multiple triggers with the sametrigger event specified on a table. Multiple triggers are executed sequentially in the order in which they were defined.

Page 129: RDMs 8.4 SQL User's Guide

9. Database Triggers

SQL User Guide 121

The SQL trigger_stmts can themselves make changes to tables on which other triggers have been defined. Thus, trigger exe-cution can be nested.

Note that any rows that are modified by a trigger remained locked until the user either commits or rolls back the transaction.

Any trigger can be disabled and subsequently re-enabled through use of the alter trigger statement.

alter trigger trigname {enable | disable}

The altered trigger status takes effect immediately upon successful execution of the alter trigger statement.

9.3 Trigger Security

The ability for non-administrator users to create triggers is included in the create database command-level privilege.This canbe set by executing the following grant statement.

grant create database to user_id [, user_id ]...

The create database privilege can be removed by executing the following revoke statement.

revoke create database from user_id [, user_id ]...

A user must either be an administrator or have create database command privilege in order to create, alter or drop triggers.

In addition to having the proper command privilege, A non-administrator user must also have been granted trigger privilegeon any tables on which the user will be creating triggers. Trigger privileges are set using the following grant statement.

grant trigger on [dbname.]tabname to user_id [, user_id ]...

Trigger privilege is required for a user to create, alter, or drop a trigger on the specified table. Trigger privileges can berevoked by issuing the following statement.

revoke trigger on [dbname.]tabname from user_id [, user_id ]...

Revoking trigger privileges does not affect any triggers that may have already been created by the specified user.

Triggers execute under the authority of the user who created the trigger and not that of the user who executed the originalinsert, update, or delete statement that caused the trigger to fire. Thus, the user that issues the create trigger statement musthave the proper security privileges on any table that is to be accessed or modified by the trigger’s SQL statements. Laterchanges to the security settings for the user who created the trigger will not affect the execution of the trigger. Please refer toChapter 11, "SQL Database Access Security" for details.

A trigger can be dropped by executing the drop trigger statement.

drop trigger trigname

All triggers that have been defined on a particular table are automatically dropped when the table is dropped.

Page 130: RDMs 8.4 SQL User's Guide

9. Database Triggers

SQL User Guide 122

9.4 Trigger Examples

The use of triggers in a database system necessarily means that modifications made to the tables on which triggers have beendefined will have side effects that are hidden from the user who issued the original SQL modification statement. Generally,side effects are not a good thing to have occur in a software system. Yet, triggers are am important and useful feature for cer-tain kinds of processing requirements. The examples in this section illustrate two such uses. Triggers are particularly useful inmaintaining certain kinds of statistics such as usage or summary stats. Triggers are also very useful in maintain various kindsof audit trails.

Summary Statistics

The query below returns the sales totals for each customer in the sales database.

set double display(12, "#,#.##");select cust_id, sum(amount) from sales_order group by 1;cust_id sum(amount)ATL 113,659.75BUF 263,030.36CHI 160,224.65

. . .SEA 60,756.36SFF 112,345.66TBB 104,038.25WAS 63,039.90

An alternative approach which does not require running a query that scans through the entire sales_order table each time canbe implemented with triggers. A new column named sales_tot of type double is declared in the customer table. The followingthree triggers can be defined on the sales_order table that keeps the related customer’s sales total amount up to date.

create trigger InsSalesTot after insert on sales_orderreferencing new row as new_orderfor each rowupdate customerset sales_tot = sales_tot + new_order.amountwhere cust_id = new_order.cust_id;

create trigger UpdSalesTot after update of amount on sales_orderreferencing old row as old_order new row as new_orderfor each rowupdate customerset sales_tot = sales_tot + (new_order.amount - old_order.amount)where cust_id = new_order.cust_id;

create trigger DelSalesTot before delete on sales_orderreferencing old row as old_orderfor each rowupdate customerset sales_tot = sales_tot - old_order.amountwhere cust_id = old_order.cust_id;

The first trigger, InsSalesTot, executes an update on the customer table after each successful insert on the sales_order table byadding the new sales_order's amount through the correlation name new_order to the current value of the customer's sales_tot.The second trigger is fired only when there is an update executed that changes the value of the amount column in the sales_order table. When that occurs the customer's sales_tot column needs to subtract out the old amount and add in the new one.The DelSalesTot trigger fires whenever a sales_order row is deleted causing its amount to be subtracted from the customer'ssales_tot.

Page 131: RDMs 8.4 SQL User's Guide

9. Database Triggers

SQL User Guide 123

Now suppose you want to also maintain the sales totals for each salesperson in addition to each customer. You can also add asales_tot column of type double to the salesperson table and use a trigger to update it as well as the customer sales_totcolumn. The simplest way to do this is to modify the above triggers to update the row of the salesperson table who managesthe account of the customer whose sales_order is being modified as shown below.

create trigger InsSalesTot after insert on sales_orderreferencing new row as new_orderfor each row

begin atomicupdate customerset sales_tot = sales_tot + new_order.amountwhere cust_id = new_order.cust_id

update salespersonset sales_tot = sales_tot + new_order.amountwhere sale_id = (select sale_id from customer

where cust_id = new_order.cust_id)end;create trigger UpdSalesTot after update of amount on sales_order

referencing old row as old_order new row as new_orderfor each row

begin atomicupdate customerset sales_tot = sales_tot + (new_order.amount - old_order.amount)where customer.cust_id = new_order.cust_id;

update salespersonset sales_tot = sales_tot + (new_order.amount - old_order.amount)where sale_id = (select sale_id from customer

where cust_id = new_order.cust_id)end;create trigger DelSalesTot before delete on sales_order

referencing old row as old_orderfor each row

begin atomicupdate customerset sales_tot = sales_tot - old_order.amountwhere customer.cust_id = old_order.cust_id;

update salespersonset sales_tot = sales_tot - old_order.amountwhere sale_id = (select sale_id from customer

where cust_id = new_order.cust_id)end;

Since each trigger contains two SQL update statements, they must be enclosed between the begin atomic and end pairs. Alsonote that the subquery is needed to locate the salesperson row to be updated through the customer row based on the cust_idcolumn in the sales_order table.

The same result can also be achieved not by modifying the original triggers but by introducing one new trigger that updatesthe salesperson's sales_tot whenever a related customer's sales_tot column is updated. Note that the saleperson sales_tot doesnot need to be updated when a new customer row is inserted (because the sales_tot is initially zero) or when a customer rowis deleted (because the sales_order rows associated with the customer must first be deleted which causes the customer's sales_tot to be updated). The trigger definition is as follows.

create trigger UpdSPSalesTot after update of amount on customerreferencing old row as old_cust new row as new_custfor each rowupdate salesperson

Page 132: RDMs 8.4 SQL User's Guide

9. Database Triggers

SQL User Guide 124

set sales_tot = sales_tot + (new_cust.amount - old_cust.amount)where sale_id = new_cust.sale_id;

This trigger fires whenever an update is executed on the sales_tot column in the customer table. That will only occur whenone of the earlier triggers fires due to the execution of an insert, delete, or update of the amount column on the sales_ordertable. Thus, this is an example of a nested trigger—a trigger which fires in response to the firing of another trigger.

The sales database example is delivered with the sales_tot column already declared in the salesperson and customer tables butwithout the triggers having been declared. Now, however, you want to create the triggers that will maintain the sales_tot val-ues for each customer and salesperson but data already exists in the database. So, the sales totals somehow need to be ini-tialized at the time the triggers are created. To do this the database should be opened in exclusive access to ensure that noupdates occur between the time the triggers are first installed and the sales_tot values in the customer table are initialized.The following rsql script shows how this can be done.

open sales exclusive;set double display(12, "#,#.##");select sale_id, sales_tot from salesperson;

sale_id sales_totBCK 0.00BNF 0.00BPS 0.00

...WAJ 0.00WWW 0.00select cust_id, sales_tot from customer;

sale_id sales_totATL 0.00BUF 0.00CHI 0.00

...TBB 0.00WAS 0.00create trigger InsSalesTot ...create trigger UpdSalesTot ...create trigger DelSalesTot ...create trigger UpdSPSalesTot ...update customer set sales_tot =

query("select sum(amount) from sales_order where cust_id = ?", cust_id);

*** 28 rows affectedselect cust_id, sales_tot from customer;commit;

sale_id sales_totATL 113,659.75BUF 263,030.36CHI 160,224.65

...TBB 104,038.25WAS 63,039.90select sale_id, sales_tot from salesperson;

sale_id sales_totBCK 237,392.56BNF 112,345.66

Page 133: RDMs 8.4 SQL User's Guide

9. Database Triggers

SQL User Guide 125

BPS 0.00...

WAJ 141,535.34WWW 49,461.20close sales;

Note that the update statement that sets the sales_tot values for each row in the customer table uses the query system func-tion (a copy has also been included as an example user-defined function called "subquery").

Audit Trails

Audit trails keep track of certain changes that are made to a database along with an identification of the user who initiatedthe change and a timestamp as to when the change occurred. Suppose we want to keep track of changes made to the sales_order table. The following statement creates a table called orders_log that will contain on row per sales_order change andgrants insert (only) privileges on it to all users.

create table sales.orders_log(chg_desc char(30),chg_user char(32) default user,chg_timestamp timestamp default now

);commit;grant insert on orders_log to public;

Six statement-level triggers are needed to track all successful and unsuccessful attempts to change the sales_order table: threebefore triggers to track all attempts and three after triggers to track only those changes that succeed. Note that should thetransaction that contains the sales_order change statement be rolled back, the changes to orders_log will also be rolled back.Thus, only unsuccessful change attempts associated with subsequently committed transactions will be logged in the orders_log table. The declarations of the triggers are given below.

create trigger bef_ord_ins before insert on sales_orderfor each statementinsert into orders_log(chg_desc) values "insert attempted";

create trigger bef_ord_upd before update on sales_orderinsert into orders_log(chg_desc) values "update attempted";

create trigger bef_ord_del before delete on sales_orderinsert into orders_log(chg_desc) values "delete attempted";

create trigger aft_ord_ins after insert on sales_orderinsert into orders_log(chg_desc) values "insert successful";

create trigger aft_ord_upd after update on sales_orderinsert into orders_log(chg_desc) values "update successful";

create trigger aft_ord_del after delete on sales_orderinsert into orders_log(chg_desc) values "update successful";

By the way, as you can see from the above trigger declarations the for each statement clause is optional and is the default ifno for each clause is specified.

The rsql script below creates a couple of new users who each make several changes to the sales_order table in order to see theresults of the firing of the associated triggers. Note also that the original row-level triggers are still operative.

create user kirk password "tiberius" on sqldev;grant all commands to kirk;

Page 134: RDMs 8.4 SQL User's Guide

9. Database Triggers

SQL User Guide 126

grant select on orders_log to kirk;create user jones password "tough" on sqldev;grant all commands to jones;grant select on orders_log to jones;.c 2 server kirk tiberiusinsert into sales_order values "IND",2400,today,now,10000.00,0,null;

*** 1 rows affectedcommit;.c 3 server jones toughupdate sales_order set amount = 1000.00 where ord_num = 2400;*** 1 rows affecteddelete from sales_order where ord_num = 2210;****RSQL Diagnostic 3713: non-zero references on primary/unique keycommit;select * from orders_log;

chg_desc chg_user chg_timestampinsert attempted kirk 2009-07-27 11:58:17.9460insert successful kirk 2009-07-27 11:58:17.9460update attempted jones 2009-07-27 11:59:48.2900update successful jones 2009-07-27 11:59:48.2900delete attempted jones 2009-07-27 12:00:06.3680.c 2*** using statement handle 1 of connection 2delete from sales_order where ord_num = 2400;

*** 1 rows affectedselect * from orders_log;

chg_desc chg_user chg_timestampinsert attempted kirk 2009-07-27 11:58:17.9460insert successful kirk 2009-07-27 11:58:17.9460update attempted jones 2009-07-27 11:59:48.2900update successful jones 2009-07-27 11:59:48.2900delete attempted jones 2009-07-27 12:00:06.3680delete attempted kirk 2009-07-27 12:05:10.0710delete attempted kirk 2009-07-27 12:05:49.9620delete successful kirk 2009-07-27 12:05:49.9620rollback;select * from orders_log;

chg_desc chg_user chg_timestampinsert attempted kirk 2009-07-27 11:58:17.9460insert successful kirk 2009-07-27 11:58:17.9460update attempted jones 2009-07-27 11:59:48.2900update successful jones 2009-07-27 11:59:48.2900delete attempted jones 2009-07-27 12:00:06.3680

9.5 Accessing Trigger Definitions

Trigger definitions are stored in the system catalog. Two predefined stored procedures are available for accessing trigger defin-itions. Procedure ShowTrigger will return a result set containing a single char column and one row for each line of text fromthe original declaration for the trigger name specified in the procedure argument. Procedure ShowAllTriggers returns twocolumns: the trigger name and a line of text from the original declaration. Example calls and their respective result sets areshown in the example below.

Page 135: RDMs 8.4 SQL User's Guide

9. Database Triggers

SQL User Guide 127

exec ShowTrigger("UpdSalesTot");TRIGGER DEFINITIONcreate trigger UpdSalesTot after update of amount on sales_order

referencing old row as old_order new row as new_orderfor each rowupdate customer set sales_tot = sales_tot + (new_order.amount - old_order.amount)

where customer.cust_id = new_order.cust_id;exec ShowAllTriggers;NAME DEFINITIONInsSalesTot create trigger InsSalesTot after insert on sales_orderInsSalesTot referencing new row as new_orderInsSalesTot for each rowInsSalesTot update customer set sales_tot = sales_tot + new_order.amountInsSalesTot where customer.cust_id = new_order.cust_id;UpdSalesTot create trigger UpdSalesTot after update of amount on sales_orderUpdSalesTot referencing old row as old_order new row as new_orderUpdSalesTot for each rowUpdSalesTot update customerUpdSalesTot set sales_tot = sales_tot + (new_order.amount - old_order.amountUpdSalesTot where customer.cust_id = new_order.cust_id;DelSalesTot create trigger DelSalesTot before delete on sales_orderDelSalesTot referencing old row as old_orderDelSalesTot for each rowDelSalesTot update customer set sales_tot = sales_tot - old_order.amountDelSalesTot where customer.cust_id = old_order.cust_id;UpdSPSalesTot create trigger UpdSPSalesTot after update of sales_tot on customerUpdSPSalesTot referencing old row as oldc new row as newcUpdSPSalesTot for each rowUpdSPSalesTot update salespersonUpdSPSalesTot set sales_tot = sales_tot + (newc.sales_tot - oldc.sales_tot)UpdSPSalesTot where sale_id = newc.sale_id;

Page 136: RDMs 8.4 SQL User's Guide

10. Shared (Multi-User) Database Access

SQL User Guide 128

10. Shared (Multi-User) Database AccessAn RDM Server database is designed to be efficiently accessed my multiple, concurrent users. In such a multi-user envir-onment, some method must be used by the DBMS to protect against attempts by multiple users to update the same data at thesame time. RDM Server SQL applies locks on the shared data in order to restrict changes to shared data to one user at a time.Why locking is needed is explained by the following example.

Table 10-1 shows the sequence of actions of two connections trying to update the same table row at approximately the sametime without using locks. At time t1, connection 1 reads the row from the database. Connection 2 reads the row at t2. Bothconnections then update and write the row back to the database, with connection 1 going first for each operation. However,at the end of the last write, the row copy for connection 2 does not include changes from connection 1 (changes occurredafter connection 2 read the row).

Time Connection 1 Connection 2

t1 read row

t2 read row

t3 update row

t4 update row

t5 write row

t6 write row

Table 10-1. Multi-User Database Access without Locks

In this case, connection 2 can access connection 1 changes if connection 2 can read the row after time t5. What is necessary isto provide a lock to serialize updates to the shared data. Table 10-2 illustrates the sequence of operations for the two exampleapplications synchronized by the use of locks. Note that once the lock request for connection 1 is granted at time t2, con-nection 2 must wait for the row to be unlocked before continuing. When connection 1 completes its updates, it frees the lockat time t6. This action triggers RDM Server to grant the lock to connection 2, after which connection 2 can read the row(including the connection 1 changes) and then make its own changes.

Time Connection 1 Connection 2

t1 request row lock

t2 lock granted request row lock

t3 read row

t4 update row

t5 write row

t6 free lock

t7 read row

t8 update row

t9 write row

t10 free lock

Table 10-2. Multi-User Database Access with Locks

Page 137: RDMs 8.4 SQL User's Guide

10. Shared (Multi-User) Database Access

SQL User Guide 129

As the above example illustrates, an important feature of a multi-user DBMS is to provide the shared database access controlnecessary to ensure that no data is lost and that the data is logically consistent (that is, the required inter-data relationshipsexist). RDM Server SQL automatically manages the needed row-level locking for you. Yet it is important for you to knowhow this is done and the features provided by RDM Server SQL that give you control over how SQL manages it. This is thesubject of the rest of this chapter.

10.1 Locking in SQL

10.1.1 Row-Level Locking

Two types of locks are used by RDM Server. A read lock (sometimes called a share or shared lock) is issued by SQL for eachrow that is fetched from a select statement. Any number of connections can have a read lock on the same row. A write lock(sometimes called an exclusive lock) is requested for each row to be modified by an update statement or deleted by a deletestatement. Once the write lock request has been granted to one connection, no other connected can lock (read or write) thatrow until the write lock is freed upon execution of a commit or rollback statement. RDM Server SQL implicitly places awrite lock on rows created by execution of an insert statement. Note that SQL will also request and place a write lock on anyexisting rows that are referenced by foreign key values in the newly inserted row.

Locks are managed by SQL in conjunction with transactions. All locks that are issued outside of a transaction are read locks.After a transaction is started, as noted above, write lock requests are issued for the rows that are accessed for each insert,update or delete statement that is executed as part of the transaction.

Within a transaction, the row-level locks used by a select statement depend on the current transaction mode (see section 10.2below). Normally, a read lock is placed on the current row (i.e. most recently fetched row) and freed when the next row isread. This is called cursor stability transaction mode. In read repeatability transaction mode the read locks are kept in placeuntil the transaction ends. This ensures that a previously fetched row does not change during the transaction.

The with exclusive lock clause of the select statement requests that the system apply write-locks instead of read-locks to therows of the result set. If no transaction is active when the statement is executed, SQL will automatically start a transaction.The select statement that contains the with exclusive lock clause must be updateable which means that it:

l Does not contain a distinct, group by or order by clause, or a subquery,l Does not contain any column expressions, andl Has a from clause that refers to only a single table.

The with exclusive lock clause follows the where clause in the specification of a select statement as shown in the followingsyntax and example.

select:select [first | all | distinct] {* | select_item [, select_item]...}

from table_ref [, table_ref]...[where cond_expr][with exclusive lock][group by col_ref [, col_ref]... [having cond_expr]][order by col_ref [asc | desc] [, col_ref [asc | desc]]...]

select * from salesperson where mgr_id is not null with exclusive lock;

Page 138: RDMs 8.4 SQL User's Guide

10. Shared (Multi-User) Database Access

SQL User Guide 130

10.1.2 Table-Level Locking

The lock table and unlock table statements allow you to lock an entire table. Two lock modes are provided. Shared modeallows you (and others) read-only access to the table. Exclusive mode allows you to modify the table while denying all otherusers access to it. The syntax for lock table is shown below.

lock_table:lock table lock_spec[, lock_spec]...

lock_spec:[dbname.]tabname[, [dbname.]tabname]... [in] {share | exclusive} [mode]

All table locks are automatically freed whenever a transaction is commited or rolled back. Shared mode table locks can befreed explicitly with unlock table statement shown below.

unlock_table:unlock table [dbname.]tabname[[, dbname.]tabname]...

For example, you can issue the following lock table statement to lock the salesperson and customer tables in shared mode andthe sales_order and item tables in exclusive mode.

lock table salesperson, customer sharesales_order, item exclusive;

A typical use for table locks is to place an exclusive lock on a table in order to do a bulk load, as illustrated in the followingexample which loads the inventory database from comma-delimited text files stored in the RDM Server catdev device. By theway, notice the update stats statement following the bulk load. It is always a good practice to execute update stats after mak-ing substantial modifications to a database to ensure that the optimizer is generating access plans based on reasonable usagestatistics.

lock table product, outlet, on_hand exclusive;insert into product from file "product.txt" on catdev;insert into outlet from file "outlet.txt" on catdev;insert into on_hand from file "onhand.txt" on catdev;commit;update stats on invntory;

10.1.3 Lock Timeouts and Deadlock

RDM Server SQL issues lock requests that are either granted or denied. Lock requests are normally queued, waiting for thecurrent lock on the row (or table) to be freed at which time the request at the front of the queue will be granted. Associatedwith each connection is a lock timeout value that specifies how long an ungranted lock request can wait on the queue. Thisis an important feature to prevent the occurrence of a deadlock in which two connections each hold locks on rows for whichthe other connection has a lock request (this is the simplest form of a deadlock—there are many ways in which deadlock canoccur among multiple users). In order to avoid deadlock, when a timeout error is returned from the execution of an insert,update or delete statement, the proper procedure is to rollback the transaction and start over. This will free that transaction'slocks allowing another connection's competing transaction to proceed.

The set timeout statement can be used to set the lock request timeout value for a connection.

Page 139: RDMs 8.4 SQL User's Guide

10. Shared (Multi-User) Database Access

SQL User Guide 131

set_timeout:set timeout [to | =] numseconds

The numseconds is an integer constant that specified the minimum number of seconds a lock request is to wait. The systemdefault is 30 seconds. Setting the timeout value to 0 will cause lock requests that cannot be granted to timeout immediately.Setting the timeout value to -1 will cause lock requests to wait indefinitely.

WARNING: Do not disable timeouts (set timeout = -1) on a deployed/operational database unless you areabsolutely certain that there is no way a deadlock can occur in your application. If you are using row-levellocking it is highly unlikely that you can be certain your application is deadlock free. Disabling timeouts is afeature intended primarily for diagnosis and testing.

10.2 Transaction Modes

RDM Server SQL automatically controls locking of accessed rows during the processing of select, insert, update, and deletestatements. There are several methods provided by RDM Server SQL that allow you to control the behavior of the lockingoperation. The following two set statements are used to establish the desired multi-user operational behavior.

set_read_repeatability:set read repeatability [to | =] {on | off}

set_transaction:set trans[action] isolation [to | =] {on | off}

The effects of these two statements are described in table 10-3 below.

transactionisolation

repeatablereads

description

on on This is called read repeatability mode. Changes from other con-nections (users) are not visible until committed. All rows arelocked. Read locks within a transaction are kept until the trans-action commits or is rolled back.

on off Called cursor stability mode; this is the default mode for RDMServer SQL. Changes are not visible to other connections until com-mitted. A read lock is kept for the current row only. When thecursor is advanced to the next row the current row is freed.

off on Allows dirty reads outside of a transaction whereby uncommittedchanges from other connections are visible and read locks are notrequired to read data from the database. Inside a transaction, beha-vior is identical to read repeatability mode.

off off Allows dirty reads outside of a transaction whereby uncommittedchanges from other connections are visible and read locks are notrequired to read data from the database. Inside a transaction, beha-vior is identical to cursor stability mode.

Table 10-3. Transactions Control Settings Part I

Page 140: RDMs 8.4 SQL User's Guide

10. Shared (Multi-User) Database Access

SQL User Guide 132

Regardless of what mode you are in, all rows that are modified through execution of an insert, update, or delete statement arewrite-locked and remain so until the transaction is ended through either a commit or a rollback. Because of this, it is a goodidea to keep the sizes of your transactions small. The more rows that are changed within a transaction, the more locks theserver must manage. The overhead associated with this lock management could become excessive. A commit/rollback willfree all of the write locks. Thus, short transactions can increase system throughput.

Read repeatability mode is the strictest form of transaction isolation available. In this mode, every row that is read within atransaction is read-locked and kept locked until the transaction ends. Thus, rows that are re-fetched inside a transaction areguaranteed to have the same values.

Cursor stability mode is the RDM Server SQL system's default mode. In this mode, a read lock is placed on each row as it isfetched. When the next row is fetched, the lock on the current row is freed and the new row is locked. Thus, only the currentrow is locked at any time.

So-called "dirty read" mode is useful in situations where the preciseness of the data is not particularly important. It could beused, for example, when you are looking for a particular row of a table of the "I'll know it when I see it" variety. Its advantageis that it does not place any locks and, therefore, does not get blocked by any rows in its path that happen to be write-lockednor will it block other write-lock requests.

The transaction and read repeatability modes can also be set using the form of the set transaction statement shown below.

set_transaction:set trans[action] trans_mode[, trans_mode]

trans_mode:isolation level {read uncommited | read committed | repeatable read}

This form adheres to standard SQL with the modes set as indicated in the table below.

mode setting transactionisolation

repeatablereads

read uncommitted on on

read committed off on

repeatable read off off

Table 10-4. Transactions Control Settings Part II

Page 141: RDMs 8.4 SQL User's Guide

11. Stored Procedures and Views

SQL User Guide 133

11. Stored Procedures and Views

11.1 Stored Procedures

11.1.1 Create a Stored Procedure

An RDM Server SQL stored procedure is a precompiled group of one or more SQL (DML) statements stored in the systemcatalog. A stored procedure is defined using the create procedure statement that conforms to the following syntax.

create_procedure:create proc[edure] procname "description" [(arg_spec[, arg_spec]...)] as

proc_stmt...end proc[edure]

| create proc[edure] procname ["description"] in libname on devname

arg_spec:argname type_spec [default constant]

proc_stmt:open | close | flush | initialize_database | insert | delete

| update | select | lock_table | call | initialize_table| begin_trans | start_trans | commit | rollback | mark| set | notify | update_stats

insert:insert_values | insert_from_select

The name of the stored procedure is procname and must be unique as stored procedures have system-wide scope. An optional"description" string can be specified and will be stored with the procedure definition in the system catalog.

Procedures can have arguments. Associated with each argument is a name, data type and optional default value. The argnameis a case-insensitive identifier that can be any name except that it must be unique in the argument list of this procedure. Thedata type declared for each argument can be any of the specified arg_type entries. Note that it is not necessary to specify alength for a character argument as any is interpreted as a string since the length is determined from the actual value passed tothe procedure at the time it is invoked. The same is true for the precision and scaled of decimal type arguments.

One or more SQL statements that comprise the body of the stored procedure are placed in the order in which they will beexecuted between the as and the end procedure clauses. They do not need to be separated by semi-colons. Only the specifiedSQL statements can be contained in a stored procedure. The syntax for each of those statements is defined elsewhere in thismanual and/or the SQL Language Reference.

The order_report procedure illustrated below is a typical example of how you might use a stored procedure. The argumentssupply the date range over which a standard report is produced. If a value for end_date is not supplied when the procedure isexecuted, the end date be set to the current date, as defined by its default clause.

create procedure order_report(start_date date,end_date date default today) asselect sale_name, company, ord_num, ord_date, amount, tax

Page 142: RDMs 8.4 SQL User's Guide

11. Stored Procedures and Views

SQL User Guide 134

from salesperson, customer, sales_orderwhere salesperson.sale_id = customer.sale_id and

customer.cust_id = sales_order.cust_id andord_date between start_date and end_date

end procedure;

Assuming that the salesperson's sale_id is the same as the user name, the check_tickle procedure below retrieves all of a sales-person's notes in date order for the specified note_id. Note that you can abbreviate procedure as proc.

create proc check_tickle(id char) asselect note_date, cust_id, textln

from note, note_linewhere note_id = id and sale_id = user()

and note.note_id = note_line.note_idand note.note_date = note_line.note_date

order by 1, 2;end proc;

The preceding examples contain only one statement, but a stored procedure can contain any number of statements. Theexample procedure below, product_summary, uses two select statements. This stored procedure shows the total amount of aparticular product stored at all outlets, followed by the total amount of that same product that has been ordered.

create proc product_summary(pid smallint) asselect prod_id, prod_desc, sum(quantity) total_available

from product, on_handwhere prod_id = pid and product.prod_id = on_hand.prod_id

select prod_id sum(quantity) total_orderedfrom item where prod_id = pid

end proc;

11.1.2 Call (Execute) a Stored Procedure

An RDM Server SQL stored procedure is called through an call (execute) statement that references the procedure that con-forms to the syntax shown below.

call:{call | exec[ute]} procname [( arg_value [, arg_value]... )

arg_value:constant | ? | argname | corname.colname

An argument value, arg_value, can be one of the following:

l a constant that is compatible with its declared argument type,l a parameter marker (?) but not if the procedure is being called from within another procedure or a trigger,l the name of an argument of the stored procedure containing this call statement,l a reference to an old or new column value within a trigger definition.

Page 143: RDMs 8.4 SQL User's Guide

11. Stored Procedures and Views

SQL User Guide 135

RDM Server SQL returns control to the calling application after it completes processing. When an error occurs in the exe-cution of any of the statements in the stored procedure, the procedure immediately terminates and returns an error code to thecalling application.

If the stored procedure has arguments, the call statement must specify a value (or a place holder) for each one and they mustbe specified in the same order they were defined in the create procedure statement. The application does not have to supplya value for an argument that has a default value, but it does need to supply a comma as a placeholder for that parameter, asthe following example illustrates.

call myproc(17002,,1);

The statement in the next example invokes the order_report stored procedure created in the previous section.

call order_report(date "06-01-1997", date "06-30-1997");

Since the next statement is executed on 6/30/97, it will produce the same results as the preceding statement because of thedefault value specified for end_date. Note the use of exec (the alternate form of the execute statement) and the comma place-holder for the final default parameter.

exec order_report(date "06-01-1997",);

The following example invokes the check_tickle stored procedure defined in the prior section.

call check_tickle("PROSPECT");

11.2 Views

11.2.1 Create View

A view is a table derived from the results of the select statement which defines the view. Views can be used just like anytable, but it does not contain any rows of its own. Instead, it is solely composed of rows returned from its select statement onthe underlying base tables. The syntax for the create view statement is shown below.

create_view:create view [dbname.]viewname ["description"] [ (colname [, colname ]...) ]

as select expression[, expression]... from table_ref [, table_ref]...[where cond_expr][group by col_ref [, col_ref ]... [having cond_expr] ][with check option]

The table defined by the view is the one that results from executing the specified query. The select expressions constrain thevisible columns in the view. The where clause constrains the rows of the view to only those that satisfy its condition.

If a list of column names is specified, there must be one column name (colname) for each select expression. The value asso-ciated with each column is the value of its respective select expression (for example, the value of the fourth column is the res-ult of the fourth expression). If a column name list is not specified, the column names of the view are the same as the column

Page 144: RDMs 8.4 SQL User's Guide

11. Stored Procedures and Views

SQL User Guide 136

names in the select statement. If any of the columns in the select statement are expressions or if two columns have the samename, a column name list must be specified. Note that the select statement defining the view cannot have an order by clause.

In the following example, the create view statement defines a view that provides a summary of the total order amounts persalesperson per month for the current year. The sales_summary view contains three columns, sale_name, sales_month (monthin which the order was taken), and order_tot (total orders for the salesperson for the month).

create view sales_summary(sales_month, sale_name, order_tot) asselect month(ord_date), sale_name, sum(amount)

from salesperson, customer, sales_orderwhere year(ord_date) = year(curdate()) and

salesperson.sale_id = customer.sale_id andcustomer.cust_id = sales_order.cust_id

group by 1, 2;

11.2.2 Retrieving Data from a View

Use a view in exactly the same way you would use any table. For example, the select statement shown below uses the viewdefined in the previous section.

select * from sales_summary where sales_month in (1,2);

SALES_MONTH SALE_NAME ORDER_TOT1 Flores, Bob 19879.51 Jones, Walter 76887.871 Kennedy, Bob 328070.831 McGuire, Sidney 3437.51 Nash, Gail 1362501 Porter, Greg 74034.91 Robinson, Stephanie 29073.511 Stouffer, Bill 15901.611 Warren, Wayne 79957.981 Williams, Steve 32094.751 Wyman, Eliska 173878.572 Flores, Bob 8824.562 Jones, Walter 860652 Kennedy, Bob 103874.82 McGuire, Sidney 9386.252 Nash, Gail 3927.92 Robinson, Stephanie 164816.472 Stouffer, Bill 4049.092 Warren, Wayne 11265.922 Williams, Steve 623402 Wyman, Eliska 74851.2

The next example includes an order by clause. Notice that although the order_tot value is calculated using an aggregate func-tion (sum), the comparison is specified in the where clause and not in a having clause. If the comparison were defined as partof the view, it would need to be in the having clause of the create view's select statement.

select order_tot, sales_month, sale_name from sales_summarywhere order_tot > 10000.0 order by 1 desc;

ORDER_TOT SALES_MONTH SALE_NAME328070.83 1 Kennedy, Bob

Page 145: RDMs 8.4 SQL User's Guide

11. Stored Procedures and Views

SQL User Guide 137

252425 4 Porter, Greg173878.57 1 Wyman, Eliska164816.47 2 Robinson, Stephanie

143375 3 Kennedy, Bob137157.05 4 Wyman, Eliska

136250 1 Nash, Gail104019.5 6 Nash, Gail103874.8 2 Kennedy, Bob103076.79 6 Wyman, Eliska

11.2.3 Updateable Views

An updateable view can be the table referenced in an insert, delete or update statement. A view is considered updateablewhen the select statement defining the view meets all the following conditions.

l It does not contain a subquery or a distinct, group by or order by clause.l It does not contain any column expressions.l It has a from clause referring to only a single table. .

A view having a with check option specification must be updateable. When specified, the with check option requires thatany insert or update statements referencing the view must satisfy the where condition of the view's defining select statement.The following create view defines a view that restricts the outlet table to only those rows located in western region states.

create view west_outlets asselect * from outlet where state in ("CA","CO","OR","WA")with check option;

If you attempted to insert a row into the west_outlets view with a state value other than one of the states listed in the whereclause, the insert statement would be rejected as in the example below. If the with check option had been omitted in the viewdefinition, the row would have been stored.

insert into west_outlets values("SAL","Salem","MA",0);*** integrity constraint violation: check

11.2.4 Drop View

When a view is no longer needed, you can delete the view from the system by issuing a drop view statement.

drop_view:drop view [dbname.]viewname [cascade | restrict]

The cascade option (the default) causes all other views referencing this view to be automatically dropped by the system. Therestrict option prohibits dropping viewname if any other views exist that reference this view. The example code below cre-ates two views to help illustrate the use of the drop view statement.

create view acct_orders asselect sale_id, sale_name, cust_id, ord_num, ord_date, amount

Page 146: RDMs 8.4 SQL User's Guide

11. Stored Procedures and Views

SQL User Guide 138

from salesperson, customer, sales_orderwhere salesperson.sale_id = customer.sale_id

and customer.cust_id = sales_order.cust_id;

create view sales_summary(sales_month, sales_month, order_tot) asselect month(ord_date), sale_name, sum(amount)

from acct_orders where year(ord_date) = year(curdate())group by 1, 2;

The following statement will be rejected by SQL because a dependent view exists (sales_summary).

drop view acct_orders restrict;

The next statement, however, will drop not only the acct_orders view but sales_summary as well.

drop view acct_orders cascade;

The same result will occur using the next statement because cascade is the default action.

drop view acct_orders;

Thus, when in doubt, always specify restrict. You cannot undo (or roll back) a drop view.

11.2.5 Views and Database Security

One of the more important uses of views is in conjunction with database security. Views can have permissions assigned tothem just as with any table. If it is important to be able to restrict which columns from a base table users can have access to,you can simply define a view that includes only those columns. The view would be accessible to those users, whereas thebase table would not. For example, the following view could be used to hide personal information about salespersons such asdate of birth and commission rate from unauthorized eyes.

create view sales_staffas select sale_id, sale_name, region, office, mgr_id from salesperson;

Once the proper permissions have been established, the dob and commission columns would not be accessible to normalusers.

You can also use views to restrict rows from certain users. The west_outlets view in the last section could be set up so thatthose salespersons from the western region could only access information (for example, inventory quantities) from offices loc-ated in those particular states.

Page 147: RDMs 8.4 SQL User's Guide

12. SQL Database Access Security

SQL User Guide 139

12. SQL Database Access SecurityDatabase security provides the ability to restrict user access to database information through restrictions on the databasecolumns and tables, or on the kinds of statements that a particular user can use.

The RDM Server system has two classes of users. Administrator users have full access rights to all system capabilities anddatabases. Normal users have only the access rights granted to them by administrators and database owners. A system will typ-ically have only a single administrator user (often referred to as the system or database administrator). RDM Server does not,however, require that there be only one administrator.

RDM Server SQL provides two classes of access privileges: command privileges and database access privileges. Commandprivileges allow an administrator to specify the kinds of commands that a particular user is allowed to use. Database accessprivileges allow an administrator or database owner to specify the database information and operations that a particular user isallowed to access. User access rights are assigned for an RDM Server SQL database using the grant and revoke statements.

To manipulate an RDM Server SQL database, a user must have both table and command privileges. For example, RDM Serverdoes not allow a user without delete command privileges to issue a delete statement, even if delete data access privileges onthe table have been granted to that user.

Attempts to execute an SQL statement by a user for which proper access privileges have not been grantedwill result in an access rights violation error returned from RDM Server SQL.

Changes to a user's security settings do not take effect until the next time that user logs in to RDM Server.

12.1 Command Access Privileges

12.1.1 Grant Command Access Privileges

Command privileges specify the kinds of RDM Server SQL statements available to a user for database manipulation. The formof the grant statement that is used to do this is defined by the following syntax.

grant:grant cmd_spec to user_id[, user_id]...

cmd_spec:all commands [but command [, command]...]

| commands command [, command]...

command:create {database | proc[edure] | trigger}

| insert | update | delete| lock table | unlock table

The user_id is an identifier that is case-sensitive and must exactly match the user id for the desired user. Two methods ofgranting command privileges can be used. You can grant all commands but and list only those commands the user cannotexecute or you can grant commands followed by the list of only those command the user can issue. The specific commandprivilege classes that can be granted (or not granted) are given in the table below. All other commands (including select) canbe issued by any user.

Page 148: RDMs 8.4 SQL User's Guide

12. SQL Database Access Security

SQL User Guide 140

Command Class Description

create database Allows user to issue any DDL statement or create, alter, or drop trigger statement.

create view Allows user to define his/her own views.

create procedure Allows user to define his/her own stored procedures.

create trigger Allows user to define triggers.

insert, update, or delete Allows user to issue insert, update, or delete statements.

lock table Allows user to issue lock and unlock table statements.

Table 12-1. Command Privilege Definitions

The example below grants permission for all users to issue any statements except DDL statements. It allows only the usersGeorge and Martha to create databases.

grant all commands but create database to public;grant all commands to George, Martha;

The next example restricts the user Jack to issuing select, update, and create view statements.

grant commands create view, update to "Jack";

12.1.2 Revoke Command Access Privileges

To rescind command privileges, an administrator can issue a revoke which identifies the specific commands that a user can nolonger issue. As with grant, two methods of specification are allowed as shown below. One form identifies the commandsfrom the restricted list that the user cannot use. The other form (all but) identifies the commands from the restricted list thatthe user can use.

revoke:revoke cmd_spec to user_id[, user_id]...

cmd_spec:all commands [but command [, command]...]

| commands command [, command]...

command:create {database | proc[edure] | trigger}

| insert | update | delete| lock table | unlock table

The privileges that are being revoked must have been previously granted. The specified privileges can be revoked from allusers (public) or be restricted from only the users listed in the revoke command.

The example below grants permission for all users to issue any statements except DDL statements. It allows only the usersGeorge and Martha to create databases.

grant all commands but create database to public;grant all commands to George, Martha;

The next example restricts the user Jack to issuing select, update, and create view statements.

Page 149: RDMs 8.4 SQL User's Guide

12. SQL Database Access Security

SQL User Guide 141

grant commands create view, update to Jack;

12.2 Database Access Privileges

12.2.1 Grant Table Access Privileges

Database access privileges allow an administrator or database owner to specify the database information and operations that aparticular user is allowed to access. You can assign user access privileges to database tables, views, and columns using the fol-lowing form of the grant statement.

grant:grant item_spec to {public | user_id[, user_id]...}

[with grant option] [cascade | restrict]

item_spec:{privilege[, privilege]... | all [privileges] } on [dbname.]tabname

privilege:select | delete | insert | update [(colname[, colname]...)] | trigger

The creator of a database (that is, the user who issued the create database statement) is the owner of that database. When adatabase is created, only the owner and administrator users are allowed to access that database. The owner can grant otherusers certain access privileges to the database. The grant statement is used to assign these access privileges to other users. Par-ticular privileges can be granted to specific users or to all users (public). The with grant option grants the specified users theright to issue other grant statements on the specified table. The cascade option indicates that the access privilege is to cas-cade down to the RDM Server core level access rights settings for the user. This only matters where the specified user(s) willbe executing application components that perform core-level access to the SQL database. The restrict option applies only toSQL usage and is the default.

The types of access privileges are defined in the following table.

Command Class Description

all privileges Allows user all of the following access privileges on the table.

select Allows user to issues select statements on the table.

insert Allows user to insert rows into the table.

delete Allows user to delete rows from the table.

update Allows user to update any column of any row in the table.

update (colname [, colname ]...) Allows user to update only the listed columns of any row in the table.

trigger Allows user create, alter, or drop a trigger on the table.

Table 12-2. Database Access Privilege Definitions

Note that users who are granted a trigger privilege on a table must also have the create database command privilege.

In the example below, the system administrator or database owner is allowing all users privileges to issue select statements toquery invntory database tables. Only users George and Martha have permissions to modify the database.

grant select on invntory.product to public;grant select on invntory.outlet to public;grant select on invntory.on_hand to public;

Page 150: RDMs 8.4 SQL User's Guide

12. SQL Database Access Security

SQL User Guide 142

grant all on invntory.product to George, Martha;grant all on invntory.outlet to George, Martha;grant all privileges on invntory.on_hand to George, Martha;

The following example illustrates how you can use a view to restrict access to a portion of a database table.

create view skk_customers asselect * from customer where sale_id = "SKK"

with check option;grant all privileges on skk_customers to Sidney;

12.2.2 Revoke Table Access Privileges

The revoke statement is used to rescind a user's database table access privileges that had been previously granted. The syntaxfor the revoke statement is shown below.

revoke:revoke item_spec to {public | user_id[, user_id]...}

[with grant option] [cascade | restrict]

item_spec:{privilege[, privilege]... | all [privileges] } on [dbname.]tabname

privilege:select | delete | insert | update [(colname[, colname]...)] | trigger

The specified privileges can be revoked from all users (public) or be restricted from only the users specified in the revoke com-mand. As with grant, the cascade option indicates that the access privilege is to cascade down to the RDM Server core levelaccess rights settings for the user. This only matters where the specified user(s) will be executing application components thatperform core-level access to the SQL database. The restrict option applies only to SQL usage and is the default.

In the example below, the system administrator or owner is revoking George's access privileges for several tables of the invn-tory database.

revoke insert, update, delete on product from George;revoke insert, update, delete on outlet from George;revoke insert, update, delete on on_hand from George;

The next example shows an rsql script that automatically drops the home_sales view when user Martha's access privilege onthe salesperson table is revoked.

.c 1 RDM Server Admin xyzzycreate view home_sales as select sale_name from salesperson where office = "SEA";grant select on home_sales to Martha;.c 2 RDM Server Martha HipposAreHipselect * from home_sales;

SALE_NAMEFlores, BobPorter, GregStouffer, BillBlades, Chris

Page 151: RDMs 8.4 SQL User's Guide

12. SQL Database Access Security

SQL User Guide 143

.d 2

.c 1revoke select on home_sales from Martha;.c 2 RDS Martha HipposAreHipselect * from home_sales;****RSQL Diagnostic 4200: user access rights violation: home_sales

Page 152: RDMs 8.4 SQL User's Guide

13. Using SQL in a C Application Program

SQL User Guide 144

13. Using SQL in a C Application ProgramYou use the RDM Server SQL system from a C application program by making calls to the RDM Server SQL application pro-gramming interface (API) library functions. The RDM Server SQL API is based on the industry standard Open Database Con-nectivity API specification developed by Microsoft. A complete description of the ODBC standard is available on the Webat: http://msdn.microsoft.com/en-us/library/ms710252(v=vs.85).aspx.

SQL statements are dynamically compiled and executed and result sets are retrieved from the RDM Server through these func-tion calls. Raima has also included a variety of additional functions in order to support RDM Server specific capabilities.

The RDM Server ODBC functions allow you to connect to one or more RDM Servers on the network as depicted below inFigure 13.1. A given client application program can have any number of active connections. You can even have more thanone connection from a client to one server. Since each connection has its own individual context, you can simultaneously beprocessing active statements in multiple connections.

Figure 13-1. RDM Server Client-Server Application Architecture

SQL statements are compiled and executed using different functions. Once compiled, a statement can be repeatedly executedwithout having to be recompiled. Statements can contain parameter markers that serve as place holders for constant valuesthat are bound to program variables when the statement is executed. New sets of parameter values are assigned by simplychanging the value of the program variable before re-executing the statement.

The set of select statement result rows (result set) is retrieved a row at a time. The result columns can either be bound to pro-gram variables or individually retrieved one column at a time after each result row has been fetched.

Page 153: RDMs 8.4 SQL User's Guide

13. Using SQL in a C Application Program

SQL User Guide 145

Cursors can be defined for a select statement to support positioned updates and deletes. An update or delete statement canrefer to the cursor associated with an active select statement to modify a particular row of a table.

Several functions are provided through which you can interrogate RDM Server SQL about the nature of a compiled statement.For example, you can find out how many columns are in the result set as well as information about each one (such as thecolumn name, type, and length). RDM Server SQL also provides additional function calls to utilize RDM Server SQLenhancements or to simply provide information not included in the standard ODBC API. For example, RDM Server SQLincludes a non-ODBC function that will tell you the type of statement after the statement has been compiled (prepared).

One of the most powerful features of RDM Server SQL is its extensibility provided through its server-based programming cap-abilities. The RDM Server SQL API that is used in server-based programming has additional functions to support, forexample, User-Defined Function (UDF) and User-Defined Procedure (UDP) implementations as described in Developing SQLServer Extensions.

13.1 Overview of the RDM Server SQL API

This section contains summary descriptions of all of the RDM Server SQL functions. The functions are organized into tablesfrom the following usage categories:

l Connecting to RDM Server database serversl Setting and retrieving RDM Server SQL optionsl Preparing (compiling) SQL statementsl Executing SQL statementsl Retrieving result information and datal Terminating statements and transactionsl Terminating RDM Server server connectionsl System catalog access functionsl RDM Server SQL support functionsl ODBC support functions

Each client application program accesses RDM Server SQL client interface functions through handles which are initially alloc-ated through a call to function SQLAllocHandle. There are four types of handles used in the ODBC API. An environmenthandle that is used to keep track of the RDM Server connections utilized by the client application. Each connection has anassociated connection handle that is first allocated and then passed to function SQLConnect to log in to a specified RDMServer SQL server. All statements that are to be executed on that server are associated with that particular connection handle.A statement handle is used to keep track of all of the information related to the compilation and execution of an SQL state-ment. A descriptor handle which is used for keeping track of information about columns and parameters.

The functions used to establish a connection with an RDM Server are described below.

Function Name PurposeSQLAllocHandle Allocates an environment, connection, statement, or descriptor handle. Only one environment

handle is used by each client program. Each environment handle can support multiple con-nections. Connection handles manages information related to one RDM Server connection.Statement handles manage information related to one RDM Server SQL statement. Descriptorhandles are used to hold information about SQL statement parameters and result columns.

SQLConnect Connects and logs in to the specified Raima Database Server with the specified user nameand password.

SQLDriverConnect Connects and logs in to the specified Raima Database Server with the specified user nameand password. May prompt user for further information.

Table 13-1. Server Connection Functions

Page 154: RDMs 8.4 SQL User's Guide

13. Using SQL in a C Application Program

SQL User Guide 146

Function Name PurposeSQLSessionId Called by the application to get the RDM Server session id associated with an SQL con-

nection handle. The session id is used with the RDM Server remote procedure call functionrpc_emCall to call an RDM Server extension module from a client application.

SQLConnectWith Called by an extension module, UDF or UDP to get the RDM Server SQL connection handleassociated with an RDM Server session id.

The RDM Server SQL system supports a variety of runtime operational control options (attributes). These include four levelsof multi-user locking and transaction isolation control. These options can be set for all of the statements executed on a par-ticular connection or for a single statement and are managed using the following functions.

Function Name PurposeSQLGetEnvAttr Returns an environment attribute setting.SQLSetEnvAttr Sets an environment attribute.SQLGetConnectAttr Returns a current connection attribute setting.SQLSetConnectAttr Sets a connection attribute.SQLSetStmtAttr Sets a statement attribute.SQLGetStmtAttr Returns a current statement attribute setting.

Table 13-2. SQL Control Attribute Functions

SQL statements are submitted to an RDM Server SQL server as text strings. As such, they need to be compiled into a formthat is suitable for efficient execution. All functions that involve some kind of operation on a specific SQL statement use thesame statement handle which must first be allocated via a call to SQLAllocHandle. The functions that are called before astatement can be executed are listed below in Table 13-3.

Function Name PurposeSQLAllocHandle Allocates a statement handle.SQLGetCursorName Returns the cursor name associated for the statement handle.SQLSetCursorName Sets the cursor name for the statement handle.SQLPrepare Prepares an RDM Server SQL statement for execution.SQLBindParameter Binds a client program variable to a particular SQL parameter marker.

Table 13-3. SQL Statement Preparation Functions

Execution of a previously prepared SQL statement is performed through a call to the SQLExecute function. You can bothprepare and execute a statement with a single call to the SQLExecDirect function. The execution control functions are lis-ted in Table 2 4.

Function Name PurposeSQLExecute Executes a previously prepared statement.SQLExecDirect Prepares and executes a statement.SQLNumParams Returns the number of parameter markers in a statement.SQLDescribeParam Returns a description (e.g., data type) associated with a parameter marker.SQLParamData Used along with SQLPutData to provide parameter (usually blob data) values.SQLPutData Assigns specific (or next chunk of a blob) value for a parameter.

Table 13-4. Statement Execution Functions

Much of the work performed by an RDM Server SQL application will be associated with the processing of the data actuallyretrieved from the database on the server. This work entails making inquiries to RDM Server SQL about the characteristics of

Page 155: RDMs 8.4 SQL User's Guide

13. Using SQL in a C Application Program

SQL User Guide 147

a compiled or executed statement, fetching results, and processing errors. The functions used in this regard are summarizedbelow.

Function Name PurposeSQLRowCount Returns the number of rows affected by the last statement (insert, update, or delete).SQLNumResultCols Returns the number of columns in the select statement result set.SQLDescribeStmt Returns the type of statement that is associated with the specified statement handle.SQLDescribeCol Returns a description of a column in the select statement result set.SQLColAttribute Returns additional attribute descriptions of a column in the select statement result set.SQLBindCol Specifies the location of a client program variable into which a column result is to be stored.SQLFetch Retrieves the next row of select statement result.SQLFetchScroll Retrieves a rowset of select statement result rows.SQLSetPos Sets the cursor position within a static cursor.SQLGetData Returns a column value from the result set.SQLMoreResults Determines if there are more result sets to be processed and, if so, executes the next statement

to initialize the result set.SQLGetDiagField Retrieves the current value of a field in the diagnostic record associated with the statement.SQLGetDiagRec Retrieves the current value of the diagnostic record associated with the statement.SQLWhenever Registers the address of a function in the client program that is to be called by RDM Server

SQL whenever the specified error occurs.SQLError Returns error or status information.

Table 13-5. Results Processing Functions

The processing of a statement is terminated using several functions depending on the desired results. Database modificationstatements (that is, insert, update, or delete) are terminated by either committing or rolling back the changes made during atransaction. When you have finished your use of a statement handle, you should free the handle so that the system can free allof the memory associated with it. The functions that perform these operations are described below.

Function Name PurposeSQLFreeStmt Ends statement processing and closes the associated cursor and discards pending results.SQLCloseCursor Closes the cursor on the statement handle.SQLFreeHandle Frees statement handle and all resources associated with the statement handle.SQLCancel Cancels an SQL statement.SQLEndTran Commits or rolls back a transaction.

Table 13-6. Statement Termination Functions

A client application program ends by disconnecting from all servers that it is connected to and then freeing the connectionhandles and the environment handle using the following functions.

Function Name PurposeSQLDisconnect Closes the connection.SQLFreeHandle Frees the connection or environment handle.

Table 13-7. Connection Termination Functions

Several functions are provided which allow an SQL application to retrieve database definition information from the SQL sys-tem catalog. These functions each automatically execute a system-defined select statement or stored procedure that returns aresult set that can be accessed using SQLFetch.

Page 156: RDMs 8.4 SQL User's Guide

13. Using SQL in a C Application Program

SQL User Guide 148

Function Name PurposeSQLTables Retrieves result set of table definitions.SQLColumns Retrieves result set of column definitions.SQLForeignKeys Retrieve information about a table's foreign key columns.SQLPrimaryKeys Retrieve information about a table's primary key columns.SQLSpecialColumns Retrieves result set of columns that optimally access table rows.SQLProcedures Retrieves result set of available stored procedures.SQLStatistics Retrieves result set of statistics about a table and/or indexes.

Table 13-8. Catalog Access Functions

The RDM Server SQL support functions are provided to facilitate use of the direct access capabilities of RDM Server. Thesefunctions assist in retrieving rowid values that are automatically assigned by RDM Server SQL as well as allowing SQL pro-grams to easily utilize the low-level RDM Server (Core API) function calls when necessary.

Function Name PurposeSQLRowId Returns the rowid of the current row.SQLRowDba Returns the RDM Server database address of the current row.SQLDBHandle Returns the RDM Server database handle for an open SQL database.SQLRowIdToDba Converts SQL row id to RDM Server database address.SQLDbaToRowId Converts RDM Server database address to SQL rowid.

Table 13-9. RDM Server SQL Support Functions

With the Microsoft ODBC specification, third-party front-end tool vendors can call functions that provide information describ-ing the capabilities that are supported by a back-end database. SQLGetInfo, SQLGetTypeInfo, and SQLGetFunctionscan be called to discover the ODBC features that are supported in RDM Server SQL.

Function Name PurposeSQLNativeSql Translates ODBC SQL statement into RDM Server SQL.SQLGetFunctions Retrieves information about RDM Server SQL-supported functions.SQLGetInfo Retrieves information about RDM Server SQL-supported ODBC capabilities.SQLTypeInfo Retrieves result set of RDM Server SQL data types.

Table 13-10. ODBC Support Functions

13.2 Programming Guidelines

This section gives an overview of the calling sequences for accessing the RDM Server SQL server through the RDM ServerSQL C API. Most of the function calls must be made in a particular sequence. In most cases, the sequence is quite natural.The guidelines given below illustrate the calling sequences for several of the standard types of operations. Actual pro-gramming examples are provided in subsequent sections.

This section gives an overview of the calling sequences for accessing the RDM Server SQL server through the RDM ServerSQL C API. Most of the function calls must be made in a particular sequence. In most cases, the sequence is quite natural.The guidelines given below illustrate the calling sequences for several of the standard types of operations. Actual pro-gramming examples are provided in subsequent sections.

Figure 13-2 shows the sequence of calls required to connect to a particular RDM Server. The first call to SQLAllocHandleallocates an environment handle that is then passed to the next call to SQLAllocHandle which is used to allocated a con-nection handle. The connection handle is passed to SQLConnect, which in turn connects to the specified RDM Server. All

Page 157: RDMs 8.4 SQL User's Guide

13. Using SQL in a C Application Program

SQL User Guide 149

of the activity associated with a particular connection is identified by the connection handle. RDM Server SQL allows anapplication to open any number of connections to any number of RDM Server systems.

Figure 13-2. Connecting to RDM Server Flow Chart

Function SQLDisconnect will close (log out from) the RDM Server connection. An error is returned if any uncommittedtransactions are pending on the connection. Any active statements are automatically freed for the specified connection. Beforecalling SQLDisconnect, you should explicitly close (SQLFreeStmt) all active statements for a particular connection.

The connection handle can be reused in another SQLConnect or released by a call to SQLFreeHandle. When all con-nections have been closed and freed, a final call to SQLFreeHandle is made to free the environment handle.

A flow chart that gives a typical sequence of calls for processing a select statement is shown in Figure 13-3. A statement isassociated with a statement handle allocated by the call to SQLAllocHandle. Any number of statement handles (i.e., sep-arate SQL statements) can be active at a time for a given connection. Statement handles are analogous to cursors when theSQL statement associated with the statement handle is a select statement. Function SQLPrepare is used to compile (but notexecute) an SQL statement. If SQLPrepare is successful, you can call functions SQLDescribeCol and SQLNumRes-ultCols to get information about the result columns such as the column name, data type and length. This is used so that theappropriate host variables can be set up (through calls to SQLBindCol) to hold the column values for each result row.

Page 158: RDMs 8.4 SQL User's Guide

13. Using SQL in a C Application Program

SQL User Guide 150

Figure 13-3. Select Statement Processing Flow Chart

SQL statements can have embedded parameter markers. A parameter marker is specified by a '?' in a position that would nor-mally take a literal constant. The host variable for each parameter value must be specified by a call to SQLBindParameterbefore the statement is executed by SQLExecute.

Page 159: RDMs 8.4 SQL User's Guide

13. Using SQL in a C Application Program

SQL User Guide 151

Each row of the result set is retrieved one-at-a-time through the call to SQLFetch. When all rows have been fetched,SQLCloseCursor is called to terminate processing of the select statement. In this example, the handle is then freed by thecall to SQLFreeHandle, so it can no longer be used. Alternatively, the statement could just be closed, terminating the cur-rent select statement execution but still allowing the statement to be reexecuted at a later time. Notice in this example thatstatement compilation and execution are performed by separate functions. This allows the same statement to be executed mul-tiple times without having to recompile it. For example, you might specify a different set of parameter values for each sub-sequent execution. The flow chart shown below shows a modified segment of the prior flow chart to indicate how this isdone.

Figure 13-4. Select Statement Re-Execution Flow Chart

Figure 13-5 gives a flow chart showing a sequence of calls that perform a positioned update. A positioned update statementinvolves the use of two statement handles. The one associated with the select statement is the cursor. The update statement isexecuted through the other statement handle once the cursor has been positioned to the desired row.

The particular cursor on which the update is performed can be specified two ways. In this example, function SQLSetCurs-orName is called to specify a user-defined cursor name. That name would then need to be referenced in the where current ofclause in the update statement text compiled by the call to SQLPrepare. Alternatively, function SQLGetCursorNamecould be called to retrieve a system-generated cursor name which would need to be incorporated into the update statementstring prior to the call to SQLPrepare used to compile it.

When all updates have been completed, function SQLEndTrans is called to commit the changed rows to the database. Thiscould also be done by a call to SQLExecDirect to compile and execute a commit statement.

Page 160: RDMs 8.4 SQL User's Guide

13. Using SQL in a C Application Program

SQL User Guide 152

Figure 13-5. Positioned Update Flow Chart

13.3 ODBC API Usage Elements

This section describes the basic elements that are used by the RDM Server SQL ODBC API functions. Included are descrip-tions of the standard header files, data type and constant definitions contained in those header files that are used in the func-tion calls for argument types, indicator and descriptor variables, and status return codes.

13.3.1 Header Files

Your RDM Server SQL C application must include at least one of the three standard header files described below in Table13-11. The files can be found in the RDM Server include directory.

File Descriptionsql.h Standard ODBC Core-level header file. Includes prototypes, data and constant definitions for

the ODBC Core-level functions. Automatically included by sqlext.h.sqlext.h Microsoft ODBC levels 1 and 2 extensions header file. Includes prototypes and data defin-

itions for the ODBC level 1 and 2 functions. Automatically included by sqlrds.h.sqlrds.h RDM Server SQL main header file. Includes prototypes and data definitions for all functions

used with RDM Server SQL.

Table 13-11. RDM Server SQL Header Files

Inclusion of sqlrds.h in your application provides access to all RDM Server SQL capabilities. You can include sql.hwith your application to ensure its conformance to only the ODBC Core-level specification. Include sqlext.h to ensureconformance to the full ODBC specification. The sqlext.h file automatically includes sql.h. The sqlrds.h file auto-matically includes sqlext.h.

Page 161: RDMs 8.4 SQL User's Guide

13. Using SQL in a C Application Program

SQL User Guide 153

The sql.h and sqlext.h files in the RDM Server include directory are our own developed versions of the same filesthat are part of the Microsoft ODBC SDK.

13.3.2 Data Types

SQL API uses a special set of type definitions. Rather than relying on the base types defined in the ANSI C programming lan-guage, data types that map into the standard C data types have been defined. The function arguments have been specifiedusing these ODBC-defined data types. The application variables that you pass in to the functions must be declared with theproper data type. Those defined as either int64 or int32 will be int64 on a 64-bit RDM Server installation otherwiseint32. There are also some RDM Server-specific data types that are not included in the table. These are described in theSQL API Reference Manual in the descriptions of the functions that use them.

Type Name DescriptionSQLHANDLE Generic handle. Can be any one of the four handle types: SQLHENV, SQLHDBC, SQLHSTMT,

SQLHDESC: "void *".SQLHENV Environment handle.SQLHDBC Connection handle.SQLHSTMT Statement handle.SQLHDESC Descriptor handle.SQLPOINTER Generic pointer variable: "void *".SQLLEN Signed buffer/string length variable: int64 or int32.SQLULEN Unsigned buffer/string length variable: int64 or int32.SQLCHAR Standard character: unsigned char.SQLWCHAR Wide character: usually wchar_t.SQLSMALLINT int16.SQLUSMALLINT uint16.SQLINTEGER int32.SQLUINTEGER uint32.SQLBIGINT int64.SQLUBIGINT uint64.SQLREAL float.SQLFLOAT double.SQLDOUBLE double.SQLDECIMAL unsigned char (byte array).SQLNUMERIC unsigned char (byte array).SQLDATE unsigned char (string).SQLTIME unsigned char (string).SQLTIMESTAMP unsigned char (string).SQLVARCHAR unsigned char (string).SQLRETURN Function return code: int16.DATE_STRUCTSQL_DATE_STRUCT

Unpacked date struct.

TIME_STRUCTSQL_TIME_STRUCT

Unpacked time struct.

TIMESTAMP_STRUCTSQL_TIMESTAMP_STRUCT

Unpacked timestamp struct.

Table 13-11. SQL API Data Type Descriptions

Page 162: RDMs 8.4 SQL User's Guide

13. Using SQL in a C Application Program

SQL User Guide 154

13.3.3 Use of Handles

The RDM Server SQL application uses several ODBC-defined handles. Introduced in ODBC 3, the SQLAllocHandle func-tion is used to allocate all of the handles. An environment handle (SQLHENV type) is allocated by passing SQL_HANDLE_ENV as the handle type. Although only one environment handle is required, more may be allocated if needed. Before execut-ing any other ODBC function using the environment handle SQLSetEnvAttr should be called with SQL_ATTR_ODBC_VERSION to set the version of ODBC that the application will use. A connection handle (SQLHDBC type) is allocated bypassing SQL_HANDLE_DBC as the handle type. The application can open any number of connections to any number ofRDM Servers, with each connection referenced through a separate connection handle. A statement handle (SQLHSTMT type)is allocated by passing SQL_HANDLE_STMT as the handle type. There is no restriction on the number of RDM Server SQLstatement handles your application can use. However, to conserve server memory, it is good practice to keep to a minimumthe number of active statement handles. Lastly, a descriptor handle (SQLHDESC type) is allocated by passing SQL_HANDLE_DESC as the handle type.

When your RDM Server SQL application needs to call a Core API function or a server-side extension module, it must use aCore session handle (RDM_SESS type) associated with the active server connection handle. (Each connection corresponds toa single RDM Server login session.) The application calls SQLSessionId to retrieve the session handle.

For a particular server connection, your RDM Server SQL application might also need to call SQLDBHandle to obtain thedatabase handle (RDM_DB type) that RDM Server uses for a Core database. With this handle, the application can use theruntime API (bypassing RDM Server SQL) to access database information.

13.3.4 Buffer Arguments

The usage rules for passing buffer arguments are as follows:

l Each RDM Server function argument pointing to a string or data buffer has an associated length argument.l An input length argument contains the actual length of a string or buffer. If the application specifies a length value ofSQL_NTS (an ODBC-specified negative constant meaning "null-terminated string"), the pointer must address a null-ter-minated string. A length greater than or equal to zero implies the input string is not null-terminated (for example, ifyour application is written in Pascal).

l Two length arguments are used for an output buffer. The first argument provides the size (in bytes) of the output buf-fer. The second argument is a pointer to a variable in which RDM Server returns the number of bytes actually writtento the buffer. If the value is null, the function sets the result length to the ODBC negative constant SQL_NULL_DATA.

l All RDM Server API functions that fill output buffers with character data write null-terminated strings. The buffersyour application provides to receive this data must be long enough to hold the terminal null byte.

l A null can be passed for the result output argument as long as the database schema does not allow a null for the resultfield. If a null field is allowed in the database schema, then ODBC 3.51 requires the result output argument be sup-plied. If not supplied, errNOINDVAR will be returned.

13.4 SQL C Application Development

13.4.1 RDM Server SQL and ODBC

RDM Server includes an ODBC driver and related files so that RDM Server can be accessed by Microsoft Windows applic-ations through the ODBC Driver Manager (DM). The driver is installed through the instodbc utility for Microsoft Win-dows. Connecting and using a driver through the ODBC DM is described in detail in the Microsoft ODBC manual. Details of

Page 163: RDMs 8.4 SQL User's Guide

13. Using SQL in a C Application Program

SQL User Guide 155

driver installation are given where applicable in Installing the Server Software and Installing RDM Server Client Software,from the RDM Server Installation / Administration Guide.

Note that your application does not need to operate with the ODBC DM if RDM Server SQL is the only type of data sourceused. Since the ODBC API is the native API for RDM Server SQL, you can simply link an ODBC-compliant application dir-ectly to the RDM Server SQL API in the client library, eliminating the overhead of the ODBC Driver Manager.

13.4.2 Connecting to RDM Server

An application often uses the following basic steps in processing RDM Server SQL statements:

1. Call SQLAllocHandle to allocate the environment handle for the program.2. Call SQLAllocHandle to allocate a connection handle.3. Call SQLConnect to connect the user to a specific RDM Sserver.

Perform the following basic steps to end your RDM Server session:

1. Call SQLDisconnect to terminate the client connection to the server.2. Call SQLFreeHandle to free the connection handle.3. Call SQLFreeHandle to free the environment handle.

The example below illustrates the use of these function calls. Note that the type definitions for the environment and con-nection handles are declared in the standard header file sql.h. Also note the use of the constant SQL_NTS for the lengtharguments in the call to SQLConnect to indicate that each of the char arguments is a standard C null-terminated string.

#include "sql.h"

char user[15]; /* user name */char pw[15]; /* password */SQLHENV eh; /* environment handle */SQLHDBC ch; /* connection handle */SQLHSTMT sh; /* statement handle */

...SQLAllocHandle(SQL_HANDLE_ENV, NULL, &eh);SQLAllocHandle(SQL_HANDL_DBC, eh, &ch);

/* fetch user name and password */ClientLogin(user, pw);

/* connect to MIS server */stat = SQLConnect(ch, "MISserver", SQL_NTS, user, SQL_NTS, pw, SQL_NTS);if (stat != SQL_SUCCESS ) return ( ErrHandler() );

... /* run MIS application */

SQLDisconnect(ch);SQLFreeHandle(SQL_HANDLE_DBC, ch);SQLFreeHandle(SQL_HANDLE_ENV, eh);

You can establish connections to any RDM Server system that is available on the network. RDM Server SQL maintains eachconnection in a separate task context. You can even have multiple connections to the same RDM Server system where, forexample, you may need to have separate task contexts to the same server.

Page 164: RDMs 8.4 SQL User's Guide

13. Using SQL in a C Application Program

SQL User Guide 156

13.4.3 Basic SQL Statement Processing

An application often uses the following basic steps in processing RDM Server SQL statements:

l Calls SQLAllocHandle (with SQL_HANDLE_STMT) to allocate a statement handle. This statement handle will beused in all the following steps.

l Calls SQLPrepare to compile the statement.l If processing a select statement, calls SQLBindCol to bind column results to host program variables.l Calls SQLBindParameter, if necessary, to associate a host variable with a parameter referenced in the statement.l Calls SQLExecute to execute the statement. This is usually the end of processing for any statement other than select.l For select statements, calls SQLFetch or SQLFetchScroll to retrieve the result set.l When finished with the handle, calls SQLFreeHandle (with SQL_HANDLE_STMT) to free it. If reusing the handle,calls SQLCancel or SQLFreeStmt with the SQL_CLOSE setting. Alternatively, you may call SQLCloseCursorto close an open cursor for reuse.

An application can call SQLExecDirect instead of making separate calls to SQLPrepare and SQLExecute but onlywhen a single call to SQLExecute would be needed.

The following example shows a simple, statement execution sequence that opens the example sales and invntory databases. Itconsists of a call to SQLAllocHandle to allocate a statement handle, a call to SQLExecDirect to compile and executethe open statement, and a call to SQLFreeHandle to drop the statement handle. The OpenDatabases function assumes thatthe server connection handle is valid. If it is not valid, SQLAllocHandle returns the SQL_INVALID_HANDLE error code.

#include "sqlext.h"

SQLRETURN OpenDatabases(SQLHANDLE dbc){SQLRETURN stat;SQLHANDLE hstmt;

if (stat = SQLAllocHandle(SQL_HANDLE_STMT, dbc, &hstmt)) == SQL_SUCCESS)stat = SQLExecDirect(hstmt, "open sales, invntory", SQL_NTS);

SQLFreeHandle(SQL_HANDLE_STMT, hstmt);return stat;

}

13.4.4 Using Parameter Markers

To save processing time, your application can compile a statement once, by calling SQLPrepare, and then execute the state-ment multiple times with calls to SQLExecute. If the statement has embedded parameter markers ("?") in it, different valuescan be substituted for these parameters before each statement execution. The application calls the SQLBindParameter functionbefore statement execution to associate host program variables with parameter markers. Each time the application calls SQLEx-ecute for the statement, the current values from the bound variables are substituted in the statement for the parameter markers.

The next example uses parameter markers with an insert statement to insert rows in the product table from data input by auser. The input is gathered by the local function GetValues, which sets the bound variables to the appropriate values. TheSQLExecute call then executes the insert statement using the current values in the variables.

In this example, note that the call to the SQLGetDiagRec function retrieves the sqlstate code and error message in the eventthat SQLExecute returns an error. The example also illustrates the use of SQLEndTran to commit or roll back databasechanges based on the occurrence of an error.

Page 165: RDMs 8.4 SQL User's Guide

13. Using SQL in a C Application Program

SQL User Guide 157

#include <stdio.h>#include <stdlib.h>#include "sqlext.h"

char insert[] = "insert into product(prod_id, prod_desc, price, cost) values(?,?,?,?)";

SQLHANDLE henv;SQLHANDLE hdbc;SQLHANDLE hstmt;SQLHANDLE error_handle;

int16 prod_id;char prod_desc[40];double price, cost;int16 handle_type;

int main(void){

FILE *txtFile;char sqlstate[6], emsg[80];char user[15], pw[8];int32 lineno;SQLUSMALLINT txtype;

if ((txtFile = fopen("product.txt", "r")) == NULL)abort("unable to open file\n");

SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv);SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)

SQL_OV_ODBC3, SQL_IS_INTEGER);SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);

/* fetch user name and password */ClientLogin(user, pw);

/* connect to MIS server */if ((stat = SQLConnect(hdbc, "MIS", SQL_NTS, user, SQL_NTS, pw, SQL_NTS)) != SQL_SUCCESS)

{handle_type = SQL_HANDLE_DBC;error_handle = hdbc;goto quit;

}

SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);SQLPrepare(hstmt, insert, SQL_NTS);SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_SHORT, SQL_SMALLINT,

0, 0, &prod_id, 0, NULL);SQLBindParameter(hstmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR,

40, 0, prod_desc, 40, NULL);SQLBindParameter(hstmt, 3, SQL_PARAM_INPUT, SQL_C_DOUBLE, SQL_FLOAT,

0, 0, &price, 0, NULL);SQLBindParameter(hstmt, 4, SQL_PARAM_INPUT, SQL_C_DOUBLE, SQL_FLOAT,

0, 0, &cost, 0, NULL);

handle_type = SQL_HANDLE_STMT;error_handle = hstmt;while (GetValues(&prod_id, prod_desc, &price, &cost)) {

if ((stat = SQLExecute(hstmt)) != SQL_SUCCESS)break;

}

Page 166: RDMs 8.4 SQL User's Guide

13. Using SQL in a C Application Program

SQL User Guide 158

quit:if (stat == SQL_SUCCESS)

txtype = SQL_COMMIT;else {

SQLGetDiagRec(handle_type, error_handle, 1, sqlstate, NULL, emsg, 80, NULL};printf("***Line %d - ERROR(%s): %s\n", lineno, sqlstate, errmsg);txtype = SQL_ROLLBACK;

}

/* commit or roll back transaction */SQLEndTran(SQL_HANDLE_DBC, hdbc, txtype);return 0;

}

13.4.4 Premature Statement Termination

Your application terminates a database modification statement (insert, update, or delete) by either committing or rolling backthe changes made during the transaction. When the application finishes using a statement handle, it should free the handle sothat RDM Server can free all associated memory.

The application terminates processing of a select statement by a call to SQLFreeStmt, with the SQL_CLOSE option,SQLCloseCursor, or SQLCancel (see the following example). Any result rows that the application has not fetched arethrown out at this time.

#include "sqlext.h"...

/* Print all rows of a table */SQLRETURN PrintTable(SQLHDBC svr, /* server connection handle */char *tabname) /* name of table whose rows are to be printed */

{char stmt[80];SQLHSTMT sh;

...

/* set up and compile select statement */sprintf(stmt, "select * from %s", tabname);SQLAllocHandle(SQL_HANDLE_STMT, svr, &sh);if (SQLExecDirect(sh, (SQLCHAR *)stmt, SQL_NTS) != SQL_SUCCESS)return ErrHandler();

...

/* print all rows in table */while (SQLFetch(sh) == SQL_SUCCESS) {...if (cancelled_by_user)SQLCancel(sh); /* or, SQLFreeStmt(sh, SQL_CLOSE); */

...}

Page 167: RDMs 8.4 SQL User's Guide

13. Using SQL in a C Application Program

SQL User Guide 159

return SQL_SUCCESS;}

13.4.5 Retrieving Date/Time Values

Your application can access and manipulate RDM Server SQL-specific date and time values at the runtime (d_) level with theVAL functions found in the RDM Server SQL API. These functions enable you to translate a date or time value from its nat-ive packed format (types *_VAL) into the ODBC format (types *_STRUCT), or vice versa.

To use the date or time manipulation functions, your application must include the sqlrds.h file. Error codes that can bereturned by these functions are defined in the valerrs.h header file, which will be automatically included when youinclude sqlrds.h. These files are found in the RDM Server include directory.

Caution: Records changed via the RDM Server runtime API ignore SQL constraint checking. Therefore, if your applicationdefines column constraints, it must validate the values before writing to the database.

Note that the *_VAL data types (DATE_VAL, TIME_VAL, etc.) are always associated with internal storage format, while the*_STRUCT data types (DATE_STRUCT, etc.) are standard ODBC types. Both types are defined in RDM Server ReferenceManual. Note also that the structure definitions are not currently produced by the ddlproc schema compiler; your applic-ation must declare them.

13.4.6 Retrieving Decimal Values

RDM Server SQL provides support for the ODBC date, time, and timestamp data types. Database columns of those types canbe returned in struct variables of type DATE_STRUCT, TIME_STRUCT, or TIMESTAMP_STRUCT. These structure typesare declared in sqlext.h as shown below.

typedef struct tagDATE_STRUCT {SQLSMALLINT year; /* year (>= 1 A.D., for example, 1993) */SQLUSMALLINT month; /* month number: 1 to 12 */SQLUSMALLINT day; /* day of month: 1 to 31 */

} DATE_STRUCT;

typedef struct tagTIME_STRUCT {SQLUSMALLINT hour; /* hour of day: 0 to 23 */SQLUSMALLINT minute; /* minute of hour: 0 to 59 */SQLUSMALLINT second; /* second of minute: 0 to 59 */

} TIME_STRUCT;

typedef struct tagTIMESTAMP_STRUCT {SQLSMALLINT year; /* year (>= 1 A.D., for example, 1993) */SQLUSMALLINT month; /* month number: 1 to 12 */SQLUSMALLINT day; /* day of month: 1 to 31 */;SQLUSMALLINT hour; /* hour of day: 0 to 23 */SQLUSMALLINT minute; /* minute of hour: 0 to 59 */SQLUSMALLINT second; /* second of minute: 0 to 59 */SQLUINTEGER fraction; /* billionths of a second: 0 to 999,900,000

} TIMESTAMP_STRUCT; (RDM Server SQL accurate to 4 places only) */

typedef DATE_STRUCT SQL_DATE_STRUCT;typedef TIME_STRUCT SQL_TIME_STRUCT;typedef TIMESTAMP_STRUCT SQL_TIMESTAMP_STRUCT;

The DATE_STRUCT name has been changed in ODBC 3 to SQL_DATE_STRUCT but either can be used.

Page 168: RDMs 8.4 SQL User's Guide

13. Using SQL in a C Application Program

SQL User Guide 160

Use of date and time data is shown in the example below, which prints the year-to-date sales orders for a particular customer.In this example, SQLBindCol is called to request the column values in their native data type.

13.4.7 Retrieving Decimal Data

The RDM Server SQL support module stores decimal values in a proprietary BCD format. RDM Server provides a library offunctions (BCD- prefix) your application can call to manipulate values stored in this format. The functions allow the applic-ation to convert between a string representation of a decimal value (for example, "123.4567") and the internal RDM ServerBCD format, as well as to perform all of the usual decimal arithmetic.

To call a decimal manipulation function, your application must first allocate a BCD environment handle, specifying the max-imum precision and scale for the values you will manipulate, as shown in the code example below. The application passesthis handle to any of the decimal manipulation functions that it calls.

The application can set any BCD value needed. However, if the application must store BCD values directly in the database,the maximum precision and scale you use must be identical to that specified by the RDM Server SQL support module. Thefollowing code fragment shows how your application can determine from the syscat (system catalog) database what the sys-tem values are for these parameters.

...

int16 maxprecision, maxscale, bcd_len;char *bcd_buf;BCD_HENV hBcd;

/* determine the max precision and scale on the server */SQLExecDirect(hStmt, "select maxprecision, maxscale from sysparms", SQL_NTS);SQLBindCol(hStmt, 1, SQL_SMALLINT, &maxprecision, sizeof(int16), NULL);SQLBindCol(hStmt, 2, SQL_SMALLINT, &maxscale, sizeof(int16), NULL);SQLFetch(hStmt);SQLFreeStmt(hStmt, SQL_CLOSE);printf("max precision = %hd, max scale = %hd\n", maxprecision, maxscale);

/* allocate a BCD environment corresponding to configuration on server */BCDAllocEnv((unsigned char)maxprecision, (unsigned char)maxscale, &hBCD);

/* allocate a buffer to contain the decimal string */bcd_len = maxprecision+3; /* sign, decimal, and NULL byte */bcd_buf = malloc(bcd_len);

...

13.4.8 Status and Error Handling

RDM Server returns to your RDM Server SQL application the codes and messages described in the Return Codes and ErrorMessages section. RDM Server SQL API return code constants are defined in sql.h; these return codes are prefixed by"SQL_".

Each RDM Server SQL API function returns a code indicating the success or failure of the operation. If an error occurs, yourapplication must call SQLGetDiagField or SQLGetDiagRec for details about the error.

The RDM Server SQL API provides a nonstandard function called SQLSetErrorFcn that your application can call to spe-cify its own error handler. The prototype is shown below. SQLSetErrorFcn can be called any number of times to specifythe same or different handlers for different handles or error codes.

Page 169: RDMs 8.4 SQL User's Guide

13. Using SQL in a C Application Program

SQL User Guide 161

Your application error handler is called by an RDM Server SQL API function that has produced an error. The following is theprototype for the application error handler function (sqlrds.h file).

int32 REXTERNAL ErrorHandler(int16 handleType, SQLHANDLE handle, int32 code)

where:

handleType (input) Specifies the type of the input handle.handle (input) Specifies the input handle.code (input) Specifies the status/error code.

The calling RDM Server SQL function passes into the ErrorHandler the appropriate handle type and handle for the givenerror. For example, if SQLExecute detects an error, it will pass into ErrorHandler SQL_HANDLE_STMT as the handle typeand the statement handle associated with the error. The RDM Server SQL API function also provides the status or error codeto the ErrorHandler function; the error handler then can call SQLGetDiagField or SQLGetDiagRec to retrieve detailedinformation about the status or error. The return from ErrorHandler becomes the status code returned by the originally calledRDM Server SQL API function. Normally, the return value is simply equal to the value of the code parameter.

The following example shows the simplest use of the SQLSetErrorFcn and ErrorHandler functions. The call to SQLSetEr-rorFcn passes a valid connection handle and SQL_ERROR as the error code. This causes the automatic calling of the errorhandler by RDM Server SQL API functions for all errors associated with the specified connection, including functions that ref-erence statement handles allocated from the connection.

#include <stdio.h>#include "sqlrds.h" /* SQLSetErrorFcn is a Birdstep extension */

int32 REXTERNAL ErrHandler(int16 hType,SQLHANDLE handle,int32 code)

{SQLUINTEGER rsqlcode;SQLCHAR buf[80], sqlstate[6];

SQLGetDiagRec(hType, handle, 1, sqlstate, &rsqlcode, buf, 80, NULL);printf("****RSQL Error %ld: %s\n", rsqlcode, buf);

return code;}

...SQLSetErrorFcn(SQL_HANDLE_DBC, hdbc, SQL_ERROR, ErrHandler);...

The next example shows how your application can specify separate error handlers for different errors. The first call toSQLSetErrorFcn registers the standard error handler (ErrHandler) from the previous example. The second call toSQLSetErrorFcn registers a handler for a specific error code, errINVCONVERT.

When an error occurs, the RDM Server SQL support module checks to see if there is a handler registered for the associatederror code and statement handle. If not, it then checks for a handler for the code and the connection handle. Then, if a handleris still not found, the support module checks for a handler for the return code (for example, SQL_ERROR) corresponding tothe statement handle or connection handle.

Page 170: RDMs 8.4 SQL User's Guide

13. Using SQL in a C Application Program

SQL User Guide 162

#include <stdio.h>#include "sqlrds.h" /* SQLWhenever is a Birdstep extension */

/* ================================================================Invalid data type conversion*/int32 REXTERNAL BadConvert(int16 hType,SQLHANDLE handle,int32 code)

{/* My error message is better */printf("**** A type conversion specified in SQLBindCol ");printf("or SQLBindParameter call is not valid.\n");

return code;}

.../* Register standard error handler */SQLSetErrorFcn(hType, handle, SQL_ERROR, ErrHandler);

/* Register invalid conversion handler */SQLSetErrorFcn(hType, handle, errINVCONVERT, BadConvert);...

In this example, an errINVCONVERT error on any statement handle associated with the server connection handle (hdbc) willresult in a call to BadConvert. For any other error on that connection, the ErrHandler function is called.

Caution: This particular case was created only to illustrate the use of separate error handlers. You shouldnot define a separate handler for each error code to output a more readable error message. It is far moreefficient to use a table in a single error handler.

13.4.9 Select Statement Processing

The application associates select with a statement handle allocated by a call to SQLAllocHandle. After allocating the state-ment handle, the application calls SQLPrepare to compile (but not to execute) the statement. When compilation is suc-cessful, the application can call SQLDescribeCol and SQLNumResultCols to get information about the result columns,such as the column name and data type. This information can be used in the call to SQLBindCol to set up host variables tohold the column values for each result row.

Before statement execution, your application can call the SQLBindParameter function to associate the host program vari-ables with parameter markers. These markers are placeholders for constant values in the SQL statement, and are specified witha question mark (?). The application then calls SQLExecute to run the select statement. During execution, the values fromthe host program variables are substituted for the parameter markers. The following example illustrates how parameter markersare used.

select company, ord_num, ord_date, amount from customer, sales_orderwhere customer.cust_id = sales_order.cust_id

and ord_date = ?;

If you need to execute the same select statement multiple times without having to recompile it, use separatecalls to SQLPrepare and SQLExecute.

Page 171: RDMs 8.4 SQL User's Guide

13. Using SQL in a C Application Program

SQL User Guide 163

When statement execution is complete, your RDM Server SQL application calls SQLFetch to retrieve the rows of the resultset, one at a time. When all rows have been fetched, SQLFreeHandle is called to free the select statement handle and dropit so it can no longer be used. Alternatively, the application can call SQLCancel to close the handle, terminating statementexecution but still allowing the statement to be re-executed at a later time. It also can call SQLFetchScroll to retrieve mul-tiple rows in a single call.

The following example illustrates the processing of a select statement summarizing the year-to-date total sales for each sales-person in the sales database. The SalesSummary function is called with an open connection handle to the server containingthe database. This function allocates its own statement handle and calls SQLExecDirect to compile and execute the selectstatement.

Calls to SQLBindCol bind the two column results to character buffers sale_name and amount. These function calls pass thebuffer size, as well as SQL_C_CHAR, indicating the result is to be converted to a character string. Note that a buffer size ofSQL_NTS is invalid for these calls, since the buffers are for output only. The last parameter passed to SQLBindCol is theaddress of an integer (SQLLEN) variable to contain the output result length. For both calls in this example this parameter isNULL, indicating that the application does not need the result length.

This example retrieves each row of the result set by calling SQLFetch. Each call retrieves the next row and stores thecolumn results in the program locations specified in the SQLBindCol calls.

#include "sql.h"

char stmt[] ="select sale_name, sum(amount) from salesperson, customer, sales_order ""where salesperson.sale_id = customer.sale_id ""and customer.cust_id = sales_order.cust_id ""group by sale_name";

SQLRETURN SalesSummary(SQLHDBC hdbc) /* connection handle to sales database server */

{char sale_name[31]; /* salesperson name */char amount[20]; /* formatted sales order amount */SQLHSTMT sh; /* statement handle */SQLRETURN stat; /* SQL status code */

if ((stat = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &sh)) != SQL_SUCCESSS)return(stat);

if ((stat = SQLExecDirect(sh, stmt, SQL_NTS)) == SQL_SUCCESS) {SQLBindCol(sh, 1, SQL_C_CHAR, sale_name, 31, NULL);SQLBindCol(sh, 2, SQL_C_CHAR, amount, 20, NULL);while ((stat = SQLFetch(sh)) == SQL_SUCCESS)

printf("Acct manager %s has a total of $%s in orders\n", sale_name, amount);}return stat;

}

In the next example, the PrintTable function outputs all columns and rows contained in the specified table. Unlike the priorexample, which has a fixed number of columns in the result set, this example can have a varying number of result columns.Thus the code calls SQLNumResultCols to get the number of columns in the result set. An array of column resultdescriptors (cols) is allocated to contain the definition and result information for each column. Function SQLDescribeColis called to retrieve the name, type, and display size for each result set column. The result value buffer is dynamically alloc-ated and bound to its result column through the call to SQLBindCol.

Page 172: RDMs 8.4 SQL User's Guide

13. Using SQL in a C Application Program

SQL User Guide 164

As each row is retrieved by SQLFetch, each column value and its result length are stored in the COL_RESULT container forthat column. The length for a null column value is returned as SQL_NULL_DATA. In this case (see the following example),the program displays NULL.

#include "sql.h"

/* result container */typedef static struct {

SQLCHAR name[33]; /* column name */void *value; /* column value */SQLSMALLINT type; /* column type */SQLLEN len; /* result value length */

} COL_RESULT;

/* Print all rows of a table */SQLRETURN PrintTable(

SQLHDBC svr, /* server connection handle */char *tabname) /* name of table whose rows are to be printed */

{char stmt[80];SQLHSTMT sh;SQLSMALLINT tot_cols;COL_RESULT *cols;SQLUINTEGER size;int32 row;

/* set up and compile select statement */sprintf(stmt, "select * from %s", tabname);SQLAllocHandle(SQL_HANDLE_STMT, svr, &sh);if (SQLExecDirect(sh, stmt, SQL_NTS) != SQL_SUCCESS)

return ErrHandler();

/* allocate column results container */SQLNumResultCols(sh, &tot_cols);cols = (COL_RESULT *)calloc(tot_cols, sizeof(COL_RESULT));

/* fetch column names and bind column results */for (i = 0; i < tot_cols; ++i) {

SQLDescribeCol(sh, i+1, cols[i].name, 33, NULL, &cols[i].type, &size, NULL, NULL);cols[i].value = malloc(size+1);SQLBindCol(sh, i+1, SQL_C_CHAR, cols[i].value, size+1, &cols[i].len);

}

/* print all rows in record-oriented format */printf("========== %s ==========", stmt);for (row = 1; SQLFetch(sh) == SQL_SUCCESS; ++row ) {

printf("**** row %ld:\n", row);for (i = 0; i < tot_cols; ++i) {

printf(" %32.32s: %s\n", cols[i].name,cols[i].len == SQL_NULL_DATA "NULL" : cols[i].value);

}}

/* drop statement handle and free allocated memory */SQLFreeHandle(SQL_HANDLE_STMT, sh);for (i = 0; i < tot_cols; ++i)

free(cols[i].value);free((void *)cols);return SQL_SUCCESS;

}

Page 173: RDMs 8.4 SQL User's Guide

13. Using SQL in a C Application Program

SQL User Guide 165

The next example presents an application function that invokes the myproc stored procedure mentioned previously. After com-piling and executing the initial statement, the application calls SQLNumResultCols to determine if that statement was aselect statement. If there are result columns, the application can call SQLDescribeCol and SQLBindCol to set up pro-cessing of the result set. Then the program calls SQLFetch until it returns SQL_NO_DATA.

If there are no result columns, the initial statement was either insert, update, or delete. The application can callSQLRowCount to count the number of rows affected by the modification statement. After processing the result, the applic-ation calls the SQLMoreResults function to determine if there are any more stored procedure statements to be processedand, if so, to execute the next one.

A stored procedure containing more than one select statement requires that the application call theSQLMoreResults function after SQLFetch returns SQL_NO_DATA. This call determines if any moreresult sets exist and initializes their processing. SQLMoreResults can be called repeatedly to executeeach subsequent statement in the procedure. If the statement is a select statement, SQLFetch can then becalled repeatedly to fetch the latest result set. When there are no more statements in the procedure,SQLMoreResults returns SQL_NO_DATA.

#include "sqlext.h"

typedef struct col_result {char name[33];char *value;SQLUINTEGER prec;

} COL_RESULT;

void RunProc(SQLHSTMT hstmt){

SQLSMALLINT nocols, col;SQLLEN norows;

stat = SQLExecDirect(hstmt, (SQLCHAR *)"execute myproc()", SQL_NTS);while (stat == SQL_SUCCESS) {

SQLNumResultCols(hstmt, &nocols);

if (nocols > 0) {/* set up and fetch result set */results = (COL_RESULT *)calloc(nocols, sizeof(COL_RESULT *));for (col = 0; col < nocols; ++col) {

COL_RESULT *rp = &results[col];SQLDescribeCol(hstmt, col+1, rp->name, 33, NULL, NULL, &rp->prec, NULL, NULL);rp->value = malloc(rp->prec+1); SQLBindCol(hstmt, col+1, SQL_C_CHAR,

rp->value, rp->prec+1, NULL); }while (SQLFetch(hstmt) != SQL_NO_DATA)

DisplayResultRow(results, nocols);

/* free results memory */for (col = 0; col < nocols; ++col)

free(results[col].value);

free(results);}else {

/* report number of rows affected */SQLRowCount(hstmt, &norows);if (norows > 0)

printf("*** %ld number of rows affected\n");}

Page 174: RDMs 8.4 SQL User's Guide

13. Using SQL in a C Application Program

SQL User Guide 166

stat = SQLMoreResults(hstmt);}

}

13.4.10 Positioned Update and Delete

A cursor is a named, updateable select statement where the cursor position is the current row (that is, the row returned fromthe most recent call to SQLFetch). An updateable select statement does not include a group by or order by clause and onlyrefers to a single table in the from clause. If the table is a view, that view must be updateable. Cursors are used in con-junction with positioned updates and deletes to allow the current row from a select statement to be updated or deleted.

The general procedure for a positioned update (or delete) is as follows:

1. Call SQLAllocHandle to allocate the statement handle for the select statement.2. Call SQLAllocHandle to allocate a statement handle for the update statement.3. Call SQLPrepare with the first statement handle to compile the select statement.4. To specify your own cursor name, call SQLSetCursorName using the select statement handle. If necessary, this func-

tion can be called before step 3. To use a system-generated cursor name, skip this step.5. Using the select statement handle, call SQLBindCol and SQLBindParameter as often as necessary and then call

SQLExecute.6. If the cursor name is system-generated, call SQLGetCursorName to copy the cursor name into the update statement

(where current of clause).7. Call SQLPrepare to compile the update statement.8. Call SQLFetch repeatedly with the select statement handle until a row to be modified is retrieved.9. To perform the update, assign the values to the desired parameters and call SQLExecute using the update statement

handle. Repeat steps 8 and 9 until finished.10. Call SQLEndTran to commit the changes.11. Free the statement handles by calling SQLFreeHandle.

The following example illustrates positioned update processing. The RaiseComm function fetches and displays each row ofthe salesperson table so that the user (for example, the sales manager) can raise a salesperson's commission rate by 1 percent.RaiseComm uses SQLSetCursorName to give the select statement the cursor name "comm_raise". Note that if the statementassociated with the cursor is not an updateable select statement, SQLSetCursorName returns an error code.

#include "sql.h"

static char SaleSel[] ="select sale_id, sale_name, commission, mgr_id from salesperson";

static char SaleUpd[] ="update salesperson set commission=commission+0.01 ""where current of comm_raise";

/* Raise commission for selected salespersons */SQLRETURN RaiseComm(HDBC srv){

char sale_id[4], mgr_id[4], sale_name[31];float comm;SQLLEN mgrIdInd;char sqlstate[6], errmsg[80];

Page 175: RDMs 8.4 SQL User's Guide

13. Using SQL in a C Application Program

SQL User Guide 167

SQLHSTMT sHdl, uHdl;SQLRETURN stat;

/* step 1: allocate select statement handle */if ((stat = SQLAllocHandle(SQL_HANDLE_STMT, svr, &sHdl)) != SQL_SUCCESS)

return(stat); /* this will catch connection handle problems */

/* step 2: allocate update statement handle */SQLAllocHandle(SQL_HANDLE_STMT, svr, &uHdl);

/* step 3: compile the select statement */SQLPrepare(sHdl, SaleSel, SQL_NTS);

/* step 4: specify cursor name */SQLSetCursorName(sHdl, "comm_raise", SQL_NTS);

/* step 5: bind select stmt columns and execute select statement */SQLBindCol(sHdl, 1, SQL_C_DEFAULT, sale_id, 4, NULL);SQLBindCol(sHdl, 2, SQL_C_DEFAULT, sale_name, 31, NULL);SQLBindCol(sHdl, 3, SQL_C_DEFAULT, &comm, sizeof(float), NULL);

/* mgrIdInd will be SQL_NULL_DATA for managers */SQLBindCol(sHdl, 4, SQL_C_DEFAULT, mgr_id, 4, &mgrIdInd);

if ((stat = SQLExecute(sHdl)) == SQL_SUCCESS) {/* step 6: compile positioned update statement */SQLPrepare(uHdl, SaleUpd, SQL_NTS);

/* step 7: fetch each row and display, allowing user to updateif desired */while ((stat = SQLFetch(sHdl)) == SQL_SUCCESS) {

if (mgrIdInd != SQL_NULL_DATA &&DisplaySalesperson(sale_id, sale_name, comm, csize) == UPDATED) {

/* step 8: this salesperson gets the raise */if ((stat = SQLExecute(uHdl)) != SQL_SUCCESS)

break;}

}}

if (stat == SQL_ERROR) {SQLGetDiagRec(SQL_HANDLE_DBC, svr, 1, sqlstate, NULL, emsg, 80, NULL)

printf("***ERROR(%s): %s\n", sqlstate, errmsg);SQLEndTran(SQL_HANDLE_DBC, svr, SQL_ROLLBACK);

}else {

/* step 9: commit the changes */SQLEndTran(SQL_HANDLE_DBC, svr, SQL_COMMIT);

}

/* step 10: drop the statement handles */SQLFreeHandle(SQL_HANDLE_STMT, sHdl);SQLFreeHandle(SQL_HANDLE_STMT, uHdl);return stat;

}

Like a positioned update, a positioned delete can be used to delete the current row of a specified cursor. Execution of a posi-tioned delete is identical to a positioned update except that no columns are updated and the row is simply deleted. A posi-tioned delete must first define a select statement cursor by calling SQLGetCursorName or SQLSetCursorName. Then theapplication issues a delete statement using the where clause with a current of qualifier to specify the cursor. In this case, the

Page 176: RDMs 8.4 SQL User's Guide

13. Using SQL in a C Application Program

SQL User Guide 168

delete statement removes only the row indicated by the cursor. The following statement deletes the salesperson indicated bythe cursor named "comm_raise", as described in Processing a Positioned Update.

delete salesperson where current of comm_raise;

13.5 Using Cursors and Bookmarks

13.5.1 Using Cursors

In ODBC, a user fetches data from a database by executing an SQL query (through SQLExecDirect or SQLExecute). Theserver determines a result set of rows that match the requested query, and creates a cursor that points to a row in this result set.The user then fetches the data by calling SQLFetch or SQLFetchScroll.

Rowset

If the user calls SQLFetch, RDM Server returns the data for one row; if the user calls SQLFetchScroll, RDM Serverreturns a group of rows (a rowset), starting with the row pointed to by the cursor. The number of rows in a rowset is determ-ined by the rowset size setting, set with the SQLSetStmtAttr function, and the SQL_ROWSET_SIZE option. (The defaultis 1.) The user can fetch additional rowsets by calling these fetch functions again.

Types of Cursors

The five types of cursors available in ODBC can be divided into two categories:

l Non-scrollable cursors allow the user to fetch only the next rowset in the result set. When the end of the result set isreached, the fetch function returns SQL_NO_DATA_FOUND. There is one kind of non-scrollable cursor, the forward-only cursor.

l Scrollable cursors give users the choice of which rowset to fetch (for example, the next rowset, the previous rowset, ora rowset starting at an absolute row number). Scrollable cursor types include static, dynamic, keyset-driven, and mixedcursors.

RDM Server supports one kind of scrollable cursor, the static cursor.

13.5.2 Static Cursors

This cursor is called static because the result set's membership is determined when the query is executed and does not changefor the life of the query. Therefore, if another user changes the data in a row that the user has fetched, this change is unseenby the first user until that user re-executes the query. In essence, a snapshot of the result set is taken when the query isexecuted and that snapshot does not change until the query is re-executed.

RDM Server caches result set data on the client side. When the data is requested through SQLFetchScroll, RDM Serverfetches as many rows from the server as necessary to meet the request. Thus, if the user requests the first rowset, RDM Serveronly fetches that rowset. But, if the user requests the last rowset, RDM Server must fetch all intervening rows from the serverinto the client side cache before it can fetch the requested rowset. If the result set is large, this could take several minutes.However, once the data is on the client side, any request for a rowset is met quickly. When the cursor is freed (by callingSQLCloseCursor), the client side cache is cleared.

Page 177: RDMs 8.4 SQL User's Guide

13. Using SQL in a C Application Program

SQL User Guide 169

Using Static Cursors

By default, all cursors are forward-only. To implement static cursors the user must, before executing the query, callSQLSetStmtAttr with the SQL_ATTR_CURSOR_TYPE option set to SQL_CURSOR_STATIC. If the user employsSQLFetch to retrieve data, the cursor is still restricted to forward-only movement; furthermore, a user cannot mixSQLFetchScroll and SQLFetch on a given cursor.

However, if a user employs SQLExtendedFetch, the user can fetch any rowset from the result set in any order. (Forinstance, the user can fetch the last rowset in the result set or the rowset starting with the fiftieth row.) Once a rowset isfetched, the user can call SQLSetPos (for static cursors only) to position the cursor at a particular row within the rowset. Therow's data can then be retrieved into variables using the SQLGetData function. Alternatively, the data can be retrieved bybinding columns to arrays of variables, just as with the forward-only cursor.

Limitations on Static Cursors

As explained in "Static Cursors" above, a static cursor cannot reflect changes to database data made after a query has beenexecuted. Static cursors additionally have the following limitations:

Changing the default display string for a data type affects what the server can retrieve. If you bind a column to SQL_C_CHAR that has a default display type (as set by the SQL statement set type display), the cursor caches it as a string using theformat string. This means that you cannot subsequently rebind that column as a non-character type and fetch data until thequery is re-executed. Nor can you call SQLGetData to fetch the data in its native form, since this function also retrieves theinformation from the client side cache.

Similarly, if you bind a column that has a specified display format as a non-character type, you cannot rebind it (or useSQLGetData) as a character type during the life of the cursor. This is because the default format information for the type isstored on the server, while the fetched data might be coming from the client side cache, which has no access to this inform-ation. Therefore, RDM Server returns the information as specified when the cursor was first opened (i.e., on the first SQLEx-tendedFetch call). Note that there is no binding/SQLGetData limitation of this type if no default format string wasspecified for the data type.

It is possible in RDM Server to change, between fetches, the default format string for a data type from a result set. RDMServer, however, freezes the format string (if any) at cursor creation time (i.e., during the first SQLExtendedFetch), so thequery must be re-executed to reflect the change. The new format will not be used for the data type until the query is re-executed. This rule also applies if a format string is created for a data type that did not have one at query execution time.

The following example demonstrates this limitation. Suppose the user employs static cursors and calls the following statementon the current connection:

set real display(10, "$#,#.##");

Any columns of data type real that are bound to SQL_C_CHAR variables will be returned using the specified format string.Suppose the user executes the query and calls SQLExtendedFetch, having bound the only column of type SQL_REAL inthe query to char. The resulting data will be returned in the dollar-sign format specified.

However, if the user tries to call SQLGetData on the field as follows, an error results:

SQLGetData(hstmt, 1, SQL_C_FLOAT, &fval, 0, NULL);

Because of this limitation, the user cannot convert the SQL_REAL column to a SQL_C_FLOAT column for the life of thestatic cursor.

Page 178: RDMs 8.4 SQL User's Guide

13. Using SQL in a C Application Program

SQL User Guide 170

Suppose the user re-executes the query, binds the column as SQL_C_FLOAT, calls SQLExtendedFetch, and tries to rebindthe column as SQL_C_CHAR. The user will get another error, because now the column is returned as SQL_C_FLOAT andthe client side cache does not have access to the previously specified dollar sign display format.

BLOB fields are handled differently in static cursor mode, because fetching huge BLOB fields into the client side cache inhib-its performance. (Note that this handling method does not meet ODBC specifications.) At cursor creation time (i.e., during thefirst SQLExtendedFetch), RDM Server only fetches BLOBs that have been bound (using SQLBindCol). Further, it onlyfetches up to the number of BLOB bytes necessary to fill the requested bound buffer.

Thus, if the user binds a BLOB column to a 50-byte field, a maximum of 50 bytes of that particular BLOB will be returnedwhen SQLExtendedFetch is called. The user then cannot fetch more than that 50 bytes, because the data is not availableon the client side. The user cannot retrieve the data from the server because a static cursor's data is set when the cursor is cre-ated. (The BLOB's data might have changed since the cursor was created with the SQLExtendedFetch call.)

To retrieve more data, the user must re-execute the query, binding to a larger buffer on re-execution. Note that an increasedbound buffer size could affect performance, because more data might be sent over from the server during the fetch. Also, if theuser has not bound the BLOB column before the first call to SQLExtendedFetch, no BLOB data is available for that BLOBfor the life of the cursor, unless the user has first used the SQL_FETCH_MAXBLOB option. (For details, see the descriptionof the SQLSetStmtAttr.)

13.5.3 Using Bookmarks

RDM Server supports the ODBC concept of bookmarks, which allows you to mark a row and then return to that specific rowlater. Bookmarks are identifiers for a particular row that can be used to re-fetch a given row, provided the statement has beenfetched using static cursors and the SQLExtendedFetch function. Bookmarks are stored in integer buffers.

Activate a Bookmark

To use bookmarks (which are turned off by default), you must activate them on the statement handle with the SQLSetSt-mtAttr function. Use the SQL_USE_BOOKMARKS option with the SQL_UB_ON setting.

Once bookmarks are activated, execute a query, then fetch a rowset using SQLExtendedFetch (bookmarks do not workwith SQLFetch). To set a current row within the rowset, call SQLSetPos. (By default, the first row in the rowset is the cur-rent row.)

Turn Off a Bookmark

To turn off bookmarks, use the SQLSetStmtAttr function and the SQL_ATTR_USE_BOOKMARKS option, the same asfor activating, but use the SQL_UB_OFF setting instead. This option only works if the statement has previously been set upto use static cursors.

Retrieve a Bookmark

To retrieve the bookmark for a row, call SQLGetStmtAttr and specify the SQL_FETCH_BOOKMARK_PTR option. Thebookmark is saved in a four-byte integer buffer. A bookmark can also be retrieved by using SQLBindCol or SQLGetData.

Page 179: RDMs 8.4 SQL User's Guide

13. Using SQL in a C Application Program

SQL User Guide 171

Return to a Bookmark

To return to the bookmarks, call SQLFetchScroll with the fetch option set to SQL_FETCH_BOOKMARK and therowNum parameter set to the previously fetched bookmark. The returned rowset will start with the row marked by the book-mark. When the statement is freed, the bookmarks become invalid and must be re-fetched for subsequent queries.

13.5.4 Retrieving Blob Data

Like other data types, columns of the long varchar, long wvarchar, or long varbinary type can be retrieved via a selectstatement. However, BLOB types cannot be used in where clauses or in any other expressions, except as parameters to aUDF. The data can be either bound or unbound, and either SQLFetch or SQLFetchScroll can be used to retrieve the data. Thelimitations involved with these methods are described below.

You can use the SQLBindCol function to bind a BLOB column to a buffer. If the buffer is not large enough to hold theentire BLOB, the BLOB will be truncated to fit the buffer. If you have provided an output length variable to SQLBindCol,this variable will contain the full length of the BLOB before truncation. The only way to retrieve the remainder of the BLOBis to use the SQLGetData function. Therefore, use SQLBindCol to bind BLOB parameters only if you have relatively smallBLOBs or if you only care about the first portion of a BLOB. To retrieve a large number of rows in a rowset withSQLFetchScroll, you need to call SQLBindCol to bind an array of buffers of rowset size, which will require con-siderable memory if you are retrieving large BLOBs. As an option, you can allocate less memory for the buffer, which will res-ult in the BLOB data being truncated.

You also can retrieve the BLOB data using SQLGetData. This function can be used to retrieve all the BLOB data in chunksof any size. However, you must use SQLFetch to retrieve the result set one row at a time, since you cannot useSQLGetData with SQLFetchScroll. You can call SQLGetData multiple times if necessary; each time it writes into theprovided buffer the number of bytes specified in the buffer length parameter of the function. It will also write into the outputlength parameter (if the parameter is provided) the number of bytes remaining to retrieve from the BLOB before the currentcall to SQLGetData. The next time you call SQLGetData, the next chunk of the BLOB is returned into the buffer. If trun-cation has occurred, SQLGetData returns SQL_SUCCESS_WITH_INFO. When it returns the last part of the data,SQLGetData returns SQL_SUCCESS. If called after this, SQLGetData returns SQL_NO_DATA.

The following example retrieves all data in the CDAlbum table associated with a specific composer, Beethoven. First, weexecute a statement:

SQLExecDirect(hstmt,"select * from cdalbum ""where composer = 'Beethoven, Ludwig Von';", SQL_NTS);

Next, if we are going to bind the column, we call SQLBindCol:

SQLBindCol(hstmt, 6, SQL_C_CHAR, notes, sizeof(notes), NULL);SQLBindCol(hstmt, 7, SQL_C_BINARY, jacketpic, sizeof(jacketpic), NULL);

Both notes and jacketpic are character arrays; by binding the long varbinary column as SQL_C_BINARY, we eliminate theneed for a null terminator. Next, call SQLFetch or SQLFetchScroll. If we call SQLFetchScroll, the notes and jack-etpic buffers must be two dimensional arrays where the first dimension equals the rowset size. For example, if the rowset sizeis 50, the notes and jacketpic buffers might be declared as follows.

char notes[50][NOTES_SIZE];char jacketpic[50][JACKETPIC_SIZE];

Page 180: RDMs 8.4 SQL User's Guide

13. Using SQL in a C Application Program

SQL User Guide 172

After calling the fetch function, the buffer will contain up to sizeof(buffer) bytes of the BLOB in the buffer. At thispoint, if you previously called SQLFetch, you can call SQLGetData to get the remainder of the BLOB data. Note that thefirst time you call SQLGetData, it will refetch the data you already have in the buffer (the first bytes), which were bound tothat buffer when you called SQLFetch.

Alternatively, if we only use SQLGetData to retrieve the data, we call SQLFetch to fetch each row. Then callSQLGetData multiple times to retrieve all the BLOB data. This approach might look like the following:

#define JPIC_SIZE 1000#define NOTES_SIZE 100

char jpic[JPIC_SIZE], notes[NOTES_SIZE];SQLINTEGER buflen;int32 len, offset;

SQLExecDirect(---); /*as above */while (SQLFetch(hstmt) == SQL_SUCCESS) {offset = 0;do {status = SQLGetData(hstmt, 6, SQL_C_CHAR, notes, NOTES_SIZE, &buflen);if (status == SQL_SUCCESS || status == SQL_SUCCESS_WITH_INFO) {/* Copy data elsewhere, as our buffer will be overwritten

the next time we call SQLGetData.*/len = (int32)(buflen < sizeof(notes) ? buflen : sizeof(notes)-1);memcpy(somebuf+offset, notes, len);offset += len;status = SQL_SUCCESS;

}} while (status == SQL_SUCCESS);

/* Then do the same thing for the jacketpic blob. */do {status = SQLGetData(hstmt, 7, SQL_C_BINARY, jpic, JPIC_SIZE, &buflen);if (status == SQL_SUCCESS || status == SQL_SUCCESS_WITH_INFO) {/* Same idea as above, except this buffer has no

null terminator in it.*/...

}...

} while (status == SQL_SUCCESS);}

For most BLOB values, however, the usual way to insert or update BLOB data is to use parameter markers and the SQLPara-mData and SQLPutData functions to put the data into the BLOB in chunks (or all at once if you wish). To insert or updatethe data this way, first prepare a statement containing a parameter marker for the BLOB column, bind the parameter, callSQLExecute, call SQLParamData, then repeatedly call SQLPutData to put the data into the BLOB. Finally, callSQLParamData again to prepare the next BLOB for insert/update, or to complete the modifications if there are no furtherBLOBs. For example, if we have an external data file containing a copy of the album's jacket picture, we must prepare aninsert statement:

SQLPrepare (hstmt, "insert into cdalbum values(,'Eine Kleine Nachtmusik','Mozart, Wolfgang', 'Classical', ?, null);", SQL_NTS);

Page 181: RDMs 8.4 SQL User's Guide

13. Using SQL in a C Application Program

SQL User Guide 173

Before executing this statement, we first must bind a variable to the parameter marker in the BLOB field. BLOB parametersmust be bound as SQL_DATA_AT_EXEC parameters, meaning data for the parameter will be provided after statement exe-cution. In RDM Server, the SQL_LEN_DATA_AT_EXEC(length) macro indicates that the parameter is DATA_AT_EXEC.The length parameter of this macro must be non-negative (usually 0) and is ignored by RDM Server. The last parameter inSQLBindParameter is a pointer to an SQLINTEGER variable equal to the result of this macro.

Our next requirement concerns the variable we bind to the parameter; it must be a 4-byte value. This value can either be ascalar value or a pointer. For example, we might bind to the BLOB parameter a pointer to a string containing the name of thefile containing the jacket picture, as shown below.

status = SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_BINARY,SQL_LONGVARBINARY, 0, 0, picFileName, 0, &bloblen);

Here, picFileName is a pointer to the string "c:\albums\jackets\mozart733.jpg" containing the album's jacket pic-ture data and bloblen = SQL_LEN_DATA_AT_EXEC(0). With all parameters in place, we can execute the statement:

status = SQLExecute(hstmt);

The value of status after this call will not be SQL_SUCCESS, but SQL_NEED_DATA, indicating that statement execution isnot complete. Thus, our next step is adding the BLOB data to this record. First, call SQLParamData, which takes two para-meters (the second is a pointer). RDM Server will return into the pointer the value associated with the first bound DATA_AT_EXEC parameter it finds. The SQL_NEED_DATA status code is returned if any DATA_AT_EXEC parameters are found.RDM Server first searches for and returns all non-BLOB DATA_AT_EXEC parameters (if any), then returns the BLOB para-meters. Each call to SQLParamData returns the next parameter, one parameter for each call. When there are no more toreturn, SQLParamData returns SQL_SUCCESS. In our example, we only have one DATA_AT_EXEC parameter. Therefore,after we call SQLParamData, ptr will point to the path to the jacket cover picture's file string ("c:\al-bums\jackets\mozart733.jpg ") that we bound earlier with SQLBindParameter:

status = SQLParamData(hstmt, &ptr);

Next, call SQLPutData as many times as necessary to put all the data into the BLOB field. When finished, call SQLPara-mData again to move to the next DATA_AT_EXEC parameter. If there is another DATA_AT_EXEC parameter, SQLPara-mData will return SQL_NEED_DATA. Otherwise, it will return SQL_SUCCESS, indicating the insert is now complete. Inour example, we call fopen using the value in ptr set by SQLParamData, and read the data out of the file. We will send thedata in chunks of 1024 bytes. We call SQLPutData multiple times until all the data is sent, then call SQLParamDataagain:

#define BUFSIZE 1024

while ((status = SQLParamData(hstmt, &ptr)) == SQL_NEED_DATA) {if ((fn = fopen(ptr, "rb")) != NULL) {do {/* put next block of data from file */buflen = fread(buf, 1, BUFSIZE, fn);status = SQLPutData(hstmt, buf, buflen);

} while (buflen == BUFSIZE && status != SQL_ERROR);

fclose(fn);/* check here if status == SQL_ERROR or SQL_SUCCESS */if (status == SQL_ERROR) {...

Page 182: RDMs 8.4 SQL User's Guide

13. Using SQL in a C Application Program

SQL User Guide 174

}}

}

It is useful to have the pointer bound in SQLBindParameter represent something uniquely identifying the BLOB, par-ticularly if there is more than one BLOB in the record. You must insert the data into the BLOBs in the order requested byRDM Server (via SQLParamData); RDM Server returns the BLOBs in the order they are placed in the table.

Similarly, in an update statement, you cannot use the BLOB in the where clause to identify which rows to update (unlessyou have a UDF that takes BLOB parameters). It is useful to define another field in the record that will uniquely identifywhich records you want to update. In our example database, cd_id is a unique primary key that can be used. When the updateoccurs, the entire new BLOB must be inserted into the database, completely replacing the BLOB already in the database (ifany). You cannot simply append changes onto the end of a BLOB.

As mentioned earlier, you cannot directly reference columns of the long varchar, long wvarchar, and long varbinary typein the where clause of a select, update, or delete statement. You can, however, pass a BLOB column as an argument to a user-defined function (UDF).

One of the many uses for a UDF is doing fast low-level database lookups. Inside the UDF, these low-level operations can beused to manipulate BLOBs. For instance, a UDF could return the BLOB's size, or whether the BLOB is NULL. You mightwrite a "BLOB grep" function to return whether a supplied string occurs in the BLOB.

You can also pass BLOB data types into UDFs (or UDPs) as parameters. As a simple example, if we write a UDF called blob-grep, we might execute the following select statement to retrieve the names of composers whose biographies contain thestring "violin".

select composer from cdalbumwhere blobgrep(notes, "violin") = 1;

The blobgrep function itself could use runtime BLOB functions to search the current BLOB for the requested string, returning1 if the string is found, 0 if not.

Page 183: RDMs 8.4 SQL User's Guide

14. Developing SQL Server Extensions

SQL User Guide 175

14. Developing SQL Server ExtensionsSQL server extensions are application-specific, C language modules that are extension of RDM Server and are called fromRDM Server SQL. SQL server extensions include the following:

l C-based, user-defined functions (UDFs)l C-based, user-defined procedures (UDPs)l Transaction trigger functions

All these modules extend the capabilities of RDM Server SQL. Called by the RDM Server SQL system during the processingof SQL statements, these modules run in DLLs or shared libraries on the RDM Server. They are easy to develop and provide apowerful tool for development of high-performance RDM Server SQL database applications.

14.1 User-Defined Functions (UDF)

A UDF is an application-specific function used just like the RDM Server SQL scalar and aggregate functions, but developedto meet the specific needs of your SQL application. After you have completed development of your UDF you need to registerit with the RDM Server SQL system. This is done using the create function statement, as shown in the following syntax.

create_function:create [scalar | aggregate] function[s]

fcnname ["description"] [, fcnname ["description"]]...in libname on devname

A scalar UDF operates on a single row and retrieves a single value. An aggregate function performs computations on sets ofrows that result from a select statement usually specified with the group by clause. For example, the following statementsregister three user-defined aggregate functions contained in a DLL called "statpack.dll" on an RDM Server database devicenamed add_ins. The select statement calls the standard SQL aggregate function, avg, as well as the user-defined aggregatefunction, geomean.

create aggregate functiondevsq "compute sum of the squares of deviations",stddev "compute standard deviation",geomean "compute the geometric mean"in statpack on add_ins;

select state, avg(amount), geomean(amount) from customer, sales_orderwhere customer.cust_id = sales_order.cust_idgroup by state;

User-defined functions have a variety of uses, such as:

l Translating coded values into easy-to-read strings.l Performing special-purpose computations.l Adding new aggregate functionality.l Doing fast, low-level database lookups (including manipulation of BLOBs).l Implementing triggers called when tables are updated.

Page 184: RDMs 8.4 SQL User's Guide

14. Developing SQL Server Extensions

SQL User Guide 176

You will find sample UDF code in the examples/udf directory. This code includes a sample module (udf.c) with somesource code, which will be used throughout this section. Instructions for using the sample code are provided in ExecutingYour RDM Server Programs.

The sample UDF module defines the six user-defined functions listed in Table 13-1.

Function DescriptionHaveProduct Trigger.OkayToShip Trigger.subquery Takes a string containing a select statement that retrieves a

single-value result.std Computes exact standard deviation.stds Computes sampled standard deviation.udfcount Performs exactly the same operation as the RDM Server SQL

built-in count function.

Table 13-1. Functions Defined in the Sample UDF Module

These functions can be compiled using the provided udf.mak makefile. The resulting DLL is called udf.dll. After theDLL is created, connect to RDM Server SQL and enter the following create function statement. The examples following thisstatement illustrate the use of these functions.

create aggregate functionstd "actual standard deviation",stds "sampled standard deviation",udfcount "alternate count function"

scalar functionsubquery "selectable subquery function"

in udf on rdsdll;

You could show the average sales amounts and their standard deviations per salesperson using the following query.

select sale_name, avg(amount), std(amount)from salesperson, customer, sales_orderwhere salesperson.sale_id = customer.sale_id

and customer.cust_id = sales_order.cust_idgroup by sale_name;

Both the count and udfcount functions below should return identical results from the following two queries.

select sale_name, count(cust_id), udfcount(cust_id)from salesperson, customerwhere salesperson.sale_id = customer.sale_idgroup by sale_name;

select sale_name, count(distinct state), udfcount(distinct state)from salesperson, customerwhere salesperson.sale_id = customer.sale_idgroup by sale_name;

The next example uses the subquery function to return a percentage of total values in a single select statement.

Page 185: RDMs 8.4 SQL User's Guide

14. Developing SQL Server Extensions

SQL User Guide 177

select state, 100*sum(amount)/subquery("select sum(amount) from sales_order") pctfrom customer, sales_orderwhere customer.cust_id = sales_order.cust_idgroup by state;

The implementation of these UDFs are described in subsequent sections.

14.1.1 UDF Implementation

Keep the following concepts in mind when programming your UDF module.

l The module must include a load function named udfDescribeFcns which identifies all of the UDFs implementedin the module.

l Each UDF can optionally include an initialization function of type UDFINIT. If you define this function, SQL calls itwhen the UDF begins executing.

l Each UDF must include a function of type UDFCHECK that performs type checking on the UDF's arguments. UDFscan take any number of arguments of any type, and the value returned can be of any data type, except long varchar,long wvarchar, or long varbinary.

l The main UDF function, of type UDFFUNC, performs processing for the UDF.l An aggregate UDF must include a reset function of type UDFRESET that is called by SQL when the group by valuechanges in order to reset the aggregate calculations.

l The UDF can optionally include a cleanup function of type UDFCLEANUP. If defined, this function is called by SQLeach time UDF execution is completed.

l If the UDF is running on Microsoft Windows, the UDF must include LibMain.l A scalar UDF minimally must a type checking function (UDFCHECK) and the processing function (UDFFUNC) itself.l Each UDF should declare REXTERNAL in its function definition.

There are other specialized functions that can be used for implementing UDFs. The SQL UDF support functions (SYS prefix)allow UDFs to perform low-level database operations associate SQL modification commands with client application trans-actions, and use the decimal arithmetic capabilities of RDM Server SQL.

UDF implementations also can use the SQL date and time manipulation functions (VAL prefix). By connecting into SQL'sinternal arithmetic functions, these functions allow the UDFs to include mixed-mode arithmetic operations. The results ofmixed-mode arithmetic operations follow standard C-language rules.

UDF Module Header Files

Your UDF module code must include the header file named emmain.h. This header addresses platform-specific imple-mentation and also includes all other standard header files (e.g., sqlrds.h and sqlsys.h) that you will need in your UDFmodule. In order to use this header, you must precede the #include with two #define declarations. The first #definespecifies the name of the UDF module (in uppercase). The second #define identifies the module type (the emmain.h fileis used for all types of server extensions). The following code fragment shows the use of emmain.h for the sample UDF mod-ule.

/* Definitions and header to setup EM ------------ *//* (all EMs must have a code block like this) --- */

#define EM_NAME UDF /* the uppercased module name */

Page 186: RDMs 8.4 SQL User's Guide

14. Developing SQL Server Extensions

SQL User Guide 178

#define EMTYPE_UDF /* EMTYPE_EM, EMTYPE_UDF, EMTYPE_UDP, EMTYPE_IEF */

#include "emmain.h" /* must follow the above defs and OS #includes */

The header files that contain definitions used in UDF modules are listed in Table 13-2. The files can be found in theinclude directory.

Header File Descriptionemmain.h RDM Server standard extension module header. Use #include

to add it to all extension, UDF, UDP, and IEF modules. Auto-matically includes the sqlrds.h and sqlsys.h files forUDF, UDP, and IEF modules.

sqlrds.h RDM Server SQL extensions header file. Includes prototypesand data definitions for the C-language extension modulefunctions used with RDM Server SQL. Provides access to allRDM Server SQL capabilities. This file automaticallyincludes the sqlext.h file.

sqlsys.h RDM Server SQL UDF header file. Includes UDF functiontype declarations, UDF specific data type definitions, andSYS function prototypes.

Table 13-2. UDF Module Header Files

Function udfDescribeFcns

Each UDF library module must contain a function named udfDescribeFcns that has arguments declared as shown in theprototype specification below. This function is called when the first SQL statement that contains a reference to one of thefunctions in the library is compiled. The responsibility of udfDescribeFcns is to return a pointer to a function descriptiontable containing all of the entry point information. In addition, an optional module description string can be returned that willbe displayed on the RDM Server system console indicating that the UDF library module has been loaded.

/* ============================================================User function description, called when statement is prepared

*/void REXTERNAL udfDescribeFcns (

unsigned short *NumFcns, /* out: number of functions in module */PUDFLOADTABLE *UDFLoadTable, /* out: points to UDFLOADTABLE array */char **fcn_descr); /* out: optional description string */

{*NumFcns = RLEN(UdfTable);*UDFLoadTable = UdfTable;

*fcn_descr = "Sample of SQL user-defined functions";}

The UDFLoadTable is a struct array of type UDFLOADTABLE. There must be one entry defined in the array for eachUDF supported by the module. The declaration for UDFLOADTABLE is contained in the header file sqlsys.h and isshown below.

typedef struct udfloadtable {char udfName[33]; /* name of user function */

Page 187: RDMs 8.4 SQL User's Guide

14. Developing SQL Server Extensions

SQL User Guide 179

UDFFUNC udfCall; /* address of user function */UDFCHECK udfCheck; /* type checking call */UDFINIT udfInit; /* initialization for user function */UDFCLEANUP udfCleanup; /* cleanup for user function */UDFRESET udfReset; /* reset for user function */

} UDFLOADTABLE,*PUDFLOADTABLE;

Each element of the UDFLOADTABLE struct is described in the following table.

Function DescriptionudfName The name of the function. Must conform to a standard SQL

identifier. It is case-insensitive and unique (system-wide).udpCall Pointer to the call processing function.udfCheck Pointer to the argument type checking function. Assign to

NULL if there are no arguments.udfInit Pointer to pre-execution initialization function.udfCleanup Pointer to post-execution cleanup function.udfReset Pointer to function that reset the group calculation values for

an aggregate function. Assign to NULL for scalar functions.

Table 13-3. UDFLOADTABLE Struct Element Descriptions

Each UDFLOADTABLE entry must specify the name of the UDF and the address of at least two functions: the function(udfCall) that actually performs the operation, and another function (udfCheck) that is called during the compilation ofan SQL statement that uses the UDF to perform type checking. The type of the argument expression(s) is passed into the func-tion that must validate the argument type and return the result type. In addition, you can optionally specify: 1) the address ofa function (udfInit) that is called when the statement is first executed to perform any necessary initialization, and 2) theaddress of a function (udfCleanup) that is called after the execution has completed (for example, after SQLFetch returnsSQL_NO_DATA_FOUND). Aggregate functions are also required to provide the address of a function (udfReset) thatresets the accumulator variables when the grouping value changes. Unused function entries should be NULL.

The code that defines the UDFLOADTABLE and the udfDescribeFcns code for the examples given in the udf.c mod-ule is shown below.

/*----------------------------------------------------------------------Function prototypes

----------------------------------------------------------------------*/

/* user function for udfcount */UDFCHECK CntCheck;UDFFUNC CntFunc;UDFINIT CntInit;UDFCLEANUP CntCleanup;UDFRESET CntReset;

/* user function for standard deviation */UDFCHECK TypeCheck;UDFFUNC StdFunc;UDFINIT StdInit;UDFCLEANUP StdCleanup;UDFRESET StdReset;

/* user function for sample standard deviation */UDFFUNC StdsFunc;

Page 188: RDMs 8.4 SQL User's Guide

14. Developing SQL Server Extensions

SQL User Guide 180

/* user function for subquery function */UDFCHECK QueryCheck;UDFFUNC QueryFunc;UDFINIT QueryInit;UDFCLEANUP QueryCleanup;

/* user function for HaveProduct trigger */UDFCHECK InvCheck;UDFFUNC InvFunc;UDFINIT InvInit;UDFCLEANUP InvCleanup;

/* user function for OKayToShip trigger */UDFCHECK ShipCheck;UDFFUNC ShipFunc;UDFINIT ShipInit;UDFCLEANUP ShipCleanup;

/*---------------------------------------------------------------------Table of user-defined functions for this module

---------------------------------------------------------------------*/

/* table of user functions callable from within an sql expression */static UDFLOADTABLE UdfTable[] = {/*name UDFFUNC UDFCHECK UDFINIT UDFCLEANUP UDFRESET*//*---------- --------- ---------- --------- ------------ --------*/{"std", StdFunc, TypeCheck, StdInit, StdCleanup, StdReset},{"stds", StdsFunc, TypeCheck, StdInit, StdCleanup, StdReset},{"SubQuery", QueryFunc,QueryCheck,QueryInit,QueryCleanup,NULL },{"udfCount", CntFunc, CntCheck, CntInit, CntCleanup, CntReset},{"HaveProduct",InvFunc, InvCheck, InvInit, InvCleanup, NULL },{"OKayToShip", ShipFunc, ShipCheck, ShipInit, ShipCleanup, NULL }

};

/* =====================================================================User function description, called when statement is prepared

*/void REXTERNAL udfDescribeFcns(uint16 *NumFcns, /* out: number of functions in module */PUDFLOADTABLE *UDFLoadTable,/* out: points to UdfTable above */char **fcn_descr) /* out: optional description string */

{*NumFcns = RLEN(UdfTable);*UDFLoadTable = UdfTable;

*fcn_descr = "Sample of SQL user-defined functions";}

SQL Data VALUE Container Description

The VALUE data type that is passed to both the type checking and the processing function is a multi-type value containerdeclared as shown below. The type field contains the standard SQL_* data type constant (for example, SQL_INTEGER). Thevt union declares a container variable for values of each SQL data type.

Page 189: RDMs 8.4 SQL User's Guide

14. Developing SQL Server Extensions

SQL User Guide 181

typedef struct _value {int16 type; /* data type of value (SQL_*) */int16 cmpfcn; /* INTERNAL USE ONLY */union {

int8 tv; /* SQL_TINYINT | SQL_BIT */int16 sv; /* SQL_SMALLINT */int32 lv; /* SQL_INTEGER | SQL_DATE | SQL_TIME */int64 llv; /* SQL_BIGINT */float fv; /* SQL_REAL */double dv; /* SQL_FLOAT */const BCD_X *bv; /* SQL_DECIMAL/SQL_NUMERIC (unpacked) */const BCD_Z *zv; /* SQL_DECIMAL/SQL_NUMERIC (packed) */BINVAR xv; /* SQL_BINARY | SQL_VARBINARY */LONGVAR lvv; /* SQL_LONGVAR(CHAR|BINARY) */TIMESTAMP_VAL tsv; /* SQL_TIMESTAMP */const char *cv; /* SQL_CHAR || SQL_VARCHAR */const DB_WCHAR *wcv; /* SQL_WCHAR || SQL_WVARCHAR */

} vt;} VALUE;

Function udfInit

The code for udfCount will be used to explain how you would use each of the five functions. Function CntInit, shownbelow, is called to initialize processing of a udfcount reference in a specific SQL statement. Initialization functions arepassed two arguments. The first is the system handle that is used by SQL to identify and maintain the context of the execut-ing statement. The second argument is the address of a void pointer into which you may return a function context pointer thatyou allocate. The allocated buffer will be stored by SQL with the statement context associated with hSys. It can contain any-thing you want. In this example, COUNT_CTX contains the memory allocation tag and a long that will contain the currentcount value.

Although you can use the standard malloc and free memory allocation functions, we recommend that you use the RDMServer resource manager memory allocation function rm_getMemory. An SQL UDF support function calledSYSMemoryTag returns the memory allocation tag that you should use in your calls to rm_getMemory. Memory allocatedwith this tag remains active for the life of the statement that contains the call to the UDF. When the statement has terminated,memory will be automatically freed by SQL. In the rare event that the server should not have enough memory for your rm_getMemory request, SQL will gracefully abort the statement execution and return status SQL_ERROR (errSRVMEMORY)to the application.

The following example shows the CntInit initialization function for the sample aggregate UDF, udfCount. Note theCOUNT_CTX structure defining the UDF context.

/* used by udfcount */typedef struct count_cxt {

RM_MEMTAG mTag;int32 count;

} COUNT_CTX;

/* ============================================================Initialization function for CntFunc()*/int16 REXTERNAL CntInit(

HSYS hSys, /* in: system handle */void **cxtp) /* in: statement context pointer */

{

Page 190: RDMs 8.4 SQL User's Guide

14. Developing SQL Server Extensions

SQL User Guide 182

COUNT_CTX *cnt;RM_MEMTAG mTag;

SYSMemoryTag(hSys, &mTag);cnt = *cxtp = rm_getMemory(sizeof(COUNT_CTX), mTag);cnt->mTag = mTag;cnt->count = 0L;

return SQL_SUCCESS;}

Function udfCheck

The udfCheck function performs type checking on the argument expression(s) that are passed to the function. FunctionCntCheck, shown below, does this for udfCount. In this case, however, the job is quite simple in that the result is inde-pendent of the data type of the argument and always returns an integer (int32). You typically need to check both the num-ber of arguments and the type of the arguments required by the function. If either is incorrect the function will return statusSQL_ERROR, and the result will be assigned a character string value with a specific error message to be returned to the userthat submitted the erroneous call. The CntCheck function shown below ensures that only one argument expression has beenpassed. If not, the result container is used to return an error message and the function returns status SQL_ERROR indicatingthe fault.

UNREF_PARM is an RDM Server macro that references an unused function parameter to meet compilerrequirements. Note the absence of a ";" at the end of the calls to this macro.

int16 REXTERNAL CntCheck (HSYS hSys, /* in: system handle */int16 noargs, /* in: number of arguments to function */const VALUE *args, /* in: array of arguments */VALUE *result, /* out: result value */int16 *len) /* out: max length result string */

{int16 status = SQL_SUCCESS;

UNREF_PARM(hSys);UNREF_PARM(args);UNREF_PARM(len);

if (noargs != 1) {result->type = SQL_CHAR;result->vt.cv = "only 1 argument expression is allowed";status = SQL_ERROR;

}else

result->type = SQL_INTEGER;

return status;}

Type checking for the subquery UDF involves compiling the statement to ensure that it does not have any errors. If there areerrors, the result value is set to type SQL_SMALLINT and the result value is the RDM Server SQL error code returned fromSQLError (retrieved by the call to SQLGetDiagRec). When SQL_ERROR is returned from a type checking function, if theresult type is SQL_CHAR then SQL understands it to be a descriptive error message. If the result type is SQL_SMALLINT

Page 191: RDMs 8.4 SQL User's Guide

14. Developing SQL Server Extensions

SQL User Guide 183

then SQL understands it to be a specific RDM Server SQL error code (for example, errMISMATCH). In the latter case, thiswill be the error code returned to the calling program. You can see that QueryCheck utilizes both methods of UDF error com-munication.

In order to call a standard RDM Server SQL API function from a UDF, it is necessary to establish a connection handle thatcorresponds to the connection handle of the statement that is executing the subquery reference. Function SYSSessionIdreturns the RDS session identifier associated with the SQL system handle. Function SQLConnectWith is then called withthat session handle to return the proper connection handle. All of the SQL functions will be passed this connection handle,which is identified by the SQL system as the same connection as that of the invoking user.

int16 REXTERNAL QueryCheck (HSYS hSys, /* in: system handle */int16 noargs, /* in: number of arguments to function */const VALUE *args, /* in: array of arguments */VALUE *result, /* out: result value */int16 *len) /* out: max length result string */

{/* NOTE: The argument to subquery MUST be a string literal in orderfor this to work.*/SQLHENV hEnv; /* environment handle for SQL calls */SQLHDBC hDbc; /* connection handle for SQL calls */RDM_SESS hSess; /* RDM session id */SQLHSTMT hStmt;SQLRETURN ret;SQLSMALLINT colcount, parms;int16 status = SQL_SUCCESS;

UNREF_PARM(noargs);UNREF_PARM(len);

SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &hEnv);SQLAllocHandle(SQL_HANDLE_DBC, hEnv, &hDbc);

SYSSessionId(hSys, &hSess);SQLConnectWith(hDbc, hSess);

SQLAllocHandle(SQL_HANDLE_STMT, hDbc, &hStmt);

if ((ret = SQLPrepare(hStmt, (SQLCHAR *)args[0].vt.cv, SQL_NTS)) != SQL_SUCCESS ) {result->type = SQL_SMALLINT;SQLGetDiagRec(SQL_HANDLE_STMT, 1, &result->vt.lv, NULL, 0, NULL);status = SQL_ERROR;

}else {

SQLNumResultCols(hStmt, &colcount);if (colcount > 1) {

result->type = SQL_CHAR;result->vt.cv = "more than one result column";status = SQL_ERROR;

}else {

SQLNumParams(hStmt, &parms);if (parms) {

result->type = SQL_CHAR;result->vt.cv = "no argument markers allowed";status = SQL_ERROR;

}else

Page 192: RDMs 8.4 SQL User's Guide

14. Developing SQL Server Extensions

SQL User Guide 184

SQLDescribeCol(hStmt, 1, NULL, 0, NULL, &result->type, NULL, NULL, NULL);}

}

SQLFreeHandle(SQL_HANDLE_STMT, hStmt);SQLDisconnect(hDbc);SQLFreeHandle(SQL_HANDLE_DBC, hDbc);SQLFreeHandle(SQL_HANDLE_ENV, hEnv);

return status;}

Function udfFunc

The UDF processing function is called by SQL from the udfFunc entry in UDFLOADTABLE during execution of the SQLstatement that references the UDF. It is called once for each row that is retrieved by the statement. The function result isreturned in the VALUE container pointed to by argument result.

The following example illustrates the aggregate UDF processing function, CntFunc, defined for the udfCount. Note that theresult value returns the current count increment for each row processed, even though only the aggregate value is used. Aggreg-ate calculations require a running calculation retrieval from every processing function. This is because you have no way ofknowing from within the UDF when RDM Server will call the function for the last time. The result SQL type and value (inthis case, the type is SQL_INTEGER and the value is the current count) are return in the result output argument and SQL_SUCCESS is returned.

int16 REXTERNAL CntFunc (HSYS hSys, /* in: system handle */void **cxtp, /* in: statement context pointer */int16 noargs, /* in: number of arguments to function */const VALUE *args, /* in: array of arguments */VALUE *result) /* out: result value */

{COUNT_CTX *cnt = *cxtp;

UNREF_PARM(hSys);UNREF_PARM(noargs);

result->type = SQL_INTEGER;if (args[0].type != SQL_NULL)

result->vt.lv = ++cnt->count;else

result->vt.lv = cnt->count;

return SQL_SUCCESS;}

The processing function for the subquery UDF is shown below.Even though QueryCheck (see above) compiled the specifiedselect statement, QueryFunc needs to compile it as well because the statement containing the subquery reference may be con-tained in a precompile stored procedure.Therefore QueryFunc is being called in a (much) different context than whenQueryCheck was called. The NULL context pointer is the signal to both allocate the context and compile, execute and fetchthe subquery result. Notice that all of the work occurs on the first call to QueryFunc. All subsequent calls simply return thesubquery's result value.

Page 193: RDMs 8.4 SQL User's Guide

14. Developing SQL Server Extensions

SQL User Guide 185

int16 REXTERNAL QueryFunc (HSYS hSys, /* in: system handle */void **cxtp, /* in: statement context pointer */int16 noargs, /* in: number of arguments to function */const VALUE *args, /* in: array of arguments */VALUE *result) /* out: result value */

{SUBQ_CTX *sqp = *cxtp; /* local context */SQLHENV hEnv; /* environment handle for SQL calls */SQLHDBC hDbc; /* connection handle for SQL calls */RDM_SESS hSess; /* RDM session id */SQLHSTMT hStmt;SQLUINTEGER prec;SQLPOINTER ptr;RM_MEMTAG mTag;int16 status = SQL_SUCCESS;

UNREF_PARM(noargs);if (sqp == NULL) {

SYSMemoryTag(hSys, &mTag);

sqp = *cxtp = rm_getMemory(sizeof(SUBQ_CTX), mTag);sqp->mTag = mTag;

SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &hEnv);SQLAllocHandle(SQL_HANDLE_DBC, hEnv, &hDbc);SYSSessionId(hSys, &hSess);SQLConnectWith(hDbc, hSess);

SQLAllocHandle(SQL_HANDLE_STMT, hDbc, &hStmt);

SQLPrepare(hStmt, (UCHAR *) args[0].vt.cv, SQL_NTS);SQLDescribeCol(hStmt,1,NULL,0,NULL, &sqp->result.type, &prec, NULL, NULL);

if (result->type == SQL_CHAR || result->type == SQL_VARCHAR)ptr = sqp->result.vt.cv = rm_getMemory(prec, mTag);

elseptr = &sqp->result.vt;

SQLBindCol(hStmt, 1, SQL_C_DEFAULT, ptr, prec, NULL);SQLExecute(hStmt);

SQLFetch(hStmt);*result = sqp->result;

if (SQLFetch(hStmt) != SQL_NO_DATA) {result->type = SQL_CHAR;result->vt.cv = "subquery() must return single row";status = SQL_ERROR;

}else

sqp->result = *result;

SQLFreeHandle(SQL_HANDLE_STMT, hStmt);SQLDisconnect(hDbc);SQLFreeHandle(SQL_HANDLE_DBC, hDbc);SQLFreeHandle(SQL_HANDLE_ENV, hEnv);

}else

*result = sqp->result;

Page 194: RDMs 8.4 SQL User's Guide

14. Developing SQL Server Extensions

SQL User Guide 186

return status;}

Function udfReset

The udfReset function is only used in an aggregate UDF to perform a reset after the grouping value changes. In the fol-lowing example, the CntReset function for the udfCount UDF clears the accumulator variables for the last aggregate, restart-ing the count for the next group to zero.

int16 REXTERNAL CntReset (HSYS hSys, /* in: system handle */void **cxtp) /* in: statement context pointer */

{COUNT_CTX *cnt = *cxtp;

UNREF_PARM(hSys);

cnt->count = 0L;

return SQL_SUCCESS;}

Function udfCleanup

Your UDF can optionally include a cleanup function in the udfCleanup entry for each UDF defined in theUDFLOADTABLE. When SQL statement processing is complete, SQL calls this function to free memory allocated by theudfInit function, or any memory allocated during statement execution. For the sample UDF, udfCount, the cleanup func-tion is called CntCleanup. As shown below, CntCleanup simply frees the context pointer.

Do not ever call rm_freeTagMemory within udfCleanup using the memory tag you acquired withSYSMemoryTag. This tag is associated with aspects of the statement's memory that RDM Server usesafter udfCleanup returns. Rather, free the memory "manually" using rm_freeMemory.

void REXTERNAL CntCleanup (HSYS hSys, /* in: system handle */void **cxtp) /* in: statement context pointer */

{COUNT_CTX *cnt = *cxtp;

UNREF_PARM(hSys);

rm_freeMemory(cnt, cnt->mTag);*cxtp = NULL;

}

Page 195: RDMs 8.4 SQL User's Guide

14. Developing SQL Server Extensions

SQL User Guide 187

14.1.2 Using a UDF as a Trigger

The definition and use of standard SQL triggers was previously described in Chapter 8 where trigger was defined as "a pro-cedure associated with a table that is executed (i.e., fired) whenever that table is modified by the execution of an insert,update, or delete statement." The standard database triggers as implemented in RDM Server described in that earlier chapterare implemented using SQL statements only. If a trigger implementation requires more complex processing than can be donewith SQL statements then either the standard trigger must call as user-defined procedure (see section 14.2) to do the work or itcan be implemented through use of a UDF in conjunction with the table's check clause as described in this section.

In the database schema, you can define a trigger UDF in the check clause of the create table statement for a particulartable.The UDF returns a value (usually 1 for true and 0 for false) that is checked in the check condition. If the result of thecondition is true, SQL allows the modification to occur. If the result is false, the modification is rejected.

The example UDF module (udf.c) includes two trigger UDFs: HaveProduct and OkayToShip. The create table schema state-ments that references them are given below. Note that the prod_id and loc_id columns in the item table of the sales databasereference the corresponding primary keys in the product and outlet tables in the invntory database.

create table item(ord_num smallint not null references sales_order,prod_id smallint not null references invntory.product,loc_id char(3) not null references invntory.outlet,quantity integer not null "number of units of product ordered",

check(HaveProduct(ord_num, prod_id, loc_id, quantity) = 1)

) in salesd1;create table ship_log(ord_date timestamp default now "date/time when order was entered",ord_num smallint not null "order number",prod_id smallint not null "product id number",loc_id char(3) not null "outlet location id",quantity integer not null "quantity of item to be shipped from loc_id",backordered smallint default 0 "set to 1 when item is backordered",

check(OKayToShip(ord_num,prod_id,loc_id,quantity,backordered) = 1)

) in salesd0;

The HaveProduct UDF automatically manages the invntory database and the ship_log table. When your RDM Server SQLapplication executes an insert statement, HaveProduct looks up the on_hand record for the specified prod_id and loc_idcolumns. If there are enough items available, HaveProduct subtracts the ordered amount of the item from the quantity in theon_hand record and inserts a row in the ship_log table, from which a packing list will be created. If there are not enoughitems available, HaveProduct assigns the quantity that is available to the order (that is, sets the quantity to zero) and inserts arow in ship_log for that quantity. With the backordered flag (for the OkayToShip UDF) set to 1, HaveProduct specifies theremaining amount needed to fill the order through an additional row in ship_log.

When the RDM Server SQL application uses HaveProduct with a delete statement, the UDF adds the number of items orderedto the on_hand record and sets quantity in ship_log to 0 for the appropriate rows. The application can delete rows in ship_logwhen an order is actually shipped. When the application executes a delete statement for this table, OkayToShip checks thebackordered flag. If the flag is set, the UDF rechecks the inventory to see if there are now enough items from which to fill theorder. If there are still not enough, the trigger UDF rejects the delete request. If enough items are available, OkayToShipupdates the inventory and processes the required number of items.

Page 196: RDMs 8.4 SQL User's Guide

14. Developing SQL Server Extensions

SQL User Guide 188

The HaveProduct and OkayToShip trigger UDFs use the SQL statements shown below, along with theSALES_CTX structure containing the UDF statement context data. The statements are compiled inudfInit for each trigger UDF. The needed statements are executed by the udfFunc processingfunctions./* HaveProduct & OKayToShip SQL statements: */static char inv_cursor[]=

"select quantity from on_hand where prod_id=? and loc_id=?;";static char inv_update[]=

"update on_hand set quantity = ? where current of inv_cursor";static char shp_insert[]=

"insert into ship_log values(now, ?, ?, ?, ?, ?)";static char shp_update[]=

"update ship_log set ord_date = now, quantity = 0 where ""ord_num=? and prod_id=? and loc_id=?";

static char ord_update[]="update sales_order set ship_date = now where ord_num=?";

/* HaveProduct and OKayToShip context data */typedef struct sales_ctx {RM_MEMTAG mTag; /* system memory allocation tag */int16 stype; /* statement type (e.g. sqlINSERT) */SQLHENV henv; /* SQL environment handle */SQLHDBC hdbc; /* SQL connection handle */SQLHSTMT hInvSel; /* SQL statement handle for inv_cursor */SQLHSTMT hInvUpd; /* SQL statement handle for inv_update */SQLHSTMT hShpIns; /* SQL statement handle for shp_insert */SQLHSTMT hShpUpd; /* SQL statement handle for shp_update or ord_update */

} SALES_CTX;

The InvInit function shown below is the initialization function (type UDFINIT) for HaveProduct. It calls SQLConnectWithto use the same connection handle as the calling application, to ensure that the database changes made by the UDF areincluded in the transaction of the calling application. Thus, if the application executes a rollback statement for the trans-action, the HaveProduct changes will be rolled back as well. Note also the use of SYSDescribeStmt to determine whichtype of operation (insert, delete, etc.) the application is performing on the table.

static int16 REXTERNAL InvInit (HSYS hSys, /* in: system handle */void **cxtp) /* in: statement context pointer */

{SALES_CTX *stp;RDM_SESS hsess;RM_MEMTAG mTag;int16 status = SQL_SUCCESS;

SYSMemoryTag(hSys, &mTag);

stp = *cxtp = rm_getMemory(sizeof(SALES_CTX), mTag);stp->mTag = mTag;

SYSDescribeStmt(hSys, &stp->stype);

if ( stp->stype == sqlINSERT || stp->stype == sqlDELETE ) {/* connect to calling statement's connection */SQLAllocHandle(SQL_HANDLE_ENV, NULL, &stp->henv);SQLSetEnvAttr(stp->henv, SQL_ATTR_ODBC_VERSION,

(SQLPOINTER)SQL_OV_ODBC3, SQL_IS_INTEGER);SQLAllocHandle(SQL_HANDLE_DBC, stp->henv, &stp->hdbc);SYSSessionId(hSys, &hsess);SQLConnectWith(stp->hdbc, hsess);

Page 197: RDMs 8.4 SQL User's Guide

14. Developing SQL Server Extensions

SQL User Guide 189

SQLAllocHandle(SQL_HANDLE_STMT, stp->hdbc, &stp->hInvSel);SQLSetCursorName(stp->hInvSel, inv_cursor_name, SQL_NTS);SQLPrepare(stp->hInvSel, inv_cursor, SQL_NTS);

SQLAllocHandle(SQL_HANDLE_STMT, stp->hdbc, &stp->hInvUpd);SQLPrepare(stp->hInvUpd, inv_update, SQL_NTS);

if ( stp->stype == sqlINSERT ) {SQLAllocHandle(SQL_HANDLE_STMT, stp->hdbc, &stp->hShpIns);SQLPrepare(stp->hShpIns, shp_insert, SQL_NTS);

} else {SQLAllocHandle(SQL_HANDLE_STMT, stp->hdbc, &stp->hShpUpd);SQLPrepare(stp->hShpUpd, shp_update, SQL_NTS);

}}return status;

}

The following example illustrates the InvCheck type checking function for HaveProduct. InvCheck verifies that the applic-ation is passing the correct number and types of parameters to HaveProduct.

static int16 REXTERNAL InvCheck (HSYS hSys, /* in: system handle */int16 noargs, /* in: number of arguments to function */const VALUE *args, /* in: array of arguments */VALUE *result, /* out: result value */int16 *len) /* out: max length result string */

{int16 status = SQL_ERROR;

UNREF_PARM(hSys);UNREF_PARM(len);

/* validate arguments */if ( noargs != 4 )

result->vt.cv = "HaveProduct: requires 4 arguments";else if ( args[0].type != SQL_SMALLINT )

result->vt.cv = "HaveProduct: ord_num must be 1st arg";else if ( args[1].type != SQL_SMALLINT )

result->vt.cv = "HaveProduct: prod_id must be 2nd arg";else if ( args[2].type != SQL_CHAR )

result->vt.cv = "HaveProduct: loc_id must be 3rd arg";else if ( args[3].type != SQL_INTEGER )

result->vt.cv = "HaveProduct: quantity must be 4th arg";else {

result->type = SQL_SMALLINT;status = SQL_SUCCESS;

}return status;

}

In the following example, the InvFunc function (type UDFFUNC), which is only used in conjunction with an insert or deletestatement, performs the actual processing for HaveProduct. First, InvFunc opens a cursor to the row in the on_hand table withthe matching prod_id and loc_id values. Then, for an insert statement, InvFunc binds the parameters for the ship_log rowsand inserts one or two rows, depending on the available quantity in inventory. The function also updates the on_hand row. Ifprocessing a delete statement, with quantity set to 0 for the previously entered ship_log rows, InvFunc updates the on_handrecord to include the non-backordered item quantity.

Page 198: RDMs 8.4 SQL User's Guide

14. Developing SQL Server Extensions

SQL User Guide 190

static int16 REXTERNAL InvFunc (HSYS hSys, /* in: system handle */void **cxtp, /* in: statement context pointer */int16 noargs, /* in: number of arguments to function */const VALUE *args, /* in: array of arguments */VALUE *result) /* out: result value */

{int16 stat;int16 backordered;int32 quantity;int32 diff;const SALES_CTX *stp = *cxtp;

UNREF_PARM(hSys);UNREF_PARM(noargs);

if ( stp->stype != sqlINSERT && stp->stype != sqlDELETE ) {result->type = SQL_CHAR;result->vt.cv = "cannot update item table - delete and re-insert";return SQL_ERROR;

} else {/* look up on_hand record */SQLBindCol(stp->hInvSel,1,SQL_C_DEFAULT,&quantity,sizeof(quantity),NULL);SQLBindParameter(stp->hInvSel,1,SQL_PARAM_INPUT,SQL_C_SHORT,SQL_SMALLINT,

0L,0,(void *)&args[1].vt.sv,0,NULL);SQLBindParameter(stp->hInvSel,2,SQL_PARAM_INPUT,SQL_C_CHAR,SQL_CHAR,

3L,0,(void *)args[2].vt.cv,0,NULL);SQLExecute(stp->hInvSel);stat = SQLFetch(stp->hInvSel);if ( stat != SQL_SUCCESS ) {

SQLFreeStmt( stp->hInvSel,SQL_CLOSE );result->type = SQL_CHAR;result->vt.cv = "missing inventory record";return SQL_ERROR;

}/* set up on_hand update parameter */SQLBindParameter(stp->hInvUpd,1,SQL_PARAM_INPUT,SQL_C_LONG,SQL_INTEGER,

0L,0,&diff,0,NULL);

if ( stp->stype == sqlINSERT ) {/* set up ship_log insert parameters */SQLBindParameter(stp->hShpIns,1,SQL_PARAM_INPUT,SQL_C_SHORT,SQL_SMALLINT,

0L,0,(void *)&args[0].vt.sv,0,NULL);SQLBindParameter(stp->hShpIns,2,SQL_PARAM_INPUT,SQL_C_SHORT,SQL_SMALLINT,

0L,0,(void *)&args[1].vt.sv,0,NULL);SQLBindParameter(stp->hShpIns,3,SQL_PARAM_INPUT,SQL_C_CHAR,SQL_CHAR,

3L,0,(void *)args[2].vt.cv, 0,NULL);SQLBindParameter(stp->hShpIns,4,SQL_PARAM_INPUT,SQL_C_LONG,SQL_INTEGER,

0L,0,(void *)&quantity, 0,NULL);SQLBindParameter(stp->hShpIns,5,SQL_PARAM_INPUT,SQL_C_SHORT,SQL_SMALLINT,

0L,0,(void *)&backordered, 0, NULL);

diff = quantity - args[3].vt.lv;if ( diff >= 0 ) {

/* all needed inventory is available */backordered = 0;quantity = args[3].vt.lv;SQLExecute(stp->hShpIns); /* insert ship_log row */SQLExecute(stp->hInvUpd); /* update inventory amount */

} else {

Page 199: RDMs 8.4 SQL User's Guide

14. Developing SQL Server Extensions

SQL User Guide 191

/* there are not enough items available in inventory-- use what is there and backorder the rest */

/* insert ship_log row of used items (all remaining inventory) */backordered = 0;SQLExecute(stp->hShpIns);

/* insert ship_log row of backordered items */backordered = 1;quantity = args[3].vt.lv - quantity;SQLExecute(stp->hShpIns);

/* set inventory amount to zero */diff = 0;SQLExecute(stp->hInvUpd);

}} else {

/* delete item row */

/* put items back into inventory */diff = args[3].vt.lv + quantity;SQLExecute(stp->hInvUpd);

/* ship_log.quantity == 0 => order has been changed */SQLBindParameter(stp->hShpUpd,1,SQL_PARAM_INPUT,SQL_C_SHORT,SQL_SMALLINT,

0L,0,(void *)&args[0].vt.sv, 0, NULL);SQLBindParameter(stp->hShpUpd,2,SQL_PARAM_INPUT,SQL_C_SHORT,SQL_SMALLINT,

0L,0,(void *)&args[1].vt.sv, 0, NULL);SQLBindParameter(stp->hShpUpd,3,SQL_PARAM_INPUT,SQL_C_CHAR,SQL_CHAR,

3L,0,(void *)args[2].vt.cv, 0, NULL);SQLExecute(stp->hShpUpd);

}}SQLFreeStmt(stp->hInvSel, SQL_CLOSE);

result->type = SQL_SMALLINT;result->vt.sv = 1;

return SQL_SUCCESS; /*lint !e438 */}

The InvCleanup cleanup function is shown below for HaveProduct. InvCleanup frees all RDM Server SQL handles used bythe trigger UDF, as well as the context memory previously allocated.

static void REXTERNAL InvCleanup (HSYS hSys, /* in: system handle */void **cxtp) /* in: statement context pointer */

{const SALES_CTX *stp = *cxtp;

UNREF_PARM(hSys);

if ( stp->stype == sqlINSERT || stp->stype == sqlDELETE ) {SQLFreeHandle(SQL_HANDLE_STMT, stp->hInvSel);SQLFreeHandle(SQL_HANDLE_STMT, stp->hInvUpd);if ( stp->stype == sqlINSERT )

SQLFreeHandle(SQL_HANDLE_STMT, stp->hShpIns);else

Page 200: RDMs 8.4 SQL User's Guide

14. Developing SQL Server Extensions

SQL User Guide 192

SQLFreeHandle(SQL_HANDLE_STMT, stp->hShpUpd);SQLDisconnect(stp->hdbc);SQLFreeHandle(SQL_HANDLE_DBC, stp->hdbc);SQLFreeHandle(SQL_HANDLE_ENV, stp->henv);

}rm_freeMemory(stp, stp->mTag); /*lint !e449 */*cxtp = NULL;

}

The OkayToShip UDF is called from the check clause defined on the ship_log table. A delete on the ship_log table isdefined as indicating that the item is to be shipped to the customer.

The OkayToShip initialization function, ShipInit, is shown below. This function allocates the UDF context memory and theneeded SQL handles. It then calls SQLPrepare to compile the SQL statements that execute the desired trigger actions.

static int16 REXTERNAL ShipInit (HSYS hSys, /* in: system handle */void **cxtp) /* in: statement context pointer */

{SALES_CTX *stp;RDM_SESS hsess;RM_MEMTAG mTag;int16 status = SQL_SUCCESS;

SYSMemoryTag(hSys, &mTag);stp = *cxtp = rm_getMemory(sizeof(SALES_CTX), mTag);stp->mTag = mTag;SYSDescribeStmt(hSys, &stp->stype);if ( stp->stype == sqlDELETE ) {

/* connect to calling statement's connection */SQLAllocHandle(SQL_HANDLE_ENV, NULL, &stp->henv);SQLSetEnvAttr(stp->henv, SQL_ATTR_ODBC_VERSION,

(SQLPOINTER)SQL_OV_ODBC3, SQL_IS_INTEGER);SQLAllocHandle(SQL_HANDLE_DBC, stp->henv, &stp->hdbc);SYSSessionId(hSys, &hsess);SQLConnectWith(stp->hdbc, hsess);

SQLAllocHandle(SQL_HANDLE_STMT, stp->hdbc, &stp->hInvSel);SQLPrepare(stp->hInvSel, inv_cursor, SQL_NTS);SQLSetCursorName(stp->hInvSel, inv_cursor_name, SQL_NTS);

SQLAllocHandle(SQL_HANDLE_STMT, stp->hdbc, &stp->hInvUpd);SQLPrepare(stp->hInvUpd, inv_update, SQL_NTS);

SQLAllocHandle(SQL_HANDLE_STMT, stp->hdbc, &stp->hShpUpd);SQLPrepare(stp->hShpUpd, ord_update, SQL_NTS);

}return status;

}

The cleanup function for OkayToShip frees the allocated SQL handles.

static void REXTERNAL ShipCleanup (HSYS hSys, /* in: system handle */void **cxtp) /* in: statement context pointer */

{const SALES_CTX *stp = *cxtp;

Page 201: RDMs 8.4 SQL User's Guide

14. Developing SQL Server Extensions

SQL User Guide 193

UNREF_PARM(hSys)

if ( stp->stype == sqlDELETE ) {SQLFreeHandle(SQL_HANDLE_STMT, stp->hInvSel);SQLFreeHandle(SQL_HANDLE_STMT, stp->hInvUpd);SQLFreeHandle(SQL_HANDLE_STMT, stp->hShpUpd);SQLDisconnect(stp->hdbc);SQLFreeHandle(SQL_HANDLE_DBC, stp->hdbc);SQLFreeHandle(SQL_HANDLE_ENV, stp->henv);

}

rm_freeMemory(stp, stp->mTag); /*lint !e449 */*cxtp = NULL;

}

OkayToShip takes all of the ship_log columns except ord_date as arguments. Function ShipCheck, shown below, ensures thatthe correct number and types have been specified.

static int16 REXTERNAL ShipCheck(HSYS hSys, /* in: system handle */int16 noargs, /* in: number of arguments to function */const VALUE *args, /* in: array of arguments */VALUE *result, /* out: result value */int16 *len) /* out: max length result string */

{int16 status = SQL_ERROR;

UNREF_PARM(hSys);UNREF_PARM(len);

/* validate arguments */if ( noargs != 5 )

result->vt.cv = "OkayToShip: requires 5 arguments";else if ( args[0].type != SQL_SMALLINT )

result->vt.cv = "OkayToShip: ord_num must be 1st arg";else if ( args[1].type != SQL_SMALLINT )

result->vt.cv = "OkayToShip: prod_id must be 2nd arg";else if ( args[2].type != SQL_CHAR )

result->vt.cv = "OkayToShip: loc_id must be 3rd arg";else if ( args[3].type != SQL_INTEGER )

result->vt.cv = "OkayToShip: quantity must be 4th arg";else if ( args[4].type != SQL_SMALLINT )

result->vt.cv = "OkayToShip: backordered must be 5th arg";else {

result->type = SQL_SMALLINT;status = SQL_SUCCESS;

}return status;

}

Function ShipFunc performs the OkayToShip trigger operations. The on_hand row associated with the warehouse from whichthe item will be shipped is rechecked for backordered items to see if there is now a sufficient quantity for filling the order. Ifthere is, on_hand.quantity is decremented by the backordered quantity and the delete is allowed. If there is still not enoughinventory, the delete is rejected. If all is okay, the ship_date column in the sales_order table is updated indicating that (atleast part of) the order has been shipped.

Page 202: RDMs 8.4 SQL User's Guide

14. Developing SQL Server Extensions

SQL User Guide 194

static int16 REXTERNAL ShipFunc(HSYS hSys, /* in: system handle */void **cxtp, /* in: statement context pointer */int16 noargs, /* in: number of arguments to function */const VALUE *args, /* in: array of arguments */VALUE *result) /* out: result value */

{int16 stat;int32 quantity;int32 diff;const SALES_CTX *stp = *cxtp;

UNREF_PARM(hSys);UNREF_PARM(noargs);

if (stp->stype == sqlDELETE) {if (args[4].vt.sv == 1) {

/* item was backordered -- see if inventory now has enough */SQLBindCol(stp->hInvSel,1,SQL_C_DEFAULT,&quantity,sizeof(quantity),NULL);SQLBindParameter(stp->hInvSel,1,SQL_PARAM_INPUT,SQL_C_SHORT,SQL_SMALLINT,

0L,0,(void *)&args[1].vt.sv,0,NULL);SQLBindParameter(stp->hInvSel,2,SQL_PARAM_INPUT,SQL_C_CHAR,SQL_CHAR,

3L,0,(void *)args[2].vt.cv,0,NULL);SQLExecute(stp->hInvSel);stat = SQLFetch(stp->hInvSel);if ( stat != SQL_SUCCESS ) {

SQLFreeStmt(stp->hInvSel, SQL_CLOSE);result->type = SQL_CHAR;result->vt.cv = "missing inventory record";return SQL_ERROR;

}if (quantity >= args[3].vt.lv) {

/* inventory now has enough to ship! *//* set up on_hand update parameter */SQLBindParameter(stp->hInvUpd,1,SQL_PARAM_INPUT,SQL_C_LONG,

SQL_INTEGER,0L,0,(void *)&diff,0,NULL);diff = quantity - args[3].vt.lv;SQLExecute(stp->hInvUpd);SQLFreeStmt(stp->hInvSel, SQL_CLOSE);

} else {/* still can't ship */SQLFreeStmt(stp->hInvSel, SQL_CLOSE);result->type = SQL_CHAR;result->vt.cv = "can't delete(i.e. ship) backordered item";return SQL_ERROR;

}}/* update sales_order's ship_date */SQLBindParameter(stp->hShpUpd, 1, SQL_PARAM_INPUT, SQL_C_SHORT,

SQL_SMALLINT, 0L, 0, (void *) &args[0].vt.sv, 0, NULL);SQLExecute(stp->hShpUpd);

}result->type = SQL_SMALLINT;result->vt.sv = 1;return SQL_SUCCESS; /*lint !e438 */

}

Page 203: RDMs 8.4 SQL User's Guide

14. Developing SQL Server Extensions

SQL User Guide 195

14.1.3 Invoking a UDF

Before your application can use a UDF, it must register the module using a create function statement, as shown in the fol-lowing example. This statement registers the UDF module with the syscat database. The following statements create threeaggregate UDFs contained in a DLL called statpack on a Velocis device named add_ins.

create aggregate functiondevsq "compute sum of the squares of deviations",stddev "compute standard deviation",geomean "compute the geometric mean"

in statpack on add_ins;

Once the module is registered, a UDF can be called from SQL statements, just like the built-in RDM Server SQL-callable func-tions. Examples are given below for using an aggregate UDF and a scalar UDF.

The following example illustrates entry of statements to call the sample scalar UDF SubQuery and the sample aggregateUDFs std and stds. The example uses the sales and invntory databases.

create aggregate functions std, stdsscalar function subqueryin udf on sqlsamp;

select sale_name, avg(amount), std(amount), stds(amount)from salesperson, customer, sales_orderwhere salesperson.sale_id = customer.sale_id andcustomer.cust_id = sales_order.cust_id group by 1;

SALE_NAME AVG(AMOUNT) STD(AMOUNT) STDS(AMOUNT)Flores, Bob 19233.557778 21767.832956 23088.273442Jones, Walter 28170.703333 22055.396667 22829.504456Kennedy, Bob 61362.110000 75487.487619 78844.109392McGuire, Sidney 18948.373636 16888.086829 17712.374895Nash, Gail 34089.695556 35751.014170 37919.676831Porter, Greg 87869.300000 87370.831661 97683.559422Robinson, Stephanie 24993.631333 28766.406110 29776.059184Stouffer, Bill 3631.662500 2731.390470 2919.979236Warren, Wayne 21263.850000 24150.207498 25456.553886Williams, Steve 27464.443333 16696.742874 17709.570165Wyman, Eliska 23617.375417 31511.044841 32188.779254

select state,100*sum(amount)/subquery("select sum(amount) from sales_order") pct_of_salesfrom customer, sales_orderwhere customer.cust_id = sales_order.cust_id group by state;

STATE PCT_OF_SALESAZ 6.386350CA 13.108034CO 13.422859FL 3.591970GA 3.057682IL 4.310374IN 0.781594LA 4.993924MA 3.233216MI 11.819327MN 1.330608

Page 204: RDMs 8.4 SQL User's Guide

14. Developing SQL Server Extensions

SQL User Guide 196

MO 3.807593NJ 0.425850NY 10.425037OH 4.414228PA 3.911350TX 3.259824VA 1.695903WA 1.634471WI 4.389806

Calling an Aggregate UDF

After module registration, as described above, the application can call an aggregate UDF from SQL statements. For example,the select statement shown below calls the aggregate UDF geomean, defined in the previous section. Note that the code alsocalls the built-in aggregate function avg.

select state, avg(amount), geomean(amount) from customer, sales_orderwhere customer.cust_id = sales_order.cust_idgroup by state;

Calling a Scalar UDF

Your RDM Server SQL application calls a scalar UDF from SQL statements, just as it calls the built-in functions. The nextexample illustrates the use of the sample UDF SubQuery to retrieve a percentage of total values. Note the power of this UDF,as shown by the need to use only a single select statement in the application.

select state,100*sum(amount)/subquery("select sum(amount) from sales_order") pct_of_sales

from customer, sales_order where customer.cust_id = sales_order.cust_idgroup by state;

The select statement in the next example calls a scalar UDF called tax_rate, which returns the tax rate for a given city.

select company, city, state, tax_rate(city, state) tax_rate from customer;

This tax_rate UDF looks up the tax rate for a locale in an internal table or a database table. An application can use this UDFas shown below, to display sales orders with tax amounts that do not correspond to the going rate.

select company, city, state, ord_num, ord_date, amount, taxfrom customer, sales_orderwhere customer.cust_id = sales_order.cust_id and

not equal_float(convert(tax, float), amount * tax_rate(city, state), 0.005);

Note that this example also uses a UDF called equal_float that returns TRUE if two floating-point values differ by less thanthe value of the third parameter. Note also the use of the built-in function convert to change the value of the column tax fromtype real to type float.

Page 205: RDMs 8.4 SQL User's Guide

14. Developing SQL Server Extensions

SQL User Guide 197

14.1.4 UDF Support Library

A library of support functions for SQL user-defined functions has been provided to allow UDFs to perform low-level databaseoperations, associate SQL modification commands with the client's transactions, and utilize the SQL system's data type arith-metic capabilities. A list of the available functions is provided in the table below.

Function DescriptionSYSDBHandle Retrieve the RDM_DB handle for a specified databaseSYSDbaToRowId Convert an RDM DB_ADDR to an SQL_DBADDR (RowId)SYSMemoryTag Return UDF memory allocation tagSYSRowIdToDba Convert an SQL_DBADDR for a table to an RDM DB_

ADDRSYSRowDba Get the DB_ADDR for the current row of the specified tableSYSRowId Get the RowId (SQL_DBADDR) for the current row of the

specified tableSYSSessionId Get the RDM_SESS (SessionId) associated with a HSYSSYSDescribeStmt Get the statement type associated with a HSYSSYSValChgSign Change the sign of a VALUESYSValCompare Compare 2 VALUEsSYSValAdd Add 2 VALUEsSYSValSub Subtract 2 VALUEsSYSValMult Multiply 2 VALUEsSYSValDiv Divide 2 VALUEs

Table 13-1. UDF Support Library Functions

The SYS-prefixed functions are used to access SQL-maintained statement context information. Most of the SYS functionshave standard SQL-prefixed functions that provide the same functionality for the connection handle (HDBC) instead of thesystem handle (HSYS). These functions are provided so that the information associated with the SQL statement that uses aUDF can be accessed by the UDF.

The SYSVal-prefixed functions are provided so that, if needed, you can do mixed-mode arithmetic in your UDFs. Each of theSYSVal functions is passed arguments of type VALUE as described earlier. The functions that are provided hook into theinternal SQL arithmetic functions to perform the mixed-mode arithmetic operations. The results of the mixed mode arithmeticfollow the standard C rules.

14.2 User-Defined Procedures

A UDP is an application-specific procedure written in C that is architecturally similar to a UDF but is only invoked throughthe RDM Server SQL call (execute) statement. UDPs can do anything that RDM Server SQL stored procedures can do (includ-ing retrieve result sets), but UDPs are more flexible than stored procedures because they are written in C and can supportdynamic parameter lists. The example UDP module code (udp.c) described below is included in the examples/udp dir-ectory. Before a UDP can be used from SQL is must first be registered with the system which is done using the followingform of the create procedure statement.

create_procedure:create proc[edure] procname ["description"] in dllname on devname

The name of the procedure is given by the identifier procname along with an optional description string. The name of theUDP module in which this UDP is implemented is given by libname which must be located in the RDM Server devicenamed devname.

Page 206: RDMs 8.4 SQL User's Guide

14. Developing SQL Server Extensions

SQL User Guide 198

The example UDP module defines three UDPs, tims_data, log_login, and log_logout. The tims_data UDP retrieves a result setconsisting of data from the tims database. To illustrate how a UDP can retrieve multiple result sets, tims_data retrieves an addi-tional result set that reports the total number of rows retrieved in the first result set. The sample log_login and log_logoutUDPs are special login and logout procedures which keep a record of all users who have logged in and out of the RDMServer through the SQLConnect function.

Function Descriptiontims_data Uses RDM Server core-level (d_) API to retrieve a result set

from the TIMS database example.log_login System login tracking procedure.log_logout System logout tracking procedure.

Table 13-5. Procedures Defined in the Sample UDP Module

14.2.1 UDP Implementation

Keep the following concepts in mind when programming your UDP module.

l The UDP must include a load function named udpDescribeFcns which identifies all of the UDPs implemented in themodule.

l UDP modules that contain the implementation of a transaction trigger (registered through a call to function SQLTrans-actTrigger) must include a both a ModInit and a ModCleanup function.

l Each UDP in the module can optionally include an initialization function of type UDPINIT. This function normallyallocates and initializes UDP-specific context memory containing statement handles and other operational data. TheSQL system calls it when the at the start of the execution of the call statement which invokes the UDP.

l Each UDP that takes procedures arguments must have a type checking function of type UDPCHECK. UDPs can takeany number of arguments of any type, and the value returned can be of any data type, except long varchar, longwvarchar, or long varbinary.

l Each UDP must include a processing function of type UDPEXECUTE. This function executes the procedure and, ifapplicable, returns the first select statement result set.

l Each UDP can optionally include a function of type UDPMORERESULTS to obtain the next result set.l Each UDP can optionally include a function of type UDPCOLDATA which can be used with UDPs that return resultsets to return a description of one of the result set columns.

l Each UDP can optionally include a cleanup function of type UDPCLEANUP. If defined, this function is called byRDM Server SQL when the procedure's statement is closed, or when the udpMoreResults function returns status SQL_NO_DATA (indicating there are no more result sets to be returned).

l In coding your UDP, you must declare all functions exactly as shown in the function references (including theREXTERNAL attribute). If a function declaration deviates at all, it will not match the UDP type (for example,UDPCHECK) used in the function declaration. This will cause a compilation error.

Function udpDescribeFcns

The UDP module must contain a function with the name udpDescribeFcns (use exact name). This function is called by RDMServer SQL to fetch the definitions of UDPs contained in the module from the UDPLOADTABLE struct array it returns. Atypical udpDescribeFcns implementation is shown in the following example.

void REXTERNAL udpDescribeFcns (uint16 *NumProcs, /* out: number of procedures in module */PUDPLOADTABLE *UDPLoadTable, /* out: points to UdfTable above */

Page 207: RDMs 8.4 SQL User's Guide

14. Developing SQL Server Extensions

SQL User Guide 199

const char **fcn_descr) /* out: optional description string */{

*NumProcs = RLEN(UdpTable); /* RLEN computes # of entries in struct array */*UDPLoadTable = UdpTable;

*fcn_descr = "Sample of SQL C-based procedures";}

There must be one entry in the UDPLOADTABLE array for each UDP that is implemented in the module. The declaration forUDPLOADTABLE is contained in header file sqlsys.h (included with emmain.h) and is shown below.

typedef struct udploadtable {uint8 version; /* version of this structure */char udpName[33]; /* name of user procedure */PUDPCHECK udpCheck; /* type checking */PUDPEXECUTE udpExecute; /* execute first result set */PUDPMORERESULTS udpMoreResults; /* move to next result set */PUDPINIT udpInit; /* initilization for user procedure */PUDPCLEANUP udpCleanup; /* cleanup for user procedure */PUDPCOLDATA udpColData; /* column description data */

} UDPLOADTABLE, *PUDPLOADTABLE;

Each element of the UDPLOADTABLE struct is described in the following table.

Element Descriptionversion Must be assigned to the RDM Server defined macro:

UDPTBLVERSION.udpName The name of the procedure. Must conform to a standard SQL

identifier. It is case-insenstive and unique (system-wide).udpCheck Pointer to the argument type checking function. Assign to

NULL if there are no arguments.udpExecute Pointer to the execution function.udpMoreResults Pointer to the function that initializes processing of the next

result set. Assign to NULL if no more than 1 result set isreturned.

udpInit Pointer to pre-execution initialization function.udpCleanup Pointer to post-execution cleanup function.udpColData Pointer to function that returns descriptions of the columns in

the result set. Assign to NULL if the UDP does not return aresult set.

Table 13-6. UDPLOADTABLE Struct Element Descriptions

The udpDescribeFcns function must place an entry in the load table for each function in each UDP. You also can return anoptional string describing the module, which will be printed on the server console when the UDP is loaded. Null can be sup-plied if there is no string. The following example shows the load table and udpDescribeFcns for the sample UDP module(udp.c).

#include <stdio.h>#include <string.h>

Page 208: RDMs 8.4 SQL User's Guide

14. Developing SQL Server Extensions

SQL User Guide 200

/* Definitions and header to setup EM ------------ *//* (all EMs must have a code block like this) --- */

#define EM_NAME UDP /* the uppercased module name */

#define EMTYPE_UDP /* EMTYPE_EM, EMTYPE_UDF, EMTYPE_UDP, EMTYPE_IEF */#define DEF_ModInit /* remove if no ModInit in this module */#define DEF_ModCleanup /* remove if no ModCleanup in this module */

#include "emmain.h" /* must follow the above definitions and OS #includes */...static UDPCHECK timsCheck;static UDPEXECUTE timsExecute;static UDPMORERESULTS timsMoreResults;static UDPINIT timsInit;static UDPCLEANUP timsCleanup;static UDPCOLDATA timsColData;static UDPINIT logInit;static UDPCLEANUP logCleanup;static UDPEXECUTE logLogin;static UDPEXECUTE logLogout;

static TRANSACTTRIGGER TransactTrigger;

/*--------------------------------------------------------------------------Table of user-defined procedures in this module

---------------------------------------------------------------------------*/static const UDPLOADTABLE UdpTable[] = {

{UDPTBLVERSION, "tims_data",timsCheck,timsExecute,timsMoreResults,timsInit,timsCleanup,timsColData},

{UDPTBLVERSION, "log_login",NULL,logLogin,NULL,logInit,logCleanup,NULL},

{UDPTBLVERSION, "log_logout",NULL,logLogout,NULL,logInit,logCleanup,NULL}

};

Function ModInit

If your UDP module has a transaction trigger you must include a ModInit (and ModCleanup) function. The server callsModInit when the UDP module is loaded, passing a single module handle. Your ModInit function needs to save this handlein a global variable that will be subsequently used by SQLTransactTrigger to register the transaction trigger function.The example below shows the ModInit function defined for the sample UDP module.

Page 209: RDMs 8.4 SQL User's Guide

14. Developing SQL Server Extensions

SQL User Guide 201

static HMOD ghMod = NULL;...

int16 REXTERNAL ModInit(HMOD hMod) /* in: Module handle, used by SQLTransactTrigger() */

{ghMod = hMod;

return S_OKAY;}

Function udpInit

Your UDP can optionally include an initialization function of type UDPINIT. RDM Server SQL calls this function from theudpInit entry in UDPLOADTABLE to perform any initialization the UDP may require. Common tasks include allocatingmemory for any context data and initializations such as handle allocations, connections, and compiling statements to beexecuted in udpExecute..

If needed, your udpInit function should allocate memory for the UDP context using an rm_cGetMemory call. The memorytag (mTag) argument passed to the function should be used in all dynamic memory allocations. RDM Server SQL uses thistag to free all of the memory allocated by the UDP in case an error occurs outside the UDP.

The err argument is a pointer to a standard RDM Server SQL VALUE structure (See SQL Data VALUE ContainerDescription). The udpInit function uses this structure to pass error information back to the RDM Server SQL API for reportingto the application. The udpInit function sets the type field in the structure to either SQL_SMALLINT or SQL_CHAR. If thefield is set to SQL_SMALLINT, the vt.sv field should be set to the RDM Server SQL error code to return to the RDM ServerSQL API. If udpInit sets the type field in the VALUE structure to SQL_CHAR, then it must set the vt.cv field to point to astatic string containing an error message that will be reported under the errGENERAL error code. In addition to setting the errparameter, the udpInit function should also return SQL_ERROR if an error occurs.

The timsInit function, shown below for the tims_data UDP, allocates the UDP_CTX structure for the UDP context and opensthe tims database via the Core (d_) API with dirty read mode enabled (no locking). Then the function allocates the necessaryRDM Server SQL statement handles, executes two statements, and stores the information in the allocated UDP context. If anyerrors occur, the information is returned to RDM Server in the err argument.

typedef struct udp_ctx {SQLHENV hEnv;SQLHDBC hDbc;SQLHSTMT hStmt;RDM_SESS hSess;RDM_DB hDb;int16 finished;

} UDP_CTX;

static const char TimsCreate[] ="create temporary table timstab(""author char(31), ""id_code char(15), ""info_title char(48), ""publisher char(31), ""pub_date char(11), ""info_type smallint); ";

static const char TimsInsert[] =

Page 210: RDMs 8.4 SQL User's Guide

14. Developing SQL Server Extensions

SQL User Guide 202

"insert into timstab values(?,?,?,?,?,?)";...

/* ===================================================================Initialization function for TIMS DB access*/int16 REXTERNAL timsInit(void **ctxp, /* in: proc context pointer */int16 noargs, /* in: number of arguments passed */VALUE *args, /* in: arguments, args[noargs-1] */RDM_SESS hSess, /* in: current session id */RM_MEMTAG mTag, /* in: memory tag for rm_ memory calls */VALUE *err) /* out: container for error messages */

{int16 stat;

/* allocate a cleared UDP context memory */UDP_CTX *ctx = rm_cGetMemory(sizeof(UDP_CTX), mTag);

ctx->hSess = hSess;if ((stat = d_open("tims", "s", hSess, &ctx->hDb)) != S_OKAY)

err->type = SQL_CHAR;err->vt.cv = "unable to open TIMS";rm_freeMemory(ctx, mTag);return SQL_ERROR;

}...{

/* enable dirty reads */d_rdlockmodes(1, 1, ctx->hSess);

/* allocate and initialize SQL handles */SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &ctx->hEnv);SQLSetEnvAttr(ctx->hEnv, SQL_ATTR_ODBC_VERSION,(SQLPOINTER)SQL_OV_ODBC3,

SQL_IS_UINTEGER);SQLAllocHandle(SQL_HANDLE_DBC, ctx->hEnv, &ctx->hDbc);SQLConnectWith(ctx->hDbc, hSess);SQLAllocHandle(SQL_HANDLE_STMT, ctx->hDbc, &ctx->hStmt);

SQLExecDirect(ctx->hStmt, TimsCreate, SQL_NTS);SQLPrepare(ctx->hStmt, TimsInsert, SQL_NTS);

*ctxp = ctx;return SQL_SUCCESS;

}

The call to SQLExecDirect creates a temporary table (timstab) and the call to SQLPrepare compiles a statement to insert datainto this table. The columns declared in timstab include the author name, which will be retrieved from the author record inthe tims database and one column for each of the fields declared in the info record in tims. The insert statement includes aparameter marker for each of the columns declared in timstab.

The last statement before the return in timsInit, (*ctxp = ctx;) must be specified so that the context pointercan be passed by SQL to the other UDP functions.

Page 211: RDMs 8.4 SQL User's Guide

14. Developing SQL Server Extensions

SQL User Guide 203

Function udpCheck

When the application calls SQLPrepare to compile a call (execute) statement referencing a UDP, SQLPrepare calls the func-tion in the udpCheck entry of UPDLOADTABLE to validate that the arguments specified in the execute statement are correctfor the specified UDP. Like the udpInit function, udpCheck uses the err argument to return error information to RDM Server.In addition, the function returns SQL_ERROR if an error returns as its return code. Unlike the udfCheck function, theudpCheck function only uses the err argument for error information.

The following example shows the timsCheck type checking function for tims_data. Remember that the sample login andlogout procedures do not have any arguments and, hence, do not need a type checking function.

int16 REXTERNAL timsCheck(int16 noargs, /* in: number of arguments passed */const int16 *types, /* in: type of each arg., types[noargs-1] */VALUE *err) /* out: container for error messages */

{int16 arg;err->type = SQL_CHAR;err->vt.cv = "tims_data requires char arguments only";for (arg = 0; arg < noargs; ++arg) {

if (types[arg] != SQL_CHAR)return SQL_ERROR;

}return SQL_SUCCESS;

}

Function udpExecute

The execution function for the UDP (type UDPEXECUTE) is called by RDM Server SQL from the udpExecute entry inUDPLOADTABLE when SQLExecute is processing a call (execute) statement that references the UDP (after the udpInitfunction and udpCheck functions, if any, have been called). If a result set is generated, then the hstmt referencing this resultset must be returned in the phStmt argument. This allows the client side application to fetch the results by calling SQLFetchor SQLFetchScroll with the hstmt used in the procedure execution. The timsExecute function for the tims_data UDP isdescribed below.

Since tims is not an SQL database, timsExecute uses the Core API (d_ functions) to retrieve data from the tims database andstores the data in a temporary SQL table. The timsExecute function first calls SQLBindParameter several times to bind val-ues to the parameter markers for the insert statements. The execution function then accesses the tims database. The d_setoofunction call sets the current member of the author_list set to null so that the first d_findnm call will return the first memberof the set.

int16 REXTERNAL timsExecute(void **ctxp, /* in: proc context pointer */int16 noargs, /* in: number of arguments to procedure */VALUE *args, /* in: array of arguments */RM_MEMTAG mTag, /* in: memory tag for rm_ memory calls */SQLHSTMT *phStmt, /* out: hstmt for result set */VALUE *err) /* out: container for error messages */

{static char errmsg[65];char author[32];struct info ir;int16 stat, arg;

Page 212: RDMs 8.4 SQL User's Guide

14. Developing SQL Server Extensions

SQL User Guide 204

char *author_arg = args->vt.cv;UDP_CTX *ctx = *ctxp;RDM_DB hdb = ctx->hDb;SQLHSTMT hstmt = ctx->hStmt;

/* set up insert parameter values */SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR,

SQL_CHAR, 31, 0, author, 0, NULL);SQLBindParameter(hstmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR,

SQL_CHAR, 15, 0, ir.id_code, 0, NULL);SQLBindParameter(hstmt, 3, SQL_PARAM_INPUT, SQL_C_CHAR,

SQL_CHAR, 79, 0, ir.info_title, 0, NULL);SQLBindParameter(hstmt, 4, SQL_PARAM_INPUT, SQL_C_CHAR,

SQL_CHAR, 31, 0, ir.publisher, 0, NULL);SQLBindParameter(hstmt, 5, SQL_PARAM_INPUT, SQL_C_CHAR,

SQL_CHAR, 11, 0, ir.pub_date, 0, NULL);SQLBindParameter(hstmt, 6, SQL_PARAM_INPUT, SQL_C_SHORT,

SQL_SMALLINT, 0, 0, &ir.info_type, 0, NULL);

/* extract data from tims database & insert into SQL table */d_setoo(AUTHOR_LIST, AUTHOR_LIST, hdb);for (;;) {

while ((stat = d_findnm(AUTHOR_LIST, hdb)) == S_OKAY) {d_recread(AUTHOR, author, hdb);for (arg = 0; arg < noargs; ++arg) {

/* check for author in argument list */char *aname = args[arg].vt.cv;if (strncmp(author, aname, strlen(aname)) == 0)

break;}

if (noargs == 0 || arg < noargs)break;

}

if (stat != S_OKAY)break;

d_setor(HAS_PUBLISHED, hdb);while ((stat = d_findnm(HAS_PUBLISHED, hdb)) == S_OKAY) {

d_recread(INFO, &ir, hdb);SQLExecute(hstmt);

} } SQLFreeStmt(hstmt, SQL_RESET_PARAMS);

/* return result set from SQL table */SQLExecDirect(hstmt, "select * from timstab", SQL_NTS);*phStmt = ctx->hStmt;

return SQL_SUCCESS;}

As each author record is retrieved from the author_list set, timExecute compares the name with each string argument passed tothe tims_data UDP. When the function finds a match, it fetches the publications for the author from the has_published set.The timsExecute function reads each info record and executes the insert statement to store the result in the timstab table.

When all authors have been processed, the timsExecute function executes a select statement that will return the rows stored intimstab. The function returns the handle of the select statement in phStmt, so that subsequent calls to SQLFetch can retrievethe results.

Page 213: RDMs 8.4 SQL User's Guide

14. Developing SQL Server Extensions

SQL User Guide 205

Function udpMoreResults

UDP that need to return the result sets of more than one select statement must include a udpMoreResults function, which iscalled by RDM Server SQL from the udpMoreResults entry in UDPLOADTABLE when the application calls SQLMoreRes-ults on the statement handle associated with the call (execute) statement. (SQLMoreResults is called after the initialstatement execution, which takes place in udpExecute, has occurred.) The udpMoreResults function has the same argmentsand executes the same way as the udpExecute function, also returning the statement handle associated with the currentlyexecuting select statement in phStmt. When there are no more result sets to return, have udpMoreResults return SQL_NO_DATA informing SQL that UDP processing is complete and to call the UDP's udpCleanup function, if one exists.

In the next example, the first time timsMoreResults is called, the call to SQLExecDirect sets up a result set that retrievesthe total number of rows in the timstab table. The finished flag in the UDP context is set to indicate that the first call hasbeen made, so that the next time SQLMoreResults is called, the function sees this set flag and returns SQL_NO_DATA.

If a UDP omits the udpMoreResults function, SQLMoreResults automatically returns SQL_NO_DATA.

int16 REXTERNAL timsMoreResults(void **ctxp, /* in: proc context pointer */int16 noargs, /* in: number of arguments to procedure */VALUE *args, /* in: array of arguments */RM_MEMTAG mTag, /* in: memory tag for rm_ memory calls */SQLHSTMT *phStmt, /* out: hstmt for result set */VALUE *err) /* out: container for error messages */

{UDP_CTX *ctx = *ctxp;

if (ctx->finished)return SQL_NO_DATA;

ctx->finished = 1;SQLCloseCursor(ctx->hStmt);SQLExecDirect(ctx->hStmt,

"select count(*) 'TOTAL ROWS FOUND' from timstab", SQL_NTS);*phStmt = ctx->hStmt;return SQL_SUCCESS;

}

Function udpColData

This function, if specified, is called by SQL while processing a call to function SQLProcedureColumns in order toretrieve descriptions of the columns in the result set returned by the UDP. The function returns via the pColDescr output argu-ment a description of the column specified by argument colno where the first column is 0. The function must return statusSQL_NO_DATA when the specified colno is invalid (either less than zero or greater than or equal to the number of columnsin the UDP result set).

The udpUpdCol entry in the UDPLOADTABLE must be NULL if the UDP does not return a result set and can be NULLeven if the UDP does return a result set.

The pColDescr argument is a pointer to a struct of type UDPPROCCOLDATA which is declared in header filesqlsys.h as shown below.

Page 214: RDMs 8.4 SQL User's Guide

14. Developing SQL Server Extensions

SQL User Guide 206

/* user defined procedure column data */typedef struct udpproccoldata {

char dbname[33];char tblname[33];char procname[33];char colname[33];int16 coltype;int32 datatype;char sqltypename[33];int32 precision;int32 length;int16 scale;int16 radix;int16 nullable;const char *remarks;char col_def[33];int32 sql_data_type;int32 sql_datetime_sub;int32 char_octet_len;int32 ordinal_pos;char is_nullable[4];char specific_name[33];

} UDPPROCCOLDATA;

Each element of the UPDPROCCOLDATA struct corresponds to a column of the result set returned by the SQLPro-cedureColumns ODBC function call as described in the following table.

Element Descriptiondbname The name of the database accessed by the UDP or NULL.tblname The name of the table accessed by the UDP or NULL.procname The name of the UDP.colname The name of the result set column.coltype The type of column: only 5=result set column is supported.datatype SQL data type constant (e.g., SQL_SMALLINT).sqltypename RDM Server data type name (e.g., "smallint").precision The specified max size of a character column or the precision

of a numeric column (e.g., integer, float, double).length The maximum length in bytes to contain the column values.

For char data this includes the terminating null byte.scale For columns of type decimal this contains the number of

decimal places in the result. Zero otherwise.radix For numeric types either 10 (decimal) or 2 (all others). Zero

for non-numeric types.nullable Indicates if the column can accept a NULL value.remarks Either NULL or a udpColData-allocated (use rm_getMemory

with the mTag argument) string containing a description ofyour choice.

col_def A string containing the column's default value or"TRUNCATED" if the default value does not fit in the field.

sql_data_type 0 (unused).sql_datetime_sub 0 (unused).

Table 13-7. UDPPROCCOLDATA Struct Element Descriptions

Page 215: RDMs 8.4 SQL User's Guide

14. Developing SQL Server Extensions

SQL User Guide 207

Element Descriptionchar_octet_len Currently, this returns the same value as the length field.ordinal_pos The column's ordinal position in the result set beginning with

1.is_nullable "YES" if the column can include nulls, "NO" if not, "" if it is

unknown.specific_name Same as procname.

The tims_data version of udpColData is shown below including its declaration of the UPDPROCCOLDATA table for the res-ult set it returns.

...static const UDPPROCCOLDATA timsDataCD[] = {

{"","","tims_data","author",0,1,"CHAR",31,31,-1,10,2,NULL,"",0,0,31,1,"","tims_data"},

{"","","tims_data","id_code",0,1,"CHAR",15,15,-1,10,2,NULL,"",0,0,15,2,"","tims_data"},

{"","","tims_data","info_title",0,1,"CHAR",48,48,-1,10,2,NULL,"",0,0,48,3,"","tims_data"},

{"","","tims_data","publisher",0,1,"CHAR",31,31,-1,10,2,NULL,"",0,0,31,4,"","tims_data"},

{"","","tims_data","pub_date",0,1,"CHAR",11,11,-1,10,2,NULL,"",0,0,11,5,"","tims_data"},

{"","","tims_data","info_type",0,5,"SMALLINT",5,2,0,10,2,NULL,"",0,0,0,6,"","tims_data"},

};#define MAX_TIMSDATA_COLUMNS (sizeof(timsDataCD)/sizeof(UDPPROCCOLDATA))

...static int16 REXTERNAL timsColData(

const VALUE *args, /* in: array of arguments */UDPPROCCOLDATA *pColDescr, /* out: procedure column data pointer */int16 colno, /* in: column number */RM_MEMTAG mTag, /* in: memory tag for rm_ memory calls */VALUE *err) /* out: container for error messages */

{const char *procname = args[2].vt.cv;

UNREF_PARM(mTag)UNREF_PARM(err)

if (stricmp(procname, "tims_data") != 0)return SQL_NO_DATA;

if ((uint16) colno >= MAX_TIMSDATA_COLUMNS)return SQL_NO_DATA;

memcpy(pColDescr, &timsDataCD[colno], sizeof(UDPPROCCOLDATA));

return SQL_SUCCESS;}

Function udpCleanup

The udpCleanup function is called by SQL when UDP execution is completed (e.g., when SQL_NO_DATA is returned byupdExecute or udpMoreResults). It is used to free memory allocated in udpInit and/or udpExecute, close database

Page 216: RDMs 8.4 SQL User's Guide

14. Developing SQL Server Extensions

SQL User Guide 208

connections, and drop temporary tables.

NEVER call rm_freeTagMemory within udpCleanup using the memory tag passed into the function.Instead, free any UDP-allocated memory using rm_freeMemory.

The following example shows the timsCleanup function from the sample tims_data UDP. This function first closes the activestatement handle, then executes a drop table statement to close the timstab temporary table. Afterwards, timsCleanip dropsthe statement handle, then closes and frees the connection and the SQL environment handle. Finally, the function closes thetims database and frees the context memory using rm_freeMemory.

void REXTERNAL timsCleanup(void **ctxp, /* in: statement udp_ctx pointer */int16 mTag) /* in: memory tag for rm_ memory calls */

{UDP_CTX *ctx = *ctxp;

SQLCloseCursor(ctx->hStmt);SQLExecDirect(ctx->hStmt, "drop table timstab", SQL_NTS);SQLFreeHandle(SQL_HANDLE_STMT, ctx->hStmt);SQLDisconnect(ctx->hDbc);SQLFreeHandle(SQL_HANDLE_DBC, ctx->hDbc);SQLFreeHandle(SQL_HANDLE_ENV, ctx->hEnv);

d_rdlockmodes(0, 1, ctx->hSess);d_close(ctx->hDb);rm_freeMemory(ctx, mTag);

}

Function ModCleanup

If you include ModCleanup in the UDP module, the server calls it when unloading the module. The following exampleshows the ModCleanup function for the sample UDP.

int16 REXTERNAL ModCleanup(HMOD hMod) /* in: Module handle, used by SQLTransactTrigger() */{ghMod = NULL;

return S_OKAY;}

14.2.2 Calling a UDP

Once you have coded and successfully compiled the UDP module it needs to be registered with the system so that SQL willknow where to find it. The create procedure statement defined at the start of this section is used to do this as shown in fol-lowing is an example of this statement issued for tims_data.

create procedure tims_data in "udp" on sqlsamp;

Once the module is registered, the application can call the UDP just like an SQL-based stored procedure. The followingexample shows the call statement that executes the tims_data UDP. Each parameter specified for tims_data is the name (or

Page 217: RDMs 8.4 SQL User's Guide

14. Developing SQL Server Extensions

SQL User Guide 209

partial name) of an author for whom the UDP is retrieving applicable publications contained in the tims database. Note thatthis output shows the results of both the udpExecute function and the udpMoreResults function.

14.3 Login or Logout UDP Example

Login and logout procedures are stored procedures (or UDPs) that are invoked when all or certain users log in to or log outfrom the server. These procedures do not have parameters nor retrieve result sets. An administrator initially creates and activ-ates these procedures, but they are automatically invoked by other users when they log in or log out. Login and logout pro-cedures can be used for setting user environment values (for example, display formats) or for performing specialized securityfunctions.

Two UDPs are included in the sample UDP module (udp.c) provided with the system. The UDP named log_login is a loginprocedure and the one named log_logout is, you guessed it, a logout procedure. As loging and logout procedures do not havearguments nor retrieve resultsthe udpCheck and udpMoreResults function entries in the UDPLOADTABLE are NULL.

These UDPs both use logInit (type UDPINIT) as their initialization function, as shown in the following code. The logInitfunction allocates the memory for the UDP context (LOG_CTX), attaches to the client connection, and allocates and ini-tializes needed SQL handles. It also compiles an insert statement (LogInsert) used to store the record of a login or logout oper-ation. This insert stores a new row in the table called activity_log, which must have been previously created or elseSQLPrepare will fail. If this happens, LogInit will create the activity_log database and table and call SQLPrepare again.

The UDP context includes fields to contain for action and label strings. These are associated (by SQLBindParameter) withthe two parameter markers contained in LogInsert. The declarations of the user_name and stamp columns in activity_log spe-cify default values that automatically store the user name and the current timestamp values (the current date and time) in thetable.

...typedef struct log_ctx {HENV hEnv;HDBC hDbc;HSTMT hStmt;RDM_SESS hSess;char action[24];char label[33];

} LOG_CTX;...

static const char LogCreateDb[] ="create database activity_log";

static char LogCreate[] ="create table activity_log(""action char(23),""label char(32) default null,""user_name char(32) default user,""stamp timestamp default now)";

static char LogGrant[] = "grant insert on activity_log to public";

static char LogInsert[] = "insert into activity_log(action, label) values(?, ?)";...

/* ======================================================================Initialization function for logging functions

*/int16 REXTERNAL logInit(void **ctxp, /* in: proc context pointer */

Page 218: RDMs 8.4 SQL User's Guide

14. Developing SQL Server Extensions

SQL User Guide 210

int16 noargs, /* in: number of arguments passed */VALUE *args, /* in: arguments, args[noargs-1] */RDM_SESS hSess, /* in: current session id */RM_MEMTAG mTag, /* in: memory tag for rm_ memory calls */VALUE *err) /* out: container for error messages */

{int16 stat;HSTMT hstmt;LOG_CTX *ctx = rm_getMemory(sizeof(LOG_CTX), mTag);

ctx->hSess = hSess;

SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &ctx->hEnv);SQLAllocHandle(SQL_HANDLE_DBC, ctx->hEnv, &ctx->hDbc);SQLConnectWith(ctx->hDbc, hSess);SQLAllocHandle(SQL_HANDLE_STMT, ctx->hDbc, &hstmt);if ((stat = SQLPrepare(hstmt, LogInsert, SQL_NTS)) != SQL_SUCCESS) {/* activity_log table has not been created - create it */SQLExecDirect(hstmt, LogCreateDb, SQL_NTS);SQLExecDirect(hstmt, LogCreate, SQL_NTS);SQLExecDirect(hstmt, "commit", SQL_NTS);SQLExecDirect(hstmt, LogGrant, SQL_NTS);SQLPrepare(hstmt, LogInsert, SQL_NTS);

}

SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR,SQL_CHAR, 10, 0, ctx->action, 0, NULL);

SQLBindParameter(hstmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR,SQL_CHAR, 20, 0, ctx->label, 0, NULL);

ctx->hStmt = hstmt;

*ctxp = ctx;

return SQL_SUCCESS;}

The udpExecute function for the log_login UDP called logLogin is given below. All that logLogin must do is copy "login"to the action string and place the session ID number in the label string. he procedure then calls SQLExecute to insert therow into activity_log. After this, a call is made to SQLEndTran to commit the new row to the database.

The logLogout function for the log_logout procedure is identical to logLogin except that it copies "logout"(instead of "login") to the action string.

int16 REXTERNAL logLogin(void **ctxp, /* in: proc context pointer */int16 noargs, /* in: number of arguments to procedure */VALUE *args, /* in: array of arguments */int16 mTag, /* in: memory tag for rm_ memory calls */HSTMT *phStmt, /* out: hstmt for result set */VALUE *err) /* out: container for error messages */

{LOG_CTX *ctx = *ctxp;LOG_CTX *ptr;

/* record the login */strcpy(ctx->action, "login");sprintf(ctx->label, "session %d", ctx->hSess);

Page 219: RDMs 8.4 SQL User's Guide

14. Developing SQL Server Extensions

SQL User Guide 211

SQLExecute(ctx->hStmt);SQLEndTran(SQL_HANDLE_DBC, ctx->hDbc, SQL_COMMIT);

return SQL_SUCCESS;}

Your login or logout procedure can optionally contain a cleanup function. The sample log_login and log_logout UDPsinclude the logCleanup function shown below. This function performs cleanup by freeing and disconnecting the appropriateSQL handles, and then freeing the context memory.

void REXTERNAL logCleanup(void **ctxp, /* in: statement udp_ctx pointer */RM_MEMTAG mTag) /* memory tag for rm_ memory calls */

{LOG_CTX *ctx = *ctxp;

SQLFreeHandle(SQL_HANDLE_STMT, ctx->hStmt);SQLDisconnect(ctx->hDbc);SQLFreeHandle(SQL_HANDLE_DBC, ctx->hDbc);SQLFreeHandle(SQL_HANDLE_ENV, ctx->hEnv);

rm_freeMemory(ctx, mTag);}

Only an administrative user can set up a login or logout procedure for use. The first step is to issue a create procedure state-ment similar to the following.

create procedure log_login in udp on rdsdll;create procedure log_logout in udp on rdsdll;

After the login or logout procedure is set up, the administrator must assign the procedure by using a set login proc statement,as shown below. The for clause is used to set up the login or logout procedure for use by all users or only for the list of spe-cified user identifiers. Recall that user names (ids) are case-sensitive so that "Sam" and "sam" are considered to be differentusers.

set login proc for public to log_login;set logout proc for public to log_logout;

With the login procedure (and logout procedure if applicable) assigned, the administrator can turn the registered login orlogout procedures on or off as needed. To do this, the administrator issues a set login statement with the on or off clause. Theeffect is system wide and persists until the next set login statement is issued by any administrative user. Use of login pro-cedures is initially turned off.

set login on

Now that the sample login and logout procedures are enabled, every login and logout by any user causes a row containingthe action, user ID, time, etc., to be inserted into the activity_log table. After a few log ins and log outs, the administrator canview the table by issuing the following select statement.

select * from activity_log;

Page 220: RDMs 8.4 SQL User's Guide

14. Developing SQL Server Extensions

SQL User Guide 212

To disable calls to a login or logout procedure, the administrator can issue another set login proc statement. This time, theadministrator specifies null instead of the procedure name in the to clause.

set login proc for public to null;

14.4 Transaction Triggers

A transaction trigger is a server-side function, residing in a UDP module, that RDM Server calls whenever a transaction oper-ation occurs (commit, rollback, savepoint, or rollback to a savepoint). The trigger can be registered to be called either for thenext transaction operation only or for every transaction operation. The function is activated from within a UDP by a call tothe SQL API function, SQLTransactTrigger.

14.4.1 Transaction Trigger Registration

Transaction triggers are registered through a call to SQLTransactTrigger usually issued from a login UDP. This functionis only available on the server; it cannot be called from the client side. Thus, it must be called from a UDP (C-based pro-cedure). The declaration for SQLTransactTrigger as shown below.

int16 REXTERNAL SQLTransactTrigger(HMOD hMod,RDM_SESS hSess,const char *name,PTRANSACTTRIGGER Trigger,void *ptr,int16 mode);

The SQLTransactTrigger function arguments are described in the table below.

Argument DescriptionhMod The handle that uniquely identifies the UDP module. The

hMod value is originally passed into ModInit in the UDPmodule when the module is first loaded. If you are using trans-action triggers in your UDP, you must define the ModInitfunction, so you can get the module handle and save it in aglobal variable for later use in SQLTransactTrigger.

hSess The session handle of the user activating the transaction trig-ger. This handle is an argument to the udpInit functon whichcan either itself call SQLTransactTrigger or save the sessionhandle in the UDP context so that the udpExecute functioncan call SQLTransactTrigger (see examples in this section).

name A unique name to be associated with this transaction trigger.Trigger A pointer to the transaction trigger implementation function

to be activated.ptr A pointer to any context memory needed by the transaction

trigger. Note that this memory needs to survive as long as theconnection (session) is open.

mode Informs the SQL system as to how often the transaction trig-ger is to fire on transactions issued by the connection asso-ciated with hSess.Set to SYS_COMMIT_EVERY to indicate

Page 221: RDMs 8.4 SQL User's Guide

14. Developing SQL Server Extensions

SQL User Guide 213

Argument Descriptionthat the trigger is to fire after every transaction. Set to SYS_COMMIT_ONCE to indicate that the trigger is only to fire onthe next transaction operation after which the transaction trig-ger is deactivated and a subsequent SQLTransactTrigger callis required in order to reactivate the trigger.

Once the transaction trigger is registered, the RDM Server "fires" (calls) the function any time the related connection executesa transaction commit, savepoint, or rollback. This can happen through a call to either SQLEndTran or SQLEx-tendedTransact, through a commit, mark, or rollback statement, or (if the session is in auto-commit mode) when RDMServer automatically issues a transaction commit following an insert, update, or delete.

The code below shows a version of the logLogin function that implements a transaction trigger. After recording the login inthe activity_log table, the function allocates a context of type LOG_CTX, calls SQLConnectWith to associated the invok-ing client's session handle with an SQL connection, allocates a statement handle, and compiles the LogInsert statement. Itthen calls SQLBindParameter to associate the action and label variables in the log context with LogInsert's parametermarkers.

Finally, logLogin calls SQLTransactTrigger, passing the global module handle set in the original call to ModInit. Thecall to SQLTransactTrigger also passes the session handle stored in the log_login context by logInit and names the trans-action trigger activity_log. Additionally, the call provides the address of the transaction trigger function named Trans-actTrigger, a pointer to the context for the transaction trigger function, and a constant indicating that the transaction triggerfunction is to be called on every commit, savepoint, or rollback operation.

typedef struct log_action {int16 type;char *label;struct log_action *next;struct log_action *prev;

} LOG_ACTION;

typedef struct log_ctx {HENV hEnv;HDBC hDbc;HSTMT hStmt;RDM_SESS hSess;RM_MEMTAG mTag;char action[16];char label[21];LOG_ACTION *act_list;

} LOG_CTX;.../* ======================================================================

Main for login procedure*/int16 REXTERNAL logLogin(void **ctxp, /* in: proc context pointer */int16 noargs, /* in: number of arguments to procedure */VALUE *args, /* in: array of arguments */RM_MEMTAG mTag, /* in: memory tag for rm_ memory calls */HSTMT *phStmt, /* out: hstmt for result set */VALUE *err) /* out: container for error messages */

{LOG_CTX *ctx = *ctxp;LOG_CTX *ptr;

/* record the login */

Page 222: RDMs 8.4 SQL User's Guide

14. Developing SQL Server Extensions

SQL User Guide 214

strcpy(ctx->action, "login");sprintf(ctx->label, "session %d", ctx->hSess);SQLExecute(ctx->hStmt);SQLEndTran(SQL_HANDLE_DBC, ctx->hDbc, SQL_COMMIT);

/* set the transaction trigger for this connection *//* allocate trigger's log context on global tag */ptr = rm_getMemory(sizeof(LOG_CTX), 0);SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &ptr->hEnv);SQLAllocHandle(SQL_HANDLE_DBC, ptr->hEnv, &ptr->hDbc);SQLConnectWith(ptr->hDbc, ctx->hSess);SQLAllocHandle(SQL_HANDLE_STMT, ptr->hDbc, &ptr->hStmt);SQLPrepare(ptr->hStmt, LogInsert, SQL_NTS);SQLBindParameter(ptr->hStmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR,

SQL_CHAR, 10, 0, ptr->action, 0, NULL);SQLBindParameter(ptr->hStmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR,

SQL_CHAR, 20, 0, ptr->label, 0, NULL);ptr->act_list = NULL;ptr->mTag = rm_createTag(NULL, 0, NULL, NULL, 0, RM_NOSEM);

SQLTransactTrigger(ghMod, ctx->hSess, "activity_log",TransactTrigger, ptr, SYS_COMMIT_EVERY);

return SQL_SUCCESS;}

14.4.2 Transaction Trigger Implementation

Once SQLTransactTrigger is called, the transaction trigger function will be called by RDM Server when a transactionoperation calls. The transaction trigger function is a function of type TRANSACTTRIGGER, with a prototype as shownbelow. Note that you can name this function whatever you want as it is only identified from the function pointer passed in toSQLTransactTrigger call that activated it.

void REXTERNAL TransactTrigger(int16 type,char *label,char *name,void *ptr);

Each of the arguments are described in Table 13-9 below.

Argument Descriptiontype See Table 13-10.label The transaction identifier specified with the savepoint or roll-

back.name The name of the transaction trigger being fired. Allows more

than one transaction trigger to share the same implementationfunction.

ptr A pointer to the transaction trigger context data passed in tothe call to SQLTransactTrigger which activated this trigger.

Table 13-9. Transaction Trigger Implementation Function Argument Descriptions

The table below describes each of the possible values for the type function argument.

Page 223: RDMs 8.4 SQL User's Guide

14. Developing SQL Server Extensions

SQL User Guide 215

Type DescriptionSYS_SAVEPOINT Triggered by a "savepoint label" operation.SYS_ROLLBACK Triggered by a "rollback [to savepoint label]" operation. The

label argument will be an empty string ("") to indicate that theentire transaction is rolled back.

SYS_COMMIT Triggered by a "commit" operation.SYS_REMOVE The trigger is being deleted. This happens because either the

trigger was registered to fire only once and has already fired,or was registered to fire for every transaction and the applic-ation is disconnecting from the server. This value indicatesthat the trigger should perform any necessary cleanup (e.g.,freeing the allocated memory pointed to by the ptr argument).

Table 13-10. Transaction Type Descriptions

The transaction trigger is always called AFTER the transaction operation has completed. Hence, if type isSYS_COMMIT the TransactTrigger function is called after the commit has completed writing its changesto the database.

The following example implements a transaction trigger (function TransactTrigger in udp.c) that is used in conjunction withlogin procedures and that records a log of every transaction including the user id of the user issuing the transaction and thetimestamp when it occurred. The log is maintained in the activity_log database/table which is created by the logInit functionfor the log_login UDP described earlier in section 14.3.

TransactTrigger operation depends on the value passed in through argument type. If type is SYS_REMOVE then the trans-action trigger is being deleted and the trigger function needs to clean up after itself by freeing its previously allocated handlesand memory. Note that the call to SQLDisconnect breaks the association between the connection handle (ctx->hDbc) andthe client session handle that was established by the call to SQLConnectWith that was issued by function logInit.

If type is SYS_SAVEPOINT or SYS_ROLLBACK to a previous savepoint (indicated by a its presence in the list of pre-viously issued savepoint labels stored in the ctx->act_list linked list), the transaction action is saved in the activity listto be later written to the activity_log table after the application's transaction has been committed (or rolled back). This isnecessary in order to ensure that the rows inserted into the activity_log table are not included in the application's transactionas they would get rolled back with an application's rollback leaving no log of the transaction operation.

The IsASavepoint function determines which type of rollback is in effect. When saving savepoint actions, the savepoint labelis stored in the LOG_ACTION entry. A SYS_ROLLBACK which has a label equal to one of the previously saved savepointlabels indicates that it is a rollback to savepoint operation that triggered this call to TransactTrigger in which case IsASave-point will return true. (Note that in RDM Server SQL the non-standard begin transaction statement can specify a transactionid which can be specified in a subsequent commit or rollback in which case the label passed to TransactTrigger does not cor-respond to a savepoint.)

When the type is SYS_COMMIT or SYS_ROLLBACK (not associated with a prior savepoint) then a row is inserted into theactivity_log table for each previously saved action as well as the final commit or rollback itself. As TransactTrigger is calledafter the commit or rollback has completed the rows inserted into the activity_log table will be performed in an independenttransaction which is committed by the call to SQLEndTran ad the end of the function.

/* ======================================================================Function to find savepoint label in the list*/static int16 IsASavepoint(

LOG_ACTION *lap,

Page 224: RDMs 8.4 SQL User's Guide

14. Developing SQL Server Extensions

SQL User Guide 216

char *label){

LOG_ACTION *lgp;

for (lgp = lap; lgp; lgp = lgp->next) {if (strcmp (lgp->label, label) == 0)

return 1;}

return 0;}

/* ======================================================================Transact Trigger*/static void REXTERNAL TransactTrigger (

int16 type,const char *label,const char *name,void *ptr)

{LOG_CTX *ctx = (LOG_CTX *)ptr;LOG_ACTION *lap = NULL;const LOG_ACTION *lgp;

UNREF_PARM(name);

if (type == SYS_REMOVE) {rm_freeTagMemory(ctx->mTag, 1);ctx->mTag = NULL;

SQLFreeHandle(SQL_HANDLE_STMT, ctx->hStmt);SQLDisconnect(ctx->hDbc);SQLFreeHandle(SQL_HANDLE_DBC, ctx->hDbc);SQLFreeHandle(SQL_HANDLE_ENV, ctx->hEnv);rm_freeMemory(ctx, TAG0);

}else {

if (type == SYS_SAVEPOINT ||(type == SYS_ROLLBACK && IsASavepoint(ctx->act_list, label))) {

/* save the event info, to be committed later to the log */lap = (LOG_ACTION *)rm_getMemory(sizeof(LOG_ACTION), ctx->mTag);if (lap != NULL) {

lap->type = type;lap->next = ctx->act_list;lap->prev = NULL;if (ctx->act_list) ctx->act_list->prev = lap;lap->label = (label && *label) ? rm_Strdup(label, ctx->mTag) : NULL;ctx->act_list = lap;

}}else if (type == SYS_COMMIT || type == SYS_ROLLBACK) {

/* Process stored actions (if any) */if (ctx->act_list) {

/* Find first event (it's last in the list) */for (lap = ctx->act_list; lap->next; lap = lap->next)

;for (lgp = lap; lgp; lgp = lgp->prev) {

switch (lgp->type) {case SYS_SAVEPOINT:

Page 225: RDMs 8.4 SQL User's Guide

14. Developing SQL Server Extensions

SQL User Guide 217

strcpy(ctx->action, "savepoint");break;

case SYS_ROLLBACK:strcpy(ctx->action, "rollback to savepoint");break;

default:break;

}strcpy(ctx->label, lgp->label);SQLExecute(ctx->hStmt);

}ctx->act_list = NULL;rm_freeTagMemory (ctx->mTag, 0);

}switch (type) {case SYS_COMMIT: strcpy(ctx->action, "commit"); break;case SYS_ROLLBACK: strcpy(ctx->action, "rollback"); break;default: break;}strcpy(ctx->label, label);SQLExecute(ctx->hStmt);SQLEndTran(SQL_HANDLE_DBC, ctx->hDbc, SQL_COMMIT);

}}

}

Page 226: RDMs 8.4 SQL User's Guide

15. Query Optimization

SQL User Guide 218

15. Query OptimizationThe RDM Server SQL query optimizer is designed to generate efficient execution plans for database queries. The typicalkinds of queries used in an embedded database application environment include standard join, index lookup optimizations,and grouping and sorting. Some of the more sophisticated optimization techniques (for example, support of on-line analysisprocessing) are not included in the RDM Server optimizer. However, RDM Server does provide various access methods(which support very fast access to individual rows) with the direct access capabilities of predefined joins and rowid primarykeys.

Overview of the Query Optimization Process

In SQL, queries are specified using the select statement, and many methods (or query execution plans) exist for processing aquery. The goal of the optimizer is to discover, among many possible options, which plan will execute in the shortest amountof time. The only way to guarantee a specific plan as optimal is to execute every possibility and select the fastest one. As thisdefeats the purpose of optimization, other methods must be devised.

The query optimizer must resolve two interrelated issues: how it will access each table referenced in the query, and in whatorder. To access requested rows in a table, the optimizer can choose from a variety of access methods, such as indexes or pre-defined joins. It determines the best execution plan by estimating the cost associated with each access method and by factor-ing in the constraints on these methods imposed by each possible access ordering. Note that the decisions made by theoptimizer are independent of the listed order of the tables in the from clause or the location of the expressions in the whereclause.

Consider the following example query from the sales database.

select company, ord_num, ord_date, amountfrom customer, sales_order

where customer.cust_id = sales_order.cust_id andstate = "CO" and ord_date = date "1993-04-01";

Two tables will be accessed: customer and sales_order. The first relational expression in the where clause specifies the joinpredicate, which relates the two tables based on their declared foreign and primary keys. The DDL for the sales database (filesales.sql) contains a create join called purchases on the sales_order foreign key, providing bidirectional direct accessbetween the two tables. Note that the state column in the customer table is also the first column in the cust_geo index, andthe ord_date column in the sales_order table is the first column in the order_ndx index. Thus the optimizer has choices ofwhich index to use. All possible execution plans considered by the RDM Server query optimizer for this query are listed inthe following table.

Table 15-1. Possible Execution Plans for Example Query

1. Scan customer table (that is, read all rows) to locate rows where state = "CO", then for each matching customer row,scan sales_order table to locate rows that match customer's cust_id and have ord_date = 1993-04-01.

2. Scan customer table to locate rows where state = "CO", then for each customer row, read each sales_order row thatis connected through the purchases join, and return only those that have ord_date = 1993-04-01.

3. Use the cust_geo index to find the customer rows where state = "CO", then for each customer row, scan sales_ordertable to locate rows that match customer's cust_id and have ord_date = 1993-04-01.

4. Use the cust_geo index to find the customer rows where state = "CO", then for each customer row, read each sales_order row that is connected through the purchases join, and return only those that have ord_date = 1993-04-01.

Page 227: RDMs 8.4 SQL User's Guide

15. Query Optimization

SQL User Guide 219

5. Scan sales_order table to locate rows where ord_date = 1993-04-01, then for each sales_order row, scan customertable to locate rows that match sales_order's cust_id and have state = "CO".

6. Scan sales_order table to locate rows where ord_date = 1993-04-01, then for each sales_order row, read the cus-tomer row that is connected through the purchases join, and return only those that have state = "CO".

7. Use the order_ndx index to find the sales_order rows where ord_date = 1993-04-01, then for each sales_order row,scan customer table to locate rows that match sales_order's cust_id and have state = "CO".

8. Use the order_ndx index to find the sales_order rows where ord_date = 1993-04-01, then for each sales_order row,read the customer row that is connected through the purchases join, and return only those that have state = "CO".

Because the time (based on the number of disk accesses) required to scan an entire table is generally much greater than thetime needed to locate a row through an index, plans 4 and 8 seem the best. However, it is unclear which of the two plans isoptimal. In fact, both are probably good enough to obtain acceptable performance.

Additional information to help you make the best choice includes the number of rows in each table (28 customers, 127sales_orders), the number of customers from Colorado (1), and the number of orders for April 1, 1993 (5). With this data wecan deduce that plan 4 is better than plan 8. Plan 4 requires 1 index lookup to find the one customer from Colorado (about 3reads) plus the average cost to read through an instance of the purchases set to retrieve and check the dates of the relatedsales_order records (average number of orders per customer = 127/28 = 4). Thus, plan 4 uses about 7 disk accesses. Plan 8will use the order_ndx index to find the 5 sales_order rows dated 1993-04-01 (about 8 reads) plus one additional read tofetch and check the related customer record through the purchases set (5 reads). Hence, plan 8 uses about 13 disk accesses.

Note that plans 1 and 5 perform what is called a Cartesian or cross-product—for each row of the first table accessed, all rowsof the second table are retrieved. (Thus if the first table contained 500 rows and the second table contained 1000 rows, thequery would read a total of 500,000 rows.) Cross-products are extremely inefficient and will never be considered by the optim-izer except when a necessary join predicate has been omitted from the query. In our example, this would occur if the rela-tional expression, "customer.cust_id = sales_order.cust_id" was not specified. Necessary join predicates are often erroneouslyomitted when four or more tables are listed in the from clause and/or when multi-column join predicates (for compound for-eign and primary keys) are required.

The following diagram shows the basic operational phases of the query optimization process, as illustrated by the previousexample.’

Page 228: RDMs 8.4 SQL User's Guide

15. Query Optimization

SQL User Guide 220

Figure 15-1. Query Optimization Process

Using the information in the system catalog, the select statement is parsed, validated, and represented in a set of easily pro-cessed query description tables. These tables include a tree representation of the where clause expressions (called the expres-sion tree) and information about the tables, columns, keys, indexes, and joins in the database.

The system then analyzes those tables, and constructs both the access rule table and the expression table. For the referencedtables, the analysis process uses the system catalog and the distribution statistics (collected by the update statistics statement).The access rule table contains a rule entry for each possible access method (for example, table scan or index lookup) for eachtable referenced in the from clause. The expression table has one entry for each conditional expression specified in the whereclause. These tables drive the actual optimization process.

Finally, the optimizer determines the plan with the lowest total cost. An execution plan basically consists of a series of steps(one step for each table listed in the from clause), of how the table in that particular plan step will be accessed. The possible

Page 229: RDMs 8.4 SQL User's Guide

15. Query Optimization

SQL User Guide 221

access rules that can be applied at that step are sorted by their cost so that the first candidate rule is the cheapest. The optim-izer's goal is to select one access rule for each step that minimizes the total cost of the complete execution plan. As the optim-izer iterates through the steps, the cost of the candidate plan is updated. As soon as a candidate plan's cost exceeds the cost ofthe currently best complete plan, the candidate plan is abandoned at its current step and the next rule for that step is thentested. Conditional expressions that are incorporated into the plan are deleted from the expression tree so that they are notredundantly executed.

Cost-Based Optimization

The cost to determine the execution plan is the time it takes the optimizer to find the "optimal" plan. An execution plan con-sists of n steps where n is the number of tables listed in the from clause. Each step of the plan specifies the table to beaccessed and the method to be used to access a row from that table. The cost increases factorially to the number of tables lis-ted in the from clause (n!). Performance impact is noticeable in RDM Server for queries that reference more than about 8tables. This is due to the increasing number of combinations of access orderings that must be considered (2 tables have 2 pos-sible orderings, 3 have 6, 4 have 24, etc.). The cost to estimate each candidate plan also includes a linear factor of the numberof access methods available at each step in a plan from which the optimizer must choose. More access methods means theoptimizer must do more work, but the odds of finding a good plan improve.

The cost to carry out an execution plan is the amount of I/O time required to read the database information from disk. InRDM Server, an estimate of this cost is based on an estimate of the total number of logical I/O accesses that will occur duringexecution. Because it is extremely difficult to accurately estimate the effects caused by caching performance and diverse data-base page sizes, physical I/O estimates are not possible. The logical I/O estimates are based on analysis of the logical I/O timerequired to access a record occurrence for each access method.

An heuristic optimizer selects an execution plan by using built-in heuristics or general rules about which particular accessmethod will return the fewest rows. For example, heuristic optimizers automatically assume that a "col = value" condition willrestrict the result set to fewer rows than would a "col > value" condition (which is assumed to restrict, on average, only halfthe rows). In a case where 100 rows contain "value" and zero rows contain greater than "value", this assumption breaks downand the choice would not be optimal.

A cost-based optimizer maintains the data distribution statistics it uses to more quantitatively determine the better of twoaccess methods. A histogram is maintained for the distribution of most commonly occurring data in each column. The per-centage of the file that is covered by a given inequality expression involving an indexed column (for example, "where ord_date between date '1993-02-01' and date '1993-02-28'") is interpolated from the histogram, providing a more accurate assess-ment of the better alternative than a built-in heuristic.

The statistics maintained for use by cost-based optimizers are used to: 1) guide the choice between alternative access methodsderived from the relational expressions specified in the where clause, 2) estimate the number of output rows that result fromeach plan step, and 3) estimate the number of logical I/O's incurred by each possible access method.

The statistics used by the RDM Server cost-based optimizer include:

l Number of pages in a filel Number of rows per page in a filel Number of rows in a tablel Depth of an index's B-treel Number of keys per page in an indexl Frequency distribution histogram of most commonly occurring values for each column

Page 230: RDMs 8.4 SQL User's Guide

15. Query Optimization

SQL User Guide 222

Update Statistics

The statistics are collected and stored in the SQL system catalog by executing the update statistics statement. The histogramfor each column is collected from a sampling of the data files. The other statistics are maintained by the RDM Server runtimesystem.

The histogram for each column contains a sampling of values (25 by default, controlled by the OptHistoSize configurationparameter), and a count of the number of times that value was found from the sampled number of rows (1000 by default, con-trolled by the OptSampleSize configuration parameter). The sampled values are taken from rows evenly distributed through-out the table.

When update statistics has not been performed on a database, RDM Server SQL uses default values that assume each tablecontains 1000 rows. It is highly recommended that you always execute update statistics on every production database. Theexecution time for an update statistics statement is not excessive in RDM Server and does not vary significantly with the sizeof the database. Therefore, we suggest regular executions (possibly once per week or month, or following significant changesto the database).

Restriction Factors

The histogram values are used to compute a restriction factor associated with a specified conditional expression. The restric-tion factor estimates the percentage of rows from the table that satisfy the conditional. For example, the restriction factor for abetween conditional is equal to the frequency count total of the histogram values that satisfy the conditional, divided by thetotal of all sampled histogram values.

When an equality comparison value is not found in the histogram, the restriction factor is based on the average frequencycount of the five histogram values with the lowest frequency counts. The restriction factor for join predicates is based on theaverage frequency count of all histogram values for the foreign/primary key column (this results in an estimate of the averagenumber of duplicates per value).

The histogram for the prod_id column of the item table is shown below.

Entry # PROD_ID Value # of Occurrences

0 10320 4

1 10433 12

2 11333 8

3 11433 14

4 12325 10

5 13032 9

6 14020 6

7 15200 5

8 16300 3

9 16301 11

10 16311 15

11 17110 2

12 17214 23

13 17419 12

14 19100 4

Table 15-2. Histogram for ITEM.PROD_ID

Page 231: RDMs 8.4 SQL User's Guide

15. Query Optimization

SQL User Guide 223

Entry # PROD_ID Value # of Occurrences

15 19400 5

16 20200 6

17 20308 4

18 20400 9

19 21200 6

20 21500 3

21 23100 11

22 23200 24

23 23400 26

24 24200 17

The item table has 461 rows. All rows were sampled by update stats. The table shows the histogram counts for the first 25 dis-tinct values sampled by update stats from the item table. A total of 249 rows from the item table contained one of thoseprod_id values. These values are used by the optimizer to compute restriction factors for prod_id comparisons specified in awhere clause. The following table gives the restriction factor for some example expressions.

Conditional Expression RestrictionFactor

Cardinality Estim-ate

ActualCount

prod_id = 16311 0.032538 15 15

prod_id >= 21200 0.349398 161 143

prod_id between 11433 and 20200 0.502008 231 246

prod_id = 10450 0.006941 3 7

prod_id in(15200,20200,21200,24200)

0.073753 34 34

Table 15-3. Example Restriction Factors

The restriction factor multiplied by the cardinality of the table (461) gives the cardinality estimate of the conditional expres-sion (i.e., an estimate of the number of rows from the table that satisfy the conditional). The count of the number of actualmatching rows is also listed in the table. The accuracy of the estimate is very good but that is primarily because all table rowswere sampled.

The restriction factor for the prod_id = 16311 conditional is computed from the histogram count for entry 10 of Table 15-2,divided by the total number of sampled rows (461). Thus, 15/461 = 0.032538. Note that the cardinality estimate equals thehistogram count value because all of the rows in the table were sampled, which will only be true for small tables. If therewere 1000 rows sampled from a 50,000 row table the restriction factor would have been 0.015 and the cardinality estimate0.015*50,000 = 750 rows.

The restriction factor for inequality conditions is estimated as the percentage of the histogram table that matches the con-ditional expression. Thus, the restriction factor for prod_id >= 21200 is equal to the sum of the histogram counts for theprod_id entries >= 21200 divided by the sum of all counts (249) or, 87/249 = 0.349398. Applying the same method to prod_id between 11433 and 20200 gives us 125/249 = 0.502008.

For equality comparisons with values not in the table (or when the comparison is against a parameter marker or stored pro-cedure argument) an estimate of the average number of duplicates per row is computed. The estimate is equal to the average

Page 232: RDMs 8.4 SQL User's Guide

15. Query Optimization

SQL User Guide 224

counts for the 5 least occurring values in the histogram. Thus, the restriction factor for prod_id = 10450 is estimated as((2+3+3+4+4)/5)/461 = 0.006941 (or an average of about 3 rows per value).

The restriction factor computation for the in conditional is simply the sum of the equality comparisons for each of the listedvalues. The restriction factor for prod_id in (15200,20200,21200,24200) is (5+6+6+17)/461 = 0.073753.

Table Access Methods

RDM Server provides a variety of methods for retrieving the rows in a table. Each of these access methods is described below,including how cost is estimated for each method. The cost estimates use the above statistics as represented by the followingvalues.

Value Definition

P The number of pages in the file in which the table's rows are stored.

D The depth of the B-tree index.

C The cardinality of the table being accessed (that is, the number of rows in thetable).

Cf The cardinality of the table containing the referenced foreign key.

Cp The cardinality of the table containing the referenced primary key.

K The maximum number of key values per index page.

R The restriction factor, an estimate (between 0 and 1) of the percentage of therows of the table that satisfy the conditional expression. The restriction factor isdetermined from the frequency distribution histogram and the constant valuesspecified in the conditional expression.

Table 15-4. Cost Estimate Value Definitions

Database I/O in RDM Server is performed by reading data and index file pages. A data file page contains at least one (usuallymore) table row so each physical disk read will read into the RDM Server cache that number of rows. An index file page con-tains many keys per page depending on the size of the page and the size of the index values. RDM Server uses a B-tree struc-ture for its indexes, which guarantees that each index page is at least half full. On the average, index pages are about 60-70%full. The depth of a B-tree indicates the number of index pages that must be read to locate a particular key value. Most B-treeshave a depth of from 4 to 7 levels.

Sequential File Scan

Each row of a table is stored as a record in a file. In RDM Server, a data file can contain the rows from one or more tables.The most basic access method in RDM Server is to perform a sequential scan of a file where the table's rows are retrieved bysequentially reading through the file. If the file contains rows from more than one table, only the rows from the needed tableare returned. However, all of the rows from all of the tables stored in the file will be read (the rows are intermixed). Thus, thecost (measured in logical disk accesses) to perform a sequential scan of a table is equal to the number of pages in the file:

Cost of sequential file scan = P

A sequential file scan is used in queries where the where clause contains no optimizable conditional expressions that ref-erence foreign key, primary key, or indexed columns. See the example below.

select sale_name, dob, region, office from salespersonwhere age(dob) >= 40;

Page 233: RDMs 8.4 SQL User's Guide

15. Query Optimization

SQL User Guide 225

Direct Access Retrieval

Direct access retrieval allows retrieval of an individual row based on the value of a rowed primary key. The rowid primarykey value can be specified directly in the query or may result from a join with a table containing a referencing rowid foreignkey. The cost of a direct access retrieval is 1 (since a single file read is all that is needed to retrieve the row based on itsrowid value):

Cost of direct access retrieval = 1

Consider the following table declarations:

create table pktable(pkid rowid primary key,pktext char(50)

);create table fktable(

fkid rowid references pktable,fktext char(50)

};

The optimizer produces an execution plan that uses direct access retrieval to fetch a particular row from pktable for the fol-lowing query:

select * from pktable where pkid = 10;

The execution plan for the query below consists of two steps. The first step is a sequential scan of fktable. In the second step,fkid is used to directly access the related pktable row and each fktable row.

select pkid, pktext, fktext from pktable, fktable where pkid = fkid;

Indexed Access Retrieval

Equality Conditionals

Indexed access retrieval allows retrieval of an individual row or set of matching rows, based on the value of one or morecolumns contained in a single index. These values can be specified in the query directly or through a join predicate.

For a unique index, the cost to access a single row is equal to the depth of the index's B-tree (seldom more than 4 ) + 1 (toread the row from the data file). For a non-unique index, the cost is based on an estimate of the average number of rows hav-ing the same index value derived from the indexed column's histogram. The percentage of the table's rows that match the spe-cified equality constraint is the restriction factor (R). Thus, the estimate of number of matching rows is equal to the cardinalityof the table multiplied by the restriction factor, or:

number of matching rows = C * R

The cost estimate (in logical page reads) of an indexed access retrieval is equal to the number of index pages that must beaccessed plus the number of matching rows (1 logical page read per row), or:

Cost of index access = D + (C * R)/(.7 * K) + (C * R)

Page 234: RDMs 8.4 SQL User's Guide

15. Query Optimization

SQL User Guide 226

This assumes that each index page is an average of 70% full (D = depth of B-tree, K = maximum number of keys per indexpage). Note that this formula works for both unique and non-unique indexes (for unique indexes, R = 1/C).

In the following example, the optimizer uses the order_key index on the sales_order table to retrieve the specified row.

select * from sales_order where ord_num = 2310;

In the example below, the optimizer selects indexed access retrieval to find the item rows through the item_ids index and therelated product rows through the prod_key index.

select prod_id, quantity, prod_desc from item, productwhere item.prod_id = 17214 and product.prod_id = 17214

and item.prod_id = product.prod_id;

Notice that the where clause contains a redundant expression. Including redundant expressions provides the optimizer withmore access choices. You can set a RDM Server configuration parameter called RedundantExprs to have the optimizer auto-matically add redundant expressions where appropriate, such as in the above query.

IN Conditionals

When the in operator is used, the restriction factor is equal to the sum of the equality restriction factors for each of the listedvalues. Thus, the cost is simply the sum of the costs of the individual values.

Cost of index access for: column in (v1, v2, ..., vn) = SUM(cost(column = vi)) for all i: 1..n

The optimizer will use the order_key index on the sales_order table to retrieve each of the rows specified in:

select * from sales_order where ord_num in (2210, 2215, 2242);

Index Scan

Inequality Conditionals

Indexed scans use an index to access the rows satisfying an inequality relational expression involving the major column inthe index. The estimate of the cost of an index scan is calculated exactly the same as the indexed access method. The restric-tion factor is calculated as the percentage of the column's histogram values that match the specified conditional inequality.Consider the following query:

select * from ship_log where ord_num between 2250 and 2270;

The ship_log table contains 558 rows. The optimizer computed a restriction factor of .166667, which estimates that when thebetween condition is applied, 93 rows (.166667*558) will pass. The cost to perform a sequential scan involves reading all558 rows (145 pages) and is greater than the cost to use the index (D=2, C=558, R=.166667, K=72 => cost = 95). Thus, inthis example, the optimizer will choose to use the ship_order_key index.

Cost of index scan = D + (C * R)/(.7 * K) + (C * R)

Page 235: RDMs 8.4 SQL User's Guide

15. Query Optimization

SQL User Guide 227

LIKE Conditionals

Index scans are also used to access rows satisfying like expressions that compare the major column of an index with a literalstring pattern. The restriction factor is calculated from the histogram values that match the specified pattern. If no matches arefound, it is then calculated from average of the five lowest frequency counts in the histogram.

Two types of scans are employed depending on the position of the first wild card character (for example, "%" or "_") in thepattern. If the pattern starts with a wild card character, the entire index will be scanned and each key value will be comparedwith the specified pattern. Only those keys that match will be returned. The cost of this scan is equal to the cost of readingeach index page plus the cost of reading the row associated with each matching index value as given in the following for-mula:

Cost of full index like scan = D + (C/(.7 * K)) + (R * C)

The cost typically will be much less when the pattern does not begin with a wild card. This allows the SQL system to pos-ition within the index those values having the same prefix (consisting of all characters up to the first wild card).

Cost of prefixed like index scan = D + (C * R)/(.7 * K) + (C * R)

Note that this is identical to the cost of an equality indexed access (although the restriction factor will be greater in this case).

Primary To Foreign Key Join

The use of create join on a foreign key in the DDL establishes a predefined join set relationship between the referencedtables. Related rows in the two tables are connected using direct access pointers stored and maintained in each row's physicalrecord storage. All rows from a foreign key table are linked (in a linked list) to the row from the primary key table to whichthey refer. Thus, the optimizer can generate execution plans that directly access the related foreign key table rows after havingaccessed the primary key row. This access method is only considered by the optimizer when the join predicate (which equateseach foreign key column to its corresponding primary key column) is included in the where clause. The cost of a primary toforeign key join is equal to the average number of foreign key rows for each primary key row:

Total cost = Cardinality of primary to foreign key join = Cf / Cp

Foreign To Primary Key Join

The foreign to primary key join is also made available through use of the create join. This method allows the optimizer togenerate execution plans that directly access the primary key row referenced from a previously accessed foreign key row.Again, this access method is only considered by the optimizer when the join predicate that equates each foreign key columnto its corresponding primary key column is included in the where clause.

The cost of a foreign to primary key join = 1(each foreign key row references a single primary key row)

Note that these latter two access methods are available through the presence of a join predicate in the where clause as in thefollowing example.

select sale_name, company, city, state from salesperson, customerwhere salesperson.sale_id = customer.sale_id;

The optimizer can choose to either access the salesperson table first and then the related rows using the primary to foreign keyjoin access method based on the accounts join, or it can first access the customer table and then the related salesperson rowusing the foreign to primary key join method. The method chosen will depend on the costs involved with first accessing oneor the other of the two tables.

Page 236: RDMs 8.4 SQL User's Guide

15. Query Optimization

SQL User Guide 228

Foreign Thru Indexed/Rowid Primary Key Predefined Join

This method is used to access a foreign keyed table in which the foreign key is used in an equality comparison in the whereclause and for which the primary key table is not referenced. When the foreign key has an associated create join the optim-izer can generate a plan that allows access to the matching foreign key rows through the primary key's index (or rowid). Lookat the query below:

select company, city, state from customer where sale_id = "ERW";

The optimizer accesses the matching customer rows using the index on sale_id in the salesperson table, then retrieves therelated customer rows through the accounts predefined join. This is equivalent to the following query:

select company, city, state from customer, salespersonwhere salesperson.sale_id = "ERW" and

salesperson.sale_id = customer.sale_id;

By providing access to joined foreign key tables implicitly through the referenced primary key, faster access is achieved inupdate statements where a join is not possible. See the following example.

update customer set contact = null where sale_id = "ERW";

The cost of accessing a foreign table through the primary key is equal to the cost of accessing the primary row added to thecost of accessing the related foreign key rows.

Cost of accessing primary table through index = D + 1 (only one row is located).

Cost of accessing related foreign rows = Cost of Primary to foreign key join (see above).

If the primary key is of type rowid, the cost to access the primary row is 1.

A summary of the table access methods used by the RDM Server optimizer is shown in Table 15-5.

Access Method Cost Estimate (logical I/Os)

Sequential File Scan P

Direct Access 1

Indexed Access (equality) D + ((C * R)/(.7 * K)) + (C * R)

Indexed Access (in) SUM(Indexed Access Cost(column = vi)) for all i: 1..n.

Index Scan (inequality) D + ((C * R)/(.7 * K)) + (C * R)

Index Scan (like/no prefix) D + (C/(.7 * K)) + (R * C)

Index Scan (like/with prefix) D + ((C * R)/(.7 * K)) + (C * R)

Primary to foreign key join Cf / CpForeign to primary key join 1

Foreign thru indexed primary key D + 1 + (Cf / Cp)

Foreign thru rowid primary key 1 + (Cf / Cp)

Table 15-5. Table Access Methods Cost Formulas

Page 237: RDMs 8.4 SQL User's Guide

15. Query Optimization

SQL User Guide 229

Optimizable Expressions

The RDM Server query optimizer is able to optimize a restricted set of relational expressions that are specified in the whereclause of a select statement. Simple expressions involving a comparison between a simple column and a literal constant value(or parameter marker or stored procedure argument) can be analyzed by the optimizer to determine if any access methods existthat can retrieve rows satisfying that particular conditional. Expressions for potential use by the optimizer in an executionplan are referred to as optimizable. Table 15-6 summarizes the optimizable relational expressions.

Table 15-6. Optimizable Relational Expressions

1. RowidPkCol = constant2. NdxCol1 = constant [and NdxCol2 = constant]...3. FkCol1 = constant [and FkCol2 = constant]...4. FkCol1 = PkCol1 [and FkCol2 = PkCol2]...5. NdxCol1 = Cola [and NdxCol2 = Colb]...6. NdxCol1 in (constant[, constant]...)7. NdxCol1 {> | >= | < | <=} constant8. NdxCol1 {> | >=} constant [and NdxCol1 {< | <=} constant]9. NdxCol1 between constant and constant10. NdxCol1 like "pattern"

The constant is either a literal, a parameter marker ('?'), or a stored procedure argument (if statement is contained in a storedprocedure declaration). The RowidPkCol expression corresponds to a rowid primary key column. The NdxColi's refer to thei'th declared column in a given index. The FkCol i's (PkCol i's) refer to the i'th declared column in a foreign (primary) key. Anequality comparison must be provided for all multi-column foreign and primary key columns in order for the optimizer torecognize a join predicate. Cola, Colb, etc., are columns from the same table that match (in type and length) NdxCol1 ,NdxCol2, etc., respectively.

These expressions are all written in the following form: ColumnName relop expression. Note that expressions of the form:expression relop ColumnName are recognized and reformed by the optimizer so that the ColumnName is always listed on theleft hand side. This transformation may require modification of the relational operator. For example,

select ... from ... where 1000 > colname

would become

select ... from ... where colname < 1000

Depending on how the where clause is organized, an expression may or may not be optimizable. Conditional expressionscomposed in conjunctive normal form are optimizable. In conjunctive normal form, the where clause is constructed as fol-lows:

C1 and C2 and ... Cn

Each Ci is a conditional expression comprised of a single or multiple or'ed relational comparison. Only those Ci 's that consistof a single optimizable relational expression are optimizable. In other words, relational expressions that are sub-branches of anor'ed conditional expression are not optimizable. The best possible optimization results are obtained when the desired con-ditions use and. Some or expressions can be rewritten in a form the optimizer can process. For example, because of the orexpression in the following query, the optimizer will not use an index on the state column in the customer table.

Page 238: RDMs 8.4 SQL User's Guide

15. Query Optimization

SQL User Guide 230

select ... from customerwhere state = "CA" or state = "WA" or state = "AZ" or state = "OR";

However, for the equivalent query shown below, the optimizer would use the index on state.

select ... from customer where state in ("CA", "WA", "AZ", "OR");

Examples

These examples are all based on the example sales and invntory databases. Refer to the sales.sql and invntory.sql DDL filesfor relevant declarations of the entities referenced below.

The following select statement will locate the salesperson record for a particular salesperson ID code using the sale_keyindex.

select * from salesperson where sale_id = "GAP";

The optimizer will use the accounts predefined join to optimize the join predicate in the query below.

select * from salesperson, customerwhere salesperson.sale_id = customer.sale_id;

In the next example, those customers serviced by a specific salesperson would be accessed through the accounts predefinedjoin after locating the specified salesperson's row through the sale_key index.

select * from salesperson, customerwhere salesperson.sale_id = customer.sale_id

and sale_id = "GAP";

Note that the optimizer would not use the comments join in the following example.

select note_id, note_date, txtln from note, note_linewhere note.note_id = note_line.note_id;

The comments join cannot be used because only one of the three foreign and primary key columns from the join are specifiedin the where clause. The note_id column is the second column in the note_key index, thus the note_key index cannot beused either. Therefore, the optimizer has no good choices for resolving this query. Thus the query will be processed with thecandidate rows coming from a cross-product between the two tables and the result rows from those that have matching note_id values. This result is not what the user wants.

Note that the query below will produce (efficiently) the result set the user wants.

select sale_id, note_id, note_date, txtln from note, note_linewhere note.sale_id = note_line.sale_id

and note.note_id = note_line.note_idand note.note_date = note_line.note_date;

Page 239: RDMs 8.4 SQL User's Guide

15. Query Optimization

SQL User Guide 231

In all of the following queries, the order_ndx index on sales_order is selected by the optimizer to access the rows that sat-isfy the specified condition.

select * from sales_order where ord_date = date '1993-02-12';select * from sales_order where ord_date > date '1993-03-31';select * from sales_order

where ord_date >= date '1993-04-01' and ord_date < date '1993-05-01';select * from sales_order

where ord_date in (@'1993-01-02',@'1993-02-03',@'1993-03-04');select * from sales_order

where ord_date between date '1993-06-01' and date '1993-06-30';

In the following query, the optimizer cannot use either of the relational expressions specified.

select cust_id, ord_num, ord_date, amount from sales_orderwhere ord_num = 2293 or ord_date = date '1993-06-18';

The or expression prohibits the use of either index, because using the index on ord_num would not retrieve those rows thathave the specified ord_date, and vice-versa. In this case, the optimizer would select an access method that retrieves all rowsin the table (using file scan or a complete index scan). This query is best performed using a separate query for each part. Ingeneral, when the table is large, a temporary table can be used as shown below.

create temporary table torders(cid char(4),onum smallint,odate date,oamt double

);insert into torders

select cust_id, ord_num, ord_date, amount from sales_orderwhere ord_num = 2293;

insert into tordersselect cust_id, ord_num, ord_date, amount from sales_order

where ord_date = date '1993-06-18';select distinct * from torders;

Pattern matching using the SQL like operator can be optimized by using an index on the character column, provided thecolumn is the first (or only) declared column in an index, and the pattern is a string literal in which the first character is not awild-card character. For example, the index on cust_id is used in the following query to quickly select only those customerrows that begin with the letter "S".

select * from customer where cust_id like "S%";

If the query is written differently, as shown below, all of the cust_id values in the index will be checked to find customerrows with a cust_id ending with the letter "S".

select * from customer where cust_id like "%S";

The conditional is tested using the value from the index before the row is read so that if it does not match, there is no cost ofreading the row from the data file.

Page 240: RDMs 8.4 SQL User's Guide

15. Query Optimization

SQL User Guide 232

How the Optimizer Determines the Access Plan

Selecting Among Alternative Access Methods

Consider the following query.

select * from ship_logwhere ord_num = 2284 and prod_id = 23400;

The optimizer can choose to use either the index on ord_num or the index on prod_id to process this query. It will select theindex that it determines will execute the query in the fewest disk accesses. It estimates the required disk accesses using thedata usage statistics accumulated and stored in the system catalog by the update statistics statement. The following tableshows the relevant statistics and calculations used by the optimizer for each of the two relational expressions in the abovequery.

optimizer statistic/calculation ord_num = 2284 prod_id = 23400

number of rows in table (C) 558 558

depth of B-tree (D) 2 2

number of keys per page (K) 72 72

restriction factor (R) 0.003584 0.046595

estimate of number of result rows (C * R) 2 26

cost estimate 4 28

Table 15-7. Optimizer Statistics Example #1

The estimate of the number of rows that match each of the expressions is based on the operation (in this case "=") and thecount of histogram matches. If the histogram count is zero, the number of rows to be returned by an equality condition isequal to the average frequency count of the five lowest histogram entries divided by the cardinality of the table. In thisexample, for the "ord_num = 2284" condition, 2284 is not in the histogram. The average of the 5 lowest frequency countswas 2. Thus, the restriction factor is 2/558 or 0.003584. For the "prod_id = 23400" condition, value 23400 is in the histogramwith a frequency count of 26. The restriction factor is, therefore, 26/558 or 0.046595. The optimizer will choose the ord_numindex because of its lower cost estimate (use the formula from Table 15-5 under "Indexed Access (equality)" to calculate thecosts).

Selecting the Access Order

When a query references more than one table, the optimization process becomes more complex, because the optimizer mustchoose between different methods to access each table, and the order in which to access them. Many access methods rely onlyon the values specified in the conditional expression for the needed data. However, some access methods (those associatedwith join predicates) require that other tables have already been accessed. This places constraints on the possible orderings.Access methods available at the first step in the plan are those that do not depend on any other tables.

For possible access methods at the first plan step, the optimizer chooses the method with the lowest cost from a list of pos-sible methods sorted by cost. The accessed table is then marked as bound. The access methods available at the next step inthe plan include the choices from the first step for the other tables, plus those methods that depend on the table bound by thefirst step. These too are ordered by cost. The optimizer continues in this manner until methods have been chosen for all stepsin the plan. It then selects the method with the next highest cost and recursively evaluates a new plan. At any point in the

Page 241: RDMs 8.4 SQL User's Guide

15. Query Optimization

SQL User Guide 233

process, if the plan being evaluated exceeds the total cost of the current best complete plan, that plan is abandoned andanother is chosen. The entire optimizer algorithm is depicted in Figure 15-2 below.

Figure 15-2. Optimizer Algorithm

Page 242: RDMs 8.4 SQL User's Guide

15. Query Optimization

SQL User Guide 234

Sorting and Grouping

For select statements that include a group by or order by specification, the SQL optimizer performs two separate optimizationpasses. The first pass restricts the choice of usable access methods to only those that produce or maintain the specified order-ing. For example, an index scan retrieves its results in the order specified in the create index declaration. If the results matchthe specified ordering, they are included as a usable access method. This optimization pass is fast because, typically, very fewplans produce the desired ordering without performing an external sort of the result set. Note that ordering clauses can be sat-isfied through the use of indexes and predefined sorted joins (that is, create join with order sorted).

If a plan is produced by the first pass, it is saved (along with its cost estimate), and a second optimization is performedwithout the ordering restriction. An estimate of the cost required to sort the result set, based on the optimizer's estimate of theresult set's size, is added to the cost of the plan produced by the unrestricted pass. The optimizer will then choose the planwith the lowest cost.

The estimate of the sort cost is based on the optimizer's cardinality estimate, the length of the sort key, and the sort indexpage size. The optimizer will calculate the number of I/Os as two times the number of index pages to store the sort index (onepass to create the page and another to read each page in order) and add the number of result rows.

Note that if both the group by and order by clauses are specified, only the group by ordering can be satisfied by existingindexes and joins. A separate sort of the result set will always be required for the order by clause. If there is no index to sat-isfy the specified group by, then two sort passes will be needed. For example, consider the following query on the ship_logtable.

select * from ship_log where ord_num = 2269 order by prod_id;

The table below shows optimizer information on the two optimizer passes for the above query. Pass 1 requires use of theship_prod_key index because it is the only method available that returns rows in the specified order. Pass 2 is free to chooseany access method. The cost difference between these choices is large and the optimizer is correct to choose the plan pro-duced by pass 2, even though it will have to perform a sort on the result rows.

Optimizer Statistic/Calculation Pass 1 Pass 2

Number of rows in table (C) 558 558

Depth of B-tree (D) 2 2

Number of keys per page (K) 72 72

Restriction factor (R) 1.0 (prod_id is not used inwhere)

0.017921

Estimate of number of result rows (C *R)

558 10

Cost estimate 674 38 (includes sortcost)

Table 15-8. Optimizer Statistics Example #2

Unfortunately, the sort cost estimate can be inaccurate, because it is based on a cardinality estimate derived from database-wide data distribution statistics that will not hold for some individual cases. RDM Server provides a configuration parameter,SortLimit, that can influence the sort decision. For cardinality estimates greater than the specified SortLimit number, the optim-izer will always choose to use the restricted ordering plan rather than incur the cost of the sort. If SortLimit is zero or the car-dinality estimate is less than SortLimit, the optimizer's choice is based on its computed cost estimates. Unless you observemany instances where sorts are being performed when they should not be (or vice versa), it would be best to leave SortLimitset to zero.

Page 243: RDMs 8.4 SQL User's Guide

15. Query Optimization

SQL User Guide 235

A user can also force the optimizer to always select the restricted ordering plan by specifying nosort at the end of the orderby or group by clause. Thus, if a restricted order plan exists and nosort is specified, that plan will be executed. If not, anexternal sort of the result set will still be performed.

The optimizer will only consider orderings involving the actual columns where the sort clauses are declared in the createindex or create join statements. The optimizer does not deduce additional ordering from the presence of join predicates in thewhere clause. For example, consider the following schema fragment.

create table A(a_pk integer primary key,...

);create table B(

b_pk integer primary key,b_fk integer references A,...

);create join A_to_B order last on B(b_fk);create table C(

c_fk references B,c_date date...

);create join B_to_C order sorted on C(c_fk) by c_date;

The optimizer will recognize the join ordering to resolve the following query without performing an external sort:

select * from A,B,C where a_pk = b_fk and b_pk = c_fkorder by b_pk, c_date;

However, in the next query, the optimizer would perform an external sort even though it is possible to deduce from the joinpredicates that the sort is unnecessary.

select * from A,B,C where a_pk = b_fk and b_pk = c_fkorder by a_pk, b_pk, c_date;

The optimizer looks ahead of the sort field in the order by clause for use of the primary key column from the referenced tableof a sorted join; it thus recognizes the order produced by the join access rule. To ensure that the predefined join preserves thesort order imposed by the columns preceding b_pk in the order by clause, the optimizer must know that those columns areunique. Thus, we derive the following two guidelines:

1. To use sorted joins, always include the referenced table's primary key column(s) prior to the sort columns in the orderby clause.

2. Do not assume that the optimizer is smarter than you.

Outer Join Processing

The optimizer processes outer joins by forcing all outer joins into left outer joins (right outer joins are converted into leftouter joins by simply reversing the order). It then will disable all access paths that require the right hand table to be accessedbefore the left hand table. If there is no access path (that is, through an index or predefined join) from the left hand table tothe right hand table, the optimizer will simply perform an inner join (rather than doing a very expensive cross-product).

Page 244: RDMs 8.4 SQL User's Guide

15. Query Optimization

SQL User Guide 236

Returning the Number of Rows in a Table

The row counts for each table in a database are maintained by the RDM Server engine. SQL recognizes queries of the fol-lowing form:

select count(*) from tablename

SQL also generates a special execution plan that returns the current row count value for the specified table. No table or indexscan is needed. However, if the query is specified as shown in the next box below, the optimizer performs a scan of the tableor index (if colname is indexed) and counts the rows.

select count(columnname) from tablename

Thus, if you need the row count of the entire table, use the first form and not the second. However, note that the row countreturned from the first form includes uncommitted rows that have been inserted by another user. The second form counts onlythe currently committed rows.

Select * From Table

ANSI standard SQL states that when an order by clause is not specified, the ordering of the result rows from a table is imple-mentation dependent. Some notable ODBC-based front-end application development and report writer tools assume that a"select * from table" returns the rows in primary key order. To work effectively with these products, RDM Server SQL willreturn the rows in primary key order (or in the order defined by the first unique index on the table if there is no primary key).

Query Construction Guidelines

Some systems perform a great deal of work to convert poorly written queries into well written queries before submitting thequery to the optimizer. This is particularly useful in systems where ad hoc querying (such as in decision-support envir-onments) is performed by non-technical people. SQL is less user friendly, so often this work is performed by front-end tools.RDM Server does not perform complex query transformation analysis (it will do simple things such as converting expressionslike "10 = quantity" into "quantity = 10"). Therefore, a thorough understanding of the information provided here will assistyou in formulating queries that can be optimized efficiently by RDM Server SQL. Guidelines for writing efficient RDMServer SQL queries are listed below.

Formulate where clauses in conjunctive normal form. Avoid using or.

Formulate conditional expressions according to the forms listed in Table 15-4. Use literal constants as often aspossible. The compile-time for most queries is insignificant compared to their execution time. Thus, dynam-ically constructing and compiling queries containing literal constants (as opposed to parameter markers orstored procedures) will allow the optimizer to make more intelligent access choices based on the histogram stat-istics.

Include more (not fewer) conditional expressions in the where clause, and include redundant expressions. Forexample, foreign and primary keys exist between tables A and B, B and C, and A and C. Even though it is notstrictly necessary (mathematically) to include a join predicate between A and C, doing so provides the optim-izer with additional access path choices. Also, assuming that join predicates exist and a simple conditional isspecified for the primary key, you can include the same conditional on the foreign key as well. Look at the fol-lowing query:

Page 245: RDMs 8.4 SQL User's Guide

15. Query Optimization

SQL User Guide 237

select ... from A,B where A.pkey = B.fkey and A.pkey = 1000

You can improve this query by adding the conditional shown in an equivalent version below.

select ... from A,B where A.pkey = B.fkey and A.pkey = 1000 and B.fkey = 1000

Make certain join predicates exist for all pairs of referenced tables that are related through foreign and primarykeys.

Avoid sorting queries with large result sets in which no index is available to produce the desired ordering. Ifyou have heavy report writing requirements, consider using the replication feature to maintain a redundant,read-only copy of the database on a separate server and run your reports from that. This will allow the onlineserver to provide the best response to update requests without blocking or being blocked by a high level ofquery activity.

In defining your DDL, use create join where you would otherwise (that is, using other SQL systems), for per-formance reasons, create an index on a foreign key.

Do not include conditional expressions in the having clause that belong in the where clause. Conditionalexpressions contained in the having clause should always include an aggregate function reference. Note thatexpressions in the having clause are not taken into consideration by the optimizer.

Execute update statistics on your database(s) whenever changes have occurred which could have a significanteffect on the distribution characteristics of the data. When in doubt, run update stats.

User Control Over Optimizer Behavior

User-Specified Expression Restriction Factor

The restriction factor is the fraction of a table between 0 and 1 that is returned as a result of the application of a specificwhere condition. The lower the value, the greater the likelihood that the access method associated with that condition will bechosen by the optimizer. This factor is computed by the RDM Server optimizer from the data distribution statistics. Note thatyou can override the optimizer's estimate by using a non-standard RDM Server SQL feature. A relational expression, relexpr,can be written as "(relexpr, factor)", where factor is a decimal fraction between 0 and 1 indicating the percentage of the filerestricted by relexpr. In the example below, where the optimizer would normally access the data using the index on ord_num,the user-specified restriction factor causes the optimizer to use instead the index on ord_date.

select * from sales_order where (ord_date > date '1996-05-20',0.00001)and (ord_num = 2210, 1.0);

When statistics used by the optimizer are not accurate enough for a given query and the result is unsatisfactory, you can usethis feature to override the stats-based restriction factor and substitute your own value. However, your use of this featurerenders the query independent of future changes to the data distribution statistics.

User-Specified Index

If a column referenced in an optimizable conditional expression is used in more than one index, the optimizer will generatean access rule for each index and select the index that it sees as the best choice. If the optimizer makes a poor choice, you can

Page 246: RDMs 8.4 SQL User's Guide

15. Query Optimization

SQL User Guide 238

force its choice by specifying the index name in the column reference using colname@index_name syntax. This is illustratedin the following example from the diskdir database.

select * from filetab where size@sizendx >= 100000;

Besides the sizendx index, the optimizer could have chosen to use sizenmndx or sizedatendx. By specifying the index namewith the column name in the conditional expression, the optimizer will only consider use of that particular index. Be certainyou know exactly what you are doing when you use this feature (as well as the one from the last section).

Optimizer Iteration Threshold (OptLimit)

The time required by the optimizer to determine the optimal execution plan for a query increases factorially with the numberof tables referenced in the from clause. Thus, the time to compile and optimize a query can become noticeable when there aremany (> 8-10) referenced tables. The algorithm used by RDM Server SQL will often (but not always) determine the bestaccess plan (or a reasonably good one) early in the optimization phase.

The optimizer algorithm includes a failure-to-improve threshold limit based on the number of access plan step iterations.When the algorithm fails to generate a better access plan within the specified limit, the optimizer stops and uses the best planfound up to that point. The number of iterations that the algorithm processes depends on the number of tables being accessedand the number of usable access methods that can be chosen. The OptLimit configuration parameter can be used to specifythis failure-to improve value. When set, the optimizer will stop prematurely, after executing OptLimit number of steps, eventhough a better plan than the current best plan has not been found. (This is similar to how chess programs work in timedgames.)

We recommend that you keep this parameter disabled (OptLimit = 0) unless you have an ongoing need to dynamically com-pile complex queries in which the optimization time degrades overall system performance. If you need to specify OptLimit,the value is the number of optimizer iterations (i.e., candidate execution plan steps). You will typically choose a value greaterthan 1,000 and less than 10,000. The higher the number, the longer the optimizer will take, but the better the likelihood offinding the best plan.

An administrator can use the set opt_limit SQL statement to change the value for a particular session. The OptLimit con-figuration parameter sets the limit for all sessions. All configuration parameters in rdmserver.ini are read only at initial serverstartup.

Enabling Automatic Insertion of Redundant Conditionals

A configuration parameter named RedundantExprs can be defined in rdmserver.ini (RedundantExprs=1) that allows theoptimizer to include redundant expressions involving foreign and primary key columns involved in a join predicate.

Checking Optimizer Results

Retrieving the Execution Plan (SQLShowPlan)

You can view the execution plan by calling the SQLShowPlan function from your C/C++ application program. You canalso view the plan from the rsql utility program. SQLShowPlan returns a result set containing one row for each step in theexecution plan (one step per table listed in the from clause). This result set returns columns as shown in Table 15-9.

Page 247: RDMs 8.4 SQL User's Guide

15. Query Optimization

SQL User Guide 239

Column Name DescriptionSTEP_NUMBER The step number in the execution plan.

The first row is step 1, second is step 2, etc,DB_NAME The name of the database in which the table is defined.TABLE_NAME The name of the base table being accessed.ACCESS_METHOD The method by which the table is accessed (see below).ACCESS_NAME The name of the index or predefined join used by the access method, if applicable.STEP_CARDINALITY Estimate of the number of rows returned from this step.PLAN_CARDINALITY Estimate of the total number of rows returned by the query.PLAN_COST Estimate of the total cost (in logical I/Os) to execute the query.SORT_LEN Length of the sort record for the order by clause (0 => no sort required).GROUP_LEN Length of the sort record for the group by clause (0 => no sort required)

Table 15-9. SQLShowPlan Result Set Definition

The last four columns return the same values for each row in the result set. The access method is identified using the names inTable 15-10 below.

Name Used in SQLShowPlan Access MethodTABLE SCAN Sequential file scanDIRECT Direct accessINDEX FIND Indexed access (equality)INDEX LIST Indexed access (in)INDEX SCAN Index scan (inequality)INDEX LIKE Index scan (like)P-TO-F JOIN Primary to foreign key joinF-TO-P JOIN Foreign to primary key joinJOINED INDEX Foreign thru indexed primary keyJOINED DIRECT Foreign thru rowid primary key

Table 15-10. SQLShowPlan Access Methods

SQLShowPlan is called with two statement handles. The first is the statement handle into which the execution plan resultset will be returned. The second statement handle is for the statement whose execution plan will be retrieved. This secondhandle must be at least in the prepared state (that is, the statement must have already been compiled using SQLPrepare orSQLExecDirect). The prototype for SQLShowPlan is given below.

RETCODE SQLShowPlan(HSTMT thisHstmt, //in: handle for SQLShowPlan result setHSTMT thatHstmt) //in: handle of statement whose plan is to be fetched

SQLShowPlan will return an error if thisHstmt is not in a compatible state with SQLExecDirect (SQLShowPlan callsSQLExecDirect). An error will also be returned if thatStmt is not a prepared or executed select, update, or delete state-ment.

You can view a statement's execution plan from rsql using the ".X" command. You must execute this command under a sep-arate statement handle from the one whose execution plan you are interested. The following "select office, count" exampleillustrates the use of the command.

Page 248: RDMs 8.4 SQL User's Guide

15. Query Optimization

SQL User Guide 240

002 rsql: select office, count(*) from salesperson, customer, sales_order+ 002 rsql: where salesperson.sale_id = customer.sale_id+ 002 rsql: and customer.cust_id = sales_order.cust_id+ 002 rsql: and state in ("AZ","CA",'CO','WA','TX') group by 1 order by 2 desc;

OFFICE COUNT(*)LAX 15DEN 12SEA 9DAL 6

004 rsql: .h 2*** using statement handle 2 of connection 1004 rsql: .t*** table mode is off004 rsql: .X 1STEP_NUMBER : 1DB_NAME : SALESTABLE_NAME : CUSTOMERACCESS_METHOD : INDEX LISTACCESS_NAME : CUST_GEOSTEP_CARDINALITY: 9PLAN_CARDINALITY: 40.000000PLAN_COST : 54.000000SORT_LEN : 9GROUP_LEN : 24004 rsql: .nSTEP_NUMBER : 2DB_NAME : SALESTABLE_NAME : SALESPERSONACCESS_METHOD : F-TO-P JOINACCESS_NAME : ACCOUNTSSTEP_CARDINALITY: 9PLAN_CARDINALITY: 40.000000PLAN_COST : 54.000000SORT_LEN : 9GROUP_LEN : 24004 rsql: .nSTEP_NUMBER : 3DB_NAME : SALESTABLE_NAME : SALES_ORDERACCESS_METHOD : P-TO-F JOINACCESS_NAME : PURCHASESSTEP_CARDINALITY: 40PLAN_CARDINALITY: 40.000000PLAN_COST : 54.000000SORT_LEN : 9GROUP_LEN : 24004 rsql: .n*** no more rows

Using the SqlDebug Configuration Parameter

The ENVIRONMENT section of rdmserver.ini contains a parameter called SqlDebug. This parameter has been implementedfor internal use by Raima, but it can also be used by an SQL developer to discover the execution plan choices made by theRDM Server optimizer. Use this method only when you need more information than that provided by SQLShowPlan.

Page 249: RDMs 8.4 SQL User's Guide

15. Query Optimization

SQL User Guide 241

WARNING: Enabling the SqlDebug parameter will cause many debug files to be created which can quicklyconsume disk space. Do not enable this parameter on a production server; it should be used strictly in a testenvironment with one user only.

Debug information for each query is written into a separate debug text file in the current directory within which the RDMServer is executing. The files are named debug.nnn where nnn is 000 for the first file, 001 for the second, and so forth.

The SqlDebug parameter is a bit mapped value in which each bit setting controls the output of certain SQL internal tables.RDM Server SQL maintains the debug settings shown in the Table 15-11 below. When more than one bit setting is specified,the output for each is written into a separate debug file.

SqlDebug Debug File Output (setting 2 is currently unused)1 Formatted dump of compiled statement4 Formatted dump of query optimizer tables and execution plan8 Formatted dump of query execution plan5 1 and 49 1 and 812 4 and 813 1, 4 and 8

Table 15-11. SqlDebug Parameter Values

Setting 1 is of no interest with respect to query optimization. Setting 8 provides basically the same information asSQLShowPlan. Setting 4 will produce a formatted dump of the internal tables that drive the optimizer's analysis.

Included at the beginning of the debug file is a copy of the SQL select statement being optimized. A sample for the "selectoffice, count" query in Retrieving the Execution Plan section is shown below.

select office, count(*) from salesperson, customer, sales_orderwhere salesperson.sale_id = customer.sale_idand customer.cust_id = sales_order.cust_idand state in ("AZ","CA",'CO','WA','TX') group by 1 order by 2 desc;

----------------------------------------------------------------------------------

FROM Table:# name tableid viewid # rows step #-- ---------------------- ------- ------- -------- ------0 SALESPERSON 36 0 14 11 CUSTOMER 37 0 28 02 SALES_ORDER 38 0 127 2

Referenced Column Table:# name tableno colno accmap ndxname-- --------------- ------- ----- ------ -------0 OFFICE 0 6 0x00101 SALE_ID 0 1 0x00042 SALE_ID 1 8 0x00023 CUST_ID 1 1 0x00044 CUST_ID 2 1 0x00025 STATE 1 6 0x0004

Access Table:-----ref'd columns----- binds

# type tableno/name id 1 2 3 4 5 6 7 8 table updated

Page 250: RDMs 8.4 SQL User's Guide

15. Query Optimization

SQL User Guide 242

-- ---- ------------------- ----- ----------------------- ----- -------0 i 0/SALESPERSON 0 1 -1 -1 -1 -1 -1 -1 -1 no no1 i 0/SALESPERSON 1 -1 0 -1 -1 -1 -1 -1 -1 no no2 i 1/CUSTOMER 0 3 -1 -1 -1 -1 -1 -1 -1 no no3 i 1/CUSTOMER 1 5 -1 -1 -1 -1 -1 -1 -1 no no4 j 1/CUSTOMER 0 2 -1 -1 -1 -1 -1 -1 -1 no no5 j 2/SALES_ORDER 0 4 -1 -1 -1 -1 -1 -1 -1 no no

Expression Table:# optable col0 emult0 col1 emult1 join operation-- ------- ---- --------- ---- --------- ---- ---------0 2 1 0.071429 2 0.035714 yes eq1 2 3 0.035714 4 0.036220 yes eq2 2 5 0.321429 -1 0.000000 no in

Rule Table:binds uses binds uses sort

# method tab # tab # # rows cost id expr #s col #s col #s col #s-- ----------- ----- ----- -------- ------ ----- -------- ------- ------- -------0 FILE SCAN 0 -1 14.00 145 -11 FILE SCAN 1 -1 28.00 145 -12 FILE SCAN 2 -1 127.00 145 -13 INDEX SCAN 0 -1 14.00 15 04 INDEX SCAN 0 -1 14.00 15 15 INDEX SCAN 1 -1 28.00 29 26 INDEX SCAN 1 -1 28.00 29 37 INDEX FIND 0 1 1.00 2 0 0 1 28 INDEX FIND 1 2 1.01 2 2 1 3 49 INDEX LIST 1 -1 9.00 9 3 2 510 JOIN OWNER 1 2 1.00 1 20002 1 3 411 JOIN MEMBER 2 1 4.54 4 20002 1 4 312 JOIN OWNER 0 1 1.00 1 20001 0 1 213 JOIN MEMBER 1 0 2.00 2 20001 0 2 1

----------------------------------------------------------------------------------

Best Access Plan: cost = 54 i/o's, cardinality estimate = 40 rows

step rule # cost rows in rows out---- ------ --------- --------- ---------

0 9 9 9 91 12 9 9 92 11 36 40 40

Number of optimizer iterations: 15

Table 15-12 below lists the tables referenced in the from clause of the statement.

Column Heading Description# Index into the FROM table; referred to in other tables as the

"tableno".name The table name, view name, or correlation (alias) name, if specified.tableid SQL's permanent ID number for the table as assigned in the system

catalog.viewed SQL's permanent ID number for the view as assigned in the system

catalog.# rows The cardinality of the table when the statement was compiled.

Table 15-12. FROM Table

Page 251: RDMs 8.4 SQL User's Guide

15. Query Optimization

SQL User Guide 243

Column Heading Descriptionstep # Identifies the step number in the best plan where this table is

accessed.

Table 15-13 contains one entry for each column that is referenced in the statement.

Column Heading Description# Referenced column number; used in the other tables to identify a

specific column.name The base column name (not the alias, if an alias was specified).tableno FROM table index of the table where this column is declared.colon The column declaration number from its table (1 = first declared

column in table).accmap The column access type bit map (see Table 15-14 below).ndxname Identifies a user-specified index name.

Table 15-13. Referenced Column Table

Bit Map Description0x0001 Column is a rowid primary key (direct access)0x0002 Column is the major (first) column in a joined foreign key0x0004 Column is the major (first) column in an index0x0008 Column is a minor (not the first) column in a joined foreign key0x0010 Column is a minor (not the first) column in an index

Table 15-14. Access Type Bit Map Values

Table 15-15 contains information about the indexes and joins that potentially can be used by the optimizer.

Column Heading Description# Index number into this table; referenced in the "id" column of the

Rule Table.type Access type: "i" for an index, "j" for a predefined join.tableno/name The FROM table number and name of accessed table.id The index, foreign key, or primary/unique key entry for the accessed

table (this value is used to index into internal tables attached to thetable definition).

ref'd columns Identifies the columns (up to 8) in the index or foreign key that arereferenced in the statement. The non-negative values are indexesinto the Referenced Column Table. A -1 indicates an undeclared orunreferenced column.

binds table Indicates whether all of the columns from the table that are ref-erenced in the query are contained in the index. If yes, SQL will nothave to read the row from the data file but can retrieve all thecolumn values from the index key value.

updated Only used on update statements and indicates if one of the columnsin the index is being modified in the statement.

Table 15-15. Access Table

The "Expression Table" has one entry for each optimizable conditional expression.

Page 252: RDMs 8.4 SQL User's Guide

15. Query Optimization

SQL User Guide 244

Column Heading Descripiton# Index number into this table; it is referenced in the "expr #s" column

of the Rule Table.optable A value of 2 indicates that at least one efficient access method is

associated with the expression. A value of 1 indicates that no effi-cient access methods exist for rows that satisfy the condition to beefficiently retrieved.

col0 The Referenced Column Table entry corresponding to the (left-hand)column referenced in the conditional.

emult0 The restriction factor multiplier value associated with the col0expression.

col1 The Referenced Column Table entry corresponding to the (right-hand) column referenced in a join condition.

emult1 The restriction factor multiplier value associated with the col1 joincondition. Two restriction factors are needed depending on whichtable is being accessed.

join Indicates if the expression is a join condition (that is, col0 = col1).operation The relational operator specified in the expression.

Table 15-16. Expression Table

The heart of the optimization analysis is driven by the Rule Table (Table 15-17).

Column Heading Description# Index number into this table; it is referenced in the "rule #" column

of the Best Access Plan.method The access method associated with this rule. See Table 15-10 for a

list of the access methods.binds tab # The tableno of the table being accessed by that method. A table

becomes "bound" at that step in the access plan where the rule isapplied. Prior to that step, the table is unbound.

uses tab # The tableno of the table that contains column values needed by thisrule's access method. A -1 value means that the rule does not dependon values from any other table. Rules that rely on values fromanother table (through join predicates) can only be used in plansteps that follow the rule that accesses the used table.

# rows The optimizer's estimate of the number of rows from the table thatwill be returned by the rule. When a table depends on another tablehaving first been bound (that is, "uses tab #" != -1), then the "#rows" is the average number returned for each row of the dependenttable.

cost Estimate of the number of logical disk reads required for each applic-ation of the rule based on the formulas given in Table 15-5.

id The Access Table entry that contributed to this rule or the internaljoin identifier (i.e., a core-level d_ API set id constant). A -1 valueindicates that it is unused.

expr #s List of Expression Table entries that contributed to this rule.binds col #s The Referenced Columns (from the "binds tab #" table) that are

accessed by the rule.

Table 15-17. Rule Table

Page 253: RDMs 8.4 SQL User's Guide

15. Query Optimization

SQL User Guide 245

Column Heading Descriptionuses cols #s The Referenced Columns (from the "uses tab #" table) that are used

by the rule.sort col #s The Referenced Columns that specify the sort order in which the

rule returns its rows. A negative value indicates that this column isreturned in descending order (formed as -colno - 1).

A summary of the optimizer's results follow in Table 15-18. The cost and cardinality estimate of the Best Access Plan arereported and followed by the plan itself. The plan lists the steps in the order of their execution.

Column Heading Descriptionstep Access plan step number. The steps are executed in this order.rule # The Rule Table entry for the rule that the optimizer selected for this

step.cost The cost to apply the rule at this step in the plan is equal to the

prior step'srows out times the rule's cost from the Rule Table. For step 0, the cost is the

rule's cost.rows in The number of rows from the prior step that invoke an application

of the rule in this step. It is equal to the prior step's rows out timesthe "# rows" value from the Rule Table for the rule being applied atthis step. For step 0,

rows in is the "# rows" value from the Rule Table.rows out The optimizer's estimate of the number of the rows in rows that sat-

isfy all conditionals from the where clause involving the tableaccessed at that step in the plan. Computed from the restrictionfactors of all expressions that contribute to this rule.

Table 15-18. Best Access Plan

When a group by or order by clause is specified and the optimizer selects an execution plan that satisfies the desired order-ing without requiring a separate sort pass, a statement will be reported similar to the following:

Plan produces target ordering for order by: 3 d 1 a

The numbers here are simply the result column ordinals. When an external sort is required, the sort costs will automatically beincorporated into the report plan cost and no notice will be printed. Note that in the example above, an external sort wasrequired (the "target ordering..." message was not printed). The optimizer's estimate of the cost of the sort can be computed bysubtracting from the plan's total cost, the cost reported in the last step of the Best Access Plan. Finally, the total number ofoptimizer iterations needed to determine the best access plan is reported.

Limitations

Optimization of View References

Each view in RDM Server SQL is optimized at its creation time and stored in a compiled format. A view referenced in aselect statement is accessed according to its precompiled execution plan. This can cause performance problems if a view is ref-erenced in a query with extra conditionals or is joined with another table. Instead of "unraveling" the view definition and re-

Page 254: RDMs 8.4 SQL User's Guide

15. Query Optimization

SQL User Guide 246

optimizing it along with the extra conditionals, the view's rows are retrieved and the additional constraints are evaluated atexecution time. Thus, it is best to avoid creating joins that involve views (but a view definition can include joins on basetables). An alternative is to use stored procedures, which are optimized at compile time but can be parameterized; the optim-izer does incorporate stored procedure parameter references in its analysis.

Merge-Scan Join Operation is Not Supported

A merge-scan join operation is a join processing technique where indexes on the joined columns are merged and only rowscommon to both indexes are returned. Some optimizers even include the cost of creating an index when one of the columns isnot joined. RDM Server does not include this technique because of its ability to define direct access joins using the createjoin statement. Join processing based on these predefined joins is optimal.

Subquery Transformation (Flattening) Unsupported

Some optimizers perform an optimization technique on nested correlated subqueries where the query is "flattened" into anequivalent query that has replaced the subqueries with joins. This method is not implemented in RDM Server.