DEV Community

Cover image for SQL for Python Developers — Everything You Actually Need to Know
Prashik besekar
Prashik besekar

Posted on

SQL for Python Developers — Everything You Actually Need to Know

You know Python. Now learn the one skill that makes you twice as valuable.


Why Python Developers Need SQL

When I started learning backend development I thought Python was enough.

Build the API. Connect to MongoDB. Done.

Then I started looking at real job descriptions.

"Experience with SQL required."
"PostgreSQL knowledge preferred."
"Must be comfortable writing complex queries."

SQL was everywhere. And I had been avoiding it.

The truth is — most real world data lives in relational databases. Not MongoDB. Not JSON files. SQL databases.

MySQL. PostgreSQL. SQLite. They power banks, hospitals, e-commerce platforms, government systems, and almost every enterprise application you've ever used.

As a Python developer — SQL is not optional. It's the difference between being good and being genuinely valuable.

This is everything you actually need to know. No fluff. Just the real stuff. 🐍


Setting Up — Python + SQLite (Zero Installation)

SQLite comes built into Python. No installation needed. Perfect for learning.

import sqlite3

# Connect to database (creates it if doesn't exist)
conn = sqlite3.connect('mydb.db')
cursor = conn.cursor()

print("Database connected successfully!")

# Always close when done
conn.close()
Enter fullscreen mode Exit fullscreen mode

That's it. You have a working database in 5 lines of Python.


Part 1 — Creating Tables

Tables are where your data lives. Think of them like Excel spreadsheets with strict rules.

import sqlite3

conn = sqlite3.connect('company.db')
cursor = conn.cursor()

# Create employees table
cursor.execute('''
    CREATE TABLE IF NOT EXISTS employees (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT NOT NULL,
        email TEXT UNIQUE NOT NULL,
        department TEXT,
        salary REAL,
        hired_date TEXT,
        is_active INTEGER DEFAULT 1
    )
''')

# Create departments table
cursor.execute('''
    CREATE TABLE IF NOT EXISTS departments (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT NOT NULL,
        budget REAL,
        manager_id INTEGER
    )
''')

conn.commit()
print("Tables created successfully!")
conn.close()
Enter fullscreen mode Exit fullscreen mode

Data types you need to know:

SQL Type Python equivalent Use for
INTEGER int Numbers, IDs, counts
REAL float Decimals, prices
TEXT str Names, emails, descriptions
BLOB bytes Files, images
NULL None Missing values

Part 2 — Inserting Data

import sqlite3

conn = sqlite3.connect('company.db')
cursor = conn.cursor()

# Insert single record
cursor.execute('''
    INSERT INTO employees (name, email, department, salary, hired_date)
    VALUES (?, ?, ?, ?, ?)
''', ('Prashik Besekar', 'prashik@company.com', 'Engineering', 45000, '2026-01-15'))

# Insert multiple records at once
employees = [
    ('Rahul Sharma', 'rahul@company.com', 'Engineering', 50000, '2025-06-01'),
    ('Priya Patel', 'priya@company.com', 'Marketing', 42000, '2025-08-15'),
    ('Amit Kumar', 'amit@company.com', 'Engineering', 55000, '2024-12-01'),
    ('Sneha Joshi', 'sneha@company.com', 'HR', 38000, '2026-02-01'),
    ('Vikram Singh', 'vikram@company.com', 'Marketing', 48000, '2025-03-20'),
]

cursor.executemany('''
    INSERT INTO employees (name, email, department, salary, hired_date)
    VALUES (?, ?, ?, ?, ?)
''', employees)

conn.commit()
print(f"Inserted {cursor.rowcount} records")
conn.close()
Enter fullscreen mode Exit fullscreen mode

Important: Always use ? placeholders for values. NEVER use string formatting — it creates SQL injection vulnerabilities.

# WRONG - SQL injection risk 
name = "Prashik"
cursor.execute(f"SELECT * FROM employees WHERE name = '{name}'")

# RIGHT - Safe 
cursor.execute("SELECT * FROM employees WHERE name = ?", (name,))
Enter fullscreen mode Exit fullscreen mode

Part 3 — Selecting Data (The Most Important Part)

Basic SELECT

import sqlite3

conn = sqlite3.connect('company.db')
cursor = conn.cursor()

# Get all employees
cursor.execute("SELECT * FROM employees")
all_employees = cursor.fetchall()

for emp in all_employees:
    print(emp)

# Get specific columns only
cursor.execute("SELECT name, salary, department FROM employees")
results = cursor.fetchall()

for name, salary, dept in results:
    print(f"{name} | {dept} | ₹{salary:,.0f}")

conn.close()
Enter fullscreen mode Exit fullscreen mode

WHERE — Filtering Results

# Single condition
cursor.execute("SELECT * FROM employees WHERE department = ?", ('Engineering',))

# Multiple conditions
cursor.execute("""
    SELECT name, salary 
    FROM employees 
    WHERE department = ? AND salary > ?
""", ('Engineering', 45000))

# IN operator — multiple values
cursor.execute("""
    SELECT name, department 
    FROM employees 
    WHERE department IN (?, ?)
""", ('Engineering', 'Marketing'))

# LIKE — pattern matching
cursor.execute("""
    SELECT name, email 
    FROM employees 
    WHERE email LIKE ?
""", ('%@company.com',))

# NULL check
cursor.execute("SELECT * FROM employees WHERE department IS NOT NULL")

# Range with BETWEEN
cursor.execute("""
    SELECT name, salary 
    FROM employees 
    WHERE salary BETWEEN ? AND ?
""", (40000, 55000))
Enter fullscreen mode Exit fullscreen mode

ORDER BY — Sorting

# Sort by salary highest to lowest
cursor.execute("""
    SELECT name, salary, department 
    FROM employees 
    ORDER BY salary DESC
""")

# Sort by multiple columns
cursor.execute("""
    SELECT name, department, salary 
    FROM employees 
    ORDER BY department ASC, salary DESC
""")
Enter fullscreen mode Exit fullscreen mode

LIMIT — Control How Many Results

# Get top 3 highest paid
cursor.execute("""
    SELECT name, salary 
    FROM employees 
    ORDER BY salary DESC 
    LIMIT 3
""")

# Pagination — skip first 5, get next 5
cursor.execute("""
    SELECT name, salary 
    FROM employees 
    ORDER BY salary DESC 
    LIMIT 5 OFFSET 5
""")
Enter fullscreen mode Exit fullscreen mode

Part 4 — Aggregations (Making Data Tell Stories)

This is where SQL becomes truly powerful.

# COUNT — how many records
cursor.execute("SELECT COUNT(*) FROM employees")
total = cursor.fetchone()[0]
print(f"Total employees: {total}")

# SUM — add up values
cursor.execute("SELECT SUM(salary) FROM employees")
total_salary = cursor.fetchone()[0]
print(f"Total salary budget: ₹{total_salary:,.0f}")

# AVG — average value
cursor.execute("SELECT AVG(salary) FROM employees")
avg_salary = cursor.fetchone()[0]
print(f"Average salary: ₹{avg_salary:,.0f}")

# MIN and MAX
cursor.execute("SELECT MIN(salary), MAX(salary) FROM employees")
min_sal, max_sal = cursor.fetchone()
print(f"Salary range: ₹{min_sal:,.0f} — ₹{max_sal:,.0f}")
Enter fullscreen mode Exit fullscreen mode

GROUP BY — Aggregations by Category

# Average salary by department
cursor.execute("""
    SELECT 
        department,
        COUNT(*) as employee_count,
        AVG(salary) as avg_salary,
        SUM(salary) as total_salary
    FROM employees
    GROUP BY department
    ORDER BY avg_salary DESC
""")

results = cursor.fetchall()
print("\nDepartment Summary:")
print("-" * 60)
for dept, count, avg, total in results:
    print(f"{dept:<15} | {count} employees | Avg: ₹{avg:,.0f} | Total: ₹{total:,.0f}")
Enter fullscreen mode Exit fullscreen mode

HAVING — Filter After Grouping

# Departments with more than 2 employees
cursor.execute("""
    SELECT department, COUNT(*) as count
    FROM employees
    GROUP BY department
    HAVING COUNT(*) > 2
""")

# Departments where average salary > 45000
cursor.execute("""
    SELECT department, AVG(salary) as avg_sal
    FROM employees
    GROUP BY department
    HAVING AVG(salary) > 45000
    ORDER BY avg_sal DESC
""")
Enter fullscreen mode Exit fullscreen mode

Part 5 — JOINs (Combining Tables)

JOINs are the most powerful SQL feature. They combine data from multiple tables.

# First let's add some orders data
cursor.execute('''
    CREATE TABLE IF NOT EXISTS orders (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        employee_id INTEGER,
        product TEXT,
        amount REAL,
        order_date TEXT,
        FOREIGN KEY (employee_id) REFERENCES employees(id)
    )
''')

orders = [
    (1, 'Laptop', 85000, '2026-03-01'),
    (2, 'Monitor', 25000, '2026-03-05'),
    (1, 'Keyboard', 3000, '2026-03-10'),
    (3, 'Chair', 15000, '2026-03-15'),
]

cursor.executemany(
    "INSERT INTO orders (employee_id, product, amount, order_date) VALUES (?, ?, ?, ?)",
    orders
)
conn.commit()
Enter fullscreen mode Exit fullscreen mode

INNER JOIN — Only Matching Records

# Get employees with their orders
cursor.execute("""
    SELECT 
        e.name,
        e.department,
        o.product,
        o.amount,
        o.order_date
    FROM employees e
    INNER JOIN orders o ON e.id = o.employee_id
    ORDER BY e.name
""")

results = cursor.fetchall()
for row in results:
    print(row)
Enter fullscreen mode Exit fullscreen mode

LEFT JOIN — All Records From Left Table

# Get ALL employees even if they have no orders
cursor.execute("""
    SELECT 
        e.name,
        e.department,
        COALESCE(o.product, 'No orders') as product,
        COALESCE(o.amount, 0) as amount
    FROM employees e
    LEFT JOIN orders o ON e.id = o.employee_id
    ORDER BY e.name
""")
Enter fullscreen mode Exit fullscreen mode

Part 6 — Updating and Deleting

# Update single record
cursor.execute("""
    UPDATE employees 
    SET salary = salary * 1.10
    WHERE name = ?
""", ('Prashik Besekar',))

print(f"Updated {cursor.rowcount} records")

# Update multiple records
cursor.execute("""
    UPDATE employees 
    SET salary = salary * 1.05
    WHERE department = ? AND salary < ?
""", ('Engineering', 50000))

# Delete specific records
cursor.execute("""
    DELETE FROM employees 
    WHERE is_active = 0
""")

# Safe delete with confirmation
def safe_delete(cursor, employee_id):
    cursor.execute("SELECT name FROM employees WHERE id = ?", (employee_id,))
    employee = cursor.fetchone()

    if not employee:
        print(f"Employee {employee_id} not found")
        return

    confirm = input(f"Delete {employee[0]}? (yes/no): ")
    if confirm.lower() == 'yes':
        cursor.execute("DELETE FROM employees WHERE id = ?", (employee_id,))
        print(f"Deleted {employee[0]}")
    else:
        print("Cancelled")

conn.commit()
Enter fullscreen mode Exit fullscreen mode

Part 7 — Python + SQL Best Practices

Use Context Managers

# Clean way to handle connections
with sqlite3.connect('company.db') as conn:
    cursor = conn.cursor()
    cursor.execute("SELECT * FROM employees")
    results = cursor.fetchall()
# Connection automatically closed here 
Enter fullscreen mode Exit fullscreen mode

Use Pandas for Analysis

import pandas as pd
import sqlite3

conn = sqlite3.connect('company.db')

# Load SQL query directly into DataFrame
df = pd.read_sql_query("""
    SELECT 
        department,
        COUNT(*) as count,
        AVG(salary) as avg_salary
    FROM employees
    GROUP BY department
""", conn)

print(df)
print(df.describe())

# Save DataFrame back to SQL
new_data = pd.DataFrame({
    'name': ['New Employee'],
    'email': ['new@company.com'],
    'department': ['Engineering'],
    'salary': [45000],
    'hired_date': ['2026-05-01']
})

new_data.to_sql('employees', conn, if_exists='append', index=False)
conn.close()
Enter fullscreen mode Exit fullscreen mode

Build a Reusable Database Class

import sqlite3
import pandas as pd
from contextlib import contextmanager

class Database:
    def __init__(self, db_path):
        self.db_path = db_path

    @contextmanager
    def get_connection(self):
        conn = sqlite3.connect(self.db_path)
        try:
            yield conn
            conn.commit()
        except Exception as e:
            conn.rollback()
            raise e
        finally:
            conn.close()

    def execute(self, query, params=None):
        with self.get_connection() as conn:
            cursor = conn.cursor()
            cursor.execute(query, params or ())
            return cursor.fetchall()

    def query_df(self, query, params=None):
        with self.get_connection() as conn:
            return pd.read_sql_query(query, conn, params=params)

# Use it
db = Database('company.db')

# Simple query
results = db.execute("SELECT * FROM employees WHERE department = ?", ('Engineering',))

# Query as DataFrame
df = db.query_df("SELECT department, AVG(salary) as avg FROM employees GROUP BY department")
print(df)
Enter fullscreen mode Exit fullscreen mode

Part 8 — PostgreSQL for Production

SQLite is great for learning. Production apps use PostgreSQL.

pip install psycopg2-binary
Enter fullscreen mode Exit fullscreen mode
import psycopg2
import os

# Connect to PostgreSQL
conn = psycopg2.connect(
    host=os.environ.get('DB_HOST', 'localhost'),
    database=os.environ.get('DB_NAME', 'myapp'),
    user=os.environ.get('DB_USER', 'postgres'),
    password=os.environ.get('DB_PASSWORD'),
    port=5432
)

cursor = conn.cursor()

# Same SQL syntax as SQLite!
cursor.execute("""
    SELECT name, salary 
    FROM employees 
    WHERE department = %s
    ORDER BY salary DESC
""", ('Engineering',))  # Note: %s instead of ? for PostgreSQL

results = cursor.fetchall()
for row in results:
    print(row)

conn.close()
Enter fullscreen mode Exit fullscreen mode

The SQL syntax is almost identical. The main difference — PostgreSQL uses %s instead of ? for parameters.


The SQL Cheat Sheet Every Python Developer Needs

-- SELECT
SELECT column1, column2 FROM table WHERE condition ORDER BY column LIMIT 10;

-- INSERT
INSERT INTO table (col1, col2) VALUES (val1, val2);

-- UPDATE
UPDATE table SET column = value WHERE condition;

-- DELETE
DELETE FROM table WHERE condition;

-- AGGREGATIONS
SELECT COUNT(*), SUM(col), AVG(col), MIN(col), MAX(col) FROM table;

-- GROUP BY
SELECT column, COUNT(*) FROM table GROUP BY column HAVING COUNT(*) > 1;

-- JOINS
SELECT * FROM table1 t1
INNER JOIN table2 t2 ON t1.id = t2.foreign_id;

SELECT * FROM table1 t1
LEFT JOIN table2 t2 ON t1.id = t2.foreign_id;

-- SUBQUERY
SELECT * FROM employees WHERE salary > (SELECT AVG(salary) FROM employees);
Enter fullscreen mode Exit fullscreen mode

Final Thoughts

SQL is not complicated. It reads almost like English.

SELECT the data I want FROM this table WHERE these conditions are true.

Once it clicks — and it clicks fast — you will use it every single day as a Python developer.

Every data engineering role. Every backend role. Every analytics role. SQL is there.

Learn it properly once. Use it forever.

Start with SQLite today. Build the employee database from this article. Run every query. Break things. Fix them.

By the end of the day you'll have a skill that makes you twice as valuable. 💪


Follow LearnWithPrashik for more practical Python and data engineering content.

Connect with me:
LinkedIn: linkedin.com/in/prashik-besekar
GitHub: github.com/prashikBesekar

Top comments (0)