Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
O driver mssql-python suporta binary, varbinary, e image tipos de dados para Microsoft SQL.
O Microsoft SQL armazena dados binários nestes tipos de coluna:
| Tipo | Descrição | Tamanho máximo |
|---|---|---|
binary(n) |
Dados binários de comprimento fixo. | 8.000 bytes |
varbinary(n) |
Dados binários de comprimento variável. | 8.000 bytes |
varbinary(max) |
Dados binários grandes. | 2 GB |
image |
Dados binários legados de grande dimensão (descontinuados). | 2 GB |
O driver mssql-python devolve dados binários como objetos Pythonbytes.
Quando armazenar o binário na base de dados versus o sistema de ficheiros:
- Armazena na base de dados quando os ficheiros são pequenos (menos de 1 MB), a consistência transacional com outros dados é importante, ou quando precisas de fazer backup de dados e ficheiros juntos.
- Armazene no sistema de ficheiros (ou no Armazenamento de Blobs do Azure) quando os ficheiros forem grandes (mais de 1 MB), quando necessitar de distribuição através de uma CDN ou quando precisar de servir ficheiros diretamente aos clientes sem interações com a base de dados.
- Como solução intermédia, a funcionalidade FILESTREAM do Microsoft SQL armazena dados no sistema de ficheiros com consistência transacional.
Inserir dados binários
Passe objetos Python bytes como parâmetros; o controlador mapeia-os para as colunas varbinary.
Inserir bytes diretamente
Cria um objeto Python bytes e passa-o 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()
Inserir a partir do ficheiro
Leia um ficheiro em modo binário e insira o seu conteúdo:
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()
Inserir ficheiro de imagem
Armazene ficheiros de imagem com metadados detetando tipos MIME a partir de extensões de ficheiros:
# 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")
Recuperar dados binários
O driver retorna os valores das colunas varbinary e binary como objetos Python bytes.
Obter coluna binária
Consulte colunas binárias e inspecione o objeto devolvido 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
Guardar no ficheiro
Recuperar dados binários e escrevê-los no 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 linhas binárias para ficheiros
Exportar múltiplas linhas binárias para ficheiros locais:
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")
Inserir valores binários NULL
Passe None para inserir um SQL NULL numa coluna binária. Como #Documents é uma tabela temporária, declare primeiro os tipos dos parâmetros utilizando setinputsizes() para que o controlador associe Content a 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
O driver normalmente infere os tipos de parâmetros através de SQLDescribeParam, mas não consegue resolver metadados de tipo para uma tabela ou variável temporária. A inserção de None numa coluna binary ou varbinary de um objeto temporário sem setinputsizes() gera ProgrammingError: Implicit conversion from data type varchar to varbinary(max) is not allowed. Passe uma entrada por parâmetro, por ordem, e use uma constante do tipo SQL, como mssql_python.SQL_VARBINARY para a coluna binária. Para uma tabela normal (permanente), o driver resolve automaticamente o tipo e podes passar None diretamente.
Grandes dados binários
Para ficheiros com mais de alguns megabytes, insira o conteúdo completo numa única operação de escrita em varbinary(max), em vez de ler o ficheiro em pequenos blocos.
Transmitir ficheiros grandes
Para ficheiros grandes, processe em blocos:
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
Use cópia em massa para dados binários
Use o bulkcopy() método para inserir eficientemente múltiplos ficheiros binários numa única operação.
Importante
bulkcopy() carrega dados através de uma conexão separada ao servidor, pelo que a tabela de destino já deve existir e estar confirmada e visível a outras sessões. Se criares a tabela no mesmo script com o autocommit desativado, chama conn.commit() antes de bulkcopy(). As tabelas temporárias locais (#name) não são suportadas porque são privadas da sessão que as criou. Um destino não confirmado ou inacessível faz com que bulkcopy() falhe com um erro de tempo limite de Failed to retrieve destination metadata.
Por defeito, bulkcopy() mapeia cada valor numa linha para uma coluna da tabela por posição ordinal, pelo que cada coluna deve ter um valor por ordem. Quando a tabela tem uma coluna de identidade ou quando só preenche algumas colunas, passe column_mappings para indicar explicitamente as colunas de destino. Caso contrário, os valores deslocam-se para as colunas erradas e bulkcopy() falha.
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"]
Operações de dados binários
Use a função do HASHBYTES Microsoft SQL para comparar conteúdos binários sem obter valores completos.
Comparar dados binários
Encontre linhas binárias que partilhem conteúdo idêntico ao calcular e comparar hashes de conteúdo no 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
Computar o hash em Python
Calcule os hashes SHA-256 em Python e armazene-os juntamente com dados binários:
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 e decodificar dados binários
Converter dados binários para e a partir de cadeias codificadas em 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")
Trabalho com formatos binários específicos
Estes exemplos mostram como validar e inserir formatos de ficheiros comuns antes de os armazenar.
Documentos PDF
Verifique as assinaturas dos ficheiros PDF antes de os inserir:
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}
)
Imagens com PIL/Pillow
Redimensione as imagens usando o Pillow (PIL) antes de as guardar para poupar espaço:
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))
Dados comprimidos
Reduza o armazenamento comprimindo dados binários com gzip antes da inserção:
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)
Melhores práticas
Aplique estas orientações para escolher o tipo de coluna correto e a estratégia de armazenamento para dados binários.
Use tipos de colunas apropriados
Escolha o tipo de coluna com base nas características dos seus dados:
Para diferentes casos de uso binários, selecione o tipo de dado Microsoft SQL apropriado:
-- 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
Considere o FILESTREAM para ficheiros grandes
Para ficheiros grandes (mais de 1 MB), considere a funcionalidade Microsoft SQL FILESTREAM, que armazena dados no sistema de ficheiros. O FILESTREAM requer configuração do lado do servidor antes de ser utilizado:
# 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 dados binários
Valide os tipos de ficheiros verificando bytes mágicos (assinaturas de ficheiros) antes de armazenar dados binários:
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})