The DISTINCT clause removes duplicate rows from query results, returning only unique combinations of the selected columns.
Use DISTINCT when you need unique values:
- Remove duplicate rows
- Find unique combinations of columns
- Count unique values
DISTINCT ON is PostgreSQL-only and requires specific columns.
// Get unique categories
$categories = $db->find()
->from('products')
->select(['category'])
->distinct()
->orderBy('category')
->get();// Get unique combinations of category and region
$combinations = $db->find()
->from('sales')
->select(['category', 'region'])
->distinct()
->orderBy('category')
->get();// Get unique active product categories
$activeCategories = $db->find()
->from('products')
->select(['category'])
->where('active', 1)
->distinct()
->get();// Count unique customers
$uniqueCustomers = $db->find()
->from('orders')
->select(['unique_customers' => Db::count('DISTINCT customer_id')])
->getOne();
echo "Unique customers: " . $uniqueCustomers['unique_customers'];// Get unique products per category
$results = $db->find()
->from('products')
->select([
'category',
'unique_products' => Db::count('DISTINCT product_id'),
])
->groupBy('category')
->get();Note: DISTINCT ON is only supported in PostgreSQL. For other databases, use DISTINCT or window functions.
try {
// Get first product from each category (PostgreSQL only)
$results = $db->find()
->from('products')
->select(['category', 'name', 'price'])
->distinctOn('category')
->orderBy('category')
->orderBy('price', 'DESC')
->get();
} catch (\RuntimeException $e) {
// Will throw exception on MySQL and SQLite
echo "DISTINCT ON not supported: " . $e->getMessage();
}// Get first product per category and region
$results = $db->find()
->from('products')
->select(['category', 'region', 'name'])
->distinctOn(['category', 'region'])
->orderBy('category')
->orderBy('region')
->get();DISTINCT requires sorting/hashing to identify duplicates:
// ✅ Good - with index on category
$db->find()
->from('products')
->select(['category'])
->distinct()
->get();
// Consider adding index:
// CREATE INDEX idx_products_category ON products(category);// ❌ Unnecessary - id is unique
$db->find()
->from('users')
->select(['id', 'email'])
->distinct()
->get();
// ✅ Better - no DISTINCT needed
$db->find()
->from('users')
->select(['id', 'email'])
->get();// Get unique countries for dropdown
$countries = $db->find()
->from('users')
->select(['country'])
->distinct()
->orderBy('country')
->get();// Get unique tags
$tags = $db->find()
->from('post_tags')
->select(['tag'])
->distinct()
->orderBy('tag')
->get();// Get available filter options
$filters = [
'categories' => $db->find()
->from('products')
->where('active', 1)
->select(['category'])
->distinct()
->get(),
'brands' => $db->find()
->from('products')
->where('active', 1)
->select(['brand'])
->distinct()
->get(),
];// Analyze unique customers by month
$results = $db->find()
->from('orders')
->select([
'month' => Db::month('created_at'),
'unique_customers' => Db::count('DISTINCT customer_id'),
'total_orders' => Db::count('*'),
])
->groupBy(Db::month('created_at'))
->orderBy('month')
->get();Sometimes GROUP BY can replace DISTINCT:
// Using DISTINCT
$categories = $db->find()
->from('products')
->select(['category'])
->distinct()
->get();
// Equivalent with GROUP BY (can be faster with proper indexes)
$categories = $db->find()
->from('products')
->select(['category'])
->groupBy('category')
->get();Use GROUP BY when you also need aggregations:
// Get categories with product count
$categories = $db->find()
->from('products')
->select([
'category',
'product_count' => Db::count('*'),
])
->groupBy('category')
->get();DISTINCTapplies to all selected columns- Unique combination of all columns in SELECT
// Returns unique (category, brand) combinations
$db->find()
->from('products')
->select(['category', 'brand'])
->distinct()
->get();// DISTINCT works well with ORDER BY
$db->find()
->from('products')
->select(['category'])
->distinct()
->orderBy('category')
->get();- MySQL:
DISTINCTfully supported, noDISTINCT ON - PostgreSQL: Both
DISTINCTandDISTINCT ONsupported - SQLite:
DISTINCTfully supported, noDISTINCT ON
- Use indexes on columns in DISTINCT queries:
CREATE INDEX idx_products_category ON products(category);- Be specific with column selection:
// ✅ Good - only select needed columns
$db->find()->select(['category'])->distinct()->get();
// ❌ Bad - unnecessary columns slow down DISTINCT
$db->find()->select(['*'])->distinct()->get();- Consider GROUP BY for aggregations:
// ✅ Good - use GROUP BY when aggregating
$db->find()
->select(['category', 'count' => Db::count('*')])
->groupBy('category')
->get();- PostgreSQL DISTINCT ON requires proper ORDER BY:
// ✅ Good - DISTINCT ON columns match ORDER BY
$db->find()
->distinctOn('category')
->orderBy('category') // Required!
->orderBy('price', 'DESC')
->get();See examples/01-basic/05-ordering.php for complete working examples (Examples 11-12).
- Select Operations - Basic SELECT queries
- Aggregations - GROUP BY and aggregate functions
- Ordering - ORDER BY examples