SQL Syntax Best Practices

SQL Syntax Best Practices

Introduction

SQL may look intimidating at first, but once you understand its basic rules and flow, it becomes one of the simplest and most powerful tools you’ll ever use. In any real database system — especially in financial, stock-market, or high-volume analytics environments — clean SQL isn’t a luxury; it’s a necessity. I used Coursera Labs to run some SQL queries to refresh my memories on basics. You need to know how to structure your queries properly, avoid common mistakes, and write efficient, readable code that actually performs well under large datasets. It is not a rememberable memory if we are not on DB related work daily.That’s why I like to break SQL down into simple, foundational ideas: the rules, the best practices, and the pitfalls you must avoid. Once you get those right, everything else becomes a matter of consistent practice.

This article walks through the basic SQL syntax rules, how to write high-quality SELECT queries, what errors beginners frequently make, and then connects all of this to real-world stock exchange scenarios. Even if you’re new to SQL, you’ll walk away understanding not just how queries work — but how to think like an analyst who handles millions of rows daily.

Let us look at some flow of SQL Query in sequence of images:

Article content


Article content


Article content


Article content


Article content


Article content


Article content


Watch the video: Link


Basic Rules

Here are the basic SQL syntax rules:

  • SQL Keywords: Not case-sensitive (e.g., SELECT, INSERT, UPDATE), but usually written in uppercase for readability.
  • Statements End with Semicolon (;): Used to separate SQL statements (mandatory in some databases).
  • Whitespace and Line Breaks: Ignored but used for better readability.
  • Single Quotes (') for String Values: Example: WHERE name = 'John' (Double quotes are used for identifiers in some databases).
  • **Backticks (\``) for Column and Table Names**: Used in MySQL for special column or table names (e.g., SELECT * FROM order`).
  • Case Sensitivity: Column and table names may be case-sensitive depending on the database.
  • Comments: Single-line comment: -- This is a comment Multi-line comment: /* This is a multi-line comment */
  • Clauses Order in Queries (for SELECT statements): SELECT (columns) FROM (table) WHERE (conditions) GROUP BY (grouping data) HAVING (filtering grouped data) ORDER BY (sorting results) LIMIT (restricting number of rows)
  • Aliasing (AS): Used to rename columns or tables for readability (e.g., SELECT first_name AS name FROM users).
  • JOINs: Used to combine data from multiple tables (INNER JOIN, LEFT JOIN, RIGHT JOIN, FULL JOIN).
  • Functions and Operators: Use built-in SQL functions like COUNT(), SUM(), AVG(), and operators like =, >, <, AND, OR, LIKE, etc.


Best Practices in Select Queries

Best Practices for SELECT Queries in SQL

✅ 1. Use Specific Column Names Instead of SELECT *

🔹 Avoid SELECT * as it retrieves unnecessary data, which can slow down performance. ✅ Example:

SELECT id, name, email FROM users;
        

✅ 2. Use Proper Aliases for Readability

🔹 Use AS to make column names more readable. ✅ Example:

SELECT first_name AS Name, created_at AS RegistrationDate FROM users;
        

✅ 3. Filter Data Efficiently with WHERE

🔹 Always use WHERE to limit rows instead of retrieving all data. ✅ Example:

SELECT * FROM orders WHERE status = 'Completed';
        

✅ 4. Use LIMIT to Restrict Rows

🔹 If you only need a few rows, use LIMIT to improve query performance. ✅ Example:

SELECT * FROM customers ORDER BY created_at DESC LIMIT 10;
        

✅ 5. Use ORDER BY for Sorted Results

🔹 Ensure your results are sorted appropriately. ✅ Example:

SELECT name, age FROM employees ORDER BY age DESC;
        

✅ 6. Use GROUP BY with Aggregate Functions

🔹 If using aggregate functions (COUNT(), SUM(), etc.), always include GROUP BY. ✅ Example:

SELECT department, COUNT(*) FROM employees GROUP BY department;
        

✅ 7. Use HAVING for Filtering Aggregated Results

🔹 Use HAVING instead of WHERE for conditions on aggregated data. ✅ Example:

SELECT department, COUNT(*) FROM employees GROUP BY department HAVING COUNT(*) > 10;
        

✅ 8. Use Indexing for Faster Queries

🔹 Ensure frequently queried columns (especially in WHERE or JOIN clauses) are indexed.

✅ 9. Optimize JOIN Operations

🔹 Use appropriate joins (INNER JOIN, LEFT JOIN) and ensure indexes are in place. ✅ Example:

SELECT u.name, o.order_date 
FROM users u 
INNER JOIN orders o ON u.id = o.user_id;
        

✅ 10. Avoid Using DISTINCT Unnecessarily

🔹 DISTINCT can be performance-heavy. Use only when duplicate removal is necessary. ✅ Example:

SELECT DISTINCT city FROM customers;
        

✅ 11. Use EXPLAIN to Analyze Query Performance

🔹 Use EXPLAIN before executing a query to check how the database processes it. ✅ Example:

EXPLAIN SELECT * FROM orders WHERE status = 'Pending';
        



Most Common Syntax Errors in SELECT Queries in SQL

❌ 1. Missing or Incorrect FROM Clause

Correct:

SELECT name FROM users;
        

Error:

SELECT name;  -- Missing FROM clause
        

❌ 2. Using WHERE Instead of HAVING with Aggregates

Correct:

SELECT department, COUNT(*) FROM employees GROUP BY department HAVING COUNT(*) > 10;
        

Error:

SELECT department, COUNT(*) FROM employees GROUP BY department WHERE COUNT(*) > 10;  -- WHERE can't be used with aggregate functions
        

❌ 3. Using SELECT * in Large Queries

🔹 Not a syntax error but a bad practice affecting performance. ✅ Use specific columns:

SELECT id, name FROM customers;
        

Error:

SELECT * FROM customers;  -- Fetches unnecessary data
        

❌ 4. Incorrect Column or Table Name Spelling

Correct:

SELECT first_name FROM employees;
        

Error:

SELECT fist_name FROM employees;  -- Column name is incorrect
        

❌ 5. Missing Quotes for String Values

Correct:

SELECT * FROM users WHERE name = 'John';
        

Error:

SELECT * FROM users WHERE name = John;  -- Strings must be enclosed in single quotes
        

❌ 6. Using AS in WHERE Clause

Correct:

SELECT first_name AS name FROM users WHERE first_name = 'Alice';
        

Error:

SELECT first_name AS name FROM users WHERE name = 'Alice';  -- Alias can't be used in WHERE
        

❌ 7. Misplaced ORDER BY or GROUP BY

Correct:

SELECT department, COUNT(*) FROM employees GROUP BY department ORDER BY COUNT(*) DESC;
        

Error:

SELECT department, COUNT(*) FROM employees ORDER BY COUNT(*) DESC GROUP BY department;  -- Incorrect order of clauses
        

❌ 8. Incorrect JOIN Syntax

Correct:

SELECT users.name, orders.order_date 
FROM users 
INNER JOIN orders ON users.id = orders.user_id;
        

Error:

SELECT users.name, orders.order_date 
FROM users 
INNER JOIN orders;  -- Missing ON condition
        

❌ 9. Missing GROUP BY with Aggregate Functions

Correct:

SELECT department, COUNT(*) FROM employees GROUP BY department;
        

Error:

SELECT department, COUNT(*) FROM employees;  -- GROUP BY is required
        

❌ 10. Using DISTINCT Incorrectly

Correct:

SELECT DISTINCT city FROM customers;
        

Error:

SELECT city DISTINCT FROM customers;  -- Incorrect placement of DISTINCT
        

Let us deep dive into SQL Joins to understand the best part of their features.

SQL Joins

  • Inner joins
  • Left joins
  • Right joins
  • Self-join and cross join
  • UNION and UNION ALL

Here are SQL examples for different types of joins using a stock exchange dataset where we have large indices of digital assets.


Inner Join

Retrieves only matching records from both tables.

SELECT 
    a.asset_id, a.asset_name, t.trade_date, t.price
FROM 
    assets a
INNER JOIN 
    trades t ON a.asset_id = t.asset_id
WHERE 
    t.trade_date = '2024-02-01';
        

Use case: Fetching only those digital assets that have recorded trades on a specific date.


Left Join

Retrieves all records from the left table and only matching records from the right table.

SELECT 
    a.asset_id, a.asset_name, t.trade_date, t.price
FROM 
    assets a
LEFT JOIN 
    trades t ON a.asset_id = t.asset_id
WHERE 
    t.trade_date IS NULL;
        

Use case: Finding digital assets that have never been traded.


Right Join

Retrieves all records from the right table and only matching records from the left table.

SELECT 
    t.trade_id, t.trade_date, a.asset_name, a.market_cap
FROM 
    trades t
RIGHT JOIN 
    assets a ON t.asset_id = a.asset_id;
        

Use case: Ensuring that all trades have associated digital asset details.


Self Join

Joins a table with itself.

SELECT 
    a1.asset_name AS asset_1, a2.asset_name AS asset_2, a1.market_cap, a2.market_cap
FROM 
    assets a1
JOIN 
    assets a2 ON a1.market_cap > a2.market_cap
WHERE 
    a1.sector = a2.sector;
        

Use case: Comparing digital assets within the same sector based on market capitalization.


Cross Join

Returns all possible combinations between two tables.

SELECT 
    a.asset_name, i.index_name
FROM 
    assets a
CROSS JOIN 
    indices i;
        

Use case: Generating all possible asset-index pairings to analyze potential index inclusion.


UNION

Combines the result sets of two queries while removing duplicates.

SELECT asset_id, asset_name FROM assets
UNION
SELECT asset_id, 'Delisted' FROM delisted_assets;
        

Use case: Merging active and delisted digital asset records while avoiding duplicates.


UNION ALL

Combines result sets including duplicates.

SELECT asset_id, asset_name FROM assets
UNION ALL
SELECT asset_id, 'Delisted' FROM delisted_assets;
        

Use case: Keeping track of asset history, including duplicate entries when necessary.


Keys and Relationships

Here are examples of different types of keys and relationships in a stock exchange scenario where digital assets are tracked.


Primary Key

A primary key uniquely identifies each record in a table.

CREATE TABLE assets (
    asset_id INT PRIMARY KEY, 
    asset_name VARCHAR(255) NOT NULL, 
    market_cap DECIMAL(18,2), 
    sector VARCHAR(100)
);
        

  • asset_id is the primary key because it uniquely identifies each digital asset.
  • No two assets can have the same asset_id, ensuring uniqueness.


Surrogate Key

A surrogate key is an artificial identifier, often auto-incremented.

CREATE TABLE trades (
    trade_id BIGINT AUTO_INCREMENT PRIMARY KEY, 
    asset_id INT NOT NULL, 
    trade_date DATE NOT NULL, 
    price DECIMAL(18,2), 
    volume BIGINT
);
        

  • trade_id is a surrogate key because it is system-generated and does not have any business meaning.
  • asset_id is a foreign key that links to the assets table.


Foreign Key

A foreign key establishes a relationship between two tables.

ALTER TABLE trades 
ADD CONSTRAINT fk_trades_assets 
FOREIGN KEY (asset_id) REFERENCES assets(asset_id);
        

  • The trades table has asset_id as a foreign key, referencing the assets table.
  • This enforces referential integrity, ensuring trades are only recorded for valid assets.


Composite Key

A composite key consists of two or more columns that together form a unique identifier.

CREATE TABLE asset_indices (
    asset_id INT, 
    index_id INT, 
    weight DECIMAL(5,2), 
    PRIMARY KEY (asset_id, index_id)
);
        

  • Primary Key: The combination of asset_id and index_id ensures uniqueness.
  • This table models assets belonging to multiple indices, where each asset can exist in multiple indices but only once per index.


Candidate Key

A candidate key is any column or set of columns that could be a primary key.

CREATE TABLE exchanges (
    exchange_id INT PRIMARY KEY, 
    exchange_code VARCHAR(10) UNIQUE, 
    exchange_name VARCHAR(255) NOT NULL
);
        

  • exchange_id is the primary key.
  • exchange_code is a candidate key because it uniquely identifies an exchange and could be used as the primary key.


Super Key

A super key is any column set that uniquely identifies a record, including the primary key.

Example for the assets table:

  • Super Keys: (asset_id), (asset_id, asset_name), (asset_id, market_cap)
  • Primary Key: (asset_id)

The primary key is the minimal super key.


Unique Key

Ensures unique values in a column, but unlike a primary key, it allows NULL values.

CREATE TABLE traders (
    trader_id INT PRIMARY KEY, 
    email VARCHAR(255) UNIQUE, 
    name VARCHAR(255) NOT NULL
);
        

  • trader_id is the primary key.
  • email is a unique key to prevent duplicate trader registrations.


One-to-One Relationship

Each asset has only one detailed record.

CREATE TABLE asset_details (
    asset_id INT PRIMARY KEY, 
    description TEXT, 
    headquarters VARCHAR(255), 
    FOREIGN KEY (asset_id) REFERENCES assets(asset_id) ON DELETE CASCADE
);
        

  • One asset_id in assets maps to one asset_id in asset_details.


One-to-Many Relationship

An asset can have many trades.

CREATE TABLE trades (
    trade_id BIGINT AUTO_INCREMENT PRIMARY KEY, 
    asset_id INT NOT NULL, 
    trade_date DATE NOT NULL, 
    FOREIGN KEY (asset_id) REFERENCES assets(asset_id)
);
        

  • One asset_id in assets corresponds to multiple trade_ids in trades.


Many-to-Many Relationship

An asset can belong to multiple indices, and an index can have multiple assets.

CREATE TABLE asset_indices (
    asset_id INT, 
    index_id INT, 
    weight DECIMAL(5,2), 
    PRIMARY KEY (asset_id, index_id), 
    FOREIGN KEY (asset_id) REFERENCES assets(asset_id), 
    FOREIGN KEY (index_id) REFERENCES indices(index_id)
);
        

  • A junction table is used to model this relationship.

Let us see some real time examples on test database optimization, query performance, and financial data analytics. Here are some commonly asked SQL questions, real-world examples, and efficient solutions used in stock exchange systems.


1. Retrieve the Top 5 Most Traded Stocks in the Last 30 Days

📌 Scenario: You need to find the top 5 stocks with the highest trading volume in the last 30 days.

SELECT asset_symbol, SUM(trade_volume) AS total_volume
FROM trades t
JOIN assets a ON t.asset_id = a.asset_id
WHERE trade_timestamp >= NOW() - INTERVAL 30 DAY
GROUP BY asset_symbol
ORDER BY total_volume DESC
LIMIT 5;
        

Optimizations:

  • Index on trade_timestamp for efficient date filtering.
  • Index on trade_volume for quick aggregations.


2. Find the 10 Most Volatile Stocks Over the Last 6 Months

📌 Scenario: Measure volatility as the standard deviation of daily closing prices.

SELECT asset_symbol, STDDEV(close_price) AS price_volatility
FROM historical_prices h
JOIN assets a ON h.asset_id = a.asset_id
WHERE trade_date >= NOW() - INTERVAL 6 MONTH
GROUP BY asset_symbol
ORDER BY price_volatility DESC
LIMIT 10;
        

Optimizations:

  • Partition historical_prices by trade_date for better performance.
  • Use window functions for rolling volatility calculations.


3. Identify Consecutive Days of Price Increase for a Stock

📌 Scenario: Detect streaks where a stock price increased for multiple consecutive days.

WITH price_changes AS (
    SELECT asset_id, trade_date, 
           close_price - LAG(close_price) OVER (PARTITION BY asset_id ORDER BY trade_date) AS price_diff
    FROM historical_prices
)
SELECT asset_id, COUNT(*) AS streak_length
FROM price_changes
WHERE price_diff > 0
GROUP BY asset_id
ORDER BY streak_length DESC
LIMIT 10;
        

Optimizations:

  • LAG() function to compare current and previous day's price.
  • Partitioning by asset_id to avoid full table scans.


4. Rank Stocks by Performance in the Last Year

📌 Scenario: Rank all assets by their percentage increase over the last year.

WITH yearly_prices AS (
    SELECT asset_id, 
           FIRST_VALUE(close_price) OVER (PARTITION BY asset_id ORDER BY trade_date) AS first_price,
           LAST_VALUE(close_price) OVER (PARTITION BY asset_id ORDER BY trade_date RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS last_price
    FROM historical_prices
    WHERE trade_date BETWEEN NOW() - INTERVAL 1 YEAR AND NOW()
)
SELECT asset_id, 
       ((last_price - first_price) / first_price) * 100 AS percentage_change
FROM yearly_prices
ORDER BY percentage_change DESC
LIMIT 10;
        

Optimizations:

  • FIRST_VALUE() and LAST_VALUE() for efficient stock price retrieval.
  • Partitioning on asset_id for better query performance.


5. Detect Sudden Price Drops Over 10% in a Single Day

📌 Scenario: Find stocks that dropped more than 10% in a single day over the last month.

SELECT asset_id, trade_date, open_price, close_price,
       ((open_price - close_price) / open_price) * 100 AS percentage_drop
FROM historical_prices
WHERE trade_date >= NOW() - INTERVAL 1 MONTH
AND ((open_price - close_price) / open_price) > 0.10
ORDER BY percentage_drop DESC;
        

Optimizations:

  • Index on trade_date for faster filtering.
  • Computed column for percentage drop to avoid recalculations.


6. Find Stocks That Have Never Been Traded

📌 Scenario: Identify digital assets that exist in assets but have no trades recorded.

SELECT a.asset_symbol 
FROM assets a
LEFT JOIN trades t ON a.asset_id = t.asset_id
WHERE t.asset_id IS NULL;
        

Optimizations:

  • Left Join + NULL Check ensures we capture missing trades.
  • Index on asset_id for fast lookups.


7. Calculate the Moving Average of a Stock Over the Last 50 Days

📌 Scenario: Compute a 50-day moving average for a stock.

SELECT asset_id, trade_date, 
       AVG(close_price) OVER (PARTITION BY asset_id ORDER BY trade_date ROWS BETWEEN 49 PRECEDING AND CURRENT ROW) AS moving_avg
FROM historical_prices
WHERE trade_date >= NOW() - INTERVAL 6 MONTH;
        

Optimizations:

  • AVG() OVER() window function prevents multiple joins.
  • Date filtering before aggregation avoids scanning old data.


8. Find Stocks That Consistently Close Higher Than Their Opening Price

📌 Scenario: Stocks that have closed higher than they opened for at least 90% of trading days in the last year.

SELECT asset_id, 
       (SUM(CASE WHEN close_price > open_price THEN 1 ELSE 0 END) * 100 / COUNT(*)) AS uptrend_percentage
FROM historical_prices
WHERE trade_date >= NOW() - INTERVAL 1 YEAR
GROUP BY asset_id
HAVING uptrend_percentage > 90
ORDER BY uptrend_percentage DESC;
        

Optimizations:

  • Conditional aggregation with CASE WHEN for percentage calculations.
  • Partitioning by trade_date for better performance.


9. Find the First and Last Trade for Each Stock

📌 Scenario: Get the first and last recorded trade for each asset.

SELECT asset_id, 
       MIN(trade_timestamp) AS first_trade, 
       MAX(trade_timestamp) AS last_trade
FROM trades
GROUP BY asset_id;
        

Optimizations:

  • MIN() and MAX() for efficient first/last trade retrieval.
  • Index on trade_timestamp to improve aggregation speed.


10. Identify the Most Profitable Stocks Over the Last 6 Months

📌 Scenario: Find stocks with the highest return on investment (ROI).

WITH price_data AS (
    SELECT asset_id, 
           FIRST_VALUE(close_price) OVER (PARTITION BY asset_id ORDER BY trade_date) AS start_price,
           LAST_VALUE(close_price) OVER (PARTITION BY asset_id ORDER BY trade_date RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS end_price
    FROM historical_prices
    WHERE trade_date >= NOW() - INTERVAL 6 MONTH
)
SELECT asset_id, 
       ((end_price - start_price) / start_price) * 100 AS roi
FROM price_data
ORDER BY roi DESC
LIMIT 10;
        

Optimizations:

  • FIRST_VALUE() and LAST_VALUE() avoid subqueries.
  • Partitioned queries ensure efficient lookups.

To understand SQL deeply, we explore a full range of examples — from basic joins to advanced financial analytics queries. You start with the fundamentals: keywords, quoting conventions, comments, and the correct order of clauses like SELECT → FROM → WHERE → GROUP BY → HAVING → ORDER BY. Then we move into the best practices: using specific column names instead of SELECT *, applying LIMIT for efficiency, filtering with WHERE, sorting with ORDER BY, grouping data correctly, and always analysing performance using EXPLAIN plans.

We also look at the most common syntax errors developers make — missing FROM clauses, misusing HAVING, incorrect JOIN statements, and spelling issues in table or column names. These mistakes might look simple, but in production systems they can break dashboards, cause wrong numbers, or slow entire queries.

The article then shifts to SQL joins using practical examples from a stock exchange dataset. You learn inner, left, right, self, and cross joins, along with UNION vs UNION ALL. These joins are not just theory — they’re tied to scenarios like identifying untraded assets, finding matching records, comparing digital asset performance, or generating all possible asset-index combinations.

Finally, we dive into real-time financial analytics SQL queries: ranking high-volume stocks, computing volatility, identifying streaks, detecting price drops, and calculating moving averages. These examples show how SQL powers real trading systems, risk engines, and reporting dashboards. Each solution includes optimization techniques such as indexing, partitioning, and window functions — the tools professionals rely on daily.


Summary

In simple terms, written this article that shows you how SQL actually works in real life. You learn the rules, the structure, and the discipline needed to write clean, effective queries. You understand what to do (best practices) and what not to do (common errors). You also get hands-on examples across joins, keys, relationships, and financial analytics — the kind of scenarios where SQL performance genuinely matters.

SQL isn’t just about typing commands; it’s about thinking clearly. When you write SQL with intention — understanding how databases read your query, how indexes support your logic, and how joins connect your data — you become far more effective as an analyst or engineer.

Any thoughts?


To view or add a comment, sign in

More articles by NARAYANAN PALANI 👁️‍🗨️

Explore content categories