Skip to content

Aggregation & Functions ​

Master data aggregation in PGRestify with COUNT, SUM, AVG, MIN, MAX operations, GROUP BY patterns, window functions, and custom PostgreSQL functions.

Overview ​

PGRestify provides comprehensive support for data aggregation through PostgREST's aggregation capabilities. You can perform statistical calculations, group data, and use advanced window functions to analyze your data efficiently without retrieving raw records.

Basic Aggregation Functions ​

COUNT Operations ​

Simple Record Counting ​

typescript
// Get total count of records
const totalUsers = await client
  .from('users')
  .select('*', { count: 'exact' })
  .execute();

console.log(`Total users: ${totalUsers.count}`); // e.g., Total users: 1500

// Count with head: true returns only the count, no data
const userCount = await client
  .from('users')
  .select('*', { count: 'exact', head: true })
  .execute();

console.log(`User count: ${userCount.count}`);
// No data returned, only count

Filtered Counting ​

typescript
// Count active users only
const activeUserCount = await client
  .from('users')
  .select('*', { count: 'exact', head: true })
  .eq('is_active', true)
  .execute();

// Count posts published in the last month
const lastMonth = new Date();
lastMonth.setMonth(lastMonth.getMonth() - 1);

const recentPostCount = await client
  .from('posts')
  .select('*', { count: 'exact', head: true })
  .gte('published_at', lastMonth.toISOString())
  .eq('status', 'published')
  .execute();

Distinct Counting ​

typescript
// Count distinct values (requires custom RPC function)
const uniqueCategories = await client
  .rpc('count_distinct_categories')
  .execute();

// Or using a view with distinct
const distinctAuthors = await client
  .from('distinct_post_authors_view')
  .select('*', { count: 'exact', head: true })
  .execute();

SUM Operations ​

typescript
// Sum using RPC functions (PostgreSQL doesn't support SUM in PostgREST select directly)
const totalRevenue = await client
  .rpc('sum_order_totals')
  .execute();

// Sum with filters
const thisMonthRevenue = await client
  .rpc('sum_order_totals', {
    start_date: '2024-01-01',
    end_date: '2024-01-31'
  })
  .execute();

// Sum by category using GROUP BY (via RPC)
const revenueByCategory = await client
  .rpc('sum_revenue_by_category')
  .execute();

AVG (Average) Operations ​

typescript
// Calculate averages using RPC functions
const averageOrderValue = await client
  .rpc('calculate_average_order_value')
  .execute();

// Average with grouping
const averageScoreByCategory = await client
  .rpc('avg_score_by_category')
  .execute();

// Average rating for products
const averageRating = await client
  .rpc('get_average_product_rating', { product_id: 123 })
  .execute();

MIN/MAX Operations ​

typescript
// Find minimum and maximum values
const priceRange = await client
  .rpc('get_price_range')
  .execute();

// Latest and earliest dates
const dateRange = await client
  .rpc('get_date_range', { table_name: 'posts' })
  .execute();

// Min/Max with grouping
const categoryPriceRanges = await client
  .rpc('get_price_range_by_category')
  .execute();

GROUP BY Patterns ​

Basic Grouping with RPC Functions ​

typescript
// Group users by registration date
const usersByDate = await client
  .rpc('group_users_by_registration_date')
  .execute();

// Group orders by month
const ordersByMonth = await client
  .rpc('group_orders_by_month')
  .execute();

// Group products by category with counts
const productsByCategory = await client
  .rpc('count_products_by_category')
  .execute();

Advanced Grouping Patterns ​

typescript
// Complex grouping with multiple dimensions
const salesAnalysis = await client
  .rpc('sales_analysis_by_region_and_month')
  .execute();

// Grouping with calculated fields
const userActivityStats = await client
  .rpc('user_activity_statistics')
  .execute();

// Result example:
// [
//   { region: 'North', month: '2024-01', total_sales: 50000, order_count: 150 },
//   { region: 'South', month: '2024-01', total_sales: 35000, order_count: 98 }
// ]

Custom GROUP BY Functions ​

typescript
// Create a function to group data dynamically
const getGroupedData = async (
  groupBy: 'day' | 'week' | 'month',
  dateColumn = 'created_at'
) => {
  return client
    .rpc('group_data_by_period', {
      period: groupBy,
      date_col: dateColumn
    })
    .execute();
};

// Usage
const dailyStats = await getGroupedData('day');
const monthlyStats = await getGroupedData('month');

Window Functions ​

Basic Window Functions ​

typescript
// Ranking functions
const topPerformers = await client
  .rpc('get_employee_rankings')
  .execute();

// Result with rankings:
// [
//   { name: 'Alice', score: 95, rank: 1, dense_rank: 1 },
//   { name: 'Bob', score: 90, rank: 2, dense_rank: 2 },
//   { name: 'Charlie', score: 90, rank: 3, dense_rank: 2 }
// ]

// Moving averages
const movingAverages = await client
  .rpc('calculate_moving_averages', { window_size: 7 })
  .execute();

// Cumulative sums
const cumulativeSales = await client
  .rpc('calculate_cumulative_sales')
  .execute();

Advanced Window Operations ​

typescript
// Partition-based window functions
const departmentStats = await client
  .rpc('calculate_department_statistics')
  .execute();

// Result with partitioned data:
// [
//   { 
//     employee: 'Alice', 
//     department: 'Engineering', 
//     salary: 80000,
//     dept_avg: 75000,
//     dept_rank: 1
//   }
// ]

// Lead/Lag operations for time series
const timeSeriesAnalysis = await client
  .rpc('analyze_time_series_data')
  .execute();

Custom Window Function Examples ​

sql
-- Example PostgreSQL function for rankings
CREATE OR REPLACE FUNCTION get_product_rankings()
RETURNS TABLE (
  product_id INT,
  product_name TEXT,
  total_sales NUMERIC,
  sales_rank INT,
  category TEXT,
  category_rank INT
) AS $$
BEGIN
  RETURN QUERY
  SELECT 
    p.id,
    p.name,
    COALESCE(SUM(oi.quantity * oi.unit_price), 0) as total_sales,
    RANK() OVER (ORDER BY COALESCE(SUM(oi.quantity * oi.unit_price), 0) DESC) as sales_rank,
    p.category,
    RANK() OVER (
      PARTITION BY p.category 
      ORDER BY COALESCE(SUM(oi.quantity * oi.unit_price), 0) DESC
    ) as category_rank
  FROM products p
  LEFT JOIN order_items oi ON p.id = oi.product_id
  GROUP BY p.id, p.name, p.category;
END;
$$ LANGUAGE plpgsql;

Statistical Functions ​

Basic Statistics ​

typescript
// Get comprehensive statistics for numerical columns
const productStats = await client
  .rpc('get_product_price_statistics')
  .execute();

// Result example:
// {
//   count: 1000,
//   mean: 299.99,
//   median: 199.99,
//   mode: 99.99,
//   std_dev: 150.25,
//   variance: 22575.06,
//   min: 9.99,
//   max: 1999.99
// }

// Distribution analysis
const priceDistribution = await client
  .rpc('analyze_price_distribution', { bucket_count: 10 })
  .execute();

Correlation and Advanced Analytics ​

typescript
// Correlation between different metrics
const correlationAnalysis = await client
  .rpc('calculate_metric_correlations')
  .execute();

// Trend analysis
const trendData = await client
  .rpc('analyze_sales_trends', { 
    start_date: '2024-01-01',
    end_date: '2024-12-31' 
  })
  .execute();

// Percentile calculations
const percentileData = await client
  .rpc('calculate_performance_percentiles')
  .execute();

Custom PostgreSQL Functions ​

Creating Aggregation Functions ​

sql
-- Example: Monthly revenue calculation
CREATE OR REPLACE FUNCTION monthly_revenue(
  start_month DATE DEFAULT DATE_TRUNC('month', CURRENT_DATE)
)
RETURNS TABLE (
  month DATE,
  revenue NUMERIC,
  order_count BIGINT,
  avg_order_value NUMERIC
) AS $$
BEGIN
  RETURN QUERY
  SELECT 
    DATE_TRUNC('month', o.created_at) as month,
    SUM(o.total_amount) as revenue,
    COUNT(*) as order_count,
    AVG(o.total_amount) as avg_order_value
  FROM orders o
  WHERE o.created_at >= start_month
    AND o.status = 'completed'
  GROUP BY DATE_TRUNC('month', o.created_at)
  ORDER BY month;
END;
$$ LANGUAGE plpgsql;

Complex Business Logic Functions ​

sql
-- Customer lifetime value calculation
CREATE OR REPLACE FUNCTION calculate_customer_ltv()
RETURNS TABLE (
  customer_id INT,
  customer_name TEXT,
  total_orders BIGINT,
  total_spent NUMERIC,
  avg_order_value NUMERIC,
  ltv_score NUMERIC
) AS $$
BEGIN
  RETURN QUERY
  SELECT 
    c.id as customer_id,
    c.name as customer_name,
    COUNT(o.id) as total_orders,
    COALESCE(SUM(o.total_amount), 0) as total_spent,
    COALESCE(AVG(o.total_amount), 0) as avg_order_value,
    -- Simple LTV calculation
    COALESCE(SUM(o.total_amount), 0) * 
    (CASE 
      WHEN COUNT(o.id) > 10 THEN 1.5
      WHEN COUNT(o.id) > 5 THEN 1.2
      ELSE 1.0
    END) as ltv_score
  FROM customers c
  LEFT JOIN orders o ON c.id = o.customer_id
  GROUP BY c.id, c.name
  ORDER BY ltv_score DESC;
END;
$$ LANGUAGE plpgsql;

Calling Custom Functions ​

typescript
// Call monthly revenue function
const monthlyRevenue = await client
  .rpc('monthly_revenue', { 
    start_month: '2024-01-01' 
  })
  .execute();

// Call customer LTV calculation
const customerLTV = await client
  .rpc('calculate_customer_ltv')
  .execute();

// Call with multiple parameters
const salesAnalysis = await client
  .rpc('analyze_sales_performance', {
    start_date: '2024-01-01',
    end_date: '2024-12-31',
    include_refunds: false,
    group_by_region: true
  })
  .execute();

Dynamic Aggregation Patterns ​

Flexible Aggregation Functions ​

typescript
interface AggregationRequest {
  table: string;
  groupBy: string[];
  aggregates: Array<{
    field: string;
    operation: 'sum' | 'avg' | 'count' | 'min' | 'max';
    alias?: string;
  }>;
  filters?: Record<string, any>;
}

const performDynamicAggregation = async (request: AggregationRequest) => {
  // This would call a generic aggregation function
  return client
    .rpc('dynamic_aggregation', {
      table_name: request.table,
      group_columns: request.groupBy,
      aggregate_config: request.aggregates,
      filter_conditions: request.filters
    })
    .execute();
};

// Usage
const salesByRegion = await performDynamicAggregation({
  table: 'orders',
  groupBy: ['region', 'month'],
  aggregates: [
    { field: 'total_amount', operation: 'sum', alias: 'total_revenue' },
    { field: 'id', operation: 'count', alias: 'order_count' },
    { field: 'total_amount', operation: 'avg', alias: 'avg_order_value' }
  ],
  filters: { status: 'completed' }
});

Conditional Aggregation ​

typescript
// Aggregation with conditional logic
const conditionalStats = await client
  .rpc('calculate_conditional_statistics')
  .execute();

// Example function with conditional aggregation:
/*
SELECT 
  category,
  COUNT(*) as total_products,
  COUNT(CASE WHEN price > 100 THEN 1 END) as expensive_count,
  COUNT(CASE WHEN in_stock THEN 1 END) as in_stock_count,
  AVG(CASE WHEN price > 100 THEN price END) as avg_expensive_price
FROM products
GROUP BY category;
*/

Time-Series Aggregation ​

Time-Based Grouping ​

typescript
// Daily aggregations
const dailyMetrics = await client
  .rpc('get_daily_metrics', {
    metric_type: 'sales',
    start_date: '2024-01-01',
    end_date: '2024-01-31'
  })
  .execute();

// Hourly patterns
const hourlyPatterns = await client
  .rpc('analyze_hourly_patterns')
  .execute();

// Seasonal analysis
const seasonalTrends = await client
  .rpc('calculate_seasonal_trends', { years: 2 })
  .execute();

Rolling Aggregations ​

typescript
// Rolling averages
const rollingAverages = await client
  .rpc('calculate_rolling_metrics', {
    metric: 'daily_sales',
    window_days: 7
  })
  .execute();

// Year-over-year comparisons
const yoyGrowth = await client
  .rpc('calculate_yoy_growth')
  .execute();

Performance Optimization for Aggregations ​

Efficient Aggregation Patterns ​

typescript
// Use materialized views for expensive aggregations
const cachedStats = await client
  .from('daily_sales_summary_mv') // Materialized view
  .select('*')
  .gte('date', '2024-01-01')
  .execute();

// Incremental aggregation updates
const incrementalUpdate = await client
  .rpc('update_aggregation_tables', {
    last_update: '2024-01-15 00:00:00'
  })
  .execute();

Index Optimization ​

sql
-- Indexes for better aggregation performance
CREATE INDEX idx_orders_created_at_status ON orders(created_at, status);
CREATE INDEX idx_order_items_product_id ON order_items(product_id);
CREATE INDEX idx_users_created_at ON users(DATE(created_at));

Batch Aggregation Processing ​

typescript
// Process large aggregations in batches
const processBatchAggregation = async (batchSize = 1000) => {
  let offset = 0;
  const results = [];
  
  while (true) {
    const batch = await client
      .rpc('process_aggregation_batch', {
        batch_size: batchSize,
        offset_val: offset
      })
      .execute();
    
    if (batch.data.length === 0) break;
    
    results.push(...batch.data);
    offset += batchSize;
  }
  
  return results;
};

Common Aggregation Use Cases ​

E-commerce Analytics ​

typescript
// Sales dashboard metrics
const salesDashboard = await client
  .rpc('get_sales_dashboard_metrics')
  .execute();

// Product performance analysis
const productPerformance = await client
  .rpc('analyze_product_performance', {
    time_period: 'last_30_days'
  })
  .execute();

// Customer segmentation
const customerSegments = await client
  .rpc('segment_customers_by_behavior')
  .execute();

User Analytics ​

typescript
// User engagement metrics
const engagementMetrics = await client
  .rpc('calculate_user_engagement')
  .execute();

// Activity patterns
const activityPatterns = await client
  .rpc('analyze_user_activity_patterns')
  .execute();

// Retention analysis
const retentionAnalysis = await client
  .rpc('calculate_user_retention', {
    cohort_period: 'month'
  })
  .execute();

Financial Reporting ​

typescript
// Revenue reporting
const revenueReport = await client
  .rpc('generate_revenue_report', {
    start_date: '2024-01-01',
    end_date: '2024-12-31',
    group_by: 'month'
  })
  .execute();

// Profitability analysis
const profitabilityAnalysis = await client
  .rpc('calculate_profitability_metrics')
  .execute();

Real-time Aggregation Updates ​

Trigger-Based Updates ​

sql
-- Trigger to update aggregation tables
CREATE OR REPLACE FUNCTION update_daily_stats()
RETURNS TRIGGER AS $$
BEGIN
  -- Update daily statistics when orders change
  INSERT INTO daily_sales_stats (date, revenue, order_count)
  VALUES (DATE(NEW.created_at), NEW.total_amount, 1)
  ON CONFLICT (date) DO UPDATE SET
    revenue = daily_sales_stats.revenue + NEW.total_amount,
    order_count = daily_sales_stats.order_count + 1;
  
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER order_stats_trigger
  AFTER INSERT ON orders
  FOR EACH ROW
  EXECUTE FUNCTION update_daily_stats();

Scheduled Aggregation Updates ​

typescript
// Refresh materialized views periodically
const refreshAggregations = async () => {
  await client
    .rpc('refresh_all_aggregation_views')
    .execute();
};

// Schedule this function to run periodically
setInterval(refreshAggregations, 60 * 60 * 1000); // Every hour

Summary ​

PGRestify's aggregation capabilities provide:

  • Complete Function Set: COUNT, SUM, AVG, MIN, MAX operations
  • GROUP BY Support: Flexible grouping patterns via RPC functions
  • Window Functions: Advanced analytics with ranking and partitioning
  • Custom Functions: PostgreSQL function integration for complex logic
  • Statistical Analysis: Comprehensive statistical calculations
  • Time-Series Support: Time-based aggregations and trending
  • Performance Optimization: Efficient aggregation patterns and indexing
  • Real-time Updates: Trigger-based and scheduled aggregation updates

Master these aggregation techniques to build powerful analytics and reporting features that provide valuable insights from your PostgreSQL data through PGRestify.

Released under the MIT License.