PostgreSQL Operators
Overview
Operators are symbols or keywords used to perform operations. PostgreSQL supports various types of operators including arithmetic, comparison, logical, string, and other special operators.
Arithmetic Operators
Basic Arithmetic Operations
sql
-- Addition
SELECT 10 + 5 AS sum; -- 15
-- Subtraction
SELECT 10 - 5 AS difference; -- 5
-- Multiplication
SELECT 10 * 5 AS product; -- 50
-- Division
SELECT 10 / 5 AS quotient; -- 2
SELECT 10.0 / 3.0 AS decimal_division; -- 3.333...
-- Modulo (remainder)
SELECT 10 % 3 AS remainder; -- 1
-- Exponentiation
SELECT 2 ^ 3 AS power; -- 8
-- Square root
SELECT |/ 25 AS square_root; -- 5
-- Cube root
SELECT ||/ 27 AS cube_root; -- 3
-- Factorial
SELECT 5! AS factorial; -- 120Practical Examples
sql
-- Calculate order totals
SELECT
order_id,
price * quantity AS subtotal,
(price * quantity) * 0.08 AS tax,
(price * quantity) * 1.08 AS total
FROM order_items;
-- Calculate discounted prices
SELECT
product_name,
price AS original_price,
price * 0.8 AS discounted_price,
price - (price * 0.8) AS savings
FROM products;
-- Calculate percentages
SELECT
category,
COUNT(*) AS count,
COUNT(*) * 100.0 / (SELECT COUNT(*) FROM products) AS percentage
FROM products
GROUP BY category;Comparison Operators
Basic Comparisons
sql
-- Equal
SELECT * FROM products WHERE price = 100;
-- Not equal
SELECT * FROM products WHERE price <> 50;
SELECT * FROM products WHERE price != 50; -- Same as above
-- Greater than
SELECT * FROM products WHERE price > 50;
-- Greater than or equal
SELECT * FROM products WHERE price >= 50;
-- Less than
SELECT * FROM products WHERE price < 100;
-- Less than or equal
SELECT * FROM products WHERE price <= 100;Comparison Examples
sql
-- Find products in specific price range
SELECT * FROM products
WHERE price >= 50 AND price <= 100;
-- Find orders not in specific status
SELECT * FROM orders
WHERE status <> 'cancelled';
-- Find highly rated products
SELECT * FROM products
WHERE rating >= 4.5;Logical Operators
AND Operator
sql
-- All conditions must be true
SELECT * FROM users
WHERE status = 'active'
AND country = 'USA'
AND age >= 18;
-- Multiple AND conditions
SELECT * FROM products
WHERE category = 'Electronics'
AND price > 100
AND stock > 0
AND rating >= 4.0;OR Operator
sql
-- At least one condition must be true
SELECT * FROM products
WHERE category = 'Electronics'
OR category = 'Books';
-- Multiple OR conditions
SELECT * FROM users
WHERE country = 'USA'
OR country = 'UK'
OR country = 'Canada';NOT Operator
sql
-- Negate condition
SELECT * FROM users
WHERE NOT status = 'banned';
-- Equivalent to
SELECT * FROM users
WHERE status <> 'banned';
-- NOT with other operators
SELECT * FROM products
WHERE NOT (price > 1000 OR discontinued = true);Combining Logical Operators
sql
-- Use parentheses to control precedence
SELECT * FROM products
WHERE (category = 'Electronics' OR category = 'Computers')
AND price < 500
AND stock > 0;
-- Complex logic
SELECT * FROM orders
WHERE (status = 'pending' OR status = 'processing')
AND (priority = 'high' OR total_amount > 1000)
AND NOT payment_method = 'cash';String Operators
Concatenation Operator
sql
-- Use || to concatenate strings
SELECT first_name || ' ' || last_name AS full_name FROM users;
-- Concatenate multiple strings
SELECT 'Order #' || order_id || ' - ' || status AS order_info FROM orders;
-- Use CONCAT function
SELECT CONCAT(first_name, ' ', last_name) AS full_name FROM users;
SELECT CONCAT('Total: $', price) AS price_label FROM products;Pattern Matching Operators
LIKE Operator
sql
-- % matches any characters
SELECT * FROM users WHERE name LIKE 'J%'; -- Starts with J
SELECT * FROM users WHERE email LIKE '%@gmail.com'; -- Ends with @gmail.com
SELECT * FROM users WHERE name LIKE '%john%'; -- Contains john
-- _ matches single character
SELECT * FROM users WHERE name LIKE '_ohn'; -- Matches John, Kohn, etc.
-- NOT LIKE
SELECT * FROM users WHERE email NOT LIKE '%@spam.com';ILIKE Operator (Case-Insensitive)
sql
-- Case-insensitive matching
SELECT * FROM users WHERE name ILIKE 'john%'; -- Matches John, JOHN, john
-- Case-insensitive search
SELECT * FROM products WHERE name ILIKE '%laptop%';SIMILAR TO Operator
sql
-- SQL standard regex
SELECT * FROM users WHERE name SIMILAR TO '(John|Jane)%';
SELECT * FROM products WHERE sku SIMILAR TO '[A-Z]{3}[0-9]{4}';Regular Expression Operators
sql
-- ~ case-sensitive regex match
SELECT * FROM users WHERE name ~ '^J';
SELECT * FROM users WHERE email ~ '[0-9]+';
-- ~* case-insensitive regex match
SELECT * FROM users WHERE name ~* '^john';
-- !~ does not match (case-sensitive)
SELECT * FROM users WHERE email !~ '@spam\.com$';
-- !~* does not match (case-insensitive)
SELECT * FROM users WHERE name !~* '^test';Range Operators
BETWEEN Operator
sql
-- Numeric range
SELECT * FROM products WHERE price BETWEEN 10 AND 100;
-- Equivalent to
SELECT * FROM products WHERE price >= 10 AND price <= 100;
-- Date range
SELECT * FROM orders
WHERE order_date BETWEEN '2024-01-01' AND '2024-12-31';
-- NOT BETWEEN
SELECT * FROM products WHERE price NOT BETWEEN 10 AND 100;IN Operator
sql
-- List of values
SELECT * FROM users WHERE country IN ('USA', 'UK', 'Canada');
-- Numeric list
SELECT * FROM products WHERE category_id IN (1, 2, 3, 5, 8);
-- Subquery
SELECT * FROM products
WHERE category_id IN (SELECT id FROM categories WHERE active = true);
-- NOT IN
SELECT * FROM users WHERE country NOT IN ('Spam', 'Test');NULL Operators
IS NULL and IS NOT NULL
sql
-- Check for NULL values
SELECT * FROM users WHERE phone IS NULL;
-- Check for non-NULL values
SELECT * FROM users WHERE phone IS NOT NULL;
-- Multiple NULL checks
SELECT * FROM users
WHERE phone IS NULL
AND address IS NULL;IS DISTINCT FROM
sql
-- NULL-safe comparison
SELECT * FROM users WHERE email IS DISTINCT FROM NULL;
-- Equivalent to
SELECT * FROM users WHERE email IS NOT NULL;
-- Compare two potentially NULL values
SELECT * FROM products
WHERE old_price IS DISTINCT FROM new_price;Array Operators
sql
-- Array contains
SELECT * FROM products WHERE tags @> ARRAY['electronics', 'sale'];
-- Array is contained by
SELECT * FROM products WHERE ARRAY['electronics'] <@ tags;
-- Array overlap
SELECT * FROM products WHERE tags && ARRAY['new', 'featured'];
-- Array concatenation
SELECT ARRAY[1,2,3] || ARRAY[4,5,6]; -- {1,2,3,4,5,6}
-- Array element access
SELECT tags[1] FROM products; -- First elementJSON Operators
sql
-- Get JSON field
SELECT data->>'name' AS name FROM users;
SELECT data->'address'->>'city' AS city FROM users;
-- JSON path
SELECT data#>'{address,city}' AS city FROM users;
-- JSON contains
SELECT * FROM users WHERE data @> '{"status": "active"}';
-- JSON key exists
SELECT * FROM users WHERE data ? 'email';Bitwise Operators
sql
-- Bitwise AND
SELECT 5 & 3; -- 1
-- Bitwise OR
SELECT 5 | 3; -- 7
-- Bitwise XOR
SELECT 5 # 3; -- 6
-- Bitwise NOT
SELECT ~5; -- -6
-- Left shift
SELECT 5 << 2; -- 20
-- Right shift
SELECT 20 >> 2; -- 5Type Cast Operators
sql
-- :: operator
SELECT '100'::INTEGER;
SELECT '2024-01-15'::DATE;
SELECT NOW()::DATE;
SELECT 10::NUMERIC(10, 2);
-- CAST function
SELECT CAST('100' AS INTEGER);
SELECT CAST('2024-01-15' AS DATE);
SELECT CAST(price AS TEXT) FROM products;Subquery Operators
EXISTS Operator
sql
-- Check if subquery returns rows
SELECT * FROM users u
WHERE EXISTS (
SELECT 1 FROM orders o
WHERE o.user_id = u.id
);
-- NOT EXISTS
SELECT * FROM users u
WHERE NOT EXISTS (
SELECT 1 FROM orders o
WHERE o.user_id = u.id
);ANY/SOME Operator
sql
-- Greater than any value
SELECT * FROM products
WHERE price > ANY (SELECT price FROM products WHERE category = 'Books');
-- Equal to any value
SELECT * FROM users
WHERE country = ANY (ARRAY['USA', 'UK', 'Canada']);ALL Operator
sql
-- Greater than all values
SELECT * FROM products
WHERE price > ALL (SELECT price FROM products WHERE category = 'Books');
-- Not equal to all values
SELECT * FROM users
WHERE country <> ALL (ARRAY['Spam', 'Test']);Operator Precedence
From highest to lowest:
::(type cast)[](array element).(table/column name separator)-(unary minus)^(exponentiation)*,/,%(multiply, divide, modulo)+,-(add, subtract)||(string concatenation)BETWEEN,IN,LIKE,ILIKE,SIMILAR TO<,>,=,<=,>=,<>(comparison)IS NULL,IS NOT NULL,IS DISTINCT FROMNOT(logical NOT)AND(logical AND)OR(logical OR)
Use Parentheses to Control Precedence
sql
-- Without parentheses
SELECT * FROM products WHERE price > 50 OR category = 'Books' AND stock > 0;
-- Equivalent to: WHERE price > 50 OR (category = 'Books' AND stock > 0)
-- With parentheses to clarify intent
SELECT * FROM products WHERE (price > 50 OR category = 'Books') AND stock > 0;Practical Examples
Example 1: E-commerce Product Filtering
sql
SELECT
product_name,
price,
CASE
WHEN price < 50 THEN 'Budget'
WHEN price BETWEEN 50 AND 200 THEN 'Mid-range'
ELSE 'Premium'
END AS price_range,
stock > 0 AS in_stock
FROM products
WHERE (category IN ('Electronics', 'Computers') OR on_sale = true)
AND price <= 1000
AND rating >= 4.0
AND NOT discontinued = true;Example 2: User Data Analysis
sql
SELECT
first_name || ' ' || last_name AS full_name,
email,
EXTRACT(YEAR FROM AGE(birth_date)) AS age,
CASE
WHEN EXTRACT(YEAR FROM AGE(birth_date)) < 18 THEN 'Minor'
WHEN EXTRACT(YEAR FROM AGE(birth_date)) BETWEEN 18 AND 65 THEN 'Adult'
ELSE 'Senior'
END AS age_group
FROM users
WHERE status = 'active'
AND email LIKE '%@company.com'
AND created_at >= CURRENT_DATE - INTERVAL '1 year';Example 3: Order Statistics
sql
SELECT
order_id,
customer_id,
total_amount,
total_amount * 0.08 AS tax,
total_amount * 1.08 AS total_with_tax,
CASE
WHEN total_amount > 1000 THEN 'VIP'
WHEN total_amount > 500 THEN 'Premium'
ELSE 'Standard'
END AS order_tier
FROM orders
WHERE status IN ('completed', 'shipped')
AND order_date BETWEEN '2024-01-01' AND '2024-12-31'
AND NOT (payment_method = 'cash' AND total_amount > 5000);Performance Optimization Tips
sql
-- 1. Create indexes on frequently used operator columns
CREATE INDEX idx_products_price ON products(price);
CREATE INDEX idx_users_status ON users(status);
-- 2. Avoid functions on indexed columns
-- Bad
SELECT * FROM users WHERE UPPER(email) = 'JOHN@EXAMPLE.COM';
-- Good
SELECT * FROM users WHERE email = 'john@example.com';
-- 3. Use appropriate operators
-- Use IN instead of multiple OR
SELECT * FROM users WHERE country IN ('USA', 'UK', 'Canada');
-- 4. Use EXPLAIN to analyze queries
EXPLAIN ANALYZE
SELECT * FROM products WHERE price > 100 AND category = 'Electronics';Best Practices
- Use parentheses for clarity: Even when not required, use parentheses to make logic clearer
- Choose the right operator: Use the most appropriate operator for the task
- Handle NULL properly: Use IS NULL instead of = NULL
- Consider performance: Create indexes on frequently used columns
- Match types: Ensure data types match in comparisons
- Use standard SQL: Prefer standard SQL operators for better portability
Summary
PostgreSQL operators are fundamental to building queries:
- Arithmetic operators: +, -, *, /, %, ^
- Comparison operators: =, <>, <, >, <=, >=
- Logical operators: AND, OR, NOT
- String operators: ||, LIKE, ILIKE, ~
- Range operators: BETWEEN, IN
- NULL operators: IS NULL, IS NOT NULL
- Special operators: EXISTS, ANY, ALL
Understanding and correctly using operators is crucial for writing efficient SQL queries.
PostgreSQL Expressions
Expression Overview
Expressions are combinations of values and operators in PostgreSQL that compute results. Expressions can be used in SELECT, WHERE, HAVING, and other clauses.
Conditional Expressions
CASE Expression
Simple CASE Expression
sql
-- Basic syntax
SELECT
name,
status,
CASE status
WHEN 'active' THEN 'Active'
WHEN 'pending' THEN 'Pending Review'
WHEN 'suspended' THEN 'Suspended'
ELSE 'Unknown'
END AS status_label
FROM users;
-- Practical application
SELECT
product_name,
category,
CASE category
WHEN 'Electronics' THEN 'Electronics'
WHEN 'Books' THEN 'Books'
WHEN 'Clothing' THEN 'Apparel'
ELSE 'Other'
END AS category_label
FROM products;Searched CASE Expression
sql
-- Basic syntax
SELECT
name,
age,
CASE
WHEN age < 18 THEN 'Minor'
WHEN age >= 18 AND age < 65 THEN 'Adult'
ELSE 'Senior'
END AS age_group
FROM users;
-- Price tiers
SELECT
product_name,
price,
CASE
WHEN price < 50 THEN 'Budget'
WHEN price >= 50 AND price < 200 THEN 'Mid-range'
WHEN price >= 200 AND price < 1000 THEN 'Premium'
ELSE 'Luxury'
END AS price_tier
FROM products;
-- Order priority
SELECT
order_id,
total_amount,
customer_type,
CASE
WHEN customer_type = 'VIP' THEN 1
WHEN total_amount > 1000 THEN 2
WHEN total_amount > 500 THEN 3
ELSE 4
END AS priority
FROM orders;Nested CASE Expression
sql
SELECT
product_name,
category,
price,
stock,
CASE
WHEN stock = 0 THEN 'Out of Stock'
WHEN stock < 10 THEN
CASE
WHEN price > 500 THEN 'Low Stock - High Value'
ELSE 'Low Stock - Regular'
END
ELSE 'In Stock'
END AS stock_status
FROM products;COALESCE Expression
sql
-- Return first non-NULL value
SELECT COALESCE(phone, 'No phone') AS contact_phone FROM users;
-- Multiple fallback values
SELECT COALESCE(mobile, home_phone, work_phone, 'No contact') AS phone FROM users;
-- Practical application
SELECT
name,
COALESCE(email, 'no-email@example.com') AS email,
COALESCE(phone, 'N/A') AS phone,
COALESCE(address, 'No address provided') AS address
FROM users;
-- Use in calculations
SELECT
product_name,
price,
COALESCE(discount_price, price) AS final_price,
price - COALESCE(discount_price, price) AS savings
FROM products;NULLIF Expression
sql
-- Return NULL if two values are equal
SELECT NULLIF(price, 0) FROM products;
SELECT NULLIF(email, '') FROM users;
-- Avoid division by zero
SELECT
total_sales,
total_orders,
total_sales / NULLIF(total_orders, 0) AS average_order_value
FROM sales_summary;
-- Practical application
SELECT
product_name,
old_price,
new_price,
NULLIF(old_price, new_price) AS price_changed
FROM products;GREATEST and LEAST Expressions
sql
-- GREATEST returns maximum value
SELECT GREATEST(10, 20, 15) AS max_value; -- 20
SELECT GREATEST(price, cost, 0) AS highest FROM products;
-- LEAST returns minimum value
SELECT LEAST(10, 20, 15) AS min_value; -- 10
SELECT LEAST(stock, reorder_level) AS min_stock FROM products;
-- Practical application
SELECT
product_name,
price,
competitor_price,
LEAST(price, competitor_price) AS best_price,
GREATEST(price, competitor_price) - LEAST(price, competitor_price) AS price_difference
FROM products;Window Expressions
sql
-- ROW_NUMBER
SELECT
name,
price,
ROW_NUMBER() OVER (ORDER BY price DESC) AS price_rank
FROM products;
-- RANK and DENSE_RANK
SELECT
name,
category,
price,
RANK() OVER (PARTITION BY category ORDER BY price DESC) AS rank,
DENSE_RANK() OVER (PARTITION BY category ORDER BY price DESC) AS dense_rank
FROM products;
-- Aggregate window functions
SELECT
order_date,
total_amount,
SUM(total_amount) OVER (ORDER BY order_date) AS running_total,
AVG(total_amount) OVER (ORDER BY order_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS moving_avg
FROM orders;Expression Best Practices
- Use parentheses for readability: Use parentheses in complex expressions to clarify precedence
- Avoid excessive nesting: Deeply nested expressions are hard to maintain
- Use meaningful aliases: Provide clear aliases for complex expressions
- Handle NULL values: Use COALESCE or NULLIF to handle NULL values
- Consider performance: Complex expressions may impact query performance
- Type matching: Ensure data types are compatible in expressions
Expression Summary
PostgreSQL expressions provide powerful data processing capabilities:
- CASE expressions: Conditional logic
- COALESCE: NULL value handling
- NULLIF: Conditional NULL return
- GREATEST/LEAST: Multi-value comparison
- Window expressions: Advanced analytics
Mastering expressions enables writing more flexible and powerful SQL queries.