The source of much copied reference material: Paul Vinkenoog
Copyright © 2017-2024 Firebird Project and all contributing authors, under the Public Documentation License Version 1.0.
Please refer to the License Notice in the Appendix
This volume represents a compilation of topics concerning Firebird’s SQL language written by members of the Russian-speaking community of Firebird developers and users. In 2014, it culminated in a language reference manual, in Russian. At the instigation of Alexey Kovyazin, a campaign was launched amongst Firebird users world-wide to raise funds to pay for a professional translation into English, from which translations into other languages would proceed under the auspices of the Firebird Documentation Project.
1. About the Firebird SQL Language Reference: for Firebird 2.5
This Firebird SQL Language Reference is the first comprehensive manual to cover all aspects of the query language used by developers to communicate, through their applications, with the Firebird relational database management system. It has a long history.
1.1. Subject Matter
The subject matter of this volume is wholly Firebird’s implementation of the SQL relational database language. Firebird conforms closely with international standards for SQL, from data type support, data storage structures, referential integrity mechanisms, to data manipulation capabilities and access privileges. Firebird also implements a robust procedural language — procedural SQL (PSQL) — for stored procedures, triggers and dynamically-executable code blocks. These are the areas addressed in this volume.
1.2. Authorship
The material for assembling this Language Reference has been accumulating in the tribal lore of the open source community of Firebird core developers and user-developers for 15 years. The gift of the InterBase 6 open source codebase in July 2000 from the (then) Inprise/Borland conglomerate was warmly welcomed. However, it came without rights to existing documentation. Once the code base had been forked by its owners for private, commercial development, it became clear that the open source, non-commercial Firebird community would never be granted right of use.
The two important books from the InterBase 6 published set were the Data Definition Guide and the Language Reference. The former covered the data definition language (DDL) subset of the SQL language, while the latter covered most of the rest. Fortunately for Firebird users over the years, both have been easy to find on-line as PDF books.
1.2.1. Language Reference Updates
The Data Definition Guide, covering the creation and maintenance of metadata for databases, was “good enough” for several years: the data definition language (DDL) of a DBMS is stable and grows slowly in comparison to the data manipulation language (DML) employed for queries and the PSQL used for server-based programming.
The leader of the Firebird Project’s documentation team, Paul Vinkenoog, took up the cause for documenting the huge volume of improvements and additions to DML and PSQL as Firebird advanced through its releases. Paul was personally responsible for collating, assembling and, to a great extent, authoring a cumulative series of “Language Reference Updates” — one for every major release from v.1.5 forward.
1.2.2. Gestation of the Big Book
From around 2010, Paul, with Firebird Project lead Dmitry Yemanov and a documenter colleague Thomas Woinke, set about the task of designing and assembling a complete SQL language reference for Firebird. They began with the material from the LangRef Updates, which is voluminous. It was going to be a big job but, for all concerned, a spare-time one.
Then, in 2013-4, two benefactor companies — MICEX amd IBSurgeon — funded three writers to take up this stalled book outline and publish a Firebird 2.5 Language Reference in Russian. They wrote the bulk of the missing DDL section from scratch and wrote, translated or reused DML and PSQL material from the LangRef Updates, Russian language support forums, Firebird release notes, read-me files and other sources. By the end of 2014, they had the task almost complete, in the form of a Microsoft Word document.
Translation …
The Russian sponsors, recognising that their efforts needed to be shared with the world-wide Firebird community, asked some Project members to initiate a crowd-funding campaign to have the Russian text professionally translated into English. The translated text would be edited and converted to the Project’s standard DocBook format for addition to the open document library of the Firebird Project. From there, the source text would be available for translation into other languages for addition to the library.
The fund-raising campaign happened at the end of 2014 and was successful. In June, 2015, professional translator Dmitry Borodin began translating the Russian text. From him, the raw English text went in stages for editing and DocBook conversion by Helen Borrie — and here is The Firebird SQL Language Reference for V.2.5, by…everyone!
… and More Translation
Once the DocBook source appears in CVS, we hope the trusty translators will start making versions in German, Japanese, Italian, French, Portuguese, Spanish, Czech. Certainly, we never have enough translators so please, you Firebirders who have English as a second language, do consider translating some sections into your first language.
1.2.3. Contributors
Direct Content
-
Dmitry Filippov (writer)
-
Alexander Karpeykin (writer)
-
Alexey Kovyazin (writer, editor)
-
Dmitry Kuzmenko (writer, editor)
-
Denis Simonov (writer, editor, coordinator)
-
Paul Vinkenoog (writer, designer)
-
Dmitry Yemanov (writer)
Resource Content
-
Adriano dos Santos Fernandes
-
Alexander Peshkov
-
Vladyslav Khorsun
-
Claudio Valderrama
-
Helen Borrie
-
and others
Translation
-
Dmitry Borodin, megaTranslations.ru
Editing and Conversion of English Text
-
Helen Borrie
1.3. Acknowledgments
The first full language reference manual for Firebird would not have eventuated without the funding that finally brought it to fruition. We acknowledge these contributions with gratitude and thank you all for stepping up.
Sponsors and Other Donors
Sponsors of the Russian Language Reference Manual
-
Moscow Exchange (Russia)
Moscow Exchange is the largest exchange holding in Russia and Eastern Europe, founded on December 19, 2011, through the consolidation of the MICEX (founded in 1992) and RTS (founded in 1995) exchange groups. Moscow Exchange ranks among the world’s top 20 exchanges by trading in bonds and by the total capitalization of shares traded, as well as among the 10 largest exchange platforms for trading derivatives.
-
Technical support and developer of administrator tools for the Firebird DBMS.
Sponsors of the Translation Project
-
Syntess Software BV (Netherlands)
-
Mitaro Business Solutions (Liechtenstein)
Other Donors
Listed below are the names of companies and individuals whose cash contributions covered the costs for translation into English, editing of the raw, translated text and conversion of the whole into the Firebird Project’s standard DocBook 4 documentation source format.
Integrity Software Design, Inc. (U.S.A.) |
dimari GmbH (Germany) |
beta Eigenheim GmbH (Germany) |
KIMData GmbH (Germany) |
Jason Wharton (U.S.A) |
Trans-X (Sweden) |
Sanchez Balcewich (Uruguay) |
Cointec Ingenieros y Consultores, S.L. (Spain) |
Aage Johansen (Norway) |
Mattic Software (Netherlands) |
André Knappstein (Germany) |
Paul F. McGuire (U.S.A.) |
Marcus Marques da Rocha (Brazil) |
Martin Kerkhoff |
Thomas Vedel (Denmark) |
Bulhan Bulhan (Australia) |
Alexandre Benson Smith (Brazil) |
Guillermo Nabrink (Brazil) |
Pierre Voirin (France) |
Heiko Tappe (Germany) |
Doug Chamberlin (U.S.A.) |
Craig Cox (U.S.A.) |
OMNet, Inc. (Switzerland) |
Alfred Steller (Germany) |
Konrad Butz (Germany) |
Thomas Smekal (Canada) |
Carlos H. Cantu (Brazil) |
XTRALOG SARL (France) |
Laszlo Urmenyi (Brazil) |
Fernando Pimenta (Brazil) |
Rudolf Grauberger (Germany) |
Thomas Steinmaurer (Austria) |
Rene Lobsiger (Switzerland) |
Hian Pin Tjioe |
Xavier Codina |
Mick Arundell (Australia) |
Russell Belding (New Zealand) |
Anticlei Scheid (Brazil) |
Luca Minuti (Italy) |
Mark Rotteveel (Netherlands) |
Chris Mathews (U.S.A.) |
Hannes Streicher (Germany) |
Wolfgang Lemmermeyer (Germany) |
Paolo Sciarrini (Italy) |
Acosta Belzusarri |
Daniel Motos Guerra |
Alberto Alfonso Luna |
Simeon Bodurov |
Cees Meijer |
Robert Nixon |
Olivier Dehorter (France) |
András Omacht (Hungary) |
Web Express |
Sergio Conzalez |
Marc Bleuwart |
Gabor Boros |
Shaymon Gracia Campos |
Cserna Zsombor (Hungary) |
David Keith |
Uwe Gerold |
Daniele Teti (Italy) |
Pedro Pablo Busto Criado |
Istvan Szabo |
Spiridon Pavlovic |
J. L. Garcia Naranjo |
A. Morales Morales |
Helen Cullen (New Zealand) |
Francisco Ibarra Ozuna |
|
2. SQL Language Structure
This reference describes the SQL language supported by Firebird.
2.1. Background to Firebird’s SQL Language
To begin, a few points about some characteristics that are in the background to Firebird’s language implementation.
2.1.1. SQL Flavours
Distinct subsets of SQL apply to different sectors of activity. The SQL subsets in Firebird’s language implementation are:
-
Dynamic SQL (DSQL)
-
Procedural SQL (PSQL)
-
Embedded SQL (ESQL)
-
Interactive SQL (ISQL)
Dynamic SQL is the major part of the language which corresponds to the Part 2 (SQL/Foundation) part of the SQL specification. DSQL represents statements passed by client applications through the public Firebird API and processed by the database engine.
Procedural SQL augments Dynamic SQL to allow compound statements containing local variables, assignments, conditions, loops and other procedural constructs.
PSQL corresponds to the Part 4 (SQL/PSM) part of the SQL specifications.
Originally, PSQL extensions were available in persistent stored modules (procedures and triggers) only, but in more recent releases they were surfaced in Dynamic SQL as well (see EXECUTE BLOCK
).
Embedded SQL defines the DSQL subset supported by Firebird gpre, the application which allows you to embed SQL constructs into your host programming language (C, C++, Pascal, Cobol, etc.) and preprocess those embedded constructs into the proper Firebird API calls.
Only a portion of the statements and expressions implemented in DSQL are supported in ESQL. |
Interactive ISQL refers to the language that can be executed using Firebird isql, the command-line application for accessing databases interactively. As a regular client application, its native language is DSQL. It also offers a few additional commands that are not available outside its specific environment.
Both DSQL and PSQL subsets are completely presented in this reference. Neither ESQL nor ISQL flavours are described here unless mentioned explicitly.
2.1.2. SQL Dialects
SQL dialect is a term that defines the specific features of the SQL language that are available when accessing a database. SQL dialects can be defined at the database level and specified at the connection level. Three dialects are available:
-
Dialect 1 is intended solely to allow backward comptibility with legacy databases from very old InterBase versions, v.5 and below. Dialect 1 databases retain certain language features that differ from Dialect 3, the default for Firebird databases.
-
Date and time information are stored in a
DATE
data type. ATIMESTAMP
data type is also available, that is identical to thisDATE
implementation. -
Double quotes may be used as an alternative to apostrophes for delimiting string data. This is contrary to the SQL standard — double quotes are reserved for a distinct syntactic purpose both in standard SQL and in Dialect 3. Double-quoting strings is therefore to be avoided strenuously.
-
The precision for
NUMERIC
andDECIMAL
data types is smaller than in Dialect 3 and, if the precision of a fixed decimal number is greater than 9, Firebird stores it internally as a long floating point value. -
The
BIGINT
(64-bit integer) data type is not supported. -
Identifiers are case-insensitive and must always comply with the rules for regular identifiers — see the section Identifiers below.
-
Although generator values are stored as 64-bit integers, a Dialect 1 client request,
SELECT GEN_ID (MyGen, 1)
, for example, will return the generator value truncated to 32 bits.
-
-
Dialect 2 is available only on the Firebird client connection and cannot be set in the database. It is intended to assist debugging of possible problems with legacy data when migrating a database from dialect 1 to 3.
-
In Dialect 3 databases,
-
numbers (
DECIMAL
andNUMERIC
data types) are stored internally as long fixed point values (scaled integers) when the precision is greater than 9. -
The
TIME
data type is available for storing time-of-day data only. -
The
DATE
data type stores only date information. -
The 64-bit integer data type
BIGINT
is available. -
Double quotes are reserved for delimiting non-regular identifiers, enabling object names that are case-sensitive or that do not meet the requirements for regular identifiers in other ways.
-
All strings must be delimited with single quotes (apostrophes).
-
Generator values are stored as 64-bit integers.
-
Use of Dialect 3 is strongly recommended for newly developed databases and applications. Both database and connection dialects should match, except under migration conditions with Dialect 2. This reference describes the semantics of SQL Dialect 3 unless specified otherwise. |
2.2. Basic Elements: Statements, Clauses, Keywords
The primary construct in SQL is the statement. A statement defines what the database management system should do with a particular data or metadata object. More complex statements contain simpler constructs — clauses and options.
- Clauses
-
A clause defines a certain type of directive in a statement. For instance, the
WHERE
clause in aSELECT
statement and in some other data manipulation statements (UPDATE, DELETE
) specifies criteria for searching one or more tables for the rows that are to be selected, updated or deleted. TheORDER BY
clause specifies how the output data — result set — should be sorted. - Options
-
Options, being the simplest constructs, are specified in association with specific keywords to provide qualification for clause elements. Where alternative options are available, it is usual for one of them to be the default, used if nothing is specified for that option. For instance, the
SELECT
statement will return all of the rows that match the search criteria unless theDISTINCT
option restricts the output to non-duplicated rows. - Keywords
-
All words that are included in the SQL lexicon are keywords. Some keywords are reserved, meaning their usage as identifiers for database objects, parameter names or variables is prohibited in some or all contexts. Non-reserved keywords can be used as identifiers, although it is not recommended. From time to time, non-reserved keywords may become reserved when some new language feature is introduced.
For instance, the following statement will be executed without errors because, although
ABS
is a keyword, it is not a reserved word.CREATE TABLE T (ABS INT NOT NULL);
On the contrary, the following statement will return an error because
ADD
is both a keyword and a reserved word.CREATE TABLE T (ADD INT NOT NULL);
Refer to the list of reserved words and keywords in the chapter Reserved Words and Keywords.
2.3. Identifiers
All database objects have names, often called identifiers. Two types of names are valid as identifiers: regular names, similar to variable names in regular programming languages, and delimited names that are specific to SQL. To be valid, each type of identifier must conform to a set of rules, as follows:
2.3.1. Rules for Regular Object Identifiers
-
Length cannot exceed 31 characters
-
The name must start with an unaccented, 7-bit ASCII alphabetic character. It may be followed by other 7-bit ASCII letters, digits, underscores or dollar signs. No other characters, including spaces, are valid. The name is case-insensitive, meaning it can be declared and used in either upper or lower case. Thus, from the system’s point of view, the following names are the same:
fullname FULLNAME FuLlNaMe FullName
<name> ::= <letter> | <name><letter> | <name><digit> | <name>_ | <name>$ <letter> ::= <upper letter> | <lower letter> <upper letter> ::= A | B | C | D | E | F | G | H | I | J | K | L | M | N | O | P | Q | R | S | T | U | V | W | X | Y | Z <lower letter> ::= a | b | c | d | e | f | g | h | i | j | k | l | m | n | o | p | q | r | s | t | u | v | w | x | y | z <digit> ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
2.3.2. Rules for Delimited Object Identifiers
-
Length cannot exceed 31 characters
-
The entire string must be enclosed in double-quotes, e.g.
"anIdentifier"
-
It may contain characters from any Latin character set, including accented characters, spaces and special characters
-
An identifier can be a reserved word
-
Delimited identifiers are case-sensitive in all contexts
-
Trailing spaces in delimited names are removed, as with any string constant
-
Delimited identifiers are available in Dialect 3 only. For more details on dialects, see SQL Dialects
<delimited name> ::= "<permitted_character>[<permitted_character> …]"
A delimited identifier such as |
2.4. Literals
Literals are used to represent data in a direct format. Examples of standard types of literals are:
integer - 0, -34, 45, 0X080000000;
real - 0.0, -3.14, 3.23e-23;
string - 'text', 'don''t!';
binary string - x'48656C6C6F20776F726C64'
date - DATE'2018-01-19';
time - TIME'15:12:56';
timestamp - TIMESTAMP'2018-01-19 13:32:02';
null state - null
Details about handling the literals for each data type are discussed in the next chapter, Data Types and Subtypes.
2.5. Operators and Special Characters
A set of special characters is reserved for use as operators or separators.
<special char> ::=
<space> | " | % | & | ' | ( | ) | * | + | , | -
| . | / | : | ; | < | = | > | ? | [ | ] | ^ | { | }
Some of these characters, alone or in combinations, may be used as operators (arithmetical, string, logical), as SQL command separators, to quote identifiers and to mark the limits of string literals or comments.
<operator> ::= <string concatenation operator> | <arithmetic operator> | <comparison operator> | <logical operator> <string concatentation operator> ::= "||" <arithmetic operator> ::= * | / | + | - | <comparison operator> ::= = | <> | != | ~= | ^= | > | < | >= | <= | !> | ~> | ^> | !< | ~< | ^< <logical operator> ::= NOT | AND | OR
For more details on operators, see Expressions.
2.6. Comments
Comments may be present in SQL scripts, SQL statements and PSQL modules. A comment can be any text specified by the code writer, usually used to document how particular parts of the code work. The parser ignores the text of comments.
Firebird supports two types of comments: block and in-line.
<comment> ::= <block comment> | <single-line comment> <block comment> ::= /* <ASCII char>[<ASCII char> …] */ <single-line comment> ::= -- <ASCII char>[<ASCII char> …]<end line>
Block comments start with the /*
character pair and end with the */
character pair.
Text in block comments may be of any length and can occupy multiple lines.
In-line comments start with a pair of hyphen characters, --
and continue up to the end of the current line.
CREATE PROCEDURE P(APARAM INT)
RETURNS (B INT)
AS
BEGIN
/* This text will be ignored during the execution of the statement
since it is a comment
*/
B = A + 1; -- In-line comment
SUSPEND;
END
3. Data Types and Subtypes
Data of various types are used to:
-
define columns in a database table in the
CREATE TABLE
statement or change columns usingALTER TABLE
-
declare or change a domain using the
CREATE DOMAIN
orALTER DOMAIN
statements -
declare local variables in stored procedures, PSQL blocks and triggers and specify parameters in stored procedures
-
indirectly specify arguments and return values when declaring external functions (UDFs — user-defined functions)
-
provide arguments for the
CAST()
function when explicitly converting data from one type to another
Name | Size | Precision & Limits | Description |
---|---|---|---|
|
64 bits |
From -263 to (263 - 1) |
The data type is available in Dialect 3 only |
|
Varying |
The size of a |
A data type of a dynamically variable size for storing large amounts of data, such as images, text, digital sounds. The basic structural unit is a segment. The blob subtype defines its content |
|
n characters. Size in bytes depends on the encoding, the number of bytes in a character |
from 1 to 32,767 bytes |
A fixed-length character data type. When its data is displayed, trailing spaces are added to the string up to the specified length. Trailing spaces are not stored in the database but are restored to match the defined length when the column is displayed on the client side. Network traffic is reduced by not sending spaces over the LAN. If the number of characters is not specified, 1 is used by default. |
|
32 bits |
From 0001-01-01 AD to 9999-12-31 AD |
|
|
Varying (16, 32 or 64 bits) |
precision = from 1 to 18, defines the least possible number of digits to store; scale = from 0 to 18, defines the number of digits after the decimal point |
A number with a decimal point that has scale digits after the point.
scale must be less than or equal to precision.
Example: |
|
64 bits |
2.225 * 10-308 to 1.797 * 10308 |
Double-precision IEEE, ~15 digits, reliable size depends on the platform |
|
32 bits |
1.175 * 10-38 to 3.402 * 1038 |
Single-precision IEEE, ~7 digits |
|
32 bits |
-2,147,483,648 up to 2,147,483,647 |
Signed long |
|
Varying (16, 32 or 64 bits) |
precision = from 1 to 18, defines the exact number of digits to store; scale = from 0 to 18, defines the number of digits after the decimal point |
A number with a decimal point that has scale digits after the point.
scale must be less than or equal to precision.
Example: |
|
16 bits |
-32,768 to 32,767 |
Signed short (word) |
|
32 bits |
0:00 to 23:59:59.9999 |
|
|
64 bits (2 X 32 bits) |
From start of day 0001-01-01 AD to end of day 9999-12-31 AD |
Date and time of day |
|
n characters. Size in bytes depends on the encoding, the number of bytes in a character |
from 1 to 32,765 bytes |
Variable length string type. The total size of characters in bytes cannot be larger than (32KB-3), taking into account their encoding. The two trailing bytes store the declared length. There is no default size: the n argument is mandatory. Leading and trailing spaces are stored and they are not trimmed, except for those trailing characters that are past the declared length. |
Note About Dates
Bear in mind that a time series consisting of dates in past centuries is processed without taking into account the actual historical facts, as though the Gregorian calendar were applicable throughout the entire series. |
3.1. Integer Data Types
The SMALLINT
, INTEGER
and BIGINT
data types are used for integers of various precision in Dialect 3.
Firebird does not support an unsigned integer data type.
3.1.1. SMALLINT
The 16-bit SMALLINT
data type is for compact data storage of integer data for which only a narrow range of possible values is required.
Numbers of the SMALLINT
type are within the range from -216 to 216 - 1, that is, from -32,768 to 32,767.
SMALLINT
ExamplesCREATE DOMAIN DFLAG AS SMALLINT DEFAULT 0 NOT NULL
CHECK (VALUE=-1 OR VALUE=0 OR VALUE=1);
CREATE DOMAIN RGB_VALUE AS SMALLINT;
3.1.2. INTEGER
The INTEGER
data type is a 32-bit integer.
The shorthand name of the data type is INT
.
Numbers of the INTEGER
type are within the range from -232 to 232 - 1, that is, from -2,147,483,648 to 2,147,483,647.
INTEGER
ExampleCREATE TABLE CUSTOMER (
CUST_NO INTEGER NOT NULL,
CUSTOMER VARCHAR(25) NOT NULL,
CONTACT_FIRST VARCHAR(15),
CONTACT_LAST VARCHAR(20),
...
PRIMARY KEY (CUST_NO) )
3.1.3. BIGINT
BIGINT
is an SQL:99-compliant 64-bit integer data type, available only in Dialect 3.
If a client uses Dialect 1, the generator value sent by the server is reduced to a 32-bit integer (INTEGER
).
When Dialect 3 is used for connection, the generator value is of type BIGINT
.
Numbers of the BIGINT
type are within the range from -263 to 263 - 1, or from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807.
3.1.4. Hexadecimal Format for Integer Numbers
Starting from Firebird 2.5, constants of the three integer types can be specified in hexadecimal format by means of 9 to 16 hexadecimal digits for BIGINT
or 1 to 8 digits for INTEGER
.
Hex representation for writing to SMALLINT
is not explicitly supported but Firebird will transparently convert a hex number to SMALLINT
if necessary, provided it falls within the ranges of negative and positive SMALLINT
.
The usage and numerical value ranges of hexadecimal notation are described in more detail in the discussion of number constants in the chapter entitled Common Language Elements.
CREATE TABLE WHOLELOTTARECORDS (
ID BIGINT NOT NULL PRIMARY KEY,
DESCRIPTION VARCHAR(32)
);
INSERT INTO MYBIGINTS VALUES (
-236453287458723,
328832607832,
22,
-56786237632476,
0X6F55A09D42, -- 478177959234
0X7FFFFFFFFFFFFFFF, -- 9223372036854775807
0XFFFFFFFFFFFFFFFF, -- -1
0X80000000, -- -2147483648, an INTEGER
0X080000000, -- 2147483648, a BIGINT
0XFFFFFFFF, -- -1, an INTEGER
0X0FFFFFFFF -- 4294967295, a BIGINT
);
The hexadecimal INTEGER
s in the above example are automatically cast to BIGINT
before being inserted into the table.
However, this happens after the numerical value is determined, so 0x80000000
(8 digits) and 0x080000000
(9 digits) will be saved as different BIGINT
values.
3.2. Floating-Point Data Types
Floating point data types are stored in an IEEE 754 binary format that comprises sign, exponent and mantissa.
Precision is dynamic, corresponding to the physical storage format of the value, which is exactly 4 bytes for the FLOAT
type and 8 bytes for DOUBLE PRECISION
.
Considering the peculiarities of storing floating-point numbers in a database, these data types are not recommended for storing monetary data. For the same reasons, columns with floating-point data are not recommended for use as keys or to have uniqueness constraints applied to them.
For testing data in columns with floating-point data types, expressions should check using a range, for instance, BETWEEN
, rather than searching for exact matches.
When using these data types in expressions, extreme care is advised regarding the rounding of evaluation results.
3.3. Fixed-Point Data Types
Fixed-point data types ensure the predictability of multiplication and division operations, making them the choice for storing monetary values.
Firebird implements two fixed-point data types: NUMERIC
and DECIMAL
.
According to the standard, both types limit the stored number to the declared scale (the number of digits after the decimal point).
Different treatments limit precision for each type: precision for NUMERIC
columns is exactly “as declared”, while DECIMAL
columns accepts numbers whose precision is at least equal to what was declared.
The behaviour of NUMERIC and DECIMAL in Firebird is like the SQL-standard DECIMAL ;
the precision is at least equal to what was declared.
|
For instance, NUMERIC(4, 2)
defines a number consisting altogether of four digits, including two digits after the decimal point;
that is, it can have up to two digits before the point and no more than two digits after the point.
If the number 3.1415 is written to a column with this data type definition, the value of 3.14 will be saved in the NUMERIC(4, 2)
column.
The form of declaration for fixed-point data, for instance, NUMERIC(p, s)
, is common to both types.
It is important to realise that the s
argument in this template is scale, rather than “a count of digits after the decimal point”.
Understanding the mechanism for storing and retrieving fixed-point data should help to visualise why: for storage, the number is multiplied by 10s (10 to the power of s
), converting it to an integer; when read, the integer is converted back.
The method of storing fixed-point data in the DBMS depends on several factors: declared precision, database dialect, declaration type.
Precision | Data type | Dialect 1 | Dialect 3 |
---|---|---|---|
1 - 4 |
|
|
|
1 - 4 |
|
|
|
5 - 9 |
|
|
|
10 - 18 |
|
|
|
3.3.1. NUMERIC
NUMERIC | NUMERIC(precision) | NUMERIC(precision, scale)
Parameter | Description |
---|---|
precision |
Precision, between 1 and 18. Defaults to 9. |
scale |
Scale, between 0 and scale. Defaults to 0. |
Further to the explanation above, the DBMS will store NUMERIC
data according the declared precision and scale.
Some more examples are:
NUMERIC(4) stored as SMALLINT (exact data) NUMERIC(4,2) SMALLINT (data * 102) NUMERIC(10,4) (Dialect 1) DOUBLE PRECISION (Dialect 3) BIGINT (data * 104)
Always keep in mind that the storage format depends on the precision.
For instance, you define the column type as |
3.3.2. DECIMAL
DECIMAL | DECIMAL(precision) | DECIMAL(precision, scale)
Parameter | Description |
---|---|
precision |
Precision, between 1 and 18. Defaults to 9. |
scale |
Scale, between 0 and scale. Defaults to 0. |
The storage format in the database for DECIMAL
is very similar to NUMERIC
, with some differences that are easier to observe with the help of some more examples:
DECIMAL(4) stored as INTEGER (exact data) DECIMAL(4,2) INTEGER (data * 102) DECIMAL(10,4) (Dialect 1) DOUBLE PRECISION (Dialect 3) BIGINT (data * 104)
3.4. Data Types for Dates and Times
The DATE
, TIME
and TIMESTAMP
data types are used to work with data containing dates and times.
Dialect 3 supports all the three types, while Dialect 1 has only DATE
.
The DATE
type in Dialect 3 is “date-only”, whereas the Dialect 1 DATE
type stores both date and time-of-day, equivalent to TIMESTAMP
in Dialect 3.
Dialect 1 has no “date-only” type.
Dialect 1 |
If fractions of seconds are stored in date and time data types, Firebird stores them to ten-thousandths of a second. If a lower granularity is preferred, the fraction can be specified explicitly as thousandths, hundredths or tenths of a second in Dialect 3 databases of ODS 11 or higher.
Some useful knowledge about subseconds precision:
The time-part of a
Deci-milliseconds precision is rare and is not currently stored in columns or variables.
The best assumption to make from all this is that, although Firebird stores |
3.4.1. DATE
The DATE
data type in Dialect 3 stores only date without time.
The available range for storing data is from January 01, 1 to December 31, 9999.
Dialect 1 has no “date-only” type.
In Dialect 1, date literals without a time part, as well as If, for some reason, it is important to you to store a Dialect 1 timestamp literal with an explicit zero time-part, the engine will accept a literal like |
3.4.2. TIME
The TIME
data type is available in Dialect 3 only.
It stores the time of day within the range from 00:00:00.0000 to 23:59:59.9999.
If you need to get the time-part from DATE
in Dialect 1, you can use the EXTRACT
function.
EXTRACT()
EXTRACT (HOUR FROM DATE_FIELD)
EXTRACT (MINUTE FROM DATE_FIELD)
EXTRACT (SECOND FROM DATE_FIELD)
See also the EXTRACT()
function in the chapter entitled Built-in Functions.
3.4.3. TIMESTAMP
The TIMESTAMP
data type is available in Dialect 3 and Dialect 1.
It comprises two 32-bit words — a date-part and a time-part — to form a structure that stores both date and time-of-day.
It is the same as the DATE
type in Dialect 1.
The EXTRACT
function works equally well with TIMESTAMP
as with the Dialect 1 DATE
type.
3.4.4. Operations Using Date and Time Values
The method of storing date and time values makes it possible to involve them as operands in some arithmetic operations. In storage, a date value or date-part of a timestamp is represented as the number of days elapsed since “date zero” — November 17, 1898 — whilst a time value or the time-part of a timestamp is represented as the number of seconds (with fractions of seconds taken into account) since midnight.
An example is to subtract an earlier date, time or timestamp from a later one, resulting in an interval of time, in days and fractions of days.
Operand 1 | Operation | Operand 2 | Result |
---|---|---|---|
|
|
|
|
|
|
Numeric value |
|
|
|
|
|
|
|
Numeric value |
|
|
|
Numeric value |
|
|
|
|
Number of days elapsed, within the range |
|
|
Numeric value |
|
|
|
|
Number of seconds elapsed, within the range |
|
|
Numeric value |
|
|
|
|
Number of days and part-day, within the range |
|
|
Numeric value |
|
Notes
The |
3.5. Character Data Types
For working with character data, Firebird has the fixed-length CHAR
and the variable-length VARCHAR
data types.
The maximum size of text data stored in these data types is 32,767 bytes for CHAR
and 32,765 bytes for VARCHAR
.
The maximum number of characters that will fit within these limits depends on the CHARACTER SET
being used for the data under consideration.
The collation sequence does not affect this maximum, although it may affect the maximum size of any index that involves the column.
If no character set is explicitly specified when defining a character object, the default character set specified when the database was created will be used.
If the database does not have a default character set defined, the field gets the character set NONE
.
3.5.1. Unicode
Most current development tools support Unicode, implemented in Firebird with the character sets UTF8
and UNICODE_FSS
. UTF8
comes with collations for many languages.
UNICODE_FSS
is more limited and is used mainly by Firebird internally for storing metadata.
Keep in mind that one UTF8
character occupies up to 4 bytes, thus limiting the size of CHAR
fields to 8,191 characters (32,767/4).
The actual “bytes per character” value depends on the range the character belongs to.
Non-accented Latin letters occupy 1 byte, Cyrillic letters from the |
The UTF8
character set implemented in Firebird supports the latest version of the Unicode standard, thus recommending its use for international databases.
3.5.2. Client Character Set
While working with strings, it is essential to keep the character set of the client connection in mind.
If there is a mismatch between the character sets of the stored data and that of the client connection, the output results for string columns are automatically re-encoded, both when data are sent from the client to the server and when they are sent back from the server to the client.
For example, if the database was created in the WIN1251
encoding but KOI8R
or UTF8
is specified in the client’s connection parameters, the mismatch will be transparent.
3.5.3. Special Character Sets
NONE
The character set NONE
is a special character set in Firebird.
It can be characterized such that each byte is a part of a string, but the string is stored in the system without any clues about what constitutes any character: character encoding, collation, case, etc. are simply unknown.
It is the responsibility of the client application to deal with the data and provide the means to interpret the string of bytes in some way that is meaningful to the application and the human user.
OCTETS
Data in OCTETS
encoding are treated as bytes that may not actually be interpreted as characters.
OCTETS
provides a way to store binary data, which could be the results of some Firebird functions.
The database engine has no concept of what it is meant to do with a string of bits in OCTETS
, other than just store it and retrieve it.
Again, the client side is responsible for validating the data, presenting them in formats that are meaningful to the application and its users and handling any exceptions arising from decoding and encoding them.
3.5.4. Collation Sequence
Each character set has a default collation sequence (COLLATE
) that specifies the collation order.
Usually, it provides nothing more than ordering based on the numeric code of the characters and a basic mapping of upper- and lower-case characters.
If some behaviour is needed for strings that is not provided by the default collation sequence and a suitable alternative collation is supported for that character set, a COLLATE
clause can be specified in the column definition.collation
A COLLATE
clause can be applied in other contexts besides the column definition.
For greater-than/less-than comparison operations, it can be added in the collation
WHERE
clause of a SELECT
statement.
If output needs to be sorted in a special alphabetic sequence, or case-insensitively, and the appropriate collation exists, then a COLLATE
clause can be included with the ORDER BY
clause when rows are being sorted on a character field and with the GROUP BY
clause in case of grouping operations.
Case-Insensitive Searching
For a case-insensitive search, the UPPER
function could be used to convert both the search argument and the searched strings to upper-case before attempting a match:
…
where upper(name) = upper(:flt_name)
For strings in a character set that has a case-insensitive collation available, you can simply apply the collation, to compare the search argument and the searched strings directly.
For example, using the WIN1251
character set, the collation PXW_CYRL
is case-insensitive for this purpose:
…
WHERE FIRST_NAME COLLATE PXW_CYRL >= :FLT_NAME
…
ORDER BY NAME COLLATE PXW_CYRL
UTF8
Collation Sequences
The following table shows the possible collation sequences for the UTF8
character set.
Collation | Characteristics |
---|---|
|
Collation works according to the position of the character in the table (binary). Added in Firebird 2.0 |
|
Collation works according to the UCA algorithm (Unicode Collation Algorithm) (alphabetical). Added in Firebird 2.0 |
|
The default, binary collation, identical to |
|
Case-insensitive collation, works without taking character case into account. Added in Firebird 2.1 |
|
Case-insensitive, accent-insensitive collation, works alphabetically without taking character case or accents into account. Added in Firebird 2.5 |
An example of collation for the UTF8 character set without taking into account the case or accentuation of characters (similar to COLLATE PXW_CYRL
).
...
ORDER BY NAME COLLATE UNICODE_CI_AI
3.5.5. Character Indexes
In Firebird earlier than version 2.0, a problem can occur with building an index for character columns that use a non-standard collation sequence: the length of an indexed field is limited to 252 bytes with no COLLATE
specified or 84 bytes if COLLATE
is specified.
Multi-byte character sets and compound indexes limit the size even further.
Starting from Firebird 2.0, the maximum length for an index equals one quarter of the page size, i.e. from 1,024 — for page size 4,096 — to 4,096 bytes — for page size 16,384. The maximum length of an indexed string is 9 bytes less than that quarter-page limit.
The following formula calculates the maximum length of an indexed string (in characters):
max_char_length = FLOOR((page_size / 4 - 9) / N)
where N is the number of bytes per character in the character set.
The table below shows the maximum length of an indexed string (in characters), according to page size and character set, calculated using this formula.
Page Size |
Bytes per character |
||||
---|---|---|---|---|---|
1 |
2 |
3 |
4 |
6 |
|
4,096 |
1,015 |
507 |
338 |
253 |
169 |
8,192 |
2,039 |
1,019 |
679 |
509 |
339 |
16,384 |
4,087 |
2,043 |
1,362 |
1,021 |
682 |
With case-insensitive collations (“_CI”), one character in the index will occupy not 4, but 6 (six) bytes, so the maximum key length for a page of — for example — 4,096 bytes, will be 169 characters. |
CREATE DATABASE
, Collation sequence, SELECT
, WHERE
, GROUP BY
, ORDER BY
3.5.6. Character Types in Detail
CHAR
CHAR
is a fixed-length data type.
If the entered number of characters is less than the declared length, trailing spaces will be added to the field.
Generally, the pad character does not have to be a space: it depends on the character set.
For example, the pad character for the OCTETS
character set is zero.
The full name of this data type is CHARACTER
, but there is no requirement to use full names and people rarely do so.
Fixed-length character data can be used to store codes whose length is standard and has a definite “width” in directories. An example of such a code is an EAN13 barcode — 13 characters, all filled.
{ CHAR | CHARACTER } [ (length) ] [CHARACTER SET <set>] [COLLATE <name>]
If no length is specified, it is taken to be 1. A valid length is from 1 to the maximum number of characters that can be accommodated within 32,767 bytes. |
VARCHAR
VARCHAR
is the basic string type for storing texts of variable length, up to a maximum of 32,765 bytes.
The stored structure is equal to the actual size of the data plus 2 bytes where the length of the data is recorded.
All characters that are sent from the client application to the database are considered meaningful, including the leading and trailing spaces. However, trailing spaces are not stored: they will be restored upon retrieval, up to the recorded length of the string.
The full name of this type is CHARACTER VARYING
.
Another variant of the name is written as CHAR VARYING
.
{ VARCHAR | CHAR VARYING | CHARACTER VARYING } (length) [CHARACTER SET <set>] [COLLATE <name>]
NCHAR
NCHAR
is a fixed-length character data type with the ISO8859_1
character set predefined.
In all other respects it is the same as CHAR
.
{ NCHAR | NATIONAL { CHAR | CHARACTER } } [ (length) ]
A similar data type is available for the variable-length string type: NATIONAL CHARACTER VARYING
.
3.6. Binary Data Types
BLOB
s (Binary Large Objects) are complex structures used to store text and binary data of an undefined length, often very large.
BLOB [SUB_TYPE <subtype>] [SEGMENT SIZE <segment size>] [CHARACTER SET <character set>] [COLLATE <collation name>]
BLOB (<segment size>) BLOB (<segment size>, <subtype>) BLOB (, <subtype>)
Specifying the BLOB segment is throwback to times past, when applications for working with BLOB data were written in C (Embedded SQL) with the help of the gpre pre-compiler. Nowadays, it is effectively irrelevant. The segment size for BLOB data is determined by the client side and is usually larger than the data page size, in any case.
3.6.1. BLOB
Subtypes
The optional SUB_TYPE
parameter specifies the nature of data written to the column.
Firebird provides two pre-defined subtypes for storing user data:
- Subtype 0:
BINARY
-
If a subtype is not specified, the specification is assumed to be for untyped data and the default
SUB_TYPE 0
is applied. The alias for subtype zero isBINARY
. This is the subtype to specify when the data are any form of binary file or stream: images, audio, word-processor files, PDFs and so on. - Subtype 1:
TEXT
-
Subtype 1 has an alias,
TEXT
, which can be used in declarations and definitions. For instance,BLOB SUB_TYPE TEXT
. It is a specialized subtype used to store plain text data that is too large to fit into a string type. ACHARACTER SET
may be specified, if the field is to store text with a different encoding to that specified for the database. From Firebird 2.0, aCOLLATE
clause is also supported.Specifying a
CHARACTER SET
withoutSUB_TYPE
impliesSUB_TYPE TEXT
.
It is also possible to add custom data subtypes, for which the range of enumeration from -1 to -32,768 is reserved. Custom subtypes enumerated with positive numbers are not allowed, as the Firebird engine uses the numbers from 2-upward for some internal subtypes in metadata.
3.6.2. BLOB
Specifics
The maximum size of a BLOB
field is limited to 4GB, regardless of whether the server is 32-bit or 64-bit.
(The internal structures related to BLOB
s maintain their own 4-byte counters.)
For a page size of 4 KB (4096 bytes) the maximum size is lower — slightly less than 2GB.
Text BLOBs of any length and any character set — including multi-byte — can be operands for practically any statement or internal functions. The following operators are supported completely:
= |
(assignment) |
=, <>, <, ⇐, >, >= |
(comparison) |
|
(concatenation) |
|
|
|
|
|
|
Partial support:
-
An error occurs with these if the search argument is larger than or equal to 32 KB:
STARTING [WITH]
,LIKE
,CONTAINING
-
Aggregation clauses work not on the contents of the field itself, but on the BLOB ID. Aside from that, there are some quirks:
SELECT DISTINCT
returns several NULL values by mistake if they are present
ORDER BY
—
GROUP BY
concatenates the same strings if they are adjacent to each other, but does not do it if they are remote from each other
BLOB
Storage-
By default, a regular record is created for each BLOB and it is stored on a data page that is allocated for it. If the entire
BLOB
fits onto this page, it is called a level 0 BLOB. The number of this special record is stored in the table record and occupies 8 bytes. -
If a
BLOB
does not fit onto one data page, its contents are put onto separate pages allocated exclusively to it (blob pages), while the numbers of these pages are stored into theBLOB
record. This is a level 1 BLOB. -
If the array of page numbers containing the
BLOB
data does not fit onto a data page, the array is put on separate blob pages, while the numbers of these pages are put into theBLOB
record. This is a level 2 BLOB. -
Levels higher than 2 are not supported.
3.6.3. ARRAY
Type
The support of arrays in the Firebird DBMS is a departure from the traditional relational model. Supporting arrays in the DBMS could make it easier to solve some data-processing tasks involving large sets of similar data.
Arrays in Firebird are stored in BLOB
of a specialized type.
Arrays can be one-dimensional and multidimensional and of any data type except BLOB
and ARRAY
.
CREATE TABLE SAMPLE_ARR (
ID INTEGER NOT NULL PRIMARY KEY,
ARR_INT INTEGER [4]
);
This example will create a table with a field of the array type consisting of four integers. The subscripts of this array are from 1 to 4.
Specifying Explicit Boundaries for Dimensions
By default, dimensions are 1-based — subscripts are numbered from 1. To specify explicit upper and lower bounds of the subscript values, use the following syntax:
'[' <lower>:<upper> ']'
Adding More Dimensions
A new dimension is added using a comma in the syntax. In this example we create a table with a two-dimensional array, with the lower bound of subscripts in both dimensions starting from zero:
CREATE TABLE SAMPLE_ARR2 (
ID INTEGER NOT NULL PRIMARY KEY,
ARR_INT INTEGER [0:3, 0:3]
);
The DBMS does not offer much in the way of language or tools for working with the contents of arrays.
The database employee.fdb
, found in the ../examples/empbuild
directory of any Firebird distribution package, contains a sample stored procedure showing some simple work with arrays:
PSQL Source for SHOW_LANGS
, a procedure involving an array
CREATE OR ALTER PROCEDURE SHOW_LANGS (
CODE VARCHAR(5),
GRADE SMALLINT,
CTY VARCHAR(15))
RETURNS (LANGUAGES VARCHAR(15))
AS
DECLARE VARIABLE I INTEGER;
BEGIN
I = 1;
WHILE (I <= 5) DO
BEGIN
SELECT LANGUAGE_REQ[:I]
FROM JOB
WHERE (JOB_CODE = :CODE)
AND (JOB_GRADE = :GRADE)
AND (JOB_COUNTRY = :CTY)
AND (LANGUAGE_REQ IS NOT NULL))
INTO :LANGUAGES;
IF (LANGUAGES = '') THEN
/* PRINTS 'NULL' INSTEAD OF BLANKS */
LANGUAGES = 'NULL';
I = I +1;
SUSPEND;
END
END
If the features described are enough for your tasks, you might consider using arrays in your projects. Currently, no improvements are planned to enhance support for arrays in Firebird.
3.7. Special Data Types
“Special” data types …
3.7.1. SQL_NULL
Data Type
The SQL_NULL
type holds no data, but only a state: NULL
or NOT NULL
.
It is not available as a data type for declaring table fields, PSQL variables or parameter descriptions.
It was added to support the use of untyped parameters in expressions involving the IS NULL
predicate.
An evaluation problem occurs when optional filters are used to write queries of the following type:
WHERE col1 = :param1 OR :param1 IS NULL
After processing, at the API level, the query will look like this:
WHERE col1 = ? OR ? IS NULL
This is a case where the developer writes an SQL query and considers :param1
as though it were a variable that he can refer to twice.
However, at the API level, the query contains two separate and independent _parameters.
The server cannot determine the type of the second parameter since it comes in association with IS NULL
.
The SQL_NULL
data type solves this problem.
Whenever the engine encounters an “? IS NULL
” predicate in a query, it assigns the SQL_NULL
type to the parameter, which will indicate that parameter is only about “nullness” and the data type or the value need not be addressed.
The following example demonstrates its use in practice.
It assumes two named parameters — say, :size
and :colour
— which might, for example, get values from on-screen text fields or drop-down lists.
Each named parameter corresponds with two positional parameters in the query.
SELECT
SH.SIZE, SH.COLOUR, SH.PRICE
FROM SHIRTS SH
WHERE (SH.SIZE = ? OR ? IS NULL)
AND (SH.COLOUR = ? OR ? IS NULL)
Explaining what happens here assumes the reader is familiar with the Firebird API and the passing of parameters in XSQLVAR structures — what happens under the surface will not be of interest to those who are not writing drivers or applications that communicate using the “naked” API.
The application passes the parameterized query to the server in the usual positional ?
-form.
Pairs of “identical” parameters cannot be merged into one so, for two optional filters, for example, four positional parameters are needed: one for each ?
in our example.
After the call to isc_dsql_describe_bind()
, the SQLTYPE of the second and fourth parameters will be set to SQL_NULL
.
Firebird has no knowledge of their special relation with the first and third parameters: that responsibility lies entirely on the application side.
Once the values for size and colour have been set (or left unset) by the user and the query is about to be executed, each pair of XSQLVAR
s must be filled as follows:
- User has supplied a value
-
First parameter (value compare): set
*sqldata
to the supplied value and*sqlind
to0
(forNOT NULL
)Second parameter (
NULL
test): setsqldata
tonull
(null pointer, not SQLNULL
) and*sqlind
to0
(forNOT NULL
) - User has left the field blank
-
Both parameters: set
sqldata
tonull
(null pointer, not SQLNULL
) and*sqlind
to-1
(indicatingNULL
)
In other words: The value compare parameter is always set as usual.
The SQL_NULL
parameter is set the same, except that sqldata
remains null
at all times.
3.8. Conversion of Data Types
When composing an expression or specifying an operation, the aim should be to use compatible data types for the operands. When a need arises to use a mixture of data types, it should prompt you to look for a way to convert incompatible operands before subjecting them to the operation. The ability to convert data may well be an issue if you are working with Dialect 1 data.
3.8.1. Explicit Data Type Conversion
The CAST
function enables explicit conversion between many pairs of data types.
CAST ( { <value> | NULL } AS <data_type>) <data_type> ::= <sql_datatype> | [TYPE OF] domain | TYPE OF COLUMN relname.colname
Casting to a Domain
When you cast to a domain, any constraints declared for it are taken into account, i.e., NOT NULL
or CHECK
constraints.
If the value does not pass the check, the cast will fail.
If TYPE OF
is additionally specified — casting to its base type — any domain constraints are ignored during the cast.
If TYPE OF
is used with a character type (CHAR/VARCHAR
), the character set and collation are retained.
Casting to TYPE OF COLUMN
When operands are cast to the type of a column, the specified column may be from a table or a view.
Only the type of the column itself is used. For character types, the cast includes the character set, but not the collation. The constraints and default values of the source column are not applied.
CREATE TABLE TTT (
S VARCHAR (40)
CHARACTER SET UTF8 COLLATE UNICODE_CI_AI
);
COMMIT;
SELECT
CAST ('I have many friends' AS TYPE OF COLUMN TTT.S)
FROM RDB$DATABASE;
Conversions Possible for the CAST
Function
From Data Type | To Data Type |
---|---|
Numeric types |
Numeric types, |
|
|
|
|
|
|
|
|
Keep in mind that partial information loss is possible.
For instance, when you cast the |
Literal Formats
To cast string data types to the DATE
, TIME
or TIMESTAMP
data types, you need the string argument to be one of the predefined date and time literals (see Table 9) or a representation of the date in one of the allowed date-time literal formats:
<timestamp_format> ::= { [YYYY<p>]MM<p>DD[<p>HH[<p>mm[<p>SS[<p>NNNN]]]] | MM<p>DD[<p>YYYY[<p>HH[<p>mm[<p>SS[<p>NNNN]]]]] | DD<p>MM[<p>YYYY[<p>HH[<p>mm[<p>SS[<p>NNNN]]]]] | MM<p>DD[<p>YY[<p>HH[<p>mm[<p>SS[<p>NNNN]]]]] | DD<p>MM[<p>YY[<p>HH[<p>mm[<p>SS[<p>NNNN]]]]] | NOW | TODAY | TOMORROW | YESTERDAY } <date_format> ::= { [YYYY<p>]MM<p>DD | MM<p>DD[<p>YYYY] | DD<p>MM[<p>YYYY] | MM<p>DD[<p>YY] | DD<p>MM[<p>YY] | TODAY | TOMORROW | YESTERDAY } <time_format> := { HH[<p>mm[<p>SS[<p>NNNN]]] | NOW } <p> ::= whitespace | . | : | , | - | /
Argument | Description |
---|---|
timestamp_format |
Format of timestamp literal |
date_literal |
Format of date literal |
time_literal |
Format of time literal |
YYYY |
Four-digit year |
YY |
Two-digit year |
MM |
Month. It may contain 1 or 2 digits (1-12 or 01-12). You can also specify the three-letter shorthand name or the full name of a month in English. Case-insensitive |
DD |
Day. It may contain 1 or 2 digits (1-31 or 01-31) |
HH |
Hour. It may contain 1 or 2 digits (0-23 or 00-23) |
mm |
Minutes. It may contain 1 or 2 digits (0-59 or 00-59) |
SS |
Seconds. It may contain 1 or 2 digits (0-59 or 00-59) |
NNNN |
Ten-thousandths of a second. It may contain from 1 to 4 digits (0-9999) |
p |
A separator, any of permitted characters. Leading and trailing spaces are ignored |
Literal |
Description |
Data Type |
|
---|---|---|---|
Dialect 1 |
Dialect 3 |
||
|
Current date and time |
|
|
|
Current date |
|
|
|
Current date + 1 (day) |
|
|
|
Current date - 1 (day) |
|
|
Use of the complete specification of the year in the four-digit form — |
select
cast('04.12.2014' as date) as d1, -- DD.MM.YYYY
cast('04 12 2014' as date) as d2, -- MM DD YYYY
cast('4-12-2014' as date) as d3, -- MM-DD-YYYY
cast('04/12/2014' as date) as d4, -- MM/DD/YYYY
cast('04,12,2014' as date) as d5, -- MM,DD,YYYY
cast('04.12.14' as date) as d6, -- DD.MM.YY
-- DD.MM with current year
cast('04.12' as date) as d7,
-- MM/DD with current year
cast('04/12' as date) as d8,
cast('2014/12/04' as date) as d9, -- YYYY/MM/DD
cast('2014 12 04' as date) as d10, -- YYYY MM DD
cast('2014.12.04' as date) as d11, -- YYYY.MM.DD
cast('2014-12-04' as date) as d12, -- YYYY-MM-DD
cast('4 Jan 2014' as date) as d13, -- DD MM YYYY
cast('2014 Jan 4' as date) as dt14, -- YYYY MM DD
cast('Jan 4, 2014' as date) as dt15, -- MM DD, YYYY
cast('11:37' as time) as t1, -- HH:mm
cast('11:37:12' as time) as t2, -- HH:mm:ss
cast('11:31:12.1234' as time) as t3, -- HH:mm:ss.nnnn
cast('11.37.12' as time) as t4, -- HH.mm.ss
-- DD.MM.YYYY HH:mm
cast('04.12.2014 11:37' as timestamp) as dt1,
-- MM/DD/YYYY HH:mm:ss
cast('04/12/2014 11:37:12' as timestamp) as dt2,
-- DD.MM.YYYY HH:mm:ss.nnnn
cast('04.12.2014 11:31:12.1234' as timestamp) as dt3,
-- MM/DD/YYYY HH.mm.ss
cast('04/12/2014 11.37.12' as timestamp) as dt4
from rdb$database
Shorthand Casts for Date and Time Data Types
Firebird allows the use of a shorthand “C-style” type syntax for casts from string to the types DATE
, TIME
and TIMESTAMP
.
<data_type> 'date_literal_string'
-- 1
UPDATE PEOPLE
SET AGECAT = 'SENIOR'
WHERE BIRTHDATE < DATE '1-Jan-1943';
-- 2
INSERT INTO APPOINTMENTS
(EMPLOYEE_ID, CLIENT_ID, APP_DATE, APP_TIME)
VALUES (973, 8804, DATE 'today' + 2, TIME '16:00');
-- 3
NEW.LASTMOD = TIMESTAMP 'now';
These shorthand expressions are evaluated directly during parsing, as though the statement were already prepared for execution.
Thus, even if the query is run several times, the value of, for instance, If you need the time to be evaluated at each execution, use the full
|
3.8.2. Implicit Data Type Conversion
Implicit data conversion is not possible in Dialect 3 — the CAST
function is almost always required to avoid data type clashes.
In Dialect 1, in many expressions, one type is implicitly cast to another without the need to use the CAST function. For instance, the following statement in Dialect 1 is valid:
UPDATE ATABLE
SET ADATE = '25.12.2016' + 1
and the date literal will be cast to the date type implicitly.
In Dialect 3, this statement will throw error 35544569, “Dynamic SQL Error: expression evaluation not supported, Strings cannot be added or subtracted in dialect 3” — a cast will be needed:
UPDATE ATABLE
SET ADATE = CAST ('25.12.2016' AS DATE) + 1
or, with the short cast:
UPDATE ATABLE
SET ADATE = DATE '25.12.2016' + 1
In Dialect 1, mixing integer data and numeric strings is usually possible because the parser will try to cast the string implicitly. For example,
2 + '1'
will be executed correctly.
In Dialect 3, an expression like this will raise an error, so you will need to write it as a CAST
expression:
2 + CAST('1' AS SMALLINT)
The exception to the rule is during string concatenation.
Implicit Conversion During String Concatenation
When multiple data elements are being concatenated, all non-string data will undergo implicit conversion to string, if possible.
SELECT 30||' days hath September, April, June and November' CONCAT$
FROM RDB$DATABASE;
CONCAT$
------------------------------------------------
30 days hath September, April, June and November
3.9. Custom Data Types — Domains
In Firebird, the concept of a “user-defined data type” is implemented in the form of the domain. Creating a domain does not truly create a new data type, of course. A domain provides the means to encapsulate an existing data type with a set of attributes and make this “capsule” available for multiple usage across the whole database. If several tables need columns defined with identical or nearly identical attributes, a domain makes sense.
Domain usage is not limited to column definitions for tables and views. Domains can be used to declare input and output parameters and variables in PSQL code.
3.9.1. Domain Attributes
A domain definition contains required and optional attributes. The data type is a required attribute. Optional attributes include:
-
a default value
-
to allow or forbid
NULL
-
CHECK
constraints -
character set (for character data types and text BLOB fields)
-
collation (for character data types)
CREATE DOMAIN BOOL3 AS SMALLINT
CHECK (VALUE IS NULL OR VALUE IN (0, 1));
Explicit Data Type Conversion for the description of differences in the data conversion mechanism when domains are specified for the TYPE OF
and TYPE OF COLUMN
modifiers.
3.9.2. Domain Override
While defining a column using a domain, it is possible to override some of the attributes inherited from the domain. Table 3.9 summarises the rules for domain override.
Attribute | Override? | Comments |
---|---|---|
Data type |
No |
|
Default value |
Yes |
|
Text character set |
Yes |
It can be also used to restore the default database values for the column |
Text collation sequence |
Yes |
|
|
Yes |
To add new conditions to the check, you can use the corresponding |
|
No |
Often it is better to leave domain nullable in its definition and decide whether to make it |
3.9.3. Creating and Administering Domains
A domain is created with the DDL statement CREATE DOMAIN
.
CREATE DOMAIN name [AS] <type> [DEFAULT {<const> | <literal> | NULL | <context_var>}] [NOT NULL] [CHECK (<condition>)] [COLLATE <collation>]
CREATE DOMAIN
in the Data Definition Language (DDL) section.
Altering a Domain
To change the attributes of a domain, use the DDL statement ALTER DOMAIN
.
With this statement you can:
-
rename the domain
-
change the data type
-
delete the current default value
-
set a new default value
-
delete an existing
CHECK
constraint -
add a new
CHECK
constraint
ALTER DOMAIN name [{TO <new_name>}] [{SET DEFAULT {<literal> | NULL | <context_var>} | DROP DEFAULT}] [{ADD [CONSTRAINT] CHECK (<dom_condition>) | DROP CONSTRAINT}] [{TYPE <datatype>}]
When planning to alter a domain, its dependencies must be taken into account: whether there are table columns, any variables, input and/or output parameters with the type of this domain declared in the PSQL code. If you change domains in haste, without carefully checking them, your code may stop working!
When you convert data types in a domain, you must not perform any conversions that may result in data loss.
Also, for example, if you convert |
ALTER DOMAIN
in the Data Definition Language (DDL) section.
Deleting (Dropping) a Domain
The DDL statement DROP DOMAIN
deletes a domain from the database, provided it is not in use by any other database objects.
DROP DOMAIN name
Any user connected to the database can delete a domain. |
DROP DOMAIN Test_Domain
DROP DOMAIN
in the Data Definition Language (DDL) section.
4. Common Language Elements
This chapter covers the elements that are common throughout the implementation of the SQL language — the expressions that are used to extract and operate on assertions about data and the predicates that test the truth of those assertions.
4.1. Expressions
SQL expressions provide formal methods for evaluating, transforming and comparing values. SQL expressions may include table columns, variables, constants, literals, various statements and predicates and also other expressions. The complete list of possible tokens in expressions follows.
- Column name
-
Identifier of a column from a specified table used in evaluations or as a search condition. A column of the array type cannot be an element in an expression except when used with the
IS [NOT] NULL
predicate. - Array element
-
An expression may contain a reference to an array member i.e.,
<array_name>[s]
, wheres
is the subscript of the member in the array<array_name>
- Arithmetic operators
-
The
+
,-
,*
,/
characters used to calculate values - Concatenation operator
-
The
||
(“double-pipe”) operator used to concatenate strings - Logical operators
-
The reserved words
NOT
,AND
andOR
, used to combine simple search conditions in order to create complex assertions - Comparison operators
-
The symbols
=
,<>
,!=
,~=
,^=
,<
,<=
,>
,>=
,!<
,~<
,^<
,!>
,~>
and^>
- Comparison predicates
-
LIKE
,STARTING WITH
,CONTAINING
,SIMILAR TO
,BETWEEN
,IS [NOT] NULL
andIS [NOT] DISTINCT FROM
- Existential predicates
-
Predicates used to check the existence of values in a set. The
IN
predicate can be used both with sets of comma-separated constants and with subqueries that return a single column. TheEXISTS
,SINGULAR
,ALL
,ANY
andSOME
predicates can be used only with subqueries. - Constant
-
A number or a string literal enclosed in apostrophes
- Date/time literal
-
An expression, similar to a string literal enclosed in apostrophes, that can be interpreted as a date, time or timestamp value. Date literals can be predefined literals (
'TODAY'
,'NOW'
, etc.) or strings of characters and numerals, such as'25.12.2016 15:30:35'
, that can be resolved as date and/or time strings. - Context variable
-
An internally-defined context variable
- Local variable
-
Declared local variable, input or output parameter of a PSQL module (stored procedure, trigger, unnamed PSQL block in DSQL)
- Positional parameter
-
A member of in an ordered group of one or more unnamed parameters passed to a stored procedure or prepared query
- Subquery
-
A
SELECT
statement enclosed in parentheses that returns a single (scalar) value or, when used in existential predicates, a set of values - Function identifier
-
The identifier of an internal or external function in a function expression
- Type cast
-
An expression explicitly converting data of one data type to another using the
CAST
function (CAST (<value> AS <datatype>)
). For date/time literals only, the shorthand syntax <datatype> <value> is also supported (DATE '2016-12-25'
). - Conditional expression
-
Expressions using CASE and related internal functions
- Parentheses
-
Bracket pairs
(…)
used to group expressions. Operations inside the parentheses are performed before operations outside them. When nested parentheses are used, the most deeply nested expressions are evaluated first and then the evaluations move outward through the levels of nesting. - COLLATE clause
-
Clause applied to CHAR and VARCHAR types to specify the character-set-specific collation sequence to use in string comparisons
NEXT VALUE FOR sequence
-
Expression for obtaining the next value of a specified generator (sequence). The internal
GEN_ID()
function does the same.
4.1.1. Constants
A constant is a value that is supplied directly in an SQL statement, not derived from an expression, a parameter, a column reference nor a variable. It can be a string or a number.
String Constants (Literals)
A string constant — or string literal — is a series of characters enclosed between a pair of apostrophes (“single quotes”). The maximum length of a string is 32,767 bytes; the maximum character count will be determined by the number of bytes used to encode each character.
|
The character set of a string constant is assumed to be the same as the character set of its destined storage.
String Constants in Hexadecimal Notation
From Firebird 2.5 forward, string literals can be entered in hexadecimal notation, so-called “binary strings”.
Each pair of hex digits defines one byte in the string.
Strings entered this way will have character set OCTETS
by default, but the introducer syntax can be used to force a string to be interpreted as another character set.
{x|X}'<hexstring>' <hexstring> ::= an even number of <hexdigit> <hexdigit> ::= one of 0..9, A..F, a..f
select x'4E657276656E' from rdb$database
-- returns 4E657276656E, a 6-byte 'binary' string
select _ascii x'4E657276656E' from rdb$database
-- returns 'Nerven' (same string, now interpreted as ASCII text)
select _iso8859_1 x'53E46765' from rdb$database
-- returns 'Säge' (4 chars, 4 bytes)
select _utf8 x'53C3A46765' from rdb$database
-- returns 'Säge' (4 chars, 5 bytes)
Notes
The client interface determines how binary strings are displayed to the user.
The isql utility, for example, uses upper case letters A-F, while FlameRobin uses lower case letters.
Other client programs may use other conventions, such as displaying spaces between the byte pairs: The hexadecimal notation allows any byte value (including 00) to be inserted at any position in the string. However, if you want to coerce it to anything other than OCTETS, it is your responsibility to supply the bytes in a sequence that is valid for the target character set. |
Introducer Syntax for String Literals
If necessary, a string literal may be preceded by a character set name, itself prefixed with an underscore “_”. This is known as introducer syntax. Its purpose is to inform the engine about how to interpret and store the incoming string.
Example
INSERT INTO People
VALUES (_ISO8859_1 'Hans-Jörg Schäfer')
Number Constants (Literals)
A number constant — or number literal — is any valid number in a supported notation:
-
In SQL, for numbers in the standard decimal notation, the decimal point is always represented by period (
.
, full-stop, dot) character and thousands are not separated. Inclusion of commas, blanks, etc. will cause errors. -
Exponential notation is supported. For example, 0.0000234 can be expressed as
2.34e-5
. -
Hexadecimal notation is supported by Firebird 2.5 and higher versions — see below.
Hexadecimal Notation for Numbers
From Firebird 2.5 forward, integer values can be entered in hexadecimal notation.
Numbers with 1-8 hex digits will be interpreted as type INTEGER
;
numbers with 9-16 hex digits as type BIGINT
.
0{x|X}<hexdigits> <hexdigits> ::= 1-16 of <hexdigit> <hexdigit> ::= one of 0..9, A..F, a..f
select 0x6FAA0D3 from rdb$database -- returns 117088467
select 0x4F9 from rdb$database -- returns 1273
select 0x6E44F9A8 from rdb$database -- returns 1850014120
select 0x9E44F9A8 from rdb$database -- returns -1639646808 (an INTEGER)
select 0x09E44F9A8 from rdb$database -- returns 2655320488 (a BIGINT)
select 0x28ED678A4C987 from rdb$database -- returns 720001751632263
select 0xFFFFFFFFFFFFFFFF from rdb$database -- returns -1
-
Hex numbers in the range 0 .. 7FFF FFFF are positive
INTEGER
s with values between 0 .. 2147483647 decimal. To coerce a number toBIGINT
, prepend enough zeroes to bring the total number of hex digits to nine or above. That changes the type but not the value. -
Hex numbers between 8000 0000 .. FFFF FFFF require some attention:
-
When written with eight hex digits, as in
0x9E44F9A8
, a value is interpreted as 32-bitINTEGER
. Since the leftmost bit (sign bit) is set, it maps to the negative range -2147483648 .. -1 decimal. -
With one or more zeroes prepended, as in
0x09E44F9A8
, a value is interpreted as 64-bitBIGINT
in the range 0000 0000 8000 0000 .. 0000 0000 FFFF FFFF. The sign bit is not set now, so they map to the positive range 2147483648 .. 4294967295 decimal.
Thus, in this range — and only in this range — prepending a mathematically insignificant 0 results in a totally different value. This is something to be aware of.
-
-
Hex numbers between 1 0000 0000 .. 7FFF FFFF FFFF FFFF are all positive
BIGINT
. -
Hex numbers between 8000 0000 0000 0000 .. FFFF FFFF FFFF FFFF are all negative
BIGINT
. -
A SMALLINT cannot be written in hex, strictly speaking, since even
0x1
is evaluated asINTEGER
. However, if you write a positive integer within the 16-bit range0x0000
(decimal zero) to0x7FFF
(decimal 32767) it will be converted toSMALLINT
transparently.It is possible to write to a negative
SMALLINT
in hex, using a 4-byte hex number within the range0xFFFF8000
(decimal -32768) to0xFFFFFFFF
(decimal -1).
4.1.2. SQL Operators
SQL operators comprise operators for comparing, calculating, evaluating and concatenating values.
Operator Precedence
SQL Operators are divided into four types. Each operator type has a precedence, a ranking that determines the order in which operators and the values obtained with their help are evaluated in an expression. The higher the precedence of the operator type is, the earlier it will be evaluated. Each operator has its own precedence within its type, that determines the order in which they are evaluated in an expression.
Operators with the same precedence are evaluated from left to right. To force a different evaluation order, operations can be grouped by means of parentheses.
Operator Type | Precedence | Explanation |
---|---|---|
Concatenation |
1 |
Strings are concatenated before any other operations take place |
Arithmetic |
2 |
Arithmetic operations are performed after strings are concatenated, but before comparison and logical operations |
Comparison |
3 |
Comparison operations take place after string concatenation and arithmetic operations, but before logical operations |
Logical |
4 |
Logical operators are executed after all other types of operators |
Concatenation Operator
The concatenation operator, two pipe characters known as “double pipe” — ‘||
’ — concatenates (connects together) two character strings to form a single string.
Character strings can be constants or values obtained from columns or other expressions.
SELECT LAST_NAME || ', ' || FIRST_NAME AS FULL_NAME
FROM EMPLOYEE
Arithmetic Operators
Operator | Purpose | Precedence |
---|---|---|
|
Unary plus |
1 |
|
Unary minus |
1 |
|
Multiplication |
2 |
|
Division |
2 |
|
Addition |
3 |
|
Subtraction |
3 |
UPDATE T
SET A = 4 + 1/(B-C)*D
Where operators have the same precedence, they are evaluated in left-to-right sequence. |
Comparison Operators
Operator | Purpose | Precedence |
---|---|---|
|
Is equal to, is identical to |
1 |
|
Is not equal to |
1 |
|
Is greater than |
1 |
|
Is less than |
1 |
|
Is greater than or equal to |
1 |
|
Is less than or equal to |
1 |
|
Is not greater than |
1 |
|
Is not less than |
1 |
This group also includes comparison predicates BETWEEN
, LIKE
, CONTAINING
, SIMILAR TO
, IS
and others.
IF (SALARY > 1400) THEN
…
Logical Operators
Operator | Purpose | Precedence |
---|---|---|
|
Negation of a search condition |
1 |
|
Combines two or more predicates, each of which must be true for the entire predicate to be true |
2 |
|
Combines two or more predicates, of which at least one predicate must be true for the entire predicate to be true |
3 |
IF (A < B OR (A > C AND A > D) AND NOT (C = D)) THEN …
NEXT VALUE FOR
DSQL, PSQL
NEXT VALUE FOR
returns the next value of a sequence.
SEQUENCE
is an SQL-compliant term for a generator in Firebird and its ancestor, InterBase.
The NEXT VALUE FOR
operator is equivalent to the legacy GEN_ID (…, 1)
function and is the recommended syntax for retrieving the next sequence value.
NEXT VALUE FOR sequence-name
NEW.CUST_ID = NEXT VALUE FOR CUSTSEQ;
Unlike |
4.1.3. Conditional Expressions
A conditional expression is one that returns different values according to how a certain condition is met.
It is composed by applying a conditional function construct, of which Firebird supports several.
This section describes only one conditional expression construct: CASE
.
All other conditional expressions apply internal functions derived from CASE
and are described in Conditional Functions.
CASE
DSQL, PSQL
The CASE
construct returns a single value from a number of possible ones.
Two syntactic variants are supported:
-
The simple
CASE
, comparable to a case construct in Pascal or a switch in C -
The searched
CASE
, which works like a series of “if … else if … else if
” clauses.
Simple CASE
… CASE <test-expr> WHEN <expr> THEN <result> [WHEN <expr> THEN <result> ...] [ELSE <defaultresult>] END …
When this variant is used, test-expr is compared expr 1, expr 2 etc., until a match is found and the corresponding result is returned.
If no match is found, defaultresult from the optional ELSE
clause is returned.
If there are no matches and no ELSE
clause, NULL
is returned.
The matching works identically to the “=
” operator.
That is, if test-expr is NULL
, it does not match any expr, not even an expression that resolves to NULL
.
The returned result does not have to be a literal value: it might be a field or variable name, compound expression or NULL
literal.
SELECT
NAME,
AGE,
CASE UPPER(SEX)
WHEN 'M' THEN 'Male'
WHEN 'F' THEN 'Female'
ELSE 'Unknown'
END GENDER,
RELIGION
FROM PEOPLE
A short form of the simple CASE
construct is the DECODE
function.
Searched CASE
CASE WHEN <bool_expr> THEN <result> [WHEN <bool_expr> THEN <result> …] [ELSE <defaultresult>] END
The bool_expr expression is one that gives a ternary logical result: TRUE
, FALSE
or NULL
.
The first expression to return TRUE
determines the result.
If no expressions return TRUE
, defaultresult from the optional ELSE
clause is returned as the result.
If no expressions return TRUE
and there is no ELSE
clause, the result will be NULL
.
As with the simple CASE
construct, the result need not be a literal value: it might be a field or variable name, a compound expression, or be NULL
.
CANVOTE = CASE
WHEN AGE >= 18 THEN 'Yes'
WHEN AGE < 18 THEN 'No'
ELSE 'Unsure'
END
4.1.4. NULL
in Expressions
NULL
is not a value in SQL, but a state indicating that the value of the element either is unknown or it does not exist.
It is not a zero, nor a void, nor an “empty string”, and it does not act like any value.
When you use NULL
in numeric, string or date/time expressions, the result will always be NULL
.
When you use NULL
in logical (Boolean) expressions, the result will depend on the type of the operation and on other participating values.
When you compare a value to NULL
, the result will be unknown.
Important to Note
|
Expressions Returning NULL
Expressions in this list will always return NULL
:
1 + 2 + 3 + NULL
'Home ' || 'sweet ' || NULL
MyField = NULL
MyField <> NULL
NULL = NULL
not (NULL)
If it seems difficult to understand why, remember that NULL
is a state that stands for “unknown”.
NULL
in Logical Expressions
It has already been shown that NOT (NULL)
results in NULL
.
The interaction is a bit more complicated for the logical AND
and logical OR
operators:
NULL or false = NULL
NULL or true = true
NULL or NULL = NULL
NULL and false = false
NULL and true = NULL
NULL and NULL = NULL
Up to and including Firebird 2.5.x, there is no implementation for a logical (Boolean) data type — that is coming in Firebird 3. However, there are logical expressions (predicates) that can return true, false or unknown.
(1 = NULL) or (1 <> 1) -- returns NULL
(1 = NULL) or (1 = 1) -- returns TRUE
(1 = NULL) or (1 = NULL) -- returns NULL
(1 = NULL) and (1 <> 1) -- returns FALSE
(1 = NULL) and (1 = 1) -- returns NULL
(1 = NULL) and (1 = NULL) -- returns NULL
4.1.5. Subqueries
A subquery is a special form of expression that is actually a query embedded within another query.
Subqueries are written in the same way as regular SELECT
queries, but they must be enclosed in parentheses.
Subquery expressions can be used in the following ways:
-
To specify an output column in the SELECT list
-
To obtain values or conditions for search predicates (the
WHERE
,HAVING
clauses). -
To produce a set that the enclosing query can select from, as though were a regular table or view. Subqueries like this appear in the FROM clause (derived tables) or in a Common Table Expression (CTE)
Correlated Subqueries
A subquery can be correlated. A query is correlated when the subquery and the main query are interdependent. To process each record in the subquery, it is necessary to fetch a record in the main query; i.e., the subquery fully depends on the main query.
SELECT *
FROM Customers C
WHERE EXISTS
(SELECT *
FROM Orders O
WHERE C.cnum = O.cnum
AND O.adate = DATE '10.03.1990');
When subqueries are used to get the values of the output column in the SELECT list, a subquery must return a scalar result.
Scalar Results
Subqueries used in search predicates, other than existential and quantified predicates, must return a scalar result; that is, not more than one column from not more than one matching row or aggregation. If the result would return more, a run-time error will occur (“Multiple rows in a singleton select…”).
Although it is reporting a genuine error, the message can be slightly misleading. A “singleton SELECT” is a query that must not be capable of returning more than one row. However, “singleton” and “scalar” are not synonymous: not all singleton SELECTS are required to be scalar; and single-column selects can return multiple rows for existential and quantified predicates. |
-
A subquery as the output column in a
SELECT
list:SELECT e.first_name, e.last_name, (SELECT sh.new_salary FROM salary_history sh WHERE sh.emp_no = e.emp_no ORDER BY sh.change_date DESC ROWS 1) AS last_salary FROM employee e
-
A subquery in the
WHERE
clause for obtaining the employee’s maximum salary and filtering by it:SELECT e.first_name, e.last_name, e.salary FROM employee e WHERE e.salary = ( SELECT MAX(ie.salary) FROM employee ie )
4.2. Predicates
A predicate is a simple expression asserting some fact, let’s call it P
.
If P
resolves as TRUE, it succeeds.
If it resolves to FALSE or NULL (UNKNOWN), it fails.
A trap lies here, though: suppose the predicate, P
, returns FALSE.
In this case NOT(P)
will return TRUE.
On the other hand, if P
returns NULL (unknown), then NOT(P)
returns NULL as well.
In SQL, predicates can appear in CHECK
constraints, WHERE
and HAVING
clauses, CASE
expressions, the IIF()
function and in the ON
condition of JOIN
clauses.
4.2.1. Assertions
An assertion is a statement about the data that, like a predicate, can resolve to TRUE, FALSE or NULL.
Assertions consist of one or more predicates, possibly negated using NOT
and connected by AND
and OR
operators.
Parentheses may be used for grouping predicates and controlling evaluation order.
A predicate may embed other predicates. Evaluation sequence is in the outward direction, i.e., the innermost predicates are evaluated first. Each “level” is evaluated in precedence order until the truth of the ultimate assertion is resolved.
4.2.2. Comparison Predicates
A comparison predicate consists of two expressions connected with a comparison operator. There are six traditional comparison operators:
=, >, <, >=, <=, <>
For the complete list of comparison operators with their variant forms, see Comparison Operators.
If one of the sides (left or right) of a comparison predicate has NULL
in it, the value of the predicate will be UNKNOWN.
-
Retrieve information about computers with the CPU frequency not less than 500 MHz and the price lower than $800:
SELECT * FROM Pc WHERE speed >= 500 AND price < 800;
-
Retrieve information about all dot matrix printers that cost less than $300:
SELECT * FROM Printer WHERE ptrtype = 'matrix' AND price < 300;
-
The following query will return no data, even if there are printers with no type specified for them, because a predicate that compares
NULL
withNULL
returnsNULL
:SELECT * FROM Printer WHERE ptrtype = NULL AND price < 300;
On the other hand,
ptrtype
can be tested forNULL
and return a result: it is just that it is not a comparison test:SELECT * FROM Printer WHERE ptrtype IS NULL AND price < 300;
— see
IS [NOT] NULL
.
Note about String Comparison
When |
Other Comparison Predicates
Other comparison predicates are marked by keyword symbols.
BETWEEN
DSQL, PSQL, ESQL
<value> [NOT] BETWEEN <value_1> AND <value_2>
The BETWEEN
predicate tests whether a value falls within a specified range of two values.
(NOT BETWEEN
tests whether the value does not fall within that range.)
The operands for BETWEEN
predicate are two arguments of compatible data types.
Unlike in some other DBMS, the BETWEEN
predicate in Firebird is not symmetrical — if the lower value is not the first argument, the BETWEEN
predicate will always return FALSE.
The search is inclusive (the values represented by both arguments are included in the search).
In other words, the BETWEEN
predicate could be rewritten:
<value> >= <value_1> AND <value> <= <value_2>
When BETWEEN
is used in the search conditions of DML queries, the Firebird optimizer can use an index on the searched column, if it is available.
SELECT *
FROM EMPLOYEE
WHERE HIRE_DATE BETWEEN date '01.01.1992' AND CURRENT_DATE
LIKE
DSQL, PSQL, ESQL
<match value> [NOT] LIKE <pattern> [ESCAPE <escape character>] <match value> ::= character-type expression <pattern> ::= search pattern <escape character> ::= escape character
The LIKE
predicate compares the character-type expression with the pattern defined in the second expression.
Case- or accent-sensitivity for the comparison is determined by the collation that is in use.
A collation can be specified for either operand, if required.
Two wildcard symbols are available for use in the search pattern:
-
the percentage symbol (
%
) will match any sequence of zero or more characters in the tested value -
the underscore character (
_
) will match any single character in the tested value
If the tested value matches the pattern, taking into account wildcard symbols, the predicate is TRUE.
ESCAPE
Character OptionIf the search string contains either of the wildcard symbols, the ESCAPE
clause can be used to specify an escape character.
The escape character must precede the ‘%
’ or ‘_
’} symbol in the search string, to indicate that the symbol is to be interpreted as a literal character.
LIKE
-
Find the numbers of departments whose names start with the word “Software”:
SELECT DEPT_NO FROM DEPT WHERE DEPT_NAME LIKE 'Software%';
It is possible to use an index on the DEPT_NAME field if it exists.
AboutLIKE
and the OptimizerActually, the
LIKE
predicate does not use an index. However, if the predicate takes the form ofLIKE 'string%'
, it will be converted to theSTARTING WITH
predicate, which will use an index.So, if you need to search for the beginning of a string, it is recommended to use the
STARTING WITH
predicate instead of theLIKE
predicate. -
Search for employees whose names consist of 5 letters, start with the letters “Sm” and end with “th”. The predicate will be true for such names as “Smith” and “Smyth”.
SELECT first_name FROM employee WHERE first_name LIKE 'Sm_th'
-
Search for all clients whose address contains the string “Rostov”:
SELECT * FROM CUSTOMER WHERE ADDRESS LIKE '%Rostov%'
If you need to do a case-insensitive search for something enclosed inside a string (
LIKE '%Abc%'
), use of theCONTAINING
predicate is recommended, in preference to theLIKE
predicate. -
Search for tables containing the underscore character in their names. The ‘
#
’ character is used as the escape character:SELECT RDB$RELATION_NAME FROM RDB$RELATIONS WHERE RDB$RELATION_NAME LIKE '%#_%' ESCAPE '#'
STARTING WITH
DSQL, PSQL, ESQL
<value> [NOT] STARTING WITH <value>
The STARTING WITH
predicate searches for a string or a string-like type that starts with the characters in its value argument.
The search is case-sensitive.
When STARTING WITH
is used in the search conditions of DML queries, the Firebird optimizer can use an index on the searched column, if it exists.
Search for employees whose last names start with “Jo”:
SELECT LAST_NAME, FIRST_NAME
FROM EMPLOYEE
WHERE LAST_NAME STARTING WITH 'Jo'
CONTAINING
DSQL, PSQL, ESQL
<value> [NOT] CONTAINING <value>
The CONTAINING
predicate searches for a string or a string-like type looking for the sequence of characters that matches its argument.
It can be used for an alphanumeric (string-like) search on numbers and dates.
A CONTAINING
search is not case-sensitive.
However, if an accent-sensitive collation is in use then the search will be accent-sensitive.
When CONTAINING
is used in the search conditions of DML queries, the Firebird optimizer can use an index on the searched column, if a suitable one exists.
-
Search for projects whose names contain the substring “Map”:
SELECT * FROM PROJECT WHERE PROJ_NAME CONTAINING 'Map';
Two rows with the names “AutoMap” and “MapBrowser port” are returned.
-
Search for changes in salaries with the date containing number 84 (in this case, it means changes that took place in 1984):
SELECT * FROM SALARY_HISTORY WHERE CHANGE_DATE CONTAINING 84;
SIMILAR TO
DSQL, PSQL
string-expression [NOT] SIMILAR TO <pattern> [ESCAPE <escape-char>] <pattern> ::= an SQL regular expression <escape-char> ::= a single character
SIMILAR TO
matches a string against an SQL regular expression pattern.
Unlike in some other languages, the pattern must match the entire string in order to succeed — matching a substring is not enough.
If any operand is NULL
, the result is NULL
.
Otherwise, the result is TRUE
or FALSE
.
The following syntax defines the SQL regular expression format. It is a complete and correct top-down definition. It is also highly formal, rather long and probably perfectly fit to discourage everybody who hasn’t already some experience with regular expressions (or with highly formal, rather long top-down definitions). Feel free to skip it and read the next section, Building Regular Expressions, which uses a bottom-up approach, aimed at the rest of us.
<regular expression> ::= <regular term> ['|' <regular term> ...] <regular term> ::= <regular factor> ... <regular factor> ::= <regular primary> [<quantifier>] <quantifier> ::= ? | * | + | '{' <m> [,[<n>]] '}' <m>, <n> ::= unsigned int, with <m> <= <n> if both present <regular primary> ::= <character> | <character class> | % | (<regular expression>) <character> ::= <escaped character> | <non-escaped character> <escaped character> ::= <escape-char> <special character> | <escape-char> <escape-char> <special character> ::= any of the characters []()|^-+*%_?{} <non-escaped character> ::= any character that is not a <special character> and not equal to <escape-char> (if defined) <character class> ::= '_' | '[' <member> ... ']' | '[^' <non-member> ... ']' | '[' <member> ... '^' <non-member> ... ']' <member>, <non-member> ::= <character> | <range> | <predefined class> <range> ::= <character>-<character> <predefined class> ::= '[:' <predefined class name> ':]' <predefined class name> ::= ALPHA | UPPER | LOWER | DIGIT | ALNUM | SPACE | WHITESPACE
In this section are the elements and rules for building SQL regular expressions.
Within regular expressions, most characters represent themselves. The only exceptions are the special characters below:
[ ] ( ) | ^ - + * % _ ? { }
... and the escape character, if it is defined.
A regular expression that contains no special or escape characters matches only strings that are identical to itself (subject to the collation in use).
That is, it functions just like the ‘=
’ operator:
'Apple' similar to 'Apple' -- true
'Apples' similar to 'Apple' -- false
'Apple' similar to 'Apples' -- false
'APPLE' similar to 'Apple' -- depends on collation
The known SQL wildcards ‘_
’ and ‘%
’ match any single character and a string of any length, respectively:
'Birne' similar to 'B_rne' -- true
'Birne' similar to 'B_ne' -- false
'Birne' similar to 'B%ne' -- true
'Birne' similar to 'Bir%ne%' -- true
'Birne' similar to 'Birr%ne' -- false
Notice how ‘%
’ also matches the empty string.
A bunch of characters enclosed in brackets define a character class. A character in the string matches a class in the pattern if the character is a member of the class:
'Citroen' similar to 'Cit[arju]oen' -- true
'Citroen' similar to 'Ci[tr]oen' -- false
'Citroen' similar to 'Ci[tr][tr]oen' -- true
As can be seen from the second line, the class only matches a single character, not a sequence.
Within a class definition, two characters connected by a hyphen define a range. A range comprises the two endpoints and all the characters that lie between them in the active collation. Ranges can be placed anywhere in the class definition without special delimiters to keep them apart from the other elements.
'Datte' similar to 'Dat[q-u]e' -- true
'Datte' similar to 'Dat[abq-uy]e' -- true
'Datte' similar to 'Dat[bcg-km-pwz]e' -- false
The following predefined character classes can also be used in a class definition:
[:ALPHA:]
-
Latin letters a..z and A..Z. With an accent-insensitive collation, this class also matches accented forms of these characters.
[:DIGIT:]
-
Decimal digits 0..9.
[:ALNUM:]
-
Union of
[:ALPHA:]
and[:DIGIT:]
. [:UPPER:]
-
Uppercase Latin letters A..Z. Also matches lowercase with case-insensitive collation and accented forms with accent-insensitive collation.
[:LOWER:]
-
Lowercase Latin letters a..z. Also matches uppercase with case-insensitive collation and accented forms with accent-insensitive collation.
[:SPACE:]
-
Matches the space character (ASCII 32).
[:WHITESPACE:]
-
Matches horizontal tab (ASCII 9), linefeed (ASCII 10), vertical tab (ASCII 11), formfeed (ASCII 12), carriage return (ASCII 13) and space (ASCII 32).
Including a predefined class has the same effect as including all its members. Predefined classes are only allowed within class definitions. If you need to match against a predefined class and nothing more, place an extra pair of brackets around it.
'Erdbeere' similar to 'Erd[[:ALNUM:]]eere' -- true
'Erdbeere' similar to 'Erd[[:DIGIT:]]eere' -- false
'Erdbeere' similar to 'Erd[a[:SPACE:]b]eere' -- true
'Erdbeere' similar to [[:ALPHA:]] -- false
'E' similar to [[:ALPHA:]] -- true
If a class definition starts with a caret, everything that follows is excluded from the class. All other characters match:
'Framboise' similar to 'Fra[^ck-p]boise' -- false
'Framboise' similar to 'Fr[^a][^a]boise' -- false
'Framboise' similar to 'Fra[^[:DIGIT:]]boise' -- true
If the caret is not placed at the start of the sequence, the class contains everything before the caret, except for the elements that also occur after the caret:
'Grapefruit' similar to 'Grap[a-m^f-i]fruit' -- true
'Grapefruit' similar to 'Grap[abc^xyz]fruit' -- false
'Grapefruit' similar to 'Grap[abc^de]fruit' -- false
'Grapefruit' similar to 'Grap[abe^de]fruit' -- false
'3' similar to '[[:DIGIT:]^4-8]' -- true
'6' similar to '[[:DIGIT:]^4-8]' -- false
Lastly, the already mentioned wildcard ‘_
’ is a character class of its own, matching any single character.
A question mark (‘?
’) immediately following a character or class indicates that the preceding item may occur 0 or 1 times in order to match:
'Hallon' similar to 'Hal?on' -- false
'Hallon' similar to 'Hal?lon' -- true
'Hallon' similar to 'Halll?on' -- true
'Hallon' similar to 'Hallll?on' -- false
'Hallon' similar to 'Halx?lon' -- true
'Hallon' similar to 'H[a-c]?llon[x-z]?' -- true
An asterisk (‘*
’) immediately following a character or class indicates that the preceding item may occur 0 or more times in order to match:
'Icaque' similar to 'Ica*que' -- true
'Icaque' similar to 'Icar*que' -- true
'Icaque' similar to 'I[a-c]*que' -- true
'Icaque' similar to '_*' -- true
'Icaque' similar to '[[:ALPHA:]]*' -- true
'Icaque' similar to 'Ica[xyz]*e' -- false
A plus sign (‘+
’) immediately following a character or class indicates that the preceding item must occur 1 or more times in order to match:
'Jujube' similar to 'Ju_+' -- true
'Jujube' similar to 'Ju+jube' -- true
'Jujube' similar to 'Jujuber+' -- false
'Jujube' similar to 'J[jux]+be' -- true
'Jujube' sililar to 'J[[:DIGIT:]]+ujube' -- false
If a character or class is followed by a number enclosed in braces (‘{
’ and ‘}
’), it must be repeated exactly that number of times in order to match:
'Kiwi' similar to 'Ki{2}wi' -- false
'Kiwi' similar to 'K[ipw]{2}i' -- true
'Kiwi' similar to 'K[ipw]{2}' -- false
'Kiwi' similar to 'K[ipw]{3}' -- true
If the number is followed by a comma (‘,
’), the item must be repeated at least that number of times in order to match:
'Limone' similar to 'Li{2,}mone' -- false
'Limone' similar to 'Li{1,}mone' -- true
'Limone' similar to 'Li[nezom]{2,}' -- true
If the braces contain two numbers separated by a comma, the second number not smaller than the first, then the item must be repeated at least the first number and at most the second number of times in order to match:
'Mandarijn' similar to 'M[a-p]{2,5}rijn' -- true
'Mandarijn' similar to 'M[a-p]{2,3}rijn' -- false
'Mandarijn' similar to 'M[a-p]{2,3}arijn' -- true
The quantifiers ‘?
’, ‘*
’ and ‘+
’ are shorthand for {0,1}
, {0,}
and {1,}
, respectively.
Regular expression terms can be OR’ed with the ‘|
’ operator.
A match is made when the argument string matches at least one of the terms:
'Nektarin' similar to 'Nek|tarin' -- false
'Nektarin' similar to 'Nektarin|Persika' -- true
'Nektarin' similar to 'M_+|N_+|P_+' -- true
One or more parts of the regular expression can be grouped into subexpressions (also called subpatterns) by placing them between parentheses (‘(
’ and ‘)
’).
A subexpression is a regular expression in its own right.
It can contain all the elements allowed in a regular expression, and can also have quantifiers added to it.
'Orange' similar to 'O(ra|ri|ro)nge' -- true
'Orange' similar to 'O(r[a-e])+nge' -- true
'Orange' similar to 'O(ra){2,4}nge' -- false
'Orange' similar to 'O(r(an|in)g|rong)?e' -- true
In order to match against a character that is special in regular expressions, that character has to be escaped. There is no default escape character; rather, the user specifies one when needed:
'Peer (Poire)' similar to 'P[^ ]+ \(P[^ ]+\)' escape '\' -- true
'Pera [Pear]' similar to 'P[^ ]+ #[P[^ ]+#]' escape '#' -- true
'Päron-äppledryck' similar to 'P%$-ä%' escape '$' -- true
'Pärondryck' similar to 'P%--ä%' escape '-' -- false
The last line demonstrates that the escape character can also escape itself, if needed.
IS [NOT] DISTINCT FROM
DSQL, PSQL
<operand1> IS [NOT] DISTINCT FROM <operand2>
Two operands are considered DISTINCT if they have a different value or if one of them is NULL
and the other non-null.
They are NOT DISTINCT if they have the same value or if both of them are NULL
.
IS [NOT] NULL
DSQL, PSQL, ESQL
<value> IS [NOT] NULL
Since NULL
is not a value, these operators are not comparison operators.
The IS [NOT] NULL
predicate tests the assertion that the expression on the left side has a value (IS NOT NULL) or has no value (IS NULL).
Search for sales entries that have no shipment date set for them:
SELECT * FROM SALES
WHERE SHIP_DATE IS NULL;
Note regarding the
IS predicatesUp to and including Firebird 2.5, the |
4.2.3. Existential Predicates
This group of predicates includes those that use subqueries to submit values for all kinds of assertions in search conditions.
Existential predicates are so called because they use various methods to test for the existence or non-existence of some assertion, returning TRUE
if the existence or non-existence is confirmed or FALSE
otherwise.
EXISTS
DSQL, PSQL, ESQL
[NOT] EXISTS (<select_stmt>)
The EXISTS
predicate uses a subquery expression as its argument.
It returns TRUE
if the subquery result would contain at least one row; otherwise it returns FALSE
.
NOT EXISTS
returns FALSE
if the subquery result would contain at least one row; it returns TRUE
otherwise.
The subquery can specify multiple columns, or |
-
Find those employees who have projects.
SELECT * FROM employee WHERE EXISTS(SELECT * FROM employee_project ep WHERE ep.emp_no = employee.emp_no)
-
Find those employees who have no projects.
SELECT * FROM employee WHERE NOT EXISTS(SELECT * FROM employee_project ep WHERE ep.emp_no = employee.emp_no)
IN
DSQL, PSQL, ESQL
<value> [NOT] IN (<select_stmt> | <value_list>) <value_list> ::= <value_1> [, <value_2> …]
The IN
predicate tests whether the value of the expression on the left side is present in the set of values specified on the right side.
The set of values cannot have more than 1500 items.
The IN
predicate can be replaced with the following equivalent forms:
(<value> = <value_1> [OR <value> = <value_2> …]) <value> = { ANY | SOME } (<select_stmt>)
When the IN
predicate is used in the search conditions of DML queries, the Firebird optimizer can use an index on the searched column, if a suitable one exists.
In its second form, the IN
predicate tests whether the value of the expression on the left side is present — or not present, if NOT IN
is used — in the result of the executed subquery on the right side.
The subquery must be specified to result in only one column, otherwise the error “count of column list and variable list do not match” will occur.
Queries specified using the IN
predicate with a subquery can be replaced with a similar query using the EXISTS
predicate.
For instance, the following query:
SELECT
model, speed, hd
FROM PC
WHERE
model IN (SELECT model
FROM product
WHERE maker = 'A');
can be replaced with a similar one using the EXISTS predicate:
SELECT
model, speed, hd
FROM PC
WHERE
EXISTS (SELECT *
FROM product
WHERE maker = 'A'
AND product.model = PC.model);
However, a query using NOT IN
with a subquery does not always give the same result as its NOT EXISTS
counterpart.
The reason is that EXISTS
always returns TRUE or FALSE, whereas IN
returns NULL
in one of these two cases:
-
when the test value is
NULL
and theIN ()
list is not empty -
when the test value has no match in the
IN ()
list and at least one list element isNULL
It is in only these two cases that IN ()
will return NULL
while the corresponding EXISTS
predicate will return FALSE
('no matching row found').
In a search or, for example, an IF (…)
statement, both results mean “failure” and it makes no difference to the outcome.
But, for the same data, NOT IN ()
will return NULL
, while NOT EXISTS
will return TRUE
, leading to opposite results.
As an example, suppose you have the following query:
-- Looking for people who were not born
-- on the same day as any famous New York citizen
SELECT P1.name AS NAME
FROM Personnel P1
WHERE P1.birthday NOT IN (SELECT C1.birthday
FROM Celebrities C1
WHERE C1.birthcity = 'New York');
Now, assume that the NY celebrities list is not empty and contains at least one NULL birthday.
Then for every citizen who does not share his birthday with a NY celebrity, NOT IN
will return NULL
, because that is what IN
does.
The search condition is thereby not satisfied and the citizen will be left out of the SELECT
result, which is wrong.
For citizens whose birthday does match with a celebrity’s birthday, NOT IN
will correctly return FALSE
, so they will be left out too, and no rows will be returned.
If the NOT EXISTS
form is used:
-- Looking for people who were not born
-- on the same day as any famous New York citizen
SELECT P1.name AS NAME
FROM Personnel P1
WHERE NOT EXISTS (SELECT *
FROM Celebrities C1
WHERE C1.birthcity = 'New York'
AND C1.birthday = P1.birthday);
non-matches will have a NOT EXISTS
result of TRUE
and their records will be in the result set.
Advice
If there is any chance of |
-
Find employees with the names “Pete”, “Ann” and “Roger”:
SELECT * FROM EMPLOYEE WHERE FIRST_NAME IN ('Pete', 'Ann', 'Roger');
-
Find all computers that have models whose manufacturer starts with the letter “A”:
SELECT model, speed, hd FROM PC WHERE model IN (SELECT model FROM product WHERE maker STARTING WITH 'A');
SINGULAR
DSQL, PSQL, ESQL
[NOT] SINGULAR (<select_stmt>)
The SINGULAR
predicate takes a subquery as its argument and evaluates it as TRUE if the subquery returns exactly one result row; otherwise the predicate is evaluated as FALSE.
The subquery may list several output columns since the rows are not returned anyway.
They are only tested for (singular) existence.
For brevity, people usually specify ‘SELECT *
’.
The SINGULAR
predicate can return only two values: TRUE
or FALSE
.
Find those employees who have only one project.
SELECT *
FROM employee
WHERE SINGULAR(SELECT *
FROM employee_project ep
WHERE ep.emp_no = employee.emp_no)
4.2.4. Quantified Subquery Predicates
A quantifier is a logical operator that sets the number of objects for which this assertion is true. It is not a numeric quantity, but a logical one that connects the assertion with the full set of possible objects. Such predicates are based on logical universal and existential quantifiers that are recognised in formal logic.
In subquery expressions, quantified predicates make it possible to compare separate values with the results of subqueries; they have the following common form:
<value expression> <comparison operator> <quantifier> <subquery>
ALL
DSQL, PSQL, ESQL
<value> <op> ALL (<select_stmt>)
When the ALL
quantifier is used, the predicate is TRUE if every value returned by the subquery satisfies the condition in the predicate of the main query.
Show only those clients whose ratings are higher than the rating of every client in Paris.
SELECT c1.*
FROM Customers c1
WHERE c1.rating > ALL
(SELECT c2.rating
FROM Customers c2
WHERE c2.city = 'Paris')
If the subquery returns an empty set, the predicate is TRUE for every left-side value, regardless of the operator. This may appear to be contradictory, because every left-side value will thus be considered both smaller and greater than, both equal to and unequal to, every element of the right-side stream. Nevertheless, it aligns perfectly with formal logic: if the set is empty, the predicate is true 0 times, i.e., for every row in the set. |
ANY
and SOME
DSQL, PSQL, ESQL
<value> <op> {ANY | SOME} (<select_stmt>)
The quantifiers ANY
and SOME
are identical in their behaviour.
Apparently, both are present in the SQL standard so that they could be used interchangeably in order to improve the readability of operators.
When the ANY
or the SOME
quantifier is used, the predicate is TRUE if any of the values returned by the subquery satisfies the condition in the predicate of the main query.
If the subquery would return no rows at all, the predicate is automatically considered as FALSE.
Show only those clients whose ratings are higher than those of one or more clients in Rome.
SELECT *
FROM Customers
WHERE rating > ANY
(SELECT rating
FROM Customers
WHERE city = 'Rome')
5. Data Definition (DDL) Statements
DDL is the data definition language subset of Firebird’s SQL language. DDL statements are used to create, modify and delete database objects that have been created by users. When a DDL statement is committed, the metadata for the object are created, changed or deleted.
5.1. DATABASE
This section describes how to create a database, connect to an existing database, alter the file structure of a database and how to delete one. It also explains how to back up a database in two quite different ways and how to switch the database to the “copy-safe” mode for performing an external backup safely.
5.1.1. CREATE DATABASE
Creating a new database
DSQL, ESQL
CREATE {DATABASE | SCHEMA} <filespec> [USER 'username' [PASSWORD 'password']] [PAGE_SIZE [=] size] [LENGTH [=] num [PAGE[S]] [SET NAMES 'charset'] [DEFAULT CHARACTER SET default_charset [COLLATION collation]] -- not supported in ESQL [<sec_file> [<sec_file> ...]] [DIFFERENCE FILE 'diff_file'] -- not supported in ESQL <filespec> ::= "'" [server_spec]{filepath | db_alias} "'" <server_spec> ::= servername[/{port|service}]: | \\servername\ <sec_file> ::= FILE 'filepath' [LENGTH [=] num [PAGE[S]] [STARTING [AT [PAGE]] pagenum]
Parameter | Description |
---|---|
filespec |
File specification for primary database file |
server_spec |
Remote server specification in TCP/IP or Windows Networking style. Optionally includes a port number or service name |
filepath |
Full path and file name including its extension. The file name must be specified according to the rules of the platform file system being used. |
db_alias |
Database alias previously created in the |
servername |
Host name or IP address of the server where the database is to be created |
username |
User name of the owner of the new database. It may consist of up to 31 characters. Case-insensitive |
password |
Password of the user name as the database owner. The maximum length is 31 characters; however only the first 8 characters are considered. Case-sensitive |
size |
Page size for the database, in bytes. Possible values are 4096 (the default), 8192 and 16384 |
num |
Maximum size of the primary database file, or a secondary file, in pages |
charset |
Specifies the character set of the connection available to a client connecting after the database is successfully created. Single quotes are required |
default_charset |
Specifies the default character set for string data types |
collation |
Default collation for the default character set |
sec_file |
File specificaton for a secondary file |
pagenum |
Starting page number for a secondary database file |
diff_file |
File path and name for DIFFERENCE files (.delta files) |
The CREATE DATABASE
statement creates a new database.
You can use CREATE DATABASE
or CREATE SCHEMA
.
They are synonymous.
A database may consist of one or several files. The first (main) file is called the primary file, subsequent files are called secondary file[s].
Multi-file Databases
Nowadays, multi-file databases are considered an anachronism. It made sense to use multi-file databases on old file systems where the size of any file is limited. For instance, you could not create a file larger than 4 GB on FAT32. |
The primary file specification is the name of the database file and its extension with the full path to it according to the rules of the OS platform file system being used. The database file must not exist at the moment when the database is being created. If it does exist, you will get an error message and the database will not be created.
If the full path to the database is not specified, the database will be created in one of the system directories. The particular directory depends on the operating system. For this reason, unless you have a strong reason to prefer that situation, always specify the absolute path, when creating either the database or an alias for it.
Using a Database Alias
You can use aliases instead of the full path to the primary database file. Aliases are defined in the [path]` aliases.conf` file in the following format:
alias = filepath
Creating a Database Remotely
If you create a database on a remote server, you should specify the remote server specification. The remote server specification depends on the protocol being used. If you use the TCP/IP protocol to create a database, the primary file specification should look like this:
_servername_[/{_port_|_service_}]:{_filepath_ | _db_alias_}
If you use the Named Pipes protocol to create a database on a Windows server, the primary file specification should look like this:
\\servername\{filepath | db_alias}
Optional Parameters for CREATE DATABASE
USER
andPASSWORD
-
Clauses for specifying the user name and the password, respectively, of an existing user in the security database
security2.fdb
. You do not have to specify the username and password if theISC_USER
andISC_PASSWORD
environment variables are set. The user specified in the process of creating the database will be its owner. This will be important when considering database and object privileges. PAGE_SIZE
-
Clause for specifying the database page size. This size will be set for the primary file and all secondary files of the database. If you specify the database page size less than 4,096, it will be changed automatically to the default page size, 4,096. Other values not equal to either 4,096, 8,192 or 16,384 will be changed to the closest smaller supported value. If the database page size is not specified, it is set to the default value of 4,096.
LENGTH
-
Clause specifying the maximum size of the primary or secondary database file, in pages. When a database is created, its primary and secondary files will occupy the minimum number of pages necessary to store the system data, regardless of the value specified in the
LENGTH
clause. TheLENGTH
value does not affect the size of the only (or last, in a multi-file database) file. The file will keep increasing its size automatically when necessary. SET NAMES
-
Clause specifying the character set of the connection available after the database is successfully created. The character set
NONE
is used by default. Notice that the character set should be enclosed in a pair of apostrophes (single quotes). DEFAULT CHARACTER SET
-
Clause specifying the default character set for creating data structures of string data types. Character sets are applied to
CHAR
,VARCHAR
andBLOB TEXT
data types. The character setNONE
is used by default. It is also possible to specify the defaultCOLLATION
for the default character set, making that collation sequence the default for the default character set. The default will be used for the entire database except where an alternative character set, with or without a specified collation, is used explicitly for a field, domain, variable, cast expression, etc. STARTING AT
-
Clause that specifies the database page number at which the next secondary database file should start. When the previous file is completely filled with data according to the specified page number, the system will start adding new data to the next database file.
DIFFERENCE FILE
-
Clause specifying the path and name for the file delta that stores any mutations to the database file after it has been switched to the “copy-safe” mode by the
ALTER DATABASE BEGIN BACKUP
statement. For the detailed description of this clause, seeALTER DATABASE
. SET SQL DIALECT
-
Databases are created in Dialect 3 by default. For the database to be created in SQL dialect 1, you will need to execute the statement
SET SQL DIALECT 1
from script or the client application, e.g. in isql, before theCREATE DATABASE
statement.
Examples Using CREATE DATABASE
-
Creating a database in Windows, located on disk D with a page size of 8,192. The owner of the database will be the user wizard. The database will be in Dialect 1 and it will use
WIN1251
as its default character set.SET SQL DIALECT 1; CREATE DATABASE 'D:\test.fdb' USER 'wizard' PASSWORD 'player' PAGE_SIZE = 8192 DEFAULT CHARACTER SET WIN1251;
-
Creating a database in the Linux operating system with a page size of 4,096. The owner of the database will be the user wizard. The database will be in Dialect 3 and it will use
UTF8
as its default character set, withUNICODE_CI_AI
as the default collation.CREATE DATABASE '/home/firebird/test.fdb' USER 'wizard' PASSWORD 'player' DEFAULT CHARACTER SET UTF8 COLLATION UNICODE_CI_AI;
-
Creating a database on the remote server “baseserver” with the path specified in the alias “test” that has been defined previously in the file
aliases.conf
. The TCP/IP protocol is used. The owner of the database will be the user wizard. The database will be in Dialect 3 and will useUTF8
as its default character set.CREATE DATABASE 'baseserver:test' USER 'wizard' PASSWORD 'player' DEFAULT CHARACTER SET UTF8;
-
Creating a database in Dialect 3 with
UTF8
as its default character set. The primary file will contain up to 10,000 pages with a page size of 8,192. As soon as the primary file has reached the maximum number of pages, Firebird will start allocating pages to the secondary filetest.fdb2
. If that file is filled up to its maximum as well,test.fdb3
becomes the recipient of all new page allocations. As the last file, it has no page limit imposed on it by Firebird. New allocations will continue for as long as the file system allows it or until the storage device runs out of free space. If aLENGTH
parameter were supplied for this last file, it would be ignored.SET SQL DIALECT 3; CREATE DATABASE 'baseserver:D:\test.fdb' USER 'wizard' PASSWORD 'player' PAGE_SIZE = 8192 DEFAULT CHARACTER SET UTF8 FILE 'D:\test.fdb2' STARTING AT PAGE 10001 FILE 'D:\test.fdb3' STARTING AT PAGE 20001;
-
Creating a database in Dialect 3 with
UTF8
as its default character set. The primary file will contain up to 10,000 pages with a page size of 8,192. As far as file size and the use of secondary files are concerned, this database will behave exactly like the one in the previous example.SET SQL DIALECT 3; CREATE DATABASE 'baseserver:D:\test.fdb' USER 'wizard' PASSWORD 'player' PAGE_SIZE = 8192 LENGTH 10000 PAGES DEFAULT CHARACTER SET UTF8 FILE 'D:\test.fdb2' FILE 'D:\test.fdb3' STARTING AT PAGE 20001;
5.1.2. ALTER DATABASE
Altering the file organisation of a database or toggling its “copy-safe” state
DSQL — both functions. ESQL — file reorganisation only
ALTER {DATABASE | SCHEMA} [<add_sec_clause> [<add_sec_clause> ...]] [ADD DIFFERENCE FILE 'diff_file' | DROP DIFFERENCE FILE] [{BEGIN | END} BACKUP] <add_sec_clause> ::= ADD <sec_file> [<sec_file> ...] <sec_file> ::= FILE 'filepath' [STARTING [AT [PAGE]] pagenum] [LENGTH [=] num [PAGE[S]]
Multiple files can be added in one ADD clause:
Multiple |
Parameter | Description |
---|---|
add_sec_clause |
Adding a secondary database file |
sec_file |
File specification for secondary file |
filepath |
Full path and file name of the delta file or the secondary database file |
pagenum |
Page number from which the secondary database file is to start |
num |
Maximum size of the secondary file in pages |
diff_file |
File path and name of the .delta file (difference file) |
The ALTER DATABASE
statement can:
-
add secondary files to a database
-
switch a single-file database into and out of the “copy-safe” mode (DSQL only)
-
set or unset the path and name of the delta file for physical backups (DSQL only)
Only administrators have the authority to use ALTER DATABASE
.
Parameters for ALTER DATABASE
ADD FILE
-
Adds a secondary file to the database. It is necessary to specify the full path to the file and the name of the secondary file. The description for the secondary file is similar to the one given for the
CREATE DATABASE
statement. ADD DIFFERENCE FILE
-
specifies the path and name of the delta file that stores any mutations to the database whenever it is switched to the “copy-safe” mode. This clause does not actually add any file. It just overrides the default name and path of the .delta file. To change the existing settings, you should delete the previously specified description of the .delta file using the
DROP DIFFERENCE FILE
clause before specifying the new description of the delta file. If the path and name of the .delta file are not overridden, the file will have the same path and name as the database, but with the.delta
file extension.If only a file name is specified, the .delta file will be created in the current directory of the server. On Windows, this will be the system directory — a very unwise location to store volatile user files and contrary to Windows file system rules.
DROP DIFFERENCE FILE
-
This is the clause that deletes the description (path and name) of the .delta file specified previously in the
ADD DIFFERENCE FILE
clause. The file is not actually deleted.DROP DIFFERENCE FILE
deletes the path and name of the .delta file from the database header. Next time the database is switched to the “copy-safe” mode, the default values will be used (i.e. the same path and name as those of the database, but with the .delta extension). BEGIN BACKUP
-
This is the clause that switches the database to the “copy-safe” mode.
ALTER DATABASE
with this clause freezes the main database file, making it possible to back it up safely using file system tools, even if users are connected and performing operations with data. Until the backup state of the database is reverted to NORMAL, all changes made to the database will be written to the .delta (difference) file.Despite its syntax, a statement with the
BEGIN BACKUP
clause does not start a backup process but just creates the conditions for doing a task that requires the database file to be read-only temporarily. END BACKUP
-
This is the clause used to switch the database from the “copy-safe” mode to the normal mode. A statement with this clause merges the .delta file with the main database file and restores the normal operation of the database. Once the
END BACKUP
process starts, the conditions no longer exist for creating safe backups by means of file system tools.
Use of Making a safe backup with the gbak utility remains possible at all times, although it is not recommended to run gbak while the database is in LOCKED or MERGE state. |
Examples of ALTER DATABASE
Usage
-
Adding a secondary file to the database. As soon as 30000 pages are filled in the previous primary or secondary file, the Firebird engine will start adding data to the secondary file
test4.fdb
.ALTER DATABASE ADD FILE 'D:\test4.fdb' STARTING AT PAGE 30001;
-
Specifying the path and name of the delta file:
ALTER DATABASE ADD DIFFERENCE FILE 'D:\test.diff';
-
Deleting the description of the delta file:
ALTER DATABASE DROP DIFFERENCE FILE;
-
Switching the database to the “copy-safe” mode:
ALTER DATABASE BEGIN BACKUP;
-
Switching the database back from the “copy-safe” mode to the normal operation mode:
ALTER DATABASE END BACKUP;
5.1.3. DROP DATABASE
Deleting the database to which you are currently connected
DSQL, ESQL
DROP DATABASE
The DROP DATABASE
statement deletes the current database.
Before deleting a database, you have to connect to it.
The statement deletes the primary file, all secondary files and all shadow files.
Only administrators have the authority to use DROP DATABASE
.
Deleting the database the client is connected to.
DROP DATABASE;
5.2. SHADOW
A shadow is an exact, page-by-page copy of a database. Once a shadow is created, all changes made in the database are immediately reflected in the shadow. If the primary database file becomes unavailable for some reason, the DBMS will switch to the shadow.
This section describes how to create and delete shadow files.
5.2.1. CREATE SHADOW
Creating a shadow for the current database
DSQL, ESQL
CREATE SHADOW <sh_num> [AUTO | MANUAL] [CONDITIONAL] 'filepath' [LENGTH [=] num [PAGE[S]]] [<secondary_file> ...] <secondary_file> ::= FILE 'filepath' [STARTING [AT [PAGE]] pagenum] [LENGTH [=] num [PAGE[S]]]
Parameter | Description |
---|---|
sh_num |
Shadow number — a positive number identifying the shadow set |
filepath |
The name of the shadow file and the path to it, in accord with the rules of the operating system |
num |
Maximum shadow size, in pages |
secondary_file |
Secondary file specification |
page_num |
The number of the page at which the secondary shadow file should start |
The CREATE SHADOW
statement creates a new shadow.
The shadow starts duplicating the database right at the moment it is created.
It is not possible for a user to connect to a shadow.
Like a database, a shadow may be multi-file. The number and size of a shadow’s files are not related to the number and size of the files of database it is shadowing.
The page size for shadow files is set to be equal to the database page size and cannot be changed.
If a calamity occurs involving the original database, the system converts the shadow to a copy of the database and switches to it.
The shadow is then unavailable.
What happens next depends on the MODE
option.
AUTO | MANUAL
Modes
When a shadow is converted to a database, it becomes unavailable. A shadow might alternatively become unavailable because someone accidentally deletes its file, or the disk space where the shadow files are stored is exhausted or is itself damaged.
-
If the AUTO mode is selected (the default value), shadowing ceases automatically, all references to it are deleted from the database header and the database continues functioning normally.
If the
CONDITIONAL
option was set, the system will attempt to create a new shadow to replace the lost one. It does not always succeed, however, and a new one may need to be created manually. -
If the MANUAL mode attribute is set when the shadow becomes unavailable, all attempts to connect to the database and to query it will produce error messages. The database will remain inaccessible until either the shadow again becomes available or the database administrator deletes it using the
DROP SHADOW
statement. MANUAL should be selected if continuous shadowing is more important than uninterrupted operation of the database.
Options for CREATE SHADOW
LENGTH
-
Clause specifying the maximum size of the primary or secondary shadow file in pages. The
LENGTH
value does not affect the size of the only shadow file, nor the last if it is a set. The last (or only) file will keep automatically increasing in size as long as it is necessary. STARTING AT
-
Clause specifying the shadow page number at which the next shadow file should start. The system will start adding new data to the next shadow file when the previous file is filled with data up to the specified page number.
Only administrators have the authority to use CREATE SHADOW
.
You can verify the sizes, names and location of the shadow files by connecting to the database using isql and running the command |
5.2.2. DROP SHADOW
Deleting a shadow from the current database
DSQL, ESQL
DROP SHADOW sh_num
Parameter | Description |
---|---|
sh_num |
Shadow number — a positive number identifying the shadow set |
The DROP SHADOW
statement deletes the specified shadow for the database one is connected to.
When a shadow is dropped, all files related to it are deleted and shadowing to the specified sh_num ceases.
Only administrators have the authority to use DROP SHADOW
.
Deleting “shadow number 1”.
DROP SHADOW 1;
5.3. DOMAIN
DOMAIN
is one of the object types in a relational database.
A domain is created as a specific data type with some attributes attached to it.
Once it has been defined in the database, it can be reused repeatedly to define table columns, PSQL arguments and PSQL local variables.
Those objects inherit all of the attributes of the domain.
Some attributes can be overridden when the new object is defined, if required.
This section describes the syntax of statements used to create, modify and delete domains. A detailed description of domains and their usage can be found in Custom Data Types — Domains.
5.3.1. CREATE DOMAIN
Creating a new domain
DSQL, ESQL
CREATE DOMAIN name [AS] <datatype> [DEFAULT {<literal> | NULL | <context_var>}] [NOT NULL] [CHECK (<dom_condition>)] [COLLATE collation_name] <datatype> ::= {SMALLINT | INTEGER | BIGINT} [<array_dim>] | {FLOAT | DOUBLE PRECISION} [<array_dim>] | {DATE | TIME | TIMESTAMP} [<array_dim>] | {DECIMAL | NUMERIC} [(precision [, scale])] [<array_dim>] | {{CHAR | CHARACTER} [VARYING] | VARCHAR} [(size)] [<array_dim>] [CHARACTER SET charset_name] | {NCHAR | NATIONAL {CHARACTER | CHAR}} [VARYING] [(size)] [<array_dim>] | BLOB [SUB_TYPE {subtype_num | subtype_name}] [SEGMENT SIZE seglen] [CHARACTER SET charset_name] | BLOB [(seglen [, subtype_num])] <array_dim> ::= '[' [m:]n [,[m:]n ...] ']' <dom_condition> ::= <val> <operator> <val> | <val> [NOT] BETWEEN <val> AND <val> | <val> [NOT] IN (<val> [, <val> ...] | <select_list>) | <val> IS [NOT] NULL | <val> IS [NOT] DISTINCT FROM <val> | <val> [NOT] CONTAINING <val> | <val> [NOT] STARTING [WITH] <val> | <val> [NOT] LIKE <val> [ESCAPE <val>] | <val> [NOT] SIMILAR TO <val> [ESCAPE <val>] | <val> <operator> {ALL | SOME | ANY} (<select_list>) | [NOT] EXISTS (<select_expr>) | [NOT] SINGULAR (<select_expr>) | (<dom_condition>) | NOT <dom_condition> | <dom_condition> OR <dom_condition> | <dom_condition> AND <dom_condition> <operator> ::= <> | != | ^= | ~= | = | < | > | <= | >= | !< | ^< | ~< | !> | ^> | ~> <val> ::= VALUE | <literal> | <context_var> | <expression> | NULL | NEXT VALUE FOR genname | GEN_ID(genname, <val>) | CAST(<val> AS <datatype>) | (<select_one>) | func([<val> [, <val> ...]])
Parameter | Description |
---|---|
name |
Domain name consisting of up to 31 characters |
datatype |
SQL data type |
literal |
A literal value that is compatible with datatype |
context_var |
Any context variable whose type is compatible with datatype |
dom_condition |
Domain condition |
collation_name |
Name of a collation sequence that is valid for charset_name, if it is supplied with datatype or, otherwise, is valid for the default character set of the database |
array_dim |
Array dimensions |
m, n |
Integer numbers defining the index range of an array dimension |
precision |
The total number of significant digits that a value of the datatype can hold (1..18) |
scale |
The number of digits after the decimal point (0..precision) |
size |
The maximum size of a string in characters |
charset_name |
The name of a valid character set, if the character set of the domain is to be different to the default character set of the database |
subtype_num |
|
subtype_name |
|
seglen |
Segment size (max. 65535) |
select_one |
A scalar |
select_list |
A |
select_expr |
A |
expression |
An expression resolving to a value that is compatible with datatype |
genname |
Sequence (generator) name |
func |
Internal function or UDF |
The CREATE DOMAIN
statement creates a new domain.
Any SQL data type can be specified as the domain type.
Type-specific Details
ARRAY
Types-
-
If the domain is to be an array, the base type can be any SQL data type except
BLOB
andARRAY
. -
The dimensions of the array are specified between square brackets. (In the Syntax block, these brackets appear in quotes to distinguish them from the square brackets that identify optional syntax elements.)
-
For each array dimension, one or two integer numbers define the lower and upper boundaries of its index range:
-
By default, arrays are 1-based. The lower boundary is implicit and only the upper boundary need be specified. A single number smaller than 1 defines the range num..1 and a number greater than 1 defines the range 1..num.
-
Two numbers separated by a colon (‘
:
’) and optional whitespace, the second greater than the first, can be used to define the range explicitly. One or both boundaries can be less than zero, as long as the upper boundary is greater than the lower.
-
-
When the array has multiple dimensions, the range definitions for each dimension must be separated by commas and optional whitespace.
-
Subscripts are validated only if an array actually exists. It means that no error messages regarding invalid subscripts will be returned if selecting a specific element returns nothing or if an array field is
NULL
.
-
CHARACTER
Types-
You can use the
CHARACTER SET
clause to specify the character set for theCHAR
,VARCHAR
andBLOB
(SUB_TYPE TEXT
) types. If the character set is not specified, the character set specified asDEFAULT CHARACTER SET
in creating the database will be used. If no character set was specified then, the character setNONE
is applied by default when you create a character domain.With character set
NONE
, character data are stored and retrieved the way they were submitted. Data in any encoding can be added to a column based on such a domain, but it is impossible to add this data to a column with a different encoding. Because no transliteration is performed between the source and destination encodings, errors may result. DEFAULT
Clause-
The optional
DEFAULT
clause allows you to specify a default value for the domain. This value will be added to the table column that inherits this domain when theINSERT
statement is executed, if no value is specified for it in the DML statement. Local variables and arguments in PSQL modules that reference this domain will be initialized with the default value. For the default value, use a literal of a compatible type or a context variable of a compatible type. NOT NULL
Constraint-
Columns and variables based on a domain with the
NOT NULL
constraint will be prevented from being written asNULL
, i.e., a value is required.When creating a domain, take care to avoid specifying limitations that would contradict one another. For instance,
NOT NULL
andDEFAULT NULL
are contradictory. CHECK
Constraint[s]-
The optional
CHECK
clause specifies constraints for the domain. A domain constraint specifies conditions that must be satisfied by the values of table columns or variables that inherit from the domain. A condition must be enclosed in parentheses. A condition is a logical expression (also called a predicate) that can return the Boolean resultsTRUE
,FALSE
andUNKNOWN
. A condition is considered satisfied if the predicate returns the valueTRUE
or “unknown value” (equivalent toNULL
). If the predicate returnsFALSE
, the condition for acceptance is not met. VALUE
Keyword-
The keyword
VALUE
in a domain constraint substitutes for the table column that is based on this domain or for a variable in a PSQL module. It contains the value assigned to the variable or the table column.VALUE
can be used anywhere in theCHECK
constraint, though it is usually used in the left part of the condition. COLLATE
-
The optional
COLLATE
clause allows you to specify the collation sequence if the domain is based on one of the string data types, includingBLOB
s with text subtypes. If no collation sequence is specified, the collation sequence will be the one that is default for the specified character set at the time the domain is created.
Any user connected to the database can create a domain.
CREATE DOMAIN
Examples
-
Creating a domain that can take values greater than 1,000, with a default value of 10,000.
CREATE DOMAIN CUSTNO AS INTEGER DEFAULT 10000 CHECK (VALUE > 1000);
-
Creating a domain that can take the values 'Yes' and 'No' in the default character set specified during the creation of the database.
CREATE DOMAIN D_BOOLEAN AS CHAR(3) CHECK (VALUE IN ('Yes', 'No'));
-
Creating a domain with the
UTF8
character set and theUNICODE_CI_AI
collation sequence.CREATE DOMAIN FIRSTNAME AS VARCHAR(30) CHARACTER SET UTF8 COLLATE UNICODE_CI_AI;
-
Creating a domain of the
DATE
type that will not accept NULL and uses the current date as the default value.CREATE DOMAIN D_DATE AS DATE DEFAULT CURRENT_DATE NOT NULL;
-
Creating a domain defined as an array of 2 elements of the
NUMERIC(18, 3)
type. The starting array index is 1.CREATE DOMAIN D_POINT AS NUMERIC(18, 3) [2];
Domains defined over an array type may be used only to define table columns. You cannot use array domains to define local variables in PSQL modules.
-
Creating a domain whose elements can be only country codes defined in the
COUNTRY
table.CREATE DOMAIN D_COUNTRYCODE AS CHAR(3) CHECK (EXISTS(SELECT * FROM COUNTRY WHERE COUNTRYCODE = VALUE));
The example is given only to show the possibility of using predicates with queries in the domain test condition. It is not recommended to create this style of domain in practice unless the lookup table contains data that are never deleted.
5.3.2. ALTER DOMAIN
Altering the current attributes of a domain or renaming it
DSQL, ESQL
ALTER DOMAIN domain_name [TO new_name] [TYPE <datatype>] [SET DEFAULT {<literal> | NULL | <context_var>} | DROP DEFAULT] [ADD [CONSTRAINT] CHECK (<dom_condition>) | DROP CONSTRAINT] <datatype> ::= {SMALLINT | INTEGER | BIGINT} | {FLOAT | DOUBLE PRECISION} | {DATE | TIME | TIMESTAMP} | {DECIMAL | NUMERIC} [(precision [, scale])] | {CHAR | CHARACTER} [VARYING] | VARCHAR} [(size)] [CHARACTER SET charset_name] | {NCHAR | NATIONAL {CHARACTER | CHAR}} [VARYING] [(size)] | BLOB [SUB_TYPE {subtype_num | subtype_name}] [SEGMENT SIZE seglen] [CHARACTER SET charset_name] | BLOB [(seglen [, subtype_num])] <dom_condition> ::= <val> <operator> <val> | <val> [NOT] BETWEEN <val> AND <val> | <val> [NOT] IN (<val> [, <val> ...] | <select_list>) | <val> IS [NOT] NULL | <val> IS [NOT] DISTINCT FROM <val> | <val> [NOT] CONTAINING <val> | <val> [NOT] STARTING [WITH] <val> | <val> [NOT] LIKE <val> [ESCAPE <val>] | <val> [NOT] SIMILAR TO <val> [ESCAPE <val>] | <val> <operator> {ALL | SOME | ANY} (<select_list>) | [NOT] EXISTS (<select_expr>) | [NOT] SINGULAR (<select_expr>) | (<dom_condition>) | NOT <dom_condition> | <dom_condition> OR <dom_condition> | <dom_condition> AND <dom_condition> <operator> ::= <> | != | ^= | ~= | = | < | > | <= | >= | !< | ^< | ~< | !> | ^> | ~> <val> ::= VALUE | <literal> | <context_var> | <expression> | NULL | NEXT VALUE FOR genname | GEN_ID(genname, <val>) | CAST(<val> AS <datatype>) | (<select_one>) | func([<val> [, <val> ...]])
Parameter | Description |
---|---|
new_name |
New name for domain, consisting of up to 31 characters |
datatype |
SQL data type |
literal |
A literal value that is compatible with datatype |
context_var |
Any context variable whose type is compatible with datatype |
precision |
The total number of significant digits that a value of the datatype can hold (1..18) |
scale |
The number of digits after the decimal point (0..precision) |
size |
The maximum size of a string in characters |
charset_name |
The name of a valid character set, if the character set of the domain is to be changed |
subtype_num |
|
subtype_name |
|
seglen |
Segment size (max. 65535) |
select_one |
A scalar |
select_list |
A |
select_expr |
A |
expression |
An expression resolving to a value that is compatible with datatype |
genname |
Sequence (generator) name |
func |
Internal function or UDF |
The ALTER DOMAIN
statement enables changes to the current attributes of a domain, including its name.
You can make any number of domain alterations in one ALTER DOMAIN
statement.
TO name
-
Use the
TO
clause to rename the domain, as long as there are no dependencies on the domain, i.e. table columns, local variables or procedure arguments referencing it. SET DEFAULT
-
With the
SET DEFAULT
clause you can set a new default value. If the domain already has a default value, there is no need to delete it first — it will be replaced by the new one. DROP DEFAULT
-
Use this clause to delete a previously specified default value and replace it with
NULL
. ADD CONSTRAINT CHECK
-
Use the
ADD CONSTRAINT CHECK
clause to add aCHECK
constraint to the domain. If the domain already has aCHECK
constraint, it will have to be deleted first, using anALTER DOMAIN
statement that includes aDROP CONSTRAINT
clause. TYPE
-
The
TYPE
clause is used to change the data type of the domain to a different, compatible one. The system will forbid any change to the type that could result in data loss. An example would be if the number of characters in the new type were smaller than in the existing type.
When you alter the attributes of a domain, existing PSQL code may become invalid. For information on how to detect it, read the piece entitled The RDB$VALID_BLR Field in Appendix A. |
Any user connected to the database can alter a domain, provided it is not prevented by dependencies from objects to which that user does not have sufficient privileges.
What ALTER DOMAIN
Cannot Alter
-
If the domain was declared as an array, it is not possible to change its type or its dimensions; nor can any other type be changed to an
ARRAY
type. -
In Firebird 2.5 and lower, the
NOT NULL
constraint can be neither enabled nor disabled for a domain. -
There is no way to change the default collation without dropping the domain and recreating it with the desired attributes.
ALTER DOMAIN
Examples
-
Changing the data type to
INTEGER
and setting or changing the default value to 2,000:ALTER DOMAIN CUSTNO TYPE INTEGER SET DEFAULT 2000;
-
Renaming a domain.
ALTER DOMAIN D_BOOLEAN TO D_BOOL;
-
Deleting the default value and adding a constraint for the domain:
ALTER DOMAIN D_DATE DROP DEFAULT ADD CONSTRAINT CHECK (VALUE >= date '01.01.2000');
-
Changing the
CHECK
constraint:ALTER DOMAIN D_DATE DROP CONSTRAINT; ALTER DOMAIN D_DATE ADD CONSTRAINT CHECK (VALUE BETWEEN date '01.01.1900' AND date '31.12.2100');
-
Changing the data type to increase the permitted number of characters:
ALTER DOMAIN FIRSTNAME TYPE VARCHAR(50) CHARACTER SET UTF8;
5.3.3. DROP DOMAIN
Deleting an existing domain
DSQL, ESQL
DROP DOMAIN domain_name
The DROP DOMAIN
statement deletes a domain that exists in the database.
It is not possible to delete a domain if it is referenced by any database table columns or used in any PSQL module.
In order to delete a domain that is in use, all columns in all tables that refer to the domain will have to be dropped and all references to the domain will have to be removed from PSQL modules.
Any user connected to the database can drop a domain.
Example
Deleting the COUNTRYNAME domain:
DROP DOMAIN COUNTRYNAME;
5.4. TABLE
As a relational DBMS, Firebird stores data in tables. A table is a flat, two-dimensional structure containing any number of rows. Table rows are often called records.
All rows in a table have the same structure and consist of columns. Table columns are often called fields. A table must have at least one column. Each column contains a single type of SQL data.
This section describes how to create, alter and delete tables in a database.
5.4.1. CREATE TABLE
creating a new table (relation)
DSQL, ESQL
CREATE [GLOBAL TEMPORARY] TABLE tablename [EXTERNAL [FILE] 'filespec'] (<col_def> [, {<col_def> | <tconstraint>} ...]) [ON COMMIT {DELETE | PRESERVE} ROWS] <col_def> ::= <regular_col_def> | <computed_col_def> <regular_col_def> ::= colname {<datatype> | domainname} [DEFAULT {<literal> | NULL | <context_var>}] [NOT NULL] [<col_constraint>] [COLLATE collation_name] <computed_col_def> ::= colname [<datatype>] {COMPUTED [BY] | GENERATED ALWAYS AS} (<expression>) <datatype> ::= {SMALLINT | INTEGER | BIGINT} [<array_dim>] | {FLOAT | DOUBLE PRECISION} [<array_dim>] | {DATE | TIME | TIMESTAMP} [<array_dim>] | {DECIMAL | NUMERIC} [(precision [, scale])] [<array_dim>] | {CHAR | CHARACTER} [VARYING] | VARCHAR} [(size)] [<array_dim>] [CHARACTER SET charset_name] | {NCHAR | NATIONAL {CHARACTER | CHAR}} [VARYING] [(size)] [<array_dim>] | BLOB [SUB_TYPE {subtype_num | subtype_name}] [SEGMENT SIZE seglen] [CHARACTER SET charset_name] | BLOB [(seglen [, subtype_num])] <array_dim> ::= '[' [m:]n [, [m:]n ...] ']' <col_constraint> ::= [CONSTRAINT constr_name] { PRIMARY KEY [<using_index>] | UNIQUE [<using_index>] | REFERENCES other_table [(colname)] [<using_index>] [ON DELETE {NO ACTION | CASCADE | SET DEFAULT | SET NULL}] [ON UPDATE {NO ACTION | CASCADE | SET DEFAULT | SET NULL}] | CHECK (<check_condition>) } <tconstraint> ::= [CONSTRAINT constr_name] { PRIMARY KEY (<col_list>) [<using_index>] | UNIQUE (<col_list>) [<using_index>] | FOREIGN KEY (<col_list>) REFERENCES other_table [(<col_list>)] [<using_index>] [ON DELETE {NO ACTION | CASCADE | SET DEFAULT | SET NULL}] [ON UPDATE {NO ACTION | CASCADE | SET DEFAULT | SET NULL}] | CHECK (<check_condition>) }" <col_list> ::= colname [, colname ...] <using_index> ::= USING [ASC[ENDING] | DESC[ENDING]] INDEX indexname <check_condition> ::= <val> <operator> <val> | <val> [NOT] BETWEEN <val> AND <val> | <val> [NOT] IN (<val> [, <val> ...] | <select_list>) | <val> IS [NOT] NULL | <val> IS [NOT] DISTINCT FROM <val> | <val> [NOT] CONTAINING <val> | <val> [NOT] STARTING [WITH] <val> | <val> [NOT] LIKE <val> [ESCAPE <val>] | <val> [NOT] SIMILAR TO <val> [ESCAPE <val>] | <val> <operator> {ALL | SOME | ANY} (<select_list>) | [NOT] EXISTS (<select_expr>) | [NOT] SINGULAR (<select_expr>) | (<check_condition>) | NOT <check_condition> | <check_condition> OR <check_condition> | <check_condition> AND <check_condition> <operator> ::= <> | != | ^= | ~= | = | < | > | <= | >= | !< | ^< | ~< | !> | ^> | ~> <val> ::= colname ['['array_idx [, array_idx ...]']'] | <literal> | <context_var> | <expression> | NULL | NEXT VALUE FOR genname | GEN_ID(genname, <val>) | CAST(<val> AS <datatype>) | (<select_one>) | func([<val> [, <val> ...]])
Parameter | Description |
---|---|
tablename |
Name (identifier) for the table. It may consist of up to 31 characters and must be unique in the database. |
filespec |
File specification (only for external tables). Full file name and path, enclosed in single quotes, correct for the local file system and located on a storage device that is physically connected to Firebird’s host computer. |
colname |
Name (identifier) for a column in the table. May consist of up to 31 characters and must be unique in the table. |
datatype |
SQL data type |
col_constraint |
Column constraint |
tconstraint |
Table constraint |
constr_name |
The name (identifier) of a constraint. May consist of up to 31 characters. |
other_table |
The name of the table referenced by the foreign key constraint |
other_col |
The name of the column in other_table that is referenced by the foreign key |
literal |
A literal value that is allowed in the given context |
context_var |
Any context variable whose data type is allowed in the given context |
check_condition |
The condition applied to a CHECK constraint, that will resolve as either true, false or |
collation |
Collation |
array_dim |
Array dimensions |
m, n |
Integer numbers defining the index range of an array dimension |
precision |
The total number of significant digits that a value of the datatype can hold (1..18) |
scale |
The number of digits after the decimal point (0..precision) |
size |
The maximum size of a string in characters |
charset_name |
The name of a valid character set, if the character set of the column is to be different to the default character set of the database |
subtype_num |
|
subtype_name |
|
seglen |
Segment size (max. 65535) |
select_one |
A scalar |
select_list |
A |
select_expr |
A |
expression |
An expression resolving to a value that is is allowed in the given context |
genname |
Sequence (generator) name |
func |
Internal function or UDF |
The CREATE TABLE
statement creates a new table.
Any user can create it and its name must be unique among the names of all tables, views and stored procedures in the database.
A table must contain at least one column that is not computed and the names of columns must be unique in the table.
A column must have either an explicit SQL data type, the name of a domain whose attributes will be copied for the column, or be defined as COMPUTED BY
an expression (a calculated field).
A table may have any number of table constraints, including none.
Making a Column Non-nullable
In Firebird, columns are nullable by default.
The optional NOT NULL
clause specifies that the column cannot take NULL
in place of a value.
Character Columns
You can use the CHARACTER SET
clause to specify the character set for the CHAR
, VARCHAR
and BLOB
(text subtype) types.
If the character set is not specified, the character set specified during the creation of the database will be used by default.
If no character set was specified during the creation of the database, the NONE
character set is applied by default.
In this case, data is stored and retrieved the way it was submitted.
Data in any encoding can be added to such a column, but it is not possible to add this data to a column with a different encoding.
No transliteration is performed between the source and destination encodings, which may result in errors.
The optional COLLATE
clause allows you to specify the collation sequence for character data types, including BLOB SUB_TYPE TEXT
.
If no collation sequence is specified, the collation sequence that is default for the specified character set during the creation of the column is applied by default.
Setting a DEFAULT
Value
The optional DEFAULT
clause allows you to specify the default value for the table column.
This value will be added to the column when an INSERT
statement is executed if no value was specified for it and that column was omitted from the INSERT
command.
The default value can be a literal of a compatible type, a context variable that is type-compatible with the data type of the column, or NULL
, if the column allows it.
If no default value is explicitly specified, NULL
is implied.
An expression cannot be used as a default value.
Domain-based Columns
To define a column, you can use a previously defined domain.
If the definition of a column is based on a domain, it may contain a new default value, additional CHECK
constraints and a COLLATE
clause that will override the values specified in the domain definition.
The definition of such a column may contain additional column constraints (for instance, NOT NULL
), if the domain does not have it.
It is not possible to define a domain-based column that is nullable if the domain was defined with the |
Calculated Fields
Calculated fields can be defined with the COMPUTED [BY]
or GENERATED ALWAYS AS
clause (according to the SQL:2003 standard).
They mean the same.
Describing the data type is not required (but possible) for calculated fields, as the DBMS calculates and stores the appropriate type as a result of the expression analysis.
Appropriate operations for the data types included in an expression must be specified precisely.
If the data type is explicitly specified for a calculated field, the calculation result is converted to the specified type. This means, for instance, that the result of a numeric expression could be rendered as a string.
In a query that selects a COMPUTED BY
column, the expression is evaluated for each row of the selected data.
Instead of a computed column, in some cases it makes sense to use a regular column whose value is evaluated in triggers for adding and updating data. It may reduce the performance of inserting/updating records, but it will increase the performance of data selection. |
Defining an ARRAY
Column
-
If the column is to be an array, the base type can be any SQL data type except
BLOB
andARRAY
. -
The dimensions of the array are specified between square brackets. (In the Syntax block these brackets appear in quotes to distinguish them from the square brackets that identify optional syntax elements.)
-
For each array dimension, one or two integer numbers define the lower and upper boundaries of its index range:
-
By default, arrays are 1-based. The lower boundary is implicit and only the upper boundary need be specified. A single number smaller than 1 defines the range num..1 and a number greater than 1 defines the range 1..num.
-
Two numbers separated by a colon (‘
:
’) and optional whitespace, the second greater than the first, can be used to define the range explicitly. One or both boundaries can be less than zero, as long as the upper boundary is greater than the lower.
-
-
When the array has multiple dimensions, the range definitions for each dimension must be separated by commas and optional whitespace.
-
Subscripts are validated only if an array actually exists. It means that no error messages regarding invalid subscripts will be returned if selecting a specific element returns nothing or if an array field is
NULL
.
Constraints
Four types of constraints can be specified. They are:
-
Primary key (
PRIMARY KEY
) -
Unique key (
UNIQUE
) -
Foreign key (
REFERENCES
) -
CHECK
constraint (CHECK
)
Constraints can be specified at column level (“column constraints”) or at table level (“table constraints”).
Table-level constraints are needed when keys (uniqueness constraint, Primary Key, Foreign Key) are to be formed across multiple columns and when a CHECK
constraint involves other columns in the row besides the column being defined.
Syntax for some types of constraint may differ slightly according to whether the constraint is being defined at column or table level.
-
A column-level constraint is specified during a column definition, after all column attributes except
COLLATION
are specified, and can involve only the column specified in that definition -
Table-level constraints are specified after all of the column definitions. They are a more flexible way to set constraints, since they can cater for constraints involving multiple columns
-
You can mix column-level and table-level constraints in the same
CREATE TABLE
statement
The system automatically creates the corresponding index for a primary key (PRIMARY KEY
), a unique key (UNIQUE
) and a foreign key (REFERENCES
for a column-level constraint, FOREIGN KEY REFERENCES
for one at the table level).
Names for Constraints and Their Indexes
Column-level constraints and their indexes are named automatically:
-
The constraint name has the form
INTEG_n
, where n represents one or more digits -
The index name has the form
RDB$PRIMARYn
(for a primary key index),RDB$FOREIGNn
(for a foreign key index) orRDB$n
(for a unique key index). Again, n represents one or more digits.
Automatic naming of table-level constraints and their indexes follows the same pattern, unless the names are supplied explicitly.
A constraint can be named explicitly if the CONSTRAINT
clause is used for its definition.
While the CONSTRAINT
clause is optional for defining column-level constraints, it is mandatory for table-level.
By default, the constraint index will have the same name as the constraint.
If a different name is wanted for the constraint index, a USING
clause can be included.
PRIMARY KEY
The PRIMARY KEY
constraint is built on one or more key columns, each column having the NOT NULL
constraint specified for it.
The values across the key columns in any row must be unique.
A table can have only one primary key.
-
A single-column Primary Key can be defined as a column level or a table-level constraint
-
A multi-column Primary Key must be specified as a table-level constraint
The UNIQUE
Constraint
The UNIQUE
constraint defines the requirement of content uniqueness for the values in a key throughout the table.
A table can contain any number of unique key constraints.
As with the Primary Key, the Unique constraint can be multi-column. If so, it must be specified as a table-level constraint.
NULL
in Unique KeysFirebird’s SQL-99-compliant rules for UNIQUE
constraints allow one or more NULL
s in a column with a UNIQUE
constraint.
That makes it possible to define a UNIQUE
constraint on a column that does not have the NOT NULL
constraint.
For UNIQUE
keys that span multiple columns, the logic is a little complicated:
-
Multiple rows having null in all the columns of the key are allowed
-
Multiple rows having keys with different combinations of nulls and non-null values are allowed
-
Multiple rows having the same key columns null and the rest filled with non-null values are allowed, provided the values differ in at least one column
-
Multiple rows having the same key columns null and the rest filled with non-null values that are the same in every column will violate the constraint
The rules for uniqueness can be summarised thus:
In principle, all nulls are considered distinct.
However, if two rows have exactly the same key columns filled with non-null values, the NULL
columns are ignored and the uniqueness is determined on the non-null columns as though they constituted the entire key.
RECREATE TABLE t( x int, y int, z int, unique(x,y,z));
INSERT INTO t values( NULL, 1, 1 );
INSERT INTO t values( NULL, NULL, 1 );
INSERT INTO t values( NULL, NULL, NULL );
INSERT INTO t values( NULL, NULL, NULL ); -- Permitted
INSERT INTO t values( NULL, NULL, 1 ); -- Not permitted
FOREIGN KEY
A Foreign Key ensures that the participating column(s) can contain only values that also exist in the referenced column(s) in the master table.
These referenced columns are often called target columns.
They must be the primary key or a unique key in the target table.
They need not have a NOT NULL
constraint defined on them although, if they are the primary key, they will, of course, have that constraint.
The foreign key columns in the referencing table itself do not require a NOT NULL
constraint.
A single-column Foreign Key can be defined in the column declaration, using the keyword REFERENCES
:
... ,
ARTIFACT_ID INTEGER REFERENCES COLLECTION (ARTIFACT_ID),
The column ARTIFACT_ID
in the example references a column of the same name in the table COLLECTIONS
.
Both single-column and multi-column foreign keys can be defined at the table level. For a multi-column Foreign Key, the table-level declaration is the only option. This method also enables the provision of an optional name for the constraint:
...
CONSTRAINT FK_ARTSOURCE FOREIGN KEY(DEALER_ID, COUNTRY)
REFERENCES DEALER (DEALER_ID, COUNTRY),
Notice that the column names in the referenced (“master”) table may differ from those in the Foreign Key.
If no target columns are specified, the Foreign Key automatically references the target table’s Primary Key. |
With the sub-clauses ON UPDATE
and ON DELETE
it is possible to specify an action to be taken on the affected foreign key column(s) when referenced values in the master table are changed:
NO ACTION
-
(the default) - Nothing is done
CASCADE
-
The change in the master table is propagated to the corresponding row(s) in the child table. If a key value changes, the corresponding key in the child records changes to the new value; if the master row is deleted, the child records are deleted.
SET DEFAULT
-
The Foreign Key columns in the affected rows will be set to their default values as they were when the foreign key constraint was defined.
SET NULL
-
The Foreign Key columns in the affected rows will be set to
NULL
.
The specified action, or the default NO ACTION
, could cause a Foreign Key column to become invalid.
For example, it could get a value that is not present in the master table, or it could become NULL
while the column has a NOT NULL
constraint.
Such conditions will cause the operation on the master table to fail with an error message.
...
CONSTRAINT FK_ORDERS_CUST
FOREIGN KEY (CUSTOMER) REFERENCES CUSTOMERS (ID)
ON UPDATE CASCADE ON DELETE SET NULL
CHECK
Constraint
The CHECK
constraint defines the condition the values inserted in this column must satisfy.
A condition is a logical expression (also called a predicate) that can return the TRUE, FALSE and UNKNOWN values.
A condition is considered satisfied if the predicate returns TRUE or value UNKNOWN (equivalent to NULL
).
If the predicate returns FALSE, the value will not be accepted.
This condition is used for inserting a new row into the table (the INSERT
statement) and for updating the existing value of the table column (the UPDATE
statement) and also for statements where one of these actions may take place (UPDATE OR INSERT, MERGE).
A |
CHECK
conditions — whether defined at table level or column level — refer to table columns by their names.
The use of the keyword VALUE
as a placeholder, as in domain CHECK
constraints, is not valid in the context of defining column constraints.
with two column-level constraints and one at table-level:
CREATE TABLE PLACES (
...
LAT DECIMAL(9, 6) CHECK (ABS(LAT) <= 90),
LON DECIMAL(9, 6) CHECK (ABS(LON) <= 180),
...
CONSTRAINT CHK_POLES CHECK (ABS(LAT) < 90 OR LON = 0)
);
Global Temporary Tables (GTT)
Global temporary tables have persistent metadata, but their contents are transaction-bound (the default) or connection-bound. Every transaction or connection has its own private instance of a GTT, isolated from all the others. Instances are only created if and when the GTT is referenced. They are destroyed when the transaction ends or on disconnection. The metadata of a GTT can be modified or removed using ALTER TABLE and DROP TABLE, respectively.
CREATE GLOBAL TEMPORARY TABLE tablename (<column_def> [, {<column_def> | <table_constraint>} ...]) [ON COMMIT {DELETE | PRESERVE} ROWS]
Syntax notes
|
Restrictions on GTTs
GTTs can be “dressed up” with all the features and paraphernalia of ordinary tables (keys, references, indexes, triggers and so on) but there are a few restrictions:
-
GTTs and regular tables cannot reference one another
-
A connection-bound (“
PRESERVE ROWS
”) GTT cannot reference a transaction-bound (“DELETE ROWS
”) GTT -
Domain constraints cannot reference any GTT
-
The destruction of a GTT instance at the end of its life cycle does not cause any
BEFORE
/AFTER
delete triggers to fire
In an existing database, it is not always easy to distinguish a regular table from a GTT, or a transaction-level GTT from a connection-level GTT. Use this query to find out what type of table you are looking at:
For an overview of the types of all the relations in the database:
The |
External Tables
The optional EXTERNAL [FILE]
clause specifies that the table is stored outside the database in an external text file of fixed-length records.
The columns of a table stored in an external file can be of any type except BLOB
or ARRAY
, although for most purposes, only columns of CHAR
types would be useful.
All you can do with a table stored in an external file is insert new rows (INSERT
) and query the data (SELECT
).
Updating existing data (UPDATE
) and deleting rows (DELETE
) are not possible.
A file that is defined as an external table must be located on a storage device that is physically present on the machine where the Firebird server runs and, if the parameter ExternalFileAccess in the firebird.conf
configuration file is Restrict
, it must be in one of the directories listed there as the argument for Restrict
.
If the file does not exist yet, Firebird will create it on first access.
The ability to use external files for a table depends on the value set for the ExternalFileAccess parameter in
|
External File Format
The “row” format of the external table is fixed length. There are no field delimiters: both field and row boundaries are determined by maximum sizes, in bytes, of the field definitions. It is important to keep this in mind, both when defining the structure of the external table and when designing an input file for an external table that is to import data from another application. The ubiquitous “.csv” format, for example, is of no use as an input file and cannot be generated directly into an external file.
The most useful data type for the columns of external tables is the fixed-length CHAR
type, of suitable lengths for the data they are to carry.
Date and number types are easily cast to and from strings whereas, unless the files are to be read by another Firebird database, the native data types will appear to external applications as unparseable “alphabetti”.
Of course, there are ways to manipulate typed data so as to generate output files from Firebird that can be read directly as input files to other applications, using stored procedures, with or without employing external tables. Such techniques are beyond the scope of a language reference. Here, we provide some guidelines and tips for producing and working with simple text files, since the external table feature is often used as an easy way to produce or read transaction-independent logs that can be studied off-line in a text editor or auditing application.
Generally, external files are more useful if rows are separated by a delimiter, in the form of a “newline” sequence that is recognised by reader applications on the intended platform.
For most contexts on Windows, it is the two-byte 'CRLF' sequence, carriage return (ASCII code decimal 13) and line feed (ASCII code decimal 10).
On POSIX, LF on its own is usual;
for some MacOSX applications, it may be LFCR.
There are various ways to populate this delimiter column.
In our example below, it is done by using a BEFORE INSERT
trigger and the internal function ASCII_CHAR
.
For our example, we will define an external log table that might be used by an exception handler in a stored procedure or trigger. The external table is chosen because the messages from any handled exceptions will be retained in the log, even if the transaction that launched the process is eventually rolled back because of another, unhandled exception. For demonstration purposes, it has just two data columns, a time stamp and a message. The third column stores the row delimiter:
CREATE TABLE ext_log
EXTERNAL FILE 'd:\externals\log_me.txt' (
stamp CHAR (24),
message CHAR(100),
crlf CHAR(2) -- for a Windows context
);
COMMIT;
Now, a trigger, to write the timestamp and the row delimiter each time a message is written to the file:
SET TERM ^;
CREATE TRIGGER bi_ext_log FOR ext_log
ACTIVE BEFORE INSERT
AS
BEGIN
IF (new.stamp is NULL) then
new.stamp = CAST (CURRENT_TIMESTAMP as CHAR(24));
new.crlf = ASCII_CHAR(13) || ASCII_CHAR(10);
END ^
COMMIT ^
SET TERM ;^
Inserting some records (which could have been done by an exception handler or a fan of Shakespeare):
insert into ext_log (message)
values('Shall I compare thee to a summer''s day?');
insert into ext_log (message)
values('Thou art more lovely and more temperate');
The output:
2015-10-07 15:19:03.4110Shall I compare thee to a summer's day?
2015-10-07 15:19:58.7600Thou art more lovely and more temperate
CREATE TABLE
Examples
-
Creating the
COUNTRY
table with the primary key specified as a column constraint.CREATE TABLE COUNTRY ( COUNTRY COUNTRYNAME NOT NULL PRIMARY KEY, CURRENCY VARCHAR(10) NOT NULL );
-
Creating the
STOCK
table with the named primary key specified at the column level and the named unique key specified at the table level.CREATE TABLE STOCK ( MODEL SMALLINT NOT NULL CONSTRAINT PK_STOCK PRIMARY KEY, MODELNAME CHAR(10) NOT NULL, ITEMID INTEGER NOT NULL, CONSTRAINT MOD_UNIQUE UNIQUE (MODELNAME, ITEMID) );
-
Creating the
JOB
table with a primary key constraint spanning two columns, a foreign key constraint for theCOUNTRY
table and a table-levelCHECK
constraint. The table also contains an array of 5 elements.CREATE TABLE JOB ( JOB_CODE JOBCODE NOT NULL, JOB_GRADE JOBGRADE NOT NULL, JOB_COUNTRY COUNTRYNAME, JOB_TITLE VARCHAR(25) NOT NULL, MIN_SALARY NUMERIC(18, 2) DEFAULT 0 NOT NULL, MAX_SALARY NUMERIC(18, 2) NOT NULL, JOB_REQUIREMENT BLOB SUB_TYPE 1, LANGUAGE_REQ VARCHAR(15) [1:5], PRIMARY KEY (JOB_CODE, JOB_GRADE), FOREIGN KEY (JOB_COUNTRY) REFERENCES COUNTRY (COUNTRY) ON UPDATE CASCADE ON DELETE SET NULL, CONSTRAINT CHK_SALARY CHECK (MIN_SALARY < MAX_SALARY) );
-
Creating the
PROJECT
table with primary, foreign and unique key constraints with custom index names specified with theUSING
clause.CREATE TABLE PROJECT ( PROJ_ID PROJNO NOT NULL, PROJ_NAME VARCHAR(20) NOT NULL UNIQUE USING DESC INDEX IDX_PROJNAME, PROJ_DESC BLOB SUB_TYPE 1, TEAM_LEADER EMPNO, PRODUCT PRODTYPE, CONSTRAINT PK_PROJECT PRIMARY KEY (PROJ_ID) USING INDEX IDX_PROJ_ID, FOREIGN KEY (TEAM_LEADER) REFERENCES EMPLOYEE (EMP_NO) USING INDEX IDX_LEADER );
-
Creating the
SALARY_HISTORY
table with two computed fields. The first one is declared according to the SQL:2003 standard, while the second one is declared according to the traditional declaration of computed fields in Firebird.CREATE TABLE SALARY_HISTORY ( EMP_NO EMPNO NOT NULL, CHANGE_DATE TIMESTAMP DEFAULT 'NOW' NOT NULL, UPDATER_ID VARCHAR(20) NOT NULL, OLD_SALARY SALARY NOT NULL, PERCENT_CHANGE DOUBLE PRECISION DEFAULT 0 NOT NULL, SALARY_CHANGE GENERATED ALWAYS AS (OLD_SALARY * PERCENT_CHANGE / 100), NEW_SALARY COMPUTED BY (OLD_SALARY + OLD_SALARY * PERCENT_CHANGE / 100) );
-
Creating a connection-scoped global temporary table.
CREATE GLOBAL TEMPORARY TABLE MYCONNGTT ( ID INTEGER NOT NULL PRIMARY KEY, TXT VARCHAR(32), TS TIMESTAMP DEFAULT CURRENT_TIMESTAMP) ON COMMIT PRESERVE ROWS;
-
Creating a transaction-scoped global temporary table that uses a foreign key to reference a connection-scoped global temporary table. The
ON COMMIT
sub-clause is optional becauseDELETE ROWS
is the default.CREATE GLOBAL TEMPORARY TABLE MYTXGTT ( ID INTEGER NOT NULL PRIMARY KEY, PARENT_ID INTEGER NOT NULL REFERENCES MYCONNGTT(ID), TXT VARCHAR(32), TS TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ON COMMIT DELETE ROWS;
5.4.2. ALTER TABLE
altering the structure of a table.
DSQL, ESQL
ALTER TABLE tablename <operation> [, <operation> ...] <operation> ::= ADD <col_def> | ADD <tconstraint> | DROP colname | DROP CONSTRAINT constr_name | ALTER [COLUMN] colname <col_mod> <col_def> ::= <regular_col_def> | <computed_col_def> <regular_col_def> ::= colname {<datatype> | domainname} [DEFAULT {<literal> | NULL | <context_var>}] [NOT NULL] [<col_constraint>] [COLLATE collation_name] <computed_col_def> ::= colname [<datatype>] {COMPUTED [BY] | GENERATED ALWAYS AS} (<expression>) <col_mod> ::= <regular_col_mod> | <computed_col_mod> <regular_col_mod> ::= TO newname | POSITION newpos | TYPE {<datatype> | domainname} | SET DEFAULT {<literal> | NULL | <context_var>} | DROP DEFAULT <computed_col_mod> ::= TO newname | POSITION newpos | [TYPE <datatype>] {COMPUTED [BY] | GENERATED ALWAYS AS} (<expression>) <datatype> ::= {SMALLINT | INTEGER | BIGINT} [<array_dim>] | {FLOAT | DOUBLE PRECISION} [<array_dim>] | {DATE | TIME | TIMESTAMP} [<array_dim>] | {DECIMAL | NUMERIC} [(precision [, scale])] [<array_dim>] | {CHAR | CHARACTER} [VARYING] | VARCHAR} [(size)] [<array_dim>] [CHARACTER SET charset_name] | {NCHAR | NATIONAL {CHARACTER | CHAR}} [VARYING] [(size)] [<array_dim>] | BLOB [SUB_TYPE {subtype_num | subtype_name}] [SEGMENT SIZE seglen] [CHARACTER SET charset_name] | BLOB [(seglen [, subtype_num])] <array_dim> ::= '[' [m:]n [,[m:]n ...] ']' <col_constraint> ::= [CONSTRAINT constr_name] { PRIMARY KEY [<using_index>] | UNIQUE [<using_index>] | REFERENCES other_table [(colname)] [<using_index>] [ON DELETE {NO ACTION | CASCADE | SET DEFAULT | SET NULL}] [ON UPDATE {NO ACTION | CASCADE | SET DEFAULT | SET NULL}] | CHECK (<check_condition>) } <tconstraint> ::= [CONSTRAINT constr_name] { PRIMARY KEY (<col_list>) [<using_index>] | UNIQUE (<col_list>) [<using_index>] | FOREIGN KEY (<col_list>) REFERENCES other_table [(<col_list>)] [<using_index>] [ON DELETE {NO ACTION | CASCADE | SET DEFAULT | SET NULL}] [ON UPDATE {NO ACTION | CASCADE | SET DEFAULT | SET NULL}] | CHECK (<check_condition>) } <col_list> ::= colname [, colname ...] <using_index> ::= USING [ASC[ENDING] | DESC[ENDING]] INDEX indexname <check_condition> ::= <val> <operator> <val> | <val> [NOT] BETWEEN <val> AND <val> | <val> [NOT] IN (<val> [, <val> ...] | <select_list>) | <val> IS [NOT] NULL | <val> IS [NOT] DISTINCT FROM <val> | <val> [NOT] CONTAINING <val> | <val> [NOT] STARTING [WITH] <val> | <val> [NOT] LIKE <val> [ESCAPE <val>] | <val> [NOT] SIMILAR TO <val> [ESCAPE <val>] | <val> <operator> {ALL | SOME | ANY} (<select_list>) | [NOT] EXISTS (<select_expr>) | [NOT] SINGULAR (<select_expr>) | (<search_condition>) | NOT <search_condition> | <search_condition> OR <search_condition> | <search_condition> AND <search_condition> <operator> ::= <> | != | ^= | ~= | = | < | > | <= | >= | !< | ^< | ~< | !> | ^> | ~> <val> ::= colname ['['array_idx [, array_idx ...]']'] | <literal> | <context_var> | <expression> | NULL | NEXT VALUE FOR genname | GEN_ID(genname, <val>) | CAST(<val> AS <datatype>) | (<select_one>) | func([<val> [, <val> ...]])
Parameter | Description |
---|---|
tablename |
Name (identifier) of the table |
operation |
One of the available operations altering the structure of the table |
colname |
Name (identifier) for a column in the table, max. 31 characters. Must be unique in the table. |
newname |
New name (identifier) for the column, max. 31 characters. Must be unique in the table. |
newpos |
The new column position (an integer between 1 and the number of columns in the table) |
col_constraint |
Column constraint |
tconstraint |
Table constraint |
constr_name |
The name (identifier) of a constraint. May consist of up to 31 characters. |
other_table |
The name of the table referenced by the foreign key constraint |
literal |
A literal value that is allowed in the given context |
context_var |
A context variable whose type is allowed in the given context |
check_condition |
The condition of a |
collation |
Name of a collation sequence that is valid for charset_name, if it is supplied with datatype or, otherwise, is valid for the default character set of the database |
array_dim |
Array dimensions |
m, n |
Integer numbers defining the index range of an array dimension |
precision |
The total number of significant digits that a value of the datatype can hold (1..18) |
scale |
The number of digits after the decimal point (0..precision) |
size |
The maximum size of a string in characters |
charset_name |
The name of a valid character set, if the character set of the column is to be different to the default character set of the database |
subtype_num |
|
subtype_name |
|
seglen |
Segment size (max. 65535) |
select_one |
A scalar |
select_list |
A |
select_expr |
A |
expression |
An expression resolving to a value that is is allowed in the given context |
genname |
Sequence (generator) name |
func |
Internal function or UDF |
The ALTER TABLE
statement changes the structure of an existing table.
With one ALTER TABLE
statement it is possible to perform multiple operations, adding/dropping columns and constraints and also altering column specifications.
Multiple operations in an ALTER TABLE
statement are separated with commas.
Version Count Increments
Some changes in the structure of a table increment the metadata change counter (“version count”) assigned to every table. The number of metadata changes is limited to 255 for each table. Once the counter reaches the 255 limit, you will not be able to make any further changes to the structure of the table without resetting the counter.
The ADD
Clause
With the ADD
clause you can add a new column or a new table constraint.
The syntax for defining the column and the syntax of defining the table constraint correspond with those described for CREATE TABLE
statement.
-
Each time a new column is added, the metadata change counter is increased by one
-
Adding a new table constraint does not increase the metadata change counter
Points to Be Aware of
|
The DROP
Clause
The DROP <column name>
clause deletes the specified column from the table.
An attempt to drop a column will fail if anything references it.
Consider the following items as sources of potential dependencies:
-
column or table constraints
-
indexes
-
stored procedures and triggers
-
views
-
Each time a column is dropped, the table’s metadata change counter is increased by one.
The DROP CONSTRAINT
Clause
The DROP CONSTRAINT
clause deletes the specified column-level or table-level constraint.
A PRIMARY KEY
or UNIQUE
key constraint cannot be deleted if it is referenced by a FOREIGN KEY
constraint in another table.
It will be necessary to drop that FOREIGN KEY
constraint before attempting to drop the PRIMARY KEY
or UNIQUE
key constraint it references.
-
Deleting a column constraint or a table constraint does not increase the metadata change counter.
The ALTER [COLUMN]
Clause
With the ALTER [COLUMN]
clause, attributes of existing columns can be modified without the need to drop and re-add the column.
Permitted modifications are:
-
change the name (does not affect the metadata change counter)
-
change the data type (increases the metadata change counter by one)
-
change the column position in the column list of the table (does not affect the metadata change counter)
-
delete the default column value (does not affect the metadata change counter)
-
set a default column value or change the existing default (does not affect the metadata change counter)
-
change the type and expression for a computed column (does not affect the metadata change counter)
Renaming a Column: the TO
Keyword
The TO keyword with a new identifier renames an existing column. The table must not have an existing column that has the same identifier.
It will not be possible to change the name of a column that is included in any constraint: PRIMARY KEY
, UNIQUE
key, FOREIGN KEY
, column constraint or the CHECK
constraint of the table.
Renaming a column will also be disallowed if the column is used in any trigger, stored procedure or view.
Changing the Data Type of a Column: the TYPE
Keyword
The keyword TYPE
changes the data type of an existing column to another, allowable type.
A type change that might result in data loss will be disallowed.
As an example, the number of characters in the new type for a CHAR
or VARCHAR
column cannot be smaller than the existing specification for it.
If the column was declared as an array, no change to its type or its number of dimensions is permitted.
The data type of a column that is involved in a foreign key, primary key or unique constraint cannot be changed at all.
Changing the Position of a Column: the POSITION
Keyword
The POSITION keyword changes the position of an existing column in the notional “left-to-right” layout of the record.
Numbering of column positions starts at 1.
-
If a position less than 1 is specified, an error message will be returned
-
If a position number is greater than the number of columns in the table, its new position will be adjusted silently to match the number of columns.
The DROP DEFAULT
and SET DEFAULT
Clauses
The optional DROP DEFAULT
clause deletes the default value for the column if it was put there previously by a CREATE TABLE
or ALTER TABLE
statement.
-
If the column is based on a domain with a default value, the default value will revert to the domain default
-
An execution error will be raised if an attempt is made to delete the default value of a column which has no default value or whose default value is domain-based
The optional SET DEFAULT
clause sets a default value for the column.
If the column already has a default value, it will be replaced with the new one.
The default value applied to a column always overrides one inherited from a domain.
The COMPUTED [BY]
or GENERATED ALWAYS AS
Clauses
The data type and expression underlying a computed column can be modified using a COMPUTED [BY]
or GENERATED ALWAYS AS
clause in the ALTER TABLE ALTER [COLUMN]
statement.
Converting a regular column to a computed one and vice versa are not permitted.
Attributes that Cannot Be Altered
The following alterations are not supported:
-
Enabling or disabling the
NOT NULL
constraint on a column -
Changing the default collation for a character type column
Only the table owner and administrators have the authority to use ALTER TABLE
.
Examples Using ALTER TABLE
-
Adding the
CAPITAL
column to theCOUNTRY
table.ALTER TABLE COUNTRY ADD CAPITAL VARCHAR(25);
-
Adding the
CAPITAL
column with theUNIQUE
constraint and deleting theCURRENCY
column.ALTER TABLE COUNTRY ADD CAPITAL VARCHAR(25) NOT NULL UNIQUE, DROP CURRENCY;
-
Adding the
CHK_SALARY
check constraint and a foreign key to theJOB
table.ALTER TABLE JOB ADD CONSTRAINT CHK_SALARY CHECK (MIN_SALARY < MAX_SALARY), ADD FOREIGN KEY (JOB_COUNTRY) REFERENCES COUNTRY (COUNTRY);
-
Setting default value for the
MODEL
field, changing the type of theITEMID
column and renaming the MODELNAME column.ALTER TABLE STOCK ALTER COLUMN MODEL SET DEFAULT 1, ALTER COLUMN ITEMID TYPE BIGINT, ALTER COLUMN MODELNAME TO NAME;
-
Changing the computed columns
NEW_SALARY
andSALARY_CHANGE
.ALTER TABLE SALARY_HISTORY ALTER NEW_SALARY GENERATED ALWAYS AS (OLD_SALARY + OLD_SALARY * PERCENT_CHANGE / 100), ALTER SALARY_CHANGE COMPUTED BY (OLD_SALARY * PERCENT_CHANGE / 100);
5.4.3. DROP TABLE
deleting a table
DSQL, ESQL
DROP TABLE tablename
Parameter | Description |
---|---|
tablename |
Name (identifier) of the table |
The DROP TABLE
statement deletes an existing table.
If the table has dependencies, the DROP TABLE
statement will fail with an execution error.
When a table is dropped, all triggers for its events and indexes built for its fields will be deleted as well.
Only the table owner and administrators have the authority to use DROP TABLE
.
Deleting the COUNTRY
table.
DROP TABLE COUNTRY;
5.4.4. RECREATE TABLE
creating a new table (relation) or recreating an existing one
DSQL
RECREATE [GLOBAL TEMPORARY] TABLE tablename [EXTERNAL [FILE] 'filespec'] (<col_def> [, {<col_def> | <tconstraint>} ...]) [ON COMMIT {DELETE | PRESERVE} ROWS]
See the CREATE TABLE
section for the full syntax of CREATE TABLE
and descriptions of defining tables, columns and constraints.
RECREATE TABLE
creates or recreates a table.
If a table with this name already exists, the RECREATE TABLE
statement will try to drop it and create a new one.
Existing dependencies will prevent the statement from executing.
Creating or recreating the COUNTRY
table.
RECREATE TABLE COUNTRY (
COUNTRY COUNTRYNAME NOT NULL PRIMARY KEY,
CURRENCY VARCHAR(10) NOT NULL
);
5.5. INDEX
An index is a database object used for faster data retrieval from a table or for speeding up the sorting of query.
Indexes are used also to enforce the refererential integrity constraints PRIMARY KEY
, FOREIGN KEY
and UNIQUE
.
This section describes how to create indexes, activate and deactivate them, delete them and collect statistics (recalculate selectivity) for them.
5.5.1. CREATE INDEX
Creating an index for a table
DSQL, ESQL
CREATE [UNIQUE] [ASC[ENDING] | DESC[ENDING]] INDEX indexname ON tablename {(col [, col …]) | COMPUTED BY (<expression>)}
Parameter | Description |
---|---|
indexname |
Index name. It may consist of up to 31 characters |
tablename |
The name of the table for which the index is to be built |
col |
Name of a column in the table.
Columns of the types |
expression |
The expression that will compute the values for a computed index, also known as an “expression index” |
The CREATE INDEX
statement creates an index for a table that can be used to speed up searching, sorting and grouping.
Indexes are created automatically in the process of defining constraints, such as primary key, foreign key or unique constraints.
An index can be built on the content of columns of any data type except for BLOB
and arrays.
The name (identifier) of an index must be unique among all index names.
Key Indexes
When a primary key, foreign key or unique constraint is added to a table or column, an index with the same name is created automatically, without an explicit directive from the designer.
For example, the
|
Unique Indexes
Specifying the keyword UNIQUE
in the index creation statement creates an index in which uniqueness will be enforced throughout the table.
The index is referred to as a “unique index”.
A unique index is not a constraint.
Unique indexes cannot contain duplicate key values (or duplicate key value combinations, in the case of compound, or multi-column, or multi-segment) indexes.
Duplicated NULL
s are permitted, in accordance with the SQL:99 standard, in both single-segment and multi-segment indexes.
Index Direction
All indexes in Firebird are uni-directional.
An index may be constructed from the lowest value to the highest (ascending order) or from the highest value to the lowest (descending order). The keywords ASC[ENDING]
and DESC[ENDING]
are used to specify the direction of the index.
The default index order is ASC[ENDING]
.
It is quite valid to define both an ascending and a descending index on the same column or key set.
A descending index can be useful on a column that will be subjected to searches on the high values (“newest”, maximum, etc.) |
Computed (Expression) Indexes
In creating an index, you can use the COMPUTED BY
clause to specify an expression instead of one or more columns.
Computed indexes are used in queries where the condition in a WHERE
, ORDER BY
or GROUP BY
clause exactly matches the expression in the index definition.
The expression in a computed index may involve several columns in the table.
Expression indexes can also be used as a workaround for indexing computed columns: use the name of the computed column as the expression. |
Limits on Indexes
Certain limits apply to indexes.
The maximum length of a key in an index is limited to ¼ of the page size.
Maximum Indexes per Table
The number of indexes that can be accommodated for each table is limited. The actual maximum for a specific table depends on the page size and the number of columns in the indexes.
Page Size |
Number of Indexes Depending on Column Count |
||
---|---|---|---|
Single |
2-Column |
3-Column |
|
4096 |
203 |
145 |
113 |
8192 |
408 |
291 |
227 |
16384 |
818 |
584 |
454 |
Character Index Limits
The maximum indexed string length is 9 bytes less than the maximum key length. The maximum indexable string length depends on the page size and the character set.
Page Size |
Maximum Indexable String Length by Charset Type |
|||
---|---|---|---|---|
1 byte/char |
2 byte/char |
3 byte/char |
4 byte/char |
|
4096 |
1015 |
507 |
338 |
253 |
8192 |
2039 |
1019 |
679 |
509 |
16384 |
4087 |
2043 |
1362 |
1021 |
Only the table owner and administrators have the authority to use CREATE INDEX
.
Examples Using CREATE INDEX
-
Creating an index for the
UPDATER_ID
column in theSALARY_HISTORY
tableCREATE INDEX IDX_UPDATER ON SALARY_HISTORY (UPDATER_ID);
-
Creating an index with keys sorted in the descending order for the
CHANGE_DATE
column in theSALARY_HISTORY
tableCREATE DESCENDING INDEX IDX_CHANGE ON SALARY_HISTORY (CHANGE_DATE);
-
Creating a multi-segment index for the
ORDER_STATUS
,PAID
columns in theSALES
tableCREATE INDEX IDX_SALESTAT ON SALES (ORDER_STATUS, PAID);
-
Creating an index that does not permit duplicate values for the
NAME
column in theCOUNTRY
tableCREATE UNIQUE INDEX UNQ_COUNTRY_NAME ON COUNTRY (NAME);
-
Creating a computed index for the
PERSONS
tableCREATE INDEX IDX_NAME_UPPER ON PERSONS COMPUTED BY (UPPER (NAME));
An index like this can be used for a case-insensitive search:
SELECT * FROM PERSONS WHERE UPPER(NAME) STARTING WITH UPPER('Iv');
5.5.2. ALTER INDEX
Activating or deactivating an index; rebuilding an index
DSQL, ESQL
ALTER INDEX indexname {ACTIVE | INACTIVE}
Parameter | Description |
---|---|
indexname |
Index name |
The ALTER INDEX
statement activates or deactivates an index.
There is no facility on this statement for altering any attributes of the index.
-
With the
INACTIVE
option, the index is switched from the active to inactive state. The effect is similar to theDROP INDEX
statement except that the index definition remains in the database. Altering a constraint index to the inactive state is not permitted.An active index can be deactivated if there are no queries using that index; otherwise, an “object in use” error is returned.
Activating an inactive index is also safe. However, if there are active transactions modifying the table, the transaction containing the
ALTER INDEX
statement will fail if it has theNOWAIT
attribute. If the transaction is inWAIT
mode, it will wait for completion of concurrent transactions.On the other side of the coin, if our
ALTER INDEX
succeeds and starts to rebuild the index atCOMMIT
, other transactions modifying that table will fail or wait, according to theirWAIT
/NO WAIT
attributes. The situation is exactly the same forCREATE INDEX
.How is it Useful?It might be useful to switch an index to the inactive state whilst inserting, updating or deleting a large batch of records in the table that owns the index.
-
With the
ACTIVE
option, if the index is in the inactive state, it will be switched to active state and the system rebuilds the index.How is it Useful?Even if the index is active when
ALTER INDEX … ACTIVE
is executed, the index will be rebuilt. Rebuilding indexes can be a useful piece of houskeeping to do, occasionally, on the indexes of a large table in a database that has frequent inserts, updates or deletes but is infrequently restored.
Use of ALTER INDEX
on a Constraint Index
Altering the enforcing index of a PRIMARY KEY
, FOREIGN KEY
or UNIQUE
constraint to INACTIVE
is not permitted.
However, ALTER INDEX … ACTIVE
works just as well with constraint indexes as it does with others, as an index rebuilding tool.
Only the table owner and administrators have the authority to use ALTER INDEX
.
5.5.3. DROP INDEX
Deleting an index
DSQL, ESQL
DROP INDEX indexname
Parameter | Description |
---|---|
indexname |
Index name |
The DROP INDEX
statement deletes the named index from the database.
A constraint index cannot deleted using |
Only the table owner and administrators have the authority to use DROP INDEX
.
Deleting the IDX_UPDATER
index
DROP INDEX IDX_UPDATER;
5.5.4. SET STATISTICS
Recalculating the selectivity of an index
DSQL, ESQL
SET STATISTICS INDEX indexname
Parameter | Description |
---|---|
indexname |
Index name |
The SET STATISTICS
statement recalculates the selectivity of the specified index.
Index Selectivity
The selectivity of an index is the result of evaluating the number of rows that can be selected in a search on every index value. A unique index has the maximum selectivity because it is impossible to select more than one row for each value of an index key if it is used. Keeping the selectivity of an index up to date is important for the optimizer’s choices in seeking the most optimal query plan.
Index statistics in Firebird are not automatically recalculated in response to large batches of inserts, updates or deletions. It may be beneficial to recalculate the selectivity of an index after such operations because the selectivity tends to become outdated.
The statements |
The selectivity of an index can be recalculated by the owner of the table or an administrator.
It can be performed under concurrent load without risk of corruption.
However, be aware that, under concurrent load, the newly calculated statistics could become outdated as soon as SET STATISTICS
finishes.
5.6. VIEW
A view is a virtual table that is actually a stored and named SELECT
query for retrieving data of any complexity.
Data can be retrieved from one or more tables, from other views and also from selectable stored procedures.
Unlike regular tables in relational databases, a view is not an independent data set stored in the database. The result is dynamically created as a data set when the view is selected.
The metadata of a view are available to the process that generates the binary code for stored procedures and triggers, just as though they were concrete tables storing persistent data.
5.6.1. CREATE VIEW
Creating a view
DSQL
CREATE VIEW viewname [<full_column_list>] AS <select_statement> [WITH CHECK OPTION] <full_column_list> ::= (colname [, colname ...])
Parameter | Description |
---|---|
viewname |
View name, maximum 31 characters |
select_statement |
SELECT statement |
full_column_list |
The list of columns in the view |
colname |
View column name. Duplicate column names are not allowed. |
The CREATE VIEW
statement creates a new view.
The identifier (name) of a view must be unique among the names of all views, tables and stored procedures in the database.
The name of the new view can be followed by the list of column names that should be returned to the caller when the view is invoked. Names in the list do not have to be related to the names of the columns in the base tables from which they derive.
If the view column list is omitted, the system will use the column names and/or aliases from the SELECT
statement.
If duplicate names or non-aliased expression-derived columns make this impossible to obtain a valid list, creation of the view fails with an error.
The number of columns in the view’s list must exactly match the number of columns in the selection list of the underlying SELECT statement in the view definition.
Additional Points
|
Updatable Views
A view can be updatable or read-only.
If a view is updatable, the data retrieved when this view is called can be changed by the DML statements INSERT
, UPDATE
, DELETE
, UPDATE OR INSERT
or MERGE
.
Changes made in an updatable view are applied to the underlying table(s).
A read-only view can be made updateable with the use of triggers. Once triggers have been defined on a view, changes posted to it will never be written automatically to the underlying table, even if the view was updateable to begin with. It is the responsibility of the programmer to ensure that the triggers update (or delete from, or insert into) the base tables as needed.
A view will be automatically updatable if all the following conditions are met:
-
the
SELECT
statement queries only one table or one updatable view -
the
SELECT
statement does not call any stored procedures -
each base table (or base view) column not present in the view definition is covered by one of the following conditions:
-
it is nullable
-
it has a non-
NULL
default value -
it has a trigger that supplies a permitted value
-
-
the
SELECT
statement contains no fields derived from subqueries or other expressions -
the
SELECT
statement does not contain fields defined through aggregate functions, such asMIN
,MAX
,AVG
,SUM
,COUNT
,LIST
-
the
SELECT
statement contains noORDER BY
orGROUP BY
clause -
the
SELECT
statement does not include the keywordDISTINCT
or row-restrictive keywords such asROWS
,FIRST
,SKIP
WITH CHECK OPTION
The optional WITH CHECK OPTION
clause requires an updatable view to check whether new or updated data meet the condition specified in the WHERE
clause of the SELECT
statement.
Every attempt to insert a new record or to update an existing one is checked whether the new or updated record would meet the WHERE
criteria.
If they fail the check, the operation is not performed and an appropriate error message is returned.
WITH CHECK OPTION
can be specified only in a CREATE VIEW
statement in which a WHERE
clause is present to restrict the output of the main SELECT
statement.
An error message is returned otherwise.
Please note:
If Furthermore, view fields omitted from the For views that do not have |
Ownership of a View
The creator of a view becomes its owner.
To create a view, a non-admin user needs at least SELECT
access to the underlying table(s) and/or view(s), and the EXECUTE
privilege on any selectable stored procedures involved.
To enable insertions, updates and deletions through the view, the creator/owner must also possess the corresponding INSERT
, UPDATE
and DELETE
rights on the base object(s).
Granting other users privileges on the view is only possible if the view owner himself has these privileges on the underlying objects WITH GRANT OPTION
.
It will always be the case if the view owner is also the owner of the underlying objects.
Examples of Creating Views
-
Creating view returning the
JOB_CODE
andJOB_TITLE
columns only for those jobs whereMAX_SALARY
is less than $15,000.CREATE VIEW ENTRY_LEVEL_JOBS AS SELECT JOB_CODE, JOB_TITLE FROM JOB WHERE MAX_SALARY < 15000;
-
Creating a view returning the
JOB_CODE
andJOB_TITLE
columns only for those jobs whereMAX_SALARY
is less than $15,000. Whenever a new record is inserted or an existing record is updated, theMAX_SALARY < 15000
condition will be checked. If the condition is not true, the insert/update operation will be rejected.CREATE VIEW ENTRY_LEVEL_JOBS AS SELECT JOB_CODE, JOB_TITLE FROM JOB WHERE MAX_SALARY < 15000 WITH CHECK OPTION;
-
Creating a view with an explicit column list.
CREATE VIEW PRICE_WITH_MARKUP ( CODE_PRICE, COST, COST_WITH_MARKUP ) AS SELECT CODE_PRICE, COST, COST * 1.1 FROM PRICE;
-
Creating a view with the help of aliases for fields in the
SELECT
statement (the same result as in Example 3).CREATE VIEW PRICE_WITH_MARKUP AS SELECT CODE_PRICE, COST, COST * 1.1 AS COST_WITH_MARKUP FROM PRICE;
-
Creating a read-only view based on two tables and a stored procedure.
CREATE VIEW GOODS_PRICE AS SELECT goods.name AS goodsname, price.cost AS cost, b.quantity AS quantity FROM goods JOIN price ON goods.code_goods = price.code_goods LEFT JOIN sp_get_balance(goods.code_goods) b ON 1 = 1;
5.6.2. ALTER VIEW
Modifying an existing view
DSQL
ALTER VIEW viewname [<full_column_list>] AS <select_statement> [WITH CHECK OPTION] <full_column_list> ::= (colname [, colname ...])
Parameter | Description |
---|---|
viewname |
Name of an existing view |
select_statement |
SELECT statement |
full_column_list |
The list of columns in the view |
colname |
View column name. Duplicate column names are not allowed. |
Use the ALTER VIEW
statement for changing the definition of an existing view.
Privileges for views remain intact and dependencies are not affected.
The syntax of the ALTER VIEW
statement corresponds completely with that of CREATE VIEW
.
Be careful when you change the number of columns in a view. Existing application code and PSQL modules that access the view may become invalid. For information on how to detect this kind of problem in stored procedures and trigger, see The RDB$VALID_BLR Field in the Appendix. |
Only the view owner and administrators have the authority to use ALTER VIEW
.
ALTER VIEW
Altering the view PRICE_WITH_MARKUP
ALTER VIEW PRICE_WITH_MARKUP (
CODE_PRICE,
COST,
COST_WITH_MARKUP
) AS
SELECT
CODE_PRICE,
COST,
COST * 1.15
FROM PRICE;
5.6.3. CREATE OR ALTER VIEW
Creating a new view or altering an existing view.
DSQL
CREATE OR ALTER VIEW viewname [<full_column_list>] AS <select_statement> [WITH CHECK OPTION] <full_column_list> ::= (colname [, colname ...])
Parameter | Description |
---|---|
viewname |
Name of a view which may or may not exist |
select_statement |
SELECT statement |
full_column_list |
The list of columns in the view |
colname |
View column name. Duplicate column names are not allowed. |
Use the CREATE OR ALTER VIEW
statement for changing the definition of an existing view or creating it if it does not exist.
Privileges for an existing view remain intact and dependencies are not affected.
The syntax of the CREATE OR ALTER VIEW
statement corresponds completely with that of CREATE VIEW
.
Creating the new view PRICE_WITH_MARKUP
view or altering it if it already exists:
CREATE OR ALTER VIEW PRICE_WITH_MARKUP (
CODE_PRICE,
COST,
COST_WITH_MARKUP
) AS
SELECT
CODE_PRICE,
COST,
COST * 1.15
FROM PRICE;
5.6.4. DROP VIEW
Deleting (dropping) a view
DSQL
DROP VIEW viewname
Parameter | Description |
---|---|
viewname |
View name |
The DROP VIEW
statement deletes an existing view.
The statement will fail if the view has dependencies.
Only the view owner and administrators have the authority to use DROP VIEW
.
Deleting the PRICE_WITH_MARKUP
view.
DROP VIEW PRICE_WITH_MARKUP;
5.6.5. RECREATE VIEW
Creating a new view or recreating an existing view
DSQL
RECREATE VIEW viewname [<full_column_list>] AS <select_statement> [WITH CHECK OPTION] <full_column_list> ::= (colname [, colname ...])
Parameter | Description |
---|---|
viewname |
View name, maximum 31 characters |
select_statement |
SELECT statement |
full_column_list |
The list of columns in the view |
colname |
View column name. Duplicate column names are not allowed. |
Creates or recreates a view.
If there is a view with this name already, the engine will try to drop it before creating the new instance.
If the existing view cannot be dropped, because of dependencies or insufficient rights, for example, RECREATE VIEW
fails with an error.
Creating the new view PRICE_WITH_MARKUP
view or recreating it, if it already exists.
RECREATE VIEW PRICE_WITH_MARKUP (
CODE_PRICE,
COST,
COST_WITH_MARKUP
) AS
SELECT
CODE_PRICE,
COST,
COST * 1.15
FROM PRICE;
5.7. TRIGGER
A trigger is a special type of stored procedure that is not called directly, instead being executed when a specified event occurs in the associated table or view. A trigger is specific to one and only one relation (table or view) and one phase in the timing of the event (BEFORE or AFTER). It can be specified to execute for one specific event (insert, update, delete) or for some combination of two or three of those events.
Another form of trigger — known as a “database trigger” — can be specified to fire in association with the start or end of a user session (connection) or a user transaction.
5.7.1. CREATE TRIGGER
Creating a new trigger
DSQL, ESQL
CREATE TRIGGER trigname { <relation_trigger_legacy> | <relation_trigger_sql2003> | <database_trigger> } AS [<declarations>] BEGIN [<PSQL_statements>] END <relation_trigger_legacy> ::= FOR {tablename | viewname} [ACTIVE | INACTIVE] {BEFORE | AFTER} <mutation_list> [POSITION number] <relation_trigger_sql2003> ::= [ACTIVE | INACTIVE] {BEFORE | AFTER} <mutation_list> [POSITION number] ON {tablename | viewname} <database_trigger> ::= [ACTIVE | INACTIVE] ON <db_event> [POSITION number] <mutation_list> ::= <mutation> [OR <mutation> [OR <mutation>]] <mutation> ::= { INSERT | UPDATE | DELETE } <db_event> ::= { CONNECT | DISCONNECT | TRANSACTION START | TRANSACTION COMMIT | TRANSACTION ROLLBACK } <declarations> ::= {<declare_var> | <declare_cursor>}; [{<declare_var> | <declare_cursor>}; …]
Parameter | Description |
---|---|
trigname |
Trigger name consisting of up to 31 characters. It must be unique among all trigger names in the database. |
relation_trigger_legacy |
Legacy style of trigger declaration for a relation trigger |
relation_trigger_sql2003 |
Relation trigger declaration compliant with the SQL:2003 standard |
database_trigger |
Database trigger declaration |
tablename |
Name of the table with which the relation trigger is associated |
viewname |
Name of the view with which the relation trigger is associated |
mutation_list |
List of relation (table | view) events |
number |
Position of the trigger in the firing order. From 0 to 32,767 |
db_event |
Connection or transaction event |
declarations |
Section for declaring local variables and named cursors |
declare_var |
Local variable declaration |
declare_cursor |
Named cursor declaration |
PSQL_statements |
Statements in Firebird’s programming language (PSQL) |
The CREATE TRIGGER
statement is used for creating a new trigger.
A trigger can be created either for a relation (table | view) event (or a combination of events), or for a database event.
CREATE TRIGGER
, along with its associates ALTER TRIGGER
, CREATE OR ALTER TRIGGER
and RECREATE TRIGGER
, is a compound statement, consisting of a header and a body.
The header specifies the name of the trigger, the name of the relation (for a relation trigger), the phase of the trigger and the event[s] it applies to.
The body consists of optional declarations of local variables and named cursors followed by one or more statements, or blocks of statements, all enclosed in an outer block that begins with the keyword BEGIN
and ends with the keyword END
.
Declarations and embedded statements are terminated with semi-colons (‘;
’).
The name of the trigger must be unique among all trigger names.
Statement Terminators
Some SQL statement editors — specifically the isql utility that comes with Firebird and possibly some third-party editors — employ an internal convention that requires all statements to be terminated with a semi-colon. This creates a conflict with PSQL syntax when coding in these environments. If you are unacquainted with this problem and its solution, please study the details in the PSQL chapter in the section entitled Switching the Terminator in isql.
Relation Triggers (on Tables or Views)
Relation triggers are executed at the row (record) level every time the row image changes.
A trigger can be either ACTIVE
or INACTIVE
.
Only active triggers are executed.
Triggers are created ACTIVE
by default.
Forms of Declaration
Firebird supports two forms of declaration for relation triggers:
-
The original, legacy syntax
-
The SQL:2003 standard-compliant form (recommended)
The SQL:2003 standard-compliant form is the recommended one.
A relation trigger specifies — among other things — a phase and one or more events.
Phase
Phase concerns the timing of the trigger with regard to the change-of-state event in the row of data:
-
A
BEFORE
trigger is fired before the specified database operation (insert, update or delete) is carried out -
An
AFTER
trigger is fired after the database operation has been completed
Row Events
A relation trigger definition specifies at least one of the DML operations INSERT
, UPDATE
and DELETE
, to indicate one or more events on which the trigger should fire.
If multiple operations are specified, they must be separated by the keyword OR
.
No operation may occur more than once.
Firing Order of Triggers
The keyword POSITION
allows an optional execution order (“firing order”) to be specified for a series of triggers that have the same phase and event as their target.
The default position is 0.
If multiple triggers have the same position and phase, those triggers will be executed in an undefined order, while respecting the total order by position and phase.
Variable Declarations
The optional declarations section beneath the keyword AS
in the header of the trigger is for defining variables and named cursors that are local to the trigger.
For more details, see DECLARE VARIABLE
and DECLARE CURSOR
in the Procedural SQL chapter.
The Trigger Body
The local declarations (if any) are the final part of a trigger’s header section.
The trigger body follows, where one or more blocks of PSQL statements are enclosed in a structure that starts with the keyword BEGIN
and terminates with the keyword END
.
Only the owner of the view or table and administrators have the authority to use CREATE TRIGGER
.
Examples of CREATE TRIGGER for Tables and Views
-
Creating a trigger in the “legacy” form, firing before the event of inserting a new record into the
CUSTOMER
table occurs.CREATE TRIGGER SET_CUST_NO FOR CUSTOMER ACTIVE BEFORE INSERT POSITION 0 AS BEGIN IF (NEW.CUST_NO IS NULL) THEN NEW.CUST_NO = GEN_ID(CUST_NO_GEN, 1); END
-
Creating a trigger firing before the event of inserting a new record into the
CUSTOMER
table in the SQL:2003 standard-compliant form.CREATE TRIGGER set_cust_no ACTIVE BEFORE INSERT POSITION 0 ON customer AS BEGIN IF (NEW.cust_no IS NULL) THEN NEW.cust_no = GEN_ID(cust_no_gen, 1); END
-
Creating a trigger that will file after either inserting, updating or deleting a record in the
CUSTOMER
table.CREATE TRIGGER TR_CUST_LOG ACTIVE AFTER INSERT OR UPDATE OR DELETE POSITION 10 ON CUSTOMER AS BEGIN INSERT INTO CHANGE_LOG (LOG_ID, ID_TABLE, TABLE_NAME, MUTATION) VALUES (NEXT VALUE FOR SEQ_CHANGE_LOG, OLD.CUST_NO, 'CUSTOMER', CASE WHEN INSERTING THEN 'INSERT' WHEN UPDATING THEN 'UPDATE' WHEN DELETING THEN 'DELETE' END); END
Database Triggers
Triggers can be defined to fire upon “database events”, which really refers to a mixture of events that act across the scope of a session (connection) and events that act across the scope of an individual transaction:
-
CONNECT
-
DISCONNECT
-
TRANSACTION START
-
TRANSACTION COMMIT
-
TRANSACTION ROLLBACK
Execution of Database Triggers and Exception Handling
CONNECT
and DISCONNECT
triggers are executed in a transaction created specifically for this purpose.
If all goes well, the transaction is committed.
Uncaught exceptions cause the transaction to roll back, and
-
for a
CONNECT
trigger, the connection is then broken and the exception is returned to the client -
for a
DISCONNECT
trigger, exceptions are not reported. The connection is broken as intended
TRANSACTION
triggers are executed within the transaction whose start, commit or rollback evokes them.
The action taken after an uncaught exception depends on the event:
-
In a
TRANSACTION START
trigger, the exception is reported to the client and the transaction is rolled back -
In a
TRANSACTION COMMIT
trigger, the exception is reported, the trigger’s actions so far are undone and the commit is cancelled -
In a
TRANSACTION ROLLBACK
trigger, the exception is not reported and the transaction is rolled back as intended.
Obviously there is no direct way of knowing if a DISCONNECT
or TRANSACTION ROLLBACK
trigger caused an exception.
It also follows that the connection to the database cannot happen if a CONNECT
trigger causes an exception and a transaction cannot start if a TRANSACTION START
trigger causes one, either.
Both phenomena effectively lock you out of your database until you get in there with database triggers suppressed and fix the bad code.
Some Firebird command-line tools have been supplied with switches that an administrator can use to suppress the automatic firing of database triggers. So far, they are:
gbak -nodbtriggers
isql -nodbtriggers
nbackup -T
In a two-phase commit scenario, TRANSACTION COMMIT
triggers fire in the prepare phase, not at the commit.
-
The use of the
IN AUTONOMOUS TRANSACTION DO
statement in the database event triggers related to transactions (TRANSACTION START
,TRANSACTION ROLLBACK
,TRANSACTION COMMIT
) may cause the autonomous transaction to enter an infinite loop -
The
DISCONNECT
andTRANSACTION ROLLBACK
event triggers will not be executed when clients are disconnected via monitoring tables (DELETE FROM MON$ATTACHMENTS
)
Only the database owner and administrators have the authority to create database triggers.
Examples of CREATE TRIGGER
for “Database Triggers”
-
Creating a trigger for the event of connecting to the database that logs users logging into the system. The trigger is created as inactive.
CREATE TRIGGER tr_log_connect INACTIVE ON CONNECT POSITION 0 AS BEGIN INSERT INTO LOG_CONNECT (ID, USERNAME, ATIME) VALUES (NEXT VALUE FOR SEQ_LOG_CONNECT, CURRENT_USER, CURRENT_TIMESTAMP); END
-
Creating a trigger for the event of connecting to the database that does not permit any users, except for SYSDBA, to log in during off hours.
CREATE EXCEPTION E_INCORRECT_WORKTIME 'The working day has not started yet.'; CREATE TRIGGER TR_LIMIT_WORKTIME ACTIVE ON CONNECT POSITION 1 AS BEGIN IF ((CURRENT_USER <> 'SYSDBA') AND NOT (CURRENT_TIME BETWEEN time '9:00' AND time '17:00')) THEN EXCEPTION E_INCORRECT_WORKTIME; END
5.7.2. ALTER TRIGGER
Modifying and deactivating an existing trigger
DSQL, ESQL
ALTER TRIGGER trigname [ACTIVE | INACTIVE] [{BEFORE | AFTER} <mutation_list> | ON <db_event>] [POSITION number] [ AS [<declarations>] BEGIN [<PSQL_statements>] END ] <mutation_list> ::= <mutation> [OR <mutation> [OR <mutation>]] <mutation> ::= { INSERT | UPDATE | DELETE } <db_event> ::= { CONNECT | DISCONNECT | TRANSACTION START | TRANSACTION COMMIT | TRANSACTION ROLLBACK } <declarations> ::= {<declare_var> | <declare_cursor>}; [{<declare_var> | <declare_cursor>}; …]
Parameter | Description |
---|---|
trigname |
Name of an existing trigger |
mutation_list |
List of relation (table | view) events |
number |
Position of the trigger in the firing order. From 0 to 32,767 |
declarations |
Section for declaring local variables and named cursors |
declare_var |
Local variable declaration |
declare_cursor |
Named cursor declaration |
PSQL_statements |
Statements in Firebird’s programming language (PSQL) |
The ALTER TRIGGER
statement allows certain changes to the header and body of a trigger.
Permitted Changes to Triggers
-
Status (
ACTIVE | INACTIVE
) -
Phase (
BEFORE | AFTER
) -
Events; but relation trigger events cannot be changed to database trigger events, nor vice versa
-
Position in the firing order
-
Modifications to code in the trigger body
If some element was not specified, it remains unchanged.
Reminders
The More than one relation event — The keyword |
Administrators and the following users have the authority to use ALTER TRIGGER
:
-
For relation triggers, the owner of the table
-
For database triggers, the owner of the database
Examples using ALTER TRIGGER
-
Deactivating the
set_cust_no
trigger (switching it to the inactive status).ALTER TRIGGER set_cust_no INACTIVE;
-
Changing the firing order position of the
set_cust_no
trigger.ALTER TRIGGER set_cust_no POSITION 14;
-
Switching the
TR_CUST_LOG
trigger to the inactive status and modifying the list of events.ALTER TRIGGER TR_CUST_LOG INACTIVE AFTER INSERT OR UPDATE;
-
Switching the
tr_log_connect trigger
to the active status, changing its position and body.ALTER TRIGGER tr_log_connect ACTIVE POSITION 1 AS BEGIN INSERT INTO LOG_CONNECT (ID, USERNAME, ROLENAME, ATIME) VALUES (NEXT VALUE FOR SEQ_LOG_CONNECT, CURRENT_USER, CURRENT_ROLE, CURRENT_TIMESTAMP); END
5.7.3. CREATE OR ALTER TRIGGER
Creating a new trigger or altering an existing trigger
DSQL
CREATE OR ALTER TRIGGER trigname { <relation_trigger_legacy> | <relation_trigger_sql2003> | <database_trigger> } AS [<declarations>] BEGIN [<PSQL_statements>] END
For the full detail of the syntax, see CREATE TRIGGER
.
The CREATE OR ALTER TRIGGER
statement creates a new trigger if it does not exist;
otherwise it alters and recompiles it with the privileges intact and dependencies unaffected.
CREATE OR ALTER TRIGGER
Creating a new trigger if it does not exist or altering it if it does exist.
CREATE OR ALTER TRIGGER set_cust_no
ACTIVE BEFORE INSERT POSITION 0 ON customer
AS
BEGIN
IF (NEW.cust_no IS NULL) THEN
NEW.cust_no = GEN_ID(cust_no_gen, 1);
END
5.7.4. DROP TRIGGER
Deleting an existing trigger
DSQL, ESQL
DROP TRIGGER trigname
Parameter | Description |
---|---|
trigname |
Trigger name |
The DROP TRIGGER
statement deletes an existing trigger.
Administrators and the following users have the authority to use DROP TRIGGER
:
-
For relation triggers, the owner of the table
-
For database triggers, the owner of the database
DROP TRIGGER
Deleting the set_cust_no
trigger.
DROP TRIGGER set_cust_no;
5.7.5. RECREATE TRIGGER
Creating a new trigger or recreating an existing trigger
DSQL
RECREATE TRIGGER trigname { <relation_trigger_legacy> | <relation_trigger_sql2003> | <database_trigger> } AS [<declarations>] BEGIN [<PSQL_statements>] END
For the full detail of the syntax, see CREATE TRIGGER
.
The RECREATE TRIGGER
statement creates a new trigger if no trigger with the specified name exists;
otherwise the RECREATE TRIGGER
statement tries to delete the existing trigger and create a new one.
The operation will fail on COMMIT
if the trigger is in use.
Be aware that dependency errors are not detected until the |
RECREATE TRIGGER
Creating or recreating the set_cust_no
trigger.
RECREATE TRIGGER set_cust_no
ACTIVE BEFORE INSERT POSITION 0 ON customer
AS
BEGIN
IF (NEW.cust_no IS NULL) THEN
NEW.cust_no = GEN_ID(cust_no_gen, 1);
END
5.8. PROCEDURE
A stored procedure is a software module that can be called from a client, another procedure, an executable block or a trigger. Stored procedures, executable blocks and triggers are written in procedural SQL (PSQL). Most SQL statements are available in PSQL as well, sometimes with limitations or extensions. Among notable exceptions are DDL and transaction control statements.
Stored procedures can have many input and output parameters.
5.8.1. CREATE PROCEDURE
Creating a new stored procedure
DSQL, ESQL
CREATE PROCEDURE procname [(<inparam> [, <inparam> ...])] [RETURNS (<outparam> [, <outparam> ...])] AS [<declarations>] BEGIN [<PSQL_statements>] END <inparam> ::= <param_decl> [{= | DEFAULT} <value>] <outparam> ::= <param_decl> <value> ::= {<literal> | NULL | <context_var>} <param_decl> ::= paramname <type> [NOT NULL] [COLLATE collation] <type> ::= <datatype> | [TYPE OF] domain | TYPE OF COLUMN rel.col <datatype> ::= {SMALLINT | INT[EGER] | BIGINT} | {FLOAT | DOUBLE PRECISION} | {DATE | TIME | TIMESTAMP} | {DECIMAL | NUMERIC} [(precision [, scale])] | {CHAR | CHARACTER} [VARYING] | VARCHAR} [(size)] [CHARACTER SET charset] | {NCHAR | NATIONAL {CHARACTER | CHAR}} [VARYING] [(size)] | BLOB [SUB_TYPE {subtype_num | subtype_name}] [SEGMENT SIZE seglen] [CHARACTER SET charset] | BLOB [(seglen [, subtype_num])] <declarations> ::= {<declare_var> | <declare_cursor>}; [{<declare_var> | <declare_cursor>}; …]
Parameter | Description |
---|---|
procname |
Stored procedure name consisting of up to 31 characters. Must be unique among all table, view and procedure names in the database |
inparam |
Input parameter description |
outparam |
Output parameter description |
declarations |
Section for declaring local variables and named cursors |
declare_var |
Local variable declaration |
declare_cursor |
Named cursor declaration |
PSQL_statements |
Procedural SQL statements |
literal |
A literal value that is assignment-compatible with the data type of the parameter |
context_var |
Any context variable whose type is compatible with the data type of the parameter |
paramname |
The name of an input or output parameter of the procedure. It may consist of up to 31 characters. The name of the parameter must be unique among input and output parameters of the procedure and its local variables |
datatype |
SQL data type |
collation |
Collation sequence |
domain |
Domain name |
rel |
Table or view name |
col |
Table or view column name |
precision |
The total number of significant digits that the parameter should be able to hold (1..18) |
scale |
The number of digits after the decimal point (0..precision) |
size |
The maximum size of a string type parameter or variable, in characters |
charset |
Character set of a string type parameter or variable |
subtype_num |
|
subtype_name |
|
seglen |
Segment size (max. 65535) |
The CREATE PROCEDURE
statement creates a new stored procedure.
The name of the procedure must be unique among the names of all stored procedures, tables and views in the database.
CREATE PROCEDURE
is a compound statement, consisting of a header and a body.
The header specifies the name of the procedure and declares input parameters and the output parameters, if any, that are to be returned by the procedure.
The procedure body consists of declarations for any local variables and named cursors that will be used by the procedure, followed by one or more statements, or blocks of statements, all enclosed in an outer block that begins with the keyword BEGIN
and ends with the keyword END
.
Declarations and embedded statements are terminated with semi-colons (‘;
’).
Statement Terminators
Some SQL statement editors — specifically the isql utility that comes with Firebird and possibly some third-party editors — employ an internal convention that requires all statements to be terminated with a semi-colon. This creates a conflict with PSQL syntax when coding in these environments. If you are unacquainted with this problem and its solution, please study the details in the PSQL chapter in the section entitled Switching the Terminator in isql.
Parameters
Each parameter has a data type specified for it.
The NOT NULL
constraint can also be specified for any parameter, to prevent NULL
being passed or assigned to it.
A collation sequence can be specified for string-type parameters, using the COLLATE
clause.
- Input Parameters
-
Input parameters are presented as a parenthesized list following the name of the procedure. They are passed into the procedure as values, so anything that changes them inside the procedure has no effect on the parameters in the calling program. Input parameters may have default values. Those that do have values specified for them must be located at the end of the list of parameters.
- Output Parameters
-
The optional
RETURNS
clause is for specifying a parenthesised list of output parameters for the stored procedure.
Use of Domains in Declarations
A domain name can be specified as the type of a parameter. The parameter will inherit all domain attributes. If a default value is specified for the parameter, it overrides the default value specified in the domain definition.
If the TYPE OF
clause is added before the domain name, only the data type of the domain is used: any of the other attributes of the domain — NOT NULL
constraint, CHECK
constraints, default value — are neither checked nor used.
However, if the domain is of a text type, its character set and collation sequence are always used.
Use of Column Type in Declarations
Input and output parameters can also be declared using the data type of columns in existing tables and views.
The TYPE OF COLUMN
clause is used for that, specifying relationname.columnname as its argument.
When TYPE OF COLUMN
is used, the parameter inherits only the data type and — for string types — the character set and collation sequence.
The constraints and default value of the column are ignored.
Bug warning for pre-Firebird 3 versions:
For input parameters, the collation that comes with the column’s type is ignored in comparisons (e.g. equality tests). For local variables, the behaviour varies. The bug was fixed for Firebird 3. |
Variable and Cursor Declarations
The optional declarations section, located last in the header section of the procedure definition, defines variables local to the procedure and its named cursors.
Local variable declarations follow the same rules as parameters regarding specification of the data type.
See details in the PSQL chapter for DECLARE VARIABLE
and DECLARE CURSOR
.
Procedure Body
The header section is followed by the procedure body, consisting of one or more PSQL statements enclosed between the outer keywords BEGIN
and END
.
Multiple BEGIN … END
blocks of terminated statements may be embedded inside the procedure body.
Any user connected to the database can create a new stored procedure. The user who creates a stored procedure becomes its owner.
Examples
Creating a stored procedure that inserts a record into the BREED
table and returns the code of the inserted record:
CREATE PROCEDURE ADD_BREED (
NAME D_BREEDNAME, /* Domain attributes are inherited */
NAME_EN TYPE OF D_BREEDNAME, /* Only the domain type is inherited */
SHORTNAME TYPE OF COLUMN BREED.SHORTNAME,
/* The table column type is inherited */
REMARK VARCHAR(120) CHARACTER SET WIN1251 COLLATE PXW_CYRL,
CODE_ANIMAL INT NOT NULL DEFAULT 1
)
RETURNS (
CODE_BREED INT
)
AS
BEGIN
INSERT INTO BREED (
CODE_ANIMAL, NAME, NAME_EN, SHORTNAME, REMARK)
VALUES (
:CODE_ANIMAL, :NAME, :NAME_EN, :SHORTNAME, :REMARK)
RETURNING CODE_BREED INTO CODE_BREED;
END
Creating a selectable stored procedure that generates data for mailing labels (from employee.fdb
):
CREATE PROCEDURE mail_label (cust_no INTEGER)
RETURNS (line1 CHAR(40), line2 CHAR(40), line3 CHAR(40),
line4 CHAR(40), line5 CHAR(40), line6 CHAR(40))
AS
DECLARE VARIABLE customer VARCHAR(25);
DECLARE VARIABLE first_name VARCHAR(15);
DECLARE VARIABLE last_name VARCHAR(20);
DECLARE VARIABLE addr1 VARCHAR(30);
DECLARE VARIABLE addr2 VARCHAR(30);
DECLARE VARIABLE city VARCHAR(25);
DECLARE VARIABLE state VARCHAR(15);
DECLARE VARIABLE country VARCHAR(15);
DECLARE VARIABLE postcode VARCHAR(12);
DECLARE VARIABLE cnt INTEGER;
BEGIN
line1 = '';
line2 = '';
line3 = '';
line4 = '';
line5 = '';
line6 = '';
SELECT customer, contact_first, contact_last, address_line1,
address_line2, city, state_province, country, postal_code
FROM CUSTOMER
WHERE cust_no = :cust_no
INTO :customer, :first_name, :last_name, :addr1, :addr2,
:city, :state, :country, :postcode;
IF (customer IS NOT NULL) THEN
line1 = customer;
IF (first_name IS NOT NULL) THEN
line2 = first_name || ' ' || last_name;
ELSE
line2 = last_name;
IF (addr1 IS NOT NULL) THEN
line3 = addr1;
IF (addr2 IS NOT NULL) THEN
line4 = addr2;
IF (country = 'USA') THEN
BEGIN
IF (city IS NOT NULL) THEN
line5 = city || ', ' || state || ' ' || postcode;
ELSE
line5 = state || ' ' || postcode;
END
ELSE
BEGIN
IF (city IS NOT NULL) THEN
line5 = city || ', ' || state;
ELSE
line5 = state;
line6 = country || ' ' || postcode;
END
SUSPEND; -- the statement that sends an output row to the buffer
-- and makes the procedure "selectable"
END
5.8.2. ALTER PROCEDURE
Modifying an existing stored procedure
DSQL, ESQL
ALTER PROCEDURE procname [(<inparam> [, <inparam> ...])] [RETURNS (<outparam> [, <outparam> ...])] AS [<declarations>] BEGIN [<PSQL_statements>] END <inparam> ::= <param_decl> [{= | DEFAULT} <value>] <outparam> ::= <param_decl> <param_decl> ::= paramname <type> [NOT NULL] [COLLATE collation] <type> ::= <datatype> | [TYPE OF] domain | TYPE OF COLUMN rel.col <datatype> ::= {SMALLINT | INT[EGER] | BIGINT} | {FLOAT | DOUBLE PRECISSION} | {DATE | TIME | TIMESTAMP} | {DECIMAL | NUMERIC} [(precision [, scale])] | {CHAR | CHARACTER} [VARYING] | VARCHAR} [(size)] [CHARACTER SET charset] | {NCHAR | NATIONAL {CHARACTER | CHAR} [VARYING] [(size)] | BLOB [SUB_TYPE {subtype_num | subtype_name}] [SEGMENT SIZE seglen] [CHARACTER SET charset] | BLOB [(seglen [, subtype_num])] <declarations> ::= {<declare_var> | <declare_cursor>}; [{<declare_var> | <declare_cursor>}; …]
Parameter | Description |
---|---|
procname |
Name of an existing stored procedure |
inparam |
Input parameter description |
outparam |
Output parameter description |
declarations |
Section for declaring local variables and named cursors |
declare_var |
Local variable declaration |
declare_cursor |
Named cursor declaration |
PSQL_statements |
Procedural SQL statements |
literal |
A literal value that is assignment-compatible with the data type of the parameter |
context_var |
Any context variable whose type is compatible with the data type of the parameter |
paramname |
The name of an input or output parameter of the procedure. It may consist of up to 31 characters. The name of the parameter must be unique among input and output parameters of the procedure and its local variables |
datatype |
SQL data type |
collation |
Collation sequence |
domain |
Domain name |
rel |
Table or view name |
col |
Table or view column name |
precision |
The total number of significant digits that the parameter should be able to hold (1..18) |
scale |
The number of digits after the decimal point (0..precision) |
size |
The maximum size of a string type parameter or variable, in characters |
charset |
Character set of a string type parameter or variable |
subtype_num |
|
subtype_name |
|
seglen |
Segment size (max. 65535) |
The ALTER PROCEDURE
statement allows the following changes to a stored procedure definition:
-
the set and characteristics of input and output parameters
-
local variables
-
code in the body of the stored procedure
After ALTER PROCEDURE
executes, existing privileges remain intact and dependencies are not affected.
Take care about changing the number and type of input and output parameters in stored procedures.
Existing application code and procedures and triggers that call it could become invalid because the new description of the parameters is incompatible with the old calling format.
For information on how to troubleshoot such a situation, see the article The |
The procedure owner and Administrators have the authority to use ALTER PROCEDURE
.
Altering the GET_EMP_PROJ
stored procedure.
ALTER PROCEDURE GET_EMP_PROJ (
EMP_NO SMALLINT)
RETURNS (
PROJ_ID VARCHAR(20))
AS
BEGIN
FOR SELECT
PROJ_ID
FROM
EMPLOYEE_PROJECT
WHERE
EMP_NO = :emp_no
INTO :proj_id
DO
SUSPEND;
END
5.8.3. CREATE OR ALTER PROCEDURE
Creating a new stored procedure or altering an existing one
DSQL
CREATE OR ALTER PROCEDURE procname [(<inparam> [, <inparam> ...])] [RETURNS (<outparam> [, <outparam> ...])] AS [<declarations>] BEGIN [<PSQL_statements>] END
For the full syntax detail, see CREATE PROCEDURE
.
The CREATE OR ALTER PROCEDURE
statement creates a new stored procedure or alters an existing one.
If the stored procedure does not exist, it will be created by invoking a CREATE PROCEDURE
statement transparently.
If the procedure already exists, it will be altered and compiled without affecting its existing privileges and dependencies.
Creating or altering the GET_EMP_PROJ
procedure.
CREATE OR ALTER PROCEDURE GET_EMP_PROJ (
EMP_NO SMALLINT)
RETURNS (
PROJ_ID VARCHAR(20))
AS
BEGIN
FOR SELECT
PROJ_ID
FROM
EMPLOYEE_PROJECT
WHERE
EMP_NO = :emp_no
INTO :proj_id
DO
SUSPEND;
END
5.8.4. DROP PROCEDURE
Deleting a stored procedure
DSQL, ESQL
DROP PROCEDURE procname
Parameter | Description |
---|---|
procname |
Name of an existing stored procedure |
The DROP PROCEDURE
statement deletes an existing stored procedure.
If the stored procedure has any dependencies, the attempt to delete it will fail and the appropriate error will be raised.
The procedure owner and Administrators have the authority to use DROP PROCEDURE
.
Deleting the GET_EMP_PROJ
stored procedure.
DROP PROCEDURE GET_EMP_PROJ;
5.8.5. RECREATE PROCEDURE
Creating a new stored procedure or recreating an existing one
DSQL
RECREATE PROCEDURE procname [(<inparam> [, <inparam> ...])] [RETURNS (<outparam> [, <outparam> ...])] AS [<declarations>] BEGIN [<PSQL_statements>] END
For the full syntax detail, see CREATE PROCEDURE
.
The RECREATE PROCEDURE
statement creates a new stored procedure or recreates an existing one.
If there is a procedure with this name already, the engine will try to delete it and create a new one.
Recreating an existing procedure will fail at the COMMIT
request if the procedure has dependencies.
Be aware that dependency errors are not detected until the |
After a procedure is successfully recreated, privileges to execute the stored procedure and the privileges of the stored procedure itself are dropped.
Creating the new GET_EMP_PROJ
stored procedure or recreating the existing GET_EMP_PROJ
stored procedure.
RECREATE PROCEDURE GET_EMP_PROJ (
EMP_NO SMALLINT)
RETURNS (
PROJ_ID VARCHAR(20))
AS
BEGIN
FOR SELECT
PROJ_ID
FROM
EMPLOYEE_PROJECT
WHERE
EMP_NO = :emp_no
INTO :proj_id
DO
SUSPEND;
END
5.9. EXTERNAL FUNCTION
REVIEW STATUS
All sections from this point forward to the end of the chapter are awaiting technical and editorial review. |
External functions, also known as “user-defined functions” (UDFs) are programs written in an external programming language and stored in dynamically loaded libraries. Once declared to a database, they become available in dynamic and procedural statements as though they were implemented in the SQL language internally.
External functions extend the possibilities for processing data with SQL considerably.
To make a function available to a database, it is declared using the statement DECLARE EXTERNAL FUNCTON
.
The library containing a function is loaded when any function included in it is called.
External functions may be contained in more than one library — or “module”, as it is referred to in the syntax. |
5.9.1. DECLARE EXTERNAL FUNCTION
Declaring a user-defined function (UDF) to the database
DSQL, ESQL
DECLARE EXTERNAL FUNCTION funcname [<arg_type_decl> [, <arg_type_decl> ...]] RETURNS { <sqltype> [BY {DESCRIPTOR | VALUE}] | CSTRING(length) | PARAMETER param_num } [FREE_IT] ENTRY_POINT 'entry_point' MODULE_NAME 'library_name' <arg_type_decl> ::= <sqltype> [{BY DESCRIPTOR} | NULL] | CSTRING(length) [NULL]
Parameter | Description |
---|---|
funcname |
Function name in the database.
It may consist of up to 31 characters.
It should be unique among all internal and external function names in the database and need not be the same name as the name exported from the UDF library via |
entry_point |
The exported name of the function |
library_name |
The name of the module ( |
sqltype |
SQL data type. It cannot be an array or an array element |
length |
The maximum length of a null-terminated string, specified in bytes |
param_num |
The number of the input parameter, numbered from 1 in the list of input parameters in the declaration, describing the data type that will be returned by the function |
The DECLARE EXTERNAL FUNCTION
statement makes a user-defined function available in the database.
UDF declarations must be made in each database that is going to use them.
There is no need to declare UDFs that will never be used.
The name of the external function must be unique among all function names.
It may be different from the exported name of the function, as specified in the ENTRY_POINT
argument.
DECLARE EXTERNAL FUNCTION
Input Parameters
The input parameters of the function follow the name of the function and are separated with commas.
Each parameter has an SQL data type specified for it.
Arrays cannot be used as function parameters.
As well as the SQL types, the CSTRING
type is available for specifying a null-terminated string with a maximum length of LENGTH
bytes.
By default, input parameters are passed by reference.
The BY DESCRIPTOR
clause may be specified instead, if the input parameter is passed by descriptor.
Passing a parameter by descriptor makes it possible to process NULLs
.
Clauses and Keywords
RETURNS
clause-
(Required) specifies the output parameter returned by the function. A function is scalar: it returns one and only one parameter. The output parameter can be of any SQL type (except an array or an array element) or a null-terminated string (
CSTRING
). The output parameter can be passed by reference (the default), by descriptor or by value. If theBY DESCRIPTOR
clause is specified, the output parameter is passed by descriptor. If theBY VALUE
clause is specified, the output parameter is passed by value. PARAMETER
keyword-
specifies that the function returns the value from the parameter under number param_num. It is necessary if you need to return a value of data type
BLOB
. FREE_IT
keyword-
means that the memory allocated for storing the return value will be freed after the function is executed. It is used only if the memory was allocated dynamically in the UDF. In such a UDF, the memory must be allocated with the help of the
ib_util_malloc
function from theib_util
module, a requirement for compatibility with the functions used in Firebird code and in the code of the shipped UDF modules, for allocating and freeing memory. ENTRY_POINT
clause-
specifies the name of the entry point (the name of the imported function), as exported from the module.
MODULE_NAME
clause-
defines the name of the module where the exported function is located. The link to the module should not be the full path and extension of the file, if that can be avoided. If the module is located in the default location (in the
../UDF
subdirectory of the Firebird server root) or in a location explicitly configured infirebird.conf
, it makes it easier to move the database between different platforms. TheUDFAccess
parameter in the firebird.conf file allows access restrictions to external functions modules to be configured.
Any user connected to the database can declare an external function (UDF).
Examples using DECLARE EXTERNAL FUNCTION
-
Declaring the
addDay
external function located in thefbudf
module. The input and output parameters are passed by reference.DECLARE EXTERNAL FUNCTION addDay TIMESTAMP, INT RETURNS TIMESTAMP ENTRY_POINT 'addDay' MODULE_NAME 'fbudf';
-
Declaring the
invl
external function located in thefbudf
module. The input and output parameters are passed by descriptor.DECLARE EXTERNAL FUNCTION invl INT BY DESCRIPTOR, INT BY DESCRIPTOR RETURNS INT BY DESCRIPTOR ENTRY_POINT 'idNvl' MODULE_NAME 'fbudf';
-
Declaring the
isLeapYear
external function located in thefbudf
module. The input parameter is passed by reference, while the output parameter is passed by value.DECLARE EXTERNAL FUNCTION isLeapYear TIMESTAMP RETURNS INT BY VALUE ENTRY_POINT 'isLeapYear' MODULE_NAME 'fbudf';
-
Declaring the
i64Truncate
external function located in thefbudf
module. The input and output parameters are passed by descriptor. The second parameter of the function is used as the return value.DECLARE EXTERNAL FUNCTION i64Truncate NUMERIC(18) BY DESCRIPTOR, NUMERIC(18) BY DESCRIPTOR RETURNS PARAMETER 2 ENTRY_POINT 'fbtruncate' MODULE_NAME 'fbudf';
5.9.2. ALTER EXTERNAL FUNCTION
Changing the entry point and/or the module name for a user-defined function (UDF)
DSQL
ALTER EXTERNAL FUNCTION funcname [ENTRY_POINT 'new_entry_point'] [MODULE_NAME 'new_library_name']
Parameter | Description |
---|---|
funcname |
Function name in the database |
new_entry_point |
The new exported name of the function |
new_library_name |
The new name of the module ( |
The ALTER EXTERNAL FUNCTION
statement changes the entry point and/or the module name for a user-defined function (UDF).
Existing dependencies remain intact after the statement containing the change[s] is executed.
- The
ENTRY_POINT
clause -
is for specifying the new entry point (the name of the function as exported from the module).
- The
MODULE_NAME
clause -
is for specifying the new name of the module where the exported function is located.
Any user connected to the database can change the entry point and the module name.
ALTER EXTERNAL FUNCTION
-
Changing the entry point for an external function
ALTER EXTERNAL FUNCTION invl ENTRY_POINT 'intNvl';
-
Changing the module name for an external function
ALTER EXTERNAL FUNCTION invl MODULE_NAME 'fbudf2';
5.9.3. DROP EXTERNAL FUNCTION
Removing a user-defined function (UDF) from a database
DSQL, ESQL
DROP EXTERNAL FUNCTION funcname
Parameter | Description |
---|---|
funcname |
Function name in the database |
The DROP EXTERNAL FUNCTION
statement deletes the declaration of a user-defined function from the database.
If there are any dependencies on the external function, the statement will fail and the appropriate error will be raised.
Any user connected to the database can delete the declaration of an internal function.
DROP EXTERNAL FUNCTION
Deleting the declaration of the addDay
function.
DROP EXTERNAL FUNCTION addDay;
5.10. FILTER
A BLOB FILTER
filter is a database object that is actually a special type of external function, with the sole purpose of taking a BLOB
object in one format and converting it to a BLOB
object in another format.
The formats of the BLOB
objects are specifed with user-defined BLOB
subtypes.
External functions for converting BLOB
types are stored in dynamic libraries and loaded when necessary.
For more details on BLOB
subtypes, see Binary Data Types.
5.10.1. DECLARE FILTER
Declaring a BLOB
filter to the database
DSQL, ESQL
DECLARE FILTER filtername INPUT_TYPE <sub_type> OUTPUT_TYPE <sub_type> ENTRY_POINT 'function_name' MODULE_NAME 'library_name' <sub_type> ::= number | <mnemonic> <mnemonic> ::= BINARY | TEXT | BLR | ACL | RANGES | SUMMARY | FORMAT | TRANSACTION_DESCRIPTION | EXTERNAL_FILE_DESCRIPTION | user_defined
Parameter | Description |
---|---|
filtername |
Filter name in the database.
It may consist of up to 31 characters.
It need not be the same name as the name exported from the filter library via |
sub_type |
|
number |
|
mnemonic |
|
function_name |
The exported name (entry point) of the function |
library_name |
The name of the module where the filter is located |
user_defined |
User-defined |
The DECLARE FILTER
statement makes a BLOB
filter available to the database.
The name of the BLOB
filter must be unique among the names of BLOB
filters.
Specifying the Subtypes
The subtypes can be specified as the subtype number or as the subtype mnemonic name.
Custom subtypes must be represented by negative numbers (from -1 to -32,768).
An attempt to declare more than one BLOB
filter with the same combination of the input and output types will fail with an error.
INPUT_TYPE
-
clause defining the
BLOB
subtype of the object to be converted OUTPUT_TYPE
-
clause defining the
BLOB
subtype of the object to be created.
Mnemonic names can be defined for custom
After the transaction is committed, the mnemonic names can be used in declarations when you create new filters. The value of the column Warning
From Firebird 3 onward, the system tables will no longer be writable by users.
However, inserting custom types into |
Parameters
ENTRY_POINT
-
clause defining the name of the entry point (the name of the imported function) in the module.
MODULE_NAME
-
The clause defining the name of the module where the exported function is located. By default, modules must be located in the UDF folder of the root directory on the server. The
UDFAccess
parameter infirebird.conf
allows editing of access restrictions to filter libraries.
Any user connected to the database can declare a BLOB filter.
Examples of FILTER
-
Creating a
BLOB
filter using subtype numbers.DECLARE FILTER DESC_FILTER INPUT_TYPE 1 OUTPUT_TYPE -4 ENTRY_POINT 'desc_filter' MODULE_NAME 'FILTERLIB';
-
Creating a
BLOB
filter using subtype mnemonic names.DECLARE FILTER FUNNEL INPUT_TYPE blr OUTPUT_TYPE text ENTRY_POINT 'blr2asc' MODULE_NAME 'myfilterlib';
5.10.2. DROP FILTER
Removing a BLOB
filter declaration from the database
DSQL, ESQL
DROP FILTER filtername
Parameter | Description |
---|---|
filtername |
Filter name in the database |
The DROP FILTER
statement removes the declaration of a BLOB
filter from the database.
Removing a BLOB
filter from a database makes it unavailable for use from that database.
The dynamic library where the conversion function is located remains intact and the removal from one database does not affect other databases in which the same BLOB
filter is still declared.
Any user connected to the database can drop a BLOB filter.
Deleting a BLOB
filter.
DROP FILTER DESC_FILTER;
5.11. SEQUENCE
(GENERATOR
)
A sequence or a generator is a database object used to get unique number values to fill a series. “Sequence” is the SQL-compliant term for the same thing which, in Firebird, has traditionally been known as “generator”. Both terms are implemented in Firebird, which recognises and has syntax for both terms.
Sequences (or generators) are always stored as 64-bit integers, regardless of the SQL dialect of the database.
If a client is connected using Dialect 1, the server sends sequence values to it as 32-bit integers. Passing a sequence value to a 32-bit field or variable will not cause errors as long as the current value of the sequence does not exceed the limits of a 32-bit number. However, as soon as the sequence value exceeds this limit, a database in Dialect 3 will produce an error. A database in Dialect 1 will keep cutting the values, which will compromise the uniqueness of the series. |
This section describes how to create, set and delete sequences.
5.11.1. CREATE SEQUENCE (GENERATOR)
Creating a new SEQUENCE
(GENERATOR
)
DSQL, ESQL
CREATE {SEQUENCE | GENERATOR} seq_name
Parameter | Description |
---|---|
seq_name |
Sequence (generator) name. It may consist of up to 31 characters |
The statements CREATE SEQUENCE
and CREATE GENERATOR
are synonymous — both create a new sequence.
Either can be used but CREATE SEQUENCE
is recommended if standards-compliant metadata management is important.
When a sequence is created, its value is set to 0.
Each time the NEXT VALUE FOR seq_name
operator is used with that sequence, its value increases by 1.
The GEN_ID(seq_name, <step>)
function can be called instead, to “step” the series by a different integer number.
Any user connected to the database can create a sequence (generator).
-
Creating the
EMP_NO_GEN
series usingCREATE SEQUENCE
.CREATE SEQUENCE EMP_NO_GEN;
-
Creating the
EMP_NO_GEN
series usingCREATE GENERATOR
.CREATE GENERATOR EMP_NO_GEN;
5.11.2. ALTER SEQUENCE
Setting the value of a sequence or generator to a specified value
DSQL
ALTER SEQUENCE seq_name RESTART WITH new_val
Parameter | Description |
---|---|
seq_name |
Sequence (generator) name |
new_val |
New sequence (generator) value. A 64-bit integer from -2-63 to 263-1. |
The ALTER SEQUENCE
statement sets the current value of a sequence or generator to the specified value.
Incorrect use of the |
Any user connected to the database can set the sequence (generator) value.
-
Setting the value of the
EMP_NO_GEN
sequence to 145.ALTER SEQUENCE EMP_NO_GEN RESTART WITH 145;
-
Doing the same thing, using
SET GENERATOR
:SET GENERATOR EMP_NO_GEN TO 145;
5.11.3. SET GENERATOR
Setting the value of a sequence or generator to a specified value
DSQL, ESQL
SET GENERATOR seq_name TO new_val
Parameter | Description |
---|---|
seq_name |
Generator (sequence) name |
new_val |
New sequence (generator) value. A 64-bit integer from -2-63 to 263-1. |
The SET GENERATOR
statement sets the current value of a sequence or generator to the specified value.
Although |
Any user connected to the database can set the sequence (generator) value.
-
Setting the value of the
EMP_NO_GEN
sequence to 145:SET GENERATOR EMP_NO_GEN TO 145;
-
Doing the same thing, using
ALTER SEQUENCE
:ALTER SEQUENCE EMP_NO_GEN RESTART WITH 145;
5.11.4. DROP SEQUENCE (GENERATOR)
Deleting SEQUENCE
(GENERATOR
)
DSQL, ESQL
DROP {SEQUENCE | GENERATOR} seq_name
Parameter | Description |
---|---|
seq_name |
Sequence (generator) name. It may consist of up to 31 characters |
The statements DROP SEQUENCE
and DROP GENERATOR
statements are equivalent: both delete an existing sequence (generator).
Either is valid but DROP SEQUENCE
, being current, is recommended.
The statements will fail if the sequence (generator) has dependencies.
Any user connected to the database can drop a sequence (generator).
Dropping the EMP_NO_GEN
series:
DROP SEQUENCE EMP_NO_GEN;
5.12. EXCEPTION
This section describes how to create, modify and delete custom exceptions for use in error handlers in PSQL modules.
5.12.1. CREATE EXCEPTION
Creating a new exception for use in PSQL modules
DSQL, ESQL
CREATE EXCEPTION exception_name 'message'
Parameter | Description |
---|---|
exception_name |
Exception name. The maximum length is 31 characters |
message |
Default error message. The maximum length is 1,021 characters |
The statement CREATE EXCEPTION
creates a new exception for use in PSQL modules.
If an exception of the same name exists, the statement will fail with an appropriate error message.
The exception name is a standard identifier. In a Dialect 3 database, it can be enclosed in double quotes to make it case-sensitive and, if required, to use characters that are not valid in regular identifiers. See Identifiers for more information.
The default message is stored in character set NONE
, i.e., in characters of any single-byte character set.
The text can be overridden in the PSQL code when the exception is thrown.
Any user connected to the database can create an exception.
-
Creating an exception named
E_LARGE_VALUE
:CREATE EXCEPTION E_LARGE_VALUE 'The value is out of range';
-
Creating an exception named
ERROR_REFIN_RATE
:CREATE EXCEPTION ERROR_REFIN_RATE 'Error detected in the spread of discount rates';
Tips
Grouping Custom exceptions are stored in the system table |
5.12.2. ALTER EXCEPTION
Modifying the message returned from a custom exception
DSQL, ESQL
ALTER EXCEPTION exception_name 'message'
Parameter | Description |
---|---|
exception_name |
Exception name |
message |
New default error message. The maximum length is 1,021 characters |
The statement ALTER EXCEPTION
can be used at any time, to modify the default text of the message.
Any user connected to the database can alter an exception message.
-
Changing the default message for the exception
E_LARGE_VALUE
:ALTER EXCEPTION E_LARGE_VALUE 'The value exceeds the prescribed limit of 32,765 bytes';
-
Changing the default message for the exception
ERROR_REFIN_RATE
:ALTER EXCEPTION ERROR_REFIN_RATE 'Rate is outside the allowed range';
5.12.3. CREATE OR ALTER EXCEPTION
Modifying the message returned from a custom exception, if the exception exists; otherwise, creating a new exception
DSQL
CREATE OR ALTER EXCEPTION exception_name 'message'
Parameter | Description |
---|---|
exception_name |
Exception name |
message |
Error message. The maximum length is limited to 1,021 characters |
The statement CREATE OR ALTER EXCEPTION
is used to create the specified exception if it does not exist, or to modify the text of the error message returned from it if it exists already.
If an existing exception is altered by this statement, any existing dependencies will remain intact.
Any user connected to the database can use this statement to create an exception or alter the text of one that already exists.
Changing the message for the exception E_LARGE_VALUE
:
CREATE OR ALTER EXCEPTION E_LARGE_VALUE
'The value is higher than the permitted range 0 to 32,765';
5.12.4. DROP EXCEPTION
Deleting a custom exception
DSQL, ESQL
DROP EXCEPTION exception_name
Parameter | Description |
---|---|
exception_name |
Exception name |
The statement DROP EXCEPTION
is used to delete an exception.
Any dependencies on the exception will cause the statement to fail and the exception will not be deleted.
If an exception is used only in stored procedures, it can be deleted at any time. If it is used in a trigger, it cannot be deleted.
In planning to delete an exception, all references to it should first be removed from the code of stored procedures, to avoid its absence causing errors.
Any user connected to the database can delete an exception.
-
Deleting exception
ERROR_REFIN_RATE
:DROP EXCEPTION ERROR_REFIN_RATE;
-
Deleting exception
E_LARGE_VALUE
:DROP EXCEPTION E_LARGE_VALUE;
5.12.5. RECREATE EXCEPTION
Creating a new custom exception or recreating an existing one
DSQL
RECREATE EXCEPTION exception_name 'message'
Parameter | Description |
---|---|
exception_name |
Exception name. The maximum length is 31 characters |
message |
Error message. The maximum length is limited to 1,021 characters |
The statement RECREATE EXCEPTION
creates a new exception for use in PSQL modules.
If an exception of the same name exists already, the RECREATE EXCEPTION
statement will try to delete it and create a new one.
If there are any dependencies on the existing exception, the attempted deletion fails and RECREATE EXCEPTION
is not executed.
Any user connected to the database can [re]create an exception.
Recreating the E_LARGE_VALUE
exception:
RECREATE EXCEPTION E_LARGE_VALUE
'The value exceeds its limit';
5.13. COLLATION
5.13.1. CREATE COLLATION
Making a new collation for a supported character set available to the database
DSQL
CREATE COLLATION collname FOR charset [FROM basecoll | FROM EXTERNAL ('extname')] [NO PAD | PAD SPACE] [CASE [IN]SENSITIVE] [ACCENT [IN]SENSITIVE] ['<specific-attributes>'] <specific-attributes> ::= <attribute> [; <attribute> ...] <attribute> ::= attrname=attrvalue
Parameter | Description |
---|---|
collname |
The name to use for the new collation. The maximum length is 31 characters |
charset |
A character set present in the database |
basecoll |
A collation already present in the database |
extname |
The collation name used in the |
The CREATE COLLATION
statement does not “create” anything: its purpose is to make a collation known to a database.
The collation must already be present on the system, typically in a library file, and must be properly registered in a .conf
file in the intl
subdirectory of the Firebird installation.
The collation may alternatively be based on one that is already present in the database.
How the Engine Detects the Collation
If no FROM
clause is present, Firebird will scan the .conf
file(s) in the intl
subdirectory for a collation with the name specified as the object of CREATE COLLATION
.
In other words, omitting the FROM basecoll
clause is equivalent to specifying FROM EXTERNAL ('collname')
.
The — single-quoted — extname is case-sensitive and must correspond exactly with the collation name in the .conf
file.
The collname, charset and basecoll parameters are case-insensitive unless enclosed in double-quotes.
Specific Attributes
The available specific attributes are listed in the table below. Not all specific attributes apply to every collation, even if specifying them does not cause an error.
Specific attributes are case sensitive. |
In the table, “1 bpc” indicates that an attribute is valid for collations of character sets using 1 byte per character (so-called narrow character sets). “UNI” stands for “UNICODE collations”.
Atrribute | Values | Valid for | Comment |
---|---|---|---|
|
|
1 bpc |
Disables compressions (a.k.a. contractions). Compressions cause certain character sequences to be sorted as atomic units, e.g. Spanish c+h as a single character ch |
|
|
1 bpc |
Disables expansions. Expansions cause certain characters (e.g. ligatures or umlauted vowels) to be treated as character sequences and sorted accordingly |
|
default or M.m |
UNI |
Specifies the ICU library version to use.
Valid values are the ones defined in the applicable <intl_module> element in |
|
xx_YY |
UNI |
Specifies the collation locale. Requires complete version of ICU libraries. Format: a locale string like “du_NL” (unquoted) |
|
|
1 bpc |
Uses more than one ordering level |
|
|
UNI |
Treats contiguous groups of decimal digits in the string as atomic units and sorts them numerically. (This is also known as natural sorting) |
|
|
1 bpc |
Orders special characters (spaces, symbols etc.) before alphanumeric characters |
If you want to add a new character set with its default collation into your database, declare and run the stored procedure In order for this to work, the character set must be present on the system and registered in a |
Any user connected to the database can use CREATE COLLATION
to add a new collation.
Examples using CREATE COLLATION
-
Creating a collation using the name found in the
fbintl.conf
file (case-sensitive).CREATE COLLATION ISO8859_1_UNICODE FOR ISO8859_1;
-
Creating a collation using a special (user-defined) name (the “external” name must completely match the name in the
fbintl.conf
file).CREATE COLLATION LAT_UNI FOR ISO8859_1 FROM EXTERNAL ('ISO8859_1_UNICODE');
-
Creating a case-insensitive collation based on one already existing in the database.
CREATE COLLATION ES_ES_NOPAD_CI FOR ISO8859_1 FROM ES_ES NO PAD CASE INSENSITIVE;
-
Creating a case-insensitive collation based on one already existing in the database with specific attributes.
CREATE COLLATION ES_ES_CI_COMPR FOR ISO8859_1 FROM ES_ES CASE INSENSITIVE 'DISABLE-COMPRESSIONS=0';
-
Creating a case-insensitive collation by the value of numbers (the so-called natural collation).
CREATE COLLATION nums_coll FOR UTF8 FROM UNICODE CASE INSENSITIVE 'NUMERIC-SORT=1'; CREATE DOMAIN dm_nums AS varchar(20) CHARACTER SET UTF8 COLLATE nums_coll; -- original (manufacturer) numbers CREATE TABLE wares(id int primary key, articul dm_nums ...);
5.13.2. DROP COLLATION
Removing a collation from the database
DSQL
DROP COLLATION collname
Parameter | Description |
---|---|
collname |
The name of the collation |
The DROP COLLATION
statement removes the specified collation from the database, if it exists.
An error will be raised if the specified collation is not present.
If you want to remove an entire character set with all its collations from the database, declare and execute the stored procedure |
Any user connected to the database can use DROP COLLATION
to remove a collation.
DROP COLLATION
Deleting the ES_ES_NOPAD_CI
collation.
DROP COLLATION ES_ES_NOPAD_CI;
5.14. CHARACTER SET
5.14.1. ALTER CHARACTER SET
Setting the default collation for a character set
DSQL
ALTER CHARACTER SET charset SET DEFAULT COLLATION collation
Parameter | Description |
---|---|
charset |
Character set identifier |
collation |
The name of the collation |
The statement ALTER CHARACTER SET
statement changes the default collation for the specified character set.
It will affect the future usage of the character set, except for cases where the COLLATE
clause is explicitly overridden.
In that case, the collation sequence of existing domains, columns and PSQL variables will remain intact after the change to the default collation of the underlying character set.
If you change the default collation for the database character set (the one defined when the database was created), it will change the default collation for the database. If you change the default collation for the character set that was specified during the connection, string constants will be interpreted according to the new collation value, except in those cases where the character set and/or the collation have been overridden. |
Setting the default UNICODE_CI_AI
collation for the UTF8
encoding.
ALTER CHARACTER SET UTF8
SET DEFAULT COLLATION UNICODE_CI_AI;
5.15. ROLE
A role is a database object that packages a set of SQL privileges. Roles implement the concept of access control at a group level. Multiple privileges are granted to the role and then that role can be granted to or revoked from one or many users.
A user that is granted a role must supply that role in his login credentials in order to exercise the associated privileges. Any other privileges granted to the user are not affected by his login with the role. Logging in with multiple roles simultaneously is not supported.
In this section the tasks of creating and dropping roles are discussed.
5.15.1. CREATE ROLE
Creating a new ROLE
object
DSQL, ESQL
CREATE ROLE rolename
Parameter | Description |
---|---|
rolename |
Role name. The maximum length is 31 characters |
The statement CREATE ROLE
creates a new role object, to which one or more privileges can be granted subsequently.
The name of a role must be unique among the names of roles in the current database.
It is advisable to make the name of a role unique among user names as well. The system will not prevent the creation of a role whose name clashes with an existing user name but, if it happens, the user will be unable to connect to the database. |
Any user connected to the database can create a role. The user that creates a role becomes its owner.
Creating a role named SELLERS
:
CREATE ROLE SELLERS;
5.15.2. ALTER ROLE
ALTER ROLE
has no place in the create-alter-drop paradigm for database objects since a role has no attributes that can be modified.
Its actual effect is to alter an attribute of the database: Firebird uses it to enable and disable the capability for Windows Adminstrators to assume administrator privileges automatically when logging in.
This procedure can affect only one role: the system-generated role RDB$ADMIN
that exists in every database of ODS 11.2 or higher.
Several factors are involved in enabling this feature.
For details, see AUTO ADMIN MAPPING in the Security chapter.
5.15.3. DROP ROLE
Deleting a role
DSQL, ESQL
DROP ROLE rolename
The statement DROP ROLE
deletes an existing role.
It takes just a single argument, the name of the role.
Once the role is deleted, the entire set of privileges is revoked from all users and objects that were granted the role.
A role can be deleted by its owner or by an administrator.
Deleting the role SELLERS:
DROP ROLE SELLERS;
5.16. COMMENTS
Database objects and a database itself may contain comments.
It is a convenient mechanism for documenting the development and maintenance of a database.
Comments created with COMMENT ON
will survive a gbak backup and restore.
5.16.1. COMMENT ON
Documenting metadata
DSQL
COMMENT ON <object> IS {'sometext' | NULL} <object> ::= DATABASE | <basic-type> objectname | COLUMN relationname.fieldname | PARAMETER procname.paramname <basic-type> ::= CHARACTER SET | COLLATION | DOMAIN | EXCEPTION | EXTERNAL FUNCTION | FILTER | GENERATOR | INDEX | PROCEDURE | ROLE | SEQUENCE | TABLE | TRIGGER | VIEW
Parameter | Description |
---|---|
sometext |
Comment text |
basic-type |
Metadata object type |
objectname |
Metadata object name |
relationname |
Name of table or view |
procname |
Name of stored procedure |
paramname |
Name of a stored procedure parameter |
The COMMENT ON
statement adds comments for database objects (metadata).
Comments are saved to text fields of the BLOB
type in the RDB$DESCRIPTION
column of the corresponding system tables.
Client applications can view comments from these fields.
If you add an empty comment (“ |
The table or procedure owner and Administrators have the authority to use COMMENT ON
.
COMMENT ON
-
Adding a comment for the current database
COMMENT ON DATABASE IS 'It is a test (''my.fdb'') database';
-
Adding a comment for the
METALS
tableCOMMENT ON TABLE METALS IS 'Metal directory';
-
Adding a comment for the
ISALLOY
field in theMETALS
tableCOMMENT ON COLUMN METALS.ISALLOY IS '0 = fine metal, 1 = alloy';
-
Adding a comment for a parameter
COMMENT ON PARAMETER ADD_EMP_PROJ.EMP_NO IS 'Employee ID';
6. Data Manipulation (DML) Statements
REVIEW STATUS
All sections from this point forward to the end of the chapter are awaiting technical and editorial review. |
DML — data manipulation language — is the subset of SQL that is used by applications and procedural modules to extract and change data.
Extraction, for the purpose of reading data, both raw and manipulated, is achieved with the SELECT
statement.
INSERT
is for adding new data and DELETE
is for erasing data that are no longer required.
UPDATE
, MERGE
and UPDATE OR INSERT
all modify data in various ways.
6.1. SELECT
Retrieving data
DSQL, ESQL, PSQL
[WITH [RECURSIVE] <cte> [, <cte> ...]] SELECT [FIRST m] [SKIP n] [DISTINCT | ALL] <columns> FROM <source> [[AS] alias] [<joins>] [WHERE <condition>] [GROUP BY <grouping-list> [HAVING <aggregate-condition>]] [PLAN <plan-expr>] [UNION [DISTINCT | ALL] <other-select>] [ORDER BY <ordering-list>] [ROWS <m> [TO <n>]] [FOR UPDATE [OF <columns>]] [WITH LOCK] [INTO <variables>] <variables> ::= [:]varname [, [:]varname ...]
Description
The SELECT
statement retrieves data from the database and hands them to the application or the enclosing SQL statement.
Data are returned in zero or more rows, each containing one or more columns or fields.
The total of rows returned is the result set of the statement.
The only mandatory parts of the SELECT
statement are:
-
The
SELECT
keyword, followed by a columns list. This part specifies what you want to retrieve. -
The
FROM
keyword, followed by a selectable object. This tells the engine where you want to get it from.
In its most basic form, SELECT
retrieves a number of columns from a single table or view, like this:
select id, name, address
from contacts
Or, to retrieve all the columns:
select * from sales
In practice, the rows retrieved are often limited by a WHERE
clause.
The result set may be sorted by an ORDER BY
clause, and FIRST
, SKIP
or ROWS
may further limit the number of output rows.
The column list may contain all kinds of expressions instead of just column names, and the source need not be a table or view: it may also be a derived table, a common table expression (CTE) or a selectable stored procedure (SP). Multiple sources may be combined in a JOIN
, and multiple result sets may be combined in a UNION
.
The following sections discuss the available SELECT
subclauses and their usage in detail.
6.1.1. FIRST
, SKIP
Retrieving a slice of rows from an ordered set
DSQL, PSQL
SELECT [FIRST <m>] [SKIP <n>] FROM ... ... <m>, <n> ::= <integer-literal> | <query-parameter> | (<integer-expression>)
Argument | Description |
---|---|
integer-literal |
Integer literal |
query-parameter |
Query parameter place-holder.
|
integer-expression |
Expression returning an integer value |
FIRST and SKIP are non-standard syntax
|
Description
FIRST
limits the output of a query to the first m rows. SKIP
will suppress the given n rows before starting to return output.
FIRST
and SKIP
are both optional.
When used together as in “FIRST m SKIP n
”, the n topmost rows of the output set are discarded and the first m rows of the rest of the set are returned.
Characteristics of FIRST
and SKIP
-
Any argument to
FIRST
andSKIP
that is not an integer literal or an SQL parameter must be enclosed in parentheses. This implies that a subquery expression must be enclosed in two pairs of parentheses. -
SKIP 0
is allowed but totally pointless. -
FIRST 0
is also allowed and returns an empty set. -
Negative
SKIP
and/orFIRST
values result in an error. -
If a
SKIP
lands past the end of the dataset, an empty set is returned. -
If the number of rows in the dataset (or the remainder left after a
SKIP
) is less than the value of the m argument supplied forFIRST
, that smaller number of rows is returned. These are valid results, not error conditions.
An error occurs when you use
will delete all records from the table.
The subquery retrieves 10 rows each time, deletes them and the operation is repeated until the table is empty.
Keep it in mind!
Or, better, use the |
Examples of FIRST
/SKIP
The following query will return the first 10 names from the People
table:
select first 10 id, name from People
order by name asc
The following query will return everything but the first 10 names:
select skip 10 id, name from People
order by name asc
And this one returns the last 10 rows. Notice the double parentheses:
select skip ((select count(*) - 10 from People))
id, name from People
order by name asc
This query returns rows 81 to 100 of the People table:
select first 20 skip 80 id, name from People
order by name asc
6.1.2. The SELECT
Columns List
The columns list contains one or more comma-separated value expressions.
Each expression provides a value for one output column.
Alternatively, *
(“select star”) can be used to stand for all the columns in a relation (i.e.
a table, view or selectable stored procedure).
SELECT [...] [DISTINCT | ALL] <output-column> [, <output-column> ...] [...] FROM ... <output-column> ::= [<qualifier>.]* | <value-expression> [COLLATE collation] [[AS] alias] <value-expression> ::= [<qualifier>.]table-column | [<qualifier>.]view-column | [<qualifier>.]selectable-SP-outparm | <literal> | <context-variable> | <function-call> | <single-value-subselect> | <CASE-construct> | any other expression returning a single value of a Firebird data type or NULL <qualifier> ::= a relation name or alias
Argument | Description |
---|---|
qualifier |
Name of relation (view, stored procedure, derived table); or an alias for it |
collation |
Only for character-type columns: a collation name that exists and is valid for the character set of the data |
alias |
Column or field alias |
table-column |
Name of a table column |
view-column |
Name of a view column |
selectable-SP-outparm |
Declared name of an output parameter of a selectable stored procedure |
constant |
A constant |
context-variable |
Context variable |
function-call |
Scalar or aggregate function call expression |
single-value-subselect |
A subquery returning one scalar value (singleton) |
CASE-construct |
CASE construct setting conditions for a return value |
other-single-value-expr |
Any other expression returning a single value of a Firebird data type; or NULL |
Description
It is always valid to qualify a column name (or “*
”) with the name or alias of the table, view or selectable SP to which it belongs, followed by a dot (‘.
’).
For example, relationname.columnname
, relationname.*
, alias.columnname
, alias.*
.
Qualifying is required if the column name occurs in more than one relation taking part in a join.
Qualifying “*
” is always mandatory if it is not the only item in the column list.
Aliases obfuscate the original relation name: once a table, view or procedure has been aliased, only the alias can be used as its qualifier throughout the query. The relation name itself becomes unavailable. |
The column list may optionally be preceded by one of the keywords DISTINCT
or ALL
:
-
DISTINCT
filters out any duplicate rows. That is, if two or more rows have the same values in every corresponding column, only one of them is included in the result set -
ALL
is the default: it returns all of the rows, including duplicates.ALL
is rarely used; it is supported for compliance with the SQL standard.
A COLLATE
clause will not change the appearance of the column as such.
However, if the specified collation changes the case or accent sensitivity of the column, it may influence:
-
The ordering, if an
ORDER BY
clause is also present and it involves that column -
Grouping, if the column is part of a
GROUP BY
clause -
The rows retrieved (and hence the total number of rows in the result set), if
DISTINCT
is used
Examples of SELECT
queries with different types of column lists
A simple SELECT
using only column names:
select cust_id, cust_name, phone
from customers
where city = 'London'
A query featuring a concatenation expression and a function call in the columns list:
select 'Mr./Mrs. ' || lastname, street, zip, upper(city)
from contacts
where date_last_purchase(id) = current_date
A query with two subselects:
select p.fullname,
(select name from classes c where c.id = p.class) as class,
(select name from mentors m where m.id = p.mentor) as mentor
from pupils p
The following query accomplishes the same as the previous one using joins instead of subselects:
select p.fullname,
c.name as class,
m.name as mentor
join classes c on c.id = p.class
from pupils p
join mentors m on m.id = p.mentor
This query uses a CASE
construct to determine the correct title, e.g.
when sending mail to a person:
select case upper(sex)
when 'F' then 'Mrs.'
when 'M' then 'Mr.'
else ''
end as title,
lastname,
address
from employees
Querying a selectable stored procedure:
select * from interesting_transactions(2010, 3, 'S')
order by amount
Selecting from columns of a derived table.
A derived table is a parenthesized SELECT
statement whose result set is used in an enclosing query as if it were a regular table or view.
The derived table is shown in bold here:
select fieldcount,
count(relation) as num_tables
from (select r.rdb$relation_name as relation,
count(*) as fieldcount
from rdb$relations r
join rdb$relation_fields rf
on rf.rdb$relation_name = r.rdb$relation_name
group by relation)
group by fieldcount
Asking the time through a context variable (CURRENT_TIME
):
select current_time from rdb$database
For those not familiar with RDB$DATABASE
: this is a system table that is present in all Firebird databases and is guaranteed to contain exactly one row.
Although it wasn’t created for this purpose, it has become standard practice among Firebird programmers to select from this table if you want to select “from nothing”, i.e., if you need data that are not bound to a any table or view, but can be derived from the expressions in the output columns alone.
Another example is:
select power(12, 2) as twelve_squared, power(12, 3) as twelve_cubed
from rdb$database
Finally, an example where you select some meaningful information from RDB$DATABASE
itself:
select rdb$character_set_name from rdb$database
As you may have guessed, this will give you the default character set of the database.
Functions, Aggregate Functions, Context Variables, CASE
, Subqueries
6.1.3. The FROM
clause
The FROM
clause specifies the source(s) from which the data are to be retrieved.
In its simplest form, this is just a single table or view.
But the source can also be a selectable stored procedure, a derived table or a common table expression.
Multiple sources can be combined using various types of joins.
This section concentrates on single-source selects. Joins are discussed in a following section.
SELECT ... FROM <source> [<joins>] [...] <source> ::= table [[AS] alias] | selectable-stored-procedure [(<args>)] [[AS] alias] | <derived-table> <derived-table> ::= (<select-statement>) [[AS] alias] [(<column-aliases>)] <column-aliases> ::= column-alias [, column-alias ...]
Argument | Description |
---|---|
table |
Name of a table, view or CTE |
selectable-stored-procedure |
Name of a selectable stored procedure |
args |
Selectable stored procedure arguments |
derived table |
Derived table query expression |
select-statement |
Any SELECT statement |
column-aliases |
Alias for a column in a relation, CTE or derived table |
alias |
The alias of a data source (table, view, procedure, CTE, derived table) |
Selecting FROM
a table or view
When selecting from a single table or view, the FROM
clause need not contain anything more than the name.
An alias may be useful or even necessary if there are subqueries that refer to the main select statement (as they often do — subqueries like this are called correlated subqueries).
Examples
select id, name, sex, age from actors
where state = 'Ohio'
select * from birds
where type = 'flightless'
order by family, genus, species
select firstname,
middlename,
lastname,
date_of_birth,
(select name from schools s where p.school = s.id) schoolname
from pupils p
where year_started = '2012'
order by schoolname, date_of_birth
Never mix column names with column aliases!
If you specify an alias for a table or a view, you must always use this alias in place of the table name whenever you query the columns of the relation (and wherever else you make a reference to columns, such as Correct use:
Incorrect use:
|
Selecting FROM
a stored procedure
A selectable stored procedure is a procedure that:
-
contains at least one output parameter, and
-
utilizes the
SUSPEND
keyword so the caller can fetch the output rows one by one, just as when selecting from a table or view.
The output parameters of a selectable stored procedure correspond to the columns of a regular table.
Selecting from a stored procedure without input parameters is just like selecting from a table or view:
select * from suspicious_transactions
where assignee = 'John'
Any required input parameters must be specified after the procedure name, enclosed in parentheses:
select name, az, alt from visible_stars('Brugge', current_date, '22:30')
where alt >= 20
order by az, alt
Values for optional parameters (that is, parameters for which default values have been defined) may be omitted or provided. However, if you provide them only partly, the parameters you omit must all be at the tail end.
Supposing that the procedure visible_stars
from the previous example has two optional parameters: min_magn
(numeric(3,1)
) and spectral_class
(varchar(12)
), the following queries are all valid:
select name, az, alt
from visible_stars('Brugge', current_date, '22:30');
select name, az, alt
from visible_stars('Brugge', current_date, '22:30', 4.0);
select name, az, alt
from visible_stars('Brugge', current_date, '22:30', 4.0, 'G');
But this one isn’t, because there’s a “hole” in the parameter list:
select name, az, alt
from visible_stars('Brugge', current_date, '22:30', 'G');
An alias for a selectable stored procedure is specified after the parameter list:
select
number,
(select name from contestants c where c.number = gw.number)
from get_winners('#34517', 'AMS') gw
If you refer to an output parameter (“column”) by qualifying it with the full procedure name, the procedure alias should be omitted:
select
number,
(select name from contestants c where c.number = get_winners.number)
from get_winners('#34517', 'AMS')
Selecting FROM
a derived table
A derived table is a valid SELECT
statement enclosed in parentheses, optionally followed by a table alias and/or column aliases.
The result set of the statement acts as a virtual table which the enclosing statement can query.
(<select-query>) [[AS] derived-table-alias] [(<derived-column-aliases>)] <derived-column-aliases> := column-alias [, column-alias ...]
The set returned data set by this “SELECT FROM (SELECT FROM..)
” style of statement is a virtual table that can be queried within the enclosing statement, as if it were a regular table or view.
Sample using a derived table
The derived table in the query below returns the list of table names in the database and the number of columns in each. A “drill-down” query on the derived table returns the counts of fields and the counts of tables having each field count:
SELECT
FIELDCOUNT,
COUNT(RELATION) AS NUM_TABLES
FROM (SELECT
R.RDB$RELATION_NAME RELATION,
COUNT(*) AS FIELDCOUNT
FROM RDB$RELATIONS R
JOIN RDB$RELATION_FIELDS RF
ON RF.RDB$RELATION_NAME = R.RDB$RELATION_NAME
GROUP BY RELATION)
GROUP BY FIELDCOUNT
A trivial example demonstrating how the alias of a derived table and the list of column aliases (both optional) can be used:
SELECT
DBINFO.DESCR, DBINFO.DEF_CHARSET
FROM (SELECT *
FROM RDB$DATABASE) DBINFO
(DESCR, REL_ID, SEC_CLASS, DEF_CHARSET)
More about Derived Tables
Derived tables can
Furthermore,
|
A more useful example
Suppose we have a table COEFFS
which contains the coefficients of a number of quadratic equations we have to solve.
It has been defined like this:
create table coeffs (
a double precision not null,
b double precision not null,
c double precision not null,
constraint chk_a_not_zero check (a <> 0)
)
Depending on the values of a
, b
and c
, each equation may have zero, one or two solutions.
It is possible to find these solutions with a single-level query on table COEFFS
, but the code will look rather messy and several values (like the discriminant) will have to be calculated multiple times per row.
A derived table can help keep things clean here:
select
iif (D >= 0, (-b - sqrt(D)) / denom, null) sol_1,
iif (D > 0, (-b + sqrt(D)) / denom, null) sol_2
from
(select b, b*b - 4*a*c, 2*a from coeffs) (b, D, denom)
If we want to show the coefficients next to the solutions (which may not be a bad idea), we can alter the query like this:
select
a, b, c,
iif (D >= 0, (-b - sqrt(D)) / denom, null) sol_1,
iif (D > 0, (-b + sqrt(D)) / denom, null) sol_2
from
(select a, b, c, b*b - 4*a*c as D, 2*a as denom
from coeffs)
Notice that whereas the first query used a column aliases list for the derived table, the second adds aliases internally where needed. Both methods work, as long as every column is guaranteed to have a name.
Selecting FROM
a CTE
A common table expression or CTE is a more complex variant of the derived table, but it is also more powerful.
A preamble, starting with the keyword WITH
, defines one or more named CTE's, each with an optional column aliases list.
The main query, which follows the preamble, can then access these CTE's as if they were regular tables or views.
The CTE's go out of scope once the main query has run to completion.
For a full discussion of CTE's, please refer to the section Common Table Expressions (WITH … AS … SELECT).
The following is a rewrite of our derived table example as a CTE:
with vars (b, D, denom) as (
select b, b*b - 4*a*c, 2*a from coeffs
)
select
iif (D >= 0, (-b - sqrt(D)) / denom, null) sol_1,
iif (D > 0, (-b + sqrt(D)) / denom, null) sol_2
from vars
Except for the fact that the calculations that have to be made first are now at the beginning, this isn’t a great improvement over the derived table version. But we can now also eliminate the double calculation of sqrt(D) for every row:
with vars (b, D, denom) as (
select b, b*b - 4*a*c, 2*a from coeffs
),
vars2 (b, D, denom, sqrtD) as (
select b, D, denom, iif (D >= 0, sqrt(D), null) from vars
)
select
iif (D >= 0, (-b - sqrtD) / denom, null) sol_1,
iif (D > 0, (-b + sqrtD) / denom, null) sol_2
from vars2
The code is a little more complicated now, but it might execute more efficiently (depending on what takes more time: executing the SQRT
function or passing the values of b
, D
and denom
through an extra CTE).
Incidentally, we could have done the same with derived tables, but that would involve nesting.
6.1.4. Joins
Joins combine data from two sources into a single set.
This is done on a row-by-row basis and usually involves checking a join condition in order to determine which rows should be merged and appear in the resulting dataset.
There are several types (INNER
, OUTER
) and classes (qualified, natural, etc.) of joins, each with its own syntax and rules.
Since joins can be chained, the datasets involved in a join may themselves be joined sets.
SELECT ... FROM <source> [<joins>] [...] <source> ::= table [[AS] alias] | selectable-stored-procedure [(<args>)] [[AS] alias] | <derived-table> <joins> ::= <join> [<join> ...] <join> ::= [<join-type>] JOIN <source> <join-condition> | NATURAL [<join-type>] JOIN <source> | {CROSS JOIN | ,} <source> <join-type> ::= INNER | {LEFT | RIGHT | FULL} [OUTER] <join-condition> ::= ON <condition> | USING (<column-list>)
Argument | Description |
---|---|
table |
Name of a table, view or CTE |
selectable-stored-procedure |
Name of a selectable stored procedure |
args |
Selectable stored procedure input parameter(s) |
derived-table |
Derived table query expression |
alias |
An alias for a data source (table, view, procedure, CTE, derived table) |
condition |
Join condition (criterion) |
column-list |
The list of columns used for an equi-join |
Inner vs. outer joins
A join always combines data rows from two sets (usually referred to as the left set and the right set). By default, only rows that meet the join condition (i.e., that match at least one row in the other set when the join condition is applied) make it into the result set. This default type of join is called an inner join. Suppose we have the following two tables:
ID | S |
---|---|
87 |
Just some text |
235 |
Silence |
CODE | X |
---|---|
-23 |
56.7735 |
87 |
416.0 |
If we join these tables like this:
select *
from A
join B on A.id = B.code;
then the result set will be:
ID | S | CODE | X |
---|---|---|---|
87 |
Just some text |
87 |
416.0 |
The first row of A
has been joined with the second row of B
because together they met the condition “A.id = B.code
”.
The other rows from the source tables have no match in the opposite set and are therefore not included in the join.
Remember, this is an INNER
join.
We can make that fact explicit by writing:
select *
from A
inner join B on A.id = B.code;
However, since INNER
is the default, this is rarely done.
It is perfectly possible that a row in the left set matches several rows from the right set or vice versa. In that case, all those combinations are included, and we can get results like:
ID | S | CODE | X |
---|---|---|---|
87 |
Just some text |
87 |
416.0 |
87 |
Just some text |
87 |
-1.0 |
-23 |
Don’t know |
-23 |
56.7735 |
-23 |
Still don’t know |
-23 |
56.7735 |
-23 |
I give up |
-23 |
56.7735 |
Sometimes we want (or need) all the rows of one or both of the sources to appear in the joined set, regardless of whether they match a record in the other source.
This is where outer joins come in.
A LEFT
outer join includes all the records from the left set, but only matching records from the right set.
In a RIGHT
outer join it’s the other way around.
FULL
outer joins include all the records from both sets.
In all outer joins, the “holes” (the places where an included source record doesn’t have a match in the other set) are filled up with NULL
s.
In order to make an outer join, you must specify LEFT
, RIGHT
or FULL
, optionally followed by the keyword OUTER
.
Below are the results of the various outer joins when applied to our original tables A
and B
:
select *
from A
left [outer] join B on A.id = B.code;
ID | S | CODE | X |
---|---|---|---|
87 |
Just some text |
87 |
416.0 |
235 |
Silence |
<null> |
<null> |
select *
from A
right [outer] join B on A.id = B.code
ID | S | CODE | X |
---|---|---|---|
<null> |
<null> |
-23 |
56.7735 |
87 |
Just some text |
87 |
416.0 |
select *
from A
full [outer] join B on A.id = B.code
ID | S | CODE | X |
---|---|---|---|
<null> |
<null> |
-23 |
56.7735 |
87 |
Just some text |
87 |
416.0 |
235 |
Silence |
<null> |
<null> |
Qualified joins
Qualified joins specify conditions for the combining of rows.
This happens either explicitly in an ON
clause or implicitly in a USING
clause.
<qualified-join> ::= [<join-type>] JOIN <source> <join-condition> <join-type> ::= INNER | {LEFT | RIGHT | FULL} [OUTER] <join-condition> ::= ON <condition> | USING (<column-list>)
Explicit-condition joins
Most qualified joins have an ON
clause, with an explicit condition that can be any valid boolean expression but usually involves some comparison between the two sources involved.
Quite often, the condition is an equality test (or a number of AND
ed equality tests) using the “=
” operator.
Joins like these are called equi-joins.
(The examples in the section on inner and outer joins were al equi-joins.)
Examples of joins with an explicit condition:
/* Select all Detroit customers who made a purchase
in 2013, along with the purchase details: */
select * from customers c
join sales s on s.cust_id = c.id
where c.city = 'Detroit' and s.year = 2013;
/* Same as above, but include non-buying customers: */
select * from customers c
left join sales s on s.cust_id = c.id
where c.city = 'Detroit' and s.year = 2013;
/* For each man, select the women who are taller than he.
Men for whom no such woman exists are not included. */
select m.fullname as man, f.fullname as woman
from males m
join females f on f.height > m.height;
/* Select all pupils with their class and mentor.
Pupils without a mentor are also included.
Pupils without a class are not included. */
select p.firstname, p.middlename, p.lastname,
c.name, m.name
from pupils p
join classes c on c.id = p.class
left join mentors m on m.id = p.mentor;
Named columns joins
Equi-joins often compare columns that have the same name in both tables. If this is the case, we can also use the second type of qualified join: the named columns join.
Named columns joins are not supported in Dialect 1 databases. |
Named columns joins have a USING
clause which states just the column names.
So instead of this:
select * from flotsam f
join jetsam j
on f.sea = j.sea
and f.ship = j.ship;
we can also write:
select * from flotsam
join jetsam using (sea, ship)
which is considerably shorter.
The result set is a little different though — at least when using “SELECT *
”:
-
The explicit-condition join — with the
ON
clause — will contain each of the columnsSEA
andSHIP
twice: once from tableFLOTSAM
, and once from tableJETSAM
. Obviously, they will have the same values. -
The named columns join — with the
USING
clause — will contain these columns only once.
If you want all the columns in the result set of the named columns join, set up your query like this:
select f.*, j.*
from flotsam f
join jetsam j using (sea, ship);
This will give you the exact same result set as the explicit-condition join.
For an OUTER
named columns join, there’s an additional twist when using “SELECT *
” or an unqualified column name from the USING
list:
If a row from one source set doesn’t have a match in the other but must still be included because of the LEFT
, RIGHT
or FULL
directive, the merged column in the joined set gets the non-NULL
value.
That is fair enough, but now you can’t tell whether this value came from the left set, the right set, or both.
This can be especially deceiving when the value came from the right hand set, because “*
” always shows combined columns in the left hand part — even in the case of a RIGHT
join.
Whether this is a problem or not depends on the situation.
If it is, use the “a.*, b.*
” approach shown above, with a
and b
the names or aliases of the two sources.
Or better yet, avoid “*
” altogether in your serious queries and qualify all column names in joined sets.
This has the additional benefit that it forces you to think about which data you want to retrieve and where from.
It is your responsibility to make sure that the column names in the USING
list are of compatible types between the two sources.
If the types are compatible but not equal, the engine converts them to the type with the broadest range of values before comparing the values.
This will also be the data type of the merged column that shows up in the result set if “SELECT *
” or the unqualified column name is used.
Qualified columns on the other hand will always retain their original data type.
Natural joins
Taking the idea of the named columns join a step further, a natural join performs an automatic equi-join on all the columns that have the same name in the left and right table. The data types of these columns must be compatible.
Natural joins are not supported in Dialect 1 databases. |
<natural-join> ::= NATURAL [<join-type>] JOIN <source> <join-type> ::= INNER | {LEFT | RIGHT | FULL} [OUTER]
Given these two tables:
create table TA (
a bigint,
s varchar(12),
ins_date date
);
create table TB (
a bigint,
descr varchar(12),
x float,
ins_date date
);
a natural join on TA
and TB
would involve the columns a
and ins_date
, and the following two statements would have the same effect:
select * from TA
natural join TB;
select * from TA
join TB using (a, ins_date);
Like all joins, natural joins are inner joins by default, but you can turn them into outer joins by specifying LEFT
, RIGHT
or FULL
before the JOIN
keyword.
Caution: if there are no columns with the same name in the two source relations, a CROSS JOIN
is performed.
We’ll get to this type of join in a minute.
A Note on Equality
This note about equality and inequality operators applies everywhere in Firebird’s SQL language, not just in |
The “=
” operator, which is explicitly used in many conditional joins and implicitly in named column joins and natural joins, only matches values to values.
According to the SQL standard, NULL
is not a value and hence two NULL
s are neither equal nor unequal to one another.
If you need NULL
s to match each other in a join, use the IS NOT DISTINCT FROM
operator.
This operator returns true if the operands have the same value or if they are both NULL
.
select *
from A join B
on A.id is not distinct from B.code;
Likewise, in the — extremely rare — cases where you want to join on inequality, use IS DISTINCT FROM
, not “<>
”, if you want NULL
to be considered different from any value and two NULL
s considered equal:
select *
from A join B
on A.id is distinct from B.code;
Cross joins
A cross join produces the full set product of the two data sources. This means that it successfully matches every row in the left source to every row in the right source.
<cross-join> ::= {CROSS JOIN | ,} <source>
Use of the comma syntax is discouraged, and we recommend using the explicit join syntax.
Cross-joining two sets is equivalent to joining them on a tautology (a condition that is always true). The following two statements have the same effect:
select * from TA
cross join TB;
select * from TA
join TB on 1 = 1;
Cross joins are inner joins, because they only include matching records – it just so happens that every record matches! An outer cross join, if it existed, wouldn’t add anything to the result, because what outer joins add are non-matching records, and these don’t exist in cross joins.
Cross joins are seldom useful, except if you want to list all the possible combinations of two or more variables. Suppose you are selling a product that comes in different sizes, different colors and different materials. If these variables are each listed in a table of their own, this query would return all the combinations:
select m.name, s.size, c.name
from materials m
cross join sizes s
cross join colors c;
Ambiguous field names in joins
Firebird rejects unqualified field names in a query if these field names exist in more than one dataset involved in a join.
This is even true for inner equi-joins where the field name figures in the ON
clause like this:
select a, b, c
from TA
join TB on TA.a = TB.a;
There is one exception to this rule: with named columns joins and natural joins, the unqualified field name of a column taking part in the matching process may be used legally and refers to the merged column of the same name.
For named columns joins, these are the columns listed in the USING
clause.
For natural joins, they are the columns that have the same name in both relations.
But please notice again that, especially in outer joins, plain colname
isn’t always the same as left.colname
or right.colname
.
Types may differ, and one of the qualified columns may be NULL
while the other isn’t.
In that case, the value in the merged, unqualified column may mask the fact that one of the source values is absent.
Joins with stored procedures
If a join is performed with a stored procedure that is not correlated with other data streams via input parameters, there are no oddities. If correlation is involved, an unpleasant quirk reveals itself. The problem is that the optimizer denies itself any way to determine the interrelationships of the input parameters of the procedure from the fields in the other streams:
SELECT *
FROM MY_TAB
JOIN MY_PROC(MY_TAB.F) ON 1 = 1;
Here, the procedure will be executed before a single record has been retrieved from the table, MY_TAB
.
The isc_no_cur_rec error
error (no current record for fetch operation) is raised, interrupting the execution.
The solution is to use syntax that specifies the join order explicitly:
SELECT *
FROM MY_TAB
LEFT JOIN MY_PROC(MY_TAB.F) ON 1 = 1;
This forces the table to be read before the procedure and everything works correctly.
This quirk has been recognised as a bug in the optimizer and will be fixed in the next version of Firebird. |
6.1.5. The WHERE
clause
The WHERE
clause serves to limit the rows returned to the ones that the caller is interested in.
The condition following the keyword WHERE
can be as simple as a check like “AMOUNT = 3
” or it can be a multilayered, convoluted expression containing subselects, predicates, function calls, mathematical and logical operators, context variables and more.
The condition in the WHERE
clause is often called the search condition, the search expression or simply the search.
In DSQL and ESQL, the search expression may contain parameters.
This is useful if a query has to be repeated a number of times with different input values.
In the SQL string as it is passed to the server, question marks are used as placeholders for the parameters.
They are called positional parameters because they can only be told apart by their position in the string.
Connectivity libraries often support named parameters of the form :id
, :amount
, :a
etc.
These are more user-friendly;
the library takes care of translating the named parameters to positional parameters before passing the statement to the server.
The search condition may also contain local (PSQL) or host (ESQL) variable names, preceded by a colon.
SELECT ... FROM ... [...] WHERE <search-condition> [...] <search-condition> ::= a boolean expression returning TRUE, FALSE or possibly UNKNOWN (NULL)
Only those rows for which the search condition evaluates to TRUE
are included in the result set.
Be careful with possible NULL
outcomes: if you negate a NULL
expression with NOT
, the result will still be NULL
and the row will not pass.
This is demonstrated in one of the examples below.
Examples
select genus, species from mammals
where family = 'Felidae'
order by genus;
select * from persons
where birthyear in (1880, 1881)
or birthyear between 1891 and 1898;
select name, street, borough, phone
from schools s
where exists (select * from pupils p where p.school = s.id)
order by borough, street;
select * from employees
where salary >= 10000 and position <> 'Manager';
select name from wrestlers
where region = 'Europe'
and weight > all (select weight from shot_putters
where region = 'Africa');
select id, name from players
where team_id = (select id from teams where name = 'Buffaloes');
select sum (population) from towns
where name like '%dam'
and province containing 'land';
select password from usertable
where username = current_user;
The following example shows what can happen if the search condition evaluates to NULL
.
Suppose you have a table listing some children’s names and the number of marbles they possess. At a certain moment, the table contains these data:
CHILD | MARBLES |
---|---|
Anita |
23 |
Bob E. |
12 |
Chris |
<null> |
Deirdre |
1 |
Eve |
17 |
Fritz |
0 |
Gerry |
21 |
Hadassah |
<null> |
Isaac |
6 |
First, please notice the difference between NULL
and 0: Fritz is known to have no marbles at all, Chris’s and Hadassah’s marble counts are unknown.
Now, if you issue this SQL statement:
select list(child) from marbletable where marbles > 10;
you will get the names Anita, Bob E., Eve and Gerry. These children all have more than 10 marbles.
If you negate the expression:
select list(child) from marbletable where not marbles > 10
it’s the turn of Deirdre, Fritz and Isaac to fill the list. Chris and Hadassah are not included, because they aren’t known to have ten marbles or less. Should you change that last query to:
select list(child) from marbletable where marbles <= 10;
the result will still be the same, because the expression NULL <= 10
yields UNKNOWN
.
This is not the same as TRUE
, so Chris and Hadassah are not listed.
If you want them listed with the “poor” children, change the query to:
select list(child) from marbletable
where marbles <= 10 or marbles is null;
Now the search condition becomes true for Chris and Hadassah, because “marbles is null
” obviously returns TRUE
in their case.
In fact, the search condition cannot be NULL
for anybody now.
Lastly, two examples of SELECT
queries with parameters in the search.
It depends on the application how you should define query parameters and even if it is possible at all.
Notice that queries like these cannot be executed immediately: they have to be prepared first.
Once a parameterized query has been prepared, the user (or calling code) can supply values for the parameters and have it executed many times, entering new values before every call.
How the values are entered and the execution started is up to the application.
In a GUI environment, the user typically types the parameter values in one or more text boxes and then clicks an “Execute”, “Run” or “Refresh” button.
select name, address, phone frome stores
where city = ? and class = ?;
select * from pants
where model = :model and size = :size and color = :col;
The last query cannot be passed directly to the engine; the application must convert it to the other format first, mapping named parameters to positional parameters.
6.1.6. The GROUP BY
clause
GROUP BY
merges output rows that have the same combination of values in its item list into a single row.
Aggregate functions in the select list are applied to each group individually instead of to the dataset as a whole.
If the select list only contains aggregate columns or, more generally, columns whose values don’t depend on individual rows in the underlying set, GROUP BY
is optional.
When omitted, the final result set of will consist of a single row (provided that at least one aggregated column is present).
If the select list contains both aggregate columns and columns whose values may vary per row, the GROUP BY
clause becomes mandatory.
SELECT ... FROM ... GROUP BY <grouping-item> [, <grouping-item> ...] [HAVING <grouped-row-condition>] ... <grouping-item> ::= <non-aggr-select-item> | <non-aggr-expression> <non-aggr-select-item> ::= column-copy | column-alias | column-position
Argument | Description |
---|---|
non-aggr-expression |
Any non-aggregating expression that is not included in the |
column-copy |
A literal copy, from the |
column-alias |
The alias, from the |
column-position |
The position number, in the |
A general rule of thumb is that every non-aggregate item in the SELECT
list must also be in the GROUP BY
list.
You can do this in three ways:
-
By copying the item verbatim from the select list, e.g. “
class
” or “'D:' || upper(doccode)
”. -
By specifying the column alias, if it exists.
-
By specifying the column position as an integer literal between 1 and the number of columns. Integer values resulting from expressions or parameter substitutions are simply invariables and will be used as such in the grouping. They will have no effect though, as their value is the same for each row.
If you group by a column position, the expression at that position is copied internally from the select list. If it concerns a subquery, that subquery will be executed again in the grouping phase. That is to say, grouping by the column position, rather than duplicating the subquery expression in the grouping clause, saves keystrokes and bytes, but it is not a way of saving processing cycles! |
In addition to the required items, the grouping list may also contain:
-
Columns from the source table that are not in the select list, or non-aggregate expressions based on such columns. Adding such columns may further subdivide the groups. But since these columns are not in the select list, you can’t tell which aggregated row corresponds to which value in the column. So, in general, if you are interested in this information, you also include the column or expression in the select list — which brings you back to the rule: “every non-aggregate column in the select list must also be in the grouping list”.
-
Expressions that aren’t dependent on the data in the underlying set, e.g. constants, context variables, single-value non-correlated subselects etc. This is only mentioned for completeness, as adding such items is utterly pointless: they don’t affect the grouping at all. “Harmless but useless” items like these may also figure in the select list without being copied to the grouping list.
Examples
When the select list contains only aggregate columns, GROUP BY
is not mandatory:
select count(*), avg(age) from students
where sex = 'M';
This will return a single row listing the number of male students and their average age.
Adding expressions that don’t depend on values in individual rows of table STUDENTS
doesn’t change that:
select count(*), avg(age), current_date from students
where sex = 'M';
The row will now have an extra column showing the current date, but other than that, nothing fundamental has changed.
A GROUP BY
clause is still not required.
However, in both the above examples it is allowed. This is perfectly valid:
select count(*), avg(age) from students
where sex = 'M'
group by class;
and will return a row for each class that has boys in it, listing the number of boys and their average age in that particular class.
(If you also leave the current_date
field in, this value will be repeated on every row, which is not very exciting.)
The above query has a major drawback though: it gives you information about the different classes, but it doesn’t tell you which row applies to which class.
In order to get that extra bit of information, the non-aggregate column CLASS
must be added to the select list:
select class, count(*), avg(age) from students
where sex = 'M'
group by class;
Now we have a useful query.
Notice that the addition of column CLASS
also makes the GROUP BY
clause mandatory.
We can’t drop that clause anymore, unless we also remove CLASS
from the column list.
The output of our last query may look something like this:
CLASS | COUNT | AVG |
---|---|---|
2A |
12 |
13.5 |
2B |
9 |
13.9 |
3A |
11 |
14.6 |
3B |
12 |
14.4 |
… |
… |
… |
The headings “COUNT” and “AVG” are not very informative. In a simple case like this, you might get away with that, but in general you should give aggregate columns a meaningful name by aliasing them:
select class,
count(*) as num_boys,
avg(age) as boys_avg_age
from students
where sex = 'M'
group by class;
As you may recall from the formal syntax of the columns list, the AS
keyword is optional.
Adding more non-aggregate (or rather: row-dependent) columns requires adding them to the GROUP BY
clause too.
For instance, you might want to see the above information for girls as well; and you may also want to differentiate between boarding and day students:
select class,
sex,
boarding_type,
count(*) as number,
avg(age) as avg_age
from students
group by class, sex, boarding_type;
This may give you the following result:
CLASS | SEX | BOARDING_TYPE | NUMBER | AVG_AGE |
---|---|---|---|---|
2A |
F |
BOARDING |
9 |
13.3 |
2A |
F |
DAY |
6 |
13.5 |
2A |
M |
BOARDING |
7 |
13.6 |
2A |
M |
DAY |
5 |
13.4 |
2B |
F |
BOARDING |
11 |
13.7 |
2B |
F |
DAY |
5 |
13.7 |
2B |
M |
BOARDING |
6 |
13.8 |
… |
… |
… |
… |
… |
Each row in the result set corresponds to one particular combination of the variables class, sex and boarding type. The aggregate results — number and average age — are given for each of these rather specific groups individually. In a query like this, you don’t see a total for boys as a whole, or day students as a whole. That’s the tradeoff: the more non-aggregate columns you add, the more you can pinpoint very specific groups, but the more you also lose sight of the general picture. Of course, you can still obtain the “coarser” aggregates through separate queries.
HAVING
Just as a WHERE
clause limits the rows in a dataset to those that meet the search condition, so the HAVING
subclause imposes restrictions on the aggregated rows in a grouped set.
HAVING
is optional, and can only be used in conjunction with GROUP BY
.
The condition(s) in the HAVING
clause can refer to:
-
Any aggregated column in the select list. This is the most widely used alternative.
-
Any aggregated expression that is not in the select list, but allowed in the context of the query. This is sometimes useful too.
-
Any column in the
GROUP BY
list. While legal, it is more efficient to filter on these non-aggregated data at an earlier stage: in theWHERE
clause. -
Any expression whose value doesn’t depend on the contents of the dataset (like a constant or a context variable). This is valid but utterly pointless, because it will either suppress the entire set or leave it untouched, based on conditions that have nothing to do with the set itself.
A HAVING
clause can not contain:
-
Non-aggregated column expressions that are not in the
GROUP BY
list. -
Column positions. An integer in the
HAVING
clause is just an integer. -
Column aliases –- not even if they appear in the
GROUP BY
clause!
Examples
Building on our earlier examples, this could be used to skip small groups of students:
select class,
count(*) as num_boys,
avg(age) as boys_avg_age
from students
where sex = 'M'
group by class
having count(*) >= 5;
To select only groups that have a minimum age spread:
select class,
count(*) as num_boys,
avg(age) as boys_avg_age
from students
where sex = 'M'
group by class
having max(age) - min(age) > 1.2;
Notice that if you’re really interested in this information, you’d normally include min(age)
and max(age)
-– or the expression “max(age) - min(age)
” –- in the select list as well!
To include only 3rd classes:
select class,
count(*) as num_boys,
avg(age) as boys_avg_age
from students
where sex = 'M'
group by class
having class starting with '3';
Better would be to move this condition to the WHERE
clause:
select class,
count(*) as num_boys,
avg(age) as boys_avg_age
from students
where sex = 'M' and class starting with '3'
group by class;
6.1.7. The PLAN
clause
The PLAN
clause enables the user to submit a data retrieval plan, thus overriding the plan that the optimizer would have generated automatically.
PLAN <plan-expr> <plan-expr> ::= (<plan-item> [, <plan-item> ...]) | <sorted-item> | <joined-item> | <merged-item> <sorted-item> ::= SORT (<plan-item>) <joined-item> ::= JOIN (<plan-item>, <plan-item> [, <plan-item> ...]) <merged-item> ::= [SORT] MERGE (<sorted-item>, <sorted-item> [, <sorted-item> ...]) <plan-item> ::= <basic-item> | <plan-expr> <basic-item> ::= <relation> { NATURAL | INDEX (<indexlist>) | ORDER index [INDEX (<indexlist>)] } <relation> ::= table | view [table] <indexlist> ::= index [, index ...]
Argument | Description |
---|---|
table |
Table name or its alias |
view |
View name |
index |
Index name |
Every time a user submits a query to the Firebird engine, the optimizer computes a data retrieval strategy.
Most Firebird clients can make this retrieval plan visible to the user.
In Firebird’s own isql
utility, this is done with the command SET PLAN ON
.
If you are studying query plans rather than running queries, SET PLANONLY ON
will show the plan without executing the query.
In most situations, you can trust that Firebird will select the optimal query plan for you. However, if you have complicated queries that seem to be underperforming, it may very well be worth your while to examine the plan and see if you can improve on it.
Simple plans
The simplest plans consist of just a relation name followed by a retrieval method.
For example, for an unsorted single-table select without a WHERE
clause:
select * from students
plan (students natural);
If there’s a WHERE
or a HAVING
clause, you can specify the index to be used for finding matches:
select * from students
where class = '3C'
plan (students index (ix_stud_class));
The INDEX
directive is also used for join conditions (to be discussed a little later).
It can contain a list of indexes, separated by commas.
ORDER
specifies the index for sorting the set if an ORDER BY
or GROUP BY
clause is present:
select * from students
plan (students order pk_students)
order by id;
ORDER
and INDEX
can be combined:
select * from students
where class >= '3'
plan (students order pk_students index (ix_stud_class))
order by id;
It is perfectly OK if ORDER
and INDEX
specify the same index:
select * from students
where class >= '3'
plan (students order ix_stud_class index (ix_stud_class))
order by class;
For sorting sets when there’s no usable index available (or if you want to suppress its use), leave out ORDER
and prepend the plan expression with SORT
:
select * from students
plan sort (students natural)
order by name;
Or when an index is used for the search:
select * from students
where class >= '3'
plan sort (students index (ix_stud_class))
order by name;
Notice that SORT
, unlike ORDER
, is outside the parentheses.
This reflects the fact that the data rows are retrieved unordered and sorted afterwards by the engine.
When selecting from a view, specify the view and the table involved.
For instance, if you have a view FRESHMEN
that selects just the first-year students:
select * from freshmen
plan (freshmen students natural);
Or, for instance:
select * from freshmen
where id > 10
plan sort (freshmen students index (pk_students))
order by name desc;
If a table or view has been aliased, it is the alias, not the original name, that must be used in the |
Composite plans
When a join is made, you can specify the index which is to be used for matching.
You must also use the JOIN
directive on the two streams in the plan:
select s.id, s.name, s.class, c.mentor
from students s
join classes c on c.name = s.class
plan join (s natural, c index (pk_classes));
The same join, sorted on an indexed column:
select s.id, s.name, s.class, c.mentor
from students s
join classes c on c.name = s.class
plan join (s order pk_students, c index (pk_classes))
order by s.id;
And on a non-indexed column:
select s.id, s.name, s.class, c.mentor
from students s
join classes c on c.name = s.class
plan sort (join (s natural, c index (pk_classes)))
order by s.name;
With a search added:
select s.id, s.name, s.class, c.mentor
from students s
join classes c on c.name = s.class
where s.class <= '2'
plan sort (join (s index (fk_student_class), c index (pk_classes)))
order by s.name;
As a left outer join:
select s.id, s.name, s.class, c.mentor
from classes c
left join students s on c.name = s.class
where s.class <= '2'
plan sort (join (c natural, s index (fk_student_class)))
order by s.name;
If there is no index available to match the join criteria (or if you don’t want to use it), the plan must first sort both streams on their join column(s) and then merge them.
This is achieved with the SORT
directive (which we’ve already met) and MERGE
instead of JOIN
:
select * from students s
join classes c on c.cookie = s.cookie
plan merge (sort (c natural), sort (s natural));
Adding an ORDER BY
clause means the result of the merge must also be sorted:
select * from students s
join classes c on c.cookie = s.cookie
plan sort (merge (sort (c natural), sort (s natural)))
order by c.name, s.id;
Finally, we add a search condition on two indexable colums of table STUDENTS
:
select * from students s
join classes c on c.cookie = s.cookie
where s.id < 10 and s.class <= '2'
plan sort (merge (sort (c natural),
sort (s index (pk_students, fk_student_class))))
order by c.name, s.id;
As follows from the formal syntax definition, JOIN
s and MERGE
s in the plan may combine more than two streams.
Also, every plan expression may be used as a plan item in an encompassing plan.
This means that plans of certain complicated queries may have various nesting levels.
Finally, instead of MERGE
you may also write SORT MERGE
.
As this makes absolutely no difference and may create confusion with “real” SORT
directives (the ones that do make a difference), it’s probably best to stick to plain MERGE
.
Occasionally, the optimizer will accept a plan and then not follow it, even though it does not reject it as invalid. One such example was
It is advisable to treat such as plan as “deprecated”. |
6.1.8. UNION
A UNION
concatenates two or more datasets, thus increasing the number of rows but not the number of columns.
Datasets taking part in a UNION
must have the same number of columns, and columns at corresponding positions must be of the same type.
Other than that, they may be totally unrelated.
By default, a union suppresses duplicate rows. UNION ALL
shows all rows, including any duplicates.
The optional DISTINCT
keyword makes the default behaviour explicit.
<union> ::= <individual-select> UNION [DISTINCT | ALL] <individual-select> [ [UNION [DISTINCT | ALL] <individual-select> ... ] [<union-wide-clauses>] <individual-select> ::= SELECT [TRANSACTION name] [FIRST m] [SKIP n] [DISTINCT | ALL] <columns> [INTO <host-varlist>] FROM <source> [[AS] alias] [<joins>] [WHERE <condition>] [GROUP BY <grouping-list> [HAVING <aggregate-condition>]] [PLAN <plan-expr>] <union-wide-clauses> ::= [ORDER BY <ordering-list>] [ROWS m [TO n]] [FOR UPDATE [OF <columns>]] [WITH LOCK] [INTO <PSQL-varlist>]
Unions take their column names from the first select query.
If you want to alias union columns, do so in the column list of the topmost SELECT
.
Aliases in other participating selects are allowed and may even be useful, but will not propagate to the union level.
If a union has an ORDER BY
clause, the only allowed sort items are integer literals indicating 1-based column positions, optionally followed by an ASC
/DESC
and/or a NULLS {FIRST | LAST}
directive.
This also implies that you cannot order a union by anything that isn’t a column in the union.
(You can, however, wrap it in a derived table, which gives you back all the usual sort options.)
Unions are allowed in subqueries of any kind and can themselves contain subqueries. They can also contain joins, and can take part in a join when wrapped in a derived table.
Examples
This query presents information from different music collections in one dataset using unions:
select id, title, artist, length, 'CD' as medium
from cds
union
select id, title, artist, length, 'LP'
from records
union
select id, title, artist, length, 'MC'
from cassettes
order by 3, 2 -- artist, title;
If id
, title
, artist
and length
are the only fields in the tables involved, the query can also be written as:
select c.*, 'CD' as medium
from cds c
union
select r.*, 'LP'
from records r
union
select c.*, 'MC'
from cassettes c
order by 3, 2 -- artist, title;
Qualifying the “stars” is necessary here because they are not the only item in the column list. Notice how the “c” aliases in the first and third select do not conflict with each other: their scopes are not union-wide but apply only to their respective select queries.
The next query retrieves names and phone numbers from translators and proofreaders.
Translators who also work as proofreaders will show up only once in the result set, provided their phone number is the same in both tables.
The same result can be obtained without DISTINCT
.
With ALL
, these people would appear twice.
select name, phone from translators
union distinct
select name, telephone from proofreaders;
A UNION
within a subquery:
select name, phone, hourly_rate from clowns
where hourly_rate < all
(select hourly_rate from jugglers
union
select hourly_rate from acrobats)
order by hourly_rate;
6.1.9. ORDER BY
When a SELECT
statement is executed, the result set is not sorted in any way.
It often happens that rows appear to be sorted chronologically, simply because they are returned in the same order they were added to the table by INSERT
statements.
To specify a sorting order for the set specification, an ORDER BY
clause is used.
SELECT ... FROM ... ... ORDER BY <ordering-item> [, <ordering-item> …] <ordering-item> ::= {col-name | col-alias | col-position | <expression>} [COLLATE collation-name] [ASC[ENDING] | DESC[ENDING]] [NULLS {FIRST|LAST}]
Argument | Description |
---|---|
col-name |
Full column name |
col-alias |
Column alias |
col-position |
Column position in the |
expression |
Any expression |
collation-name |
Collation name (sorting order for string types) |
Description
The ORDER BY
consists of a comma-separated list of the columns on which the result data set should be sorted.
The sort order can be specified by the name of the column — but only if the column was not previously aliased in the SELECT
columns list.
The alias must be used if it was used there.
The ordinal position number of the column in the SELECT
column list, the alias given to the column in the SELECT
list with the help of the AS
keyword, or the number of the column in the SELECT
list can be used without restriction.
The three forms of expressing the columns for the sort order can be mixed in the same ORDER BY
clause.
For instance, one column in the list can be specified by its name and another column can be specified by its number.
If you use the column position to specify the sort order for a query of the |
Sorting Direction
The keyword ASCENDING
, usually abbreviated to ASC
, specifies a sort direction from lowest to highest. ASCENDING
is the default sort direction.
The keyword DESCENDING
, usually abbreviated to DESC
, specifies a sort direction from highest to lowest.
Specifying ascending order for one column and the descending order for another is allowed.
Collation Order
The keyword COLLATE
specifies the collation order for a string column if you need a collation that is different from the normal one for this column.
The normal collation order will be either the default one for the database character set or one that has been set explicitly in the column’s definition.
NULLs Position
The keyword NULLS
defines where NULL in the associated column will fall in the sort order: NULLS FIRST
places the rows with the NULL
column above rows ordered by that column’s value;
NULLS LAST
places those rows after the ordered rows.
NULLS FIRST
is the default.
Ordering UNION
-ed Sets
The discrete queries contributing to a UNION
cannot take an ORDER BY
clause.
The only option is to order the entire output, using one ORDER BY
clause at the end of the overall query.
The simplest — and, in some cases, the only — method for specifying the sort order is by the ordinal column position. However, it is also valid to use the column names or aliases, from the first contributing query only.
The ASC
/DESC
and/or NULLS
directives are available for this global set.
If discrete ordering within the contributing set is required, use of derived tables or common table expressions for those sets may be a solution.
Examples of ORDER BY
Sorting the result set in ascending order, ordering by the RDB$CHARACTER_SET_ID, RDB$COLLATION_ID
columns of the RDB$COLLATIONS table
:
SELECT
RDB$CHARACTER_SET_ID AS CHARSET_ID,
RDB$COLLATION_ID AS COLL_ID,
RDB$COLLATION_NAME AS NAME
FROM RDB$COLLATIONS
ORDER BY RDB$CHARACTER_SET_ID, RDB$COLLATION_ID;
The same, but sorting by the column aliases:
SELECT
RDB$CHARACTER_SET_ID AS CHARSET_ID,
RDB$COLLATION_ID AS COLL_ID,
RDB$COLLATION_NAME AS NAME
FROM RDB$COLLATIONS
ORDER BY CHARSET_ID, COLL_ID;
Sorting the output data by the column position numbers:
SELECT
RDB$CHARACTER_SET_ID AS CHARSET_ID,
RDB$COLLATION_ID AS COLL_ID,
RDB$COLLATION_NAME AS NAME
FROM RDB$COLLATIONS
ORDER BY 1, 2;
Sorting a SELECT *
query by position numbers — possible, but nasty and not recommended:
SELECT *
FROM RDB$COLLATIONS
ORDER BY 3, 2;
Sorting by the second column in the BOOKS
table:
SELECT
BOOKS.*,
FILMS.DIRECTOR
FROM BOOKS, FILMS
ORDER BY 2;
Sorting in descending order by the values of column PROCESS_TIME
, with NULL
s placed at the beginning of the set:
SELECT *
FROM MSG
ORDER BY PROCESS_TIME DESC NULLS FIRST;
Sorting the set obtained by a UNION
of two queries.
Results are sorted in descending order for the values in the second column, with NULL
s at the end of the set;
and in ascending order for the values of the first column with NULL
s at the beginning.
SELECT
DOC_NUMBER, DOC_DATE
FROM PAYORDER
UNION ALL
SELECT
DOC_NUMBER, DOC_DATE
FROM BUDGORDER
ORDER BY 2 DESC NULLS LAST, 1 ASC NULLS FIRST;
6.1.10. ROWS
Retrieving a slice of rows from an ordered set
DSQL, PSQL
SELECT <columns> FROM ... [WHERE ...] [ORDER BY ...] ROWS m [TO n]
Argument | Description |
---|---|
m, n |
Any integer expressions |
Limits the amount of rows returned by the SELECT
statement to a specified number or range.
The FIRST
and SKIP
clauses do the same job as ROWS
, but neither are SQL-compliant.
Unlike FIRST
and SKIP
, the ROWS
and TO
clauses accept any type of integer expression as their arguments, without parentheses.
Of course, parentheses may still be needed for nested evaluations inside the expression and a subquery must always be enclosed in parentheses.
|
Calling ROWS m
retrieves the first m records from the set specified.
Characteristics of using ROWS m
without a TO
clause:
-
If m is greater than the total number of records in the intermediate data set, the entire set is returned
-
If m = 0, an empty set is returned
-
If m < 0, the
SELECT
statement call fails with an error
Calling ROWS m TO n
retrieves the rows from the set, starting at row m and ending after row n — the set is inclusive.
Characteristics of using ROWS m
with a TO
clause:
-
If m is greater than the total number of rows in the intermediate set and n >= m, an empty set is returned
-
If m is not greater than n and n is greater than the total number of rows in the intermediate set, the result set will be limited to rows starting from m, up to the end of the set
-
If m < 1 and n < 1, the
SELECT
statement call fails with an error -
If n = m - 1, an empty set is returned
-
If n < m - 1, the
SELECT
statement call fails with an error
Using a TO
clause without a ROWS
clause:
While ROWS
replaces the FIRST
and SKIP
syntax, there is one situation where the ROWS
syntax does not provide the same behaviour: specifying SKIP n
on its own returns the entire intermediate set, without the first n rows.
The ROWS … TO
syntax needs a little help to achieve this.
With the ROWS
syntax, you need a ROWS
clause in association with the TO
clause and deliberately make the second (n) argument greater than the size of the intermediate data set.
This is achieved by creating an expression for n that uses a subquery to retrieve the count of rows in the intermediate set and adds 1 to it.
Mixing ROWS
and FIRST
/SKIP
ROWS
syntax cannot be mixed with FIRST
/SKIP
syntax in the same SELECT
expression.
Using the different syntaxes in different subqueries in the same statement is allowed.
ROWS
Syntax in UNION
Queries
When ROWS
is used in a UNION
query, the ROWS
directive is applied to the unioned set and must be placed after the last SELECT
statement.
If a need arises to limit the subsets returned by one or more SELECT
statements inside UNION
, there are a couple of options:
-
Use
FIRST
/SKIP
syntax in theseSELECT
statements — bearing in mind that an ordering clause (ORDER BY
) cannot be applied locally to the discrete queries, but only to the combined output. -
Convert the queries to derived tables with their own
ROWS
clauses.
Examples of ROWS
The following examples rewrite the examples used in the section about FIRST
and SKIP
, earlier in this chapter.
Retrieve the first ten names from the output of a sorted query on the PEOPLE
table:
SELECT id, name
FROM People
ORDER BY name ASC
ROWS 1 TO 10;
or its equivalent
SELECT id, name
FROM People
ORDER BY name ASC
ROWS 10;
Return all records from the PEOPLE
table except for the first 10 names:
SELECT id, name
FROM People
ORDER BY name ASC
ROWS 11 TO (SELECT COUNT(*) FROM People);
And this query will return the last 10 records (pay attention to the parentheses):
SELECT id, name
FROM People
ORDER BY name ASC
ROWS (SELECT COUNT(*) - 9 FROM People)
TO (SELECT COUNT(*) FROM People);
This one will return rows 81-100 from the PEOPLE
table:
SELECT id, name
FROM People
ORDER BY name ASC
ROWS 81 TO 100;
6.1.11. FOR UPDATE [OF]
SELECT ... FROM single_table [WHERE ...] [FOR UPDATE [OF ...]]
FOR UPDATE
does not do what it suggests.
Its only effect currently is to disable the pre-fetch buffer.
It is likely to change in future: the plan is to validate cursors marked with |
The OF
sub-clause does not do anything at all.
6.1.12. WITH LOCK
DSQL, PSQL
Limited pessimistic locking
WITH LOCK
provides a limited explicit pessimistic locking capability for cautious use in conditions where the affected row set is:
-
extremely small (ideally, a singleton), and
-
precisely controlled by the application code.
This is for experts only!
The need for a pessimistic lock in Firebird is very rare indeed and should be well understood before use of this extension is considered. It is essential to understand the effects of transaction isolation and other transaction attributes before attempting to implement explicit locking in your application. |
SELECT ... FROM single_table [WHERE ...] [FOR UPDATE [OF ...]] WITH LOCK
If the WITH LOCK
clause succeeds, it will secure a lock on the selected rows and prevent any other transaction from obtaining write access to any of those rows, or their dependants, until your transaction ends.
WITH LOCK
can only be used with a top-level, single-table SELECT
statement.
It is not available:
-
in a subquery specification
-
for joined sets
-
with the
DISTINCT
operator, aGROUP BY
clause or any other aggregating operation -
with a view
-
with the output of a selectable stored procedure
-
with an external table
-
with a
UNION
query
As the engine considers, in turn, each record falling under an explicit lock statement, it returns either the record version that is the most currently committed, regardless of database state when the statement was submitted, or an exception.
Wait behaviour and conflict reporting depend on the transaction parameters specified in the TPB block:
TPB mode | Behaviour |
---|---|
isc_tpb_consistency |
Explicit locks are overridden by implicit or explicit table-level locks and are ignored. |
isc_tpb_concurrency + isc_tpb_nowait |
If a record is modified by any transaction that was committed since the transaction attempting to get explicit lock started, or an active transaction has performed a modification of this record, an update conflict exception is raised immediately. |
isc_tpb_concurrency + isc_tpb_wait |
If the record is modified by any transaction that has committed since the transaction attempting to get explicit lock started, an update conflict exception is raised immediately. If an active transaction is holding ownership on this record (via explicit locking or by a normal optimistic write-lock) the transaction attempting the explicit lock waits for the outcome of the blocking transaction and, when it finishes, attempts to get the lock on the record again. This means that, if the blocking transaction committed a modified version of this record, an update conflict exception will be raised. |
isc_tpb_read_committed + isc_tpb_nowait |
If there is an active transaction holding ownership on this record (via explicit locking or normal update), an update conflict exception is raised immediately. |
isc_tpb_read_committed + isc_tpb_wait |
If there is an active transaction holding ownership on this record (via explicit locking or by a normal optimistic write-lock), the transaction attempting the explicit lock waits for the outcome of blocking transaction and when it finishes, attempts to get the lock on the record again. Update conflict exceptions can never be raised by an explicit lock statement in this TPB mode. |
Usage with a FOR UPDATE
Clause
If the FOR UPDATE
sub-clause precedes the WITH LOCK
sub-clause, buffered fetches are suppressed.
Thus, the lock will be applied to each row, one by one, at the moment it is fetched.
It becomes possible, then, that a lock which appeared to succeed when requested will nevertheless fail subsequently, when an attempt is made to fetch a row which has become locked by another transaction in the meantime.
As an alternative, it may be possible in your access components to set the size of the fetch buffer to 1. This would enable you to process the currently-locked row before the next is fetched and locked, or to handle errors without rolling back your transaction. |
OF <column-names> This optional sub-clause does nothing at all. |
How the engine deals with WITH LOCK
When an UPDATE
statement tries to access a record that is locked by another transaction, it either raises an update conflict exception or waits for the locking transaction to finish, depending on TPB mode.
Engine behaviour here is the same as if this record had already been modified by the locking transaction.
No special gdscodes are returned from conflicts involving pessimistic locks.
The engine guarantees that all records returned by an explicit lock statement are actually locked and do meet the search conditions specified in WHERE
clause, as long as the search conditions do not depend on any other tables, via joins, subqueries, etc.
It also guarantees that rows not meeting the search conditions will not be locked by the statement.
It can not guarantee that there are no rows which, though meeting the search conditions, are not locked.
This situation can arise if other, parallel transactions commit their changes during the course of the locking statement’s execution. |
The engine locks rows at fetch time. This has important consequences if you lock several rows at once. Many access methods for Firebird databases default to fetching output in packets of a few hundred rows (“buffered fetches”). Most data access components cannot bring you the rows contained in the last-fetched packet, where an error occurred.
Caveats using WITH LOCK
-
Rolling back of an implicit or explicit savepoint releases record locks that were taken under that savepoint, but it doesn’t notify waiting transactions. Applications should not depend on this behaviour as it may get changed in the future.
-
While explicit locks can be used to prevent and/or handle unusual update conflict errors, the volume of deadlock errors will grow unless you design your locking strategy carefully and control it rigorously.
-
Most applications do not need explicit locks at all. The main purposes of explicit locks are:
-
to prevent expensive handling of update conflict errors in heavily loaded applications, and
-
to maintain integrity of objects mapped to a relational database in a clustered environment.
If your use of explicit locking doesn’t fall in one of these two categories, then it’s the wrong way to do the task in Firebird.
-
-
Explicit locking is an advanced feature; do not misuse it! While solutions for these kinds of problems may be very important for web sites handling thousands of concurrent writers, or for ERP/CRM systems operating in large corporations, most application programs do not need to work in such conditions.
6.1.13. INTO
Passing SELECT
output into variables
PSQL
In PSQL code (triggers, stored procedures and executable blocks), the results of a SELECT
statement can be loaded row-by-row into local variables.
It is often the only way to do anything with the returned values at all.
The number, order and types of the variables must match the columns in the output row.
A “plain” SELECT
statement can only be used in PSQL if it returns at most one row, i.e., if it is a singleton select.
For multi-row selects, PSQL provides the FOR SELECT
loop construct, discussed later in the PSQL chapter.
PSQL also supports the DECLARE CURSOR
statement, which binds a named cursor to a SELECT
statement.
The cursor can then be used to walk the result set.
In PSQL the INTO
clause is placed at the very end of the SELECT
statement.
SELECT [...] <column-list> FROM ... [...] [INTO <variable-list>] <variable-list> ::= [:]psqlvar [, [:]psqlvar ...]
The colon prefix before local variable names in PSQL is optional in the |
Examples
Selecting some aggregated values and passing them into previously declared variables min_amt
, avg_amt
and max_amt
:
select min(amount), avg(cast(amount as float)), max(amount)
from orders
where artno = 372218
into min_amt, avg_amt, max_amt;
The |
A PSQL trigger that retrieves two values as a BLOB
field (using the LIST()
function) and assigns it INTO
a third field:
select list(name, ', ')
from persons p
where p.id in (new.father, new.mother)
into new.parentnames;
6.1.14. Common Table Expressions (“WITH … AS … SELECT
”)
DSQL, PSQL
A common table expression or CTE can be described as a virtual table or view, defined in a preamble to a main query, and going out of scope after the main query’s execution. The main query can reference any CTEs defined in the preamble as if they were regular tables or views. CTEs can be recursive, i.e. self-referencing, but they cannot be nested.
<cte-construct> ::= <cte-defs> <main-query> <cte-defs> ::= WITH [RECURSIVE] <cte> [, <cte> ...] <cte> ::= name [(<column-list>)] AS (<cte-stmt>) <column-list> ::= column-alias [, column-alias ...]
Argument | Description |
---|---|
cte-stmt |
Any |
main-query |
The main |
name |
Alias for a table expression |
column-alias |
Alias for a column in a table expression |
with dept_year_budget as (
select fiscal_year,
dept_no,
sum(projected_budget) as budget
from proj_dept_budget
group by fiscal_year, dept_no
)
select d.dept_no,
d.department,
dyb_2008.budget as budget_08,
dyb_2009.budget as budget_09
from department d
left join dept_year_budget dyb_2008
on d.dept_no = dyb_2008.dept_no
and dyb_2008.fiscal_year = 2008
left join dept_year_budget dyb_2009
on d.dept_no = dyb_2009.dept_no
and dyb_2009.fiscal_year = 2009
where exists (
select * from proj_dept_budget b
where d.dept_no = b.dept_no
);
CTE Notes
-
A CTE definition can contain any legal
SELECT
statement, as long as it doesn’t have a “WITH…
” preamble of its own (no nesting). -
CTEs defined for the same main query can reference each other, but care should be taken to avoid loops.
-
CTEs can be referenced from anywhere in the main query.
-
Each CTE can be referenced multiple times in the main query, using different aliases if necessary.
-
When enclosed in parentheses, CTE constructs can be used as subqueries in
SELECT
statements, but also inUPDATE
s,MERGE
s etc. -
In PSQL, CTEs are also supported in
FOR
loop headers:for with my_rivers as (select * from rivers where owner = 'me') select name, length from my_rivers into :rname, :rlen do begin .. end
If a CTE is declared, it must be used later: otherwise, you will get an error like this: ‘CTE "AAA" is not used in query’. |
Recursive CTEs
A recursive (self-referencing) CTE is a UNION
which must have at least one non-recursive member, called the anchor.
The non-recursive member(s) must be placed before the recursive member(s).
Recursive members are linked to each other and to their non-recursive neighbour by UNION ALL
operators.
The unions between non-recursive members may be of any type.
Recursive CTEs require the RECURSIVE
keyword to be present right after WITH
.
Each recursive union member may reference itself only once, and it must do so in a FROM
clause.
A great benefit of recursive CTEs is that they use far less memory and CPU cycles than an equivalent recursive stored procedure.
Execution Pattern
The execution pattern of a recursive CTE is as follows:
-
The engine begins execution from a non-recursive member.
-
For each row evaluated, it starts executing each recursive member one by one, using the current values from the outer row as parameters.
-
If the currently executing instance of a recursive member produces no rows, execution loops back one level and gets the next row from the outer result set.
WITH RECURSIVE DEPT_YEAR_BUDGET AS (
SELECT
FISCAL_YEAR,
DEPT_NO,
SUM(PROJECTED_BUDGET) BUDGET
FROM PROJ_DEPT_BUDGET
GROUP BY FISCAL_YEAR, DEPT_NO
),
DEPT_TREE AS (
SELECT
DEPT_NO,
HEAD_DEPT,
DEPARTMENT,
CAST('' AS VARCHAR(255)) AS INDENT
FROM DEPARTMENT
WHERE HEAD_DEPT IS NULL
UNION ALL
SELECT
D.DEPT_NO,
D.HEAD_DEPT,
D.DEPARTMENT,
H.INDENT || ' '
FROM DEPARTMENT D
JOIN DEPT_TREE H ON H.HEAD_DEPT = D.DEPT_NO
)
SELECT
D.DEPT_NO,
D.INDENT || D.DEPARTMENT DEPARTMENT,
DYB_2008.BUDGET AS BUDGET_08,
DYB_2009.BUDGET AS BUDGET_09
FROM DEPT_TREE D
LEFT JOIN DEPT_YEAR_BUDGET DYB_2008 ON
(D.DEPT_NO = DYB_2008.DEPT_NO) AND
(DYB_2008.FISCAL_YEAR = 2008)
LEFT JOIN DEPT_YEAR_BUDGET DYB_2009 ON
(D.DEPT_NO = DYB_2009.DEPT_NO) AND
(DYB_2009.FISCAL_YEAR = 2009);
The next example returns the pedigree of a horse. The main difference is that recursion occurs simultaneously in two branches of the pedigree.
WITH RECURSIVE PEDIGREE (
CODE_HORSE,
CODE_FATHER,
CODE_MOTHER,
NAME,
MARK,
DEPTH)
AS (SELECT
HORSE.CODE_HORSE,
HORSE.CODE_FATHER,
HORSE.CODE_MOTHER,
HORSE.NAME,
CAST('' AS VARCHAR(80)),
0
FROM
HORSE
WHERE
HORSE.CODE_HORSE = :CODE_HORSE
UNION ALL
SELECT
HORSE.CODE_HORSE,
HORSE.CODE_FATHER,
HORSE.CODE_MOTHER,
HORSE.NAME,
'F' || PEDIGREE.MARK,
PEDIGREE.DEPTH + 1
FROM
HORSE
JOIN PEDIGREE
ON HORSE.CODE_HORSE = PEDIGREE.CODE_FATHER
WHERE
PEDIGREE.DEPTH < :MAX_DEPTH
UNION ALL
SELECT
HORSE.CODE_HORSE,
HORSE.CODE_FATHER,
HORSE.CODE_MOTHER,
HORSE.NAME,
'M' || PEDIGREE.MARK,
PEDIGREE.DEPTH + 1
FROM
HORSE
JOIN PEDIGREE
ON HORSE.CODE_HORSE = PEDIGREE.CODE_MOTHER
WHERE
PEDIGREE.DEPTH < :MAX_DEPTH
)
SELECT
CODE_HORSE,
NAME,
MARK,
DEPTH
FROM
PEDIGREE
-
Aggregates (
DISTINCT
,GROUP BY
,HAVING
) and aggregate functions (SUM
,COUNT
,MAX
etc) are not allowed in recursive union members. -
A recursive reference cannot participate in an outer join.
-
The maximum recursion depth is 1024.
6.2. INSERT
Inserting rows of data into a table
DSQL, ESQL, PSQL
INSERT INTO target {DEFAULT VALUES | [(<column_list>)] <value_source>} [RETURNING <returning_list> [INTO <variables>]] <column_list> ::= colname [, colname ...] <value_source> ::= VALUES (<value_list>) | <select_stmt> <value_list> ::= <value> [, <value> ...] <returning_list> ::= <ret_value> [, <ret_value> ...] <ret_value> ::= colname | <value> <variables> ::= [:]varname [, [:]varname ...]
Argument | Description |
---|---|
target |
The name of the table or view to which a new row, or batch of rows, should be added |
colname |
Column in the table or view |
value |
An expression whose value is used for inserting into the table or for returning |
ret_value |
The expression to be returned in the |
varname |
Name of a PSQL local variable |
The INSERT
statement is used to add rows to a table or to one or more tables underlying a view:
-
If the column values are supplied in a
VALUES
clause, exactly one row is inserted -
The values may be provided instead by a
SELECT
expression, in which case zero to many rows may be inserted -
With the
DEFAULT VALUES
clause, no values are provided at all and exactly one row is inserted.
Restrictions
|
ALERT :
BEFORE INSERT TriggersRegardless of the method used for inserting rows, be mindful of any columns in the target table or view that are populated by |
6.2.1. INSERT … VALUES
The VALUES
list must provide a value for every column in the column list, in the same order and of the correct type.
The column list need not specify every column in the target but, if the column list is absent, the engine requires a value for every column in the table or view (computed columns excluded).
Introducer syntax provides a way to identify the character set of a value that is a string constant (literal). Introducer syntax works only with literal strings: it cannot be applied to string variables, parameters, column references or values that are expressions. |
INSERT INTO cars (make, model, year)
VALUES ('Ford', 'T', 1908);
INSERT INTO cars
VALUES ('Ford', 'T', 1908, 'USA', 850);
-- notice the '_' prefix (introducer syntax)
INSERT INTO People
VALUES (_ISO8859_1 'Hans-Jörg Schäfer');
6.2.2. INSERT … SELECT
For this method of inserting, the output columns of the SELECT
statement must provide a value for every target column in the column list, in the same order and of the correct type.
Literal values, context variables or expressions of compatible type can be substituted for any column in the source row.
In this case, a source column list and a corresponding VALUES
list are required.
If the column list is absent — as it is when SELECT *
is used for the source expression — the column_list must contain the names of every column in the target table or view (computed columns excluded).
INSERT INTO cars (make, model, year)
SELECT make, model, year
FROM new_cars;
INSERT INTO cars
SELECT * FROM new_cars;
INSERT INTO Members (number, name)
SELECT number, name FROM NewMembers
WHERE Accepted = 1
UNION ALL
SELECT number, name FROM SuspendedMembers
WHERE Vindicated = 1
INSERT INTO numbers(num)
WITH RECURSIVE r(n) as (
SELECT 1 FROM rdb$database
UNION ALL
SELECT n+1 FROM r WHERE n < 100
)
SELECT n FROM r
Of course, the column names in the source table need not be the same as those in the target table.
Any type of SELECT
statement is permitted, as long as its output columns exactly match the insert columns in number, order and type.
Types need not be exactly the same, but they must be assignment-compatible.
The “Unstable Cursor” Problem
In Firebird, up to and including Firebird 2.5, it is necessary to be aware of an implementation fault that affects this style of inserts when the objective is to duplicate rows in the same table. For example,
INSERT INTO T
SELECT * FROM T;
known affectionately as the “infinite insertion loop”, will continuously select rows and insert them, over and over, until the system runs out of storage space.
This is a quirk that affects all data-changing DML operations, with a variety of effects. It happens because, in the execution layers, DML statements use implicit cursors for performing the operations. Thus, using our simple example, execution works as follows:
FOR SELECT <values> FROM T INTO <tmp_vars>
DO
INSERT INTO T VALUES (<tmp_vars>);
The implementation results in behaviour that does not accord with the SQL standards. Future versions of Firebird will comply with the standard.
6.2.3. INSERT … DEFAULT VALUES
The DEFAULT VALUES
clause allows insertion of a record without providing any values at all, either directly or from a SELECT
statement.
This is only possible if every NOT NULL
or CHECK
ed column in the table either has a valid default declared or gets such a value from a BEFORE INSERT
trigger.
Furthermore, triggers providing required field values must not depend on the presence of input values.
INSERT INTO journal
DEFAULT VALUES
RETURNING entry_id;
6.2.4. The RETURNING
clause
An INSERT
statement adding at most one row may optionally include a RETURNING
clause in order to return values from the inserted row.
The clause, if present, need not contain all of the insert columns and may also contain other columns or expressions.
The returned values reflect any changes that may have been made in BEFORE INSERT
triggers.
ALERT : Multiple
INSERT sIn DSQL, a statement with |
INSERT INTO Scholars (
firstname,
lastname,
address,
phone,
email)
VALUES (
'Henry',
'Higgins',
'27A Wimpole Street',
'3231212',
NULL)
RETURNING lastname, fullname, id;
INSERT INTO Dumbbells (firstname, lastname, iq)
SELECT fname, lname, iq
FROM Friends
ORDER BY iq ROWS 1
RETURNING id, firstname, iq
INTO :id, :fname, :iq;
-
RETURNING
is only supported forVALUES
inserts and singletonSELECT
inserts. -
In DSQL, a statement with a
RETURNING
clause always returns exactly one row. If no record was actually inserted, the fields in this row are allNULL
. This behaviour may change in a later version of Firebird. In PSQL, if no row was inserted, nothing is returned, and the target variables keep their existing values.
6.2.5. Inserting into BLOB
columns
Inserting into BLOB
columns is only possible under the following circumstances:
-
The client application has made special provisions for such inserts, using the Firebird API. In this case, the modus operandi is application-specific and outside the scope of this manual.
-
The value inserted is a text string of no more than 32767 bytes.
If the value is not a string literal, beware of concatenations, as the output from the expression may exceed the maximum length.
-
You are using the “
INSERT … SELECT
” form and one or more columns in the result set areBLOB
s.
6.3. UPDATE
Modifying rows in tables and views
DSQL, ESQL, PSQL
UPDATE target [[AS] alias] SET col = <value> [, col = <value> ...] [WHERE {<search-conditions> | CURRENT OF cursorname}] [PLAN <plan_items>] [ORDER BY <sort_items>] [ROWS m [TO n]] [RETURNING <returning_list> [INTO <variables>]] <returning_list> ::= <ret_value> [, <ret_value> ...] <ret_value> ::= colname | NEW.colname | OLD.colname | <value> <variables> ::= [:]varname [, [:]varname ...]
Argument | Description |
---|---|
target |
The name of the table or view where the records are updated |
alias |
Alias for the table or view |
col |
Name or alias of a column in the table or view |
value |
Expression for the new value for a column that is to be updated in the table or view by the statement, or a value to be returned |
search-conditions |
A search condition limiting the set of the rows to be updated |
cursorname |
The name of the cursor through which the row[s] to be updated are positioned |
plan_items |
Clauses in the query plan |
sort_items |
Columns listed in an |
m, n |
Integer expressions for limiting the number of rows to be updated |
ret_value |
A value to be returned in the |
varname |
Name of a PSQL local variable |
The UPDATE
statement changes values in a table or in one or more of the tables that underlie a view.
The columns affected are specified in the SET
clause.
The rows affected may be limited by the WHERE
and ROWS
clauses.
If neither WHERE
nor ROWS
is present, all the records in the table will be updated.
6.3.1. Using an alias
If you assign an alias to a table or a view, the alias must be used when specifying columns and also in any column references included in other clauses.
Correct usage:
update Fruit set soort = 'pisang' where ...
update Fruit set Fruit.soort = 'pisang' where ...
update Fruit F set soort = 'pisang' where ...
update Fruit F set F.soort = 'pisang' where ...
Not possible:
update Fruit F set Fruit.soort = 'pisang' where ...
6.3.2. The SET
Clause
In the SET
clause, the assignment phrases, containing the columns with the values to be set, are separated by commas.
In an assignment phrase, column names are on the left and the values or expressions containing the assignment values are on the right.
A column may be included only once in the SET
clause.
A column name can be used in expressions on the right.
The old value of the column will always be used in these right-side values, even if the column was already assigned a new value earlier in the SET
clause.
Data in the TSET
table:
A B
---
1 0
2 0
The statement:
UPDATE tset SET a = 5, b = a;
will change the values to:
A B
---
5 1
5 2
Notice that the old values (1 and 2) are used to update the b column even after the column was assigned a new value (5).
It was not always like that. Before version 2.5, columns got their new values immediately upon assignment. It was non-standard behaviour that was fixed in version 2.5. To maintain compatibility with legacy code, the configuration file |
6.3.3. The WHERE
Clause
The WHERE
clause sets the conditions that limit the set of records for a searched update.
In PSQL, if a named cursor is being used for updating a set, using the WHERE CURRENT OF
clause, the action is limited to the row where the cursor is currently positioned.
This is a positioned update.
The |
UPDATE People
SET firstname = 'Boris'
WHERE lastname = 'Johnson';
UPDATE employee e
SET salary = salary * 1.05
WHERE EXISTS(
SELECT *
FROM employee_project ep
WHERE e.emp_no = ep.emp_no);
UPDATE addresses
SET city = 'Saint Petersburg', citycode = 'PET'
WHERE city = 'Leningrad'
UPDATE employees
SET salary = 2.5 * salary
WHERE title = 'CEO'
For string literals with which the parser needs help to interpret the character set of the data, the introducer syntax may be used. The string literal is preceded by the character set name, prefixed with an underscore character:
-- notice the '_' prefix
UPDATE People
SET name = _ISO8859_1 'Hans-Jörg Schäfer'
WHERE id = 53662;
The “Unstable Cursor” Problem
In Firebird, up to and including Firebird 2.5, it is necessary to be aware of an implementation fault that affects updates when the WHERE
conditions use the IN (<select-expr>)
and the select-expr is of the form SELECT FIRST n
or SELECT … ROWS
.
For example:
UPDATE T
SET ...
WHERE ID IN (SELECT FIRST 1 ID FROM T);
known affectionately as the “infinite update loop”, will continuously update rows, over and over, and give the impression that the server has hung.
Quirks like this can affect any data-changing DML operations, most often when the selection conditions involve a subquery. Cases have been reported where sort order interferes with expectations, without involving a subquery. It happens because, in the execution layers, instead of establishing a stable “target set” and then executing the data changes to each set member, DML statements use implicit cursors for performing the operations on whatever row currently meets the conditions, without knowledge of whether that row formerly failed the condition or was updated already. Thus, using a simple example pattern:
UPDATE T SET <fields> = <values> WHERE <conditions>
the execution works as:
FOR SELECT <values> FROM T WHERE <conditions> INTO <tmp_vars> AS CURSOR <cursor> DO UPDATE T SET <fields> = <tmp_vars> WHERE CURRENT OF <cursor>
Firebird’s implementation does not accord with the SQL standards, which require that a stable set be established before any data are changed. Firebird 3 and higher will comply with the standard.
6.3.4. The ORDER BY
and ROWS
Clauses
The ORDER BY
and ROWS
clauses make sense only when used together.
However, they can be used separately.
If ROWS
has one argument, m, the rows to be updated will be limited to the first m rows.
-
If m > the number of rows being processed, the entire set of rows is updated
-
If m = 0, no rows are updated
-
If m < 0, an error occurs and the update fails
If two arguments are used, m and n, ROWS
limits the rows being updated to rows from m to n inclusively.
Both arguments are integers and start from 1.
-
If m > the number of rows being processed, no rows are updated
-
If n > the number of rows, rows from m to the end of the set are updated
-
If m < 1 or n < 1, an error occurs and the update fails
-
If n = m - 1, no rows are updated
-
If n < m -1, an error occurs and the update fails
UPDATE employees
SET salary = salary + 50
ORDER BY salary ASC
ROWS 20;
6.3.5. The RETURNING
Clause
An UPDATE
statement involving at most one row may include RETURNING
in order to return some values from the row being updated.
RETURNING
may include data from any column of the row, not necessarily the columns that are currently being updated.
It can include literals or expressions not associated with columns, if there is a need for that.
When the RETURNING
set contains data from the current row, the returned values report changes made in the BEFORE UPDATE
triggers, but not those made in AFTER UPDATE
triggers.
The context variables OLD.fieldname
and NEW.fieldname
can be used as column names.
If OLD.
or NEW.
is not specified, the column values returned are the NEW.
ones.
In DSQL, a statement with RETURNING
always returns a single row.
If the statement updates no records, the returned values contain NULL
.
This behaviour may change in future Firebird versions.
The INTO
Sub-clause
In PSQL, the INTO
clause can be used to pass the returning values to local variables.
It is not available in DSQL.
If no records are updated, nothing is returned and variables specified in RETURNING
will keep their previous values.
When a value is returned and assigned to a
and this is valid:
|
6.3.6. Updating BLOB
columns
Updating a BLOB
column always replaces the entire contents.
Even the BLOB
ID, the “handle” that is stored directly in the column, is changed.
BLOB
s can be updated if:
-
The client application has made special provisions for this operation, using the Firebird API. In this case, the modus operandi is application-specific and outside the scope of this manual.
-
The new value is a text string of at most 32767 bytes. Please notice: if the value is not a string literal, beware of concatenations, as these may exceed the maximum length.
-
The source is itself a
BLOB
column or, more generally, an expression that returns aBLOB
. -
You use the
INSERT CURSOR
statement (ESQL only).
6.4. UPDATE OR INSERT
Updating an existing record in a table or, if it does not exist, inserting it
DSQL, PSQL
UPDATE OR INSERT INTO target [(<column_list>)] VALUES (<value_list>) [MATCHING (<column_list>)] [RETURNING <values> [INTO <variables>]] <column_list> ::= colname [, colname ...] <value_list> ::= <value> [, <value> ...] <returning_list> ::= <ret_value> [, <ret_value> ...] <ret_value> ::= colname | NEW.colname | OLD.colname | <value> <variables> ::= [:]varname [, [:]varname ...]
Argument | Description |
---|---|
target |
The name of the table or view where the record[s] is to be updated or a new record inserted |
colname |
Name of a column in the table or view |
value |
An expression whose value is to be used for inserting or updating the table, or returning a value |
ret_value |
An expression returned in the RETURNING clause |
varname |
Variable name — PSQL only |
UPDATE OR INSERT
inserts a new record or updates one or more existing records.
The action taken depends on the values provided for the columns in the MATCHING
clause (or, if the latter is absent, in the primary key).
If there are records found matching those values, they are updated.
If not, a new record is inserted.
A match only counts if all the values in the MATCHING
or primary key columns are equal.
Matching is done with the IS NOT DISTINCT
operator, so one NULL
matches another.
Restrictions
|
6.4.1. The RETURNING
clause
The optional RETURNING
clause, if present, need not contain all the columns mentioned in the statement and may also contain other columns or expressions.
The returned values reflect any changes that may have been made in BEFORE
triggers, but not those in AFTER
triggers. OLD.fieldname
and NEW.fieldname
may both be used in the list of columns to return;
for field names not preceded by either of these, the new value is returned.
In DSQL, a statement with a RETURNING
clause always returns exactly one row.
If a RETURNING
clause is present and more than one matching record is found, an error is raised.
This behaviour may change in a later version of Firebird.
6.4.2. Example of UPDATE OR INSERT
Modifying data in a table, using UPDATE OR INSERT
in a PSQL module.
The return value is passed to a local variable, whose colon prefix is optional.
UPDATE OR INSERT INTO Cows (Name, Number, Location)
VALUES ('Suzy Creamcheese', 3278823, 'Green Pastures')
MATCHING (Number)
RETURNING rec_id into :id;
The “Unstable Cursor” Problem
Because of the way the execution of data-changing DML is implemented in Firebird, up to and including Firebird 2.5, the sets targeted for updating sometimes produce unexpected results.
For more information, refer to The Unstable Cursor Problem in the |
6.5. DELETE
Deleting rows from a table or view
DSQL, ESQL, PSQL
DELETE FROM target [[AS] alias] [WHERE {<search-conditions> | CURRENT OF cursorname}] [PLAN <plan_items>] [ORDER BY <sort_items>] [ROWS m [TO n]] [RETURNING <returning_list> [INTO <variables>]] <returning_list> ::= <ret_value> [, <ret_value> ...] <ret_value> ::= colname | <value> <variables> ::= [:]varname [, [:]varname ...]
Argument | Description |
---|---|
target |
The name of the table or view from which the records are to be deleted |
alias |
Alias for the target table or view |
search-conditions |
Search condition limiting the set of rows being targeted for deletion |
cursorname |
The name of the cursor in which current record is positioned for deletion |
plan_items |
Query plan clause |
sort_items |
|
m, n |
Integer expressions for limiting the number of rows being deleted |
ret_value |
An expression to be returned in the |
value |
An expression whose value is used for returning |
varname |
Name of a PSQL variable |
DELETE
removes rows from a database table or from one or more of the tables that underlie a view. WHERE
and ROWS
clauses can limit the number of rows deleted.
If neither WHERE
nor ROWS
is present, DELETE
removes all the rows in the relation.
6.5.1. Aliases
If an alias is specified for the target table or view, it must be used to qualify all field name references in the DELETE
statement.
Supported usage:
delete from Cities where name starting 'Alex';
delete from Cities where Cities.name starting 'Alex';
delete from Cities C where name starting 'Alex';
delete from Cities C where C.name starting 'Alex';
Not possible:
delete from Cities C where Cities.name starting 'Alex';
6.5.2. WHERE
The WHERE
clause sets the conditions that limit the set of records for a searched delete.
In PSQL, if a named cursor is being used for deleting a set, using the WHERE CURRENT OF
clause, the action is limited to the row where the cursor is currently positioned.
This is a positioned delete.
The |
DELETE FROM People
WHERE firstname <> 'Boris' AND lastname <> 'Johnson';
DELETE FROM employee e
WHERE NOT EXISTS(
SELECT *
FROM employee_project ep
WHERE e.emp_no = ep.emp_no);
DELETE FROM Cities
WHERE CURRENT OF Cur_Cities; -- ESQL and PSQL only
6.5.3. PLAN
A PLAN
clause allows the user to optimize the operation manually.
DELETE FROM Submissions
WHERE date_entered < '1-Jan-2002'
PLAN (Submissions INDEX ix_subm_date);
6.5.4. ORDER BY
and ROWS
The ORDER BY
clause orders the set before the actual deletion takes place.
It only makes sense in combination with ROWS
, but is also valid without it.
The ROWS
clause limits the number of rows being deleted.
Integer literals or any integer expressions can be used for the arguments m and n.
If ROWS
has one argument, m, the rows to be deleted will be limited to the first m rows.
-
If m > the number of rows being processed, the entire set of rows is deleted
-
If m = 0, no rows are deleted
-
If m < 0, an error occurs and the deletion fails
If two arguments are used, m and n, ROWS
limits the rows being deleted to rows from m to n inclusively.
Both arguments are integers and start from 1.
-
If m > the number of rows being processed, no rows are deleted
-
If m > 0 and <= the number of rows in the set and n is outside these values, rows from m to the end of the set are deleted
-
If m < 1 or n < 1, an error occurs and the deletion fails
-
If n = m - 1, no rows are deleted
-
If n < m -1, an error occurs and the deletion fails
Deleting the oldest purchase:
DELETE FROM Purchases
ORDER BY date ROWS 1;
Deleting the highest custno(s):
DELETE FROM Sales
ORDER BY custno DESC ROWS 1 to 10;
Deleting all sales, ORDER BY
clause pointless:
DELETE FROM Sales
ORDER BY custno DESC;
Deleting one record starting from the end, i.e. from Z…:
DELETE FROM popgroups
ORDER BY name DESC ROWS 1;
Deleting the five oldest groups:
DELETE FROM popgroups
ORDER BY formed ROWS 5;
No sorting (ORDER BY
) is specified so 8 found records, starting from the fifth one, will be deleted:
DELETE FROM popgroups
ROWS 5 TO 12;
6.5.5. RETURNING
A DELETE
statement removing at most one row may optionally include a RETURNING
clause in order to return values from the deleted row.
The clause, if present, need not contain all the relation’s columns and may also contain other columns or expressions.
Notes
|
DELETE FROM Scholars
WHERE firstname = 'Henry' and lastname = 'Higgins'
RETURNING lastname, fullname, id;
DELETE FROM Dumbbells
ORDER BY iq DESC
ROWS 1
RETURNING lastname, iq into :lname, :iq;
The “Unstable Cursor” Problem
Because of the way the execution of data-changing DML is implemented in Firebird, up to and including this version, the sets targeted for deletion sometimes produce unexpected results.
For more information, refer to The Unstable Cursor Problem in the |
6.6. MERGE
Merging data from a source set into a target relation
DSQL, PSQL
MERGE INTO target [[AS] target-alias] USING <source> [[AS] source-alias] ON <join-condition> [ WHEN MATCHED THEN UPDATE SET colname = <value> [, <colname> = <value> ...]] [ WHEN NOT MATCHED THEN INSERT [(<columns>)] VALUES (<values>)] <source> ::= tablename | (<select-stmt>) <columns> ::= colname [, colname ...] <values> ::= <value> [, <value> ...]
Argument | Description |
---|---|
target |
Name of target relation (table or updatable view) |
source |
Data source. It can be a table, a view, a stored procedure or a derived table |
target-alias |
Alias for the target relation (table or updatable view) |
source-alias |
Alias for the source relation or set |
join-conditions |
The ( |
tablename |
Table or view name |
select-stmt |
Select statement of the derived table |
colname |
Name of a column in the target relation |
value |
The value assigned to a column in the target table. It is an expression that may be a literal value, a PSQL variable, a column from the source or a compatible context variable |
Description
The MERGE
statement merges data into a table or updatable view.
The source may be a table, view or “anything you can SELECT
from” in general.
Each source record will be used to update one or more target records, insert a new record in the target table, or neither.
The action taken depends on the supplied join condition and the WHEN
clause(s).
The condition will typically contain a comparison of fields in the source and target relations.
Notes
At least one
Currently, the |
ALERT : Another irregularity!
If the |
MERGE INTO books b
USING purchases p
ON p.title = b.title and p.type = 'bk'
WHEN MATCHED THEN
UPDATE SET b.desc = b.desc || '; ' || p.desc
WHEN NOT MATCHED THEN
INSERT (title, desc, bought) values (p.title, p.desc, p.bought);
MERGE INTO customers c
USING (SELECT * from customers_delta WHERE id > 10) cd
ON (c.id = cd.id)
WHEN MATCHED THEN
UPDATE SET name = cd.name
WHEN NOT MATCHED THEN
INSERT (id, name) values (cd.id, cd.name);
MERGE INTO numbers
USING (
WITH RECURSIVE r(n) AS (
SELECT 1 FROM rdb$database
UNION ALL
SELECT n+1 FROM r WHERE n < 200
)
SELECT n FROM r
) t
ON numbers.num = t.n
WHEN NOT MATCHED THEN
INSERT(num) VALUES(t.n);
The “Unstable Cursor” Problem
Because of the way the execution of data-changing DML is implemented in Firebird, up to and including this version, the sets targeted for merging sometimes produce unexpected results.
For more information, refer to The Unstable Cursor Problem in the |
6.7. EXECUTE PROCEDURE
Executing a stored procedure
DSQL, ESQL, PSQL
EXECUTE PROCEDURE procname [<inparam> [, <inparam> ...]] | [(<inparam> [, <inparam> ...])] [RETURNING_VALUES <outvar> [, <outvar> ...] | (<outvar> [, <outvar> ...])] <outvar> ::= [:]varname
Argument | Description |
---|---|
procname |
Name of the stored procedure |
inparam |
An expression evaluating to the declared data type of an input parameter |
varname |
A PSQL variable to receive the return value |
Executes an executable stored procedure, taking a list of one or more input parameters, if they are defined for the procedure, and returning a one-row set of output values, if they are defined for the procedure.
6.7.1. “Executable” Stored Procedure
The EXECUTE PROCEDURE
statement is most commonly used to invoke the style of stored procedure that is written to perform some data-modifying task at the server side — those that do not contain any SUSPEND
statements in their code.
They can be designed to return a result set, consisting of only one row, which is usually passed, via a set of RETURNING_VALUES()
variables, to another stored procedure that calls it.
Client interfaces usually have an API wrapper that can retrieve the output values into a single-row buffer when calling EXECUTE PROCEDURE
in DSQL.
Invoking the other style of stored procedure — a “selectable” one — is possible with EXECUTE PROCEDURE
but it returns only the first row of an output set which is almost surely designed to be multi-row.
Selectable stored procedures are designed to be invoked by a SELECT
statement, producing output that behaves like a virtual table.
Notes
|
6.7.2. Examples of EXECUTE PROCEDURE
In PSQL, with optional colons and without optional parentheses:
EXECUTE PROCEDURE MakeFullName
:FirstName, :MiddleName, :LastName
RETURNING_VALUES :FullName;
In Firebird’s command-line utility isql, with literal parameters and optional parentheses:
EXECUTE PROCEDURE MakeFullName ('J', 'Edgar', 'Hoover');
In isql, |
A PSQL example with expression parameters and optional parentheses:
EXECUTE PROCEDURE MakeFullName
('Mr./Mrs. ' || FirstName, MiddleName, upper(LastName))
RETURNING_VALUES (FullName);
6.8. EXECUTE BLOCK
Creating an “anonymous” block of PSQL code in DSQL for immediate execution
DSQL
EXECUTE BLOCK [(<inparams>)] [RETURNS (<outparams>)] AS [<declarations>] BEGIN [<PSQL statements>] END <inparams> ::= <param_decl> = ? [, <inparams> ] <outparams> ::= <param_decl> [, <outparams>] <param_decl> ::= paramname <type> [NOT NULL] [COLLATE collation] <type> ::= <datatype> | [TYPE OF] domain | TYPE OF COLUMN rel.col <datatype> ::= {SMALLINT | INTEGER | BIGINT} | {FLOAT | DOUBLE PRECISION} | {DATE | TIME | TIMESTAMP} | {DECIMAL | NUMERIC} [(precision [, scale])] | {CHAR | CHARACTER} [VARYING] | VARCHAR} [(size)] [CHARACTER SET charset] | {NCHAR | NATIONAL {CHARACTER | CHAR}} [VARYING] [(size)] | BLOB [SUB_TYPE {subtype_num | subtype_name}] [SEGMENT SIZE seglen] [CHARACTER SET charset] | BLOB [(seglen [, subtype_num])] <declarations> ::= <declare_item> [<declare_item> ...] <declare_item> ::= <declare_var>; | <declare_cursor>;
Argument | Description |
---|---|
param_decl |
Name and description of an input or output parameter |
declarations |
A section for declaring local variables and named cursors |
declare_var |
Local variable declaration |
declare_cursor |
Declaration of a named cursor |
paramname |
The name of an input or output parameter of the procedural block, up to 31 characters long. The name must be unique among input and output parameters and local variables in the block |
datatype |
SQL data type |
collation |
Collation sequence |
domain |
Domain |
rel |
Name of a table or view |
col |
Name of a column in a table or view |
precision |
Precision. From 1 to 18 |
scale |
Scale. From 0 to 18. It must be less than or equal to precision |
size |
The maximum size of a string, in characters |
charset |
Character set |
subtype_num |
|
subtype_name |
|
seglen |
Segment size, it cannot be greater than 65,535 |
Executes a block of PSQL code as if it were a stored procedure, optionally with input and output parameters and variable declarations. This allows the user to perform “on-the-fly” PSQL within a DSQL context.
This example injects the numbers 0 through 127 and their corresponding ASCII characters into the table ASCIITABLE
:
EXECUTE BLOCK
AS
declare i INT = 0;
BEGIN
WHILE (i < 128) DO
BEGIN
INSERT INTO AsciiTable VALUES (:i, ascii_char(:i));
i = i + 1;
END
END
The next example calculates the geometric mean of two numbers and returns it to the user:
EXECUTE BLOCK (x DOUBLE PRECISION = ?, y DOUBLE PRECISION = ?)
RETURNS (gmean DOUBLE PRECISION)
AS
BEGIN
gmean = SQRT(x*y);
SUSPEND;
END
Because this block has input parameters, it has to be prepared first.
Then the parameters can be set and the block executed.
It depends on the client software how this must be done and even if it is possible at all — see the notes below.
Our last example takes two integer values, smallest
and largest
.
For all the numbers in the range smallest
…largest
, the block outputs the number itself, its square, its cube and its fourth power.
EXECUTE BLOCK (smallest INT = ?, largest INT = ?)
RETURNS (number INT, square BIGINT, cube BIGINT, fourth BIGINT)
AS
BEGIN
number = smallest;
WHILE (number <= largest) DO
BEGIN
square = number * number;
cube = number * square;
fourth = number * cube;
SUSPEND;
number = number + 1;
END
END
Again, it depends on the client software if and how you can set the parameter values.
6.8.1. Input and output parameters
Executing a block without input parameters should be possible with every Firebird client that allows the user to enter his or her own DSQL statements.
If there are input parameters, things get trickier: these parameters must get their values after the statement is prepared but before it is executed.
This requires special provisions, which not every client application offers.
(Firebird’s own isql
, for one, doesn’t.)
The server only accepts question marks (“?
”) as placeholders for the input values, not “:a
”, “:MyParam
” etc., or literal values.
Client software may support the “:xxx
” form though, and will preprocess it before sending it to the server.
If the block has output parameters, you must use SUSPEND
or nothing will be returned.
Output is always returned in the form of a result set, just as with a SELECT
statement.
You can’t use RETURNING_VALUES
or execute the block INTO
some variables, even if there is only one result row.
6.8.2. Statement Terminators
Some SQL statement editors — specifically the isql utility that comes with Firebird and possibly some third-party editors — employ an internal convention that requires all statements to be terminated with a semi-colon. This creates a conflict with PSQL syntax when coding in these environments. If you are unacquainted with this problem and its solution, please study the details in the PSQL chapter in the section entitled Switching the Terminator in isql.
7. Procedural SQL (PSQL) Statements
Procedural SQL (PSQL) is a procedural extension of SQL. This language subset is used for writing stored procedures, triggers, and PSQL blocks.
PSQL provides all the basic constructs of traditional structured programming languages, and also includes DML statements (SELECT
, INSERT
, UPDATE
, DELETE
, etc.), with slight modifications to syntax in some cases.
7.1. Elements of PSQL
A procedural extension may contain declarations of local variables and cursors, assignments, conditional statements, loops, statements for raising custom exceptions, error handling and sending messages (events) to client applications.
Triggers have access to special context variables, two arrays that store, respectively, the NEW
values for all columns during insert and update activity, and the OLD
values during update and delete work.
Statements that modify metadata (DDL) are not available in PSQL.
7.1.1. DML Statements with Parameters
If DML statements (SELECT
, INSERT
, UPDATE
, DELETE
, etc.) in the body of the module (procedure, trigger or block) use parameters, only named parameters can be used and they must “exist” before the statements can use them.
They can be made available by being declared either as input or output parameters in the module’s header or as local variables, in DECLARE [VARIABLE]
statements at the bottom of the header.
When a DML statement with parameters is included in PSQL code, the parameter name must be prefixed by a colon (‘:
’) in most situations.
The colon is optional in statement syntax that is specific to PSQL, such as assignments and conditionals.
The colon prefix on parameters is not required when calling stored procedures from within another PSQL module or in DSQL.
7.1.2. Transactions
Stored procedures are executed in the context of the transaction in which they are called. Triggers are executed as an intrinsic part of the operation of the DML statement: thus, their execution is within the same transaction context as the statement itself. Individual transactions are launched for database event triggers.
Statements that start and end transactions are not available in PSQL, but it is possible to run a statement or a block of statements in an autonomous transaction.
7.1.3. Module Structure
PSQL code modules consist of a header and a body.
The DDL statements for defining them are complex statements;
that is, they consist of a single statement that encloses blocks of multiple statements.
These statements begin with a verb (CREATE
, ALTER
, DROP
, RECREATE
, CREATE OR ALTER
) and end with the last END
statement of the body.
The Module Header
The header provides the module name and defines any parameters and variables that are used in the body. Stored procedures and PSQL blocks may have input and output parameters. Triggers do not have either input or output parameters.
The header of a trigger indicates the database event (insert, update or delete, or a combination) and the phase of operation (BEFORE
or AFTER
that event) that will cause it to “fire”.
The Module Body
The body of a PSQL module is a block of statements that run in a logical sequence, like a program.
A block of statements is contained within a BEGIN
and an END
statement.
The main BEGIN…END
block may contain any number of other BEGIN…END
blocks, both embedded and sequential.
All statements except BEGIN
and END
are terminated by semicolons (‘;
’).
No other character is valid for use as a terminator for PSQL statements.
7.2. Stored Procedures
A stored procedure is a program stored in the database metadata for execution on the server. A stored procedure can be called by stored procedures (including itself), triggers and client applications. A procedure that calls itself is known as recursive.
7.2.1. Benefits of Stored Procedures
Stored procedures have the following advantages:
Modularity |
applications working with the database can use the same stored procedure, thereby reducing the size of the application code and avoiding code duplication. |
Simpler Application Support |
when a stored procedure is modified, changes appear immediately to all host applications, without the need to recompile them if the parameters were unchanged. |
Enhanced Performance |
since stored procedures are executed on a server instead of at the client, network traffic is reduced, which improves performance. |
7.2.2. Types of Stored Procedures
Firebird supports two types of stored procedures: executable and selectable.
Executable Procedures
Executable procedures usually modify data in a database.
They can receive input parameters and return a single set of output (RETURNS
) parameters.
They are called using the EXECUTE PROCEDURE
statement.
See an example of an executable stored procedure at the end of the CREATE PROCEDURE
section of Chapter 5.
Selectable Procedures
Selectable stored procedures usually retrieve data from a database, returning an arbitrary number of rows to the caller. The caller receives the output one row at a time from a row buffer that the database engine prepares for it.
Selectable procedures can be useful for obtaining complex sets of data that are often impossible or too difficult or too slow to retrieve using regular DSQL SELECT
queries.
Typically, this style of procedure iterates through a looping process of extracting data, perhaps transforming it before filling the output variables (parameters) with fresh data at each iteration of the loop.
A SUSPEND
statement at the end of the iteration fills the buffer and waits for the caller to fetch the row.
Execution of the next iteration of the loop begins when the buffer has been cleared.
Selectable procedures may have input parameters and the output set is specified by the RETURNS
clause in the header.
A selectable stored procedure is called with a SELECT statement.
See an example of a selectable stored procedure at the end of the CREATE PROCEDURE
section of Chapter 5.
7.2.3. Creating a Stored Procedure
The syntax for creating executable stored procedures and selectable stored procedures is exactly the same. The difference comes in the logic of the program code.
CREATE PROCEDURE procname [(<inparam> [, <inparam> ...])] [RETURNS (<outparam> [, <outparam> ...])] AS [<declarations>] BEGIN [<PSQL_statements>] END
The header of a stored procedure must contain the procedure name, and it must be unique among the names of stored procedures, tables, and views.
It may also define some input and output parameters.
Input parameters are listed after the procedure name inside a pair of brackets.
Output parameters, which are mandatory for selectable procedures, are bracketed inside one RETURNS
clause.
The final item in the header (or the first item in the body, depending on your opinion of where the border lies) is one or more declarations of any local variables and/or named cursors that your procedure might require.
Following the declarations is the main BEGIN…END
block that delineates the procedure’s PSQL code.
Within that block could be PSQL and DML statements, flow-of-control blocks, sequences of other BEGIN…END
blocks, including embedded blocks.
Blocks, including the main block, may be empty and the procedure will still compile.
It is not unusual to develop a procedure in stages, from an outline.
See CREATE PROCEDURE
in Chapter 5, Data Definition (DDL) Statements.
7.2.4. Modifying a Stored Procedure
An existing stored procedure can be altered, to change the sets of input and output parameters and anything in the procedure body.
ALTER PROCEDURE procname [(<inparam> [, <inparam> ...])] [RETURNS (<outparam> [, <outparam> ...])] AS [<declarations>] BEGIN [<PSQL_statements>] END
See ALTER PROCEDURE
, CREATE OR ALTER PROCEDURE
, RECREATE PROCEDURE
, in Chapter 5, Data Definition (DDL) Statements.
7.2.5. Deleting a Stored Procedure
The DROP PROCEDURE
statement is used to delete stored procedures.
DROP PROCEDURE procname
See DROP PROCEDURE
in Chapter 5, Data Definition (DDL) Statements.
7.3. Stored Functions
Stored PSQL scalar functions are not supported in this version but they are coming in Firebird 3.
In Firebird 2.5 and below, you can instead write a selectable stored procedure that returns a scalar result and SELECT
it from your DML query or subquery.
SELECT
PSQL_FUNC(T.col1, T.col2) AS col3,
col3
FROM T
can be replaced with:
SELECT
(SELECT output_column FROM PSQL_PROC(T.col1)) AS col3,
col2
FROM T
or
SELECT
output_column AS col3,
col2,
FROM T
LEFT JOIN PSQL_PROC(T.col1)
7.4. PSQL Blocks
A self-contained, unnamed (“anonymous”) block of PSQL code can be executed dynamically in DSQL, using the EXECUTE BLOCK
syntax.
The header of an anonymous PSQL block may optionally contain input and output parameters.
The body may contain local variable and cursor declarations;
and a block of PSQL statements follows.
An anonymous PSQL block is not defined and stored as an object, unlike stored procedures and triggers. It executes in run-time and cannot reference itself.
Just like stored procedures, anonymous PSQL blocks can be used to process data and to retrieve data from the database.
EXECUTE BLOCK [(<inparam> = ? [, <inparam> = ? ...])] [RETURNS (<outparam> [, <outparam> ...])] AS [<declarations>] BEGIN [<PSQL_statements>] END
Argument | Description |
---|---|
inparam |
Input parameter description |
outparam |
Output parameter description |
declarations |
A section for declaring local variables and named cursors |
PSQL statements |
PSQL and DML statements |
See EXECUTE BLOCK
for details.
7.5. Triggers
A trigger is another form of executable code that is stored in the metadata of the database for execution by the server. A trigger cannot be called directly. It is called automatically (“fired”) when data-changing events involving one particular table or view occur.
One trigger applies to exactly one table or view and only one phase in an event (BEFORE
or AFTER
the event).
A single trigger might be written to fire only when one specific data-changing event occurs (INSERT
, UPDATE
or DELETE
) or it might be written to apply to more than one of those.
A DML trigger is executed in the context of the transaction in which the data-changing DML statement is running. For triggers that respond to database events, the rule is different: for some of them, a default transaction is started.
7.5.1. Firing Order (Order of Execution)
More than one trigger can be defined for each phase-event combination.
The order in which they are executed (known as “firing order” can be specified explicitly with the optional POSITION
argument in the trigger definition.
You have 32,767 numbers to choose from.
Triggers with the lowest position numbers fire first.
If a POSITION
clause is omitted, the position is 0
.
If multiple triggers have the same position and phase, those triggers will be executed in an undefined order, while respecting the total order by position and phase.
7.5.2. DML Triggers
DML triggers are those that fire when a DML operation changes the state of data: modifies rows in tables, inserts new rows or deletes rows. They can be defined for both tables and views.
Trigger Options
Six base options are available for the event-phase combination for tables and views:
Before a new row is inserted |
|
After a new row is inserted |
|
Before a row is updated |
|
After a row is updated |
|
Before a row is deleted |
|
After a row is deleted |
|
These base forms are for creating single phase/single-event triggers.
Firebird also supports forms for creating triggers for one phase and multiple-events, BEFORE INSERT OR UPDATE OR DELETE
, for example, or AFTER UPDATE OR DELETE
: the combinations are your choice.
“Multi-phase” triggers, such as |
OLD
and NEW
Context Variables
For DML triggers, the Firebird engine provides access to sets of OLD
and NEW
context variables.
Each is an array of the values of the entire row: one for the values as they are before the data-changing event (the BEFORE
phase) and one for the values as they will be after the event (the AFTER
phase).
They are referenced in statements using the form NEW.column_name
and OLD.column_name
, respectively.
The column_name can be any column in the table’s definition, not just those that are being updated.
The NEW
and OLD
variables are subject to some rules:
-
In all triggers, the
OLD
value is read-only -
In
BEFORE UPDATE
andBEFORE INSERT
code, theNEW
value is read/write, unless it is aCOMPUTED BY
column -
In
INSERT
triggers, references to theOLD
variables are invalid and will throw an exception -
In
DELETE
triggers, references to theNEW
variables are invalid and will throw an exception -
In all
AFTER
trigger code, theNEW
variables are read-only
7.5.3. Database Triggers
A trigger associated with a database or transaction event can be defined for the following events:
Connecting to a database |
|
Before the trigger is executed, a default transaction is automatically started |
Disconnecting from a database |
|
Before the trigger is executed, a default transaction is automatically started |
When a transaction is started |
|
The trigger is executed in the current transaction context |
When a transaction is committed |
|
The trigger is executed in the current transaction context |
When a transaction is cancelled |
|
The trigger is executed in the current transaction context |
7.5.4. Creating Triggers
CREATE TRIGGER trigname { <relation_trigger_legacy> | <relation_trigger_sql2003> | <database_trigger> } AS [<declarations>] BEGIN [<PSQL_statements>] END <relation_trigger_legacy> ::= FOR {tablename | viewname} [ACTIVE | INACTIVE] {BEFORE | AFTER} <mutation_list> [POSITION number] <relation_trigger_sql2003> ::= [ACTIVE | INACTIVE] {BEFORE | AFTER} <mutation_list> [POSITION number] ON {tablename | viewname} <database_trigger> ::= [ACTIVE | INACTIVE] ON <db_event> [POSITION number] <mutation_list> ::= <mutation> [OR <mutation> [OR <mutation>]] <mutation> ::= { INSERT | UPDATE | DELETE } <db_event> ::= CONNECT | DISCONNECT | TRANSACTION START | TRANSACTION COMMIT | TRANSACTION ROLLBACK
The header must contain a name for the trigger that is unique among trigger names. It must include the event or events that will fire the trigger. Also, for a DML trigger it is mandatory to specify the event phase and the name of the table or view that is to “own” the trigger.
The body of the trigger can be headed by the declarations of local variables and cursors, if any.
Within the enclosing main BEGIN…END
wrapper will be one or more blocks of PSQL statements, which may be empty.
See ">CREATE TRIGGER
in Chapter 5, Data Definition (DDL) Statements.
7.5.5. Modifying Triggers
Altering the status, phase, table or view event(s), firing position and code in the body of a DML trigger are all possible.
However, you cannot modify a DML trigger to convert it to a database trigger, nor vice versa.
Any element not specified is left unchanged by ALTER TRIGGER
.
The alternative statements CREATE OR ALTER TRIGGER
and RECREATE TRIGGER
will replace the original trigger definition entirely.
ALTER TRIGGER trigname [ACTIVE | INACTIVE] [{BEFORE | AFTER} <mutation_list> | ON <db_event>] [POSITION number] [ AS [<declarations>] BEGIN [<PSQL_statements>] END ] <mutation_list> ::= <mutation> [OR <mutation> [OR <mutation>]] <mutation> ::= { INSERT | UPDATE | DELETE } <db_event> ::= { CONNECT | DISCONNECT | TRANSACTION START | TRANSACTION COMMIT | TRANSACTION ROLLBACK }
See ALTER TRIGGER
, CREATE OR ALTER TRIGGER
, RECREATE TRIGGER
in Chapter 5, Data Definition (DDL) Statements.
7.5.6. Deleting a Trigger
The DROP TRIGGER
statement is used to delete triggers.
DROP TRIGGER trigname
See DROP TRIGGER
in Chapter 5, Data Definition (DDL) Statements.
7.6. Writing the Body Code
This section takes a closer look at the procedural SQL language constructs and statements that are available for coding the body of a stored procedure, trigger or anonymous PSQL block.
7.6.1. Assignment Statements
Assigning a value to a variable
PSQL
varname = <value_expr>
Argument | Description |
---|---|
varname |
Name of a parameter or local variable |
value_expr |
An expression, constant or variable whose value resolves to the same data type as varname |
PSQL uses the equivalence symbol (‘=
’) as its assignment operator.
The assignment statement assigns an SQL expression value on the right to the variable on the left of the operator.
The expression can be any valid SQL expression: it may contain literals, internal variable names, arithmetic, logical and string operations, calls to internal functions or to external functions (UDFs).
CREATE PROCEDURE MYPROC (
a INTEGER,
b INTEGER,
name VARCHAR (30)
)
RETURNS (
c INTEGER,
str VARCHAR(100))
AS
BEGIN
-- assigning a constant
c = 0;
str = '';
SUSPEND;
-- assigning expression values
c = a + b;
str = name || CAST(b AS VARCHAR(10));
SUSPEND;
-- assigning expression value
-- built by a query
c = (SELECT 1 FROM rdb$database);
-- assigning a value from a context variable
str = CURRENT_USER;
SUSPEND;
END
7.6.2. DECLARE CURSOR
Declaring a named cursor
PSQL
DECLARE [VARIABLE] cursorname CURSOR FOR (<select>) [FOR UPDATE]
Argument | Description |
---|---|
cursorname |
Cursor name |
select |
|
The DECLARE CURSOR … FOR
statement binds a named cursor to the result set obtained in the SELECT
statement specified in the FOR
clause.
In the body code, the cursor can be opened, used to walk row-by-row through the result set and closed.
While the cursor is open, the code can perform positioned updates and deletes using the WHERE CURRENT OF
in the UPDATE
or DELETE
statement.
Cursor Idiosyncrasies
-
The optional
FOR UPDATE
clause can be included in the SELECT statement but its absence does not prevent successful execution of a positioned update or delete -
Care should be taken to ensure that the names of declared cursors do not conflict with any names used subsequently in statements for
AS CURSOR
clauses -
If the cursor is needed only to walk the result set, it is nearly always easier and less error-prone to use a
FOR SELECT
statement with theAS CURSOR
clause. Declared cursors must be explicitly opened, used to fetch data and closed. The context variableROW_COUNT
has to be checked after each fetch and, if its value is zero, the loop has to be terminated. AFOR SELECT
statement checks it automatically.Nevertheless, declared cursors provide a high level of control over sequential events and allow several cursors to be managed in parallel.
-
The
SELECT
statement may contain parameters. For instance:SELECT NAME || :SFX FROM NAMES WHERE NUMBER = :NUM
Each parameter has to have been declared beforehand as a PSQL variable, even if they originate as input and output parameters. When the cursor is opened, the parameter is assigned the current value of the variable.
Attention!
If the value of a PSQL variable used in the Note particularly that the behaviour may depend on the query plan, specifically on the indexes being used. No strict rules are in place for situations like this currently, but that could change in future versions of Firebird. |
Examples Using Named Cursors
-
Declaring a named cursor in the trigger.
CREATE OR ALTER TRIGGER TBU_STOCK BEFORE UPDATE ON STOCK AS DECLARE C_COUNTRY CURSOR FOR ( SELECT COUNTRY, CAPITAL FROM COUNTRY ); BEGIN /* PSQL statements */ END
-
A collection of scripts for creating views with a PSQL block using named cursors.
EXECUTE BLOCK RETURNS ( SCRIPT BLOB SUB_TYPE TEXT) AS DECLARE VARIABLE FIELDS VARCHAR(8191); DECLARE VARIABLE FIELD_NAME TYPE OF RDB$FIELD_NAME; DECLARE VARIABLE RELATION RDB$RELATION_NAME; DECLARE VARIABLE SOURCE TYPE OF COLUMN RDB$RELATIONS.RDB$VIEW_SOURCE; DECLARE VARIABLE CUR_R CURSOR FOR ( SELECT RDB$RELATION_NAME, RDB$VIEW_SOURCE FROM RDB$RELATIONS WHERE RDB$VIEW_SOURCE IS NOT NULL); -- Declaring a named cursor where -- a local variable is used DECLARE CUR_F CURSOR FOR ( SELECT RDB$FIELD_NAME FROM RDB$RELATION_FIELDS WHERE -- It is important that the variable must be declared earlier RDB$RELATION_NAME = :RELATION); BEGIN OPEN CUR_R; WHILE (1 = 1) DO BEGIN FETCH CUR_R INTO :RELATION, :SOURCE; IF (ROW_COUNT = 0) THEN LEAVE; FIELDS = NULL; -- The CUR_F cursor will use the value -- of the RELATION variable initiated above OPEN CUR_F; WHILE (1 = 1) DO BEGIN FETCH CUR_F INTO :FIELD_NAME; IF (ROW_COUNT = 0) THEN LEAVE; IF (FIELDS IS NULL) THEN FIELDS = TRIM(FIELD_NAME); ELSE FIELDS = FIELDS || ', ' || TRIM(FIELD_NAME); END CLOSE CUR_F; SCRIPT = 'CREATE VIEW ' || RELATION; IF (FIELDS IS NOT NULL) THEN SCRIPT = SCRIPT || ' (' || FIELDS || ')'; SCRIPT = SCRIPT || ' AS ' || ASCII_CHAR(13); SCRIPT = SCRIPT || SOURCE; SUSPEND; END CLOSE CUR_R; END
7.6.3. DECLARE VARIABLE
Declaring a local variable
PSQL
DECLARE [VARIABLE] varname {<datatype> | domain | TYPE OF {domain | COLUMN rel.col} [NOT NULL] [CHARACTER SET charset] [COLLATE collation] [{DEFAULT | = } <initvalue>]; <datatype> ::= {SMALLINT | INTEGER | BIGINT} | {FLOAT | DOUBLE PRECISION} | {DATE | TIME | TIMESTAMP} | {DECIMAL | NUMERIC} [(precision [, scale])] | {CHAR | CHARACTER [VARYING] | VARCHAR} [(size)] [CHARACTER SET charset] | {NCHAR | NATIONAL {CHARACTER | CHAR}} [VARYING] [(size)] | BLOB [SUB_TYPE {subtype_num | subtype_name}] [SEGMENT SIZE seglen] [CHARACTER SET charset] | BLOB [(seglen [, subtype_num])] <initvalue> ::= <literal> | <context_var>
Argument | Description |
---|---|
varname |
Name of the local variable |
datatype |
An SQL data type |
domain |
The name of an existing domain in this database |
rel.col |
Relation name (table or view) in this database and the name of a column in that relation |
precision |
Precision. From 1 to 18 |
scale |
Scale. From 0 to 18, it must be less than or equal to precision |
size |
The maximum size of a string in characters |
subtype_num |
|
subtype_name |
|
seglen |
Segment size, not greater than 65,535 |
initvalue |
Initial value for this variable |
literal |
Literal of a type compatible with the type of the local variable |
context_var |
Any context variable whose type is compatible with the type of the local variable |
charset |
Character set |
collation |
Collation sequence |
The statement DECLARE [VARIABLE]
is used for declaring a local variable.
The keyword VARIABLE
can be omitted.
One DECLARE [VARIABLE]
statement is required for each local variable.
Any number of DECLARE [VARIABLE]
statements can be included and in any order.
The name of a local variable must be unique among the names of local variables and input and output parameters declared for the module.
Data Type for Variables
A local variable can be of any SQL type other than an array.
-
A domain name can be specified as the type and the variable will inherit all of its attributes.
-
If the
TYPE OF domain
clause is used instead, the variable will inherit only the domain’s data type, and, if applicable, its character set and collation attributes. Any default value or constraints such asNOT NULL
orCHECK
constraints are not inherited. -
If the
TYPE OF COLUMN relation.column>
option is used to “borrow” from a column in a table or view, the variable will inherit only the column’s data type, and, if applicable, its character set and collation attributes. Any other attributes are ignored.
NOT NULL
Constraint
The variable can be constrained NOT NULL
if required.
If a domain has been specified as the data type and already carries the NOT NULL
constraint, it will not be necessary.
With the other forms, including use of a domain that is nullable, the NOT NULL
attribute should be included if needed.
CHARACTER SET
and COLLATE
clauses
Unless specified, the character set and collation sequence of a string variable will be the database defaults.
A CHARACTER SET
clause can be included, if required, to handle string data that is going to be in a different character set.
A valid collation sequence (COLLATE
clause) can also be included, with or without the character set clause.
Initializing a Variable
Local variables are NULL
when execution of the module begins.
They can be initialized so that a starting or default value is available when they are first referenced.
The DEFAULT <initvalue>
form can be used, or just the assignment operator, ‘=
’: = <initvalue>
.
The value can be any type-compatible literal or context variable.
Be sure to use this clause for any variables that are constrained to be |
Examples of various ways to declare local variables
CREATE OR ALTER PROCEDURE SOME_PROC
AS
-- Declaring a variable of the INT type
DECLARE I INT;
-- Declaring a variable of the INT type that does not allow NULL
DECLARE VARIABLE J INT NOT NULL;
-- Declaring a variable of the INT type with the default value of 0
DECLARE VARIABLE K INT DEFAULT 0;
-- Declaring a variable of the INT type with the default value of 1
DECLARE VARIABLE L INT = 1;
-- Declaring a variable based on the COUNTRYNAME domain
DECLARE FARM_COUNTRY COUNTRYNAME;
-- Declaring a variable of the type equal to the COUNTRYNAME domain
DECLARE FROM_COUNTRY TYPE OF COUNTRYNAME;
-- Declaring a variable with the type of the CAPITAL column in the COUNTRY table
DECLARE CAPITAL TYPE OF COLUMN COUNTRY.CAPITAL;
BEGIN
/* PSQL statements */
END
7.6.4. BEGIN … END
Delineating a block of statements
PSQL
<block> ::= BEGIN [<compound_statement> ...] [<when_do> ...] END <compound_statement> ::= {<block> | <statement>} <when_do> ::= !! See WHEN ... DO !!
The BEGIN … END
construct is a two-part statement that wraps a block of statements that are executed as one unit of code.
Each block starts with the half-statement BEGIN
and ends with the other half-statement END
.
Blocks can be nested to unlimited depth.
They may be empty, allowing them to act as stubs, without the need to write dummy statements.
For error handling, you can add one or more WHEN … DO
statements immediately before END
.
Other statements are not allowed after WHEN … DO
.
The BEGIN … END
itself should not be followed by a statement terminator (semicolon).
However, when defining or altering a PSQL module in the isql utility, that application requires that the last END
statement be followed by its own terminator character, that was previously switched, using SET TERM
, to some string other than a semicolon.
That terminator is not part of the PSQL syntax.
The final, or outermost, END
statement in a trigger terminates the trigger.
What the final END
statement does in a stored procedure depends on the type of procedure:
-
In a selectable procedure, the final
END
statement returns control to the caller, returning SQLCODE 100, indicating that there are no more rows to retrieve -
In an executable procedure, the final
END
statement returns control to the caller, along with the current values of any output parameters defined.
A sample procedure from the employee.fdb
database, showing simple usage of BEGIN…END
blocks:
SET TERM ^;
CREATE OR ALTER PROCEDURE DEPT_BUDGET (
DNO CHAR(3))
RETURNS (
TOT DECIMAL(12,2))
AS
DECLARE VARIABLE SUMB DECIMAL(12,2);
DECLARE VARIABLE RDNO CHAR(3);
DECLARE VARIABLE CNT INTEGER;
BEGIN
TOT = 0;
SELECT
BUDGET
FROM
DEPARTMENT
WHERE DEPT_NO = :DNO
INTO :TOT;
SELECT
COUNT(BUDGET)
FROM
DEPARTMENT
WHERE HEAD_DEPT = :DNO
INTO :CNT;
IF (CNT = 0) THEN
SUSPEND;
FOR
SELECT
DEPT_NO
FROM
DEPARTMENT
WHERE HEAD_DEPT = :DNO
INTO :RDNO
DO
BEGIN
EXECUTE PROCEDURE DEPT_BUDGET(:RDNO)
RETURNING_VALUES :SUMB;
TOT = TOT + SUMB;
END
SUSPEND;
END^
SET TERM ;^
7.6.5. IF … THEN … ELSE
Conditional jumps
PSQL
IF (<condition>) THEN <compound_statement> [ELSE <compound_statement>]
Argument | Description |
---|---|
condition |
A logical condition returning TRUE, FALSE or UNKNOWN |
single_statement |
A single statement terminated with a semicolon |
compound_statement |
Two or more statements wrapped in |
The conditional jump statement IF … THEN
is used to branch the execution process in a PSQL module.
The condition is always enclosed in parentheses.
If it returns the value TRUE, execution branches to the statement or the block of statements after the keyword THEN
.
If an ELSE
is present and the condition returns FALSE or UNKNOWN, execution branches to the statement or the block of statements after it.
An example using the IF
statement.
Assume that the FIRST
, LINE2
and LAST
variables were declared earlier.
...
IF (FIRST IS NOT NULL) THEN
LINE2 = FIRST || ' ' || LAST;
ELSE
LINE2 = LAST;
...
7.6.6. WHILE … DO
Looping constructs
PSQL
WHILE <condition> DO <compound_statement>
Argument | Description |
---|---|
condition |
A logical condition returning TRUE, FALSE or UNKNOWN |
single_statement |
A single statement terminated with a semicolon |
compound_statement |
Two or more statements wrapped in |
A WHILE
statement implements the looping construct in PSQL.
The statement or the block of statements will be executed until the condition returns TRUE.
Loops can be nested to any depth.
A procedure calculating the sum of numbers from 1 to I shows how the looping construct is used.
CREATE PROCEDURE SUM_INT (I INTEGER)
RETURNS (S INTEGER)
AS
BEGIN
s = 0;
WHILE (i > 0) DO
BEGIN
s = s + i;
i = i - 1;
END
END
Executing the procedure in isql:
EXECUTE PROCEDURE SUM_INT(4);
the result is:
S
==========
10
IF … THEN … ELSE
, LEAVE
, EXIT
, FOR SELECT
, FOR EXECUTE STATEMENT
7.6.7. LEAVE
Terminating a loop
PSQL
[label:] <loop_stmt> BEGIN ... LEAVE [label]; ... END <loop_stmt> ::= FOR <select_stmt> INTO <var_list> DO | FOR EXECUTE STATEMENT ... INTO <var_list> DO | WHILE (<condition>)} DO
Argument | Description |
---|---|
label |
Label |
select_stmt |
|
condition |
A logical condition returning TRUE, FALSE or UNKNOWN |
A LEAVE
statement immediately terminates the inner loop of a WHILE
or FOR
looping statement.
The label parameter is optional.
LEAVE
can cause an exit from outer loops as well.
Code continues to be executed from the first statement after the termination of the outer loop block.
-
Leaving a loop if an error occurs on an insert into the
NUMBERS
table. The code continues to be executed from the lineC = 0
.... WHILE (B < 10) DO BEGIN INSERT INTO NUMBERS(B) VALUES (:B); B = B + 1; WHEN ANY DO BEGIN EXECUTE PROCEDURE LOG_ERROR ( CURRENT_TIMESTAMP, 'ERROR IN B LOOP'); LEAVE; END END C = 0; ...
-
An example using labels in the
LEAVE
statement.LEAVE LOOPA
terminates the outer loop andLEAVE LOOPB
terminates the inner loop. Note that the plainLEAVE
statement would be enough to terminate the inner loop.... STMT1 = 'SELECT NAME FROM FARMS'; LOOPA: FOR EXECUTE STATEMENT :STMT1 INTO :FARM DO BEGIN STMT2 = 'SELECT NAME ' || 'FROM ANIMALS WHERE FARM = '''; LOOPB: FOR EXECUTE STATEMENT :STMT2 || :FARM || '''' INTO :ANIMAL DO BEGIN IF (ANIMAL = 'FLUFFY') THEN LEAVE LOOPB; ELSE IF (ANIMAL = FARM) THEN LEAVE LOOPA; ELSE SUSPEND; END END ...
7.6.8. EXIT
Terminating module execution
PSQL
EXIT
The EXIT
statement causes execution of the procedure or trigger to jump to the final END
statement from any point in the code, thus terminating the program.
Using the EXIT
statement in a selectable procedure:
CREATE PROCEDURE GEN_100
RETURNS (
I INTEGER
)
AS
BEGIN
I = 1;
WHILE (1=1) DO
BEGIN
SUSPEND;
IF (I=100) THEN
EXIT;
I = I + 1;
END
END
7.6.9. SUSPEND
Passing output to the buffer and suspending execution while waiting for caller to fetch it
PSQL
SUSPEND
The SUSPEND
statement is used in a selectable stored procedure to pass the values of output parameters to a buffer and suspend execution.
Execution remains suspended until the calling application fetches the contents of the buffer.
Execution resumes from the statement directly after the SUSPEND
statement.
In practice, this is likely to be a new iteration of a looping process.
Important Notes
|
Using the SUSPEND
statement in a selectable procedure:
CREATE PROCEDURE GEN_100
RETURNS (
I INTEGER
)
AS
BEGIN
I = 1;
WHILE (1=1) DO
BEGIN
SUSPEND;
IF (I=100) THEN
EXIT;
I = I + 1;
END
END
7.6.10. EXECUTE STATEMENT
Executing dynamically created SQL statements
PSQL
<execute_statement> ::= EXECUTE STATEMENT <argument> [<option> …] [INTO <variables>] <argument> ::= <paramless_stmt> | (<paramless_stmt>) | (<stmt_with_params>) (<param_values>) <param_values> ::= <named_values> | <positional_values> <named_values> ::= paramname := <value_expr> [, paramname := <value_expr> ...] <positional_values> ::= <value_expr> [, <value_expr> ...] <option> ::= WITH {AUTONOMOUS | COMMON} TRANSACTION | WITH CALLER PRIVILEGES | AS USER user | PASSWORD password | ROLE role | ON EXTERNAL [DATA SOURCE] <connect_string> <connect_string> ::= [<hostspec>] {filepath | db_alias} <hostspec> ::= <tcpip_hostspec> | <NamedPipes_hostspec> <tcpip_hostspec> ::= hostname[/port]: <NamePipes_hostspec> ::= \\hostname\ <variables> ::= [:]varname [, [:]varname ...]
Argument | Description |
---|---|
paramless_stmt |
Literal string or variable containing a non-parameterized SQL query |
stmt_with_params |
Literal string or variable containing a parameterized SQL query |
paramname |
SQL query parameter name |
value_expr |
SQL expression resolving to a value |
user |
User name.
It can be a string, |
password |
Password. It can be a string or a string variable |
role |
Role.
It can be a string, |
connection_string |
Connection string. It can be a string or a string variable |
filepath |
Path to the primary database file |
db_alias |
Database alias |
hostname |
Computer name or IP address |
varname |
Variable |
The statement EXECUTE STATEMENT
takes a string parameter and executes it as if it were a DSQL statement.
If the statement returns data, it can be passed to local variables by way of an INTO
clause.
Parameterized Statements
You can use parameters — either named or positional — in the DSQL statement string. Each parameter must be assigned a value.
Special Rules for Parameterized Statements
-
Named and positional parameters cannot be mixed in one query
-
If the statement has parameters, they must be enclosed in parentheses when
EXECUTE STATEMENT
is called, regardless of whether they come directly as strings, as variable names or as expressions -
Each named parameter must be prefixed by a colon (‘
:
’) in the statement string itself, but not when the parameter is assigned a value -
Positional parameters must be assigned their values in the same order as they appear in the query text
-
The assignment operator for parameters is the special operator “
:=
”, similar to the assignment operator in Pascal -
Each named parameter can be used in the statement more than once, but its value must be assigned only once
-
With positional parameters, the number of assigned values must match the number of parameter placeholders (question marks) in the statement exactly
-
A named parameter in the statement text can only be a regular identifier (it cannot be a quoted identifier)
Examples of EXECUTE STATEMENT
with parameters
With named parameters:
...
DECLARE license_num VARCHAR(15);
DECLARE connect_string VARCHAR (100);
DECLARE stmt VARCHAR (100) =
'SELECT license
FROM cars
WHERE driver = :driver AND location = :loc';
BEGIN
...
SELECT connstr
FROM databases
WHERE cust_id = :id
INTO connect_string;
...
FOR
SELECT id
FROM drivers
INTO current_driver
DO
BEGIN
FOR
SELECT location
FROM driver_locations
WHERE driver_id = :current_driver
INTO current_location
DO
BEGIN
...
EXECUTE STATEMENT (stmt)
(driver := current_driver,
loc := current_location)
ON EXTERNAL connect_string
INTO license_num;
...
The same code with positional parameters:
DECLARE license_num VARCHAR (15);
DECLARE connect_string VARCHAR (100);
DECLARE stmt VARCHAR (100) =
'SELECT license
FROM cars
WHERE driver = ? AND location = ?';
BEGIN
...
SELECT connstr
FROM databases
WHERE cust_id = :id
into connect_string;
...
FOR
SELECT id
FROM drivers
INTO current_driver
DO
BEGIN
FOR
SELECT location
FROM driver_locations
WHERE driver_id = :current_driver
INTO current_location
DO
BEGIN
...
EXECUTE STATEMENT (stmt)
(current_driver, current_location)
ON EXTERNAL connect_string
INTO license_num;
...
WITH {AUTONOMOUS | COMMON} TRANSACTION
Traditionally, the executed SQL statement always ran within the current transaction, and this is still the default.
WITH AUTONOMOUS TRANSACTION
causes a separate transaction to be started, with the same parameters as the current transaction.
It will be committed if the statement runs to completion without errors and rolled back otherwise.
WITH COMMON TRANSACTION
uses the current transaction if possible.
If the statement must run in a separate connection, an already started transaction within that connection is used, if available.
Otherwise, a new transaction is started with the same parameters as the current transaction.
Any new transactions started under the “COMMON
” regime are committed or rolled back with the current transaction.
WITH CALLER PRIVILEGES
By default, the SQL statement is executed with the privileges of the current user.
Specifying WITH CALLER PRIVILEGES
adds to this the privileges of the calling procedure or trigger, just as if the statement were executed directly by the routine.
WITH CALLER PRIVILEGES
has no effect if the ON EXTERNAL
clause is also present.
ON EXTERNAL [DATA SOURCE]
With ON EXTERNAL [DATA SOURCE]
, the SQL statement is executed in a separate connection to the same or another database, possibly even on another server.
If the connect string is NULL or “''
” (empty string), the entire ON EXTERNAL [DATA SOURCE]
clause is considered absent and the statement is executed against the current database.
Connection Pooling
-
External connections made by statements
WITH COMMON TRANSACTION
(the default) will remain open until the current transaction ends. They can be reused by subsequent calls toEXECUTE STATEMENT
, but only if the connect string is exactly the same, including case -
External connections made by statements
WITH AUTONOMOUS TRANSACTION
are closed as soon as the statement has been executed -
Notice that statements
WITH AUTONOMOUS TRANSACTION
can and will re-use connections that were opened earlier by statementsWITH COMMON TRANSACTION
. If this happens, the reused connection will be left open after the statement has been executed. (It must be, because it has at least one un-committed transaction!)
Transaction Pooling
-
If
WITH COMMON TRANSACTION
is in effect, transactions will be reused as much as possible. They will be committed or rolled back together with the current transaction -
If
WITH AUTONOMOUS TRANSACTION
is specified, a fresh transaction will always be started for the statement. This transaction will be committed or rolled back immediately after the statement’s execution
Exception Handling
When ON EXTERNAL
is used, the extra connection is always made via a so-called external provider, even if the connection is to the current database.
One of the consequences is that exceptions cannot be caught in the usual way.
Every exception caused by the statement is wrapped in either an eds_connection
or an eds_statement
error.
In order to catch them in your PSQL code, you have to use WHEN GDSCODE eds_connection
, WHEN GDSCODE eds_statement
or WHEN ANY
.
Without |
AS USER
, PASSWORD
and ROLE
The optional AS USER
, PASSWORD
and ROLE
clauses allow specificaton of which user will execute the SQL statement and with which role.
The method of user log-in and whether a separate connection is open depend on the presence and values of the ON EXTERNAL [DATA SOURCE]
, AS USER
, PASSWORD
and ROLE
clauses:
-
If
ON EXTERNAL
is present, a new connection is always opened, and:-
If at least one of
AS USER
,PASSWORD
andROLE
is present, native authentication is attempted with the given parameter values (locally or remotely, depending on the connect string). No defaults are used for missing parameters -
If all three are absent and the connect string contains no hostname, then the new connection is established on the local host with the same user and role as the current connection. The term 'local' means “on the same machine as the server” here. This is not necessarily the location of the client
-
If all three are absent and the connect string contains a hostname, then trusted authentication is attempted on the remote host (again, 'remote' from the perspective of the server). If this succeeds, the remote operating system will provide the user name (usually the operating system account under which the Firebird process runs)
-
-
If
ON EXTERNAL
is absent:-
If at least one of
AS USER
,PASSWORD
andROLE
is present, a new connection to the current database is opened with the suppled parameter values. No defaults are used for missing parameters -
If all three are absent, the statement is executed within the current connection
-
Notice
If a parameter value is NULL or “ |
Caveats with EXECUTE STATEMENT
-
There is no way to validate the syntax of the enclosed statement
-
There are no dependency checks to discover whether tables or columns have been dropped
-
Even though the performance in loops has been significantly improved in Firebird 2.5, execution is still considerably slower than when the same statements are launched directly
-
Return values are strictly checked for data type in order to avoid unpredictable type-casting exceptions. For example, the string
'1234'
would convert to an integer, 1234, but'abc'
would give a conversion error
All in all, this feature is meant to be used very cautiously and you should always take the caveats into account. If you can achieve the same result with PSQL and/or DSQL, it will almost always be preferable.
7.6.11. FOR SELECT
Looping row-by-row through a selected result set
PSQL
FOR <select_stmt> [AS CURSOR cursorname] DO <compound_statement>
Argument | Description |
---|---|
select_stmt |
|
cursorname |
Cursor name. It must be unique among cursor names in the PSQL module (stored procedure, trigger or PSQL block) |
single_statement |
A single statement, terminated with a colon, that performs all the processing for this |
compound_statement |
A block of statements wrapped in |
A FOR SELECT
statement
-
retrieves each row sequentially from the result set and executes the statement or block of statements on the row. In each iteration of the loop, the field values of the current row are copied into pre-declared variables.
Including the
AS CURSOR
clause enables positioned deletes and updates to be performed — see notes below -
can embed other
FOR SELECT
statements -
can carry named parameters that must be previously declared in the
DECLARE VARIABLE
statement or exist as input or output parameters of the procedure -
requires an
INTO
clause that is located at the end of theSELECT … FROM …
specification. In each iteration of the loop, the field values in the current row are copied to the list of variables specified in theINTO
clause. The loop repeats until all rows are retrieved, after which it terminates -
can be terminated before all rows are retrieved by using a
LEAVE statement
The Undeclared Cursor
The optional AS CURSOR
clause surfaces the set in the FOR SELECT
structure as an undeclared, named cursor that can be operated on using the WHERE CURRENT OF
clause inside the statement or block following the DO
command, in order to delete or update the current row before execution moves to the next iteration.
Other points to take into account regarding undeclared cursors:
-
the
OPEN
,FETCH
andCLOSE
statements cannot be applied to a cursor surfaced by theAS CURSOR
clause -
the cursor name argument associated with an
AS CURSOR
clause must not clash with any names created byDECLARE VARIABLE
orDECLARE CURSOR
statements at the top of the body code, nor with any other cursors surfaced by anAS CURSOR
clause -
The optional
FOR UPDATE
clause in theSELECT
statement is not required for a positioned update
Examples using FOR SELECT
-
A simple loop through query results:
CREATE PROCEDURE SHOWNUMS RETURNS ( AA INTEGER, BB INTEGER, SM INTEGER, DF INTEGER) AS BEGIN FOR SELECT DISTINCT A, B FROM NUMBERS ORDER BY A, B INTO AA, BB DO BEGIN SM = AA + BB; DF = AA - BB; SUSPEND; END END
-
Nested
FOR SELECT
loop:CREATE PROCEDURE RELFIELDS RETURNS ( RELATION CHAR(32), POS INTEGER, FIELD CHAR(32)) AS BEGIN FOR SELECT RDB$RELATION_NAME FROM RDB$RELATIONS ORDER BY 1 INTO :RELATION DO BEGIN FOR SELECT RDB$FIELD_POSITION + 1, RDB$FIELD_NAME FROM RDB$RELATION_FIELDS WHERE RDB$RELATION_NAME = :RELATION ORDER BY RDB$FIELD_POSITION INTO :POS, :FIELD DO BEGIN IF (POS = 2) THEN RELATION = ' "'; SUSPEND; END END END
-
Using the
AS CURSOR
clause to surface a cursor for the positioned delete of a record:CREATE PROCEDURE DELTOWN ( TOWNTODELETE VARCHAR(24)) RETURNS ( TOWN VARCHAR(24), POP INTEGER) AS BEGIN FOR SELECT TOWN, POP FROM TOWNS INTO :TOWN, :POP AS CURSOR TCUR DO BEGIN IF (:TOWN = :TOWNTODELETE) THEN -- Positional delete DELETE FROM TOWNS WHERE CURRENT OF TCUR; ELSE SUSPEND; END END
7.6.12. FOR EXECUTE STATEMENT
Executing dynamically created SQL statements that return a row set
PSQL
FOR <execute_statement> DO <compound_statement>
Argument | Description |
---|---|
execute_stmt |
An |
single_statement |
A single statement, terminated with a colon, that performs all the processing for this |
compound_statement |
A block of statements wrapped in |
The statement FOR EXECUTE STATEMENT
is used, in a manner analogous to FOR SELECT
, to loop through the result set of a dynamically executed query that returns multiple rows.
Executing a dynamically constructed SELECT
query that returns a data set:
CREATE PROCEDURE DynamicSampleThree (
Q_FIELD_NAME VARCHAR(100),
Q_TABLE_NAME VARCHAR(100)
) RETURNS(
LINE VARCHAR(32000)
)
AS
DECLARE VARIABLE P_ONE_LINE VARCHAR(100);
BEGIN
LINE = '';
FOR
EXECUTE STATEMENT
'SELECT T1.' || :Q_FIELD_NAME ||
' FROM ' || :Q_TABLE_NAME || ' T1 '
INTO :P_ONE_LINE
DO
IF (:P_ONE_LINE IS NOT NULL) THEN
LINE = :LINE || :P_ONE_LINE || ' ';
SUSPEND;
END
7.6.13. OPEN
Opening a declared cursor
PSQL
OPEN cursorname
Argument | Description |
---|---|
cursorname |
Cursor name.
A cursor with this name must be previously declared with a |
An OPEN
statement opens a previously declared cursor, executes the SELECT
statement declared for it and makes the first record of the result data set ready to fetch.
OPEN
can be applied only to cursors previously declared in a DECLARE VARIABLE
statement.
If the |
-
Using the
OPEN
statement:SET TERM ^; CREATE OR ALTER PROCEDURE GET_RELATIONS_NAMES RETURNS ( RNAME CHAR(31) ) AS DECLARE C CURSOR FOR ( SELECT RDB$RELATION_NAME FROM RDB$RELATIONS); BEGIN OPEN C; WHILE (1 = 1) DO BEGIN FETCH C INTO :RNAME; IF (ROW_COUNT = 0) THEN LEAVE; SUSPEND; END CLOSE C; END^ SET TERM ;^
-
A collection of scripts for creating views using a PSQL block with named cursors:
EXECUTE BLOCK RETURNS ( SCRIPT BLOB SUB_TYPE TEXT) AS DECLARE VARIABLE FIELDS VARCHAR(8191); DECLARE VARIABLE FIELD_NAME TYPE OF RDB$FIELD_NAME; DECLARE VARIABLE RELATION RDB$RELATION_NAME; DECLARE VARIABLE SOURCE TYPE OF COLUMN RDB$RELATIONS.RDB$VIEW_SOURCE; -- named cursor DECLARE VARIABLE CUR_R CURSOR FOR ( SELECT RDB$RELATION_NAME, RDB$VIEW_SOURCE FROM RDB$RELATIONS WHERE RDB$VIEW_SOURCE IS NOT NULL); -- named cursor with local variable DECLARE CUR_F CURSOR FOR ( SELECT RDB$FIELD_NAME FROM RDB$RELATION_FIELDS WHERE -- Important! The variable shall be declared earlier RDB$RELATION_NAME = :RELATION); BEGIN OPEN CUR_R; WHILE (1 = 1) DO BEGIN FETCH CUR_R INTO :RELATION, :SOURCE; IF (ROW_COUNT = 0) THEN LEAVE; FIELDS = NULL; -- The CUR_F cursor will use -- variable value of RELATION initialized above OPEN CUR_F; WHILE (1 = 1) DO BEGIN FETCH CUR_F INTO :FIELD_NAME; IF (ROW_COUNT = 0) THEN LEAVE; IF (FIELDS IS NULL) THEN FIELDS = TRIM(FIELD_NAME); ELSE FIELDS = FIELDS || ', ' || TRIM(FIELD_NAME); END CLOSE CUR_F; SCRIPT = 'CREATE VIEW ' || RELATION; IF (FIELDS IS NOT NULL) THEN SCRIPT = SCRIPT || ' (' || FIELDS || ')'; SCRIPT = SCRIPT || ' AS ' || ASCII_CHAR(13); SCRIPT = SCRIPT || SOURCE; SUSPEND; END CLOSE CUR_R; END
7.6.14. FETCH
Fetching successive records from a data set retrieved by a cursor
PSQL
FETCH cursorname INTO [:]varname [, [:]varname ...]
Argument | Description |
---|---|
cursorname |
Cursor name.
A cursor with this name must be previously declared with a |
varname |
Variable name |
A FETCH
statement fetches the first and successive rows from the result set of the cursor and assigns the column values to PSQL variables.
The FETCH
statement can be used only with a cursor declared with the DECLARE CURSOR
statement.
The INTO
clause gets data from the current row of the cursor and loads them into PSQL variables.
For checking whether all of the the data set rows have been fetched, the context variable ROW_COUNT
returns the number of rows fetched by the statement.
It is positive until all rows have been checked.
A ROW_COUNT
of 1 indicates that the next fetch will be the last.
Using the FETCH
statement:
SET TERM ^;
CREATE OR ALTER PROCEDURE GET_RELATIONS_NAMES
RETURNS (
RNAME CHAR(31)
)
AS
DECLARE C CURSOR FOR (
SELECT RDB$RELATION_NAME
FROM RDB$RELATIONS);
BEGIN
OPEN C;
WHILE (1 = 1) DO
BEGIN
FETCH C INTO :RNAME;
IF (ROW_COUNT = 0) THEN
LEAVE;
SUSPEND;
END
CLOSE C;
END^
SET TERM ;^
7.6.15. CLOSE
Closing a declared cursor
PSQL
CLOSE cursorname
Argument | Description |
---|---|
cursorname |
Cursor name.
A cursor with this name must be previously declared with a |
A CLOSE
statement closes an open cursor.
Any cursors that are still open will be automatically closed after the module code completes execution.
Only a cursor that was declared with DECLARE CURSOR
can be closed with a CLOSE
statement.
Using the CLOSE
statement:
SET TERM ^;
CREATE OR ALTER PROCEDURE GET_RELATIONS_NAMES
RETURNS (
RNAME CHAR(31)
)
AS
DECLARE C CURSOR FOR (
SELECT RDB$RELATION_NAME
FROM RDB$RELATIONS);
BEGIN
OPEN C;
WHILE (1 = 1) DO
BEGIN
FETCH C INTO :RNAME;
IF (ROW_COUNT = 0) THEN
LEAVE;
SUSPEND;
END
CLOSE C;
END^
7.6.16. IN AUTONOMOUS TRANSACTION
Executing a statement or a block of statements in an autonomous transaction
PSQL
IN AUTONOMOUS TRANSACTION DO <compound_statement>
Argument | Description |
---|---|
compound_statement |
A statement or a block of statements |
An IN AUTONOMOUS TRANSACTION
statement enables execution of a statement or a block of statements in an autonomous transaction.
Code running in an autonomous transaction will be committed right after its successful execution, regardless of the status of its parent transaction.
It might be needed when certain operations must not be rolled back, even if an error occurs in the parent transaction.
An autonomous transaction has the same isolation level as its parent transaction. Any exception that is thrown in the block of the autonomous transaction code will result in the autonomous transaction being rolled back and all made changes being cancelled. If the code executes successfully, the autonomous transaction will be committed.
Using an autonomous transaction in a trigger for the database ON CONNECT
event, in order to log all connection attempts, including those that failed:
CREATE TRIGGER TR_CONNECT ON CONNECT
AS
BEGIN
-- Logging all attempts to connect to the database
IN AUTONOMOUS TRANSACTION DO
INSERT INTO LOG(MSG)
VALUES ('USER ' || CURRENT_USER || ' CONNECTS.');
IF (CURRENT_USER IN (SELECT
USERNAME
FROM
BLOCKED_USERS)) THEN
BEGIN
-- Logging that the attempt to connect
-- to the database failed and sending
-- a message about the event
IN AUTONOMOUS TRANSACTION DO
BEGIN
INSERT INTO LOG(MSG)
VALUES ('USER ' || CURRENT_USER || ' REFUSED.');
POST_EVENT 'CONNECTION ATTEMPT' || ' BY BLOCKED USER!';
END
-- now calling an exception
EXCEPTION EX_BADUSER;
END
END
7.6.17. POST_EVENT
Notifying listening clients about database events in a module
PSQL
POST_EVENT event_name
Argument | Description |
---|---|
event_name |
Event name (message) limited to 127 bytes |
The POST_EVENT
statement notifies the event manager about the event, which saves it to an event table.
When the transaction is committed, the event manager notifies applications that are signalling their interest in the event.
The event name can be some sort of code or a short message: the choice is open as it is just a string up to 127 bytes.
The content of the string can be a string literal, a variable or any valid SQL expression that resolves to a string.
Notifying the listening applications about inserting a record into the SALES
table:
SET TERM ^;
CREATE TRIGGER POST_NEW_ORDER FOR SALES
ACTIVE AFTER INSERT POSITION 0
AS
BEGIN
POST_EVENT 'new_order';
END^
SET TERM ;^
7.7. Trapping and Handling Errors
Firebird has a useful lexicon of PSQL statements and resources for trapping errors in modules and for handling them. Internally-implemented exceptions exist for stalling execution when every sort of standard error occurs in DDL, DSQL and the physical environment.
In PSQL code, exceptions are handled by means of the WHEN … DO
statement.
Handling an exception in the code involves either fixing the problem in situ, or stepping past it;
either solution allows execution to continue without returning an exception message to the client.
An exception results in execution being terminated in the block.
Instead of passing the execution to the END
statement, the procedure moves outward through levels of nested blocks, starting from the block where the exception is caught, searching for the code of the handler that “knows” about this exception.
It stops searching when it finds the first WHEN
statement that can handle this exception.
7.7.1. System Exceptions
An exception is a message that is generated when an error occurs.
All exceptions handled by Firebird have predefined numeric values for context variables (symbols) and text messages associated with them. Error messages are output in English by default. Localized Firebird builds are available, where error messages are translated into other languages.
Complete listings of the system exceptions can be found in Appendix B: Exception Codes and Messages:
7.7.2. Custom Exceptions
Custom exceptions can be declared in the database as persistent objects and called in the PSQL code to signal specific errors;
for instance, to enforce certain business rules.
A custom exception consists of an identifier and a default message of approximately 1000 bytes.
For details, see CREATE EXCEPTION
.
7.7.3. EXCEPTION
Throwing a user-defined exception or re-throwing an exception
PSQL
EXCEPTION [exception_name [custom_message]]
Argument | Description |
---|---|
exception_name |
Exception name |
custom_message |
Alternative message text to be returned to the caller interface when an exception is thrown. Maximum length of the text message is 1,021 bytes |
An EXCEPTION
statement throws the user-defined exception with the specified name.
An alternative message text of up to 1,021 bytes can optionally override the exception’s default message text.
The exception can be handled in the statement, by just leaving it with no specific WHEN … DO
handler and allowing the trigger or stored procedure to terminate and roll back all operations.
The calling application gets the alternative message text, if any was specified;
otherwise, it receives the message originally defined for that exception.
Within the exception-handling block — and only within it — the caught exception can be re-thrown by executing the EXCEPTION
statement without parameters.
If located outside the block, the re-thrown EXCEPTION
call has no effect.
Custom exceptions are stored in the system table |
-
Throwing an exception with dynamically generated text:
… EXCEPTION EX_BAD_TYPE 'Incorrect record type with id ' || new.id; …
-
Throwing an exception upon a condition in the
SHIP_ORDER
stored procedure:CREATE OR ALTER PROCEDURE SHIP_ORDER ( PO_NUM CHAR(8)) AS DECLARE VARIABLE ord_stat CHAR(7); DECLARE VARIABLE hold_stat CHAR(1); DECLARE VARIABLE cust_no INTEGER; DECLARE VARIABLE any_po CHAR(8); BEGIN SELECT s.order_status, c.on_hold, c.cust_no FROM sales s, customer c WHERE po_number = :po_num AND s.cust_no = c.cust_no INTO :ord_stat, :hold_stat, :cust_no; IF (ord_stat = 'shipped') THEN EXCEPTION order_already_shipped; /* Other statements */ END
-
Throwing an exception upon a condition and replacing the original message with an alternative message:
CREATE OR ALTER PROCEDURE SHIP_ORDER ( PO_NUM CHAR(8)) AS DECLARE VARIABLE ord_stat CHAR(7); DECLARE VARIABLE hold_stat CHAR(1); DECLARE VARIABLE cust_no INTEGER; DECLARE VARIABLE any_po CHAR(8); BEGIN SELECT s.order_status, c.on_hold, c.cust_no FROM sales s, customer c WHERE po_number = :po_num AND s.cust_no = c.cust_no INTO :ord_stat, :hold_stat, :cust_no; IF (ord_stat = 'shipped') THEN EXCEPTION order_already_shipped 'Order status is "' || ord_stat || '"'; /* Other statements */ END
-
Logging an error and re-throwing it in the
WHEN
block:CREATE PROCEDURE ADD_COUNTRY ( ACountryName COUNTRYNAME, ACurrency VARCHAR(10) ) AS BEGIN INSERT INTO country (country, currency) VALUES (:ACountryName, :ACurrency); WHEN ANY DO BEGIN -- write an error in log IN AUTONOMOUS TRANSACTION DO INSERT INTO ERROR_LOG (PSQL_MODULE, GDS_CODE, SQL_CODE, SQL_STATE) VALUES ('ADD_COUNTRY', GDSCODE, SQLCODE, SQLSTATE); -- Re-throw exception EXCEPTION; END END
7.7.4. WHEN … DO
Catching an exception and handling the error
PSQL
<block> ::= BEGIN [<compound_statement> ...] [<when_do> ...] END <compound_statement> ::= {<block> | <statement>} <<when_do>> ::= WHEN {<error> [, <error> …] | ANY} DO <compound_statement> <error> ::= { EXCEPTION exception_name | SQLCODE number | GDSCODE errcode }
Argument | Description |
---|---|
exception_name |
Exception name |
number |
SQLCODE error code |
errcode |
Symbolic GDSCODE error name |
compound_statement |
A statement or a block of statements |
The WHEN … DO
statement is used to handle errors and user-defined exceptions.
The statement catches all errors and user-defined exceptions listed after the keyword WHEN
keyword.
If WHEN
is followed by the keyword ANY
, the statement catches any error or user-defined exception, even if they have already been handled in a WHEN
block located higher up.
The WHEN … DO
statements must be located at the end of a block of statements, before the block’s END
keyword, and after any other statement.
The keyword DO
is followed by a statement, or a block of statements inside a BEGIN … END
block, that handle the exception.
The SQLCODE
, GDSCODE
, and SQLSTATE
context variables are available in the context of this statement or block.
The EXCEPTION
statement, without parameters, can also be used in this context to re-throw the error or exception.
The WHEN … DO
statement or block is never executed unless one of the events targeted by its conditions occurs in run-time.
If the statement is executed, even if it actually does nothing, execution will continue as if no error occurred: the error or user-defined exception neither terminates nor rolls back the operations of the trigger or stored procedure.
However, if the WHEN … DO
statement or block does nothing to handle or resolve the error, the DML statement (SELECT
, INSERT
, UPDATE
, DELETE
, MERGE
) that caused the error will be rolled back and none of the statements below it in the same block of statements are executed.
|
Scope of a WHEN … DO
Statement
A WHEN … DO
statement catches errors and exceptions in the current block of statements.
It also catches similar exceptions in nested blocks, if those exceptions have not been handled in them.
All changes made before the statement that caused the error are visible to a WHEN … DO
statement.
However, if you try to log them in an autonomous transaction, those changes are unavailable, because the transaction where the changes took place is not committed at the point when the autonomous transaction is started.
Example 4, below, demonstrates this behaviour.
When handling exceptions, it is sometimes desirable to handle the exception by writing a log message to mark the fault and having execution continue past the faulty record. Logs can be written to regular tables but there is a problem with that: the log records will “disappear” if an unhandled error causes the module to stop executing and a rollback ensues. Use of external tables can be useful here, as data written to them is transaction-independent. The linked external file will still be there, regardless of whether the overall process succeeds or not. |
Examples using WHEN…DO
-
Replacing the standard error with a custom one:
CREATE EXCEPTION COUNTRY_EXIST ''; SET TERM ^; CREATE PROCEDURE ADD_COUNTRY ( ACountryName COUNTRYNAME, ACurrency VARCHAR(10) ) AS BEGIN INSERT INTO country (country, currency) VALUES (:ACountryName, :ACurrency); WHEN SQLCODE -803 DO EXCEPTION COUNTRY_EXIST 'Country already exists!'; END^ SET TERM ^;
-
Logging an error and re-throwing it in the
WHEN
block:CREATE PROCEDURE ADD_COUNTRY ( ACountryName COUNTRYNAME, ACurrency VARCHAR(10) ) AS BEGIN INSERT INTO country (country, currency) VALUES (:ACountryName, :ACurrency); WHEN ANY DO BEGIN -- write an error in log IN AUTONOMOUS TRANSACTION DO INSERT INTO ERROR_LOG (PSQL_MODULE, GDS_CODE, SQL_CODE, SQL_STATE) VALUES ('ADD_COUNTRY', GDSCODE, SQLCODE, SQLSTATE); -- Re-throw exception EXCEPTION; END END
-
Handling several errors in one
WHEN
block... WHEN GDSCODE GRANT_OBJ_NOTFOUND, GDSCODE GRANT_FLD_NOTFOUND, GDSCODE GRANT_NOPRIV, GDSCODE GRANT_NOPRIV_ON_BASE DO BEGIN EXECUTE PROCEDURE LOG_GRANT_ERROR(GDSCODE); EXIT; END ...
8. Built-in Functions
8.1. Context Functions
8.1.1. RDB$GET_CONTEXT()
|
DSQL, PSQL * As a declared UDF it should be available in ESQL
RDB$GET_CONTEXT ('<namespace>', <varname>) <namespace> ::= SYSTEM | USER_SESSION | USER_TRANSACTION <varname> ::= A case-sensitive quoted string of max. 80 characters
Parameter | Description |
---|---|
namespace |
Namespace |
varname |
Variable name. Case-sensitive. Maximum length is 80 characters |
VARCHAR(255)
Retrieves the value of a context variable from one of the namespaces SYSTEM
, USER_SESSION
and USER_TRANSACTION
.
The USER_SESSION
and USER_TRANSACTION
namespaces are initially empty.
The user can create and set variables in them with RDB$SET_CONTEXT()
and retrieve them with RDB$GET_CONTEXT()
.
The SYSTEM
namespace is read-only.
It contains a number of predefined variables, shown below.
DB_NAME
-
Either the full path to the database or — if connecting via the path is disallowed — its alias.
NETWORK_PROTOCOL
-
The protocol used for the connection:
'TCPv4'
,'WNET'
,'XNET'
orNULL
. CLIENT_ADDRESS
-
For TCPv4, this is the IP address. For XNET, the local process ID. For all other protocols this variable is
NULL
. CLIENT_PID
-
Process ID of remote client application.
Added in 2.5.3.
CLIENT_PROCESS
-
Process name of remote client application.
Added in 2.5.3.
CURRENT_USER
-
Same as global
CURRENT_USER
variable. CURRENT_ROLE
-
Same as global
CURRENT_ROLE
variable. ISOLATION_LEVEL
-
The isolation level of the current transaction:
'READ COMMITTED'
,'SNAPSHOT'
or'CONSISTENCY'
. LOCK_TIMEOUT
-
Lock timeout of the current transaction.
Added in 2.5.3.
READ_ONLY
-
Returns
'TRUE'
if current transaction is read-only and'FALSE'
otherwise.Added in 2.5.3.
SESSION_ID
-
Same as global
CURRENT_CONNECTION
variable. TRANSACTION_ID
-
Same as global
CURRENT_TRANSACTION
variable. ENGINE_VERSION
-
The Firebird engine (server) version. Added in 2.1.
If the polled variable exists in the given namespace, its value will be returned as a string of max. 255 characters.
If the namespace doesn’t exist or if you try to access a non-existing variable in the SYSTEM
namespace, an error is raised.
If you request a non-existing variable in one of the other namespaces, NULL
is returned.
Both namespace and variable names must be given as single-quoted, case-sensitive, non-NULL
strings.
select rdb$get_context('SYSTEM', 'DB_NAME') from rdb$database
New.UserAddr = rdb$get_context('SYSTEM', 'CLIENT_ADDRESS');
insert into MyTable (TestField)
values (rdb$get_context('USER_SESSION', 'MyVar'))
8.1.2. RDB$SET_CONTEXT()
|
DSQL, PSQL * As a declared UDF it should be available in ESQL
RDB$SET_CONTEXT ('<namespace>', <varname>, <value> | NULL) <namespace> ::= USER_SESSION | USER_TRANSACTION <varname> ::= A case-sensitive quoted string of max. 80 characters <value> ::= A value of any type, as long as it's castable to a VARCHAR(255)
Parameter | Description |
---|---|
namespace |
Namespace |
varname |
Variable name. Case-sensitive. Maximum length is 80 characters |
value |
Data of any type provided it can be cast to |
INTEGER
Creates, sets or unsets a variable in one of the user-writable namespaces USER_SESSION
and USER_TRANSACTION
.
The USER_SESSION
and USER_TRANSACTION
namespaces are initially empty.
The user can create and set variables in them with RDB$SET_CONTEXT()
and retrieve them with RDB$GET_CONTEXT()
.
The USER_SESSION
context is bound to the current connection.
Variables in USER_TRANSACTION
only exist in the transaction in which they have been set.
When the transaction ends, the context and all the variables defined in it are destroyed.
The function returns 1 when the variable already existed before the call and 0 when it didn’t.
To remove a variable from a context, set it to NULL
.
If the given namespace doesn’t exist, an error is raised.
Both namespace and variable names must be entered as single-quoted, case-sensitive, non-NULL
strings.
select rdb$set_context('USER_SESSION', 'MyVar', 493) from rdb$database
rdb$set_context('USER_SESSION', 'RecordsFound', RecCounter);
select rdb$set_context('USER_TRANSACTION', 'Savepoints', 'Yes')
from rdb$database
-
The maximum number of variables in any single context is 1000.
-
All
USER_TRANSACTION
variables will survive aROLLBACK RETAIN
(seeROLLBACK
Options) orROLLBACK TO SAVEPOINT
unaltered, no matter at which point during the transaction they were set. -
Due to its UDF-like nature,
RDB$SET_CONTEXT
can — in PSQL only — be called like a void function, without assigning the result, as in the second example above. Regular internal functions don’t allow this type of use.
8.2. Mathematical Functions
8.2.1. ABS()
DSQL, PSQL
YES → Read details
ABS (number)
Parameter | Description |
---|---|
number |
An expression of a numeric type |
Numerical
Returns the absolute value of the argument.
8.2.2. ACOS()
DSQL, PSQL
YES → Read details
ACOS (number)
Parameter | Description |
---|---|
number |
An expression of a numeric type within the range [-1; 1] |
DOUBLE PRECISION
Returns the arc cosine of the argument.
-
The result is an angle in the range [0, pi].
-
If the argument is outside the range [-1, 1],
NaN
is returned.
8.2.3. ASIN()
DSQL, PSQL
YES → Read details
ASIN (number)
Parameter | Description |
---|---|
number |
An expression of a numeric type within the range [-1; 1] |
DOUBLE PRECISION
Returns the arc sine of the argument.
-
The result is an angle in the range [-pi/2, pi/2].
-
If the argument is outside the range [-1, 1],
NaN
is returned.
8.2.4. ATAN()
DSQL, PSQL
YES → Read details
ATAN (number)
Parameter | Description |
---|---|
number |
An expression of a numeric type |
DOUBLE PRECISION
The function ATAN
returns the arc tangent of the argument.
The result is an angle in the range ←pi/2, pi/2>.
8.2.5. ATAN2()
DSQL, PSQL
YES → Read details
ATAN2 (y, x)
Parameter | Description |
---|---|
y |
An expression of a numeric type |
x |
An expression of a numeric type |
DOUBLE PRECISION
Returns the angle whose sine-to-cosine ratio is given by the two arguments, and whose sine and cosine signs correspond to the signs of the arguments. This allows results across the entire circle, including the angles -pi/2 and pi/2.
-
The result is an angle in the range [-pi, pi].
-
If x is negative, the result is pi if y is 0, and -pi if y is -0.
-
If both y and x are 0, the result is meaningless. Starting with Firebird 3, an error will be raised if both arguments are 0. At v.2.5.4, it is still not fixed in lower versions. For more details, visit Tracker ticket CORE-3201.
-
A fully equivalent description of this function is the following:
ATAN2(y, x)
is the angle between the positive X-axis and the line from the origin to the point (x, y). This also makes it obvious thatATAN2(0, 0)
is undefined. -
If x is greater than 0,
ATAN2(y, x)
is the same asATAN(y/x)
. -
If both sine and cosine of the angle are already known,
ATAN2(sin, cos)
gives the angle.
8.2.6. CEIL()
, CEILING()
DSQL, PSQL
YES → Read details (Affects CEILING
only)
CEIL[ING] (number)
Parameter | Description |
---|---|
number |
An expression of a numeric type |
BIGINT
for exact numeric number, or DOUBLE PRECISION
for floating point number
Returns the smallest whole number greater than or equal to the argument.
8.2.7. COS()
DSQL, PSQL
YES → Read details
COS (angle)
Parameter | Description |
---|---|
angle |
An angle in radians |
DOUBLE PRECISION
Returns an angle’s cosine. The argument must be given in radians.
-
Any non-
NULL
result is — obviously — in the range [-1, 1].
8.2.8. COSH()
DSQL, PSQL
YES → Read details
COSH (number)
Parameter | Description |
---|---|
number |
A number of a numeric type |
DOUBLE PRECISION
Returns the hyperbolic cosine of the argument.
-
Any non-
NULL
result is in the range [1, INF].
8.2.9. COT()
DSQL, PSQL
YES → Read details
COT (angle)
Parameter | Description |
---|---|
angle |
An angle in radians |
DOUBLE PRECISION
Returns an angle’s cotangent. The argument must be given in radians.
8.2.10. EXP()
DSQL, PSQL
EXP (number)
Parameter | Description |
---|---|
number |
A number of a numeric type |
DOUBLE PRECISION
Returns the natural exponential, enumber
8.2.11. FLOOR()
DSQL, PSQL
YES → Read details
FLOOR (number)
Parameter | Description |
---|---|
number |
An expression of a numeric type |
BIGINT
for exact numeric number, or DOUBLE PRECISION
for floating point number
Returns the largest whole number smaller than or equal to the argument.
8.2.12. LN()
DSQL, PSQL
YES → Read details
LN (number)
Parameter | Description |
---|---|
number |
An expression of a numeric type |
DOUBLE PRECISION
Returns the natural logarithm of the argument.
-
An error is raised if the argument is negative or 0.
8.2.13. LOG()
DSQL, PSQL
YES → Read details
LOG (x, y)
Parameter | Description |
---|---|
x |
Base. An expression of a numeric type |
y |
An expression of a numeric type |
DOUBLE PRECISION
Returns the x-based logarithm of y.
-
If either argument is 0 or below, an error is raised. (Before 2.5, this would result in
NaN
,±INF
or 0, depending on the exact values of the arguments.) -
If both arguments are 1,
NaN
is returned. -
If x = 1 and y < 1,
-INF
is returned. -
If x = 1 and y > 1,
INF
is returned.
8.2.14. LOG10()
DSQL, PSQL
2.5
YES → Read details
LOG10 (number)
Parameter | Description |
---|---|
number |
An expression of a numeric |