Basic SQL Operations in Firebird
This guide covers fundamental SQL operations for working with Firebird databases, perfect for beginners getting started with database management.
Connecting to Firebird
Using ISQL
# Connect to a local database
isql employee.fdb -user SYSDBA -password masterkey
# Connect to a remote database
isql server:/path/to/database.fdb -user username -password password
Creating Tables
Basic Table Creation
CREATE TABLE customers (
customer_id INTEGER NOT NULL PRIMARY KEY,
first_name VARCHAR(30) NOT NULL,
last_name VARCHAR(30) NOT NULL,
email VARCHAR(100) UNIQUE,
phone VARCHAR(20),
created_date DATE DEFAULT CURRENT_DATE
);
CREATE TABLE products (
product_id INTEGER NOT NULL PRIMARY KEY,
product_name VARCHAR(100) NOT NULL,
description BLOB SUB_TYPE 1,
price NUMERIC(10,2) NOT NULL CHECK (price > 0),
stock_quantity INTEGER DEFAULT 0,
category VARCHAR(50)
);
CREATE TABLE orders (
order_id INTEGER NOT NULL PRIMARY KEY,
customer_id INTEGER NOT NULL,
order_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
total_amount NUMERIC(10,2),
status VARCHAR(20) DEFAULT 'PENDING',
FOREIGN KEY (customer_id) REFERENCES customers(customer_id)
);
Using Auto-increment (Identity)
-- Firebird 3.0+ syntax
CREATE TABLE employees (
emp_id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
emp_name VARCHAR(50) NOT NULL,
hire_date DATE DEFAULT CURRENT_DATE
);
-- Pre-3.0 method using generators
CREATE GENERATOR gen_employee_id;
CREATE TABLE employees_old (
emp_id INTEGER NOT NULL PRIMARY KEY,
emp_name VARCHAR(50) NOT NULL
);
-- Create trigger for auto-increment
SET TERM ^ ;
CREATE TRIGGER employees_bi FOR employees_old
ACTIVE BEFORE INSERT POSITION 0
AS
BEGIN
IF (NEW.emp_id IS NULL) THEN
NEW.emp_id = GEN_ID(gen_employee_id, 1);
END^
SET TERM ; ^
Inserting Data
Basic INSERT
-- Single row insert
INSERT INTO customers (customer_id, first_name, last_name, email)
VALUES (1, 'John', 'Doe', '[email protected]');
-- Multiple rows insert (Firebird 2.0+)
INSERT INTO products (product_id, product_name, price, stock_quantity)
VALUES
(1, 'Laptop', 999.99, 10),
(2, 'Mouse', 19.99, 50),
(3, 'Keyboard', 49.99, 30);
-- Insert with returning
INSERT INTO customers (first_name, last_name, email)
VALUES ('Jane', 'Smith', '[email protected]')
RETURNING customer_id;
INSERT from SELECT
-- Copy data from one table to another
INSERT INTO customers_archive
SELECT * FROM customers WHERE created_date < '2020-01-01';
-- Insert with transformation
INSERT INTO order_summary (customer_id, total_orders, total_amount)
SELECT
customer_id,
COUNT(*),
SUM(total_amount)
FROM orders
GROUP BY customer_id;
Querying Data
Basic SELECT
-- Select all columns
SELECT * FROM customers;
-- Select specific columns
SELECT first_name, last_name, email
FROM customers;
-- With WHERE clause
SELECT * FROM products
WHERE price < 100 AND stock_quantity > 0;
-- Using aliases
SELECT
c.first_name || ' ' || c.last_name AS full_name,
c.email AS contact_email
FROM customers c;
Filtering Data
-- Comparison operators
SELECT * FROM products WHERE price >= 50 AND price <= 200;
SELECT * FROM products WHERE price BETWEEN 50 AND 200;
-- IN operator
SELECT * FROM orders
WHERE status IN ('PENDING', 'PROCESSING', 'SHIPPED');
-- LIKE operator for pattern matching
SELECT * FROM customers
WHERE email LIKE '%@gmail.com';
SELECT * FROM products
WHERE product_name LIKE 'Lap%'; -- Starts with 'Lap'
-- NULL handling
SELECT * FROM customers
WHERE phone IS NULL;
SELECT * FROM customers
WHERE phone IS NOT NULL;
Sorting and Limiting
-- ORDER BY
SELECT * FROM products
ORDER BY price DESC;
SELECT * FROM customers
ORDER BY last_name, first_name;
-- FIRST/SKIP (Firebird's LIMIT/OFFSET)
SELECT FIRST 10 * FROM products
ORDER BY price DESC;
SELECT FIRST 10 SKIP 20 * FROM products
ORDER BY product_name;
-- Using ROWS (Firebird 3.0+)
SELECT * FROM products
ORDER BY price DESC
ROWS 1 TO 10;
Updating Data
Basic UPDATE
-- Update single column
UPDATE products
SET price = price * 1.1
WHERE category = 'Electronics';
-- Update multiple columns
UPDATE customers
SET
email = '[email protected]',
phone = '123-456-7890'
WHERE customer_id = 1;
-- Update with returning
UPDATE products
SET stock_quantity = stock_quantity - 1
WHERE product_id = 1
RETURNING product_name, stock_quantity;
UPDATE with JOIN
-- Update using data from another table
UPDATE orders o
SET o.total_amount = (
SELECT SUM(od.quantity * p.price)
FROM order_details od
JOIN products p ON od.product_id = p.product_id
WHERE od.order_id = o.order_id
)
WHERE EXISTS (
SELECT 1 FROM order_details
WHERE order_id = o.order_id
);
Deleting Data
Basic DELETE
-- Delete specific rows
DELETE FROM customers
WHERE customer_id = 100;
-- Delete with condition
DELETE FROM orders
WHERE order_date < DATEADD(-1 YEAR TO CURRENT_DATE)
AND status = 'CANCELLED';
-- Delete with returning
DELETE FROM products
WHERE stock_quantity = 0
RETURNING product_id, product_name;
-- Delete all rows (use with caution!)
DELETE FROM temp_table;
Working with Dates
-- Current date and time
SELECT CURRENT_DATE, CURRENT_TIME, CURRENT_TIMESTAMP FROM RDB$DATABASE;
-- Date arithmetic
SELECT
order_date,
order_date + 7 AS week_later,
order_date - 30 AS month_ago,
DATEADD(MONTH, 1, order_date) AS next_month,
DATEDIFF(DAY, order_date, CURRENT_DATE) AS days_ago
FROM orders;
-- Extract date parts
SELECT
EXTRACT(YEAR FROM order_date) AS order_year,
EXTRACT(MONTH FROM order_date) AS order_month,
EXTRACT(DAY FROM order_date) AS order_day
FROM orders;
Aggregation
Basic Aggregates
-- Count rows
SELECT COUNT(*) FROM customers;
SELECT COUNT(DISTINCT category) FROM products;
-- Sum, Average, Min, Max
SELECT
COUNT(*) AS total_products,
SUM(stock_quantity) AS total_stock,
AVG(price) AS average_price,
MIN(price) AS lowest_price,
MAX(price) AS highest_price
FROM products;
GROUP BY
-- Group by single column
SELECT
category,
COUNT(*) AS product_count,
AVG(price) AS avg_price
FROM products
GROUP BY category;
-- Group by multiple columns
SELECT
EXTRACT(YEAR FROM order_date) AS year,
EXTRACT(MONTH FROM order_date) AS month,
COUNT(*) AS order_count,
SUM(total_amount) AS total_sales
FROM orders
GROUP BY
EXTRACT(YEAR FROM order_date),
EXTRACT(MONTH FROM order_date)
ORDER BY year, month;
-- HAVING clause
SELECT
customer_id,
COUNT(*) AS order_count,
SUM(total_amount) AS total_spent
FROM orders
GROUP BY customer_id
HAVING COUNT(*) > 5
ORDER BY total_spent DESC;
Basic Joins
INNER JOIN
-- Simple inner join
SELECT
c.first_name,
c.last_name,
o.order_id,
o.order_date,
o.total_amount
FROM customers c
INNER JOIN orders o ON c.customer_id = o.customer_id;
-- Multiple joins
SELECT
c.first_name,
o.order_id,
p.product_name,
od.quantity,
od.unit_price
FROM customers c
INNER JOIN orders o ON c.customer_id = o.customer_id
INNER JOIN order_details od ON o.order_id = od.order_id
INNER JOIN products p ON od.product_id = p.product_id;
LEFT JOIN
-- Show all customers, even those without orders
SELECT
c.first_name,
c.last_name,
COUNT(o.order_id) AS order_count
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
GROUP BY c.customer_id, c.first_name, c.last_name;
Subqueries
Subqueries in WHERE
-- Find customers with above-average orders
SELECT * FROM customers
WHERE customer_id IN (
SELECT customer_id
FROM orders
GROUP BY customer_id
HAVING SUM(total_amount) > (
SELECT AVG(total_amount) FROM orders
)
);
-- Using EXISTS
SELECT * FROM customers c
WHERE EXISTS (
SELECT 1 FROM orders o
WHERE o.customer_id = c.customer_id
AND o.total_amount > 1000
);
Subqueries in SELECT
SELECT
p.product_name,
p.price,
(SELECT AVG(price) FROM products) AS avg_price,
p.price - (SELECT AVG(price) FROM products) AS price_diff
FROM products p;
Common Table Expressions (CTEs)
-- Simple CTE
WITH high_value_orders AS (
SELECT * FROM orders
WHERE total_amount > 500
)
SELECT
customer_id,
COUNT(*) AS high_value_count
FROM high_value_orders
GROUP BY customer_id;
-- Multiple CTEs
WITH
customer_totals AS (
SELECT
customer_id,
SUM(total_amount) AS total_spent
FROM orders
GROUP BY customer_id
),
avg_spending AS (
SELECT AVG(total_spent) AS avg_total
FROM customer_totals
)
SELECT
c.first_name,
c.last_name,
ct.total_spent,
a.avg_total,
ct.total_spent - a.avg_total AS difference
FROM customers c
JOIN customer_totals ct ON c.customer_id = ct.customer_id
CROSS JOIN avg_spending a
WHERE ct.total_spent > a.avg_total;
String Operations
-- Concatenation
SELECT first_name || ' ' || last_name AS full_name
FROM customers;
-- String functions
SELECT
UPPER(first_name) AS upper_name,
LOWER(email) AS lower_email,
CHAR_LENGTH(last_name) AS name_length,
SUBSTRING(email FROM 1 FOR POSITION('@' IN email) - 1) AS email_user,
TRIM(LEADING ' ' FROM ' Hello ') AS trimmed,
REPLACE(phone, '-', '') AS phone_digits
FROM customers;
Best Practices for Beginners
- Always use explicit column names instead of SELECT *
- Use meaningful aliases for better readability
- Test DELETE and UPDATE with SELECT first
- Use transactions for multiple related operations
- Create indexes on columns used in WHERE and JOIN
- Validate data with CHECK constraints
- Use appropriate data types to save space and improve performance
Common Mistakes to Avoid
- Forgetting WHERE in UPDATE/DELETE - can affect all rows!
- Not handling NULL values properly
- Using the wrong JOIN type - understand the difference
- Ignoring case sensitivity in string comparisons
- Not using parameters in application code (SQL injection risk)
Next Steps
- Learn about transactions and isolation levels
- Explore stored procedures and triggers
- Understand indexes and query optimization
- Study advanced features like window functions
- Practice with real-world scenarios