Estoy accediendo a bases de datos IRIS con JDBC (u ODBC) usando Python. Quiero recuperar los datos en un marco de datos pandas para manipular los datos y crear gráficos a partir de ellos. Me encontré con un problema con el manejo de cadenas mientras usaba JDBC. Esta publicación es para ayudar si alguien más tiene los mismos problemas. O, si hay una manera más fácil de resolver esto, ¡házmelo saber en los comentarios!
Estoy usando OSX, por lo que no estoy seguro de cuán exclusivo es mi problema. Estoy usando Jupyter Notebooks, aunque el código generalmente sería el mismo si usaras cualquier otro programa o marco de Python.
Cuando obtengo datos de la base de datos, las descripciones de columnas y cualquier dato de cadena se devuelven como tipo de datos java.lang.String. Si imprime datos de cadena, se verá así: "(p,a,i,n,i,n,t,h,e,r,e,a,r)" en lugar del esperado "painintherear".
Esto probablemente se debe a que las cadenas de caracteres del tipo de datos java.lang.String aparecen como un iterable o una matriz cuando se recuperan usando JDBC. Esto puede suceder si el puente Python-Java que está utilizando (por ejemplo, JayDeBeApi, JDBC) no convierte automáticamente java.lang.String en una cadena Python en un solo paso.
La representación de cadena str de Python, por el contrario, tiene la cadena completa como una sola unidad. Cuando Python recupera una cadena normal (por ejemplo, a través de ODBC), no se divide en caracteres individuales.
Para solucionar este problema, debe asegurarse de que java.lang.String se convierta correctamente al tipo str de Python. Puede manejar explícitamente esta conversión al procesar los datos obtenidos para que no se interpreten como un iterable o una lista de caracteres.
Hay muchas maneras de realizar esta manipulación de cadenas; esto es lo que hice.
import pandas as pd import pyodbc import jaydebeapi import jpype def my_function(jdbc_used) # Some other code to create the connection goes here cursor.execute(query_string) if jdbc_used: # Fetch the results, convert java.lang.String in the data to Python str # (java.lang.String is returned "(p,a,i,n,i,n,t,h,e,r,e,a,r)" Convert to str type "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) # Get the column names and ensure they are Python strings column_names = [str(col[0]) for col in cursor.description] # Create the dataframe df = pd.DataFrame.from_records(results, columns=column_names) # Check the results print(df.head().to_string()) else: # I was also testing ODBC # For very large result sets get results in chunks using cursor.fetchmany(). or fetchall() results = cursor.fetchall() # Get the column names column_names = [column[0] for column in cursor.description] # Create the dataframe df = pd.DataFrame.from_records(results, columns=column_names) # Do stuff with your dataframe
Cuando se utiliza una conexión ODBC, las cadenas no se devuelven o son NA.
Si se está conectando a una base de datos que contiene datos Unicode (por ejemplo, nombres en diferentes idiomas) o si su aplicación necesita almacenar o recuperar caracteres que no sean ASCII, debe asegurarse de que los datos permanezcan correctamente codificados cuando se pasan entre las bases de datos. base de datos y su aplicación Python.
Este código garantiza que los datos de cadena se codifiquen y decodifiquen utilizando UTF-8 al enviar y recuperar datos a la base de datos. Es especialmente importante cuando se trata de caracteres que no son ASCII o para garantizar la compatibilidad con datos Unicode.
def create_connection(connection_string, password): connection = None try: # print(f"Connecting to {connection_string}") connection = pyodbc.connect(connection_string ";PWD=" password) # Ensure strings are read correctly 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
conexión.setdecoding(pyodbc.SQL_CHAR, codificación="utf8")
Le indica a pyodbc cómo decodificar datos de caracteres de la base de datos al recuperar tipos SQL_CHAR (normalmente, campos de caracteres de longitud fija).
conexión.setdecoding(pyodbc.SQL_WCHAR, codificación="utf8")
Establece la decodificación para SQL_WCHAR, tipos de caracteres anchos (es decir, cadenas Unicode, como NVARCHAR o NCHAR en SQL Server).
conexión.setencoding(codificación="utf8")
Garantiza que cualquier cadena o dato de caracteres enviado desde Python a la base de datos se codificará utilizando UTF-8,
lo que significa que Python traducirá su tipo str interno (que es Unicode) a bytes UTF-8 cuando se comunique con la base de datos.
Instalar JAVA - usar dmg
https://www.oracle.com/middleeast/java/technologies/downloads/#jdk23-mac
Actualizar shell para configurar la versión predeterminada
$ /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
Agrega JAVA_HOME a tu ruta
export JAVA_HOME=$(/usr/libexec/java_home -v 23) export PATH=$JAVA_HOME/bin:$PATH
Obtener el controlador JDBC
https://intersystems-community.github.io/iris-driver-distribution/
Pon el archivo jar en algún lugar... Yo lo puse en $HOME
$ ls $HOME/*.jar /Users/myname/intersystems-jdbc-3.8.4.jar
Se supone que has configurado ODBC (un ejemplo para otro día, el perro se comió mis notas...).
Nota: este es un truco de mi código real. Tenga en cuenta los nombres de las 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): # Path to JDBC driver jdbc_driver_path = '/Users/yourname/intersystems-jdbc-3.8.4.jar' # Ensure JAVA_HOME is set os.environ['JAVA_HOME']='/Library/Java/JavaVirtualMachines/jdk-23.jdk/Contents/Home' os.environ['CLASSPATH'] = jdbc_driver_path # Start the JVM (if not already running) if not jpype.isJVMStarted(): jpype.startJVM(jpype.getDefaultJVMPath(), classpath=[jdbc_driver_path]) # Connect to the database 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) # Ensure strings are read correctly 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 # Parameters 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" # Create connection and create charts 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", ] # Construct the column selection part of the query 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: # Fetch the results 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) # Get the column names and ensure they are Python strings (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] # Create the dataframe df = pd.DataFrame.from_records(results, columns=column_names) print(df.head().to_string()) else: # For very large result sets get results in chunks using cursor.fetchmany(). or fetchall() results = cursor.fetchall() # Get the column names column_names = [column[0] for column in cursor.description] # Create the dataframe df = pd.DataFrame.from_records(results, columns=column_names) print(df.head().to_string()) # # Build charts for a site # cf.build_7_day_rolling_average_chart(site, cursor, jdbc_used) cursor.close() connection.close() # Shutdown the JVM (if you started it) # jpype.shutdownJVM()
Descargo de responsabilidad: Todos los recursos proporcionados provienen en parte de Internet. Si existe alguna infracción de sus derechos de autor u otros derechos e intereses, explique los motivos detallados y proporcione pruebas de los derechos de autor o derechos e intereses y luego envíelos al correo electrónico: [email protected]. Lo manejaremos por usted lo antes posible.
Copyright© 2022 湘ICP备2022001581号-3