Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
O driver mssql-python fornece métodos de busca Apache Arrow para recuperação de dados colunares de alto desempenho a partir do Microsoft SQL e do Banco de Dados SQL do Azure.
Apache Arrow é uma plataforma de desenvolvimento multi-linguagens para dados colunares em memória. O driver converte conjuntos de resultados ODBC diretamente para o formato Arrow em C++, ignorando a criação de objetos em Python para melhorar o desempenho.
A integração com seta possibilita:
- Transferência de dados sem cópia para Polars, pandas e DuckDB. "Zero-copy" significa que os dados permanecem em um único buffer na memória, que o driver grava e as bibliotecas consumidoras leem diretamente, de modo que nenhuma linha é duplicada para objetos Python intermediários.
- Transmitir conjuntos de resultados via
RecordBatchReadersem carregar tudo na memória. - Formato de dados colunar ideal para cargas de análise e aprendizado de máquina.
- Uso de memória reduzido em comparação à criação de objetos Python linha por linha.
Métodos de cursor
O pyarrow pacote é obrigado a usar métodos de busca Arrow. Instale-o com pip install pyarrow. Se pyarrow não estiver instalado, chamar qualquer método Arrow gera um ImportError.
O driver mssql-python adiciona três métodos ao objeto cursor para acesso a dados do Arrow. Os três métodos convertem conjuntos de resultados ODBC para o formato Arrow na camada C++ do driver, o que evita criar objetos Python intermediários.
-
arrow()retorna todo o conjunto de resultados como uma única tabela em memória. O mais simples de usar. -
arrow_batch()retorna um lote de linhas por vez, permitindo controle manual do loop. -
arrow_reader()retorna um iterador que gera lotes automaticamente. Ideal para transmitir grandes volumes de resultados.
Usando cursor.arrow(batch_size=8192)
Obtenha todo o conjunto de resultados como um único pyarrow.Table. Esse método é o mais simples e funciona bem quando o conjunto completo de resultados cabe na memória.
import mssql_python
conn = mssql_python.connect(connection_string)
cursor = conn.cursor()
cursor.execute("SELECT ProductID, Name, ListPrice FROM Production.Product")
table = cursor.arrow()
print(type(table)) # <class 'pyarrow.lib.Table'>
print(table.num_rows) # Number of rows fetched
print(table.num_columns) # Number of columns
print(table.schema) # Column names and Arrow types
print(table.to_pandas()) # Convert to pandas DataFrame
Note
Se a sua cadeia de conexão usar Authentication=ActiveDirectoryDefault, o driver usará DefaultAzureCredential, que tenta vários provedores de credenciais em sequência. A primeira conexão pode ser lenta porque o SDK percorre a cadeia até encontrar um provedor funcionando. Em produção, se você sabe qual tipo de credencial seu ambiente usa, especifique-o diretamente (por exemplo, ActiveDirectoryMSI para identidade gerenciada) para evitar a caminhada em cadeia. Para obter mais informações, consulte Autenticação do Microsoft Entra.
Usando cursor.arrow_batch(batch_size=8192)
Busque um único pyarrow.RecordBatch contendo até batch_size linhas. Use esse método para ciclos personalizados de processamento em lote, onde você precisa de controle detalhado sobre quantas linhas são buscadas ao mesmo tempo.
cursor.execute("SELECT * FROM Production.TransactionHistory")
while True:
batch = cursor.arrow_batch(batch_size=10000)
if batch.num_rows == 0:
break
# Process each batch
print(f"Fetched {batch.num_rows} rows")
Usando cursor.arrow_reader(batch_size=8192)
Retorna um leitor que fornece objetos RecordBatch até que o conjunto de resultados se esgote. Esse método é a opção mais eficiente em memória para grandes conjuntos de resultados.
cursor.execute("SELECT * FROM Production.TransactionHistory")
reader = cursor.arrow_reader(batch_size=50000)
for batch in reader:
# Process streaming batches without loading all data
print(f"Batch: {batch.num_rows} rows")
O leitor transmite resultados pela conexão; assim, enquanto um leitor não lido estiver aberto, essa conexão não poderá iniciar outra instrução. Ao tentar um, ocorre uma falha com o erro Connection is busy with results for another command.
Três coisas liberam o leitor: iterar até o final, fechar o cursor pai ou fechar o leitor. Se você parar de ler antes que o conjunto de resultados se esgote e continuar usando o cursor, feche o leitor. Ao fechá-lo, o cursor pai também é redefinido, então você pode executar outra instrução nele.
Use o leitor como um gerenciador de contexto para que ele se feche mesmo que uma exceção interrompa o ciclo:
cursor.execute("SELECT * FROM Production.TransactionHistory")
rows_seen = 0
with cursor.arrow_reader(batch_size=50000) as reader:
for batch in reader:
rows_seen += batch.num_rows
if rows_seen >= 100000:
break
# The reader is closed here, and the cursor is ready for the next statement.
cursor.execute("SELECT COUNT(*) FROM Production.TransactionHistory")
Você também pode ligar reader.close() diretamente. Ligar mais de uma vez é seguro, e a reader.closed propriedade informa se você a fechou.
Padrões comuns
As tabelas de seta se integram diretamente com bibliotecas de dados populares em Python. Os exemplos a seguir mostram como passar dados do Arrow para pandas, Polars, DuckDB e formatos de arquivo sem copiar dados.
Carregue os resultados no pandas
cursor.execute("SELECT * FROM Production.Product")
table = cursor.arrow()
# Convert to pandas with zero-copy where possible
df = table.to_pandas()
print(df.head())
Carregar resultados no Polars
import polars as pl
cursor.execute("SELECT * FROM Production.Product")
table = cursor.arrow()
df = pl.from_arrow(table)
print(df)
Resultados da consulta com o DuckDB
O DuckDB pode consultar tabelas de seta diretamente em SQL sem copiar dados. Essa funcionalidade é útil quando você precisa de análise no estilo SQL em conjuntos de resultados que já estão no formato Arrow.
import duckdb
cursor.execute("SELECT * FROM Sales.SalesOrderHeader")
arrow_table = cursor.arrow()
# Query the Arrow table with DuckDB SQL
result = duckdb.sql("SELECT CustomerID, SUM(TotalDue) FROM arrow_table GROUP BY CustomerID")
print(result.fetchall())
Faça streaming de grandes conjuntos de resultados em Parquet
Para conjuntos de resultados grandes, transmita lotes do Arrow diretamente para um arquivo Parquet sem carregar todo o conjunto de dados na memória. O ParquetWriter grava cada lote incrementalmente.
import pyarrow.parquet as pq
cursor.execute("SELECT * FROM Production.TransactionHistory")
reader = cursor.arrow_reader(batch_size=100000)
# Write streaming batches to a Parquet file
writer = None
for batch in reader:
if writer is None:
writer = pq.ParquetWriter("output.parquet", batch.schema)
writer.write_batch(batch)
if writer:
writer.close()
Exportação para outros formatos
O PyArrow fornece gravadores integrados para CSV e o formato de arquivo IPC Arrow (também conhecido como Feather V2). Arquivos Arrow IPC preservam exatamente os tipos Arrow e são rápidos de ler novamente.
import pyarrow as pa
import pyarrow.csv as pcsv
cursor.execute("SELECT * FROM Production.Product")
table = cursor.arrow()
# Write to CSV
pcsv.write_csv(table, "products.csv")
# Write to an Arrow IPC file
with pa.ipc.new_file("products.arrow", table.schema) as writer:
writer.write_table(table)
Carregar os dados do Arrow no SQL Server
O cursor.bulkcopy_arrow() método grava dados Arrow em uma tabela sem convertê-los para tuplas de linha em Python primeiro. O source argumento aceita qualquer um dos seguintes pontos:
Um
pyarrow.Table.Um
pyarrow.RecordBatch.Um
pyarrow.RecordBatchReader, incluindo o leitor retornado porcursor.arrow_reader().Qualquer objeto que exponha a interface de dados C do Arrow por meio de
__arrow_c_stream__ou__arrow_c_array__.
import mssql_python
import pyarrow as pa
conn = mssql_python.connect(connection_string)
# bulkcopy_arrow() opens its own connection, so commit the table creation first.
conn.autocommit = True
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE ##SensorArchive (
SensorID int NOT NULL,
Reading float NULL,
Location nvarchar(50) NULL
)
""")
table = pa.table({
"SensorID": pa.array([1, 2, 3], type=pa.int32()),
"Reading": pa.array([20.5, None, 22.1], type=pa.float64()),
"Location": pa.array(["Plant A", "Plant B", None], type=pa.string()),
})
result = cursor.bulkcopy_arrow("##SensorArchive", table)
print(f"Copied {result['rows_copied']} rows in {result['batch_count']} batches")
Valores nulos de setas são escritos como valores NULL em SQL.
Transmita um conjunto de resultados para outra tabela
Como bulkcopy_arrow() aceita um leitor, você pode mover um grande conjunto de resultados entre tabelas sem materializá-lo na memória:
cursor.execute("""
CREATE TABLE ##ProductArchive (
ProductID int NOT NULL,
Name nvarchar(50) NOT NULL,
ListPrice money NOT NULL
)
""")
cursor.execute("SELECT ProductID, Name, ListPrice FROM Production.Product")
with cursor.arrow_reader(batch_size=100000) as reader:
result = cursor.bulkcopy_arrow("##ProductArchive", reader, batch_size=100000)
print(f"Copied {result['rows_copied']} rows")
Corresponder os tipos de seta às colunas de destino
O Arrow writer exige que cada tipo de coluna Arrow seja compatível com seu tipo de coluna SQL de destino. Ele não converte entre famílias, então um erro de incompatibilidade é gerado ValueError antes que qualquer linha seja gravada:
ValueError: Cannot map Arrow column 'ListPrice' (Float64) to SQL column 'ListPrice'
(Money): Usage Error: type combination is not supported by the Arrow row-major writer
Use os mapeamentos em Mapeamentos de tipos de dados na ordem inversa para escolher o tipo Seta.
Colunas monetárias, decimais e numéricas precisam de decimal128, não float64. Os dados lidos de volta com cursor.arrow() já vêm com os tipos corretos, portanto uma tabela lida do SQL Server é carregada em uma tabela correspondente sem conversão.
Mapear colunas por nome
Quando a ordem das colunas Arrow não corresponder à da tabela de destino, passe column_mappings com os nomes das colunas de destino na ordem das colunas Arrow:
from decimal import Decimal
table = pa.table({
"Name": pa.array(["Widget"], type=pa.string()),
"ProductID": pa.array([9001], type=pa.int32()),
"ListPrice": pa.array([Decimal("12.34")], type=pa.decimal128(19, 4)),
})
cursor.bulkcopy_arrow(
"##ProductArchive",
table,
column_mappings=["Name", "ProductID", "ListPrice"],
)
O método aceita as mesmas opções que cursor.bulkcopy(), incluindo batch_size, timeout, keep_identity, table_lock, e keep_nulls. Para mais informações sobre essas opções, veja Cópia em massa.
Note
Passar uma fonte de Flecha para cursor.bulkcopy() eleva TypeError e direciona você para cursor.bulkcopy_arrow().
Mapeamentos de tipo de dados
Os métodos de busca do Arrow mapeiam os tipos SQL da Microsoft para tipos do Arrow no nível de C++.
| Tipo SQL do Microsoft | Tipo de seta |
|---|---|
| int, smallint, tinyint, bigint |
int32, int16, int8, int64 |
| ponto flutuante, real |
float64, float32 |
| decimal, numérico | decimal128 |
| bit | bool |
| char, varchar, nchar, nvarchar | utf8 |
| texto, ntext | large_utf8 |
| binário, varbinário |
binary, large_binary |
| date | date32 |
| Tempo | time64[us] |
| datetime, datetime2, smalldatetime | timestamp[us] |
| datetimeoffset | timestamp[us, tz=UTC] |
| uniqueidentifier |
utf8 (corda maiúscula) |
| xml | utf8 |
Note
O driver converte o datetimeoffset tipo para UTC porque as colunas de seta exigem um fuso horário fixo. O driver normaliza informações de fuso horário por célula do Microsoft SQL para UTC durante a conversão.
O sql_variant tipo não é suportado pelos métodos Arrow fetch e gera uma exceção de tipo de dado não suportado. Use o padrão fetchone(), fetchmany(), ou fetchall() para consultas que retornam sql_variant colunas.
Considerações sobre desempenho
Métodos de busca por seta são os mais rápidos para análises e operações de dados em massa, enquanto métodos padrão de cursor são mais adequados para padrões transacionais com conjuntos de resultados pequenos.
Quando usar Arrow em vez do fetch padrão
| Scenario | Abordagem recomendada |
|---|---|
| Busque algumas linhas para exibir | fetchone() / fetchall() |
| Carregar dados no pandas ou no Polars | cursor.arrow() |
| Processar grandes conjuntos de dados em blocos | cursor.arrow_reader() |
| Consultas de uma única linha ou pequenos conjuntos de resultados | fetchone() / fetchval() |
| Pipelines de análise ou de agregação |
cursor.arrow() + Polars/DuckDB |
| Gravar resultados em Parquet ou Arrow IPC |
cursor.arrow_reader() + PyArrow I/O |
Gerenciamento de memória para grandes conjuntos de dados
Para conjuntos de resultados que possam exceder a memória disponível, use arrow_reader() com um batch_size razoável.
cursor.execute("SELECT * FROM Production.TransactionHistory")
# Process in batches of 100K rows
reader = cursor.arrow_reader(batch_size=100000)
total_rows = 0
for batch in reader:
# Work with each batch individually
total_rows += batch.num_rows
# batch goes out of scope and memory is freed
print(f"Processed {total_rows} rows")
Ajustar tamanho do lote
O batch_size parâmetro controla quantas linhas são buscadas em cada lote. O tamanho ideal depende da largura da sua linha e da memória disponível. Linhas mais largas com colunas grandes como nvarchar(max) ou varbinary(max) se beneficiam de tamanhos de lote menores, enquanto fileiras estreitas se beneficiam de tamanhos maiores.
- Padrão (8192): Bom equilíbrio para a maioria das cargas de trabalho.
- Menor (1000-5000): Use para tabelas amplas com colunas largas.
- Maior (50000-100000): Use para tabelas estreitas ou quando a taxa de transferência importa mais do que a memória.
# Narrow table with many rows - use larger batches
cursor.execute("SELECT ProductID, ListPrice FROM Production.Product")
table = cursor.arrow(batch_size=100000)
# Wide table with LOB columns - use smaller batches
cursor.execute("SELECT * FROM Production.Document")
table = cursor.arrow(batch_size=1000)