Si vous voulez transposer vos réflexes Oracle vers PostgreSQL, consultez aussi le tutoriel PostgreSQL SQL, qui inclut un guide de différences Oracle / PostgreSQL pour adapter vos requêtes plus vite.
Objectif: passer des requêtes simples à des requêtes robustes et optimisées en environnement réel.
Fonctionnement: exécute chaque requête, observe le résultat, puis explique pourquoi la requête retourne ces lignes.
-- Sélection simple
SELECT * FROM employees;
SELECT employee_id, first_name, last_name, salary
FROM employees;
-- DISTINCT
SELECT DISTINCT department_id FROM employees;
-- Alias
SELECT
first_name AS prenom,
last_name AS nom,
salary * 12 AS salaire_annuel
FROM employees;
-- Concaténation
SELECT first_name || ' ' || last_name AS nom_complet
FROM employees;
-- Opérateurs de comparaison
SELECT * FROM employees WHERE salary > 5000;
SELECT * FROM employees WHERE hire_date >= DATE '2020-01-01';
-- Opérateurs logiques
SELECT * FROM employees
WHERE salary BETWEEN 5000 AND 10000
AND department_id IN (10, 20, 30);
-- LIKE (pattern matching)
SELECT * FROM employees WHERE last_name LIKE 'S%';
SELECT * FROM employees WHERE email LIKE '%@oracle.com';
SELECT * FROM employees WHERE phone_number LIKE '___-____'; -- 3 puis 4 caractères
-- IS NULL / IS NOT NULL
SELECT * FROM employees WHERE commission_pct IS NULL;
-- Expressions régulières (Oracle 10g+)
SELECT * FROM employees
WHERE REGEXP_LIKE(email, '^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$', 'i');
-- ORDER BY
SELECT * FROM employees
ORDER BY salary DESC;
SELECT * FROM employees
ORDER BY department_id ASC, salary DESC;
-- NULLS FIRST / NULLS LAST
SELECT * FROM employees
ORDER BY commission_pct NULLS LAST;
-- FETCH FIRST (Oracle 12c+)
SELECT * FROM employees
ORDER BY salary DESC
FETCH FIRST 10 ROWS ONLY;
-- OFFSET (pagination)
SELECT * FROM employees
ORDER BY employee_id
OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;
-- ROWNUM (ancien style)
SELECT * FROM (
SELECT * FROM employees ORDER BY salary DESC
)
WHERE ROWNUM <= 10;
-- ANSI SQL-92 (recommandé) SELECT e.first_name, e.last_name, d.department_name FROM employees e INNER JOIN departments d ON e.department_id = d.department_id; -- Oracle style (ancien) SELECT e.first_name, e.last_name, d.department_name FROM employees e, departments d WHERE e.department_id = d.department_id;
-- LEFT OUTER JOIN SELECT e.first_name, d.department_name FROM employees e LEFT OUTER JOIN departments d ON e.department_id = d.department_id; -- RIGHT OUTER JOIN SELECT e.first_name, d.department_name FROM employees e RIGHT OUTER JOIN departments d ON e.department_id = d.department_id; -- FULL OUTER JOIN SELECT e.first_name, d.department_name FROM employees e FULL OUTER JOIN departments d ON e.department_id = d.department_id; -- Oracle style (+) SELECT e.first_name, d.department_name FROM employees e, departments d WHERE e.department_id = d.department_id(+); -- LEFT JOIN
-- Self join (hiérarchie)
SELECT
e.first_name AS employe,
m.first_name AS manager
FROM employees e
LEFT JOIN employees m ON e.manager_id = m.employee_id;
-- Cross join (produit cartésien)
SELECT * FROM colors CROSS JOIN sizes;
-- COUNT, SUM, AVG, MIN, MAX
SELECT
COUNT(*) AS total_employes,
COUNT(DISTINCT department_id) AS nb_departements,
AVG(salary) AS salaire_moyen,
MIN(hire_date) AS premiere_embauche,
MAX(salary) AS salaire_max
FROM employees;
-- GROUP BY
SELECT
department_id,
COUNT(*) AS nb_employes,
AVG(salary) AS salaire_moyen
FROM employees
GROUP BY department_id
ORDER BY department_id;
-- HAVING (filtrage après agrégation)
SELECT department_id, AVG(salary) AS avg_sal
FROM employees
GROUP BY department_id
HAVING AVG(salary) > 5000;
-- ROLLUP (sous-totaux hiérarchiques)
SELECT department_id, job_id, SUM(salary)
FROM employees
GROUP BY ROLLUP(department_id, job_id);
-- CUBE (toutes les combinaisons)
SELECT department_id, job_id, SUM(salary)
FROM employees
GROUP BY CUBE(department_id, job_id);
-- GROUPING SETS
SELECT department_id, job_id, SUM(salary)
FROM employees
GROUP BY GROUPING SETS (
(department_id, job_id),
(department_id),
()
);
-- Dans WHERE
SELECT * FROM employees
WHERE salary > (SELECT AVG(salary) FROM employees);
-- Dans SELECT
SELECT
first_name,
salary,
(SELECT AVG(salary) FROM employees) AS avg_salary
FROM employees;
SELECT * FROM employees
WHERE department_id IN (
SELECT department_id
FROM departments
WHERE location_id = 1700
);
-- EXISTS (plus performant)
SELECT * FROM employees e
WHERE EXISTS (
SELECT 1 FROM departments d
WHERE d.department_id = e.department_id
AND d.location_id = 1700
);
-- Employés gagnant plus que la moyenne de leur département
SELECT e1.first_name, e1.salary, e1.department_id
FROM employees e1
WHERE salary > (
SELECT AVG(salary)
FROM employees e2
WHERE e2.department_id = e1.department_id
);
-- ROW_NUMBER (numéro séquentiel)
SELECT
first_name,
salary,
ROW_NUMBER() OVER (ORDER BY salary DESC) AS rang
FROM employees;
-- RANK (avec ex-aequo, sauts)
SELECT
first_name,
salary,
RANK() OVER (ORDER BY salary DESC) AS rang
FROM employees;
-- DENSE_RANK (avec ex-aequo, sans sauts)
SELECT
first_name,
salary,
DENSE_RANK() OVER (ORDER BY salary DESC) AS rang
FROM employees;
-- PARTITION BY (par groupe)
SELECT
department_id,
first_name,
salary,
RANK() OVER (
PARTITION BY department_id
ORDER BY salary DESC
) AS rang_dept
FROM employees;
-- LAG / LEAD (valeurs précédente/suivante)
SELECT
employee_id,
salary,
LAG(salary, 1) OVER (ORDER BY employee_id) AS prev_salary,
LEAD(salary, 1) OVER (ORDER BY employee_id) AS next_salary
FROM employees;
-- FIRST_VALUE / LAST_VALUE
SELECT
employee_id,
salary,
FIRST_VALUE(salary) OVER (
PARTITION BY department_id
ORDER BY salary DESC
) AS highest_in_dept
FROM employees;
-- SUM, AVG avec fenêtre glissante
SELECT
employee_id,
salary,
SUM(salary) OVER (
ORDER BY employee_id
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
) AS rolling_sum
FROM employees;
-- UNION (élimine les doublons) SELECT employee_id FROM employees WHERE department_id = 10 UNION SELECT employee_id FROM employees WHERE salary > 10000; -- UNION ALL (garde les doublons, plus rapide) SELECT employee_id FROM employees WHERE department_id = 10 UNION ALL SELECT employee_id FROM employees WHERE department_id = 20; -- INTERSECT SELECT employee_id FROM employees WHERE department_id = 10 INTERSECT SELECT employee_id FROM employees WHERE salary > 5000; -- MINUS (différence) SELECT employee_id FROM employees WHERE department_id = 10 MINUS SELECT employee_id FROM employees WHERE salary < 3000;
-- CTE simple
WITH high_earners AS (
SELECT * FROM employees WHERE salary > 10000
)
SELECT department_id, COUNT(*)
FROM high_earners
GROUP BY department_id;
-- CTE multiples
WITH
dept_avg AS (
SELECT department_id, AVG(salary) AS avg_sal
FROM employees
GROUP BY department_id
),
high_depts AS (
SELECT department_id FROM dept_avg WHERE avg_sal > 7000
)
SELECT e.* FROM employees e
WHERE e.department_id IN (SELECT department_id FROM high_depts);
-- CTE récursive (hiérarchie)
WITH emp_hierarchy (employee_id, manager_id, level, path) AS (
-- Ancre
SELECT employee_id, manager_id, 1, first_name
FROM employees
WHERE manager_id IS NULL
UNION ALL
-- Partie récursive
SELECT e.employee_id, e.manager_id, eh.level + 1,
eh.path || ' > ' || e.first_name
FROM employees e
JOIN emp_hierarchy eh ON e.manager_id = eh.employee_id
)
SELECT * FROM emp_hierarchy ORDER BY level, employee_id;
-- Insert simple
INSERT INTO employees (employee_id, first_name, last_name, email, hire_date)
VALUES (207, 'John', 'Doe', 'jdoe@company.com', SYSDATE);
-- Insert multiple (Oracle 23c+)
INSERT INTO employees (employee_id, first_name, last_name)
VALUES
(208, 'Jane', 'Smith'),
(209, 'Bob', 'Johnson');
-- Insert depuis SELECT
INSERT INTO employees_archive
SELECT * FROM employees WHERE hire_date < DATE '2010-01-01';
-- Insert ALL (conditionnel)
INSERT ALL
WHEN salary > 10000 THEN INTO high_sal VALUES (employee_id, salary)
WHEN salary < 5000 THEN INTO low_sal VALUES (employee_id, salary)
ELSE INTO mid_sal VALUES (employee_id, salary)
SELECT employee_id, salary FROM employees;
-- Update simple
UPDATE employees
SET salary = salary * 1.10
WHERE department_id = 50;
-- Update multiple colonnes
UPDATE employees
SET salary = salary * 1.15,
commission_pct = 0.05
WHERE job_id = 'SA_REP';
-- Update depuis sous-requête
UPDATE employees e
SET salary = (
SELECT AVG(salary) * 1.1
FROM employees
WHERE department_id = e.department_id
)
WHERE department_id IN (10, 20);
-- MERGE (upsert)
MERGE INTO employees_target t
USING employees_source s ON (t.employee_id = s.employee_id)
WHEN MATCHED THEN
UPDATE SET t.salary = s.salary
WHEN NOT MATCHED THEN
INSERT (employee_id, first_name, salary)
VALUES (s.employee_id, s.first_name, s.salary);
-- Delete simple
DELETE FROM employees WHERE employee_id = 999;
-- Delete avec sous-requête
DELETE FROM employees
WHERE department_id IN (
SELECT department_id FROM departments WHERE location_id = 1700
);
-- TRUNCATE (plus rapide, pas de rollback)
TRUNCATE TABLE temp_data;
-- Table simple
CREATE TABLE customers (
customer_id NUMBER(10) PRIMARY KEY,
first_name VARCHAR2(50) NOT NULL,
last_name VARCHAR2(50) NOT NULL,
email VARCHAR2(100) UNIQUE,
created_at DATE DEFAULT SYSDATE,
is_active NUMBER(1) DEFAULT 1 CHECK (is_active IN (0, 1))
);
-- Contraintes
CREATE TABLE orders (
order_id NUMBER PRIMARY KEY,
customer_id NUMBER NOT NULL,
order_date DATE DEFAULT SYSDATE,
total_amount NUMBER(10, 2),
CONSTRAINT fk_customer
FOREIGN KEY (customer_id)
REFERENCES customers(customer_id)
ON DELETE CASCADE,
CONSTRAINT chk_amount CHECK (total_amount >= 0)
);
-- Table depuis SELECT
CREATE TABLE employees_backup AS
SELECT * FROM employees WHERE 1=0; -- Structure uniquement
-- Ajouter colonne ALTER TABLE customers ADD phone VARCHAR2(20); -- Modifier colonne ALTER TABLE customers MODIFY email VARCHAR2(150); -- Supprimer colonne ALTER TABLE customers DROP COLUMN phone; -- Renommer colonne ALTER TABLE customers RENAME COLUMN email TO email_address; -- Ajouter contrainte ALTER TABLE customers ADD CONSTRAINT chk_email CHECK (email LIKE '%@%');
-- Index simple CREATE INDEX idx_customers_email ON customers(email); -- Index composite CREATE INDEX idx_orders_cust_date ON orders(customer_id, order_date); -- Index unique CREATE UNIQUE INDEX idx_customers_email_unique ON customers(email); -- Index basé sur fonction CREATE INDEX idx_customers_upper_name ON customers(UPPER(last_name)); -- Bitmap index (pour faible cardinalité) CREATE BITMAP INDEX idx_orders_status ON orders(status);
-- Transaction simple
BEGIN
UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
UPDATE accounts SET balance = balance + 100 WHERE account_id = 2;
COMMIT;
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
RAISE;
END;
/
-- Savepoint
SAVEPOINT before_delete;
DELETE FROM temp_data;
-- Oops, erreur!
ROLLBACK TO before_delete;
-- Set transaction
SET TRANSACTION READ ONLY;
-- Ou
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
-- Analyser une requête EXPLAIN PLAN FOR SELECT * FROM employees WHERE department_id = 50; -- Voir le plan SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY); -- AUTOTRACE (SQL*Plus) SET AUTOTRACE ON EXPLAIN; SELECT * FROM employees WHERE salary > 5000;
-- Forcer l'utilisation d'un index
SELECT /*+ INDEX(employees idx_emp_dept) */
* FROM employees WHERE department_id = 50;
-- Parallélisme
SELECT /*+ PARALLEL(employees, 4) */
* FROM employees;
-- FULL table scan
SELECT /*+ FULL(employees) */
* FROM employees WHERE department_id = 50;
-- Ordre de jointure
SELECT /*+ ORDERED */
e.first_name, d.department_name
FROM employees e, departments d
WHERE e.department_id = d.department_id;
EXISTS plutôt que IN pour de grandes tablesSELECT *, spécifier les colonnesUNION ALL plutôt que UNION si pas de doublonsBULK COLLECT et FORALL en PL/SQLDBMS_STATS.GATHER_TABLE_STATSMission: optimiser un rapport SQL réel sur une table volumineuse.