Oracle Cheat Sheet

Oracle SQL, grouped by task. Two Oracle-specific habits explain most of the syntax below: DATE always carries a time component (so equality on a date rarely matches), and an empty string is stored as NULL, so IS NULL is the only test that works. FETCH FIRST needs 12c or later; the ROWNUM form still works everywhere and is shown alongside it.

Dates and times

SELECT SYSDATE, SYSTIMESTAMP, CURRENT_DATE FROM dual;
SELECT TO_DATE('2026-08-26', 'YYYY-MM-DD') FROM dual;
SELECT TO_CHAR(created_at, 'YYYY-MM-DD HH24:MI') FROM orders;
SELECT TRUNC(created_at)        AS day,
       TRUNC(created_at, 'MM')  AS month
FROM orders;
SELECT created_at + 7            AS in_a_week,
       ADD_MONTHS(created_at, 3) AS in_a_quarter,
       created_at + 1/24         AS in_an_hour
FROM orders;
SELECT delivered_at - created_at              AS days,
       MONTHS_BETWEEN(delivered_at, created_at) AS months
FROM orders;
SELECT EXTRACT(YEAR FROM created_at), TO_CHAR(created_at, 'MM') FROM orders;

Nulls and conditionals

SELECT NVL(discount, 0), NVL2(discount, 'has one', 'none') FROM orders;
SELECT COALESCE(nickname, first_name, 'there') FROM customers;
SELECT DECODE(status, 'N', 'New', 'S', 'Shipped', 'Other') FROM orders;
SELECT CASE WHEN total > 100 THEN 'large' ELSE 'small' END FROM orders;
Turn a value into NULL
SELECT total / NULLIF(item_count, 0) FROM orders;

Strings and regex

SELECT first_name || ' ' || last_name AS name FROM customers;
SELECT SUBSTR(sku, 1, 3) AS prefix, SUBSTR(sku, -4) AS suffix FROM order_items;
SELECT INSTR(email, '@') FROM customers;
SELECT * FROM customers WHERE REGEXP_LIKE(email, '^[a-z.]+@acme\.(io|dev)$');
SELECT REGEXP_SUBSTR(url, 'utm_source=([^&]+)', 1, 1, NULL, 1) FROM sessions;
SELECT REGEXP_SUBSTR(tags, '[^,]+', 1, LEVEL) AS tag
FROM posts
CONNECT BY LEVEL <= REGEXP_COUNT(tags, ',') + 1;
SELECT customer_id, LISTAGG(sku, ', ') WITHIN GROUP (ORDER BY sku)
FROM order_items GROUP BY customer_id;
SELECT TO_NUMBER('1234.50', '99999D99') FROM dual;
Pad and trim
SELECT LPAD(code, 8, '0'), TRIM(name), UPPER(email) FROM customers;

Ranking, paging and pivots

SELECT * FROM orders ORDER BY created_at DESC
FETCH FIRST 50 ROWS ONLY;
SELECT * FROM (
  SELECT * FROM orders ORDER BY created_at DESC
) WHERE ROWNUM <= 50;
Page through results
SELECT * FROM orders ORDER BY created_at DESC
OFFSET 100 ROWS FETCH NEXT 50 ROWS ONLY;
SELECT customer_id, total,
       ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY total DESC) AS rn
FROM orders;
SELECT RANK() OVER (ORDER BY total DESC), DENSE_RANK() OVER (ORDER BY total DESC)
FROM orders;
WITH recent AS (
  SELECT * FROM orders WHERE created_at > SYSDATE - 30
)
SELECT customer_id, COUNT(*) FROM recent GROUP BY customer_id;
SELECT * FROM (SELECT customer_id, status, total FROM orders)
PIVOT (SUM(total) FOR status IN ('new' AS new_total, 'shipped' AS shipped_total));
SELECT LEVEL, employee_id, manager_id
FROM employees
START WITH manager_id IS NULL
CONNECT BY PRIOR employee_id = manager_id;

Changing data

Insert one row
INSERT INTO orders (customer_id, total) VALUES (1, 49.90);
INSERT ALL
  INTO orders (customer_id, total) VALUES (1, 49.90)
  INTO orders (customer_id, total) VALUES (2, 12.00)
SELECT * FROM dual;
UPDATE orders o
SET total = (SELECT t.amount FROM order_totals t WHERE t.order_id = o.id)
WHERE EXISTS (SELECT 1 FROM order_totals t WHERE t.order_id = o.id);
MERGE INTO orders t
USING orders_staging s ON (t.id = s.id)
WHEN MATCHED THEN UPDATE SET t.total = s.total
WHEN NOT MATCHED THEN INSERT (id, total) VALUES (s.id, s.total);
DELETE FROM customers
WHERE ROWID NOT IN (SELECT MIN(ROWID) FROM customers GROUP BY email);
Commit or roll back
COMMIT;
ROLLBACK;

Tables, sequences and the data dictionary

CREATE TABLE orders (
  id NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
  customer_id NUMBER NOT NULL,
  total NUMBER(10,2),
  created_at DATE DEFAULT SYSDATE
);
CREATE TABLE orders_backup AS SELECT * FROM orders;
ALTER TABLE orders ADD (status VARCHAR2(20) DEFAULT 'new');
Rename a table or column
ALTER TABLE orders RENAME TO customer_orders;
ALTER TABLE orders RENAME COLUMN total TO amount;
Change a column type
ALTER TABLE orders MODIFY (total NUMBER(12,2));
CREATE SEQUENCE order_seq START WITH 1 INCREMENT BY 1 NOCACHE;
SELECT order_seq.NEXTVAL FROM dual;
SELECT table_name FROM user_tables ORDER BY table_name;
SELECT owner, table_name FROM all_tables WHERE owner = 'APP';
Show a table's columns
SELECT column_name, data_type, nullable
FROM user_tab_columns WHERE table_name = 'ORDERS' ORDER BY column_id;
Version and current session
SELECT banner FROM v$version;
SELECT USER, SYS_CONTEXT('USERENV', 'DB_NAME') FROM dual;

All 35 Oracle how-to guides

Each guide is a short answer with examples you can copy and run, plus the gotchas and errors that come with it.

Run Oracle queries without a desktop client

SaturnSQL is a browser-based SQL editor for teams: shared query library, schema-aware autocomplete, and scheduled exports to Google Sheets and Slack.

Try it free

Do more with Oracle

The same tasks in other databases