You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Laravel8+MySQL8环境下如何在WHERE条件中使用HAVING子句

问题原因

你当前的代码和期望SQL都存在基础语法错误:

  1. SQL标准中HAVING子句不能嵌套在WHERE条件的分组内部。SQL执行顺序为FROM -> WHERE -> GROUP BY -> SELECT -> HAVING -> ORDER BY,WHERE阶段还没计算SELECT里的自定义列,HAVING是在SELECT计算完成后执行的筛选段,不能插到WHERE的条件块里。
  2. Laravel查询构造器中,在where()闭包内调用having()/orHaving()属于错误用法,构造器会直接把HAVING关键字拼接到WHERE语句段中间,生成数据库无法解析的非法SQL。

可行实现方案

你的运行环境是MySQL 8 + Laravel 8,以下两种方案都可以实现对自定义计算列的筛选:

方案1:HAVING分组筛选(写法最简洁)

MySQL本身做了语法扩展:没有GROUP BY时,HAVING子句支持直接引用SELECT中定义的列别名,可直接用于行级数据筛选。你只需要把所有针对自定义列的筛选逻辑,统一放到HAVING的分组闭包中即可,逻辑分组的写法和WHERE完全一致。

首先确保你的两个自定义列是用原始表达式定义的,避免被框架转义:

// 定义计算列
$booking_start_at = DB::raw("CAST(CONCAT(booking_start_date, 'T', booking_start_time) AS DATETIME) AS booking_start_at");
$booking_end_at = DB::raw("CAST(CONCAT(booking_end_date, 'T', booking_end_time) AS DATETIME) AS booking_end_at");

查询代码如下:

Booking::select('*', $booking_start_at, $booking_end_at)
    ->where('room_name', $data['room_name'])
    // 自定义列的筛选全部放到having分组中
    ->having(function ($query) use ($data) {
        $rangeStart = Carbon::parse($data['booking_start_date'] . $data['booking_start_time']);
        $rangeEnd = Carbon::parse($data['booking_end_date'] . $data['booking_end_time']);
        
        $query->where(function ($q) use ($rangeStart) {
            $q->having('booking_start_at', '>', $rangeStart)
              ->orHaving('booking_end_at', '<', $rangeStart);
        })
        ->where(function ($q) use ($rangeEnd) {
            $q->having('booking_start_at', '>', $rangeEnd)
              ->orHaving('booking_end_at', '<', $rangeEnd);
        });
    })
    ->get();

该代码生成的SQL符合语法规范,可直接正常执行:

SELECT
    *,
    CAST(CONCAT(booking_start_date, 'T', booking_start_time) AS DATETIME) AS booking_start_at,
    CAST(CONCAT(booking_end_date, 'T', booking_end_time) AS DATETIME) AS booking_end_at
FROM `bookings`
WHERE `room_name` = ?
HAVING
    (
        (`booking_start_at` > ? OR `booking_end_at` < ?)
        AND
        (`booking_start_at` > ? OR `booking_end_at` < ?)
    )

方案2:子查询包裹(兼容性最好)

如果需要兼容不支持HAVING直接筛选行数据的数据库场景,可以把计算列逻辑放到子查询中,外层查询直接用WHERE筛选别名,完全不需要用到HAVING:

// 构造包含计算列的子查询
$subQuery = Booking::select('*',
    DB::raw("CAST(CONCAT(booking_start_date, 'T', booking_start_time) AS DATETIME) AS booking_start_at"),
    DB::raw("CAST(CONCAT(booking_end_date, 'T', booking_end_time) AS DATETIME) AS booking_end_at")
);

// 外层查询直接用where筛选自定义列
$results = DB::table(DB::raw("({$subQuery->toSql()}) AS booking_sub"))
    ->mergeBindings($subQuery->getQuery()) // 合并子查询的参数绑定
    ->where('room_name', $data['room_name'])
    ->where(function ($query) use ($data) {
        $rangeStart = Carbon::parse($data['booking_start_date'] . $data['booking_start_time']);
        $rangeEnd = Carbon::parse($data['booking_end_date'] . $data['booking_end_time']);
        
        $query->where(function ($q) use ($rangeStart) {
            $q->where('booking_start_at', '>', $rangeStart)
              ->orWhere('booking_end_at', '<', $rangeStart);
        })
        ->where(function ($q) use ($rangeEnd) {
            $q->where('booking_start_at', '>', $rangeEnd)
              ->orWhere('booking_end_at', '<', $rangeEnd);
        });
    })
    ->get();

避坑提示
  • 不要在where()闭包内调用having系列方法,Laravel不会自动把这类条件移动到HAVING语句段,只会直接拼接生成错误SQL
  • 传入Carbon实例作为查询参数时,Laravel会自动转换为MySQL兼容的datetime格式字符串,不需要手动调用->toDateTimeString()
  • 你之前写的期望SQL本身不符合SQL语法,HAVING关键字不能出现在WHERE的条件括号组内,任何数据库都无法解析这类语句

内容的提问来源于stack exchange,提问作者Mudit Gulgulia

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 09:09:20