【问题标题】:What is wrong with this concatenate statement?这个连接语句有什么问题?
【发布时间】:2019-07-21 13:18:18
【问题描述】:

我正在使用 MySQL 服务器 Server version: 5.6.42-cll-lve - MySQL Community Server (GPL),但我在 CONCAT() 方面遇到了一些重大问题

       public function get_urls() {
        // Create query
        $query = "SELECT a.Name, a.PrimaryVersion, e1.URL
        FROM " . $this->table . " a
        INNER JOIN
        FunnelsFlows b ON a.Funnel_ID = b.Funnel_ID
        INNER JOIN
        BackendFlows c ON b.BackendFlow_ID = c.BackendFlow_ID
        INNER JOIN
        BackendLevels d ON CONCAT(c.Level, :lv) = d.BackendLevel_ID
        LEFT JOIN
        BackendPages e1 ON d.Upsell = e1.BackendPage_ID
        LEFT JOIN
        BackendPages e2 ON d.Downsell1 = e2.BackendPage_ID
        LEFT JOIN
        BackendPages e3 ON d.Downsell2 = e3.BackendPage_ID
        WHERE                    
        a.Funnel_ID = :fid 
        LIMIT 0,1";
        // Prepare statement
        $stmt = $this->conn->prepare($query);
        // Bind ID
        $stmt->bindParam(':fid', $this->fid, PDO::PARAM_INT);
        $stmt->bindValue(':lv', $this->lv, PDO::PARAM_STR);
        // Execute query
        $stmt->execute();

运行此代码会引发以下错误:PHP Fatal error: Uncaught PDOException: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'c.Level' in 'on clause' in ...

您可以在此处查看整个数据库结构:
https://app.sqldbm.com/MySQL/Share/wBAF2JMRFFSoPPjIPdYZc0GFrngIE8md_DYjF4jNYw0

【问题讨论】:

  • CONCAT 按预期工作,但您的 SQL 没有。
  • 不要引用参数,不要引用标识符或在每个(`a`.`Level`, :level)上使用反引号
  • @PM77-1 当然,根据我的帖子“我已将其范围缩小到我可能如何使用 concat 的问题”。工具在那里,它们工作正常,我只是在使用它们时遇到了问题
  • 好的,现在我们需要SHOW CREATE TABLE BackendFlows; 我猜外键列名称类似于Level_ID。如果是这样,Q 可以仅由于拼写错误而关闭/删除。
  • @Quasimodo'sclone app.sqldbm.com/MySQL/Share/… 你可以在这里查看整个数据库

标签: php mysql pdo prepared-statement


【解决方案1】:

您的查询不能这样工作。您正在尝试使用参数:lv 连接字段内容CONCAT(c.Level, :lv)。如果有列Level,这将产生一个字符串。但是,您更愿意构建一个动态列名,例如 Level1 / Level2 aso。

不幸的是,这不能通过参数来完成。这是因为已准备好查询计划。在执行时更改列名会改变查询计划。

在这种情况下,您要么需要构建动态 SQL,要么编写复杂的 ON 子句。

ON 子句:

ON 
(
  (:lv = 1 AND c.Level1 = d.BackendLevel_ID)
  OR
  (:lv = 2 AND c.Level2 = d.BackendLevel_ID)
  -- and so on
)

动态 SQL:

$sql = 'ON (c.Level' . inval($this->lv) . ' = d.BackendLevel_ID)'

由于 PHP 的 intval 函数被认为是安全的,为了提高 SQL 性能,我更喜欢后一个示例。

【讨论】:

  • 不幸的是,这与我想出的答案相同。现在过滤变量以避免 SQL 注入。我从来没有弄清楚为什么CONCAT(c.Lev, el1) 输出为c.Lev,可能不可能作为陈述。谢谢
  • @MattB 是的,这是因为已准备好查询计划。在执行时更改列名会改变查询计划。因此,您可以在具有一定性能成本的更复杂条件或默认情况下还不错的动态 SQL 替代方案之间进行选择。如果它真的是安全的,你只需要三思而后行。 intval 返回数据类型整数,因此它不仅仅是一个过滤器。无论尝试哪种编码攻击,转换为字符串的整数都不能包含除数字之外的其他字符。在最坏的情况下,它可能会因错误而失败。
【解决方案2】:

这个怎么样?

$query = "SELECT a.Name, b.URL
        FROM " . $this->table . " a
        INNER JOIN
        FunnelsFlows b ON CONCAT(a.Level, ':lv') = b.Level_ID
        WHERE                
        a.Funnel_ID = :fid 
        LIMIT 0,1";
        // Prepare statement
        $stmt = $this->conn->prepare($query);
        // Bind ID
        $stmt->bindParam(':fid', $this->fid, PDO::PARAM_INT);
        $stmt->bindParam(':lv', $this->lv, PDO::PARAM_INT);
        // Execute query
        $stmt->execute();

那是一个快速的反应,所以我现在解释一下。

我看到你想用一个字符串连接一个表列,对吧?

首先,您尝试使用以下语句 $stmt->bindParam(':lv', $this->lv, PDO::PARAM_INT); 绑定错误标识 :level,因此您必须使用 :lv 更改它或将绑定语句更改为 $stmt->bindParam(':level', $this->lv, PDO::PARAM_INT);

第二 - 当你试图连接一个列时,你必须删除引号,所以它应该是CONCAT('a.Level',,如果第二个运算符是一个字符串,它应该是单引号,所以完整的 CONCAT 运算符应该写为关注CONCAT(a.Level, ':lv')

【讨论】:

  • “试试这个”并不是一个好的答案。你应该解释如何为什么这可以解决他们的问题。我推荐阅读,“How do I write a good answer?"
  • 这是一个快速响应,我会稍后发表评论......我现在已经完成了。我不是英语最好的人,所以这需要一些时间。我希望这能解释为什么我最初只写了声明。如果您必须完全正确,您可能还必须要求投票者解释他们的投票?我只看到您投反对票的原因,但没有看到其他投反对票。
  • @Reflective 谢谢!不幸的是,这不起作用,并且澄清“级别”列不存在。列“Level1”到“Level10”可以,所以我认为这是组合字符串。查看我的编辑以获取更新
  • 我没有投反对票,我只是发表了评论。也许其他人会回来告诉你他们投反对票的原因。
  • @MattB 现在我明白了 :)
【解决方案3】:

删除引号(列名和值):

CONCAT(a.Level, :level)

另外,你使用了错误的参数名称:

$stmt->bindParam(':level', $this->lv, PDO::PARAM_INT);
//                 ^^^^^

【讨论】:

  • 这给了我一个 PDO 错误,“Uncaught PDOException: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'c.Level' in 'on clause' in ...” 第二点制作这个缩短版本的代码是我的错误,在我的版本中都是 :lv 而不是 :level
  • 为什么是c? @MattB
  • @Quasimodo'sclone 完整的代码正在加入 5 个表,并且当我不尝试连接级别编号时工作正常,我使用 a b c d 和 e 作为表别名。实际上,级别列在表 c 上。我想它应该在那条评论中说“a.Level”
  • @Martin 到目前为止,所有建议的工作都给了我错误:PHP Fatal error: Uncaught PDOException: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'c.Level' in 'on clause' in ...。似乎出于某种原因,我的 concat 只输出了 concat 的第一部分。例如,CONCAT(a.Le, vel1) 给了我错误:PHP Fatal error: Uncaught PDOException: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'c.Le' in 'on clause' in ...
  • @MartinHeralecký 完整的代码有多个表,为了便于阅读,我将其压缩在这里。那本质上是a.Level,我得到了你对CONCAT(a.Level, :level) 的推荐,唯一的区别在于我的代码实际上是c.Level。很抱歉那里的混乱,无法弄清楚为什么 concat 忽略了第二个参数。
猜你喜欢
  • 1970-01-01
  • 2011-06-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多