SQL Integration Patterns
Skill2 starsChanged 10 months agoMITView on GitHub
Patterns for connecting to databases from Python, R, and dbt — covering ORMs, connection management, parameterized queries, and data pipeline best practices.
We split this skill into the rules it gives an agent
1 rule. About 0 are common, 0 are distinctive, and 1 is very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈0
Distinctive · estimate
This author's own choices. Worth a look.
1
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 221
Batch operations: Use executemany(), COPY FROM, or bulk_insert_mappings() for inserting more than a few hundred rows.
≈ “Common” is an estimate: at least 1 in 5 of the 308,232 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
SQL Integration Patterns
Core Principles
- Always use parameterized queries — Never interpolate user input into SQL strings. Use bind parameters to prevent SQL injection and improve query plan caching.
- Manage connections explicitly — Open connections late, close them early. Use context managers or connection pools to prevent resource leaks.
- Use ORMs for application logic, raw SQL for analytics — ORMs like SQLAlchemy and Django ORM excel at CRUD operations with business rules. Raw SQL is better for complex analytical queries and bulk operations.
- Separate transformation logic from connection logic — Keep database credentials, connection setup, and query execution in distinct layers so each can be tested and maintained independently.
- Prefer declarative over imperative — Tools like dbt, SQLAlchemy's declarative base, and dbplyr let you express what you want rather than how to get it, reducing bugs and improving readability.
Python Database Connections
Python offers multiple libraries for database access, each suited to different use cases. The core pattern is always the same: establish a connection, execute parameterized queries, and clean up resources.
import psycopg # psycopg 3
import pandas as pd
# Context manager ensures connection closes even on error
conn_string = "host=warehouse.internal dbname=analytics user=etl_svc"
with psycopg.connect(conn_string) as conn:
# Parameterized query — %s placeholders, tuple of values
result = conn.execute(
"SELECT order_id, total FROM orders WHERE region = %s AND status = %s",
("us-west", "shipped"),
).fetchall()
# pandas integration for analytical workflows
df = pd.read_sql(
"SELECT date_trunc('month', order_date) AS month, sum(total) AS revenue "
"FROM orders WHERE region = %(region)s GROUP BY 1",
conn,
params={"region": "us-west"},
)See python-db-connectors for: psycopg3 async patterns, pyodbc for SQL Server, mysql-connector, bulk inserts with COPY, and error handling strategies.
SQLAlchemy ORM Patterns
SQLAlchemy provides both a high-level ORM and a lower-level Core expression language. Modern SQLAlchemy 2.0 uses a unified approach with select() statements and type-annotated models.
from sqlalchemy import create_engine, select, ForeignKey
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship, Session
class Base(DeclarativeBase):
pass
class Customer(Base):
__tablename__ = "customers"
id: Mapped[int] = mapped_column(primary_key=True)
email: Mapped[str] = mapped_column(unique=True)
orders: Mapped[list["Order"]] = relationship(back_populates="customer")
class Order(Base):
__tablename__ = "orders"
id: Mapped[int] = mapped_column(primary_key=True)
customer_id: Mapped[int] = mapped_column(ForeignKey("customers.id"))
total: Mapped[float]
customer: Mapped["Customer"] = relationship(back_populates="orders")
engine = create_engine("postgresql+psycopg://etl_svc@warehouse.internal/analytics")
with Session(engine) as session:
stmt = select(Customer).where(Customer.email.like("%@corp.com"))
customers = session.scalars(stmt).all()See sqlalchemy-patterns for: engine configuration, session lifecycle, eager loading, raw SQL execution, and migration patterns with Alembic.
dbt Fundamentals
dbt (data build tool) transforms raw data in the warehouse using SQL SELECT statements organized as models. Models are layered: staging cleans raw sources, intermediate models join and reshape, and mart models serve final business entities.
-- models/staging/stg_orders.sql
-- Staging models clean and rename source columns
with source as (
select * from {{ source('ecommerce', 'raw_orders') }}
),
renamed as (
select
id as order_id,
user_id as customer_id,
status,
amount as order_total,
created_at as ordered_at
from source
where id is not null -- filter known bad records
)
select * from renamedSee dbt-model-patterns for: intermediate and mart models, ref() and source() usage, incremental models, and snapshot patterns.
See dbt-project-structure for: directory layout, YAML configuration, seeds, macros, packages, and testing strategies.
dbt Advanced Patterns
Incremental models process only new or changed data, dramatically reducing build times on large tables. Snapshots capture slowly changing dimensions by tracking row-level changes over time.
-- models/intermediate/int_orders_enriched.sql
{{
config(
materialized='incremental',
unique_key='order_id',
incremental_strategy='merge'
)
}}
select
o.order_id,
o.customer_id,
c.segment as customer_segment,
o.order_total,
o.ordered_at
from {{ ref('stg_orders') }} o
inner join {{ ref('stg_customers') }} c on o.customer_id = c.customer_id
{% if is_incremental() %}
where o.ordered_at > (select max(ordered_at) from {{ this }})
{% endif %}See dbt-model-patterns for: snapshot configuration, custom materializations, and test patterns.
R Database Connections
R connects to databases through the DBI interface, with backend packages like RPostgres and odbc. The dbplyr package translates dplyr verbs into SQL, enabling analysis without writing raw queries.
library(DBI)
library(RPostgres)
library(dplyr)
library(dbplyr)
con <- dbConnect(
Postgres(),
host = "warehouse.internal",
dbname = "analytics",
user = "analyst_svc"
)
# dbplyr: write dplyr, execute SQL
monthly_revenue <- tbl(con, "orders") |>
filter(region == "us-west", status == "shipped") |>
mutate(month = date_trunc("month", order_date)) |>
group_by(month) |>
summarise(revenue = sum(total, na.rm = TRUE)) |>
collect() # pulls results into local tibble
# Parameterized raw SQL via DBI
region_summary <- dbGetQuery(con, "
SELECT region, count(*) AS order_count
FROM orders WHERE status = $1
", params = list("shipped"))
dbDisconnect(con)See r-db-connectors for: connection pooling with the pool package, odbc setup, writing back to the database, and Shiny app integration.
Connection Pooling and Management
Applications that handle concurrent requests must pool database connections rather than creating one per request. Pools maintain a set of reusable connections, reducing latency and preventing resource exhaustion.
from sqlalchemy import create_engine
# Production pool configuration
engine = create_engine(
"postgresql+psycopg://app_svc@db.internal/production",
pool_size=10, # persistent connections
max_overflow=5, # temporary connections above pool_size
pool_timeout=30, # seconds to wait for a connection
pool_recycle=1800, # recycle connections after 30 minutes
pool_pre_ping=True, # verify connection is alive before use
)Anti-Patterns
| Avoid | Use Instead | Why |
f"SELECT * FROM t WHERE id = {user_id}" | cursor.execute("SELECT ... WHERE id = %s", (user_id,)) | String interpolation enables SQL injection; parameterized queries are safe and allow plan caching |
| Opening a new connection per query in a web app | SQLAlchemy connection pool or pgBouncer | Connection setup is expensive (TCP + TLS + auth); pooling amortizes that cost across requests |
SELECT * in production application queries | Explicit column lists: SELECT id, name, email | Selecting all columns wastes bandwidth, breaks when schema changes, and prevents covering indexes |
| Running dbt models with no tests | Add not_null, unique, and accepted_values tests | Untested models silently propagate bad data downstream; tests catch regressions before they reach dashboards |
| Storing credentials in source code | Environment variables or secrets manager (Vault, AWS SSM) | Credentials in code get committed to version control and leak; externalized secrets can be rotated independently |
| Using ORM for bulk inserts (row-by-row) | COPY, executemany, or bulk_save_objects | Row-by-row ORM inserts generate one INSERT per row; bulk methods are 10-100x faster for large datasets |
dbplyr collect() before filtering | Filter with dplyr verbs before collect() | Collecting first pulls the entire table into R memory; filtering first pushes the WHERE clause to the database |
Performance
- Batch operations: Use
executemany(),COPY FROM, orbulk_insert_mappings()for inserting more than a few hundred rows. Row-by-row inserts create excessive round-trips. - Connection pooling: Size pools based on expected concurrency. A pool of 10 connections with 5 overflow handles most web applications; data pipelines may need larger pools during parallel task execution.
- Read replicas: Route analytical queries to read replicas to keep the primary database responsive for writes. SQLAlchemy supports multiple bind engines for this pattern.
- Lazy loading vs. eager loading: ORM default lazy loading causes N+1 query problems. Use
joinedload()orselectinload()when you know you will access related objects. - dbt incremental models: Partition incremental models on the timestamp column used in the
is_incremental()filter. This lets the warehouse prune partitions and scan only new data. - Query result caching: For repeated analytical queries, cache results in Redis or application memory with a TTL rather than hitting the database on every request.
- Connection recycling: Set
pool_recyclebelow your database's connection timeout to prevent errors from stale connections, especially behind load balancers or in cloud environments.
source: Practical patterns drawn from psycopg3, SQLAlchemy 2.0, dbt-core 1.7+, DBI/dbplyr, and Django 5.x documentation and production usage.
In this repository38 other files it gives its agents.
- Data Engineering Cloud InfrastructureSkill
- data-eng-data-qualitySkill
- data-eng-streaming-patternsSkill
- data-eng-testing-patternsSkill
- data-eng-warehouse-patternsSkill
- DevOps CI/CD PatternsSkill
- devops-docker-patternsSkill
- devops-monitoring-observabilitySkill
- devops-terraform-patternsSkill
- git-advanced-operationsSkill
- git-workflow-patternsSkill
- modern-python-patternsSkill
- python-api-developmentSkill
- python-async-patternsSkill
- python-data-pipelinesSkill
- python-data-wranglingSkill
- python-devops-automationSkill
- python-error-handlingSkill
- python-package-developmentSkill
- python-performanceSkill
- python-testingSkill
- customizing-vectors-rSkill
- designing-oop-rSkill
- developing-packages-rSkill
- metaprogramming-rlangSkill
- optimizing-rSkill
- writing-tidyverse-rSkill
- sql-advanced-featuresSkill
- sql-analytics-patternsSkill
- sql-data-modelingSkill
- sql-query-fundamentalsSkill
- sql-query-optimizationSkill
- sql-transformationsSkill
- API DesignSkill
- webdev-html-css-patternsSkill
- webdev-javascript-fundamentalsSkill
- webdev-react-patternsSkill
- webdev-typescript-patternsSkill
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
Reviews can't be read right now.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

