带有占位符的 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 所必需的)。