Accessibilité de la base de données IRIS avec ODBC ou JDBC à l'aide de Python
Problèmes de chaînes
J'utilise Python pour accéder aux bases de données IRIS avec JDBC (ou ODBC). Je veux récupérer les données dans pandas dataframe pour manipuler les données et créer des graphiques à partir de celles-ci. Lors de l'utilisation de JDBC, j'ai rencontré un problème avec la gestion des chaînes. Cet article est destiné à aider les personnes qui ont les mêmes problèmes. S'il existe un moyen plus simple de résoudre ce problème, faites-le moi savoir dans les commentaires !
J'utilise OSX, donc je ne sais pas à quel point mon problème est unique. J'utilise Jupyter Notebooks, mais le code serait généralement le même si vous utilisiez n'importe quel autre programme ou cadre en Python.
Le problème JDBC
Lorsque je récupère des données de la base de données, les descriptions de colonnes et toutes les données de type chaîne sont renvoyées en tant que données de type java.lang.String. Si vous imprimez une chaîne de données, elle se présentera sous la forme suivante : "(p,a,i,n,i,n,t,h,e,r,e,a,r)" au lieu de l'attendu "painintherear".
Ceci est probablement dû au fait que les chaînes de caractères de type java.lang.String se présentent sous la forme d'un itérable ou d'un tableau lorsqu'elles sont récupérées à l'aide de JDBC. Cela peut arriver si le pont Python-Java que vous utilisez (par exemple, JayDeBeApi, JDBC) ne convertit pas automatiquement java.lang.String en une chaîne Python en une seule étape.
La représentation de la chaîne str de Python, en revanche, dispose de la chaîne entière en tant qu'unité unique. Lorsque Python récupère une chaîne normale (par exemple via ODBC), elle n'est pas divisée en caractères individuels.
La solution JDBC
Pour résoudre ce problème, vous devez vous assurer que le type java.lang.String est correctement converti en type str de Python. Vous pouvez explicitement gérer cette conversion lors du traitement des données récupérées afin qu'elles ne soient pas interprétées comme un itérable ou une liste de caractères.
Il existe de nombreuses façons de manipuler les chaînes de caractères ; c'est ce que j'ai fait.
import pandas as pd
import pyodbc
import jaydebeapi
import jpype
def my_function(jdbc_used)
# Un autre code pour créer la connexion se trouve ici
cursor.execute(query_string)
if jdbc_used:
# Récupération des résultats, conversion des données java.lang.String en str Python
# (java.lang.String est retourné comme suit : "(p,a,i,n,i,n,t,h,e,r,e,a,r)" Conversion en type str "painintherear"
results = []
for row in cursor.fetchall():
converted_row = [str(item) if isinstance(item, jpype.java.lang.String) else item for item in row]
results.append(converted_row)
# Obtention des noms des colonnes et vérification qu'il s'agit bien de chaînes Python
column_names = [str(col[0]) for col in cursor.description]
# Création du cadre de données
df = pd.DataFrame.from_records(results, columns=column_names)
# Vérification des résultats
print(df.head().to_string())
else:
# Je testais aussi ODBC
# Dans le cas d'ensembles de résultats très volumineux, obtenez les résultats par blocs en utilisant cursor.fetchmany() ou fetchall()
results = cursor.fetchall()
# Obtention des noms des colonnes
column_names = [column[0] for column in cursor.description]
# Création du cadre de données
df = pd.DataFrame.from_records(results, columns=column_names)
# Faites des choses avec votre cadre de données
Le problème ODBC
Lors d'une connexion ODBC, les chaînes de caractères ne sont pas renvoyées ou sont S.O.
Si vous vous connectez à une base de données qui contient des données Unicode (par exemple, des noms dans des langues différentes) ou si votre application doit stocker ou récupérer des caractères qui ne sont pas ASCII, vous devez vous assurer que les données restent correctement encodées lorsqu'elles sont transmises entre la base de données et votre application Python.
La solution ODBC
Ce code garantit que les données de type chaîne sont encodées et décodées en utilisant UTF-8 lors de l'envoi et de la récupération de données dans la base de données. C'est particulièrement important lorsqu'il s'agit de caractères non ASCII ou d'assurer la compatibilité avec les données Unicode.
def create_connection(connection_string, password):
connection = None
try:
# print(f"Connecting to {connection_string}")
connection = pyodbc.connect(connection_string + ";PWD=" + password)
# Veiller à ce que les chaînes de caractères soient lues correctement
connection.setdecoding(pyodbc.SQL_CHAR, encoding="utf8")
connection.setdecoding(pyodbc.SQL_WCHAR, encoding="utf8")
connection.setencoding(encoding="utf8")
except pyodbc.Error as e:
print(f"The error '{e}' occurred")
return connection
connection.setdecoding(pyodbc.SQL_CHAR, encoding="utf8")
Désigné ci-dessus indique à pyodbc comment décoder les données de caractères de la base de données lors de la récupération des types SQL_CHAR (typiquement, les champs de caractères de longueur fixe).
connection.setdecoding(pyodbc.SQL_WCHAR, encoding="utf8")
Désigné ci-dessus définit le décodage pour SQL_WCHAR, les types de caractères larges (c'est-à-dire les chaînes Unicode, telles que NVARCHAR ou NCHAR dans SQL Server).
connection.setencoding(encoding="utf8")
Désigné ci-dessus garantit que toutes les chaînes ou données de caractères envoyées de Python à la base de données seront encodées en UTF-8, cela signifie que Python traduira son type str interne (qui est Unicode) en octets UTF-8 lors de la communication avec la base de données.
La mise en place de l'ensemble
Installation de JDBC
Installation de JAVA-utiliser dmg
https://www.oracle.com/middleeast/java/technologies/downloads/#jdk23-mac
Mise à jour du shell pour définir la version par défaut
$ /usr/libexec/java_home -V
Matching Java Virtual Machines (2):
23 (arm64) "Oracle Corporation" - "Java SE 23" /Library/Java/JavaVirtualMachines/jdk-23.jdk/Contents/Home
1.8.421.09 (arm64) "Oracle Corporation" - "Java" /Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home
/Library/Java/JavaVirtualMachines/jdk-23.jdk/Contents/Home
$ echo $SHELL
/opt/homebrew/bin/bash
$ vi ~/.bash_profile
Ajoutez JAVA_HOME à votre chemin
export JAVA_HOME=$(/usr/libexec/java_home -v 23)
export PATH=$JAVA_HOME/bin:$PATH
Obtention du pilote JDBC
https://intersystems-community.github.io/iris-driver-distribution/
Placement du fichier jar quelque part... Je l'ai placé dans $HOME
$ ls $HOME/*.jar
/Users/myname/intersystems-jdbc-3.8.4.jar
Exemple de code
Cela suppose que vous avez configuré ODBC (un exemple à suivre un autre jour, le chien a mangé mes notes...).
Remarque : il s'agit d'un piratage de mon propre code réel. Notez les noms des variables.
import os
import datetime
from datetime import date, time, datetime, timedelta
import pandas as pd
import pyodbc
import jaydebeapi
import jpype
def jdbc_create_connection(jdbc_url, jdbc_username, jdbc_password):
# Chemin d'accès au pilote JDBC
jdbc_driver_path = '/Users/yourname/intersystems-jdbc-3.8.4.jar'
# Il faut s'assurer que JAVA_HOME est défini
os.environ['JAVA_HOME']='/Library/Java/JavaVirtualMachines/jdk-23.jdk/Contents/Home'
os.environ['CLASSPATH'] = jdbc_driver_path
# Démarrage de la JVM (si elle n'est pas déjà en cours d'exécution)
if not jpype.isJVMStarted():
jpype.startJVM(jpype.getDefaultJVMPath(), classpath=[jdbc_driver_path])
# Connexion aux bases de données:
connection = None
try:
connection = jaydebeapi.connect("com.intersystems.jdbc.IRISDriver",
jdbc_url,
[jdbc_username, jdbc_password],
jdbc_driver_path)
print("Connection successful")
except Exception as e:
print(f"An error occurred: {e}")
return connection
def odbc_create_connection(connection_string):
connection = None
try:
# print(f"Connecting to {connection_string}")
connection = pyodbc.connect(connection_string)
# Veiller à ce que les chaînes de caractères soient lues correctement
connection.setdecoding(pyodbc.SQL_CHAR, encoding="utf8")
connection.setdecoding(pyodbc.SQL_WCHAR, encoding="utf8")
connection.setencoding(encoding="utf8")
except pyodbc.Error as e:
print(f"The error '{e}' occurred")
return connection
# Paramètres
odbc_driver = "InterSystems ODBC"
odbc_host = "your_host"
odbc_port = "51773"
odbc_namespace = "your_namespace"
odbc_username = "username"
odbc_password = "password"
jdbc_host = "your_host"
jdbc_port = "51773"
jdbc_namespace = "your_namespace"
jdbc_username = "username"
jdbc_password = "password"
# Création d'une connexion et des graphiques
jdbc_used = True
if jdbc_used:
print("Using JDBC")
jdbc_url = f"jdbc:IRIS://{jdbc_host}:{jdbc_port}/{jdbc_namespace}?useUnicode=true&characterEncoding=UTF-8"
connection = jdbc_create_connection(jdbc_url, jdbc_username, jdbc_password)
else:
print("Using ODBC")
connection_string = f"Driver={odbc_driver};Host={odbc_host};Port={odbc_port};Database={odbc_namespace};UID={odbc_username};PWD={odbc_password}"
connection = odbc_create_connection(connection_string)
if connection is None:
print("Unable to connect to IRIS")
exit()
cursor = connection.cursor()
site = "SAMPLE"
table_name = "your.TableNAME"
desired_columns = [
"RunDate",
"ActiveUsersCount",
"EpisodeCountEmergency",
"EpisodeCountInpatient",
"EpisodeCountOutpatient",
"EpisodeCountTotal",
"AppointmentCount",
"PrintCountTotal",
"site",
]
# Construction de la partie de la requête relative à la sélection des colonnes
column_selection = ", ".join(desired_columns)
query_string = f"SELECT {column_selection} FROM {table_name} WHERE Site = '{site}'"
print(query_string)
cursor.execute(query_string)
if jdbc_used:
# Récupération des résultats
results = []
for row in cursor.fetchall():
converted_row = [str(item) if isinstance(item, jpype.java.lang.String) else item for item in row]
results.append(converted_row)
# Il faut récupérer les noms des colonnes et s'assurer qu'il s'agit bien de chaînes Python (java.lang.String is returned "(p,a,i,n,i,n,t,h,e,a,r,s,e)"
column_names = [str(col[0]) for col in cursor.description]
# Création du cadre de données
df = pd.DataFrame.from_records(results, columns=column_names)
print(df.head().to_string())
else:
# Dans le cas d'ensembles de résultats très volumineux, obtenez les résultats par blocs en utilisant cursor.fetchmany() ou fetchall()
results = cursor.fetchall()
# Obtention des noms des colonnes
column_names = [column[0] for column in cursor.description]
# Création du cadre de données
df = pd.DataFrame.from_records(results, columns=column_names)
print(df.head().to_string())
# # Construction des graphiques pour un site
# cf.build_7_day_rolling_average_chart(site, cursor, jdbc_used)
cursor.close()
connection.close()
# Arrêt de la JVM (si vous l'avez démarrée)
# jpype.shutdownJVM()