Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
El controlador mssql-python soporta binary, varbinary, y image tipos de datos para Microsoft SQL.
Microsoft SQL almacena datos binarios en estos tipos de columnas:
| Tipo | Descripción | Tamaño máximo |
|---|---|---|
binary(n) |
Datos binarios de longitud fija. | 8.000 bytes |
varbinary(n) |
Datos binarios de longitud variable. | 8.000 bytes |
varbinary(max) |
Datos binarios grandes. | 2 GB |
image |
Datos binarios grandes heredados (obsoletos). | 2 GB |
El controlador mssql-python devuelve datos binarios como objetos Pythonbytes.
Cuándo almacenar el binario en la base de datos frente al sistema de archivos:
- Guarda en la base de datos cuando los archivos son pequeños (menos de 1 MB), la consistencia transaccional con otros datos es importante, o cuando necesitas hacer copias de seguridad de datos y archivos juntos.
- Guarda en el sistema de archivos (o Azure Blob Storage) cuando los archivos son grandes (más de 1 MB), necesitas entrega de CDN o necesitas servir archivos directamente a clientes sin viajes de ida y vuelta a bases de datos.
- Como punto intermedio, la función FILESTREAM de Microsoft SQL almacena datos en el sistema de archivos con consistencia transaccional.
Insertar datos binarios
Pasa objetos Python bytes como parámetros; el controlador los asigna a columnas varbinary.
Insertar bytes directamente
Crea un objeto Python bytes y pásalo como parámetro de consulta:
import mssql_python
conn = mssql_python.connect(connection_string)
cursor = conn.cursor()
# Create a table to hold binary documents
cursor.execute("""
CREATE TABLE #Documents (
ID INT IDENTITY PRIMARY KEY,
Name NVARCHAR(255),
Content VARBINARY(MAX),
Size INT NULL,
ContentHash VARBINARY(32) NULL
)
""")
# Binary data as bytes
binary_data = b'\x00\x01\x02\x03\x04\x05'
cursor.execute(
"INSERT INTO #Documents (Name, Content) VALUES (%(name)s, %(content)s)",
{"name": "sample.bin", "content": binary_data}
)
conn.commit()
Insertar desde el archivo
Lee un archivo en modo binario e inserta su contenido:
def insert_file(cursor, file_path: str, name: str):
"""Insert a file as binary data."""
with open(file_path, "rb") as f:
content = f.read()
cursor.execute(
"INSERT INTO #Documents (Name, Content, Size) VALUES (%(name)s, %(content)s, %(size)s)",
{"name": name, "content": content, "size": len(content)}
)
insert_file(cursor, "image.png", "profile_picture.png")
conn.commit()
Insertar archivo de imagen
Almacenar archivos de imagen con metadatos detectando los tipos MIME a partir de extensiones de archivo:
# Create a table to hold images
cursor.execute("""
CREATE TABLE #Images (
ID INT IDENTITY PRIMARY KEY,
FileName NVARCHAR(255),
FileSize INT NULL,
ContentType NVARCHAR(100) NULL,
Width INT NULL,
Height INT NULL,
ImageData VARBINARY(MAX),
Description NVARCHAR(MAX) NULL
)
""")
def insert_image(cursor, image_path: str, description: str):
"""Insert an image into the database."""
import os
with open(image_path, "rb") as f:
image_data = f.read()
cursor.execute("""
INSERT INTO #Images (FileName, FileSize, ContentType, ImageData, Description)
VALUES (%(filename)s, %(filesize)s, %(content_type)s, %(data)s, %(desc)s)
""", {
"filename": os.path.basename(image_path),
"filesize": len(image_data),
"content_type": get_content_type(image_path),
"data": image_data,
"desc": description
})
def get_content_type(path: str) -> str:
"""Determine MIME type from file extension."""
ext = path.lower().split(".")[-1]
types = {
"png": "image/png",
"jpg": "image/jpeg",
"jpeg": "image/jpeg",
"gif": "image/gif",
"pdf": "application/pdf",
}
return types.get(ext, "application/octet-stream")
Recuperación de datos binarios
El controlador devuelve los valores de las columnas varbinary y binary como objetos Python bytes.
Obtener columna binaria
Consultar columnas binarias y examinar el objeto bytes devuelto:
cursor.execute(
"SELECT LargePhotoFileName, LargePhoto FROM Production.ProductPhoto WHERE ProductPhotoID = %(id)s",
{"id": 70}
)
row = cursor.fetchone()
# LargePhoto is bytes
print(type(row.LargePhoto)) # <class 'bytes'>
print(len(row.LargePhoto)) # Number of bytes
Guardar en archivo
Recuperar datos binarios y escribirlos en el disco:
def save_photo(cursor, photo_id: int, output_path: str):
"""Retrieve binary data and save to file."""
cursor.execute(
"SELECT LargePhoto FROM Production.ProductPhoto WHERE ProductPhotoID = %(id)s",
{"id": photo_id}
)
row = cursor.fetchone()
if row and row.LargePhoto:
with open(output_path, "wb") as f:
f.write(row.LargePhoto)
print(f"Saved {len(row.LargePhoto)} bytes to {output_path}")
else:
print("Photo not found or empty")
save_photo(cursor, 70, "output.gif")
Exportar filas binarias a archivos
Exportar múltiples filas binarias a archivos locales:
import os
def export_photos(cursor, output_dir: str):
"""Export product photos to a directory."""
os.makedirs(output_dir, exist_ok=True)
cursor.execute("""
SELECT ProductPhotoID, LargePhotoFileName, LargePhoto
FROM Production.ProductPhoto
WHERE ProductPhotoID > 1
""")
count = 0
for row in cursor:
output_path = os.path.join(output_dir, f"{row.ProductPhotoID}_{row.LargePhotoFileName}")
with open(output_path, "wb") as f:
f.write(row.LargePhoto)
count += 1
print(f"Exported {count} photos to {output_dir}")
export_photos(cursor, "./photos")
Insertar valores binarios NULL
Pase None para insertar un SQL NULL en una columna binaria. Como #Documents es una tabla temporal, declare los tipos de parámetro mediante setinputsizes() primero para que el controlador vincule Content como varbinary:
cursor.setinputsizes([(mssql_python.SQL_WVARCHAR, 255, 0), (mssql_python.SQL_VARBINARY, 0, 0)])
cursor.execute(
"INSERT INTO #Documents (Name, Content) VALUES (%(name)s, %(content)s)",
{"name": "empty", "content": None}
)
Note
El controlador normalmente infiere los tipos de parámetros a través de SQLDescribeParam, pero no puede resolver metadatos de tipo para una tabla temporal o una variable de tabla. Insertar None en una columna binary o varbinary de un objeto temporal sin setinputsizes() genera ProgrammingError: Implicit conversion from data type varchar to varbinary(max) is not allowed. Pasa una entrada por parámetro, en orden, y usa una constante de tipo SQL, como mssql_python.SQL_VARBINARY para la columna binaria. Para una tabla normal (permanente), el controlador resuelve el tipo automáticamente y puedes pasar None directamente.
Datos binarios grandes
Para archivos de más de unos pocos megabytes, inserta el contenido completo en una sola escritura varbinary(max) en lugar de leer el archivo en pequeños fragmentos.
Transmitir archivos grandes
Para archivos grandes, procesa en bloques:
def insert_large_file(conn, table: str, file_path: str, chunk_size: int = 8192):
"""Insert large file in chunks using updatetext-style approach."""
# Validate table name to prevent SQL injection
import re
if not re.match(r'^#{0,2}[A-Za-z_][A-Za-z0-9_.]*$', table):
raise ValueError(f"Invalid table name: {table}")
cursor = conn.cursor()
# Get file size
import os
file_size = os.path.getsize(file_path)
# Insert initial row with empty binary
cursor.execute(f"""
INSERT INTO {table} (Name, Content, Size)
VALUES (%(name)s, 0x, %(size)s);
SELECT SCOPE_IDENTITY();
""", {"name": os.path.basename(file_path), "size": file_size})
row_id = cursor.fetchval()
# For modern Microsoft SQL, better to use varbinary(max) and single insert
# This example shows streaming approach
with open(file_path, "rb") as f:
content = f.read()
cursor.execute(f"""
UPDATE {table} SET Content = %(content)s WHERE ID = %(id)s
""", {"content": content, "id": row_id})
conn.commit()
return row_id
Usa copia masiva para datos binarios
Utiliza el bulkcopy() método para insertar eficientemente múltiples archivos binarios en una sola operación.
Importante
bulkcopy() carga los datos a través de una conexión separada al servidor, por lo que la tabla de destino ya debe existir y estar confirmada y visible para otras sesiones. Si creas la tabla en el mismo script con autocommit desactivado, ejecuta conn.commit() antes de bulkcopy(). Las tablas temporales locales (#name) no están soportadas porque son privadas de la sesión que las creó. Un destino no confirmado o inalcanzable hace que bulkcopy() falle por un tiempo de espera de Failed to retrieve destination metadata.
Por defecto, bulkcopy() asigna cada valor de una fila a una columna de tabla por posición ordinal, por lo que cada columna debe tener un valor en orden. Cuando la tabla tiene una columna identidad o solo llenas algunas columnas, pasa column_mappings para nombrar explícitamente las columnas destino. De lo contrario, los valores se desplazan a columnas incorrectas y bulkcopy() falla.
def bulk_insert_files(conn, table: str, file_paths: list[str]):
"""Bulk insert multiple binary files."""
import os
data = []
for path in file_paths:
with open(path, "rb") as f:
content = f.read()
data.append((os.path.basename(path), content, len(content)))
cursor = conn.cursor()
result = cursor.bulkcopy(table, data, column_mappings=["Name", "Content", "Size"])
conn.commit()
return result["rows_copied"]
Operaciones de datos binarios
Utiliza la función de HASHBYTES Microsoft SQL para comparar contenido binario sin obtener valores completos.
Comparar datos binarios
Encuentra filas binarias que compartan contenido idéntico calculando y comparando hashes de contenido en Microsoft SQL Server:
# Find rows with identical binary content by hash
cursor.execute("""
SELECT LargePhotoFileName, HASHBYTES('SHA2_256', LargePhoto) AS PhotoHash
FROM Production.ProductPhoto
WHERE ProductPhotoID > 1
""")
hashes = {}
for row in cursor:
hash_value = row.PhotoHash # bytes
if hash_value in hashes:
print(f"Duplicate: {row.LargePhotoFileName} matches {hashes[hash_value]}")
else:
hashes[hash_value] = row.LargePhotoFileName
Calcular hash en Python
Calcula los hashes SHA-256 en Python y alárdalos junto con datos binarios:
import hashlib
def insert_with_hash(cursor, name: str, content: bytes):
"""Insert binary data with computed hash."""
content_hash = hashlib.sha256(content).digest()
cursor.execute("""
INSERT INTO #Documents (Name, Content, ContentHash)
VALUES (%(name)s, %(content)s, %(hash)s)
""", {"name": name, "content": content, "hash": content_hash})
Codificar y decodificar datos binarios
Convierte datos binarios a y desde cadenas codificadas en base64:
import base64
# Store base64-encoded string
def insert_base64(cursor, name: str, base64_data: str):
"""Insert base64-encoded data as binary."""
binary_data = base64.b64decode(base64_data)
cursor.execute(
"INSERT INTO #Documents (Name, Content) VALUES (%(name)s, %(content)s)",
{"name": name, "content": binary_data}
)
# Retrieve as base64
def get_as_base64(cursor, doc_id: int) -> str:
"""Retrieve binary data as base64 string."""
cursor.execute("SELECT Content FROM #Documents WHERE ID = %(id)s", {"id": doc_id})
row = cursor.fetchone()
return base64.b64encode(row.Content).decode("utf-8")
Trabajo con formatos binarios específicos
Estos ejemplos muestran cómo validar e insertar formatos de archivo comunes antes de almacenarlos.
Documentos PDF
Verifica las firmas de archivos PDF antes de insertarlas:
def insert_pdf(cursor, pdf_path: str, title: str):
"""Insert a PDF document."""
with open(pdf_path, "rb") as f:
pdf_data = f.read()
# Verify it's a PDF (magic bytes)
if not pdf_data.startswith(b'%PDF'):
raise ValueError("Not a valid PDF file")
cursor.execute(
"INSERT INTO #Documents (Name, Content) VALUES (%(title)s, %(data)s)",
{"title": title, "data": pdf_data}
)
Imágenes con PIL/Pillow
Redimensiona las imágenes usando Pillow (PIL) antes de almacenarlas para ahorrar espacio:
from PIL import Image
import io
import os
def insert_resized_image(cursor, image_path: str, max_size: tuple = (800, 600)):
"""Insert a resized image."""
# Open and resize
img = Image.open(image_path)
img.thumbnail(max_size, Image.LANCZOS)
# Convert to bytes
buffer = io.BytesIO()
img.save(buffer, format=img.format or "PNG")
image_bytes = buffer.getvalue()
cursor.execute("""
INSERT INTO #Images (FileName, Width, Height, ImageData)
VALUES (%(name)s, %(width)s, %(height)s, %(data)s)
""", {
"name": os.path.basename(image_path),
"width": img.width,
"height": img.height,
"data": image_bytes
})
def get_image_as_pil(cursor, image_id: int) -> Image.Image:
"""Retrieve image as PIL Image object."""
cursor.execute("SELECT ImageData FROM #Images WHERE ID = %(id)s", {"id": image_id})
row = cursor.fetchone()
return Image.open(io.BytesIO(row.ImageData))
Datos comprimidos
Reduce el almacenamiento comprimiendo datos binarios con gzip antes de insertarlos:
import gzip
def insert_compressed(cursor, name: str, data: bytes):
"""Insert data with gzip compression."""
compressed = gzip.compress(data)
cursor.execute(
"INSERT INTO #Documents (Name, Content, Size) VALUES (%(name)s, %(content)s, %(size)s)",
{"name": name, "content": compressed, "size": len(data)}
)
def get_decompressed(cursor, data_id: int) -> bytes:
"""Retrieve and decompress data."""
cursor.execute(
"SELECT Content FROM #Documents WHERE ID = %(id)s",
{"id": data_id}
)
row = cursor.fetchone()
return gzip.decompress(row.Content)
procedimientos recomendados
Aplica estas pautas para elegir el tipo de columna y la estrategia de almacenamiento correctos para los datos binarios.
Utiliza tipos de columnas apropiados
Elige el tipo de columna según las características de tus datos:
Para diferentes casos de uso binarios, seleccione el tipo de dato Microsoft SQL apropiado:
-- For small fixed-size binary (for example, hashes, UUIDs)
binary(32) -- SHA-256 hash
-- For variable-size binary up to 8KB (for example, thumbnails, small icons)
varbinary(8000)
-- For large binary data (for example, documents, images)
varbinary(max) -- Up to 2GB
Consideremos FILESTREAM para archivos grandes
Para archivos grandes (más de 1 MB), consideremos la función FILESTREAM de SQL de Microsoft, que almacena datos en el sistema de archivos. FILESTREAM requiere configuración en el lado del servidor antes de su uso:
# FILESTREAM-enabled databases store large binaries more efficiently
# Access is still through normal queries but storage is file-based
large_binary_data = b"\x25\x50\x44\x46" + b"\x00" * 100 # sample data
cursor.execute("""
CREATE TABLE ##FileStreamDemo (Name NVARCHAR(100), Document VARBINARY(MAX))
""")
cursor.execute("""
INSERT INTO ##FileStreamDemo (Name, Document)
VALUES (%(name)s, %(content)s)
""", {"name": "large_doc.pdf", "content": large_binary_data})
cursor.execute("SELECT Name, DATALENGTH(Document) AS DocSize FROM ##FileStreamDemo")
row = cursor.fetchone()
print(f"{row.Name}: {row.DocSize} bytes")
Validar datos binarios
Valida los tipos de archivo comprobando los bytes mágicos (firmas de archivo) antes de almacenar datos binarios:
def insert_safe_image(cursor, name: str, data: bytes):
"""Insert image with validation."""
# Check file signatures (magic bytes)
signatures = {
b'\x89PNG': 'image/png',
b'\xff\xd8\xff': 'image/jpeg',
b'GIF87a': 'image/gif',
b'GIF89a': 'image/gif',
}
content_type = None
for sig, mime in signatures.items():
if data.startswith(sig):
content_type = mime
break
if content_type is None:
raise ValueError("Unknown or unsupported image format")
cursor.execute("""
INSERT INTO #Images (FileName, ContentType, ImageData)
VALUES (%(name)s, %(type)s, %(data)s)
""", {"name": name, "type": content_type, "data": data})