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? |
|---|---|---|
| pyodbc | Interfaz ODBC para Python — la más usada | Primera opción para la mayoría de proyectos — flexible y bien documentada |
| mssql-python | Driver oficial de Microsoft (lanzado en 2026) | Nueva opción oficial — recomendada para proyectos nuevos con Azure SQL |
| pymssql | Driver puro Python para SQL Server | Alternativa ligera sin dependencias de ODBC — útil en Linux/Docker |
| SQLAlchemy + pyodbc | ORM con soporte para SQL Server | Proyectos 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 aprenderInstalar 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 unixodbcInstalar 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_INCREMENTen MySQLNVARCHAR= texto Unicode (recomendado para caracteres especiales como acentos)BIT= booleano (0 o 1)DATETIME2= tipo de fecha más preciso queDATETIMEGETDATE()= función equivalente aNOW()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 Serverimport 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 syntaxPaso 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 LinuxPaso 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 Serverdef 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() # ← imprescindibleResumen comparativo: Python + MySQL vs Python + SQL Server
| Aspecto | Python + MySQL | Python + SQL Server |
|---|---|---|
| Librería principal | mysql-connector-python | pyodbc |
| Placeholder | %s | ? (pyodbc) / %s (pymssql) |
| ID del insertado | cursor.lastrowid | OUTPUT INSERTED.id en la query |
| Autoincremento | AUTO_INCREMENT | IDENTITY(1,1) |
| Booleano | BOOLEAN | BIT (0/1) |
| Fecha actual | NOW() | GETDATE() |
| Paginación | LIMIT n OFFSET m | OFFSET m ROWS FETCH NEXT n ROWS ONLY |
| Bloqueo en lectura | FOR UPDATE | WITH (UPDLOCK, ROWLOCK) |
| Texto Unicode | VARCHAR | NVARCHAR (recomendado) |
| Driver adicional | No necesita | Requiere 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.iden la query (nolastrowid) - ✅
TrustServerCertificate=yeses necesario en entornos locales; en Azure usano - ✅ Las transacciones con
commit()yrollback()son manuales — autocommit está en False por defecto - ✅ pymssql usa
%scomo placeholder yas_dict=Truedevuelve 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. 🚀
No hay comentarios todavía. Sé el primero en compartir tu opinión.