O python facilita muito trabalhar com processos de ingestão de dados em tabelas no Sql Server usando o módulo pyodbc, conforme abaixo:
# -*- coding: utf-8 -*-
import os
import dotenv
import pyodbc as po
from datetime import datetime
## Carrega os valores do .env que contem os dados sensíveis de conexao
dotenv.load_dotenv()
## funcao de formacao da connString Sql
def strConnectionSql():
#variaveis de conexao SQL
server = os.getenv("SERVER_TARGET_SQL")
port = os.getenv("PORT_TARGET_SQL")
database = os.getenv("DATABASE_TARGET_SQL")
username = os.getenv("USERNAME_TARGET_SQL")
password = os.getenv("PASSWORD_TARGET_SQL")
strConnection = 'DRIVER={{ODBC Driver 17 for SQL Server}};\
SERVER={v_server};\
PORT={v_port};\
DATABASE={v_database};\
UID={v_username};\
PWD={v_password}'.format(v_server = server, v_port = port, v_database = database, v_username = username, v_password = password)
return strConnection
def gravaDadosSqlTable():
try:
## Connection string
connString = str(strConnectionSql())
cnxn = po.connect(connString)
cnxn.autocommit = False
cursor = cnxn.cursor()
RowCount = 0
## sql insert
sqlcmd = """
INSERT INTO [dbo].[Cliente]
(
[Id], [Codigo], [Cidade], [Estado]
) VALUES (?, ?, ?, ?);
"""
listSqlSource = [ (1, 'A12345', 'São Paulo', 'SP'),
(2, 'A12346', 'São Paulo', 'SP'),
(3, 'A12347', 'Campinas', 'SP'),
(4, 'B12348', 'Curitiba', 'PR'),
(5, 'C12349', 'Rio de Janeiro', 'RJ')
]
for params in listSqlSource:
cursor.execute(sqlcmd, params)
RowCount = RowCount + cursor.rowcount
except Exception as e:
msgException = "Error: {0}".format(e)
msgLog = 'Fim inserção de dados no destino [Erro]: {0}'.format(msgException)
print(msgLog)
cnxn.rollback()
else:
cnxn.commit()
finally:
## Close the database connection
cursor.close()
del cursor
cnxn.close()
msgLog = 'Quantidade de Registros Inseridos no destino: {0}'.format(RowCount)
print(msgLog)
## funcao inicial criada para iniciar as chamadas das demais funcoes
def main():
## log do início da aplicacao
datahora = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
msgLog = '\n***** Início da aplicação: {0}'.format(datahora)
print(msgLog)
gravaDadosSqlTable()
## log do final da aplicacao
datahora = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
msgLog = '***** Final da aplicação: {0}'.format(datahora)
print(msgLog)
#### inicio da aplicacao ####
if __name__ == "__main__":
## chamada da função inicial
main()
Porém analisando questões de performance no processo de insert acima é identificado que ocorrem conversões implícitas no Sql Server, pois por padrão o pyodbc envia os dados no formato nvarchar, e caso suas tabelas foram criadas com tipos de dados int, char, varchar entre outros essa conversão afeta a performance no Sql Server.
Abaixo como é apresentado a instrução de insert no Sql Server:
<?query --
(@P1 nvarchar(1),@P2 nvarchar(6),@P3 nvarchar(9),@P4 nvarchar(2))
INSERT INTO [dbo].[Teste]
(
[Id], [Codigo],
[Cidade], [Estado]
) VALUES (@P1, @P2, @P3, @P4);
--?>
Pesquisando sobre como podemos enviar os dados ao Sql Server no formato de tipo de dado esperado pelas colunas, encontrei uma forma que acho que nem todos sabem, que é o uso de cursor.setinputsizes que permite definir os campos do insert conforme os tipos de dados de cada coluna da sua tabela de destino.
Aqui o link que encontrei essa informação: Clique aqui
Exemplo:
cursor.setinputsizes([( po.SQL_INTEGER), (po.SQL_VARCHAR, 10, 0), \
(po.SQL_VARCHAR, 60, 0), (po.SQL_CHAR, 2, 0)])
Abaixo o script python ajustado para envio dos dados no formato correto de cada coluna da tabela evitando conversão implícita:
# -*- coding: utf-8 -*-
import os
import dotenv
import pyodbc as po
from datetime import datetime
## Carrega os valores do .env que contem os dados sensíveis de conexao
dotenv.load_dotenv()
## funcao de formacao da connString Sql
def strConnectionSql():
#variaveis de conexao SQL
server = os.getenv("SERVER_TARGET_SQL")
port = os.getenv("PORT_TARGET_SQL")
database = os.getenv("DATABASE_TARGET_SQL")
username = os.getenv("USERNAME_TARGET_SQL")
password = os.getenv("PASSWORD_TARGET_SQL")
strConnection = 'DRIVER={{ODBC Driver 17 for SQL Server}};\
SERVER={v_server};\
PORT={v_port};\
DATABASE={v_database};\
UID={v_username};\
PWD={v_password}'.format(v_server = server, v_port = port, v_database = database, v_username = username, v_password = password)
return strConnection
def gravaDadosSqlTable():
try:
## Connection string
connString = str(strConnectionSql())
cnxn = po.connect(connString)
cnxn.autocommit = False
cursor = cnxn.cursor()
RowCount = 0
## sql insert
sqlcmd = """
INSERT INTO [dbo].[Cliente]
(
[Id], [Codigo], [Cidade], [Estado]
) VALUES (?, ?, ?, ?);
"""
listSqlSource = [ (1, 'A12345', 'São Paulo', 'SP'),
(2, 'A12346', 'São Paulo', 'SP'),
(3, 'A12347', 'Campinas', 'SP'),
(4, 'B12348', 'Curitiba', 'PR'),
(5, 'C12349', 'Rio de Janeiro', 'RJ')
]
## define os tipos de dados de inputs no sql server
# isso evita que o Sql Server realize conversão implícita que afeta a performance
# foi necessário devido o pyodbc por padrão mandar dados no format nvarchar causando a conversão implícita
cursor.setinputsizes([( po.SQL_INTEGER), (po.SQL_VARCHAR, 10, 0), \
(po.SQL_VARCHAR, 60, 0), (po.SQL_CHAR, 2, 0)])
for params in listSqlSource:
cursor.execute(sqlcmd, params)
RowCount = RowCount + cursor.rowcount
except Exception as e:
msgException = "Error: {0}".format(e)
msgLog = 'Fim inserção de dados no destino [Erro]: {0}'.format(msgException)
print(msgLog)
cnxn.rollback()
else:
cnxn.commit()
finally:
## Close the database connection
cursor.close()
del cursor
cnxn.close()
msgLog = 'Quantidade de Registros Inseridos no destino: {0}'.format(RowCount)
print(msgLog)
## funcao inicial criada para iniciar as chamadas das demais funcoes
def main():
## log do início da aplicacao
datahora = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
msgLog = '\n***** Início da aplicação: {0}'.format(datahora)
print(msgLog)
gravaDadosSqlTable()
## log do final da aplicacao
datahora = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
msgLog = '***** Final da aplicação: {0}'.format(datahora)
print(msgLog)
#### inicio da aplicacao ####
if __name__ == "__main__":
## chamada da função inicial
main()
Abaixo como está a execução do insert no Sql Server:
<?query --
(@P1 int,@P2 varchar(6),@P3 varchar(9),@P4 char(2))
INSERT INTO [dbo].[Teste]
(
[Id], [Codigo],
[Cidade], [Estado]
) VALUES (@P1, @P2, @P3, @P4);
--?>
Inicialmente pode não parecer que isso faz alguma diferença, porém uma comparação simples dos planos de execução sem conversão implícita e com conversão implícita apresenta planos de execução diferentes e com menos custo de execução do Sql Server quando não ocorre a conversão, isso é válido tanto para instruções de insert, quanto select, update e delete.
Documentação oficial pyodbc: https://github.com/mkleehammer/pyodbc/wiki/Cursor#setinputsizeslist_of_value_tuples
Artigo detalhado sobre conversão implícita: https://portosql.wordpress.com/2018/10/25/os-perigos-da-conversao-implicita-1/
Bom, por enquanto é isso. Espero que ajude a mais alguém.
Fiquem com Deus.
Top comments (0)