Skip to the content.

JOIN Queries Documentation

This document explains how to use JOIN queries in the Hizzle Datastore library to relate collections together.

Overview

JOIN queries allow you to combine data from multiple collections based on a common field. This is particularly useful for:

Configuration

Defining JOINs in Collection Schema

When creating a collection, define the joins property to specify relationships with other collections:

use Hizzle\Store\Store;

$store = new Store(
    'my_store',
    array(
        'customers' => array(
            'object'        => 'Customer',
            'singular_name' => 'customer',
            'props'         => array(
                'id'    => array(
                    'type'        => 'BIGINT',
                    'length'      => 20,
                    'nullable'    => false,
                    'extra'       => 'AUTO_INCREMENT',
                    'description' => 'Customer ID',
                ),
                'name'  => array(
                    'type'        => 'VARCHAR',
                    'length'      => 255,
                    'nullable'    => false,
                    'description' => 'Customer name',
                ),
                'email' => array(
                    'type'        => 'VARCHAR',
                    'length'      => 255,
                    'nullable'    => false,
                    'description' => 'Customer email',
                ),
            ),
            'keys'          => array(
                'primary' => array( 'id' ),
                'email'   => array( 'email' ),
            ),
            // Define JOINs
            'joins'         => array(
                'payments' => array(
                    'collection'  => 'my_store_payments',  // Full collection name
                    'on'          => 'id',                 // Local field (customers.id)
                    'foreign_key' => 'customer_id',        // Foreign field (payments.customer_id)
                    'type'        => 'LEFT',               // Optional: INNER, LEFT, or RIGHT
                ),
            ),
            'labels'        => array(
                'name'          => __( 'Customers', 'textdomain' ),
                'singular_name' => __( 'Customer', 'textdomain' ),
            ),
        ),
        'payments' => array(
            'object'        => 'Payment',
            'singular_name' => 'payment',
            'props'         => array(
                'id'          => array(
                    'type'        => 'BIGINT',
                    'length'      => 20,
                    'nullable'    => false,
                    'extra'       => 'AUTO_INCREMENT',
                    'description' => 'Payment ID',
                ),
                'customer_id' => array(
                    'type'        => 'BIGINT',
                    'length'      => 20,
                    'nullable'    => false,
                    'description' => 'Customer ID',
                ),
                'amount'      => array(
                    'type'        => 'DECIMAL',
                    'length'      => '10,2',
                    'nullable'    => false,
                    'description' => 'Payment amount',
                ),
                'date'        => array(
                    'type'        => 'DATETIME',
                    'nullable'    => false,
                    'description' => 'Payment date',
                ),
            ),
            'keys'          => array(
                'primary'     => array( 'id' ),
                'customer_id' => array( 'customer_id' ),
            ),
            'labels'        => array(
                'name'          => __( 'Payments', 'textdomain' ),
                'singular_name' => __( 'Payment', 'textdomain' ),
            ),
        ),
    )
);

Usage

Basic JOIN Query

To use JOINs in a query, pass the join parameter with an array of join aliases:

$collection = Store::instance('my_store')->get('customers');

// Query with JOIN
$query = $collection->query(array(
    'join' => array('payments'),  // Include the payments join
));

Aggregate Queries with JOINs

JOINs are particularly powerful with aggregate queries:

// Total payments per customer
$query = $collection->query(array(
    'join'      => array('payments'),
    'aggregate' => array(
        'payments.amount' => array('SUM'),  // Use dot notation for joined fields
    ),
    'groupby'   => 'id',
    'extra_fields' => array('name', 'email'),  // Include customer fields
));

$results = $query->get_aggregate();
// Returns: array of objects with customer info and total payment amount

Multiple Aggregate Functions

$query = $collection->query(array(
    'join'      => array('payments'),
    'aggregate' => array(
        'payments.amount' => array('SUM', 'AVG', 'COUNT'),
        'id'              => array('COUNT'),  // Count customers
    ),
    'groupby'   => 'id',
    'extra_fields' => array('name'),
));

Filtering with Joined Data

You can filter results based on joined table data:

// Customers with payments over $100
$query = $collection->query(array(
    'join'      => array('payments'),
    'aggregate' => array(
        'payments.amount' => array('SUM'),
    ),
    'groupby'   => 'id',
));

// Then filter the aggregate results as needed

Using Multiple JOINs

If a collection has multiple JOIN configurations, you can specify which ones to use:

'joins' => array(
    'payments' => array(
        'collection'  => 'my_store_payments',
        'on'          => 'id',
        'foreign_key' => 'customer_id',
    ),
    'orders' => array(
        'collection'  => 'my_store_orders',
        'on'          => 'id',
        'foreign_key' => 'customer_id',
    ),
)

// Use both joins
$query = $collection->query(array(
    'join' => array('payments', 'orders'),
    // ... rest of query
));

// Or use just one
$query = $collection->query(array(
    'join' => 'payments',  // Single join as string
    // ... rest of query
));

REST API Usage

Aggregate Endpoint with JOINs

The aggregate endpoint supports the join parameter:

GET /wp-json/my_store/v1/customers/aggregate?join[]=payments&aggregate[payments.amount][]=SUM&groupby=id

Example with curl:

curl -X GET "https://example.com/wp-json/my_store/v1/customers/aggregate" \
  -H "Content-Type: application/json" \
  -G \
  --data-urlencode "join[]=payments" \
  --data-urlencode "aggregate[payments.amount][]=SUM" \
  --data-urlencode "groupby=id" \
  --data-urlencode "extra_fields[]=name" \
  --data-urlencode "extra_fields[]=email"

Field References

When working with joined tables, reference fields using either:

  1. Dot notation: payments.amount (standard SQL style)
  2. Double underscore: payments__amount (alternative for URL encoding)

Both formats are supported in:

JOIN Types

Three types of JOINs are supported:

Specify the type in your JOIN configuration:

'joins' => array(
    'payments' => array(
        'collection'  => 'my_store_payments',
        'on'          => 'id',
        'foreign_key' => 'customer_id',
        'type'        => 'LEFT',  // or 'INNER' or 'RIGHT'
    ),
)

Examples

Example 1: Customer Revenue Report

$customers = Store::instance('my_store')->get('customers');

$query = $customers->query(array(
    'join'         => array('payments'),
    'aggregate'    => array(
        'payments.amount' => array(
            array(
                'function' => 'SUM',
                'as'       => 'total_revenue',
            ),
            array(
                'function' => 'COUNT',
                'as'       => 'payment_count',
            ),
        ),
    ),
    'groupby'      => 'id',
    'extra_fields' => array('name', 'email'),
));

$results = $query->get_aggregate();

foreach ($results as $row) {
    echo "Customer: {$row->name}\n";
    echo "Total Revenue: \${$row->total_revenue}\n";
    echo "Number of Payments: {$row->payment_count}\n\n";
}

Example 2: Monthly Revenue by Customer

$query = $customers->query(array(
    'join'      => array('payments'),
    'aggregate' => array(
        'payments.amount' => array('SUM'),
    ),
    'groupby'   => array(
        'id'              => null,
        'payments.date'   => 'month',  // Group by month
    ),
    'extra_fields' => array('name'),
));

Example 3: Top Customers

// Find customers with highest total payments
$query = $customers->query(array(
    'join'      => array('payments'),
    'aggregate' => array(
        'payments.amount' => array('SUM'),
    ),
    'groupby'   => 'id',
    'extra_fields' => array('name', 'email'),
    'orderby'   => 'sum_amount',
    'order'     => 'DESC',
    'per_page'  => 10,
));

Best Practices

  1. Only join when needed: JOINs add complexity and can impact performance. Only include joins when you need to aggregate or filter data from related tables.

  2. Use specific join names: When a collection has multiple joins defined, explicitly specify which ones to use rather than joining all tables.

  3. Index foreign keys: Ensure foreign key fields are indexed in both tables for better performance.

  4. Use LEFT JOINs carefully: LEFT JOINs can return NULL values. Make sure your aggregate functions handle NULLs appropriately.

  5. Test performance: Always test query performance with realistic data volumes, especially with multiple JOINs.

Troubleshooting

JOIN not working

Invalid field errors

Performance issues