Skip to content

Introduction to InterBase SQL

Introduction to InterBase SQL

Written by Craig Stuntz. Edited and extended by Wayne Niddery

Presented by Wayne Niddery at BorCon 2002


Table of Contents


Introduction

SQL is not difficult to learn — which is good since any attempt to bypass learning it when working with a database server will almost certainly end in an inefficient failure. This session will introduce the basics of SQL (Structured Query Language, occasionally pronounced "sequel") grammar, and then progress to include intermediate syntax such as JOINs, aggregate functions. Finally, some Interbase-specific keywords and optimizations will be covered.

All database servers are slightly different, but most of them use SQL as the primary interface for data modification and retrieval. SQL is an ANSI standard, and was designed to provide a database server-independent way of requesting or modifying data. In practice, the standard, in spite of its monstrous length, leaves enough details for the implementers (the developers of the database servers) to decide upon, that writing a complex query which will work on every DB server on the market can range from tricky to impossible. Since most DB producers add proprietary features to their SQL implementation, SQL is only partially successful as a way to write database-independent client software.

While they’re significant enough to frustrate developers attempting to make a single query run against different servers, these differences are not so great that you will have to re-learn your SQL skills from scratch when moving from, say, SQL Server to InterBase. Once you’ve learned basic SQL, you should be able to do productive work with most of the popular commercial database servers.

All of the SQL examples in this paper are designed to work with employee.gdb, one of the example databases which ships with InterBase. Feel free to try each query using the ISQL window in IBConsole. There is a lot of information in this paper, and if you’re new to SQL you may find it helpful to try out each section as you go.

The four essential database operations

Generally, users need to be able to do one of four things with a particular record (often referred to as CRUD)

  1. Create a new record
  2. Read the record
  3. Update the data in the record
  4. Delete the record

SELECT

The SELECT verb is used to retrieve one or more records from one or more tables in the database. Some database servers allow you to retrieve records from multiple databases in a single SELECT statement, but InterBase limits SELECT statements to tables, views, and procedures within a single database.

A simple SELECT statement looks like this:

SELECT
  EMP_NO, LAST_NAME
FROM EMPLOYEE

A SELECT statement produces a result set, or a collection of data produced by the server in response to a query. When dealing with data from a database in a client application, it is helpful to think of data in terms of result sets instead of as tables, since the returned records may or may not correspond to a single physical table in the database.

This particular SELECT statement will produce a result set with two columns, and with one row for every row in the EMPLOYEE table. Each row in the result set will have one column corresponding to EMP_NO, and one column corresponding to LAST_NAME.

Another way to write this query is:

SELECT
  EMPLOYEE.EMP_NO, EMPLOYEE.LAST_NAME
FROM EMPLOYEE

In this case, we have specified the table name both in the list of fields we’d like in the result set, and in the FROM clause, where table names need to be listed. It’s not necessary to do this in a SELECT statement which only references a single table, but this syntax will be useful later on when we examine how to join data from multiple tables together into a single SQL statement.

Specifying a fields list

In the queries above, we have specified that we’d like two fields from the EMPLOYEE table inour result set. There is a shorthand syntax to indicate that we’d like every field from the EMPLOYEE table in the result set. Just specify "*" instead of a list of fields:

SELECT *
FROM EMPLOYEE 

This syntax is useful when running queries against a table when you’re not sure what the field names are, but I don’t recommend that you use it in client applications. The reason is that network traffic is one of the biggest bottlenecks in an application which works with a database server. The less network traffic, the better! So it is wise to specify only the columns which your application absolutely requires in your SELECT statements. (We’ll examine how to retrieve only the rows your application absolutely requires in just a moment.) Even if you need every column in a given table, it is still wise to specify the list of fields explicitly instead of using "*" because additional columns not required by your application might be added to the table later on.

So you should always specify the field names required by your application when writing SELECT statements. You can specify as many fields as your application requires, just separate them with commas like in the example above.

Restricting returned records with WHERE

For the same reason that it is advisable to specify only the columns required by your application in a SELECT statement — network bandwidth — it is even more advisable to restrict the rows returned to only those your user is interested in seeing. It is not at all uncommon to have hundreds of megabytes of data in a single table, and chances are very good that your users only want to see a few of these records at a time. Chances are even better that they don’t want to wait for millions of records they’re not interested in to cross the network before they can see the 10 or so records that they asked for.

A note for those migrating older applications where users could simply browse entire tables.
I regularly encounter a great deal of resistence to the idea that this mode of operation must change. The usual excuse is "the users need to be able to see all the records" and "they’ve always done it that way".
The short answer is: No, they do not need to see all the records.
No user can do anything productive browsing back and forth among thousands of records, they are always looking for particular records, or at worse, a small set of related records. The key is to give them an easy to use means of specifying what they are actually looking for. While there are often a few users who continue to insist, the vast majority catch on easily and find it much more productive.

A WHERE clause is the part of a select statement used to restrict the rows returned as a part of a result set. Here is an example of a SELECT statement with a WHERE clause added:

SELECT
  LAST_NAME
FROM EMPLOYEE
WHERE
  EMP_NO = 20

Imagine that we want to know the name of employee #20. This query will return that information with minimal network traffic. If we looked at the metadata for the employee table we would find that there is a constraint, in the form of a Primary Key, placed on the EMP_NO column which tells the database server to ensure that values in this column are unique — no two employee records can have the same value in the EMP_NO column. Therefore, the query above, which specifies a single value for EMP_NO in the WHERE clause will return at most one row. It would return no rows if no employee with EMP_NO = 20 existed.

Comparison Operators: =, <, < =, > =, >, <>

The above query used the Equals operator. As with programming languages, the other standard comparison operators are also available. The Not Equal operator can be specified as either"<>" or "!=".

Thus…

SELECT
  LAST_NAME
  FROM
EMPLOYEE WHERE
EMP_NO <> 20

… will bring back all employee names except the one for employee #20.

SELECT
  LAST_NAME
  FROM
EMPLOYEE WHERE
  EMP_NO <= 20

… will bring back names for those employees having an Emp_No of less than or equal to 20.

Boolean Operators: AND, OR, NOT

Sometimes we need more complex conditions in a WHERE clause. The AND, OR, and NOT boolean operators can be used to combine multiple conditions:

SELECT
  LAST_NAME
FROM EMPLOYEE
WHERE
  ((EMP_NO >= 20)
    AND
  (EMP_NO <= 30))
    AND
  (NOT (JOB_CODE = ‘VP’))

The use of parentheses when combining multiple conditions is strongly recommended. There is a set order of operations, but using parentheses will save confusion. Note that strings and dates are always specified with single quotes, since double quotes are reserved for a special SQL feature called delimited identifiers. We’ll discuss delimited identifiers later. Much later, if you’re lucky…

Conditions: IN, BETWEEN, LIKE

We can also do wildcard matching using LIKE. LIKE works similarly to =, except that it can accept wildcard characters. The character "%" matches a string of any length. The character "_" matches a single character. Here is an example of how to use LIKE:

SELECT
  EMP_NO, LAST_NAME
FROM EMPLOYEE
WHERE
  (LAST_NAME LIKE ‘S%’)
    OR
  (LAST_NAME LIKE ‘___’)

This query will return one row in its result set for any employee whose name starts with "S" or whose name is three characters in length. (Hey, I didn’t say it was a useful query.) Note that, depending upon the collation used, LIKE may be case-sensitive, and ‘S’ may not equal ’s’.

Be careful when using LIKE since the database server won’t always be able to optimize your query very well. This can be dangerous when selecting from a table with a lot of records in it. We’ll cover basic query optimization later on.

BETWEEN is useful for selecting ranges of values. It is inclusive of the specified values:

SELECT
  LAST_NAME
FROM EMPLOYEE
WHERE
  HIRE_DATE BETWEEN ‘1/1/2001′ AND ‘12/31/2001′

IN allows you to select records matching a specified set of values:

SELECT
  LAST_NAME
FROM EMPLOYEE
WHERE
  EMP_NO IN (20, 24, 28, 44)

IN can also be used with sub-selects as you will see.

Dealing with NULL

Please read the next paragraph very carefully:

NULL is not a value. It is a state representing no value.

A field in a table can have many different values. If it’s an integer field, it can have about four billion values. But there are only two states: NULL and NOT NULL. NULL means that either no value has ever been specified for that field in that particular row, or the NULL state has been explicitly assigned to it.

NULL is not the same thing as zero, or an empty string. The SQL rules for dealing with NULL in comparisons and operations (addition, subtraction, and the like) are very simple: the result of any operator and NULL is always NULL. So NULL + 100 = NULL, a string concatenated with NULL is also NULL

In a WHERE clause, any comparison with NULL will always evaluate to FALSE. So the following query:

SELECT
  EMP_NO, LAST_NAME
FROM EMPLOYEE
WHERE
  (HIRE_DATE = NULL) /* Don’t do this! */

…will never return any records, since nothing can ever equal NULL, not even NULL itself!

However, we sometimes want to return records where a given column is in the NULL state, so there is a special operator for this: IS

SELECT
  EMP_NO, LAST_NAME
FROM EMPLOYEE
WHERE
  (HIRE_DATE IS NULL)

Likewise, we might want to return the list of records where the HIRE_DATE column is not in the NULL state:

SELECT
  EMP_NO, LAST_NAME
FROM EMPLOYEE
WHERE
  (HIRE_DATE IS NOT NULL)

Dealing with Dates

There is often confusion about the correct format to use when expressing a date as a string literal. InterBase will accept a few different formats, but you should select one and use it consistently to avoid confusion when coding. The most common format is ‘mm/dd/yyyy’. Other accepted formats include ‘mm-dd-yyyy’, ‘dd.mm.yyyy’ and ‘yyyy.mm.dd’.

Dates must always be in single quotes. InterBase also accepts a few special quoted strings as the dates they imply, these are ‘TODAY’, ‘YESTERDAY’, ‘TOMORROW’. Starting with version 6.0, the more standard keyword CURRENT_DATE should be used, you can specify tomorrow with CURRENT_DATE + 1.

Within Delphi/BCB, one should always use the provided AsDateTime methods available in database field objects in order to avoid depending on the field objects to convert from string values. Converting strings to dates depends on the settings of individual workstations (e.g. Windows settings).

Ordering result sets

SQL result sets are always unordered unless you specify otherwise. Put another way, you cannot expect the rows in the result set to come back in the same order even if you run the exact same query twice unless you specify that you’d like the result set ordered. The records might come back in storage order (the order on the disk), they might come back in random order — you just don’t know.

So how do you tell the server that you’d like the records ordered? Simply specify which columns should be used to sort the records:

SELECT
  EMP_NO, LAST_NAME
FROM EMPLOYEE
WHERE
  (HIRE_DATE IS NOT NULL)
ORDER BY
  LAST_NAME

This query will return the records in alphabetical order, based upon the employee’s name. They’ll be in ascending order (that is, A to Z), because that is the default and we did not specify descending order.

Note that two employees might have the same name. Since we didn’t specify how to order two records with the same value in the LAST_NAME column, we will receive them in the result set in a random order — if there are two employees named "Smith" we simply cannot expect them to sort in any particular order unless we specify more fields in the ORDER BY clause:

SELECT
  EMP_NO, LAST_NAME
FROM EMPLOYEE
WHERE
  (HIRE_DATE IS NOT NULL)
ORDER BY
  LAST_NAME, HIRE_DATE DESC

In this query, we’re telling the server to first order the records by the LAST_NAME column (in ascending order, since there is no DESC next to name), and then order any group of records with the same name in descending order by the value in the HIRE_DATE column. HIRE_DATE is not one of the values we’ve selected in the result set, but that’s OK; there’s no requirement that a column be mentioned in the fields list in order to be used in the ORDER BY clause.

As of version 6.5, InterBase will always put records with a NULL state at the top of a list of sorted records, whether the list is ascending or descending. Prior versions sorted NULL to the bottom.

Parameterized queries

It is common in client applications to run very similar queries over and over again. A good example is a master-detail form. As we scroll through the result set representing the master table, the query for the detail result set will be run many times, once for each record.

Because this is so common, there is a special optimization for it supported by nearly every database server on the market, called a parameterized query.

When you type some SQL and send it to the server, there is a lot which has to happen in order to produce your result set. The server must parse your SQL into an internal structure it can understand. The server’s optimizer must examine the indices on the table and determine if any of them can be used to run the query more quickly. This is called preparing a query. On the client application, the component sets you’re using to build your application typically do some actions behind the scenes, such as interrogating the system table for field lengths and the like.

All of this takes time, and if you know ahead of time that you’ll be running the same query over and over again, it’s redundant after the first time. When you use a parameterized query, you are in effect telling the server that you’ll be running more or less the same query several times, and changing only the values used for comparison in the WHERE clause.

For example, let’s say we want a list of projects for a given employee. We’ll use this query on a master detail form which allows the user to scroll through a list of employees and see the projects each employee is working on.The "master" query will be one of the queries above which returns EMP_NO as part of the result set. Now we need to write a detail query which shows projects for a given master record:

SELECT
  PROJ_ID
FROM EMPLOYEE_PROJECT
WHERE
  EMP_NO = :EMP_NO

The colon in this query means that the second reference to EMP_NO is not a field but is a parameter. The parameter, in this case, has the same name as the field EMP_NO, but that’s not a requirement. We can now tell the server to prepare this query and then run it multiple times changing only the value of the parameter. It would be like running these two, separate queries:

SELECT
  PROJ_ID
FROM EMPLOYEE_PROJECT
WHERE
  EMP_NO = 20

SELECT
  PROJ_ID
FROM EMPLOYEE_PROJECT
WHERE
  EMP_NO = 21

…without the overhead of preparing two separate SQL statements.

You can specify any number of parameters in a query. Each time the query is executed, you can supply different values.

Parameters can also have a NULL state, but remember that if we set the EMP_NO parameter to NULL then this query would return no records since EMP_NO = NULL is false!

INSERT

In some ways, the discussion of SELECT was premature, since we can’t SELECT result sets from the database until there is data to SELECT!

The INSERT verb is used to create a new row in a single table. To create a new row in the EMPLOYEE table, we could use the following statement:

INSERT INTO EMPLOYEE (
  EMP_NO, LAST_NAME
) VALUES (
  123, ‘Smith’);

This statement, when run will result in the creation of a new record in the table with the value 123 in the EMP_NO column and the value "Smith" in the LAST_NAME column. These two columns will have the non-NULL state, but all other columns in the table will have the NULL state, unless the database administrator has created a DEFAULT value for the column. There is no requirement to specify every column in the table in an INSERT statement, but a column may have a NOT NULL constraint. If there is a NOT NULL constraint on the column and there is not a DEFAULT value for the column then you must specify a value for the column in your INSERT statement or the server will be unable to create the record and you’ll get an error.

If you will be specifying a value for every column in the new row then it’s permissible to omit the column list and just give the values, e.g.

INSERT INTO EMPLOYEE
VALUES (
  123, ‘John’, ‘Smith’, ‘1234′, ‘05/24/2002′, 600, ‘VP’, ‘2′, ‘USA’, 100000);  

But it’s crucial to specify the column values in the order they are defined. It is generally advisable to always specify the column list as it helps avoid problems.

INSERT statements do not return a result set. Only SELECT statements return a result set.

Copying data with INSERT … SELECT

It is sometimes necessary to INSERT many records into a table at once. For example, we might want to create a new project for every employee who was hired today. Instead of specifying the VALUES for the new row as constants, like in the example above, we can embed a SELECT statement into the INSERT statement, and one row will be created for each row in the result set of the SELECT statement:

INSERT INTO EMPLOYEE_PROJECT (
  EMPLOYEE_PROJECT.PROJID,
  EMPLOYEE_PROJECT.EMP_NO)
SELECT
  ‘BORCON’, EMPLOYEE.EMP_NO
FROM EMPLOYEE
WHERE
  EMPLOYEE.HIRE_DATE = CURRENT_DATE

This is a somewhat complex statement, so let’s look at it carefully.

The first line states that we’ll be inserting rows into the EMPLOYEE_PROJECT table.

The second line specifies which fields we’ll be specifying values for (later in the statement). It’s not necessary to specify the table name in the fields list as I have done here, but I wanted to clearly distinguish the columns in the table we’re inserting into from the columns coming from the table we’re Selecting from, since some of them have the same names.

The rest of the statement tells InterBase that the values will be coming from a SELECT statement rather than a list of constants. For each row in the result set, one row will be created in EMPLOYEE_PROJECT. CURRENT_DATE is a reserved word (in InterBase 6 and later) which always returns the current date from the server’s clock, so the SELECT portion of the statement will return one row for each employee who was hired today. There is only one item in the fields list of the SELECT portion of the statement which is actually a field in the EMPLOYEE table (EMP_NO). The other item is a constant. If we ran the SELECT by itself, the result set might look like this:

COLUMN_1 EMP_NO
1 21
1 22

So these are the values which will be assigned to the two rows which will be inserted into the EMPLOYEE_PROJECT table (presuming that there were two employees hired today).

Note on the Select clause of the Insert…Select statement:This is an Insert statement, not a Select statement. Only single table or view selects are allowed in the Select clause, Interbase will not currently allow joins.

UPDATE

The UPDATE verb is used to change the value of existing rows. Use of a WHERE clause is essential with UPDATE, because, by default, UPDATE changes every row in the table!

If we were going to pay everyone in the company the exact same salary, for example, we might run the following statement:

UPDATE EMPLOYEE
  SET SALARY = 5000

Running a statement like this is much more efficient than iterating through the table and updating each record, one at a time, but for the most part we generally do want to change values in only one record or a number if specific records matching some criteria, so most UPDATE statements have WHERE clauses:

UPDATE EMPLOYEE
  SET SALARY = 5000
WHERE
  EMP_NO = 20 

Bad news for employee 20, but the rest of us can breathe easy. It would probably not be a good idea to write an UPDATE statement like this:

UPDATE EMPLOYEE
  SET SALARY = 5000
WHERE
  FIRST_NAME = ‘Bob’

…because if there were more than one Bob in the company then we’d be giving a lousy salary to all of them, not just the lazy one. In general, use the primary key or another unique key of the table when writing a WHERE clause designed to affect only one record.

It’s common to write UPDATE statements which specify the value of every column in the table in the WHERE clause. Since the primary key will always uniquely identify a given row, why would anyone do this?

The answer is that this is way of protecting against multiuser conflicts (transactions are another way, but they do it differently). Let’s imagine that a user, Emma, creates a new record. Later, another user, Peter, updates the record. Emma returns to her computer, but she hasn’t refreshed her display so she doesn’t see Peter’s changes to the record. Emma decides to change the record. If only the primary key was specified in the WHERE clause of the UPDATE statement, then Emma would write over Peter’s changes without realizing it. By specifying all field values in the WHERE clause, and not just the primary key value, the UPDATE statement will have no effect (i.e, it will not change any rows) if the record has been changed since Emma last looked at it. The client application can detect that the statement had no effect and let Emma know that the row has changed (or been deleted) since she last SELECTed it.

DELETE

DELETE looks just like UPDATE except, for obvious reasons, there is no list of fields:

DELETE
FROM EMPLOYEE
WHERE
  EMP_NO = 20 

Like UPDATE, you can omit the WHERE clause if you want to affect (in this case, clear) the entire table:

DELETE FROM EMPLOYEE

Moving beyond simple SELECTs

Subqueries

A subquery is a SELECT inside of another SQL statement and is used to select values

from another table to be used for qualifying or setting values in the outer SQL statement. Subqueries can also be used in other statements too but are most commonly used in Select statements:

SELECT
  EMPLOYEE.EMP_NO, EMPLOYEE.LAST_NAME
FROM EMPLOYEE
WHERE
  EMPLOYEE.EMP_NO IN (SELECT PROJECT.EMP_NO FROM PROJECT)

This query will return the list of employees who have at least one project. Again, it’s not strictly necessary to use the table names in the fields list (e.g., EMPLOYEE.EMP_NO), but it helps to clarify where the fields are coming from since EMPLOYEE and EMPLOYEE_PROJECT both have a field called EMP_NO.

This query shows an example of using the IN operator with a subquery. We can’t use the = comparison here, because the subquery returns many rows, and we can’t use an equality comparison to compare a single value with a large number of rows. If we had tried to use = instead of IN, we’d receive the charming error message, "Multiple rows in singleton select." The = comparison could only be used if we were absolutely certain the subquery would return only one row.

Subqueries can also be used in UPDATE statements. Let’s imagine you want to set the project start date on every row in the EMPLOYEE_PROJECT table equal to the HIRE_DATE for the corresponding employee in the EMPLOYEE table. The following statement would do the trick:

UPDATE EMPLOYEE_PROJECT
  SET EMPLOYEE_PROJECT.START_DATE =
    (SELECT
      EMPLOYEE.HIRE_DATE
     FROM EMPLOYEE
     WHERE
       EMPLOYEE.EMP_NO = EMPLOYEE_PROJECT.EMP_NO
    )

(This query won’t actually work since the EMPLOYEE_PROJECT doesn’t have a field called START_DATE.)

This kind of statement works fairly well for tables which are not especially large. For very large tables, use a stored procedure to do the update (see the Optimization section for details). Attempting this on a large table, especially if more than one subquery must be used to set field values, will perform very poorly because the subqueries must be executed once for each record to be updated.

The following statement will delete any employees with no projects:

DELETE
FROM EMPLOYEE
WHERE
  EMPLOYEE.EMP_NO NOT IN
    (SELECT
      EMPLOYEE_PROJECT.EMP_NO
     FROM EMPLOYEE_PROJECT
    )


JOINs

Everything we have seen thus far is useful, but JOINs are the real power of a relational database server. JOINs allow the developer to combine data from multiple tables in to a single result set. It is common when designing databases to use a lot of separate tables for various attributes of a single entity. For example, you might store records of an employee’s salary, residence, etc., in separate tables rather than expanding the EMPLOYEE table. There a lot of advantages to doing this (the employee might have more than one residence, for example), but without being able to combine the information it would be very difficult to create a mailing list.

A JOIN is simply a way of telling the database server how to match rows from two different tables. You can then return fields from both tables in your result set. For example, let’s imagine we want to return a result set with employee names and the names of the projects they’re working on. We need to tell the database server to match EMPLOYEE_PROJECT records with records in the EMPLOYEE table. In particular, given an EMPLOYEE_PROJECT record, we need to find a record in the EMPLOYEE table with the same EMP_NO value.

The following query JOINs the two tables together to produce the desired result:

SELECT
  EMPLOYEE.LAST_NAME, EMPLOYEE_PROJECT.PROJ_ID
FROM EMPLOYEE
INNER JOIN EMPLOYEE_PROJECT
  ON EMPLOYEE_PROJECT.EMP_NO = EMPLOYEE.EMP_NO
ORDER BY
  EMPLOYEE.LAST_NAME, EMPLOYEE_PROJ_ID

This query uses a particular type of JOIN called an INNER JOIN. INNER JOIN means that one or more records with a given EMP_NO must be present in both the EMPLOYEE_PROJECT and EMPLOYEE tables in order to produce a row in the result set. In other words, if there was a row in the EMPLOYEE table for employee #10 but there was no row in the EMPLOYEE_PROJECT table for employee #10 then there would be no row in the result set for employee #10.

If no JOIN type is specified in the query (if we write JOIN EMPLOYEE instead of INNER JOIN EMPLOYEE) then an INNER JOIN will be used. INNER JOIN is the default join type.

The second most common type of JOIN is a LEFT OUTER JOIN. This type of JOIN returns at least one row for each row in the "left" table, whether or not there is a corresponding row in the "right" table.

The terms "left" and "right" often seem confusing at first, but that’s what the SQL Standard calls them. The terms are based on the fact that we (at least most of the western world) read from left to right, therefore just remember that left refers to the table mentioned first and right refers to the second table being joined to the first. In the example above, the EMPLOYEE table is the left table, and the EMPLOYEE_PROJECT table is the right table.

As previously mentioned, the query above won’t return any rows in its result set for employees without projects. What if we wanted to see these employees anyway? Use a LEFT OUTER JOIN instead of an INNER JOIN:

SELECT
  EMPLOYEE.LAST_NAME, EMPLOYEE_PROJECT.PROJ_ID
FROM EMPLOYEE
LEFT OUTER JOIN EMPLOYEE_PROJECT
  ON EMPLOYEE_PROJECT.EMP_NO = EMPLOYEE.EMP_NO
ORDER BY
  EMPLOYEE.LAST_NAME, EMPLOYEE_PROJECT.PROJ_ID

In this result set, the column EMPLOYEE_PROJECT.PROJ_ID will have a NULL state on rows for employees who don’t have any projects. This might seem confusing since there are no records in the EMPLOYEE_PROJECT table with the NULL state in the PROJ_ID field, but it simply indicates that there is no matching record. In other words, if a column is NULL in the result set of a query resulting from a JOIN, one of two things is going on:

  1. There was a matching row in the appropriate table, and the state of the corresponding column for that row was NULL, or
  2. There was no matching row in the appropriate table.

In addition to INNER and LEFT OUTER joins, there are also RIGHT OUTER and FULL OUTER joins (OUTER is optional and can be omitted). A RIGHT join, as it implies, is opposite of a LEFT join - all records are selected from the table on the right, and only matching records are selected from the table on the left. A FULL join is very rarely ever desired, it will select all records from both tables whether or not there is a match in the other.

Using aliases for field and table names

As we get used to the idea of having multiple tables in a SELECT statement, specifying the table names can get to be a lot of typing. SQL allows us to specify an alias for table names:

SELECT
  E.LAST_NAME, EP.PROJ_ID
FROM EMPLOYEE AS E
LEFT OUTER JOIN EMPLOYEE_PROJECT AS EP
  ON EP.EMP_NO = E.EMP_NO
ORDER BY
  E.LAST_NAME, EP.NAME

The the alias for a table is defined by specifying it following the table name in the FROM clause. SQL allows you to use the alias before it’s defined.

Fields can also be given aliases. As with table aliases, they are defined directly following the column name in the field list:

SELECT
  E.LAST_NAME AS SURNAME, EP.PROJ_ID AS PROJECT
FROM EMPLOYEE E
LEFT OUTER JOIN EMPLOYEE_PROJECT EP
  ON EP.EMP_NO = E.EMP_NO
ORDER BY
  E.LAST_NAME, EP.NAME 

The "AS" operator is optional and can be omitted in Interbase. Field aliases can only be used for specifying the name to be returned in the result set, a field alias name cannot be used to reference the field elsewhere in the query (such as in an Order By clause).

Aggregate Functions

The MAX, MIN, COUNT, SUM, and AVG (average) functions do exactly what their names imply. They are called "aggregate functions" because they work on the entire result set at once instead of a single row.

COUNT deserves special mention because it works in two different ways. COUNT(*) will count the number of rows in the table, after the WHERE clause is applied, JOINs are performed, etc. COUNT(FIELD_NAME) counts the number of non-NULL values in a particular column. COUNT(DISTINCT FIELD_NAME), of course, counts the number of distinct values there are in a particular column. For example, if you had a table with four rows in it, containing the following data:

ID SOME_VALUE
1
2 1
3 2
4 2

Then the following query:

SELECT
  COUNT(*), COUNT(SOME_VALUE), COUNT(DISTINCT SOME_VALUE), MAX(ID)

Would return this result set:

COUNT COUNT_2 COUNT_3 MAX
4 3 2 4

Grouping

Since aggregate functions operate on all rows of the table at once and produce a single row as a result set, they can’t be combined with "normal" selection of fields without grouping the results somehow. In other words, you can’t SELECT employee names and the count of employees in the same statement, because the employee names selection produces many rows and the count of employees produces only a single row.

You might, however, want to know how many employees have the same name. The GROUP BY clause allows you to tell the aggregate function that it should run once for each distinct value of a certain field or group of fields in the table.

SELECT
  LAST_NAME, COUNT(LAST_NAME)
FROM EMPLOYEE
GROUP BY
  LAST_NAME
ORDER BY
  LAST_NAME

The GROUP BY clause must contain all columns mentioned in the fields list except for the aggregat functions. So in this example, LAST_NAME must be in the Group By clause.

This query tells us what we want — for each name, how many people share the name. But the value for most names will be "1" and these ones would likely be uninteresting to the user of this query. So we need a way to eliminate the rows with unique names.

HAVING

The Having clause qualifies the Group By clause. After the Group By has done its work, any conditions specified in the Having clause are applied. So we can improve our query as follows:

SELECT
  LAST_NAME,
  COUNT(LAST_NAME)
FROM EMPLOYEE
GROUP BY
  LAST_NAME
HAVING
  COUNT(LAST_NAME) > 1
ORDER BY
  LAST_NAME

Now we’ll only see Last Names where at least two people share that name. This is accomplished by repeating the Count aggregate funtion on Last_Name.

DISTINCT

Distinct allows you to select unique values, or combinations of values, when there are duplicates. For example, you may want to see all the different cities your customers are from. If you execute the following:

SELECT
  CITY
FROM CUSTOMER
ORDER BY
  CITY

You will indeed see all the different cities, but each city will be repeated as many times as there are customers from that city. What is really wanted is each unique city:

SELECT DISTINCT
  CITY
FROM CUSTOMER
ORDER BY
  CITY

Cities are not necessarily unique across different provinces or states, so if your customers are international then the above won’t be good enough. Distinct works on multiple columns:

SELECT DISTINCT
  CITY, STATE_PROVINCE, COUNTRY
FROM CUSTOMER
ORDER BY
  CITY, STATE_PROVINCE, COUNTRY

Note the Order By is not required to include the same columns (or be present at all) but it is very common to do so.

CAST, EXTRACT, and other functions

Cast allows you to coerce one datatype into another. The conversion being asked for must make sense or Interbase will raise an exception. All the valid conversions can be found in the Interbase documentation. A simple example will give the idea:

SELECT
  CAST(QTY_ORDERED AS FLOAT) / 0.35
FROM SALES

Cast is not needed very often as Interbase will generally perform an implicit conversion for you. The above statement would give the same results without the cast.

Extract is available starting with Version 6.0. Extract allows parts if date and time fields to be extracted, for example:

SELECT
  LAST_NAME, FIRST_NAME, EXTRACT(YEAR FROM HIRE_DATE)
FROM EMPLOYEE

The parts that can be extracted are: YEAR, MONTH, DAY, WEEKDAY, HOUR, MINUTE, and SECOND.

ALL, ANY, SOME, EXISTS, SINGULAR

These operators are used with sub-queries. Earlier you learned about the IN operator which lets you test whether a selected value exists within a set of specified or sub-selected values - it tests for equality.

ALL allows you to perform comparisons other than equality. For example, if you want to find those employees with a salary greater than the salary of all employees in your department, you might try:

SELECT
  LAST_NAME, SALARY
FROM EMPLOYEE
WHERE
  SALARY >ALL
    (SELECT
      SALARY
     FROM EMPLOYEE
     WHERE
       DEPT_NO = 623
    )

ANY and SOME are synonyms, use whichever you like. To find employees in your department with salaries less than the lowest in any other department, you could find them with:

SELECT
  LAST_NAME, SALARY
FROM EMPLOYEE
WHERE
  SALARY < ANY
    (SELECT
       SALARY
     FROM EMPLOYEE
     WHERE
       DEPT_NO <>  623
    )

EXISTS is very valuable. Use it anywhere you need to know something exists but do not need to know the count.

If you want to see a list of Projects where no employees are currently assigned you could do this:

SELECT
  PROJ_ID, PROJ_NAME
FROM PROJECT P
WHERE
  NOT EXISTS
    (SELECT
	   EMP_NO
     FROM EMPLOYEE_PROJECT EP
     WHERE
       EP.PROJ_ID = P.PROJ_ID
    )

SINGULAR returns true if exactly one row from the sub-select matches your test. The following will return projects where there is exactly one employee assigned.

SELECT
  PROJ_ID, PROJ_NAME
FROM PROJECT P
WHERE
  SINGULAR
    (SELECT
       EMP_NO
     FROM EMPLOYEE_PROJECT EP
     WHERE
       EP.PROJ_ID = P.PROJ_ID
    )

InterBase-specific SQL

STARTING WITH

Use this instead of LIKE wherever possible. This is most important for parameterized queries as a parameterized LIKE cannot use an index while STARTING WITH can.

CONTAINING

CONTAINING is similar to using LIKE with wildcards on both sides, e.g. LIKE ‘%BASE%’. However CONTAINING is case-insensitive and can also be used to search within text blob columns.

ROWS

Rows is a new feature starting with version 6.5. Rows is very flexible and provides a great deal of power. At its simplest, it less you select the n rows from a query.

SELECT
  LAST_NAME,
  FIRST_NAME, SALARY
FROM EMPLOYEE
ORDER BY
  SALARY DESC, LAST_NAME,FIRST_NAME
ROWS 1 TO 10

This query returns only the first 10 records of the result set (You can also specify just ROWS 10). Strictly speaking, Rows can be used without the Order By, but this woud be of little value. In this example, the 10 employees with the highest salaries will be returned. It’s possible you may want all the employees with the 10 highest salaries (more than one employee may have the same salary), in this case you can use the WITH TIES option:

SELECT
  LAST_NAME,
  FIRST_NAME, SALARY
FROM EMPLOYEE
ORDER BY
  SALARY DESC, LAST_NAME,FIRST_NAME
ROWS 1 TO 10 WITH TIES

So if there were 17 employees sharing the top 10 salaries, all 17 would be returned.

Rows can also be specified as a percent:

SELECT
  LAST_NAME,
  FIRST_NAME, SALARY
FROM EMPLOYEE
ORDER BY
  SALARY DESC, LAST_NAME,FIRST_NAME
ROWS 10 PERCENT

Finally, Rows can be used for sampling. The following example returns every 20th record from the result set up to a maximum of 100 records:

SELECT
  LAST_NAME,
  FIRST_NAME, SALARY
FROM EMPLOYEE
ORDER BY
  SALARY DESC, LAST_NAME,FIRST_NAME
ROWS 100 BY 20

Probably one of the most sought after uses of this feature is to allow for very efficient paging of result sets as commonly seen on many web sites; some number of records, typically 10, are displayed with Next and Previous buttons. Rows allows you to return any slice of a result set

The Rows feature can also be used in Update and Delete statements. Normally, the Order By clause is only valid in a Select statement. However, it becomes valid for other statements as long as it is used along with the Rows feature (otherwise using rows in these statements would be of little value). For example, the following gives a raise to the top 10 salaried employees hired before 2002:

UPDATE EMPLOYEE
SET
  SALARY = SALARY * 1.10
WHERE
  EXTRACT(YEAR FROM HIRE_DATE) < 2002
ORDER BY
  SALARY DESC
ROWS 10

The following deletes the 10 employees with the newest hire date:

DELETE FROM EMPLOYEE
ORDER BY
  HIRE_DATE DESC
ROWS 10


RDB$DATABASE

This is a system table that is guaranteed to only have a single record in it. This makes a few special queries possible such as:

Asking Interbase to return the current date and time from the server

SELECT
  CURRENT_TIMESTAMP
FROM RDB$DATABASE /* 6.0+ */
SELECT
  CAST(’TODAY’ AS DATE)
FROM RDB$DATABASE /* 5.6- */

Asking Interbase to return the currently logged-in user

SELECT
  USER
FROM RDB$DATABASE

There are other situations where such a table is needed, for example to simply test for the existence of a specific record - the EXISTS function can only be used with a sub-query:

SELECT
  1
FROM RDB$DATABASE
WHERE EXISTS (
  SELECT *
  FROM
    EMPLOYEE_PROJECT
  WHERE
    EP.PROJ_ID = 123
  )

Generators

Generators are a way to create unique values in a multi-user database. In a single-user database it is acceptable to do things such as manually incrementing a value from a "key" table, or adding one to the highest current value in a table, but these schemes will not do in a multi-user environment. Most RDBMS products offer something similar, MS SQL has Identity fields and Oracle has Sequences. The standard way to use a generator from in a query is like this:

SELECT
  GEN_ID(GEN_CUSTOMER, 1)
FROM RDB$DATABASE

Generators can also be used in insert and update statements. It is also common to see them as part of insert triggers.

{ 64 } Comments

  1. Teruyuki Nakayama | July 3, 2004 at 9:29 am | Permalink

    SQL Statement

    ‘SELECT

    COUNT(*), COUNT(SOME_VALUE), MAX(ID) from XXX’ In Aggregate Functions doesn’t bring result as described.

  2. Carlos López | March 7, 2005 at 7:40 am | Permalink

    Good day,

    I have a question about aggregate functions in SQL.

    Is it possible to call a procedure in a SELECT clause? I have defined a procedure that counts the amount of a product in a given order, which I called CountProducts, I would like to do something like this:

    SELECT ID_ORDER,CountProducts(ID_ORDER,’PRODUCT1′), CountProducts(ID_ORDER,’PRODUCT2′)

    FROM ORDER

    But it doesn’t work, I tried execute procedure …. but it doesn’t either.

    I think I need a procedure because I need to count more than one product.

    Thank you,

    Carlos

  3. Craig Stuntz | March 7, 2005 at 7:49 am | Permalink

    Carlos,

    No, you can’t do that. What you can do, however, is use a SELECTable proc and JOIN the results in. You’ll have to change the arguments of your proc a little, but this way the proc is only called twice for the entire query, instead of twice for every record in the result set. Make the proc take only the string argument and return a row for each row in ORDER:

    SELECT

    O.ID_ORDER,

    CP1.PRODUCT_COUNT,

    CP1.PRODUCT_COUNT

    FROM

    ID_ORDER

    INNER JOIN CountProducts(’PRODUCT1′) CP1

    ON CP1.ID_ORDER = O.ID_ORDER

    INNER JOIN CountProducts(’PRODUCT2′) CP2

    ON CP2.ID_ORDER = O.ID_ORDER

    That said, when you write, "I think I need a procedure because I need to count more than one product." it’s not clear to me that this is the case. However, I can’t say for sure without knowing more about what you’re doing.

  4. Carlos López | March 7, 2005 at 8:01 am | Permalink

    Hello Craig,

    Thank you very much for your quick answer.

    I think I can use your approach. In fact, what I want to do is a report which includes the order number, number of items of product #1 in that order, number of items of product #2 in that order and so on, for every order in the table.

    Now also I need to wrote a ‘S’ character if the order has the AUTO string, ‘N’ if not. Perhaps I can do this with an UDF just passing the string, or perhaps including, in some way, an If in the SELECT.

    Thanks once again,

    Carlos

  5. Craig Stuntz | March 7, 2005 at 8:51 am | Permalink

    If you use InterBase 7.5 you can use the CASE function to do the ‘S’/'N’ substitution. In earlier versions of IB, the easiest way to do this is on the client, e.g., with a Delphi calculated field.

  6. Carlos López | March 8, 2005 at 3:42 am | Permalink

    Hello Craig,

    Thanks once again for your help.

    Actually I’ve been trying to do the SQL and I am bit confused, I have a table which is order_h (order header), and another one which is order_l(order_l). I need to extract from order_h the fields "order number,is_it_auto" and from order_l(order line) "product name, quantity". The results of the table should be something like this:

    ID_ORDER IS_IT_AUTO QUANTITY_PRODUCT1 QUANTITY_PRODUCT2

    64 ‘N’ 13 2

    (IS_IT_AUTO comes from evaluating the client String and if it contains AUTO the char is ‘S’, otherwise ‘N’)

    I have the SQL which is like this:

    —-

    select order_h.ID_ORDER,Product1.Units

    FROM order_h

    INNER JOIN CantidadProducto(order_h.ID_ORDER,’1′) Product1

    ON order_h.ID_ORDER=Product1.Idorder

    ———

    And the procedure CantidadProducto is like this:

    select id_order,sum(units_conf)

    from order_l

    where (cod_product=:ParameterProduct) and (id_order=:ParameterIdOrder)

    group by id_order

    into :IdOrder,:Units

    Being INPUT parameters: ParameterProduct and ParameterIdOrder

    OUTPUT parameters: IdOrder, Units

    —-

    I have to specify the order number as input in the procedure, otherwise I would get the "multiple rows in singleton select" error (more than one row).

    The problem is that when I execute the SQL I just get one empty row and I am sure there are more than one result. In fact, I execute the SQL within the procedure replacing parameters with values and it seems to work ok.

    Any suggestion please?

    Thank you very much once again,

    Carlos

  7. Carlos López | March 8, 2005 at 5:54 am | Permalink

    Craig,

    I solved it about the "multiple rows in singleton select" problem, just had to add the select between a "for" and "do" and no need for passing the order as a parameter.

    Thanks a lot,

    Carlos

  8. Carlos López | March 10, 2005 at 2:54 am | Permalink

    Hello Craig,

    Does InterBase SQL has some kind of limitation when dealing with queries?. I need to write an SQL that returns 0 if there is not product 1 in the table ORDER_L, otherwise the sum, this is what I have done:

    Procedure # 1: Returns the number of orden and total sum from ORDER_L which contains the product specify in parameter.

    CREATE PROCEDURE ORDENESCONPRODUCTO (NOMBREPRODUCTO VARCHAR(15))

    returns (ID_ORDER VARCHAR(20),

    CANTIDAD INTEGER)

    AS

    begin

    for

    SELECT ORDER_L.ID_ORDER, SUM(ORDER_L.UNITS_CONF)

    FROM ORDER_L

    WHERE(ORDER_L.COD_PRODUCT = :NOMBREPRODUCTO)

    GROUP BY

    ORDER_L.ID_ORDER

    into :Id_order, Cantidad

    do

    suspend;

    end

    Then the SQL:

    SELECT ORDER_L.ID_ORDER,ORDPRODUCTO.CANTIDAD AS SUMA_UNIDADES

    FROM ORDER_L

    LEFT JOIN ORDENESCONPRODUCTO (’1′) ORDPRODUCTO ON

    (ORDER_L.ID_ORDER = ORDPRODUCTO.ID_ORDER)

    GROUP BY

    ORDER_L.ID_ORDER,ORDPRODUCTO.CANTIDAD

    My purpose is to always write the order number in ORDER_L, but writing a NULL (or 0) if the order doesn’t have the product specify in the parameter. I think this is more less the logic the problem is that when I executed it it just crash.

    I can’t do a direct inner join with a WHERE clause because it will not work.

    Thank you very much,

    Carlos

  9. Adam | March 23, 2005 at 3:40 am | Permalink

    Hello!

    In witch case order of restrict in where clause had some different resoult score?

  10. Craig Stuntz | March 23, 2005 at 8:23 am | Permalink

    Adam,

    I don’t understand your question — can you rephrase it?

    Carlos,

    I don’t know why your program is crashing — there isn’t enough information in your message to say. But the basic technique seems fine. You can use COALESCE (new in InterBase 7.5) to turn the NULLs into 0s.

  11. Adam | March 24, 2005 at 2:01 am | Permalink

    Sorry for bad english…:(

    Let’s say I have some order in some clause:



    WHERE

    1. cust.ID = pr.ID and

    2. pr.city = ct.place and

    3. cust.date BETWEEN pr.begin_date AND end_date and

    4. o.name = :PAR_NAME

    5.

    6…

    Is it possible in some case, that the changeing of order makeing different query result?

  12. Craig Stuntz | March 24, 2005 at 9:16 am | Permalink

    Adam,

    No, the SQL standard requires that order of conjuncts in the WHERE does not change the rows returned. AFAIK, IB fully adheres to the standard in this area.

  13. Adam | March 25, 2005 at 2:11 am | Permalink

    Thank you Craig!

    One more question:

    When I start Winows XP, and then I start my database from IBConsole…why time of opening is so long (cca 1:30 min).Database has some blobs an it’s size is cca. 500MB.Is it time to long, or it is common time for opening for that size?

  14. Craig Stuntz | March 25, 2005 at 8:13 am | Permalink

    Adam,

    You are probably using an old version of InterBase. Read this:

    http://bdn.borland.com/article/0,1410,28142,00.html

    The long-term fix is to update to InterBase 7 or later.

  15. Adam | March 25, 2005 at 5:24 pm | Permalink

    Hi Craig, thanks for your answer!

    Yes, I using InterBase 6.0 because it’s free database.

  16. Adam | March 30, 2005 at 1:03 am | Permalink

    Hi Craig!

    When I create trigger I trie this and I can’t declare UPDATE and INSERT in the same trigger:

    CREATE TRIGGER "CALCULATE_PRICE" FOR "ARTICAL"

    ACTIVE AFTER UPDATE POSITION 0

    AS

    BEGIN

    IF (OLD.QUANTITY <> NEW.QUANTITY) THEN

    UPDATE ARTICAL SET PRICE = (OLD.PRICE * NEW.QUANTITY)

    WHERE OLD.ID = ID;

    END

    I wont to responde on INSERT too.How to do this?In Oracle we can do this:

    ACTIVE AFTER UPDATE OR INSERT….

  17. Craig Stuntz | March 30, 2005 at 8:06 am | Permalink

    Adam,

    No, you can’t do that in IB. What I usually do is create a stored proc which does the actual work and then call it from both triggers. But in the proc you don’t get the NEW and OLD context variables, so it depends on what you’re doing.

  18. Adam Cirkic | March 31, 2005 at 1:15 am | Permalink

    Ok.

    I never activated procedures from trigger.

    How to pass params (ID of current row) to proc?

    I ussaly do that from event func in Delphi…but how to do it in database?

    Do you have some example, please?

    By the way…I’m from Rijeka, Croatia…

    Adam

  19. Craig Stuntz | March 31, 2005 at 8:07 am | Permalink

    Adam,

    Procs can take arguments:

    CREATE PROCEDURE FOO (

    ARGUMENT_1 INTEGER,

    ARGUMENT_2 VARCHAR(20))

    AS

    And then you can pass the arguments from the row in the trigger when you call it:

    BEGIN

    EXECUTE PROCEDURE FOO(NEW.COLUMN1, NEW.COLUMN2);

    END;

  20. Adam | April 1, 2005 at 1:30 am | Permalink

    Hello Craig!

    Thans for answering…now I have just a one little problem.

    When I insert a new records from the form in Delphi, and start procedure CALCULATE_PRICE(id, new_quantity) from insert- trigger, the column new_price on form grid are not refreshed.I trie click to refresh button on update toolbar but nothing…

    I use InterBase TIBTable…update trigger work just fine, but insert also work, without refreshing.So i can’t see column new result, until close form and open it again.How to refresh and get result?

    CREATE TRIGGER "TR_CALCULATE_PRICE_INSERT" FOR "ARTIKAL"

    ACTIVE AFTER INSERT POSITION 0

    AS

    BEGIN

    EXECUTE PROCEDURE PR_CALCULATE_PRICE(NEW.ID, NEW.QUANTITY);

    END

    ^

    CREATE TRIGGER "TR_CALCULATE_PRICE_UPDATE" FOR "ARTIKAL"

    ACTIVE AFTER UPDATE POSITION 0

    AS

    BEGIN

    IF (OLD.QUANTITY <> NEW.QUANTITY) THEN

    EXECUTE PROCEDURE PR_CALCULATE_PRICE (OLD.ID, NEW.QUANTITY);

    END

    ^

  21. Craig Stuntz | April 1, 2005 at 8:28 am | Permalink

    Adam,

    First, I recommend you use TIBDataset, TIBQuery, or TIBSQL instead of TIBTable, because TIBTable is very inefficient. You can read more about this here:

    http://bdn.borland.com/article/0,1410,27659,00.html#TopicOne

    Second, the Refresh method in IBX will refresh the current record only. On TIBDataset and TIBQuery there is a ForcedRefresh property which will cause the dataset to refresh the current record after every time you post.

    -Craig

  22. Adam | April 4, 2005 at 8:27 am | Permalink

    Hi Craig,

    Do you know for some good link’s where I can free download GFIX.exe and some other InterBase reparing tools.

    My table in database was demaged and I can’t drop this table becouse it rise some I/O error…

  23. Craig Stuntz | April 4, 2005 at 9:01 am | Permalink

    Adam, gfix is included with all distributions of InterBase.

  24. Adam | April 5, 2005 at 1:58 am | Permalink

    Craig,

    …!? I didn’t know…it is probable in install directory.

    I wont to modifie exception message in a run time in my procedure.

    How to do it?

    I wont to show more information about current param’s and join them with basic message in existing exception.

    Maby, it could be done on some other way, not using exception???

  25. Adam | April 5, 2005 at 7:28 am | Permalink

    Craig,

    One more question…

    When I start GFIX.exe he just disappear in a moment.

    From which directory I shoud run it, and how to write something in it?

  26. Craig Stuntz | April 5, 2005 at 10:18 am | Permalink

    You can run gfix from any directory if you specify the path or if it’s in your PATH. If it "disappears" then you are probably trying to run it from Windows Explorer. It’s a command prompt application and you should run it from the command prompt.

  27. Adam | April 6, 2005 at 1:55 am | Permalink

    I wont to modifie exception message in a run time in my procedure.

    How to do it?

    I wont to show more information about current param’s and join them with basic message in existing exception.

    Maby, it could be done on some other way, not using exception???

  28. Craig Stuntz | April 6, 2005 at 8:55 am | Permalink

    Adam, I usually do message translations in my application. I look at the error code in a global exception handler and respond accordingly. There are several advantages to doing it this way — the original IB message isn’t suppressed, and the messages can be regionalized along with the rest of the application.

  29. Adam | April 14, 2005 at 9:16 am | Permalink

    Hi Craig!

    I wonted to store an bitmap in database but it doesn’t work!

    I seen picture on component desplay, but it doesn’t stored on database.

    I created one more field in my table:

    1) alter table artikal add ”picture” BLOB SUB_TYPE 0 SEGMENT SIZE 80

    2) I use TDBImage component whose DataSource = DataSource1 and DataField =

    PICTURE

    3) On button click event I have:

    procedure TfrmConnection.Button1Click(Sender: TObject);

    var

    B: TBitmap;

    begin

    B := TBitmap.Create;

    try

    B.LoadFromFile(’c:\My Database\Pictures\git1.bmp’); // is OK!

    DBImage1.Picture.Bitmap.Assign(B); // is OK!

    finally

    B.Free;

    end;

    end;

    What I have to do?

  30. Craig Stuntz | April 14, 2005 at 9:27 am | Permalink

    Adam,

    You would need to Post your dataset.

    BTW, I would not recommend using TDBImage as it cannot support GIF, JPEG, PNG, and other popular formats, and stores the data in a proprietary format. Instead, use a regular TImage and use the LoadFromStream method of both TImage and TBlobField/TParam to pass the image data via a TMemoryStream.

  31. pran | April 22, 2005 at 7:14 am | Permalink

    Hi,

    I am using TIBTable.

    When I add thounads(around 5000) of records in a IBTable (in loop) it takes too much time(around 5 min.).

    How can i reduse this time.

    pran

  32. Craig Stuntz | April 22, 2005 at 8:22 am | Permalink

    Pran, it depends what you’re doing. There are ways to speed this up, but as you’ve given me close to zero information about your problem I can’t really make suggestions.

    By the way, the best place to ask support-related questions is on Borland’s newsgroups. You can find information about how to subscribe and the guidelines for posting at:

    http://info.borland.com/newsgroups/guide.html

  33. Raghavan | June 3, 2005 at 8:03 am | Permalink

    "Dynamic SQL Error

    SQL error code = -204

    Ambiguous field name between table RDB$INDICES and table RDB$INDEX_SEGMENTS

    RDB$INDEX_NAME"

    is appearing in the IBConsole when i move to Indexes, i dont know much about IB, please tell me how to proceed

  34. Craig Stuntz | June 3, 2005 at 8:35 am | Permalink

    Raghavan, are you using InterBase? IBConsole doesn’t support Firebird.

  35. Raghavan | June 4, 2005 at 5:11 am | Permalink

    YES, I USE IBCONSOLE AND FIREBIRD, CANT THEY CO’EXIST?

  36. Craig Stuntz | June 4, 2005 at 9:56 am | Permalink

    They can coexist in the sense that both can be on the same machine at the same time, but IBConsole simply as no support for Firebird whatsoever. You need to use a different tool to administer Firebird.

  37. Craig Stuntz | June 6, 2005 at 8:00 am | Permalink

    I deleted a comment by Raghavan by accident while killing comment spam. It read:

    —- quote —-

    Thanx, craig.. can you suggest a different Opensource tool to administer?

    —- end quote —-

    No, I can’t recommend a tool for administering Firebird, since I don’t use that DB server. I also don’t have enough experience with any of the open source tools for IB to recommend any of them.

  38. Adam | June 15, 2005 at 3:46 pm | Permalink

    Hi Craig!

    I have a problem with rounding numbers in Delphi.

    I want to input decimal number 5,291 and grid automaticly set on 5,30…It’s wery bad!!!

    Did I missing something when I declared decimal numbers in database?Double precision or something?

  39. Craig Stuntz | June 15, 2005 at 4:23 pm | Permalink

    Adam, I can’t say for sure without knowing the column type, but I suspect the issue is your DisplayFormat in Delphi rather than the IB column.

  40. Adam | June 16, 2005 at 5:50 am | Permalink

    I declared column in InterBase:

    column_name decimal(12,2)

    I was read somewhere that is the Delphi’s bad feature…but I can’t remember where…. My costimers needed exactly all decimals!

    I use IB Query’s … TIB Components if you hear for them…but I don’t use their Display Format settings in this case.

  41. Craig Stuntz | June 16, 2005 at 8:40 am | Permalink

    If your column is DECIMAL(12, 2) then you *cannot* enter "5,291" because it has three decimal places and your column only supports two.

    You’ll have to change your metadata to, for example, DECIMAL(12,3) if you want to enter "5,291"

  42. Adam | July 7, 2005 at 1:39 pm | Permalink

    Hi Craig!

    I want to copy data from table1 in database1 to table1 in database2.

    How can I do it?

  43. Craig Stuntz | July 7, 2005 at 1:40 pm | Permalink

    With a data pump. There are several which support IB. You can’t do it in SQL.

  44. Adam | July 12, 2005 at 5:41 pm | Permalink

    Hi Craig,

    When I input a new value in table opened in IBConsole (InterBase 6.0) , I recivee this message:

    Stack overflow. The resource requirements of the runtime stack have exceeded the memory available to it.

    internal gds software consistency check (decompression overran buffer (179))

    What’s happening?I see this for the first time in my InterBase careere.

    In all other tables everything is OK.

  45. Craig Stuntz | July 12, 2005 at 8:50 pm | Permalink

    Whenever you see "internal gds software consistency check" you should check your DB for corrution. This article should help:

    http://bdn.borland.com/article/0,1410,29515,00.html

  46. Adam | July 13, 2005 at 6:45 am | Permalink

    Hi Craig!

    I found mestake in my update trigger with no appropriate condition statement that resulted with an recursion and memory was overloaded.

    However, second exception "internal gds software consistency check" confused me.

  47. Adam | August 11, 2005 at 6:12 pm | Permalink

    Ciao Craig!

    Max. size of tabele in InterBase database can be cca. 2,5 - 3 GB….

    …how to make some procedure who will rise an exception when the tabele is overload?

  48. Adam Wallace | August 16, 2005 at 5:00 am | Permalink

    Hi

    Is there any way to group by month in InterBase. I’m getting errors when i try this:

    SELECT EXTRACT(MONTH FROM m.Created) , COUNT(*) as CountMessages

    FROM Messages m

    INNER JOIN Clients c ON m.ClientID=c.ClientID

    WHERE c.ClientID=5

    GROUP BY EXTRACT(MONTH FROM m.Created)

    thanks in advance.

    Adam (but not the Adam above :)

  49. Craig Stuntz | August 16, 2005 at 7:58 am | Permalink

    To group by month you’d have to either make a shadow column for the month or put the query with EXTRACT into a VIEW, then GROUP BY a SELECT from the VIEW.

  50. Adam | August 16, 2005 at 6:24 pm | Permalink

    Hi Craig!

    1)

    I replay the question, couse I’m not shoure if yo read above one.

    Max. size of tabele in InterBase database can be cca. 2,5 - 3 GB….

    …how to make some procedure who will rise an exception when the tabele is overload, or does exist some other way?

    2)

    Is it better to use Intebase 6.0 or Firebird 1.5 for commercial pourpose?

  51. Mosaed | October 4, 2005 at 6:05 am | Permalink

    I have a picture saved in InterBase table.

    How do I retrieve the picture using C++ ?

  52. Craig Stuntz | October 4, 2005 at 8:46 am | Permalink

    Adam, I don’t know the answer to 1, and I personally wouldn’t use either option in #2 for an app I wanted to make a living with. I use InterBase 7, because I need SMP support, performance monitoring, etc.

    Mosaed, it depends on the components you’re using. In IBX you can use the FieldByName(’FOO’).SaveToStream / .SaveToFile methods. If using the API then follow the examples in the API guide. Blobs are just binary data, and retrieving a picture is no different than retrieving anything else stored in a Blob.

  53. Kumar | February 9, 2006 at 7:02 pm | Permalink

    While retrieving the Decimal filed width(15,6) its returning incorrectly. for example the value is 38 its returning 0.000038 Any ideas on this

  54. Craig Stuntz | February 10, 2006 at 8:10 am | Permalink

    Kumar, this is typically caused by a driver with a bug or which doesn’t support IB dialect 3 correctly. Make sure you have the latest version of whichever component set you’re using to connect to InterBase. Hard to be of much more help since you don’t say precisely what you’re using.

  55. Kumar | February 10, 2006 at 12:39 pm | Permalink

    Hi,

    Thanks. Im using Firebird driver.Previously i used Gemini and it works fine.

  56. Craig Stuntz | February 10, 2006 at 1:26 pm | Permalink

    It’s almost certainly a driver bug then.

  57. Artesio | June 26, 2006 at 12:10 pm | Permalink

    Hi,

    How can I retrive the last recorcord in a SELECT statement? In Access ther is the agragate function LAST, I didn’t find a similar function in Iterbase.

    Thanks

  58. Craig Stuntz | June 26, 2006 at 12:43 pm | Permalink

    Artesio, there is no such thing. Relational databases don’t have natural orders. To do what you want, either use MIN/MAX if applicable, or do an ORDER BY (whatever) DESC ROWS 1.

  59. Artesio | June 26, 2006 at 1:21 pm | Permalink

    Craig,

    My Table has 3 fields: PRODUCT_ID, STORAGE_DATE, STORAGE_PLACE. I have to retrieve the last STORAGE_PLACE, if i use MAX(STORAGE_DATE) I can’t retrieve the last STORAGE_PLACE.

    I hvae to retrieve the last STORAGE_PLACE of each porcuct.

    If I use SELECT PRODUCT_ID, MAX(STORAGE_DATE) FROM STORAGE_TABLE GROUP BY PRODUCT_ID ORDER BY PRODUCT_ID but that way I can´t retrieve the storage place. If I Group by storage place the query retunrs all storage places.

    Do you know how can I do it?

    Thx

    Artesio

  60. Craig Stuntz | June 26, 2006 at 1:24 pm | Permalink

    Artesio, I already told you how:

    SELECT STORAGE_PLACE

    FROM STORAGE_TABLE

    ORDER BY STORAGE_DATE DESC

    ROWS 1

  61. Artesio | June 26, 2006 at 1:30 pm | Permalink

    Craig,

    But that way it retruns only the first product. I need the last storage place of each product.

  62. Craig Stuntz | June 26, 2006 at 1:40 pm | Permalink

    Look closely at the DESC part.

  63. Josh | July 27, 2006 at 3:50 pm | Permalink

    Is something like this possible?

    select tbl1.field_id + ‘.’ + tbl2.field_id from tbl1, tbl2

    this is combining two fields in each line returned, I got this to work with Microsoft SQL Server but I need to find a way to make it work with InterBase.

    any ideas?

    Thanks,

    Josh

  64. Josh | July 28, 2006 at 10:34 am | Permalink

    Found my own solution. Here it is incase anyone else has the same problem.

    select tbl1.field_id || ‘.’ || tbl2.field_id from tbl1, tbl2

    http://bdn.borland.com/article/25523

    || is the concatenation operator.

    Josh

Post a Comment

Your email is never published nor shared. Required fields are marked *
Close