Remarque
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Le pilote mssql-python prend en charge binary, varbinary, et image les types de données pour Microsoft SQL.
Microsoft SQL stocke les données binaires dans ces types de colonnes :
| Type | Description | Taille maximale |
|---|---|---|
binary(n) |
Données binaires à longueur fixe. | 8 000 octets |
varbinary(n) |
Données binaires de longueur variable. | 8 000 octets |
varbinary(max) |
Données binaires volumineuses. | 2 Go |
image |
Anciennes données binaires volumineuses (déconseillées). | 2 Go |
Le pilote mssql-python retourne les données binaires sous forme d’objets Pythonbytes.
Quand stocker le binaire dans la base de données par rapport au système de fichiers :
- Stockez dans la base de données lorsque les fichiers sont petits (moins de 1 Mo), que la cohérence transactionnelle avec d’autres données est importante, ou que vous devez sauvegarder les données et les fichiers ensemble.
- Stockez dans le système de fichiers (ou Stockage Blob Azure) lorsque les fichiers sont volumineux (plus de 1 Mo), que vous avez besoin d’une livraison CDN, ou que vous devez servir les fichiers directement aux clients sans aller-retour dans la base de données.
- Pour un compromis, la fonctionnalité FILESTREAM de Microsoft SQL stocke les données dans le système de fichiers avec une cohérence transactionnelle.
Insérer des données binaires
Passez des objets Python bytes en tant que paramètres ; le pilote les associe à des colonnes varbinary.
Insérer directement les octets
Créez un objet Python bytes et passez-le comme paramètre de requête :
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()
Insérer à partir du fichier
Lisez un fichier en mode binaire et insérez son contenu :
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()
Insérer fichier image
Stockez les fichiers images avec des métadonnées en détectant les types MIME à partir des extensions de fichiers :
# 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")
Extraction de données binaires
Le pilote retourne les valeurs de colonnes varbinaire et binaire sous forme d’objets Pythonbytes.
Récupérer la colonne binaire
Interrogez les colonnes binaires et inspectez l’objet retourné bytes :
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
Enregistrer dans le fichier
Récupérer des données binaires et les écrire sur le disque :
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")
Exporter les lignes binaires vers les fichiers
Exportez plusieurs lignes binaires vers des fichiers locaux :
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")
Insérer des valeurs binaires NULL
Passez None pour insérer un NULL SQL dans une colonne binaire. Puisque #Documents est une table temporaire, déclarez les types des paramètres en utilisant d’abord setinputsizes() afin que le pilote lie Content en tant que 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
Le pilote déduit normalement les types de paramètres via SQLDescribeParam, mais il ne peut pas résoudre les métadonnées de type pour une table ou une variablede table temporaire. L’insertion de None dans une colonne binary ou varbinary d’un objet temporaire sans setinputsizes() génère ProgrammingError: Implicit conversion from data type varchar to varbinary(max) is not allowed. Passez une entrée par paramètre, dans l’ordre, et utilisez une constante de type SQL comme mssql_python.SQL_VARBINARY pour la colonne binaire. Pour une table classique (permanente), le driver résout automatiquement le type et vous pouvez passer None directement.
Grandes données binaires
Pour les fichiers de plus de quelques mégaoctets, insérez le contenu complet dans une seule écriture varbinaire(max ) au lieu de lire le fichier en petits blocs.
Diffuser de gros fichiers
Pour les gros fichiers, traiter par blocs :
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
Utilisez une copie en vrac pour les données binaires
Utilisez cette bulkcopy() méthode pour insérer efficacement plusieurs fichiers binaires en une seule opération.
Important
bulkcopy() charge les données via une connexion séparée au serveur, donc la table de destination doit déjà exister et être engagée et visible sur d’autres sessions. Si vous créez la table dans le même script avec l’autocommit désactivé, appelez conn.commit() avant bulkcopy(). Les tables temporaires locales (#name) ne sont pas prises en charge car elles sont privées à la session qui les a créées. Une destination non validée ou inaccessible provoque l’échec de bulkcopy() en raison d’un dépassement du délai d’attente Failed to retrieve destination metadata.
Par défaut, bulkcopy() chaque valeur d’une ligne est assignée à une colonne de tableau par position ordinale, donc chaque colonne doit avoir une valeur dans l’ordre. Lorsque la table a une colonne d’identité ou que vous ne remplissez que certaines colonnes, passez column_mappings pour nommer explicitement les colonnes cibles. Sinon, les valeurs se déplacent sur les mauvaises colonnes et bulkcopy() échouent.
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"]
Opérations de données binaires
Utilisez la fonction de HASHBYTES Microsoft SQL pour comparer le contenu binaire sans chercher les valeurs complètes.
Comparer les données binaires
Trouvez des lignes binaires partageant un contenu identique en calculant et en comparant les hachages de contenu dans 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
Calculer le hachage en Python
Calculez les hachages SHA-256 en Python et stockez-les aux côtés des données binaires :
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})
Encodage et décodage des données binaires
Convertir les données binaires vers et depuis des chaînes encodées 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")
Travail avec des formats binaires spécifiques
Ces exemples montrent comment valider et insérer des formats de fichiers courants avant de les stocker.
Documents PDF
Vérifiez les signatures des fichiers PDF avant de les insérer :
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}
)
Images avec PIL/Pillow
Redimensionnez les images avec Pillow (PIL) avant de les stocker pour économiser de la place :
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))
Données compressées
Réduisez le stockage en compressant les données binaires avec gzip avant insertion :
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)
Bonnes pratiques
Appliquez ces directives pour choisir le bon type de colonne et la bonne stratégie de stockage pour les données binaires.
Utilisez des types de colonnes appropriés
Choisissez le type de colonne en fonction des caractéristiques de vos données :
Pour différents cas d’usage binaires, sélectionnez le type de données Microsoft SQL approprié :
-- 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
Considérez FILESTREAM pour les fichiers volumineux
Pour les gros fichiers (plus de 1 Mo), considérez la fonctionnalité FILESTREAM SQL de Microsoft, qui stocke les données dans le système de fichiers. FILESTREAM nécessite une configuration côté serveur avant utilisation :
# 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")
Valider les données binaires
Validez les types de fichiers en vérifiant les octets magiques (signatures de fichiers) avant de stocker des données binaires :
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})