【问题标题】:PHP SQLite - prepared statement misbehaves?PHP SQLite - 准备好的语句行为不端?
【发布时间】:2018-04-23 08:59:01
【问题描述】:

我有以下 SQLite 表

CREATE TABLE keywords
(
 id INTEGER PRIMARY KEY,
 lang INTEGER NOT NULL,
 kwd TEXT NOT NULL,
 count INTEGER NOT NULL DEFAULT 0,
 locs TEXT NOT NULL DEFAULT '{}'
);

CREATE UNIQUE INDEX  kwd ON keywords(lang,kwd);

使用 PHP 我通常需要在此表中插入关键字,或者如果关键字已经存在,则更新行数。举个例子

$langs = array(0,1,2,3,4,5);
$kwds = array('noel,canard,foie gras','','','','','');

我现在这些数据通过下面的代码运行

 $len = count($langs);
 $klen = count($kwds);
 $klen = ($klen < $len)?$klen:$len;

 $sqlite = new SQLite3('/path/to/keywords.sqlite');
 $iStmt = $sqlite->prepare("INSERT OR IGNORE INTO keywords (lang,kwd) 
 VALUES(:lang,:kwd)");
 $sStmt = $sqlite->prepare("SELECT rowid FROM keywords WHERE lang = :lang 
 AND kwd = :kwd");

 if (!$iStmt || !$sStmt) return;

 for($i=0;$i < $klen;$i++)
 {
  $keywords = $kwds[$i];
  if (0 === strlen($keywords)) continue;
  $lang = intval($langs[$i]);


  $keywords = explode(',',$keywords);
  for($j=0;$j < count($keywords);$j++)
  {
   $keyword = $keywords[$j];
   if (0 === strlen($keyword)) continue;

   $iStmt->bindValue(':kwd',$keyword,SQLITE3_TEXT);
   $iStmt->bindValue(':lang',$lang,SQLITE3_INTEGER);
   $sStmt->bindValue(':lang',$lang,SQLITE3_INTEGER);
   $sStmt->bindValue(':kwd',$keyword,SQLITE3_TEXT);

   trigger_error($keyword);
   $iStmt->execute();
   $sqlite->exec("UPDATE keywords SET count = count + 1 WHERE lang = 
   '{$lang}' AND kwd = '{$keyword}';");

   $rslt = $sStmt->execute();
   trigger_error($sqlite->lastErrorMsg());
   trigger_error(json_encode($rslt->fetchArray()));
  }
 }

生成以下trigger_error 输出

关键字:noel 最后一个错误:不是错误 选择结果:{"0":1,"id":1}

关键字:canard 上一个错误:不是错误
SELECT Reult:false

关键字:鹅肝 上一个错误:不是错误 选择结果:假

从 SQLite 命令行中,我看到三个行条目在表中存在且正确,其中 id/rowid 列分别设置为 1、2 和 3。 lastErrorMsg 不报告错误,但三个$rslt-&gt;fetchArray() 语句中有两个返回false,而不是具有rowid/id 属性的数组。那么我在这里做错了什么?


我对此进行了更多调查,并找到了潜在的案例。在我的原始代码中,第一个 SQLite3::execute - $iStmt-execute() - 的结果没有被分配给任何东西。我没有看到获取和解释该结果的任何特殊原因。当我将该行代码更改为 $rslt = $iStmt-&gt;execute() 时,我得到了预期的结果 - 正确报告了插入的三行中的 rowid/id

就好像 PHP SQLite3 扩展在内部缓冲了来自 SQLiteStatement::execute 函数调用的结果。当我跳过作业时,我下一次尝试运行这样的语句时,$sStmt-&gt;execute() 实际上是在获取先前的结果。这是我在不了解 PHP SQLite3 扩展的内部工作原理的情况下的解释。也许更了解扩展的人愿意发表评论。

【问题讨论】:

  • “我在这里做错了什么” 目标是什么?什么不能按预期工作?数据按你说的保存,没有错误。你到底有什么问题?
  • @feeela 看起来 DroidOS 不希望选择语句返回 false。我在打电话,但我认为查询没有在 for 循环中重新初始化。
  • 我已经编辑了这个问题,以更好地表明我觉得没有按应有的方式工作。基本上,我需要获取新创建的行/更新行的 rowid。正如@Zev 所说,我看不出为什么select 语句返回false 而不是返回SQLite3Result 以及为什么- 鉴于它返回false- 没有lastErrorMessage
  • 不是我第一次发现自己被感动写下这些内容 - 投票“关闭”我的问题的人是否愿意评论他们选择投票的原因?如果有什么需要澄清的,可能会被要求。如果我以某种方式违反了这个论坛的规则,没有表现出足够的研究……这也可以表明。点击关闭链接太容易了!
  • @DroidOS 我需要重新安装 PHP 才能在本地使用它,但是您是否尝试将这两行移到 for 循环中:$iStmt = $sqlite-&gt;prepare("INSERT OR IGNORE INTO keywords (lang,kwd) VALUES(:lang,:kwd)"); $sStmt = $sqlite-&gt;prepare("SELECT rowid FROM keywords WHERE lang = :lang AND kwd = :kwd");

标签: php sqlite prepared-statement


【解决方案1】:

trigger_error(json_encode($rslt-&gt;fetchArray())); 之后添加$rslt = NONE; 会出现正确的结果。

FetchArray 只能被调用一次,并且不知何故 php 没有检测到变量已更改。我还尝试将bindValue 更改为bindParam 并将其移到循环之前,但这与主要问题无关。

我认为除非 php 中存在错误,否则我的解决方案不应该起作用。我对这门语言还太陌生,无法对这种观点充满信心,并希望帮助验证它。 好吧,这不是错误,而是违反了最小意外原则。该对象仍然存在于内存中,因此如果没有完成它或重置变量,则不会触发 fetch 数组。

【讨论】:

  • 我现在正在做$rslt-&gt;finalize(),根据文档,它关闭了结果集。如果结果集未最终确定,文档并不太清楚究竟会发生什么。这需要知道 PHP SQLite3 扩展如何工作的人的输入。
猜你喜欢
  • 2011-06-02
  • 1970-01-01
  • 2015-06-27
  • 1970-01-01
  • 2013-07-27
  • 1970-01-01
  • 2010-11-30
  • 2021-01-28
  • 1970-01-01
相关资源
最近更新 更多