Stored Procedures vs Python Functions: Surprising Similarities



This content originally appeared on DEV Community and was authored by Nicholus Gathirwa

Introduction

While SQL stored procedures and Python functions operate in different environments, they share remarkable conceptual similarities. Understanding these parallels can help developers leverage their knowledge across both domains.

Core Similarities

1. Encapsulation and Reusability

Both stored procedures and Python functions encapsulate logic into reusable blocks of code.

SQL Stored Procedure:

CREATE PROCEDURE CalculateBonus(
    @EmployeeID INT,
    @PerformanceRating DECIMAL(3,2)
)
AS
BEGIN
    DECLARE @Bonus DECIMAL(10,2);
    SELECT @Bonus = salary * @PerformanceRating * 0.1
    FROM employees 
    WHERE employee_id = @EmployeeID;

    RETURN @Bonus;
END;

Python Function:

def calculate_bonus(employee_id, performance_rating):
    # Simulating database lookup
    salary = get_employee_salary(employee_id)
    bonus = salary * performance_rating * 0.1
    return bonus

2. Parameter Handling

Both support various parameter types and default values.

SQL Stored Procedure:

CREATE PROCEDURE GetEmployees(
    @DepartmentID INT = NULL,
    @MinSalary DECIMAL(10,2) = 0,
    @MaxResults INT = 100
)
AS
BEGIN
    SELECT TOP (@MaxResults) *
    FROM employees
    WHERE (@DepartmentID IS NULL OR department_id = @DepartmentID)
    AND salary >= @MinSalary;
END;

Python Function:

def get_employees(department_id=None, min_salary=0, max_results=100):
    query = "SELECT * FROM employees WHERE salary >= %s"
    params = [min_salary]

    if department_id:
        query += " AND department_id = %s"
        params.append(department_id)

    query += f" LIMIT {max_results}"

    return execute_query(query, params)

3. Control Flow and Logic

Both support conditional logic, loops, and error handling.

SQL Stored Procedure:

CREATE PROCEDURE ProcessPayroll(@PayPeriod DATE)
AS
BEGIN
    DECLARE @EmployeeID INT, @Salary DECIMAL(10,2);
    DECLARE employee_cursor CURSOR FOR
        SELECT employee_id, salary FROM employees WHERE active = 1;

    BEGIN TRY
        OPEN employee_cursor;
        FETCH NEXT FROM employee_cursor INTO @EmployeeID, @Salary;

        WHILE @@FETCH_STATUS = 0
        BEGIN
            IF @Salary > 0
                INSERT INTO payroll (employee_id, amount, pay_date)
                VALUES (@EmployeeID, @Salary, @PayPeriod);

            FETCH NEXT FROM employee_cursor INTO @EmployeeID, @Salary;
        END;

        CLOSE employee_cursor;
        DEALLOCATE employee_cursor;
    END TRY
    BEGIN CATCH
        -- Error handling
        ROLLBACK TRANSACTION;
        THROW;
    END CATCH;
END;

Python Function:

def process_payroll(pay_period):
    try:
        active_employees = get_active_employees()

        for employee in active_employees:
            if employee['salary'] > 0:
                insert_payroll_record(
                    employee_id=employee['id'],
                    amount=employee['salary'],
                    pay_date=pay_period
                )
    except Exception as e:
        # Error handling
        rollback_transaction()
        raise e

4. Return Values and Output

Both can return single values, multiple values, or complex data structures.

SQL Stored Procedure:

CREATE PROCEDURE GetDepartmentStats(
    @DepartmentID INT,
    @EmployeeCount INT OUTPUT,
    @AvgSalary DECIMAL(10,2) OUTPUT
)
AS
BEGIN
    SELECT 
        @EmployeeCount = COUNT(*),
        @AvgSalary = AVG(salary)
    FROM employees
    WHERE department_id = @DepartmentID;

    -- Also return result set
    SELECT name, salary 
    FROM employees 
    WHERE department_id = @DepartmentID;
END;

Python Function:

def get_department_stats(department_id):
    employees = get_employees_by_department(department_id)

    employee_count = len(employees)
    avg_salary = sum(emp['salary'] for emp in employees) / len(employees) if employees else 0

    # Return tuple (similar to OUTPUT parameters)
    return employee_count, avg_salary, employees

Key Parallels

Performance Optimization

  • Stored Procedures: Precompiled and cached by database engine
  • Python Functions: Can be optimized with caching decorators, compiled with tools like Cython

Modularity and Organization

  • Stored Procedures: Group related database operations
  • Python Functions: Organize code into logical, testable units

Security and Access Control

  • Stored Procedures: Control data access through procedure permissions
  • Python Functions: Implement validation and authorization logic

Testing and Debugging

  • Both: Can be tested independently with mock data
  • Both: Support debugging with breakpoints and logging

Practical Example: User Authentication

SQL Stored Procedure:

CREATE PROCEDURE AuthenticateUser(
    @Username NVARCHAR(50),
    @Password NVARCHAR(255),
    @IsValid BIT OUTPUT,
    @UserRole NVARCHAR(20) OUTPUT
)
AS
BEGIN
    DECLARE @StoredHash NVARCHAR(255);

    SELECT @StoredHash = password_hash, @UserRole = role
    FROM users
    WHERE username = @Username AND active = 1;

    IF @StoredHash IS NOT NULL AND @StoredHash = HASHBYTES('SHA2_256', @Password + 'salt')
        SET @IsValid = 1;
    ELSE
        SET @IsValid = 0;
END;

Python Function:

import hashlib

def authenticate_user(username, password):
    user = get_user_by_username(username)

    if not user or not user['active']:
        return False, None

    # Hash the provided password
    password_hash = hashlib.sha256((password + 'salt').encode()).hexdigest()

    if password_hash == user['password_hash']:
        return True, user['role']
    else:
        return False, None

Key Differences to Consider

While similar in concept, important differences exist:

Aspect Stored Procedures Python Functions
Execution Environment Database server Application server
Language Features SQL-specific Full programming language
Data Access Direct database access Through connectors/ORMs
Deployment Database-specific Cross-platform
Version Control Challenging Native support
Unit Testing Limited tools Rich ecosystem

Keep in mind:

The similarities between stored procedures and Python functions highlight fundamental programming concepts that transcend specific technologies. Both serve as building blocks for creating maintainable, reusable, and efficient code. Understanding these parallels helps developers:

  • Transfer knowledge between database and application development
  • Make informed decisions about where to implement business logic
  • Appreciate the universal principles of good software design

Whether you’re working with SQL stored procedures or Python functions, the core principles of modularity, reusability, and clear interfaces remain constant.


This content originally appeared on DEV Community and was authored by Nicholus Gathirwa