3. Introduction to the SQL Language
In this section of the chapter, we present the first SQL commands for creating and working with a single table. We generally provide a simplified version of these commands. Their full syntax is available in the Firebird reference guides (see Section 2.2).
A database is used by people with various skill sets:
- the database administrator is generally someone proficient in SQL and databases. They are the ones who create the tables, as this operation is usually performed only once. Over time, they may need to modify the structure. A database is a collection of tables linked by relationships. The database administrator defines these relationships. They also grant permissions to the database’s various users. For example, they may specify that a particular user has the right to view the contents of a table but not to modify it.
- The database user is the person who brings the data to life. Depending on the permissions granted by the database administrator, they will add, modify, and delete data in the database’s various tables. They will also analyze the data to extract information useful for the smooth operation of the business, the administration, etc.
In section 2.6, we introduced the SQL editor of the [IB-Expert] tool. This is the tool we will be using. Let’s review a few points:
- The SQL Editor can be accessed via the [Tools/SQL Editor] menu option or by pressing the [F12] key

This opens an [SQL Editor] window where we can type an SQL command:

The screenshot above is often represented by the text below:
3.1. Firebird Data Types
When creating a table, you must specify the data type that a table column can contain. Here, we present the most common Firebird data types. Note that these data types may vary from one DBMS to another.
integer in the range [-32768, 32767]: 4 | |
integer in the range [–2,147,483,648, 2,147,483,647]: -100 | |
real number with n digits, m of which are decimal NUMERIC(5,2): -100.23, +027.30 | |
real number approximated to 7 significant digits: 10.4 | |
real number approximated to 15 significant digits: -100.89 | |
A string of exactly N characters. If the stored string has fewer than N characters, it is padded with spaces. CHAR(10): 'ANGERS ' (4 trailing spaces) | |
string of up to N characters VARCHAR(10): 'ANGERS' | |
a date: '2006-01-09' (YYYY-MM-DD format) | |
a time: '16:43:00' (HH:MM:SS format) | |
both date and time: '2006-01-09 16:43:00' (format YYYY-MM-DD HH:MM:SS) |
The CAST() function allows you to convert from one type to another when necessary. To convert a value V declared as type T1 to type T2, you write: CAST(V,T2). You can perform the following type conversions:
- number to string. This type conversion is implicit and does not require the use of the CAST function. Thus, the operation 1 + '3' does not require conversion of the character '3'. Its result is the number 4.
- DATE, TIME, TIMESTAMP to strings and vice versa. Thus
- TIMESTAMP to TIME or DATE and vice versa
In a table, a row may have columns with no value. We say that the value of the column is the NULL constant. You can check for the presence of this value using the operators
IS NULL / IS NOT NULL
3.2. Creating a table
To learn how to create a table, we’ll start by creating one in [Design] mode with IBExpert. To do this, we’ll follow the method described in section 2.3. This will create the following table:

This table will be used to record the books purchased by a library. The meaning of the fields is as follows:
Name | Type | Constraint | Meaning |
This table, which was created using the IBEXPERT wizard, could have been created directly using SQL statements. To view these, simply check the [DDL] tab for the table:

The SQL code used to create the [BIBLIO] table is as follows:
- line 1: owner Firebird - indicates the SQL dialect level used
- line 2: Firebird-specific - specifies the character set used
- lines 6–14: SQL standard: creates the BIBLIO table by defining the name and data type of each of its columns.
- line 16: SQL standard: creates a constraint specifying that the TITLE column does not allow duplicates
- line 17: SQL standard: specifies that the [ID] column is the table’s primary key. This means that no two rows in the table can have the same ID. This is similar to the [UNIQUE NOT NULL] constraint on the [TITLE] column, and in fact the TITLE column could have served as the primary key. The current trend is to use primary keys that have no specific meaning and are generated by the DBMS.
The syntax for the [CREATE TABLE] command is as follows:
CREATE TABLE table (column_name1 column_type1 column_constraint1, column_name2 column_type2 column_constraint2, ..., column_nameN column_typeN column_constraintN, other constraints) | |||||||||
creates the table table with the specified columns
|
The [BIBLIO] table could also have been created with the following SQL statement:
Let's demonstrate this. Let's open this query in an SQL editor (F12) to create a table we'll call [BIBLIO2]:

After execution, you must commit the transaction to see the result in the database:

Once this is done, the table appears in the database:

By double-clicking on its name, we can view its structure:

We can see the definition we created for the [BIBLIO2] table
3.3. Deleting a table
The SQL statement to delete a table is as follows:
DROP TABLE table | |
Deletes [table] |
To delete the [BIBLIO2] table we just created, we now execute the following SQL command:

and confirm it with [Commit]. The [BIBLIO2] table is deleted:

3.4. Filling a Table
Let’s insert a row into the [BIBLIO] table we just created:

Confirm the addition of the row with [Commit], then right-click on the added row:

and, as shown above, copy the inserted row to the clipboard as an INSERT SQL statement. Next, open any text editor and paste what we just copied. We get the following SQL code:
INSERT INTO BIBLIO (ID,TITLE,AUTHOR,GENRE,PURCHASE_DATE,PRICE,AVAILABLE) VALUES (1,'Candide','Voltaire','Essay','18-OCT-1985',140,'o');
The syntax for an SQL INSERT statement is as follows:
insert into table [(column1, column2, ..)] values (value1, value2, ....) | |
adds a row (value1, value2, ..) to the table. These values are assigned to column1, column2, ... if they exist; otherwise, to the table's columns in the order in which they were defined. |
To insert new rows into the [BIBLIO] table, we will type the following INSERT statements into the SQL editor. We will execute and commit these statements one by one. We will use the [New Query] button to move on to the next INSERT statement.
After committing [Commit] the various SQL statements, we obtain the following table:
![]() |
3.5. Querying a table
3.5.1. Introduction
In the SQL editor, type the following command:

and execute it. We get the following result:

The SELECT statement is used to retrieve data from database tables. This statement has a very rich syntax. Here, we will focus on the syntax for querying a single table. We will cover querying multiple tables simultaneously at a later time. The syntax for the SQL [SELECT] statement is as follows:
SELECT [ALL|DISTINCT] [*|expression1 alias1, expression2 alias2, ...] FROM table | |
displays the values of expression1 for all rows in the table. expression1 can be a column or a more complex expression. The * symbol denotes all columns. By default, all rows in the table (ALL) are displayed. If DISTINCT is present, identical selected rows are displayed only once. The values of expression1 are displayed in a column titled expression1 or alias1 if the latter was used. |
Examples:



In the examples above, we have assigned aliases (BOOK_TITLE, PURCHASE_PRICE) to the requested columns.
3.5.2. Displaying rows that meet a condition
SELECT .... WHERE condition | |
Only rows that meet the condition are displayed |
Examples


One of the books has the genre 'novel' and not 'Novel'. We use the UPPER function, which converts a string to uppercase, to get all the novels.

We can combine conditions using logical operators
logical AND | |
Logical OR | |
Logical negation |





![]() |

3.5.3. Displaying rows in a specific order
To the previous syntaxes, you can add an ORDER BY clause specifying the desired display order:
SELECT .... ORDER BY expression1 [asc|desc], expression2 [asc|desc], ... | |
The result rows of the selection are displayed in the order of 1: ascending (asc / ascending, which is the default) or descending (desc / descending) order of expression1 2: if expression1 is equal, the display is based on the values of expression2 etc. |
Examples:





3.6. Deleting rows from a table
DELETE FROM table [WHERE condition] | |
Deletes table rows that meet the condition. If no condition is specified, all rows are deleted. |
Examples:

The two commands below are executed one after the other:

3.7. Modifying the contents of a table
update table set column1 = expression1, column2 = expression2, ... [where condition] | |
For table rows that satisfy the condition (all rows if there is no condition), column1 is set to the value of expression1. |
Examples:
We capitalize all genres:

We check:

Display the prices:

The price of novels increases by 5%:
Let's check:

3.8. Permanently updating a table
When changes are made to a table, Firebird actually applies them to a copy of the table. These changes can then be made permanent or rolled back using the COMMIT and ROLLBACK commands.
COMMIT | |
Makes the updates made to the tables since the last COMMIT permanent. |
ROLLBACK | |
Reverts all changes made to the tables since the last COMMIT. |
A COMMIT is performed implicitly at the following times: a) Upon logging out of Firebird b) After each command affecting the structure of tables: CREATE, ALTER, DROP. |
Examples
In the SQL editor, you can restore the database to a known state by committing all operations performed since the last COMMIT or ROLLBACK:
We retrieve the list of titles:

Deleting a title:
Verification:

The title has been successfully deleted. Now we'll roll back all changes made since the last COMMIT / ROLLBACK:
Verification:

The deleted title has reappeared. Now let’s retrieve the list of prices:

Let's set all prices to zero.
Let's check the prices:

Let's undo the changes made to the database:
and check the prices again:

We have restored the original prices.
3.9. Adding rows from one table to another
It is possible to add rows from one table to another when their structures are compatible. To demonstrate this, let’s start by creating a table [BIBLIO2] with the same structure as [BIBLIO].
In the IBExpert database explorer, double-click the [BIBLIO] table to access the [DDL] tab:

In this tab, you’ll find the list of SQL statements used to generate the [BIBLIO] table. Copy all of this code to the clipboard (CTRL-A, CTRL-C). Then open a tool called [Script Executive] that allows you to execute a list of SQL statements:

A text editor opens, into which we can paste (CTRL-V) the text previously copied to the clipboard:

A list of SQL commands is often called an SQL script. [Script Executive] will allow us to execute such a script, whereas the SQL editor only allowed the execution of a single command at a time. The current SQL script creates the [BIBLIO] table. Let’s make it create a table named [BIBLIO2]. To do this, simply change [BIBLIO] to [BIBLIO2]:
Let's run this script using the [Run Script] button below:

The script is executed:

and we can see the new table in the database explorer:

If we double-click on [BIBLIO2] to check its contents, we find that it is empty, which is normal:

A variant of the SQL INSERT statement allows you to insert rows from one table into another:
INSERT INTO table1 [(column1, column2, ...)] SELECT column1, column2, ... FROM table2 WHERE condition | |
The rows from table2 that satisfy the condition are added to table1. The columns column1, column2, ... from table2 are assigned in order to column1, column2, ... in table1 and must therefore be of compatible types. |
Let's go back to the SQL editor:

and execute the following SQL statement:
which inserts into [BIBLIO2] all rows from [BIBLIO] corresponding to a novel. After executing the SQL statement, let’s commit it with a [Commit]:
Now, let’s view the data in the [BIBLIO2] table:

3.10. Deleting a table
DROP TABLE table | |
deletes table |
Example: Dropping the BIBLIO2 table
Confirm the change:
In the database explorer, refresh the table view:

We see that the [BIBLIO2] table has been deleted:

3.11. Modifying a table's structure
ALTER TABLE table [ ADD column_name1 column_type1 column_constraint1] [ALTER column_name2 TYPE column_type2] [DROP column_name3] [ADD constraint] [DROP CONSTRAINT constraint_name] | |
allows you to add (ADD), modify (ALTER), and drop (DROP) table columns. The syntax column_name1 column_type1 column_constraint1 is the same as that of CREATE TABLE. You can also add or drop table constraints. |
Example: Execute the following two SQL commands sequentially in the SQL editor
In the database explorer, let’s check the structure of the [BIBLIO] table:

The changes have been applied. Let’s see how the table’s contents have changed:

The new [NB_PAGES] column has been created but has no values. Let’s drop this column:
Let's check the new structure of the [BIBLIO] table:

The [NB_PAGES] column has indeed disappeared.
3.12. Views
It is possible to have a partial view of a table or multiple tables. A view behaves like a table but does not contain data. Its data is extracted from other tables or views. A view has several advantages:
- A user may be interested only in certain columns and rows of a given table. The view allows them to see only those rows and columns.
- The owner of a table may wish to grant only limited access to other users. A view allows them to do so. The users they have authorized will only have access to the view they have defined.
3.12.1. Creating a view
CREATE VIEW view_name AS SELECT column1, column2, ... FROM table WHERE condition [ WITH CHECK OPTION ] | |
creates the view view_name. This is a table with the structure column1, column2, ... from table and, for rows, the rows from table that satisfy condition (all rows if there is no condition) | |
This optional clause specifies that inserts and updates to the view must not create rows that the view could not select. |
Note The syntax of CREATE VIEW is actually more complex than that presented above and allows, in particular, the creation of a view from multiple tables. To do this, the SELECT statement simply needs to reference multiple tables (see the following chapter).
Examples
We create a view from the biblio table that includes only novels (row selection) and only the title, author, and price columns (column selection):
In the database explorer, refresh the view (F5). A view appears:

We can view the SQL statement associated with the view. To do this, double-click on the [ROMANS] view:

A view is like a table. It has a structure:

and content:

A view is used like a table. You can run SQL queries on it. Here are a few examples to try in the SQL editor:

Is the new novel visible in the [ROMANS] view?

Let’s add something other than a novel to the [BIBLIO] table:
SQL> insert into biblio(id,title,author,genre,purchase,price,available) values (11,'Poèmes saturniens','Verlaine','Poem','02-Sep-92',200,'o');
Let's check the [BIBLIO] table:

Let's check the [ROMANS] view:

The added book is not in the [ROMANS] view because it did not have upper(genre)='ROMAN'.
3.12.2. Updating a view
You can update a view just as you would a table. All tables from which the view’s data is extracted are affected by this update. Here are a few examples:
SQL> insert into biblio(id,title,author,genre,purchase_date,price,available) values (13,'The Red and the Black','Stendhal','Novel','Oct 3, 1992',110,'o')


We delete a row from the [ROMANS] view:


The row deleted from the [NOVELS] view has also been deleted from the [BIBLIO] table. We will now increase the price of the books in the [NOVELS] view:
Let's check in [NOVELS]:

What was the impact on the [BIBLIO] table?

The prices of the novels have indeed been increased by 5% in [BIBLIO] as well.
3.12.3. Deleting a view
DROP VIEW view_name | |
deletes the view named |
Example
In the database explorer, you can refresh the view (F5) to see that the [ROMANS] view has disappeared:

3.13. Using group functions
There are functions that, instead of operating on each row of a table, operate on groups of rows. These are essentially statistical functions that allow us to calculate the mean, standard deviation, etc., of the data in a column.
SELECT f1, f2, .., fn FROM table [ WHERE condition ] | |
Calculates the statistical functions fi on all rows of the table that satisfy the condition. |
SELECT f1, f2, .., fn FROM table [ WHERE condition ] [ GROUP BY expr1, expr2, ..] | |
The GROUP BY keyword divides the table rows into groups. Each group contains the rows for which the expressions expr1, expr2, ... have the same value. Example: GROUP BY genre groups together books of the same genre. The GROUP BY author,genre clause would group together books with the same author and the same genre. The WHERE condition first removes rows from the table that do not meet the condition. Then, groups are formed by the GROUP BY clause. The aggregate functions are then calculated for each group of rows. |
SELECT f1, f2, .., fn FROM table [ WHERE condition ] [ GROUP BY expression] [ HAVING group_condition] | |
The HAVING clause filters the groups formed by the GROUP BY clause. It is therefore always associated with the presence of the GROUP BY clause. Example: GROUP BY genre HAVING genre!='NOVEL' |
The available statistical functions are as follows:
average of expression | |
number of rows for which expression has a value | |
total number of rows in the table | |
Maximum value of expression | |
min of expression | |
sum of expression |
Examples

Average price? Maximum price? Minimum price?


Average price of a novel? Maximum price?

How many comic books?

How many novels cost less than 100 F?


Number of books and average price per book for books of the same genre?
SQL> select upper(genre) as GENRE, avg(price) as AVG_PRICE, count(*) as COUNT from biblio group by upper(genre)

Same question, but only for books that are not novels:
SQL>
select uppercase(genre) GENRE,avg(price) AVERAGE_PRICE,count(*) COUNT
from biblio
group by UPPER(genre)
where upper(GENRE) != 'NOVEL'

Same query, but only for books under 150 F:
SQL>
select uppercase(genre) as GENRE, average(price) as AVERAGE_PRICE, count(*) as COUNT
from library
where price<150
group by upper(genre)
where upper(GENRE) != 'NOVEL'

Same query, but we only keep groups with an average book price >100 F
SQL>
select uppercase(genre) as GENRE, average(price) as AVERAGE_PRICE, count(*) as COUNT
from biblio
group by upper(genre)
where avg(price) > 100

3.14. Creating an SQL script for a table
SQL is a standard language that can be used with many DBMSs. To be able to switch from one DBMS to another, it is useful to export a database or simply certain elements of it in the form of an SQL script which, when re-run in another DBMS, will be able to recreate the elements exported in the script.
Here, we will export the [BIBLIO] table. Let’s select the [Extract Metadata] option:

Note that you must be in the database from which you want to export elements. The option launches a wizard:
![]() |
where to generate the SQL script:
| |
file name if the [File] option is selected | |
what to export | |
Buttons to select (->) or deselect (<-) the objects to export |
If we wanted to export the entire database, we would check the [Extract All] option above. We simply want to export the BIBLIO table. To do this, using [4], we select the [BIBLIO] table, and using [2], we specify a file:

If we stop here, only the structure of the [BIBLIO] table will be exported. To export its contents, we need to use the [Data Tables] tab:
![]() |
Use [1] to select the [BIBLIO] table:
![]() |
Use [2] to generate the SQL script:

Let’s accept the prompt. This allows us to view the generated script in the [biblio.sql] file:
- Lines 1 through 3 are comments
- Lines 5 through 12 are Firebird-specific SQL
- the remaining lines are standard SQL that should be executable in a DBMS that supports the data types declared in the BIBLIO table.
Let’s run this script within Firebird to create a BIBLIO2 table that will be a clone of the BIBLIO table. To do this, use [Script Executive] (Ctrl-F12):

Let’s load the [biblio.sql] script we just generated:

Modify it to keep only the table creation and row insertion parts. The table is renamed [BIBLIO2]:
CREATE TABLE BIBLIO2 (
ID INTEGER NOT NULL,
TITLE VARCHAR(30) NOT NULL,
AUTHOR VARCHAR(20) NOT NULL,
GENRE VARCHAR(30) NOT NULL,
PURCHASE_DATE DATE NOT NULL,
PRICE NUMERIC(6,2) DEFAULT 10 NOT NULL,
AVAILABLE CHAR(1) NOT NULL
);
INSERT INTO BIBLIO2 (ID, TITLE, AUTHOR, GENRE, PURCHASE_DATE, PRICE, AVAILABLE) VALUES (2, 'Les fleurs du mal', 'Baudelaire', 'POEM', '1978-01-01', 120, 'n');
...
COMMIT WORK;
Let's run this script:
![]() | ![]() |
We can verify in the database explorer that the [BIBLIO2] table has been created and that it has the expected structure and content:
![]() | ![]() |








