Skip to content
Back to skills

Python Bio Sql For Bioinformatics

ASecurity

Ensembl, UCSC Genome Browser, NCBI, and dbSNP are all backed by relational databases. Even locally, SQLite is a practical way to store and query gene annotations, variant tables, and expression result

  • 4 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added May 27, 2026
data-aipythonsqlexpressdatabase

Works with

  • cursor
  • cli

Security analysis

A100/100

Scanned May 27, 2026

npx -y skills add Pavel-Kravchenko/Bioinformatics --skill python-bio-sql-for-bioinformatics --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Python Bio Sql For Bioinformatics?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Python Bio Sql For Bioinformatics
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/pavel-kravchenko-python-bio-sql-for-bioinformatics/badge)](https://www.skillsdirectory.com/skills/pavel-kravchenko-python-bio-sql-for-bioinformatics)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: python-bio-sql-for-bioinformatics
description: Ensembl, UCSC Genome Browser, NCBI, and dbSNP are all backed by relational databases. Even locally, SQLite is a practical way to store and query gene annotations, variant tables, and expression result
tool_type: python
primary_tool: Pandas
---

# SQL for Bioinformatics

## Key Concepts

**JOIN type determines which rows survive:** `INNER JOIN` — only matched rows; `LEFT JOIN` — all left rows, NULL for unmatched right side.

**`HAVING` vs `WHERE`:** `WHERE` filters before grouping; `HAVING` filters after. Use `HAVING COUNT(*) > 3` to filter aggregate groups.

**Subqueries vs JOINs:** JOIN is more readable and usually faster. Subqueries with `IN (SELECT ...)` are clearer for small inner sets.

**Always use parameterized queries:** Never build SQL with f-strings containing user input (SQL injection). Use `cursor.execute("WHERE gene = ?", (gene_name,))`.

```python
import sqlite3
import pandas as pd
import numpy as np

conn = sqlite3.connect(":memory:")
cursor = conn.cursor()

cursor.executescript("""
CREATE TABLE genes (
    gene_id   INTEGER PRIMARY KEY,
    symbol    TEXT NOT NULL,
    chromosome TEXT,
    start_pos  INTEGER,
    end_pos    INTEGER,
    biotype   TEXT
);

CREATE TABLE variants (
    variant_id  INTEGER PRIMARY KEY,
    gene_id     INTEGER REFERENCES genes(gene_id),
    position    INTEGER,
    ref_allele  TEXT,
    alt_allele  TEXT,
    clinical_significance TEXT
);

CREATE TABLE expression (
    expr_id   INTEGER PRIMARY KEY,
    gene_id   INTEGER REFERENCES genes(gene_id),
    tissue    TEXT,
    condition TEXT,
    tpm       REAL
);

CREATE TABLE pathways (
    pathway_id   INTEGER PRIMARY KEY,
    pathway_name TEXT
);

CREATE TABLE gene_pathway (
    gene_id    INTEGER REFERENCES genes(gene_id),
    pathway_id INTEGER REFERENCES pathways(pathway_id)
);
""")

genes_data = [
    (1, 'BRCA1', 'chr17', 43044295, 43125483, 'protein_coding'),
    (2, 'TP53',  'chr17',  7661779,  7687538, 'protein_coding'),
    (3, 'EGFR',  'chr7',  55019017, 55207337, 'protein_coding'),
    (4, 'MYC',   'chr8', 127735434,127742951, 'protein_coding'),
    (5, 'KRAS',  'chr12', 25204789, 25250936, 'protein_coding'),
    (6, 'PTEN',  'chr10', 89692905, 89728532, 'protein_coding'),
    (7, 'RB1',   'chr13', 47775885, 47954065, 'protein_coding'),
]
cursor.executemany("INSERT INTO genes VALUES (?,?,?,?,?,?)", genes_data)

variants_data = [
    (1, 1, 43045629, 'A', 'T', 'pathogenic'),
    (2, 2, 7674220,  'C', 'T', 'pathogenic'),
    (3, 3, 55181320, 'G', 'A', 'likely_pathogenic'),
    (4, 5, 25245347, 'G', 'T', 'pathogenic'),
    (5, 6, 89711933, 'T', 'A', 'benign'),
]
cursor.executemany("INSERT INTO variants VALUES (?,?,?,?,?,?)", variants_data)

np.random.seed(42)
expr_rows = []
eid = 1
for gid in range(1, 8):
    for tissue in ['liver', 'kidney', 'brain']:
        for cond in ['normal', 'tumor']:
            base = np.random.uniform(10, 200)
            tpm = round(base * (1.8 if cond == 'tumor' else 1.0) + np.random.normal(0, 5), 2)
            expr_rows.append((eid, gid, tissue, cond, max(tpm, 0.1)))
            eid += 1
cursor.executemany("INSERT INTO expression VALUES (?,?,?,?,?)", expr_rows)

pathways_data = [(1,'DNA repair'), (2,'Cell cycle'), (3,'Apoptosis')]
cursor.executemany("INSERT INTO pathways VALUES (?,?)", pathways_data)

gp_data = [(1,1),(2,1),(2,2),(4,2),(3,3),(2,3)]
cursor.executemany("INSERT INTO gene_pathway VALUES (?,?)", gp_data)

conn.commit()
```

## SELECT, WHERE, ORDER BY

```python
# All genes on chromosome 17
df = pd.read_sql_query(
    "SELECT symbol, chromosome, start_pos, end_pos FROM genes WHERE chromosome = 'chr17'",
    conn
)

# Genes longer than 100 kb, ordered by length
df = pd.read_sql_query("""
    SELECT symbol, chromosome, (end_pos - start_pos) AS length
    FROM genes
    WHERE (end_pos - start_pos) > 100000
    ORDER BY length DESC
""", conn)
```

## Aggregate Functions and GROUP BY

```python
# Average expression per tissue and condition
df = pd.read_sql_query("""
    SELECT tissue, condition, ROUND(AVG(tpm), 2) AS avg_tpm, COUNT(*) AS n_samples
    FROM expression
    GROUP BY tissue, condition
    ORDER BY tissue, condition
""", conn)

# Genes with highest average tumor expression (HAVING filters after grouping)
df = pd.read_sql_query("""
    SELECT g.symbol, ROUND(AVG(e.tpm), 2) AS avg_tumor_tpm
    FROM genes g
    JOIN expression e ON g.gene_id = e.gene_id
    WHERE e.condition = 'tumor'
    GROUP BY g.symbol
    HAVING AVG(e.tpm) > 50
    ORDER BY avg_tumor_tpm DESC
""", conn)
```

## JOIN Operations

- **INNER JOIN** — only rows with a match in both tables.
- **LEFT JOIN** — all rows from left table; NULLs where no match on right.

```python
# INNER JOIN: pathogenic variants with gene names
df = pd.read_sql_query("""
    SELECT g.symbol, g.chromosome, v.position, v.ref_allele, v.alt_allele, v.clinical_significance
    FROM variants v
    INNER JOIN genes g ON v.gene_id = g.gene_id
    WHERE v.clinical_significance = 'pathogenic'
    ORDER BY g.symbol
""", conn)

# LEFT JOIN: all genes with variant count (including genes with 0 variants)
df = pd.read_sql_query("""
    SELECT g.symbol, COUNT(v.variant_id) AS n_variants
    FROM genes g
    LEFT JOIN variants v ON g.gene_id = v.gene_id
    GROUP BY g.symbol
    ORDER BY n_variants DESC
""", conn)
```

## Subqueries

```python
# Genes that are both highly expressed in tumors AND carry pathogenic variants
df = pd.read_sql_query("""
    SELECT symbol FROM genes
    WHERE gene_id IN (
        SELECT gene_id FROM expression
        WHERE condition = 'tumor'
        GROUP BY gene_id
        HAVING AVG(tpm) > 50
    )
    AND gene_id IN (
        SELECT gene_id FROM variants
        WHERE clinical_significance = 'pathogenic'
    )
""", conn)
```

## CREATE, INSERT, UPDATE, DELETE

```python
cursor.execute('''
    CREATE TABLE IF NOT EXISTS de_results (
        gene_id INTEGER REFERENCES genes(gene_id),
        log2fc REAL,
        pvalue REAL,
        padj REAL,
        significant INTEGER
    )
''')

de_data = [
    (1, 2.3,  0.001,  0.01,  1),
    (2, 1.8,  0.005,  0.03,  1),
    (3, 0.2,  0.45,   0.78,  0),
    (5, 3.1,  0.0001, 0.002, 1),
]
cursor.executemany('INSERT INTO de_results VALUES (?,?,?,?,?)', de_data)
conn.commit()

df = pd.read_sql_query("""
    SELECT g.symbol, d.log2fc, d.padj,
           CASE WHEN d.significant = 1 THEN 'Yes' ELSE 'No' END AS significant
    FROM de_results d
    JOIN genes g ON d.gene_id = g.gene_id
    ORDER BY d.padj
""", conn)

conn.close()
```

## Pitfalls

- **JOIN type**: INNER loses unmatched rows; LEFT preserves them — choose deliberately.
- **HAVING vs WHERE**: Using WHERE on an aggregate silently fails or raises an error; use HAVING.
- **Parameterized queries**: f-string SQL with user input = SQL injection risk.
- **Off-by-one**: Python ranges are half-open `[start, stop)`; bioinformatics coordinates are often 1-based.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.