El bucle de PostgreSQL a DataFrame, para mi aplicación Dash, no funciona como se esperaba

Sep 18 2020

Estoy intentando crear un DataFrame para mi aplicación Dash. El marco de datos consta de datos del mercado de valores tomados de mi dB SQL. symbolses una lista de acciones ingresadas por el usuario, para esta publicación di algunos ejemplos.

ACTUALIZACIÓN: Con la ayuda de @Parfait, este es el código que estoy usando, pero todavía no parece funcionar incluso después de superar errores / errores anteriores.

@app.callback(
dash.dependencies.Output('table_stats', 'data'),
[dash.dependencies.Input('dynamic-dropdown', 'value')])


def statsTable(symbols):
    if symbols == None:
        raise PreventUpdate
    
    placeholders = ", ".join(['%s' for _ in symbols])
    
    # PREPARED STATEMENT WITH PARAM PLACEHOLDERS
    sql = f"""SELECT id, companyname, marketcap
                     , to_char(100.0*week52change,'999D99%'), week52high, week52low
                     , to_char(dividend_yield * 100, '99D99%'), next_earnings_date
                     , pe_ratio, ROUND(beta,2) 
              FROM security_stats 
              WHERE security_stats.id IN ({placeholders})
           """

    print(sql)
    final_df = postgresql_to_dataframe_v1(conn, sql, symbols, stats_col)

    

 

    return dt.DataTable(data=final_df.to_dict('rows'), columns=stats_col)

Aquí está mi nueva función postgresql_to_dataframe_v1 ():

def postgresql_to_dataframe_v1(conn, select_query, param, column_names):
    """
    Tranform a SELECT query into a pandas dataframe
    """
    cursor = conn.cursor()
    try:
        cursor.execute(select_query, param)             # ADJUSTED LINE
    except (Exception, psycopg2.DatabaseError) as error:
        print(f"Error: {error}")                          # MODERNIZED STRING FORMAT
        cursor.close()
        return 1
    
    # Naturally we get a list of tupples
    tupples = cursor.fetchall()
    cursor.close()
    
    # We just need to turn it into a pandas dataframe
    df = pd.DataFrame(tupples, columns=column_names)
    return df

Ahora tengo este error: "Índice de lista fuera de rango", que creo que tiene que ver con el parámetro pasado o los símbolos porque los datos no se obtienen correctamente, el error proviene de la función.

este es el mensaje que recibo en mi terminal, tenga en cuenta que utilizo print(symbols)y print(sql)al inicio de la función, por lo que ve ['VFC', 'EXPD', 'COG', 'O'] y la consulta SQL en el terminal.

Respuestas

1 Parfait Sep 18 2020 at 05:27

Básicamente, está utilizando una cadena literal sin comillas. Postgres lee su consulta de la siguiente manera, donde fbse supone que la entidad es un identificador de columna.

SELECT id, companyname, marketcap
      , to_char(100.0*week52change,'999D99%'), week52high, week52low
      , to_char(dividend_yield * 100, '99D99%'), next_earnings_date
      , pe_ratio, ROUND(beta,2) 
FROM security_stats 
WHERE security_stats.id = fb   -- should be 'fb'

Sin embargo, no agregue comillas simples en su cadena F de Python. Considere la parametrización, especialmente si los usuarios definen tickers de acciones (¡de lo contrario, un usuario inteligente y malicioso puede Bobby Tables con usted!). Específicamente, agregue un nuevo parámetro a la función y pase la stockvariable a través de un bucle.

def postgresql_to_dataframe(conn, select_query, param, column_names):
    """
    Tranform a SELECT query into a pandas dataframe
    """
    cursor = conn.cursor()
    try:
        cursor.execute(select_query, [param])      # ADJUSTED LINE IF param IS SCALAR
    except (Exception, psycopg2.DatabaseError) as error:
        print(f"Error: {error}")                   # MODERNIZED STRING FORMAT
        cursor.close()
        return 1
    
    # Naturally we get a list of tupples
    tupples = cursor.fetchall()
    cursor.close()
    
    # We just need to turn it into a pandas dataframe
    df = pd.DataFrame(tupples, columns=column_names)
    return df


for stock in symbols:
    # PREPARED STATEMENT WITH PARAM PLACEHOLDER
    sql = """SELECT id, companyname, marketcap
                     , to_char(100.0*week52change,'999D99%%'), week52high, week52low
                     , to_char(dividend_yield * 100, '99D99%%'), next_earnings_date
                     , pe_ratio, ROUND(beta,2) 
             FROM security_stats 
             WHERE security_stats.id = %s
          """

    df = postgresql_to_dataframe(conn, sql, stock, col)
    df.head()

Y para un marco de datos completamente compilado usando su función, cree una lista de marcos de datos y concatene.

df_list = [postgresql_to_dataframe(conn, sql, stock, col) for stock in symbols]

final_df = pd.concat(df_list)

Pero sería mejor usar una INcláusula para una sola llamada de consulta SQL. Nota a continuación F-string se usa solo para interpolar los %smarcadores de posición a la declaración SQL, pero luego se vincula symbolsmediante la parametrización.

placeholders = ", ".join(['%s' for _ in symbols])

# PREPARED STATEMENT WITH PARAM PLACEHOLDERS
sql = f"""SELECT id, companyname, marketcap
                 , to_char(100.0*week52change,'999D99%%'), week52high, week52low
                 , to_char(dividend_yield * 100, '99D99%%'), next_earnings_date
                 , pe_ratio, ROUND(beta,2) 
          FROM security_stats 
          WHERE security_stats.id IN ({placeholders})
       """

final_df = postgresql_to_dataframe(conn, sql, symbols, col)

# ALTERNATIVELY, WITH PANDAS METHODS
final_df = (pd.read_sql(sql, conn, params=symbols)
              .reindex(col, axis='columns'))