Cómo Conectar Python con SQL Server: Guía paso a paso con ejemplos

D
DanisCh
• 18 min de lectura
Cómo Conectar Python con SQL Server: Guía paso a paso con ejemplos
Conceptos de Base de Datos Python SQL

SQL Server es la base de datos de Microsoft más usada en entornos empresariales: bancos, aseguradoras, sistemas de gestión hospitalaria, ERP corporativos. Si trabajas en un entorno Windows o en proyectos .NET, lo más probable es que antes o después necesites conectar Python con SQL Server.

En este artículo vas a aprender a hacerlo desde cero: qué librería usar, cómo construir la cadena de conexión correctamente, cómo ejecutar el CRUD completo de forma segura y cómo conectarte tanto a SQL Server local como a Azure SQL Database.

Si ya leíste nuestra guía sobre cómo conectar Python con MySQL, verás que muchos conceptos son similares — cursores, fetchall, consultas parametrizadas — pero con diferencias importantes en la cadena de conexión y en el manejo de tipos de datos. Y si aún no tienes claros los fundamentos de SQL, te recomendamos leer primero nuestros artículos sobre qué es SQL y los comandos SQL básicos.


Las librerías disponibles para Python + SQL Server

Librería¿Qué es?¿Cuándo usarla?
pyodbcInterfaz ODBC para Python — la más usadaPrimera opción para la mayoría de proyectos — flexible y bien documentada
mssql-pythonDriver oficial de Microsoft (lanzado en 2026)Nueva opción oficial — recomendada para proyectos nuevos con Azure SQL
pymssqlDriver puro Python para SQL ServerAlternativa ligera sin dependencias de ODBC — útil en Linux/Docker
SQLAlchemy + pyodbcORM con soporte para SQL ServerProyectos grandes donde quieres trabajar con objetos Python en vez de SQL crudo

En este artículo cubrimos pyodbc (el más usado en producción), pymssql (la alternativa sin ODBC) y SQLAlchemy para el enfoque ORM.


Paso 1: Preparar el entorno

Instalar SQL Server

Tienes varias opciones para tener SQL Server disponible:

# Opción A: SQL Server con Docker (la más rápida — funciona en Mac, Linux y Windows)
docker run -d \
  --name sqlserver-dev \
  -e "ACCEPT_EULA=Y" \
  -e "MSSQL_SA_PASSWORD=MiPassword123!" \
  -e "MSSQL_PID=Developer" \
  -p 1433:1433 \
  mcr.microsoft.com/mssql/server:2022-latest

# Opción B: SQL Server Express (gratuito, solo Windows)
# → Descarga desde microsoft.com/sql-server/sql-server-downloads

# Opción C: Azure SQL Database
# → Crea una instancia gratuita en portal.azure.com
# → El tier gratuito incluye 32 GB y es suficiente para aprender

Instalar el ODBC Driver para SQL Server

pyodbc necesita un driver ODBC instalado en el sistema operativo. Este paso es diferente según tu plataforma:

# ── Windows ─────────────────────────────────────────────────
# El ODBC Driver 18 suele estar preinstalado en Windows moderno.
# Si no está: descarga "ODBC Driver 18 for SQL Server" desde microsoft.com

# Verificar drivers instalados en Windows (ejecutar en Python):
import pyodbc
print(pyodbc.drivers())
# ['ODBC Driver 17 for SQL Server', 'ODBC Driver 18 for SQL Server', ...]

# ── Ubuntu/Debian (Linux) ────────────────────────────────────
curl https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add -
curl https://packages.microsoft.com/config/ubuntu/22.04/prod.list \
  | sudo tee /etc/apt/sources.list.d/mssql-release.list

sudo apt-get update
sudo ACCEPT_EULA=Y apt-get install -y msodbcsql18
sudo apt-get install -y unixodbc-dev  # necesario para pyodbc en Linux

# ── macOS ────────────────────────────────────────────────────
brew install msodbcsql18
brew install unixodbc

Instalar las librerías Python

# Instalar pyodbc (la más usada)
pip install pyodbc

# Instalar pymssql (alternativa sin ODBC)
pip install pymssql

# Para variables de entorno
pip install python-dotenv

# Para SQLAlchemy con SQL Server
pip install sqlalchemy pyodbc

# Verificar
python -c "import pyodbc; print('pyodbc OK:', pyodbc.version)"

Crear la base de datos y tabla de ejemplo

-- Ejecuta esto en SQL Server Management Studio, Azure Data Studio o sqlcmd

CREATE DATABASE tienda;
GO

USE tienda;
GO

CREATE TABLE productos (
    id          INT IDENTITY(1,1) PRIMARY KEY,  -- AUTO_INCREMENT en SQL Server
    nombre      NVARCHAR(100) NOT NULL,          -- NVARCHAR soporta Unicode
    precio      DECIMAL(10,2) NOT NULL,
    stock       INT DEFAULT 0,
    categoria   NVARCHAR(50),
    activo      BIT DEFAULT 1,                  -- BIT = booleano en SQL Server
    creado_en   DATETIME2 DEFAULT GETDATE(),
    actualizado DATETIME2 DEFAULT GETDATE()
);
GO

INSERT INTO productos (nombre, precio, stock, categoria) VALUES
(N'Laptop Pro 15',     899.99, 20,  N'tecnologia'),
(N'Mouse Inalámbrico',  29.99, 150, N'periféricos'),
(N'Teclado Mecánico',   79.99, 80,  N'periféricos'),
(N'Monitor 4K',        399.99, 25,  N'tecnologia'),
(N'Webcam HD',          49.99, 60,  N'periféricos');
GO

💡 Diferencias importantes vs MySQL:

  • IDENTITY(1,1) en SQL Server = AUTO_INCREMENT en MySQL
  • NVARCHAR = texto Unicode (recomendado para caracteres especiales como acentos)
  • BIT = booleano (0 o 1)
  • DATETIME2 = tipo de fecha más preciso que DATETIME
  • GETDATE() = función equivalente a NOW() en MySQL

Paso 2: La cadena de conexión — el punto más crítico

La cadena de conexión (connection string) es donde más errores cometen los principiantes al conectar con SQL Server. Hay varias variantes según el entorno:

import pyodbc

# ── Variante 1: Autenticación SQL (usuario + contraseña) ─────
# La más común cuando usas un usuario SQL Server específico
connection_string = (
    "DRIVER={ODBC Driver 18 for SQL Server};"
    "SERVER=localhost,1433;"          # servidor,puerto
    "DATABASE=tienda;"
    "UID=sa;"                          # usuario
    "PWD=MiPassword123!;"             # contraseña
    "TrustServerCertificate=yes;"     # necesario en entornos locales/dev
    "Encrypt=yes;"                    # cifrado SSL
)

# ── Variante 2: Autenticación Windows (solo Windows) ────────
# Usa las credenciales del usuario de Windows actualmente logueado
# No necesitas usuario/contraseña explícitos
connection_string_windows = (
    "DRIVER={ODBC Driver 18 for SQL Server};"
    "SERVER=MiServidor\\SQLEXPRESS;"  # nombre_servidor\instancia
    "DATABASE=tienda;"
    "Trusted_Connection=yes;"         # usa credenciales de Windows
)

# ── Variante 3: Azure SQL Database ──────────────────────────
connection_string_azure = (
    "DRIVER={ODBC Driver 18 for SQL Server};"
    "SERVER=mi-servidor.database.windows.net,1433;"  # servidor Azure
    "DATABASE=tienda;"
    "UID=mi-usuario;"
    "PWD=MiPassword123!;"
    "Encrypt=yes;"
    "TrustServerCertificate=no;"      # en Azure: no confiar en cert autofirmado
    "Connection Timeout=30;"
)

# ── Variante 4: Instancia nombrada ──────────────────────────
connection_string_instancia = (
    "DRIVER={ODBC Driver 18 for SQL Server};"
    "SERVER=MI-PC\\SQLEXPRESS;"       # cuando instalaste SQL Server Express
    "DATABASE=tienda;"
    "Trusted_Connection=yes;"
)

Usar variables de entorno — siempre

# .env (NUNCA subir a git)
DB_SERVER=localhost,1433
DB_DATABASE=tienda
DB_USER=sa
DB_PASSWORD=MiPassword123!
DB_DRIVER=ODBC Driver 18 for SQL Server
import pyodbc
import os
from dotenv import load_dotenv

load_dotenv()

def construir_connection_string():
    """Construye la cadena de conexión desde variables de entorno."""
    return (
        f"DRIVER={{{os.getenv('DB_DRIVER', 'ODBC Driver 18 for SQL Server')}}};"
        f"SERVER={os.getenv('DB_SERVER', 'localhost,1433')};"
        f"DATABASE={os.getenv('DB_DATABASE')};"
        f"UID={os.getenv('DB_USER')};"
        f"PWD={os.getenv('DB_PASSWORD')};"
        "TrustServerCertificate=yes;"
        "Encrypt=yes;"
    )

def obtener_conexion():
    """Establece y devuelve una conexión a SQL Server."""
    try:
        conexion = pyodbc.connect(construir_connection_string())
        return conexion
    except pyodbc.Error as e:
        sqlstate = e.args[0]
        print(f"❌ Error de conexión [{sqlstate}]: {e}")
        return None

# Probar la conexión
conn = obtener_conexion()
if conn:
    cursor = conn.cursor()
    cursor.execute("SELECT @@VERSION")
    version = cursor.fetchone()[0]
    print(f"✅ Conectado a: {version[:50]}...")
    conn.close()

Paso 3: Diferencias clave entre pyodbc y mysql-connector

# Si vienes de mysql-connector-python, estos son los cambios principales:

# MySQL:              SQL Server (pyodbc):
# %s                  ?           ← placeholder diferente
# cursor.lastrowid    OUTPUT INSERTED.id  ← obtener ID insertado
# LIMIT n             TOP n o FETCH NEXT  ← paginación diferente
# AUTO_INCREMENT      IDENTITY(1,1)       ← autoincremento
# BOOLEAN             BIT                 ← tipo booleano
# NOW()               GETDATE()           ← fecha actual
# CONCAT(a, b)        a + b               ← concatenación

# El placeholder en pyodbc es ? en lugar de %s:
cursor.execute("SELECT * FROM productos WHERE id = ?", (1,))   # ✅ pyodbc
cursor.execute("SELECT * FROM productos WHERE id = %s", (1,))  # ❌ MySQL syntax

Paso 4: CRUD completo con pyodbc

📖 READ — leer datos

import pyodbc
import os
from dotenv import load_dotenv

load_dotenv()

def obtener_conexion():
    return pyodbc.connect(
        f"DRIVER={{{os.getenv('DB_DRIVER')}}};"
        f"SERVER={os.getenv('DB_SERVER')};"
        f"DATABASE={os.getenv('DB_DATABASE')};"
        f"UID={os.getenv('DB_USER')};"
        f"PWD={os.getenv('DB_PASSWORD')};"
        "TrustServerCertificate=yes;"
    )

# ── Obtener todos los productos ──────────────────────────────
def obtener_todos_los_productos():
    conn = obtener_conexion()
    cursor = conn.cursor()

    try:
        cursor.execute("""
            SELECT id, nombre, precio, stock, categoria
            FROM productos
            WHERE activo = 1
            ORDER BY nombre
        """)

        # pyodbc: acceder a columnas por nombre con cursor.description
        columnas = [col[0] for col in cursor.description]
        filas = cursor.fetchall()

        # Convertir a lista de diccionarios
        productos = [dict(zip(columnas, fila)) for fila in filas]
        return productos

    except pyodbc.Error as e:
        print(f"❌ Error al leer: {e}")
        return []

    finally:
        cursor.close()
        conn.close()

# ── Obtener un producto por ID ───────────────────────────────
def obtener_producto_por_id(producto_id):
    conn = obtener_conexion()
    cursor = conn.cursor()

    try:
        # ⚠️ Placeholder en pyodbc es ? (no %s como en mysql-connector)
        cursor.execute(
            "SELECT * FROM productos WHERE id = ? AND activo = 1",
            (producto_id,)
        )
        fila = cursor.fetchone()

        if fila:
            columnas = [col[0] for col in cursor.description]
            return dict(zip(columnas, fila))
        return None

    finally:
        cursor.close()
        conn.close()

# ── Búsqueda con filtros y paginación ───────────────────────
def buscar_productos(categoria=None, precio_max=None, pagina=1, por_pagina=10):
    conn = obtener_conexion()
    cursor = conn.cursor()

    try:
        # Paginación en SQL Server: OFFSET/FETCH (SQL Server 2012+)
        offset = (pagina - 1) * por_pagina

        query = """
            SELECT id, nombre, precio, stock
            FROM productos
            WHERE activo = 1
        """
        params = []

        if categoria:
            query += " AND categoria = ?"
            params.append(categoria)

        if precio_max:
            query += " AND precio <= ?"
            params.append(precio_max)

        query += """
            ORDER BY nombre
            OFFSET ? ROWS
            FETCH NEXT ? ROWS ONLY
        """
        params.extend([offset, por_pagina])

        cursor.execute(query, params)
        columnas = [col[0] for col in cursor.description]
        return [dict(zip(columnas, fila)) for fila in cursor.fetchall()]

    finally:
        cursor.close()
        conn.close()

# Probar
productos = obtener_todos_los_productos()
print(f"Total: {len(productos)} productos")
for p in productos:
    print(f"  [{p['id']}] {p['nombre']} — ${p['precio']}")

producto = obtener_producto_por_id(1)
if producto:
    print(f"\nEncontrado: {producto['nombre']}")

➕ CREATE — insertar datos

# ── Insertar un producto ─────────────────────────────────────
def insertar_producto(nombre, precio, stock, categoria):
    conn = obtener_conexion()
    cursor = conn.cursor()

    try:
        # OUTPUT INSERTED.id → devuelve el ID generado por IDENTITY
        # Equivalente a lastrowid en MySQL
        cursor.execute("""
            INSERT INTO productos (nombre, precio, stock, categoria)
            OUTPUT INSERTED.id
            VALUES (?, ?, ?, ?)
        """, (nombre, precio, stock, categoria))

        nuevo_id = cursor.fetchone()[0]
        conn.commit()

        print(f"✅ Producto creado con ID: {nuevo_id}")
        return nuevo_id

    except pyodbc.Error as e:
        conn.rollback()
        print(f"❌ Error al insertar: {e}")
        return None

    finally:
        cursor.close()
        conn.close()

# ── Insertar múltiples productos ─────────────────────────────
def insertar_varios_productos(lista_productos):
    """
    lista_productos: lista de tuplas (nombre, precio, stock, categoria)
    """
    conn = obtener_conexion()
    cursor = conn.cursor()

    try:
        sql = """
            INSERT INTO productos (nombre, precio, stock, categoria)
            VALUES (?, ?, ?, ?)
        """
        cursor.executemany(sql, lista_productos)
        conn.commit()

        print(f"✅ {cursor.rowcount} productos insertados")
        return cursor.rowcount

    except pyodbc.Error as e:
        conn.rollback()
        print(f"❌ Error: {e}")
        return 0

    finally:
        cursor.close()
        conn.close()

# Probar
nuevo_id = insertar_producto("Auriculares BT", 59.99, 40, "audio")

nuevos = [
    ("SSD 1TB",    89.99, 35, "almacenamiento"),
    ("Cable HDMI",  9.99, 200, "cables"),
    ("Hub USB-C",  29.99, 75, "periféricos"),
]
insertar_varios_productos(nuevos)

✏️ UPDATE — actualizar datos

def actualizar_precio(producto_id, nuevo_precio):
    conn = obtener_conexion()
    cursor = conn.cursor()

    try:
        cursor.execute(
            "UPDATE productos SET precio = ?, actualizado = GETDATE() WHERE id = ?",
            (nuevo_precio, producto_id)
        )
        conn.commit()

        if cursor.rowcount > 0:
            print(f"✅ Precio actualizado — {cursor.rowcount} fila/s afectadas")
            return True
        else:
            print(f"⚠️ Producto {producto_id} no encontrado")
            return False

    except pyodbc.Error as e:
        conn.rollback()
        print(f"❌ Error al actualizar: {e}")
        return False

    finally:
        cursor.close()
        conn.close()

def aplicar_descuento_categoria(categoria, porcentaje):
    conn = obtener_conexion()
    cursor = conn.cursor()

    try:
        cursor.execute("""
            UPDATE productos
            SET precio = ROUND(precio * (1 - ? / 100.0), 2),
                actualizado = GETDATE()
            WHERE categoria = ? AND activo = 1
        """, (porcentaje, categoria))

        conn.commit()
        print(f"✅ Descuento del {porcentaje}% aplicado a {cursor.rowcount} productos")
        return cursor.rowcount

    except pyodbc.Error as e:
        conn.rollback()
        print(f"❌ Error: {e}")
        return 0

    finally:
        cursor.close()
        conn.close()

# Probar
actualizar_precio(1, 849.99)
aplicar_descuento_categoria("periféricos", 10)

🗑️ DELETE — eliminar datos

# ── Borrado lógico (recomendado en producción) ───────────────
def desactivar_producto(producto_id):
    conn = obtener_conexion()
    cursor = conn.cursor()

    try:
        cursor.execute(
            "UPDATE productos SET activo = 0 WHERE id = ?",
            (producto_id,)
        )
        conn.commit()

        if cursor.rowcount > 0:
            print(f"✅ Producto {producto_id} desactivado")
            return True
        return False

    except pyodbc.Error as e:
        conn.rollback()
        print(f"❌ Error: {e}")
        return False

    finally:
        cursor.close()
        conn.close()

# ── Borrado físico ───────────────────────────────────────────
def eliminar_producto(producto_id):
    conn = obtener_conexion()
    cursor = conn.cursor()

    try:
        cursor.execute("DELETE FROM productos WHERE id = ?", (producto_id,))
        conn.commit()

        if cursor.rowcount > 0:
            print(f"✅ Producto {producto_id} eliminado")
            return True
        return False

    except pyodbc.Error as e:
        conn.rollback()
        print(f"❌ Error: {e}")
        return False

    finally:
        cursor.close()
        conn.close()

desactivar_producto(5)

Paso 5: Transacciones — operaciones atómicas

Al igual que vimos en el artículo sobre transacciones en SQL, las propiedades ACID son fundamentales para garantizar la integridad de los datos. Con pyodbc el control es explícito:

def procesar_venta(producto_id, cantidad, cliente_id):
    """
    Operación de venta atómica: reduce stock y registra la venta.
    Si algo falla, todo se revierte.
    """
    conn = obtener_conexion()
    cursor = conn.cursor()

    try:
        # pyodbc: autocommit está desactivado por defecto → control manual
        conn.autocommit = False

        # 1. Verificar y bloquear el registro con WITH (UPDLOCK, ROWLOCK)
        cursor.execute("""
            SELECT stock, precio
            FROM productos WITH (UPDLOCK, ROWLOCK)
            WHERE id = ? AND activo = 1
        """, (producto_id,))

        producto = cursor.fetchone()

        if not producto:
            raise ValueError(f"Producto {producto_id} no existe o está inactivo")

        stock_actual, precio = producto

        if stock_actual < cantidad:
            raise ValueError(
                f"Stock insuficiente. Disponible: {stock_actual}, solicitado: {cantidad}"
            )

        total = float(precio) * cantidad

        # 2. Reducir el stock
        cursor.execute(
            "UPDATE productos SET stock = stock - ? WHERE id = ?",
            (cantidad, producto_id)
        )

        # 3. Registrar la venta
        cursor.execute("""
            INSERT INTO ventas (cliente_id, producto_id, cantidad, total, fecha)
            OUTPUT INSERTED.id
            VALUES (?, ?, ?, ?, GETDATE())
        """, (cliente_id, producto_id, cantidad, total))

        venta_id = cursor.fetchone()[0]

        # 4. Confirmar si todo fue bien
        conn.commit()
        print(f"✅ Venta #{venta_id} procesada — Total: ${total:.2f}")
        return venta_id

    except ValueError as e:
        conn.rollback()
        print(f"❌ Error de negocio: {e}")
        return None

    except pyodbc.Error as e:
        conn.rollback()
        print(f"❌ Error de base de datos: {e}")
        return None

    finally:
        cursor.close()
        conn.close()

Paso 6: pymssql — la alternativa sin ODBC

Si estás en un entorno donde instalar el ODBC Driver es complicado (algunos contenedores Docker, entornos CI/CD minimalistas), pymssql es una excelente alternativa:

# pip install pymssql

import pymssql
import os
from dotenv import load_dotenv

load_dotenv()

def obtener_conexion_pymssql():
    return pymssql.connect(
        server   = os.getenv("DB_SERVER", "localhost"),
        port     = int(os.getenv("DB_PORT", 1433)),
        user     = os.getenv("DB_USER"),
        password = os.getenv("DB_PASSWORD"),
        database = os.getenv("DB_DATABASE"),
        charset  = "UTF-8",
        as_dict  = True   # devuelve filas como diccionarios automáticamente
    )

# ── CRUD con pymssql ─────────────────────────────────────────
conn = obtener_conexion_pymssql()
cursor = conn.cursor()

# Leer — with as_dict=True, el cursor devuelve dicts directamente
cursor.execute("SELECT id, nombre, precio FROM productos WHERE activo = 1")
productos = cursor.fetchall()
for p in productos:
    print(f"[{p['id']}] {p['nombre']} — ${p['precio']}")

# Insertar — pymssql usa %s como placeholder (igual que MySQL)
cursor.execute("""
    INSERT INTO productos (nombre, precio, stock, categoria)
    VALUES (%s, %s, %s, %s)
""", ("Parlante Portátil", 35.99, 60, "audio"))

conn.commit()
nuevo_id = cursor.lastrowid
print(f"✅ Insertado con ID: {nuevo_id}")

cursor.close()
conn.close()

pyodbc vs pymssql — cuándo usar cada uno

pyodbc:
  ✅ Más flexible — funciona con cualquier BD que tenga driver ODBC
  ✅ Más opciones de autenticación (Windows Auth, Azure AD, Kerberos)
  ✅ Mejor rendimiento en conexiones largas
  ❌ Requiere instalar el ODBC Driver en el SO
  ❌ Más configuración inicial

pymssql:
  ✅ Instalación más sencilla — solo pip install, sin drivers de SO
  ✅ Placeholder %s en lugar de ? (coherente con MySQL y PostgreSQL)
  ✅ as_dict=True para diccionarios automáticos
  ❌ Menos opciones de autenticación avanzada
  ❌ Sin soporte nativo para Windows Authentication en Linux

Paso 7: SQLAlchemy con SQL Server

# pip install sqlalchemy pyodbc

from sqlalchemy import create_engine, text, Column, Integer, String, Numeric, Boolean
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
import os
from dotenv import load_dotenv
import urllib

load_dotenv()

# ── Construir la URL de conexión para SQLAlchemy ─────────────
# SQLAlchemy necesita la connection string en formato URL
# Para SQL Server usamos el dialect mssql+pyodbc

params = urllib.parse.quote_plus(
    f"DRIVER={{{os.getenv('DB_DRIVER', 'ODBC Driver 18 for SQL Server')}}};"
    f"SERVER={os.getenv('DB_SERVER')};"
    f"DATABASE={os.getenv('DB_DATABASE')};"
    f"UID={os.getenv('DB_USER')};"
    f"PWD={os.getenv('DB_PASSWORD')};"
    "TrustServerCertificate=yes;"
)

DATABASE_URL = f"mssql+pyodbc:///?odbc_connect={params}"

engine  = create_engine(DATABASE_URL, echo=False)
Base    = declarative_base()
Session = sessionmaker(bind=engine)

# ── Definir el modelo ────────────────────────────────────────
class Producto(Base):
    __tablename__ = "productos"

    id        = Column(Integer, primary_key=True)
    nombre    = Column(String(100), nullable=False)
    precio    = Column(Numeric(10, 2), nullable=False)
    stock     = Column(Integer, default=0)
    categoria = Column(String(50))
    activo    = Column(Boolean, default=True)

    def __repr__(self):
        return f""

# ── CRUD con SQLAlchemy ──────────────────────────────────────
def demo_sqlalchemy():
    session = Session()

    try:
        # CREATE
        nuevo = Producto(
            nombre="Micrófono USB", precio=89.99, stock=30, categoria="audio"
        )
        session.add(nuevo)
        session.commit()
        print(f"✅ Insertado: {nuevo}")

        # READ
        todos = session.query(Producto).filter(Producto.activo == True).all()
        print(f"\nTotal activos: {len(todos)}")

        tecnologia = (session.query(Producto)
                     .filter(Producto.categoria == "tecnologia")
                     .filter(Producto.precio < 500)
                     .order_by(Producto.precio)
                     .all())
        print(f"Tecnología < $500: {len(tecnologia)}")

        # UPDATE
        producto = session.query(Producto).filter(Producto.id == 1).first()
        if producto:
            producto.precio = 849.99
            session.commit()
            print(f"\n✅ Actualizado: {producto}")

        # SQL nativo dentro de SQLAlchemy cuando necesitas algo específico de T-SQL
        resultado = session.execute(
            text("SELECT TOP 3 nombre, precio FROM productos ORDER BY precio DESC")
        )
        print("\nTop 3 más caros:")
        for fila in resultado:
            print(f"  {fila.nombre}: ${fila.precio}")

    except Exception as e:
        session.rollback()
        print(f"❌ Error: {e}")

    finally:
        session.close()

demo_sqlalchemy()

Paso 8: Conectar a Azure SQL Database

Azure SQL Database es SQL Server en la nube de Microsoft. La conexión es casi idéntica pero con algunas diferencias en la cadena de conexión:

# .env para Azure SQL
DB_SERVER=mi-servidor.database.windows.net,1433
DB_DATABASE=tienda
DB_USER=mi-usuario@mi-servidor
DB_PASSWORD=MiPasswordAzure!
DB_DRIVER=ODBC Driver 18 for SQL Server
def obtener_conexion_azure():
    """Conexión optimizada para Azure SQL Database."""
    return pyodbc.connect(
        f"DRIVER={{{os.getenv('DB_DRIVER')}}};"
        f"SERVER={os.getenv('DB_SERVER')};"
        f"DATABASE={os.getenv('DB_DATABASE')};"
        f"UID={os.getenv('DB_USER')};"
        f"PWD={os.getenv('DB_PASSWORD')};"
        "Encrypt=yes;"                    # obligatorio en Azure
        "TrustServerCertificate=no;"      # NO confiar en cert autofirmado en Azure
        "Connection Timeout=30;"          # timeout más generoso para cloud
    )

# Ejecutar stored procedures — muy comunes en SQL Server empresarial
def ejecutar_sp(nombre_sp, **params):
    """Ejecuta un stored procedure con parámetros nombrados."""
    conn = obtener_conexion_azure()
    cursor = conn.cursor()

    try:
        # Construir la llamada al SP
        param_str = ", ".join([f"@{k}=?" for k in params.keys()])
        sql = f"EXEC {nombre_sp} {param_str}"

        cursor.execute(sql, list(params.values()))

        # Si el SP devuelve resultados
        if cursor.description:
            columnas = [col[0] for col in cursor.description]
            return [dict(zip(columnas, fila)) for fila in cursor.fetchall()]

        conn.commit()
        return cursor.rowcount

    finally:
        cursor.close()
        conn.close()

# Ejemplo de uso con un SP existente:
# resultados = ejecutar_sp("sp_ObtenerProductosPorCategoria",
#                           categoria="tecnologia", precio_max=500)

Errores comunes y cómo resolverlos

❌ Error: [IM002] No data source name found (ODBC Driver no instalado)

# Error típico en Linux o cuando el driver no está instalado
# pyodbc.Error: ('IM002', '[IM002] [unixODBC][Driver Manager] Data source name not found')

# Solución: verificar drivers instalados
import pyodbc
drivers = pyodbc.drivers()
print(drivers)  # si está vacío → instala el ODBC Driver

# En Linux Ubuntu:
# sudo ACCEPT_EULA=Y apt-get install -y msodbcsql18

❌ Error de login por contraseña con requisitos de complejidad

# SQL Server requiere contraseñas con: mayúscula, minúscula, número y símbolo
# "MiPassword123!" ✅
# "password"       ❌ → "Login failed for user"

# Verifica también que el usuario SA esté habilitado:
-- En SQL Server Management Studio:
ALTER LOGIN sa ENABLE;
ALTER LOGIN sa WITH PASSWORD = 'MiPassword123!';

❌ Error: SSL Provider: [SSL: CERTIFICATE_VERIFY_FAILED]

# Ocurre en entornos locales donde el certificado es autofirmado
# Solución: añadir TrustServerCertificate=yes en la cadena de conexión

"TrustServerCertificate=yes;"  # ← para entornos de desarrollo
# En producción y Azure: usa TrustServerCertificate=no

❌ Olvidar que el placeholder en pyodbc es ? (no %s)

# ❌ Error: usas sintaxis de MySQL
cursor.execute("SELECT * FROM productos WHERE id = %s", (1,))

# ✅ En pyodbc siempre es ?
cursor.execute("SELECT * FROM productos WHERE id = ?", (1,))

# ⚠️ pymssql sí usa %s (como MySQL) — ojo al cambiar de librería

❌ Olvidar conn.commit() después de INSERT/UPDATE/DELETE

# pyodbc: autocommit está en False por defecto
# Si no haces commit, los cambios se pierden al cerrar la conexión

cursor.execute("INSERT INTO productos (nombre, precio) VALUES (?, ?)", ("Test", 9.99))
conn.commit()  # ← imprescindible

Resumen comparativo: Python + MySQL vs Python + SQL Server

AspectoPython + MySQLPython + SQL Server
Librería principalmysql-connector-pythonpyodbc
Placeholder%s? (pyodbc) / %s (pymssql)
ID del insertadocursor.lastrowidOUTPUT INSERTED.id en la query
AutoincrementoAUTO_INCREMENTIDENTITY(1,1)
BooleanoBOOLEANBIT (0/1)
Fecha actualNOW()GETDATE()
PaginaciónLIMIT n OFFSET mOFFSET m ROWS FETCH NEXT n ROWS ONLY
Bloqueo en lecturaFOR UPDATEWITH (UPDLOCK, ROWLOCK)
Texto UnicodeVARCHARNVARCHAR (recomendado)
Driver adicionalNo necesitaRequiere ODBC Driver instalado

Resumen: lo que aprendiste hoy

  • ✅ Las librerías principales son pyodbc (la más usada), mssql-python (nueva oficial de Microsoft) y pymssql (sin dependencias ODBC)
  • ✅ Siempre instala el ODBC Driver 18 for SQL Server antes de usar pyodbc
  • ✅ La cadena de conexión varía según el tipo: SQL Auth, Windows Auth o Azure SQL
  • ✅ Siempre usa variables de entorno para credenciales — nunca las pongas en el código
  • ✅ El placeholder en pyodbc es ? (no %s como en MySQL) — el error más frecuente al migrar
  • ✅ Para obtener el ID del insertado usa OUTPUT INSERTED.id en la query (no lastrowid)
  • ✅ TrustServerCertificate=yes es necesario en entornos locales; en Azure usa no
  • ✅ Las transacciones con commit() y rollback() son manuales — autocommit está en False por defecto
  • ✅ pymssql usa %s como placeholder y as_dict=True devuelve diccionarios automáticamente
  • ✅ SQLAlchemy soporta SQL Server con el dialecto mssql+pyodbc

🧪 ¿Tienes los fundamentos de Python y SQL bien sólidos?

Conectar Python con SQL Server requiere dominar funciones, manejo de errores y excepciones en Python, más los comandos SQL esenciales. Comprueba dónde estás:

👉 Test: Python Básico 👉 Test: SQL Básico 

¿Usas SQL Server en tu trabajo o proyecto? ¿Cuál fue el error que más te costó resolver: la cadena de conexión, el ODBC Driver o el placeholder ?? Cuéntanos en los comentarios 👇 — respondemos todos. 🚀

Etiquetas: Python SQL Server

¿Te ha gustado esta entrada?

Compártela con tus compañeros para que también sigan aprendiendo.

Comunidad y Comentarios

0 COMENTARIOS

No hay comentarios todavía. Sé el primero en compartir tu opinión.

Escribe tu opinión
Respondiendo a