Doctrine查询中如何设置变量用于计算列的数值计算
基于Doctrine实现目标比价查询的可落地方案
首先明确核心卡点:Doctrine的DQL原生不支持MySQL专属的用户会话变量(即@变量名 := 值的赋值语法),如果直接把这类语法拼进DQL,会被DQL解析器直接判定为非法语法,根本无法走到SQL执行阶段。不需要在“怎么在Doctrine里配置MySQL会话变量”这个方向上钻牛角尖,下面三种方案都可以实现目标逻辑:
- 方案1:取消中间变量,直接用纯表达式计算(最推荐,维护成本最低)
你当初在SQL里用@临时变量,本质是为了避免重复写相同的子查询表达式,这个需求完全可以通过直接嵌套计算表达式实现,根本不需要拆分临时变量:
对应的Doctrine QueryBuilder写法完全不需要额外配置,直接通过关联join实现即可:SELECT cp.price AS myPrice, (SELECT cpt1.price FROM competitor_product AS cpt1 WHERE cpt1.product_id = cp.product) AS compPrice1, (SELECT cpt1.price FROM competitor_product AS cpt1 WHERE cpt1.product_id = cp.product) / cp.price AS price_diff FROM customer_product AS cp WHERE cp.customer_id = 1;
这种写法完全符合Doctrine的ORM规范,后续实体字段调整、查询缓存、软删除过滤这类全局配置都能正常生效,没有解析兼容问题。$qb = $entityManager->createQueryBuilder(); $qb->select( 'cp.price AS myPrice', 'cpt1.price AS compPrice1', $qb->expr()->div('cpt1.price', 'cp.price') . ' AS price_diff' ) ->from(CustomerProduct::class, 'cp') ->leftJoin(CompetitorProduct::class, 'cpt1', 'WITH', 'cpt1.product = cp.product') ->where('cp.customer_id = :customerId') ->setParameter('customerId', 1); - 方案2:用PHP变量复用子查询逻辑,替代MySQL会话变量
如果不想重复写相同的子查询关联条件,可以直接把构建好的子查询存在PHP变量里,在多个select字段中复用,实现和@变量完全一致的效果,还不会用到MySQL专属语法:
这种写法完全兼容DQL解析规则,还能自动享受Doctrine的参数绑定、输入过滤能力,不存在SQL注入风险。$qb = $entityManager->createQueryBuilder(); // 先构建公共的竞品价格子查询,存在PHP变量中复用 $compPriceSub = $qb->expr() ->select('cpt.price') ->from(CompetitorProduct::class, 'cpt') ->where('cpt.product = cp.product'); // 直接引用PHP变量拼接多个select字段 $qb->select( 'cp.price AS myPrice', "($compPriceSub) AS compPrice1", "($compPriceSub) / cp.price AS price_diff" ) ->from(CustomerProduct::class, 'cp') ->where('cp.customer_id = :customerId') ->setParameter('customerId', 1); - 方案3:涉及MySQL专属变量逻辑的场景,直接使用原生SQL执行
如果业务场景必须用到MySQL会话变量(比如需要跨行累计、窗口类滚动计算这类DQL实现成本极高的逻辑),完全不用硬套DQL,直接通过Doctrine的DBAL层执行原生SQL即可:
注意点:执行原生SQL时必须通过参数绑定传值,不要直接把外部输入拼接到SQL字符串中;如果需要把查询结果映射回实体对象,可以配合Doctrine的// 直接编写原生SQL,通过参数绑定传值 $sql = "SELECT @myprice := cp.price, @compPrice1 := (SELECT cpt1.price FROM competitor_product AS cpt1 WHERE cpt1.product_id = cp.product) AS compPrice1, @compPrice1 / @myprice AS price_diff FROM customer_product AS cp WHERE customer_id = :customerId"; $stmt = $entityManager->getConnection()->prepare($sql); $stmt->bindValue('customerId', 1); $result = $stmt->executeQuery()->fetchAllAssociative();ResultSetMapping类配置结果映射规则,不需要手动处理数组到实体的转换逻辑。
内容的提问来源于stack exchange,提问作者Renan Arrieiro
相关产品推荐
相关产品推荐

