带有占位符的 dbExecute 不能与 RSQLite 一起可靠地工作
dbExecute with placeholders not working reliably with RSQLite
我有一个非常简单的 table,我想用 dbExecute()
更新某些行,但是占位符参数并不总是有效。
rows = dbExecute(con, "UPDATE rndid SET assigned = 'N', date = ?
WHERE id IN (?)",
params = list(NA, toString(tempIDs$id))
)
如果 tempIDs
中只有一行,上面的代码可以正常工作,但如果超过一行,它不会更新任何行,也不会抛出错误。
如果我只是用 paste()
构造字符串,那么无论行数如何,它都可以工作:
rows = dbExecute(con, paste("UPDATE rndid SET assigned = 'N', date = NULL
WHERE id IN (",toString(tempIDs$id),")"
)
)
有人知道为什么占位符方法失败或者至少我可以尝试调试它吗?我想看看 dbExecute 实际执行了什么语句。
这是 table def:
CREATE TABLE rndid (
id INTEGER UNIQUE NOT NULL,
assigned TEXT DEFAULT 'N' NOT NULL,
date TEXT DEFAULT NULL
);
原因是来自 params 的 ID 被解释为单个字符串,请参见下面的示例:
设置虚拟 mtcars 数据库:
library(DBI)
con <- dbConnect(RSQLite::SQLite(), ":memory:")
dbWriteTable(con, "mtcars", mtcars[1:5, 1:4])
dbReadTable(con, "mtcars")
# mpg cyl disp hp
# 1 21.0 6 160 110
# 2 21.0 6 160 110
# 3 22.8 4 108 93
# 4 21.4 6 258 110
# 5 18.7 8 360 175
# rows to match on cyl values
x <- c(4,6)
在没有 SQL 的情况下使用 sqlInterpolate 它变成了 '4, 6'
而不是 4, 6
:
sqlInterpolate(ANSI(),
"UPDATE mtcars SET mpg = 0, disp = ?x1 WHERE cyl IN (?x2)",
x1 = NA, x2 = toString(x))
# <SQL> UPDATE mtcars SET mpg = 0, disp = NULL WHERE cyl IN ('4, 6')
我们需要使用SQL来避免:
dbExecute(con,
sqlInterpolate(ANSI(),
"UPDATE mtcars SET mpg = 0, disp = ?x1 WHERE cyl IN (?x2)",
x1 = NA, x2 = SQL(toString(x))))
# [4]
dbReadTable(con, "mtcars")
# mpg cyl disp hp
# 1 0.0 6 NA 110
# 2 0.0 6 NA 110
# 3 0.0 4 NA 93
# 4 0.0 6 NA 110
# 5 18.7 8 360 175
#disconnect
dbDisconnect(con)
虽然我认为 zx8754 的回答描述了 sqlInterpolate
的预期用途,但您从绑定参数 (?
) 开始然后放弃它们可以提供的保护和效率的事实是不幸的是。
另一种方法是扩展 IN (?)
以使其正确。实际上,您在此示例中需要的是 IN (?,?)
。不是用数据插入字符串(这可能是不鼓励的,至少在理论上),而是用参数(问号)插入字符串;这不仅可以防止无意的 SQL 注入(它并不总是恶意的,它可能会因编码问题而发生),它还允许使用 stored-result 查询进行重复查询(对于多个绑定,如果你使用它)和在 DBMS 中更好的查询优化。 (对于后者:一些 DBMS 将优化查询,然后 re-use 优化查询,如果它没有改变;不幸的是,经常(大多数?总是?idk)只是改变这些参数 '4','6'
否定 re-use.)
使用与 zx8754 的答案相同的前提:
library(DBI)
con <- dbConnect(RSQLite::SQLite(), ":memory:")
dbWriteTable(con, "mtcars", mtcars[1:5, 1:4])
dbReadTable(con, "mtcars")
# mpg cyl disp hp
# 1 21.0 6 160 110
# 2 21.0 6 160 110
# 3 22.8 4 108 93
# 4 21.4 6 258 110
# 5 18.7 8 360 175
qry <- sprintf("UPDATE mtcars SET mpg = 0, disp = ? WHERE cyl IN (%s)",
paste(rep("?", length(x)), collapse = ","))
qry
# [1] "UPDATE mtcars SET mpg = 0, disp = ? WHERE cyl IN (?,?)"
dbExecute(con, qry, params = c(NA, x))
# [1] 4
dbReadTable(con, "mtcars")
# mpg cyl disp hp
# 1 0.0 6 NA 110
# 2 0.0 6 NA 110
# 3 0.0 4 NA 93
# 4 0.0 6 NA 110
# 5 18.7 8 360 175
我并不是说 sqlInterpolate
不安全;相反,它是一种更安全的方式(比 sprintf
/paste
/等),可以避免无意 sql 注入的可能性;但是,它违背了保持查询不受数据影响的意图(这是最大化查询优化 benefit/effect 所必需的)。
我有一个非常简单的 table,我想用 dbExecute()
更新某些行,但是占位符参数并不总是有效。
rows = dbExecute(con, "UPDATE rndid SET assigned = 'N', date = ?
WHERE id IN (?)",
params = list(NA, toString(tempIDs$id))
)
如果 tempIDs
中只有一行,上面的代码可以正常工作,但如果超过一行,它不会更新任何行,也不会抛出错误。
如果我只是用 paste()
构造字符串,那么无论行数如何,它都可以工作:
rows = dbExecute(con, paste("UPDATE rndid SET assigned = 'N', date = NULL
WHERE id IN (",toString(tempIDs$id),")"
)
)
有人知道为什么占位符方法失败或者至少我可以尝试调试它吗?我想看看 dbExecute 实际执行了什么语句。
这是 table def:
CREATE TABLE rndid (
id INTEGER UNIQUE NOT NULL,
assigned TEXT DEFAULT 'N' NOT NULL,
date TEXT DEFAULT NULL
);
原因是来自 params 的 ID 被解释为单个字符串,请参见下面的示例:
设置虚拟 mtcars 数据库:
library(DBI)
con <- dbConnect(RSQLite::SQLite(), ":memory:")
dbWriteTable(con, "mtcars", mtcars[1:5, 1:4])
dbReadTable(con, "mtcars")
# mpg cyl disp hp
# 1 21.0 6 160 110
# 2 21.0 6 160 110
# 3 22.8 4 108 93
# 4 21.4 6 258 110
# 5 18.7 8 360 175
# rows to match on cyl values
x <- c(4,6)
在没有 SQL 的情况下使用 sqlInterpolate 它变成了 '4, 6'
而不是 4, 6
:
sqlInterpolate(ANSI(),
"UPDATE mtcars SET mpg = 0, disp = ?x1 WHERE cyl IN (?x2)",
x1 = NA, x2 = toString(x))
# <SQL> UPDATE mtcars SET mpg = 0, disp = NULL WHERE cyl IN ('4, 6')
我们需要使用SQL来避免:
dbExecute(con,
sqlInterpolate(ANSI(),
"UPDATE mtcars SET mpg = 0, disp = ?x1 WHERE cyl IN (?x2)",
x1 = NA, x2 = SQL(toString(x))))
# [4]
dbReadTable(con, "mtcars")
# mpg cyl disp hp
# 1 0.0 6 NA 110
# 2 0.0 6 NA 110
# 3 0.0 4 NA 93
# 4 0.0 6 NA 110
# 5 18.7 8 360 175
#disconnect
dbDisconnect(con)
虽然我认为 zx8754 的回答描述了 sqlInterpolate
的预期用途,但您从绑定参数 (?
) 开始然后放弃它们可以提供的保护和效率的事实是不幸的是。
另一种方法是扩展 IN (?)
以使其正确。实际上,您在此示例中需要的是 IN (?,?)
。不是用数据插入字符串(这可能是不鼓励的,至少在理论上),而是用参数(问号)插入字符串;这不仅可以防止无意的 SQL 注入(它并不总是恶意的,它可能会因编码问题而发生),它还允许使用 stored-result 查询进行重复查询(对于多个绑定,如果你使用它)和在 DBMS 中更好的查询优化。 (对于后者:一些 DBMS 将优化查询,然后 re-use 优化查询,如果它没有改变;不幸的是,经常(大多数?总是?idk)只是改变这些参数 '4','6'
否定 re-use.)
使用与 zx8754 的答案相同的前提:
library(DBI)
con <- dbConnect(RSQLite::SQLite(), ":memory:")
dbWriteTable(con, "mtcars", mtcars[1:5, 1:4])
dbReadTable(con, "mtcars")
# mpg cyl disp hp
# 1 21.0 6 160 110
# 2 21.0 6 160 110
# 3 22.8 4 108 93
# 4 21.4 6 258 110
# 5 18.7 8 360 175
qry <- sprintf("UPDATE mtcars SET mpg = 0, disp = ? WHERE cyl IN (%s)",
paste(rep("?", length(x)), collapse = ","))
qry
# [1] "UPDATE mtcars SET mpg = 0, disp = ? WHERE cyl IN (?,?)"
dbExecute(con, qry, params = c(NA, x))
# [1] 4
dbReadTable(con, "mtcars")
# mpg cyl disp hp
# 1 0.0 6 NA 110
# 2 0.0 6 NA 110
# 3 0.0 4 NA 93
# 4 0.0 6 NA 110
# 5 18.7 8 360 175
我并不是说 sqlInterpolate
不安全;相反,它是一种更安全的方式(比 sprintf
/paste
/等),可以避免无意 sql 注入的可能性;但是,它违背了保持查询不受数据影响的意图(这是最大化查询优化 benefit/effect 所必需的)。