带有pd.read_sql_query()调用的DBAPI语法

时间:2018-01-01 21:32:50

标签: python pandas sqlite

我想将数据库中包含的所有表读入pandas数据帧。 This回答了我想要完成的任务,但我想使用?而不是%s的{​​{3}}的DBAPI语法。但是,我遇到了一个错误。我认为documentation可以解决这个问题,但我现在发布自己的问题,因为我无法弄明白。

最小例子

import pandas as pd
import sqlite3

pd.__version__  # 0.19.1
sqlite3.version  # 2.6.0

excon = sqlite3.connect('example.db')
c = excon.cursor()
c.execute('''CREATE TABLE stocks
         (date text, trans text, symbol text, qty real, price real)''')
c.execute("INSERT INTO stocks VALUES ('2006-01-05', 'BUY', 'RHAT', 100, 35.14)")
c.execute('''CREATE TABLE bonds
         (date text, trans text, symbol text, qty real, price real)''')
c.execute("INSERT INTO bonds VALUES ('2015-01-01', 'BUY', 'RSOCK', 90, 23.11)")

data = pd.read_sql_query('SELECT * FROM stocks', excon)
# >>> data
#          date trans symbol    qty  price
# 0  2006-01-05   BUY   RHAT  100.0  35.14

但是当我在下面添加?(?)时,收到错误消息pandas.io.sql.DatabaseError: Execution failed on sql 'SELECT * FROM (?)': near "?": syntax error

问题代码

c.execute("SELECT name FROM sqlite_master WHERE type='table';")
tables = c.fetchall()
# >>> tables
# [('stocks',), ('bonds',)]
table = tables[0]

data = pd.read_sql_query("SELECT * FROM ?", excon, params=table)

我可能缺少一些微不足道的东西,但我没有看到它!

2 个答案:

答案 0 :(得分:1)

问题在于您尝试对表名使用参数替换,这是不可能的。有一个issue on GitHub可以讨论这个问题。相关部分位于主题的最后,在@jorisvandenbossche的评论中:

  

表名AFAIK无法进行参数替换。

     

问题是,在sql中,字符串之间经常存在差异   引用和变量引用(参见例如   https://sqlite.org/lang_keywords.html报价的区别   字符串和标识符之间)。所以你要填写一个字符串   是为了sql其他东西作为变量名称(在这种情况下是一个表   名称)。

答案 1 :(得分:0)

参数替换对于防止 SQL 注入来自不安全的用户输入值至关重要。

在这个特定示例中,您直接从数据库自己的元数据中获取表名,这已经是安全的,因此可以仅使用普通字符串格式来构造查询,但将表名括在引号中仍然很好。< /p>

如果您要获取用户输入的表名,您也可以先参数化它们,然后再将它们用于普通的 Python 字符串格式。

例如

# assume this is user-entered:
table = '; select * from members; DROP members --'

c.execute("SELECT name FROM sqlite_master WHERE type='table' and name = ?;", excon, params=table )
tables = c.fetchall()

在这种情况下,用户输入了一些旨在造成破坏的恶意输入,参数化查询将清除它,查询将不返回任何行。 如果用户输入了一个干净的表格,例如table = 'stocks' 那么上面的查询将通过清洗将相同的名称返回给您,现在它是安全的。

然后可以继续正常的python字符串格式化,在这种情况下使用f-string样式:

table = tables[0]

data = pd.read_sql_query(f"""SELECT * FROM "{table}" ;""", excon)

回到你原来的例子,我上面的第一步是完全没有必要的。我只是提供了它的上下文。这是不必要的,因为没有用户输入,所以你可以做这样的事情来获取每个表的数据框字典。

c.execute("SELECT name FROM sqlite_master WHERE type='table';")
tables = c.fetchall()
# >>> tables
# [('stocks',), ('bonds',)]


dfs = dict()
for t in tables:
    dfs[t] = pd.read_sql_query(f"""SELECT * FROM "{t}" ;""", excon)

然后您可以使用表名作为键从字典中获取数据框。