Table of Contents

    SQL for Machine Learning Data Access

    Machine Learning Data Access

    SQL for Machine Learning Data Access

    Learn how to use SQL to retrieve, filter, combine, aggregate, validate, and prepare relational data for Machine Learning projects.

    Introduction

    Many Machine Learning projects begin with data stored in relational databases. Customer records, transactions, products, invoices, inventory movements, support requests, sensor readings, and operational events are commonly organized into related tables.

    SQL, which stands for Structured Query Language, is used to work with relational databases. It allows data professionals to select columns, filter rows, combine tables, calculate summaries, inspect data quality, and create model-ready datasets.

    SQL does not replace Python, pandas, or a Machine Learning library. Instead, SQL is commonly used to retrieve and prepare the required information before modeling continues in Python or another analytical environment.

    Simple definition: SQL is a language used to retrieve and transform structured data stored in relational databases. In Machine Learning projects, it helps create reliable datasets for exploration, training, validation, testing, and inference.

    Learning Objectives

    By the End of This Lesson

    • Explain how SQL supports Machine Learning projects
    • Understand databases, tables, rows, columns, keys, and relationships
    • Retrieve selected columns using SELECT
    • Filter records using WHERE
    • Sort and limit query results
    • Combine related tables using JOIN
    • Create summary features using aggregate functions
    • Group observations using GROUP BY
    • Use CASE expressions to create derived features
    • Work safely with NULL values
    • Use common table expressions and window functions
    • Create time-aware training labels and features
    • Perform data-quality checks using SQL
    • Access SQL data securely from Python
    • Prevent SQL injection and Machine Learning data leakage

    Prerequisites

    Recommended Knowledge

    • Basic understanding of structured and tabular data
    • Basic knowledge of features, targets, and observations
    • Basic understanding of the Machine Learning workflow
    • Basic familiarity with Python is recommended for the final example

    Required Tools

    • Access to an authorized relational database
    • A database client, query editor, or approved development environment
    • Read permission for the required tables or views
    • Python, pandas, SQLAlchemy, and a suitable database driver for Python examples
    • A Python virtual environment is recommended for local development
    Important: SQL syntax differs slightly across database systems. The concepts in this lesson are broadly applicable, but date functions, pagination syntax, quoted identifiers, and connection settings may need to be adjusted for your database platform.

    Understanding Relational Data

    A relational database organizes information into tables. Each table normally represents a business entity, event, or relationship. Tables can be connected using keys.

    Concept Meaning Example
    Database An organized collection of related data A sales and customer database
    Table A structured collection of related rows and columns Customers
    Row One record in a table One customer
    Column One type of information stored for each record Customer creation date
    Primary Key A column or column combination that uniquely identifies a row CustomerId
    Foreign Key A value that connects one table to another table CustomerId in an Orders table
    Schema A namespace that organizes database objects sales.Customers
    View A stored query presented like a table analytics.CustomerFeatures

    Example Database Schema

    The examples in this lesson use a simplified customer-order schema. The exact names and column types in your environment may be different.

    Table Important Columns Purpose
    Customers CustomerId, CreatedDate, Region, Segment, IsActive Stores customer-level information
    Orders OrderId, CustomerId, OrderDate, OrderAmount, OrderStatus Stores order-level information
    SupportRequests RequestId, CustomerId, CreatedDate, Category, Priority Stores customer-support activity
    CustomerOutcomes CustomerId, OutcomeDate, Churned Stores the target outcome for the example model
    Example Relationships
    Customers → Orders + Support Requests + Customer Outcomes

    The SELECT Statement

    The SELECT statement retrieves data from one or more database objects.

    SELECT
        CustomerId,
        CreatedDate,
        Region,
        Segment,
        IsActive
    FROM Customers;
    SQL Element Purpose
    SELECT Specifies the columns to return
    FROM Specifies the source table or view
    CustomerId Identifies the customer record
    Region Provides a possible categorical feature
    Avoid in Production Extraction
    SELECT *
    FROM Customers;
    Recommended
    SELECT
        CustomerId,
        CreatedDate,
        Region,
        Segment
    FROM Customers;

    Selecting only required columns reduces unnecessary data transfer, makes the feature definition clearer, and lowers the risk of retrieving sensitive or irrelevant information.

    Filter Rows with WHERE

    The WHERE clause restricts the returned rows according to one or more conditions.

    SELECT
        CustomerId,
        Region,
        Segment
    FROM Customers
    WHERE IsActive = 1;

    Multiple Conditions

    SELECT
        CustomerId,
        Region,
        Segment
    FROM Customers
    WHERE IsActive = 1
      AND Region = 'East';

    Match Several Values

    SELECT
        CustomerId,
        Region,
        Segment
    FROM Customers
    WHERE Segment IN (
        'Standard',
        'Premium'
    );

    Filter a Date Range

    SELECT
        OrderId,
        CustomerId,
        OrderDate,
        OrderAmount
    FROM Orders
    WHERE OrderDate >= '2026-01-01'
      AND OrderDate < '2026-02-01';
    Date-range practice: For timestamp columns, a half-open range with an inclusive start and exclusive end can prevent records near the final day's time boundary from being omitted.

    Sort and Limit Results

    Use ORDER BY to control result order. During exploration, a row limit can prevent an unnecessarily large result from being returned.

    SELECT
        OrderId,
        CustomerId,
        OrderDate,
        OrderAmount
    FROM Orders
    ORDER BY OrderDate DESC;

    A SQL Server example for returning a sample is:

    SELECT TOP (100)
        OrderId,
        CustomerId,
        OrderDate,
        OrderAmount
    FROM Orders
    ORDER BY OrderDate DESC;

    Some other database systems use LIMIT instead of TOP.

    Sampling Mistake Treating the newest 100 rows as a representative random sample.
    Correct Understanding An ordered limit returns rows according to the chosen order. It should not be treated as a representative sample unless the sampling strategy supports that conclusion.

    Handle NULL Values Correctly

    A database NULL normally represents a missing, unknown, or unavailable value. It is not the same as zero, an empty string, or the word “NULL.”

    Find Missing Values

    SELECT
        CustomerId,
        Region
    FROM Customers
    WHERE Region IS NULL;

    Find Non-Missing Values

    SELECT
        CustomerId,
        Region
    FROM Customers
    WHERE Region IS NOT NULL;

    Replace NULL for Reporting

    SELECT
        CustomerId,
        COALESCE(Region, 'Unknown') AS Region
    FROM Customers;
    Incorrect
    WHERE Region = NULL
    Correct
    WHERE Region IS NULL
    ML consideration: Do not replace every missing value automatically. First investigate why it is missing and whether missingness itself carries useful or sensitive information.

    Aggregate Functions

    Aggregate functions summarize multiple rows into one result. These functions are useful for data profiling and feature engineering.

    Function Purpose
    COUNT() Counts rows or non-NULL values
    SUM() Adds numerical values
    AVG() Calculates an average
    MIN() Returns the minimum value
    MAX() Returns the maximum value
    SELECT
        COUNT(*) AS OrderCount,
        SUM(OrderAmount) AS TotalOrderAmount,
        AVG(OrderAmount) AS AverageOrderAmount,
        MIN(OrderAmount) AS MinimumOrderAmount,
        MAX(OrderAmount) AS MaximumOrderAmount
    FROM Orders
    WHERE OrderStatus = 'Completed';
    NULL behavior: COUNT(*) counts rows, while COUNT(ColumnName) counts non-NULL values in the specified column. Many other aggregate functions ignore NULL values.

    Group Data with GROUP BY

    The GROUP BY clause calculates summaries for different groups. In Machine Learning feature engineering, it is commonly used to create one set of aggregate features for each observation, such as one row per customer.

    SELECT
        CustomerId,
        COUNT(*) AS OrderCount,
        SUM(OrderAmount) AS TotalOrderAmount,
        AVG(OrderAmount) AS AverageOrderAmount,
        MAX(OrderDate) AS MostRecentOrderDate
    FROM Orders
    WHERE OrderStatus = 'Completed'
    GROUP BY CustomerId;

    Filter aggregated groups using HAVING:

    SELECT
        CustomerId,
        COUNT(*) AS OrderCount,
        SUM(OrderAmount) AS TotalOrderAmount
    FROM Orders
    WHERE OrderStatus = 'Completed'
    GROUP BY CustomerId
    HAVING COUNT(*) >= 5;
    Clause Filters
    WHERE Individual rows before grouping
    HAVING Aggregated groups after grouping

    Combine Tables with JOIN

    Real datasets are frequently distributed across multiple tables. A join combines related rows using a defined relationship.

    INNER JOIN

    An inner join returns rows for which matching values exist in both joined sources.

    SELECT
        c.CustomerId,
        c.Region,
        o.OrderId,
        o.OrderDate,
        o.OrderAmount
    FROM Customers AS c
    INNER JOIN Orders AS o
        ON o.CustomerId = c.CustomerId;

    LEFT JOIN

    A left join retains all rows from the left table and returns NULL values when a matching right-side row does not exist.

    SELECT
        c.CustomerId,
        c.Region,
        o.OrderId,
        o.OrderDate,
        o.OrderAmount
    FROM Customers AS c
    LEFT JOIN Orders AS o
        ON o.CustomerId = c.CustomerId;
    Join Type General Behavior ML Use Case
    INNER JOIN Returns only matched rows Use when unmatched observations should not be retained
    LEFT JOIN Retains every row from the left source Use when every base observation must remain in the dataset
    FULL OUTER JOIN Retains matched and unmatched rows from both sides Useful for reconciliation and data-quality analysis
    Common Join Problem Joining a one-row-per-customer table directly to a one-row-per-order table and assuming the result still contains one row per customer.
    Better Approach Define the required row granularity and aggregate the order table before joining when the model needs one row per customer.

    Understand Dataset Granularity

    Granularity describes what one row represents. Before writing an ML query, define the observation unit clearly.

    Granularity Meaning of One Row Possible Model
    Customer Level One customer Customer churn prediction
    Order Level One order Late-order prediction
    Order-Line Level One product line within an order Line-level quantity or fulfillment prediction
    Customer-Month Level One customer during one month Monthly behavior prediction
    Device-Hour Level One device during one hour Equipment-condition monitoring
    Critical rule: Do not combine tables until you know what one final row must represent. Incorrect granularity can duplicate observations, inflate totals, bias training, and create misleading evaluation results.

    Detect Row Multiplication After a Join

    A join may produce more rows than expected when one key matches multiple records on either side. Compare row and identifier counts before and after a join.

    SELECT
        COUNT(*) AS TotalRows,
        COUNT(DISTINCT CustomerId) AS UniqueCustomers
    FROM Customers;
    SELECT
        COUNT(*) AS JoinedRows,
        COUNT(DISTINCT c.CustomerId) AS UniqueCustomers
    FROM Customers AS c
    LEFT JOIN Orders AS o
        ON o.CustomerId = c.CustomerId;
    Warning Sign The final dataset is expected to contain one row per customer, but the total row count is much larger than the distinct customer count.
    Recommended Aggregate transaction-level tables to customer level before joining, and validate the final key uniqueness explicitly.

    Aggregate Data Before Joining

    The following query creates one row per customer from the Orders table before joining the result to Customers.

    WITH OrderFeatures AS (
        SELECT
            CustomerId,
            COUNT(*) AS OrderCount,
            SUM(OrderAmount) AS TotalOrderAmount,
            AVG(OrderAmount) AS AverageOrderAmount,
            MAX(OrderDate) AS MostRecentOrderDate
        FROM Orders
        WHERE OrderStatus = 'Completed'
        GROUP BY CustomerId
    )
    SELECT
        c.CustomerId,
        c.Region,
        c.Segment,
        COALESCE(ofe.OrderCount, 0) AS OrderCount,
        COALESCE(ofe.TotalOrderAmount, 0) AS TotalOrderAmount,
        COALESCE(ofe.AverageOrderAmount, 0) AS AverageOrderAmount,
        ofe.MostRecentOrderDate
    FROM Customers AS c
    LEFT JOIN OrderFeatures AS ofe
        ON ofe.CustomerId = c.CustomerId;
    Why this works: The common table expression creates at most one row for each CustomerId. Joining that result to Customers preserves the intended customer-level granularity, assuming CustomerId is unique in Customers.

    Create Features with CASE

    A CASE expression creates a derived value based on conditions.

    SELECT
        CustomerId,
        OrderAmount,
        CASE
            WHEN OrderAmount >= 1000 THEN 'High'
            WHEN OrderAmount >= 500 THEN 'Medium'
            ELSE 'Low'
        END AS OrderValueBand
    FROM Orders;

    Create a Binary Indicator

    SELECT
        CustomerId,
        OrderDate,
        CASE
            WHEN OrderStatus = 'Cancelled' THEN 1
            ELSE 0
        END AS IsCancelled
    FROM Orders;
    Feature-Engineering Risk Creating categories with arbitrary thresholds and assuming they improve the model.
    Better Approach Document the reason for each threshold, validate its operational meaning, compare it with the original numerical feature, and test whether it improves model performance.

    Build Time-Based Features

    Dates and timestamps can be transformed into features representing recency, frequency, duration, weekday, month, season, or time since a previous event.

    The following example uses SQL Server-style date functions:

    DECLARE @PredictionDate date = '2026-09-01';
    
    SELECT
        CustomerId,
        DATEDIFF(
            day,
            CreatedDate,
            @PredictionDate
        ) AS AccountAgeDays
    FROM Customers
    WHERE CreatedDate <= @PredictionDate;

    Count Recent Orders

    DECLARE @PredictionDate date = '2026-09-01';
    
    SELECT
        CustomerId,
        COUNT(*) AS OrdersInPrevious90Days,
        SUM(OrderAmount) AS SpendingInPrevious90Days
    FROM Orders
    WHERE OrderDate < @PredictionDate
      AND OrderDate >= DATEADD(
            day,
            -90,
            @PredictionDate
          )
      AND OrderStatus = 'Completed'
    GROUP BY CustomerId;
    Time rule: Feature windows must end before the prediction point. Information from the future must not influence historical training examples.

    Prediction Windows and Observation Windows

    Time-aware Machine Learning datasets commonly separate the feature observation window from the target prediction window.

    Time-Aware Training Example
    Observation Window → Prediction Date → Target Window
    Time Component Purpose
    Observation Window Defines the historical information used to create features
    Prediction Date Represents the moment at which the model is assumed to make a prediction
    Target Window Defines the future period in which the outcome is measured
    Leakage Example Predicting customer churn on September 1 while using support requests or transactions created after September 1 as model inputs.
    Correct Approach Use only features available before the prediction date and measure the churn outcome during the separately defined future target window.

    Use Window Functions

    Window functions calculate values across related rows without collapsing those rows into one grouped result.

    Number Orders Within Each Customer

    SELECT
        CustomerId,
        OrderId,
        OrderDate,
        ROW_NUMBER() OVER (
            PARTITION BY CustomerId
            ORDER BY OrderDate DESC
        ) AS OrderRecencyRank
    FROM Orders;

    Find the Previous Order Date

    SELECT
        CustomerId,
        OrderId,
        OrderDate,
        LAG(OrderDate) OVER (
            PARTITION BY CustomerId
            ORDER BY OrderDate
        ) AS PreviousOrderDate
    FROM Orders;

    Create a Running Total

    SELECT
        CustomerId,
        OrderId,
        OrderDate,
        OrderAmount,
        SUM(OrderAmount) OVER (
            PARTITION BY CustomerId
            ORDER BY OrderDate, OrderId
            ROWS BETWEEN UNBOUNDED PRECEDING
            AND CURRENT ROW
        ) AS RunningOrderAmount
    FROM Orders;
    Window Function Typical Purpose
    ROW_NUMBER() Assign a sequence number to ordered rows
    RANK() Rank rows while allowing shared positions
    LAG() Access a previous row's value
    LEAD() Access a following row's value
    SUM() OVER Calculate cumulative or partition-based totals
    AVG() OVER Calculate moving or partition-based averages

    Common Table Expressions

    A common table expression, commonly called a CTE, gives a temporary name to a query result. It can make a complex feature query easier to read and maintain.

    WITH CompletedOrders AS (
        SELECT
            OrderId,
            CustomerId,
            OrderDate,
            OrderAmount
        FROM Orders
        WHERE OrderStatus = 'Completed'
    ),
    CustomerOrderFeatures AS (
        SELECT
            CustomerId,
            COUNT(*) AS OrderCount,
            SUM(OrderAmount) AS TotalOrderAmount,
            AVG(OrderAmount) AS AverageOrderAmount
        FROM CompletedOrders
        GROUP BY CustomerId
    )
    SELECT
        c.CustomerId,
        c.Region,
        c.Segment,
        COALESCE(f.OrderCount, 0) AS OrderCount,
        COALESCE(f.TotalOrderAmount, 0) AS TotalOrderAmount,
        COALESCE(f.AverageOrderAmount, 0) AS AverageOrderAmount
    FROM Customers AS c
    LEFT JOIN CustomerOrderFeatures AS f
        ON f.CustomerId = c.CustomerId;
    Maintainability benefit: Separate CTEs can make filtering, feature aggregation, label creation, and final joining easier to inspect. They do not automatically improve performance, so execution behavior must still be reviewed.

    Create a Supervised Learning Target

    In Supervised Learning, the target represents the outcome the model must learn to predict. The target definition should be created consistently and documented clearly.

    The following illustrative SQL Server query creates a target indicating whether a customer had a churn outcome within 30 days after the prediction date.

    DECLARE @PredictionDate date = '2026-09-01';
    
    SELECT
        c.CustomerId,
        CASE
            WHEN EXISTS (
                SELECT 1
                FROM CustomerOutcomes AS co
                WHERE co.CustomerId = c.CustomerId
                  AND co.Churned = 1
                  AND co.OutcomeDate >= @PredictionDate
                  AND co.OutcomeDate < DATEADD(
                        day,
                        30,
                        @PredictionDate
                      )
            )
            THEN 1
            ELSE 0
        END AS ChurnedWithin30Days
    FROM Customers AS c
    WHERE c.CreatedDate <= @PredictionDate;

    Target-Definition Checklist

    • Define the event represented by the target
    • Define the prediction date or timestamp
    • Define the target observation period
    • Define which customers or records are eligible
    • Confirm that the target is measured consistently
    • Review incomplete outcomes and delayed labels
    • Document exclusions and edge cases

    Prevent Machine Learning Data Leakage

    Data leakage occurs when information that would not legitimately be available at prediction time influences model training or evaluation. SQL feature queries are a common place where leakage can be introduced accidentally.

    Leakage Type SQL Example Prevention
    Future Leakage Aggregating transactions after the prediction date Apply an explicit upper date boundary
    Target Leakage Using an outcome-status column as an input feature Separate target tables and feature tables
    Snapshot Leakage Using the current customer record to represent a historical date Use historical snapshots or effective-date logic
    Duplicate Leakage Returning repeated copies of the same observation Validate keys and join cardinality
    Population Leakage Selecting records using knowledge of the future outcome Define eligibility using information available at prediction time
    Leaking Feature Query
    SELECT
        CustomerId,
        COUNT(*) AS AllKnownOrders
    FROM Orders
    GROUP BY CustomerId;

    If this query is used to create a historical training observation, it may include orders that occurred after that observation's prediction date.

    Time-Bounded Feature Query
    DECLARE @PredictionDate date = '2026-09-01';
    
    SELECT
        CustomerId,
        COUNT(*) AS HistoricalOrderCount
    FROM Orders
    WHERE OrderDate < @PredictionDate
    GROUP BY CustomerId;

    Data-Quality Checks with SQL

    SQL can detect missing values, duplicates, invalid ranges, unexpected categories, missing relationships, and inconsistent identifiers before the data is used for Machine Learning.

    Count Rows and Unique Keys

    SELECT
        COUNT(*) AS TotalRows,
        COUNT(DISTINCT CustomerId) AS UniqueCustomers
    FROM Customers;

    Find Duplicate Keys

    SELECT
        CustomerId,
        COUNT(*) AS DuplicateCount
    FROM Customers
    GROUP BY CustomerId
    HAVING COUNT(*) > 1;

    Count Missing Values

    SELECT
        SUM(
            CASE
                WHEN Region IS NULL THEN 1
                ELSE 0
            END
        ) AS MissingRegionCount,
        SUM(
            CASE
                WHEN Segment IS NULL THEN 1
                ELSE 0
            END
        ) AS MissingSegmentCount
    FROM Customers;

    Find Invalid Numerical Values

    SELECT
        OrderId,
        CustomerId,
        OrderAmount
    FROM Orders
    WHERE OrderAmount < 0;

    Inspect Category Values

    SELECT
        Region,
        COUNT(*) AS CustomerCount
    FROM Customers
    GROUP BY Region
    ORDER BY CustomerCount DESC;

    Find Missing Parent Records

    SELECT
        o.OrderId,
        o.CustomerId
    FROM Orders AS o
    LEFT JOIN Customers AS c
        ON c.CustomerId = o.CustomerId
    WHERE c.CustomerId IS NULL;
    Quality principle: Do not silently correct every problem in the final extraction query. Where possible, investigate and correct the data at its source, and document any transformations applied for modeling.

    Review Target Distribution

    Classification datasets should be examined for target imbalance. A high overall accuracy can be misleading when one class is much more common than another.

    SELECT
        Churned,
        COUNT(*) AS CustomerCount
    FROM CustomerOutcomes
    GROUP BY Churned
    ORDER BY Churned;

    Calculate Class Percentage

    SELECT
        Churned,
        COUNT(*) AS CustomerCount,
        100.0 * COUNT(*) /
            SUM(COUNT(*)) OVER () AS CustomerPercentage
    FROM CustomerOutcomes
    GROUP BY Churned
    ORDER BY Churned;
    Evaluation implication: When the target is imbalanced, examine precision, recall, F1 score, confusion matrices, and business error costs rather than relying only on accuracy.

    Write Efficient Queries

    ML extraction queries may process large tables. Efficiency reduces database load, execution time, data transfer, and operational cost.

    Query Practices

    • Select only required columns
    • Filter irrelevant rows as early as practical
    • Join using validated keys
    • Aggregate transaction data before joining when appropriate
    • Avoid unnecessary DISTINCT operations
    • Avoid applying functions to indexed filter columns when it prevents efficient access
    • Review the execution plan for expensive production queries
    • Use approved indexes and managed analytical views
    • Test large queries using safe development datasets or limited scopes
    • Coordinate resource-intensive extraction with database owners
    Less Efficient Date Filter
    WHERE YEAR(OrderDate) = 2026
      AND MONTH(OrderDate) = 8
    Range-Based Date Filter
    WHERE OrderDate >= '2026-08-01'
      AND OrderDate < '2026-09-01'

    Range-based filtering can make the time boundary clearer and may allow a database to use an appropriate index more effectively. Actual performance still depends on the database, indexes, data distribution, and query plan.

    Secure SQL Practices

    ML data access must follow the same security principles as any production data application. The model-development process does not justify unrestricted database access.

    Security Checklist

    • Use a dedicated identity with least-privilege access
    • Request read-only access when updates are not required
    • Access only approved tables, views, and columns
    • Do not place credentials inside source code or notebooks
    • Use an approved secret-management system
    • Encrypt connections where required
    • Protect extracted datasets according to their sensitivity
    • Avoid writing confidential data into logs
    • Use parameterized queries for external values
    • Retain only the data required for the approved purpose
    Unsafe String Construction
    customer_id = user_input
    
    query = (
        "SELECT CustomerId, Region "
        "FROM Customers "
        "WHERE CustomerId = '"
        + customer_id
        + "'"
    )
    Parameterized Query Concept
    query = """
    SELECT
        CustomerId,
        Region
    FROM Customers
    WHERE CustomerId = :customer_id
    """
    Security principle: Never create SQL commands by directly concatenating untrusted values into the SQL text.

    Access SQL Data from Python

    Python Prerequisites

    • Python installed locally or access to an approved notebook environment
    • pandas and SQLAlchemy installed
    • A database-specific Python driver installed
    • An authorized database account
    • Connection details stored outside source code

    Install Common Packages

    pip install pandas sqlalchemy

    Install the database driver required for your selected database. The exact package and connection configuration depend on the database platform and organizational environment.

    Parameterized pandas Query

    import os
    import pandas as pd
    
    from sqlalchemy import create_engine, text
    
    # Read the connection string from a protected environment variable.
    connection_string = os.environ[
        "ML_DATABASE_CONNECTION_STRING"
    ]
    
    engine = create_engine(
        connection_string
    )
    
    query = text("""
    SELECT
        CustomerId,
        Region,
        Segment,
        CreatedDate
    FROM Customers
    WHERE IsActive = :is_active
      AND CreatedDate < :prediction_date
    """)
    
    query_parameters = {
        "is_active": 1,
        "prediction_date": "2026-09-01"
    }
    
    with engine.connect() as connection:
        customers = pd.read_sql_query(
            query,
            connection,
            params=query_parameters
        )
    
    print(customers.head())
    print(customers.shape)

    Code Explanation

    Code Element Purpose
    os.environ Reads the connection string without hardcoding it into the script
    create_engine() Creates a reusable database connection interface
    text() Defines a parameterized SQL statement
    query_parameters Stores parameter values separately from SQL text
    engine.connect() Opens a managed database connection
    pd.read_sql_query() Loads the query result into a pandas DataFrame
    Do Not Commit
    connection_string = (
        "database-user-name:"
        "real-password@database-server"
    )
    Recommended Store credentials in an approved secret-management system or protected runtime configuration, and grant only the required database permissions.

    Load Large Results in Chunks

    A query result may be too large to load into memory at once. pandas can return the result in chunks so each portion can be processed separately.

    import pandas as pd
    from sqlalchemy import text
    
    query = text("""
    SELECT
        OrderId,
        CustomerId,
        OrderDate,
        OrderAmount
    FROM Orders
    WHERE OrderDate >= :start_date
      AND OrderDate < :end_date
    """)
    
    parameters = {
        "start_date": "2026-01-01",
        "end_date": "2026-02-01"
    }
    
    with engine.connect() as connection:
        chunks = pd.read_sql_query(
            query,
            connection,
            params=parameters,
            chunksize=10000
        )
    
        for chunk_number, chunk in enumerate(
            chunks,
            start=1
        ):
            print(
                f"Chunk {chunk_number}: "
                f"{len(chunk)} rows"
            )
    
            # Perform approved processing here.
    Important: Chunking reduces client-memory requirements, but it does not automatically make an inefficient database query efficient. Filtering, indexing, aggregation, and query execution should still be reviewed.

    End-to-End SQL Feature Dataset

    The following SQL Server-style example creates one row per eligible customer. Historical orders and support requests are restricted to dates before the prediction point. The target is measured during a separate future period.

    DECLARE @PredictionDate date = '2026-09-01';
    DECLARE @ObservationStartDate date = DATEADD(
        day,
        -90,
        @PredictionDate
    );
    DECLARE @TargetEndDate date = DATEADD(
        day,
        30,
        @PredictionDate
    );
    
    WITH EligibleCustomers AS (
        SELECT
            CustomerId,
            CreatedDate,
            Region,
            Segment
        FROM Customers
        WHERE CreatedDate <= @PredictionDate
    ),
    OrderFeatures AS (
        SELECT
            CustomerId,
            COUNT(*) AS OrdersInPrevious90Days,
            SUM(OrderAmount) AS SpendingInPrevious90Days,
            AVG(OrderAmount) AS AverageOrderAmount,
            MAX(OrderDate) AS MostRecentOrderDate
        FROM Orders
        WHERE OrderDate >= @ObservationStartDate
          AND OrderDate < @PredictionDate
          AND OrderStatus = 'Completed'
        GROUP BY CustomerId
    ),
    SupportFeatures AS (
        SELECT
            CustomerId,
            COUNT(*) AS RequestsInPrevious90Days,
            SUM(
                CASE
                    WHEN Priority = 'High' THEN 1
                    ELSE 0
                END
            ) AS HighPriorityRequests
        FROM SupportRequests
        WHERE CreatedDate >= @ObservationStartDate
          AND CreatedDate < @PredictionDate
        GROUP BY CustomerId
    ),
    TargetLabels AS (
        SELECT
            ec.CustomerId,
            CASE
                WHEN EXISTS (
                    SELECT 1
                    FROM CustomerOutcomes AS co
                    WHERE co.CustomerId = ec.CustomerId
                      AND co.Churned = 1
                      AND co.OutcomeDate >= @PredictionDate
                      AND co.OutcomeDate < @TargetEndDate
                )
                THEN 1
                ELSE 0
            END AS ChurnedWithin30Days
        FROM EligibleCustomers AS ec
    )
    SELECT
        ec.CustomerId,
        ec.Region,
        ec.Segment,
        DATEDIFF(
            day,
            ec.CreatedDate,
            @PredictionDate
        ) AS AccountAgeDays,
        COALESCE(
            ofe.OrdersInPrevious90Days,
            0
        ) AS OrdersInPrevious90Days,
        COALESCE(
            ofe.SpendingInPrevious90Days,
            0
        ) AS SpendingInPrevious90Days,
        COALESCE(
            ofe.AverageOrderAmount,
            0
        ) AS AverageOrderAmount,
        CASE
            WHEN ofe.MostRecentOrderDate IS NULL
                THEN NULL
            ELSE DATEDIFF(
                day,
                ofe.MostRecentOrderDate,
                @PredictionDate
            )
        END AS DaysSinceMostRecentOrder,
        COALESCE(
            sfe.RequestsInPrevious90Days,
            0
        ) AS RequestsInPrevious90Days,
        COALESCE(
            sfe.HighPriorityRequests,
            0
        ) AS HighPriorityRequests,
        tl.ChurnedWithin30Days
    FROM EligibleCustomers AS ec
    LEFT JOIN OrderFeatures AS ofe
        ON ofe.CustomerId = ec.CustomerId
    LEFT JOIN SupportFeatures AS sfe
        ON sfe.CustomerId = ec.CustomerId
    INNER JOIN TargetLabels AS tl
        ON tl.CustomerId = ec.CustomerId;

    Query Design Explanation

    Query Component Purpose
    @PredictionDate Defines the historical moment at which the prediction is assumed to occur
    @ObservationStartDate Defines the beginning of the feature observation window
    @TargetEndDate Defines the end of the future target window
    EligibleCustomers Defines the model population using information available by the prediction date
    OrderFeatures Creates customer-level order features from historical records only
    SupportFeatures Creates customer-level support features from historical records only
    TargetLabels Measures the future churn outcome separately from the features
    Final joins Produce one model-ready row for each eligible customer
    Important Limitation This is an educational schema and query. A production feature dataset requires validation against the actual database schema, business definitions, historical-data behavior, permissions, missing outcomes, and prediction requirements.

    Validate the Final ML Dataset

    Before exporting or training from the resulting query, validate its granularity, uniqueness, feature ranges, missing values, and target distribution.

    Validate One Row per Customer

    WITH FinalDataset AS (
        -- Place the final feature query here.
        SELECT
            CustomerId
        FROM Customers
    )
    SELECT
        CustomerId,
        COUNT(*) AS RowCount
    FROM FinalDataset
    GROUP BY CustomerId
    HAVING COUNT(*) > 1;

    Validate Feature Ranges

    WITH FinalDataset AS (
        -- Place the final feature query here.
        SELECT
            CustomerId,
            AccountAgeDays,
            OrdersInPrevious90Days,
            SpendingInPrevious90Days
        FROM CustomerFeatureView
    )
    SELECT
        MIN(AccountAgeDays) AS MinimumAccountAgeDays,
        MAX(AccountAgeDays) AS MaximumAccountAgeDays,
        MIN(OrdersInPrevious90Days) AS MinimumOrderCount,
        MAX(OrdersInPrevious90Days) AS MaximumOrderCount,
        MIN(SpendingInPrevious90Days) AS MinimumSpending,
        MAX(SpendingInPrevious90Days) AS MaximumSpending
    FROM FinalDataset;

    Validate the Target Distribution

    WITH FinalDataset AS (
        -- Place the final feature query here.
        SELECT
            ChurnedWithin30Days
        FROM CustomerFeatureView
    )
    SELECT
        ChurnedWithin30Days,
        COUNT(*) AS CustomerCount
    FROM FinalDataset
    GROUP BY ChurnedWithin30Days;

    Final Validation Checklist

    • One row represents the intended observation unit
    • The observation key is unique
    • No future information appears in the features
    • The target window is separated from the feature window
    • Missing values are understood and documented
    • Feature ranges are reasonable
    • Categorical values use consistent definitions
    • Join operations have not multiplied rows unexpectedly
    • The target distribution is understood
    • The result can be reproduced using the saved SQL and parameters

    Reproducibility and Version Control

    The SQL used to create a Machine Learning dataset should be treated as project source code. It should be documented, reviewed, tested, and version controlled.

    Information to Version

    • SQL extraction and feature queries
    • Prediction-date parameters
    • Feature and target definitions
    • Required table and column schema
    • Data-quality validation queries
    • Expected row granularity
    • Dataset-version or snapshot identifiers
    • Known exclusions and limitations
    Avoid Copying a query into an unnamed text file without recording its parameters, purpose, expected schema, or associated model experiment.
    Recommended Store the SQL in the source repository, review changes through the normal collaboration process, and connect the query version to the corresponding dataset and experiment record.

    Common SQL Mistakes in ML Projects

    Mistake Selecting every available column without reviewing its purpose.
    Better Approach Select only approved features that are relevant and available at prediction time.
    Mistake Joining one-to-many tables without checking row multiplication.
    Better Approach Define the final granularity and aggregate dependent tables before joining when required.
    Mistake Using the current database state to create historical training examples.
    Better Approach Use historical snapshots, effective-date logic, or event histories that reproduce what was known at the historical prediction point.
    Mistake Including future transactions in historical aggregate features.
    Better Approach Bound every historical feature by its observation window and prediction timestamp.
    Mistake Replacing all NULL values with zero.
    Better Approach Determine whether NULL means missing, unavailable, inapplicable, or no activity before choosing a replacement.
    Mistake Building SQL commands through string concatenation.
    Better Approach Use parameterized queries and approved database libraries.
    Mistake Treating a query that executes successfully as a validated dataset.
    Better Approach Validate row count, key uniqueness, join behavior, feature ranges, missingness, target distribution, time boundaries, and business definitions.

    SQL Data Access Best Practices

    Recommended Practices

    • Define what one output row represents before writing joins
    • Select only required columns
    • Use clear table aliases and descriptive feature names
    • Apply explicit time boundaries
    • Separate historical features from future labels
    • Aggregate one-to-many tables before joining when appropriate
    • Validate primary keys and join cardinality
    • Investigate NULL values before replacing them
    • Use parameterized queries for external values
    • Use least-privilege database access
    • Version SQL with the ML source code
    • Test extraction logic using automated data checks
    • Monitor schema and source-system changes
    • Document feature lineage and business meaning

    Hands-On Practice

    1

    Retrieve Active Customers

    Write a query that returns only the identifier, creation date, region, and segment of active customers.

    2

    Create Customer Order Features

    Calculate order count, total spending, average order value, and most recent order date for each customer.

    3

    Preserve Customers with No Orders

    Join the customer table to aggregated order features while retaining customers who have no matching order.

    4

    Create a Historical Feature Window

    Restrict order features to the 90 days before a defined prediction date.

    5

    Create a Future Target

    Create a binary target that indicates whether an event occurs during the 30 days after the prediction date.

    6

    Validate the Final Dataset

    Verify key uniqueness, row count, missing values, feature ranges, and target distribution.

    7

    Load the Result into pandas

    Use a parameterized query and an approved connection to load the final dataset into a pandas DataFrame.

    Knowledge Check

    1

    How does SQL support Machine Learning?

    Explain how SQL retrieves, combines, aggregates, validates, and prepares relational data for modeling.

    2

    What is dataset granularity?

    Explain what one row represents and why granularity must be defined before joining tables.

    3

    What is the difference between WHERE and HAVING?

    Explain which clause filters individual rows and which filters aggregated groups.

    4

    Why can a JOIN create duplicate observations?

    Explain what happens when one key matches several rows in another table.

    5

    Why must feature queries use a prediction timestamp?

    Explain how a timestamp separates historical information from future outcomes.

    6

    What is SQL injection?

    Explain why untrusted values should not be concatenated directly into SQL commands and how parameterized queries help.

    7

    Why is one successful query execution not enough?

    Explain why row counts, key uniqueness, feature ranges, time boundaries, and target definitions must also be validated.

    8

    Why should SQL extraction code be version controlled?

    Explain how source control supports reproducibility, review, traceability, and connection to model experiments.

    Lesson Summary

    Concept Main Point
    SQL Retrieves and transforms structured relational data
    SELECT Specifies the columns returned by a query
    WHERE Filters rows before grouping
    GROUP BY Creates summaries for defined groups
    HAVING Filters aggregated groups
    JOIN Combines related information from multiple tables
    CASE Creates conditional derived values
    Window Function Calculates ordered or partition-based values without collapsing rows
    CTE Organizes a query into named logical steps
    Granularity Defines what one result row represents
    Feature Window Defines the historical period used to create inputs
    Target Window Defines the future period used to measure an outcome
    Data Leakage Occurs when unavailable or future information influences modeling
    Parameterized Query Keeps external values separate from SQL command text
    Validation Confirms uniqueness, ranges, missingness, relationships, and target quality

    Key Takeaway

    SQL for Machine Learning is not only about retrieving records. A reliable ML query must preserve the intended row granularity, join tables correctly, use time-aware feature boundaries, separate historical features from future labels, validate data quality, prevent leakage, protect sensitive information, and remain reproducible through source control and documented parameters.

    Get Machine Learning updates by email

    Short, practical lessons and course updates from Rumman Ansari. No spam.