Skip to content

Latest commit

 

History

History
409 lines (333 loc) · 13.9 KB

File metadata and controls

409 lines (333 loc) · 13.9 KB

SQL's Execution Flow

Slide 1: SQL Query Order Processing in Python

SQL's logical processing order differs from its written syntax. Understanding this sequence is crucial for query optimization and debugging. We'll implement a Python class that demonstrates the actual execution flow of SQL operations.

class SQLQueryProcessor:
    def __init__(self, data):
        self.data = data
        self.current_state = None
    
    def from_clause(self, table_name):
        # Step 1: FROM - Initialize data source
        self.current_state = self.data[table_name]
        return self
    
    def join_clause(self, other_table, condition):
        # Step 1: JOIN - Merge datasets
        joined_data = []
        for row in self.current_state:
            for other_row in other_table:
                if condition(row, other_row):
                    joined_data.append({**row, **other_row})
        self.current_state = joined_data
        return self

    def where_clause(self, condition):
        # Step 2: WHERE - Filter records
        self.current_state = [
            row for row in self.current_state 
            if condition(row)
        ]
        return self

Slide 2: Implementing GROUP BY and HAVING Operations

The GROUP BY operation aggregates data based on specified columns, while HAVING filters these groups. This implementation showcases how Python can mirror SQL's grouping mechanisms using dictionary-based aggregation.

def group_by_clause(self, key_func, agg_func):
    # Step 3: GROUP BY - Aggregate data
    groups = {}
    for row in self.current_state:
        key = key_func(row)
        if key not in groups:
            groups[key] = []
        groups[key].append(row)
    
    # Apply aggregation function to each group
    self.current_state = [
        {'group': k, 'agg_result': agg_func(v)}
        for k, v in groups.items()
    ]
    return self

def having_clause(self, condition):
    # Step 4: HAVING - Filter groups
    self.current_state = [
        group for group in self.current_state
        if condition(group)
    ]
    return self

Slide 3: SELECT and ORDER BY Implementation

The SELECT phase determines which columns appear in the final output, while ORDER BY sorts the results. This implementation demonstrates how to handle column selection and sorting operations in Python.

def select_clause(self, columns):
    # Step 5: SELECT - Project columns
    if columns == '*':
        return self
    
    self.current_state = [
        {col: row[col] for col in columns}
        for row in self.current_state
    ]
    return self

def order_by_clause(self, key_func, reverse=False):
    # Step 6: ORDER BY - Sort results
    self.current_state = sorted(
        self.current_state,
        key=key_func,
        reverse=reverse
    )
    return self

Slide 4: LIMIT and OFFSET Implementation

The LIMIT clause controls the number of rows returned, while OFFSET determines the starting point. This implementation demonstrates how Python list slicing can effectively replicate SQL's pagination functionality.

def limit_clause(self, limit, offset=0):
    # Step 7: LIMIT/OFFSET - Pagination
    self.current_state = self.current_state[offset:offset + limit]
    return self

def execute(self):
    # Return final result set
    return self.current_state

Slide 5: Real-World Example - Sales Data Analysis

Using our SQLQueryProcessor to analyze sales data demonstrates the practical application of SQL execution order. This example processes customer transactions to identify top-performing products by revenue.

# Sample sales data
sales_data = {
    'transactions': [
        {'product_id': 1, 'customer_id': 101, 'amount': 150.0, 'date': '2024-01-01'},
        {'product_id': 2, 'customer_id': 102, 'amount': 200.0, 'date': '2024-01-01'},
        {'product_id': 1, 'customer_id': 103, 'amount': 150.0, 'date': '2024-01-02'}
    ]
}

# Create query processor instance
processor = SQLQueryProcessor(sales_data)

# Process query with proper execution order
results = processor.from_clause('transactions')\
    .where_clause(lambda x: x['amount'] > 100)\
    .group_by_clause(
        key_func=lambda x: x['product_id'],
        agg_func=lambda x: sum(item['amount'] for item in x)
    )\
    .having_clause(lambda x: x['agg_result'] > 200)\
    .order_by_clause(lambda x: x['agg_result'], reverse=True)\
    .limit_clause(5)\
    .execute()

Slide 6: Implementing Window Functions

Window functions perform calculations across related rows. This implementation shows how to create moving averages and running totals while maintaining SQL's execution order.

def window_function(self, partition_by, window_func, window_size=None):
    result = []
    # Group data by partition key
    partitions = {}
    for row in self.current_state:
        key = partition_by(row)
        if key not in partitions:
            partitions[key] = []
        partitions[key].append(row)
    
    # Apply window function to each partition
    for key, partition in partitions.items():
        partition = sorted(partition)  # Sort within partition
        for i, row in enumerate(partition):
            if window_size:
                window = partition[max(0, i-window_size+1):i+1]
            else:
                window = partition[:i+1]
            row['window_result'] = window_func(window)
            result.append(row)
    
    self.current_state = result
    return self

Slide 7: Subquery Implementation

Subqueries are queries nested within a larger query. This implementation demonstrates how to handle subqueries while maintaining proper execution order and data isolation between query levels.

def subquery(self, subquery_processor, correlation_condition=None):
    result = []
    for outer_row in self.current_state:
        # Create a copy of subquery processor for each outer row
        subquery_result = subquery_processor.execute()
        
        if correlation_condition:
            # Apply correlation condition for correlated subqueries
            filtered_result = [
                inner_row for inner_row in subquery_result
                if correlation_condition(outer_row, inner_row)
            ]
            outer_row['subquery_result'] = filtered_result
        else:
            # For non-correlated subqueries
            outer_row['subquery_result'] = subquery_result
        
        result.append(outer_row)
    
    self.current_state = result
    return self

Slide 8: Advanced Aggregation Functions

Implementation of complex aggregation functions that go beyond basic operations like SUM and COUNT. This showcases how to handle statistical computations while maintaining SQL's execution order.

class AdvancedAggregations:
    @staticmethod
    def median(values):
        sorted_values = sorted(values)
        n = len(sorted_values)
        mid = n // 2
        if n % 2 == 0:
            return (sorted_values[mid-1] + sorted_values[mid]) / 2
        return sorted_values[mid]
    
    @staticmethod
    def percentile(values, p):
        sorted_values = sorted(values)
        k = (len(sorted_values) - 1) * (p/100.0)
        f = math.floor(k)
        c = math.ceil(k)
        if f == c:
            return sorted_values[int(k)]
        d0 = sorted_values[int(f)] * (c-k)
        d1 = sorted_values[int(c)] * (k-f)
        return d0 + d1

Slide 9: Real-World Example - Time Series Analysis

Implementing time-based window functions and aggregations for financial data analysis, demonstrating how SQL's execution order handles temporal operations.

# Sample time series data
financial_data = {
    'stock_prices': [
        {'date': '2024-01-01', 'symbol': 'AAPL', 'price': 180.5},
        {'date': '2024-01-02', 'symbol': 'AAPL', 'price': 182.3},
        {'date': '2024-01-03', 'symbol': 'AAPL', 'price': 181.7}
    ]
}

def calculate_moving_average(processor, window_size=3):
    return processor.from_clause('stock_prices')\
        .window_function(
            partition_by=lambda x: x['symbol'],
            window_func=lambda window: sum(r['price'] for r in window)/len(window),
            window_size=window_size
        )\
        .order_by_clause(lambda x: x['date'])\
        .execute()

Slide 10: CTE (Common Table Expression) Implementation

Common Table Expressions provide a way to create temporary named result sets. This implementation shows how to handle CTEs while maintaining proper execution order and scope.

class CTEManager:
    def __init__(self):
        self.cte_definitions = {}
        
    def with_clause(self, cte_name, cte_processor):
        # Execute CTE and store result
        self.cte_definitions[cte_name] = cte_processor.execute()
        return self
    
    def reference_cte(self, cte_name):
        if cte_name not in self.cte_definitions:
            raise ValueError(f"CTE {cte_name} not defined")
        return SQLQueryProcessor({'cte': self.cte_definitions[cte_name]})

# Example usage
cte_manager = CTEManager()
result = cte_manager\
    .with_clause('avg_prices', 
        SQLQueryProcessor(financial_data)
        .from_clause('stock_prices')
        .group_by_clause(
            lambda x: x['symbol'],
            lambda x: sum(r['price'] for r in x) / len(x)
        )
    )\
    .reference_cte('avg_prices')\
    .execute()

Slide 11: Query Optimization Implementation

This implementation demonstrates how to optimize query execution by rewriting predicates and analyzing execution paths while maintaining SQL's logical order.

class QueryOptimizer:
    def __init__(self, query_processor):
        self.query_processor = query_processor
        self.statistics = {}
    
    def analyze_predicates(self):
        # Collect statistics about data distribution
        for column in self.query_processor.current_state[0].keys():
            values = [row[column] for row in self.query_processor.current_state]
            self.statistics[column] = {
                'distinct_count': len(set(values)),
                'null_count': sum(1 for v in values if v is None),
                'min': min(v for v in values if v is not None),
                'max': max(v for v in values if v is not None)
            }
        return self
    
    def rewrite_query(self):
        # Implement query rewriting based on statistics
        if len(self.query_processor.current_state) > 1000:
            # Add indexing for large datasets
            self.add_index()
        return self.query_processor

Slide 12: Performance Monitoring Implementation

Implementation of performance monitoring capabilities to track query execution times and resource usage across different stages of SQL processing.

import time
import resource

class QueryProfiler:
    def __init__(self):
        self.metrics = []
        
    def start_operation(self, operation_name):
        start_time = time.time()
        start_memory = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
        return {'operation': operation_name, 
                'start_time': start_time,
                'start_memory': start_memory}
    
    def end_operation(self, start_metrics):
        end_time = time.time()
        end_memory = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
        self.metrics.append({
            'operation': start_metrics['operation'],
            'duration': end_time - start_metrics['start_time'],
            'memory_usage': end_memory - start_metrics['start_memory']
        })
        return self.metrics

Slide 13: Error Handling and Query Validation

Implementation of comprehensive error handling and query validation mechanisms to ensure data integrity and proper execution order throughout the query processing pipeline.

class QueryValidator:
    def __init__(self):
        self.errors = []
        self.warnings = []
    
    def validate_query(self, query_processor):
        # Validate data types
        for row in query_processor.current_state:
            self._validate_datatypes(row)
        
        # Validate operations
        self._validate_aggregations()
        self._validate_joins()
        return len(self.errors) == 0
    
    def _validate_datatypes(self, row):
        for column, value in row.items():
            try:
                if isinstance(value, (int, float)):
                    continue
                float(value)  # Try conversion
            except ValueError:
                self.errors.append(f"Invalid numeric value in column {column}")

Slide 14: Transaction Management Implementation

Implementing ACID properties in our SQL processor to ensure data consistency and isolation during concurrent operations while maintaining proper execution order.

class TransactionManager:
    def __init__(self):
        self.active_transactions = {}
        self.locks = {}
        self.isolation_level = 'SERIALIZABLE'
    
    def begin_transaction(self, transaction_id):
        self.active_transactions[transaction_id] = {
            'state': 'ACTIVE',
            'snapshot': None,
            'modifications': []
        }
    
    def commit(self, transaction_id):
        if transaction_id not in self.active_transactions:
            raise ValueError("Invalid transaction ID")
        
        transaction = self.active_transactions[transaction_id]
        for modification in transaction['modifications']:
            modification.apply()
        
        self._release_locks(transaction_id)
        del self.active_transactions[transaction_id]

Slide 15: Additional Resources