Solucionar problemas de consulta, dados e operação com mssql-python

Use este artigo para diagnosticar problemas de execução de consultas, tipo de dado, desempenho, transação e cópia em massa com o mssql-python driver.

Problemas de execução de consultas

Tabela ou objeto não encontrado

Sintomas:

ProgrammingError: [42S02] (208) Invalid object name 'TableName'.

Possíveis causas e soluções:

  • Contexto incorreto do banco de dados

    cursor.execute("SELECT DB_NAME()")
    print(cursor.fetchone()[0])
    
  • Esquema não especificado

    cursor.execute("SELECT * FROM dbo.TableName")
    
  • A tabela não existe

    cursor.execute("""
        SELECT TABLE_NAME
        FROM INFORMATION_SCHEMA.TABLES
        WHERE TABLE_NAME = 'TableName'
    """)
    

Erro de sintaxe

Sintomas:

ProgrammingError: [42000] (102) Incorrect syntax near '...'.

Soluções:

  1. Teste a instrução SQL no SQL Server Management Studio (SSMS) para verificar a sintaxe.

  2. Use uma consulta parametrizada em vez de interpolação de strings:

    # Don't use string interpolation for query parameters.
    cursor.execute(f"SELECT * FROM Production.Product WHERE Name = '{name}'")
    
    # Use parameters.
    cursor.execute(
        "SELECT * FROM Production.Product WHERE Name = %(name)s",
        {"name": name},
    )
    

Erros de parâmetros

Sintomas:

ProgrammingError: [07001] Wrong number of parameters

Soluções:

  1. Conte os espaços reservados e os parâmetros. As contagens devem coincidir.

  2. Escolha o estilo de parâmetros correto:

    # Qmark style: positional parameters
    cursor.execute(
        "SELECT * FROM Production.Product "
        "WHERE ProductID = ? AND Name LIKE ?",
        (1, "Adjustable%"),
    )
    print(cursor.fetchone())
    
    # Pyformat style: named parameters
    cursor.execute(
        "SELECT * FROM Production.Product "
        "WHERE ProductID = %(id)s AND Name LIKE %(name)s",
        {"id": 1, "name": "Adjustable%"},
    )
    print(cursor.fetchone())
    

Contagens incorretas de linhas em lotes com múltiplos resultados

Se rowcount repetir a contagem da primeira instrução após nextset(), atualize o driver. Cada atualização bem-sucedida nextset() atualiza a contagem do novo resultado. Um resultado que produz linhas começa em -1 antes de buscar. Verifique cursor.description antes de recuperar porque os resultados de modificação de dados contêm contagens em vez de linhas.

Deixe SET NOCOUNT desativado quando precisar de resultados de linhas afetadas. Para um exemplo completo, veja Rastreie cada resultado em um lote.

Questões com tipos de dados

Erros de conversão de data e hora

Sintomas:

DataError: [22007] Invalid datetime format

Solution:

Use objetos Python datetime em vez de strings.

from datetime import datetime

cursor.execute("CREATE TABLE #Events (EventDate DATETIME)")

# This value raises an error because the date is invalid.
try:
    cursor.execute(
        "INSERT INTO #Events (EventDate) VALUES (%(event_date)s)",
        {"event_date": "2024-13-45"},
    )
except Exception as e:
    print(f"Expected error: {e}")

# Use a Python datetime object.
cursor.execute(
    "INSERT INTO #Events (EventDate) VALUES (%(event_date)s)",
    {"event_date": datetime(2024, 3, 15)},
)
cursor.execute("SELECT EventDate FROM #Events")
print(cursor.fetchone())

O construtor de timestamp requer um argumento de microssegundos

Se Timestamp(year, month, day, hour, minute, second) levantar TypeError por um argumento ausente microsecond , atualize mssql-python. O driver atual aceita o construtor DB-API de seis argumentos e define microsecond como zero por padrão. Para mais informações, veja DB-API construtor de carimbo de tempo.

Questões de precisão decimal

Sintomas:

Os números parecem truncados ou arredondados incorretamente.

Solution:

decimal.Decimal Use para valores numéricos precisos:

from decimal import Decimal

cursor.execute("CREATE TABLE #PriceDemo (ListPrice DECIMAL(10, 2))")
cursor.execute(
    "INSERT INTO #PriceDemo (ListPrice) VALUES (%(list_price)s)",
    {"list_price": Decimal("19.99")},
)

Problemas de codificação Unicode

Sintomas:

Caracteres especiais aparecem distorcidos ou causam erros.

Soluções:

  1. Use colunas nvarchar para armazenar dados Unicode em seu banco de dados.

  2. Passe as cordas diretamente. O driver cuida da codificação:

    cursor.execute("CREATE TABLE #UnicodeDemo (Name NVARCHAR(50))")
    cursor.execute(
        "INSERT INTO #UnicodeDemo (Name) VALUES (%(name)s)",
        {"name": "日本語"},
    )
    cursor.execute("SELECT Name FROM #UnicodeDemo")
    print(cursor.fetchone())
    

Problemas de desempenho

Execução lenta da consulta

Possíveis causas e soluções:

  • Índices faltando: Verifique o plano de execução da consulta no SSMS.

  • Grandes conjuntos de resultados: Use fetchmany() em vez de fetchall():

    cursor.arraysize = 1000
    while True:
        rows = cursor.fetchmany()
        if not rows:
            break
        process_rows(rows)
    
  • Agrupamento de conexões desativado: Habilitar agrupamento:

    import mssql_python
    
    mssql_python.pooling(max_size=20, idle_timeout=300)
    

Problemas de memória com resultados grandes

Sintomas:

O processo em Python fica sem memória.

Soluções:

  1. Transmita os resultados em fluxo, em vez de carregar todas as linhas na memória.

    cursor.execute("SELECT * FROM LargeTable")
    for row in cursor:
        process_row(row)
    
  2. Use paginação do lado do servidor.

    page_size = 1000
    offset = 0
    
    while True:
        cursor.execute(
            "SELECT * FROM LargeTable ORDER BY ID "
            "OFFSET ? ROWS FETCH NEXT ? ROWS ONLY",
            (offset, page_size),
        )
        rows = cursor.fetchall()
        if not rows:
            break
        process_rows(rows)
        offset += page_size
    

Questões de transação

Escopo de tabelas temporárias com confirmação automática

Tabelas temporárias de sessão (#tablename) que você cria em uma transação desaparecem quando a transação é revertida. Esse comportamento geralmente causa confusão quando o autocommit está desligado, que é o padrão:

conn = mssql_python.connect(connection_string)
cursor = conn.cursor()

cursor.execute("CREATE TABLE #TempData (ID INT, Name NVARCHAR(50))")
cursor.execute("INSERT INTO #TempData VALUES (1, 'test')")

# An explicit rollback or an error removes #TempData.
conn.rollback()

# This statement fails with "Invalid object name '#TempData'".
cursor.execute("SELECT * FROM #TempData")

Faça um commit imediatamente após criar uma tabela temporária ou use o modo autocommit:

cursor.execute("CREATE TABLE #TempData (ID INT, Name NVARCHAR(50))")
conn.commit()

cursor.execute("INSERT INTO #TempData VALUES (1, 'test')")
conn.commit()

Instruções DDL que exigem modo autocommit, como CREATE DATABASE, falham dentro de uma transação aberta. Defina o autocommit antes de executá-los:

conn.autocommit = True
cursor.execute("CREATE DATABASE TestDB")
conn.autocommit = False

Transação não comprometida

Sintomas:

As mudanças nos dados não persistem depois que você fecha a conexão.

Solution:

Com autocommit=False, que é o padrão, chame commit():

cursor.execute("CREATE TABLE #Products (Name NVARCHAR(100))")
cursor.execute(
    "INSERT INTO #Products (Name) VALUES (%(name)s)",
    {"name": "Widget"},
)
conn.commit()

Alternativamente, use o modo autocommit:

conn = mssql_python.connect(connection_string, autocommit=True)

Erros de deadlock

Sintomas:

OperationalError: [40001] (1205) Transaction ... was deadlocked on lock resources with another process

Solution:

A lógica de repetição lida com a falha imediata, mas bloqueios recorrentes indicam um problema de projeto. Capture o grafo de deadlock e analise as instruções e os tipos de bloqueio. As correções comuns incluem estas mudanças:

  • Reordene as operações para que transações concorrentes adquiram bloqueios na mesma sequência.
  • Reduza o escopo da transação.
  • Adicione índices apropriados para reduzir a duração do bloqueio.

Para uma análise completa de deadlocks, consulte o guia de deadlocks. Se você usar o Banco de Dados SQL do Azure, consulte Analisar e evitar impasses.

Problemas de carregamento em massa

Cópia em massa falha após uma atualização de pacote

Se consultas normais funcionarem, mas bulkcopy() ou bulkcopy_arrow() falhar ao importar ou carregar o módulo nativo de cópia em massa, atualize o driver com dependências:

python -m pip install --upgrade mssql-python
python -m pip check

O driver exige um runtime correspondente mssql-python-rs. Uma atualização parcial ou uma instalação com --no-deps pode deixar pacotes incompatíveis instalados. Se você usar um índice de pacotes privado, espelhe ambos os pacotes companheiros e preserve as fixações de versão da dependência declaradas pelo driver. Para as fixações atuais, veja Requisitos de dependência.

Nomes de destino não suportados

bulkcopy() e bulkcopy_arrow() aceitam no máximo três partes identificadoras. Um nome de quatro partes, como server.database.schema.table, gera RuntimeError durante a consulta de metadados antes de qualquer linha ser enviada. Conecte-se diretamente ao servidor de destino e use um nome de tabela de uma, duas ou três partes. Para escape de colchetes e erros de nomes malformados, veja Nomes da tabela de destino.

Unicode ou segundos fracionais perdidos durante cópia em massa

Atualize o driver se a cópia em massa corromper o texto UTF-8 ou truncar frações de segundo ao carregar valores datetime2 compatíveis. O runtime atual de cópia em massa lida com texto UTF-8 e preserva a precisão datetime2 disponível nos tipos de origem e destino.

Use nvarchar ou uma colação UTF-8 no varchar para texto Unicode. Os valores de Python datetime e os métodos de busca Arrow do driver são limitados a microssegundos. Para valores de 100 nanossegundos já mantidos em uma coluna de data/hora de nanossegundos do Arrow, carregue em datetime2(7) sem converter para Python datetime primeiro. Para mais informações, veja Unicode e precisão temporal e precisão temporal do Arrow.

Violações de restrições durante a cópia em massa

Sintomas:

RuntimeError: CHECK constraint ... Conflict occurred in database ...
RuntimeError: Cannot insert duplicate key ... violation of PRIMARY KEY constraint

Causa:

Os dados do seu lote violam restrições da tabela, como restrições de chave primária, de unicidade, de verificação ou de chave estrangeira.

Solution:

Valide os dados antes de carregá-los. Para grandes conjuntos de dados, carregue os dados em uma tabela de preparação e depois mescle-os à tabela de destino:

cursor.execute("CREATE TABLE ##Staging (ID INT, Name NVARCHAR(100))")
cursor.bulkcopy("##Staging", rows)

# Check for duplicate rows before the merge.
cursor.execute("""
    SELECT s.ID
    FROM ##Staging AS s
    INNER JOIN dbo.Target AS t
        ON s.ID = t.ID
""")
dupes = cursor.fetchall()
if dupes:
    print(f"Skipping {len(dupes)} duplicate rows")

# Insert rows that don't exist in the target.
cursor.execute("""
    INSERT INTO dbo.Target (ID, Name)
    SELECT s.ID, s.Name
    FROM ##Staging AS s
    WHERE NOT EXISTS (
        SELECT 1
        FROM dbo.Target AS t
        WHERE t.ID = s.ID
    )
""")
conn.commit()

Para padrões upsert com tabelas de preparação, consulte Padrões de carregamento e movimento de dados.

Erros de mapeamento de colunas

Sintomas:

RuntimeError: Bulk copy failure - column count mismatch

Causa:

O número de colunas nos seus dados não corresponde à contagem de colunas da tabela alvo, ou as colunas estão na ordem errada.

Solution:

Certifique-se de que seus dados correspondam ao esquema da tabela na ordem e contagem:

from decimal import Decimal

cursor.execute("""
    SELECT COLUMN_NAME, DATA_TYPE
    FROM INFORMATION_SCHEMA.COLUMNS
    WHERE TABLE_NAME = 'MyTable'
    ORDER BY ORDINAL_POSITION
""")
for col in cursor.fetchall():
    print(col)

rows = [
    (1, "Widget", Decimal("19.99")),
    (2, "Gadget", Decimal("29.99")),
]
cursor.bulkcopy("dbo.MyTable", rows)

Incompatibilidades de tipo durante a cópia em massa

Sintomas:

Os dados são carregados, mas os valores são truncados, arredondados ou incorretos.

Causa:

Os valores de Python não correspondem diretamente aos tipos de dados da coluna de destino. Exemplos comuns incluem float valores carregados em colunas decimais , que podem perder precisão, e strings sobredimensionadas carregadas em colunas de comprimento fixo.

Solution:

Use tipos em Python que combinem com seu esquema:

from decimal import Decimal

rows = [
    # Use Decimal for decimal and numeric columns.
    (1, "Widget", Decimal("19.99")),
    # Avoid float values because they can lose precision.
    # (1, "Widget", 19.99),
]
cursor.bulkcopy("dbo.Products", rows)

Falhas na vinculação de tipos do NumPy

Sintomas:

Os parâmetros falham silenciosamente ou geram erros de tipo de dados quando você usa tipos inteiros ou de ponto flutuante do NumPy.

Causa:

Tipos de NumPy como numpy.int64 e numpy.int32 não passam isinstance(x, int) no NumPy 2.x. A inferência de tipo do motorista não os reconhece, o que causa comportamentos inesperados.

Solution:

Converta valores do NumPy para tipos nativos de Python antes de vinculá-los:

import numpy as np

cursor.execute(
    "SELECT * FROM Production.Product WHERE ProductID = %(product_id)s",
    {"product_id": int(np.int64(42))},
)

for _, row in df.iterrows():
    cursor.execute(
        "INSERT INTO #Orders (ProductID, Qty) "
        "VALUES (%(product_id)s, %(qty)s)",
        {
            "product_id": int(row["ProductID"]),
            "qty": int(row["Qty"]),
        },
    )

Para conjuntos de dados maiores, use os caminhos de integração com o Arrow ou o pandas. Esses caminhos gerenciam a conversão de tipos internamente.

Cópia em massa com tabelas temporárias

Sintomas:

cursor.bulkcopy("#TempTable", data) gera RuntimeError: Invalid object name '#TempTable'.

Causa:

bulkcopy() Não é possível resolver tabelas temporárias de sessão (#tablename) por causa das limitações de busca de metadados. Tabelas temporárias globais (##tablename) e tabelas permanentes funcionam.

Solution:

Use uma tabela temporária global ou uma tabela de preparação regular:

# A global temp table is visible to all sessions and is dropped
# when the last session disconnects.
cursor.execute("CREATE TABLE ##Staging (ID INT, Name NVARCHAR(50))")
cursor.bulkcopy("##Staging", rows)

# Alternatively, use a permanent staging table.
cursor.execute("CREATE TABLE dbo.Staging (ID INT, Name NVARCHAR(50))")
cursor.bulkcopy("dbo.Staging", rows)

Para conjuntos de dados pequenos onde você prefere uma tabela temporária de sessão, use executemany():

cursor.execute("CREATE TABLE #Staging (ID INT, Name NVARCHAR(50))")
cursor.executemany(
    "INSERT INTO #Staging (ID, Name) VALUES (?, ?)",
    rows,
)