Laravel Eloquent 在 JSON 列中查找日期在某个范围内的所有记录
Posted
技术标签:
【中文标题】Laravel Eloquent 在 JSON 列中查找日期在某个范围内的所有记录【英文标题】:Laravel Eloquent find all records where a date is within a range in JSON column 【发布时间】:2021-08-22 12:13:59 【问题描述】:我正在尝试使用 Eloquent 查询数据库中的记录表,并仅检索 json 列中至少有 1 条记录(由对象数组组成)在给定日期范围内的记录。
日期列如下所示:
[
'date': '2021-01-01',
'date': '2021-01-02',
'date': '2021-01-03',
]
如果我通过了“2021-01-01”的开始日期,则它需要获取 dates.*.date 等于或晚于该开始日期的所有记录。
结束日期也是如此。
我有不同类型的语法,例如:
$this->where('dates->[*]->date', '<=', date($value));
$this->where('dates->*->date', '<=', date($value));
$this->where('dates.*.date', '<=', date($value));
似乎没有任何效果。
我做错了什么?
【问题讨论】:
您使用的是哪个 mysql 版本?在 8.x 中,您可以使用 JSON_TABLE 函数将日期数组提取到表中并对其执行查询 如果没有任何效果,只需通过 php 完成。您可以创建日期索引,然后与之匹配 【参考方案1】:您可以将whereJsonContains
第二个参数作为数组传递
Model::whereJsonContains('dates',['date'=>'2018-01-01'])->get();
【讨论】:
但这会寻找完全匹配?!如何返回 2018-01-01 及之后/之前的所有日期? @MarcusChristiansen.yes 你的权利。我尝试了很多方法来小于或大于但无法成功。如果我有时间我会再次检查【参考方案2】:这是一个原始 SQL 版本,我不太了解 Eloquent,但我很确定你可以执行原始 SQL。
这是你的桌子
CREATE TABLE MyTable(id int, dates varchar(100));
INSERT INTO MyTable(id, dates) values(1, '["date": "2020-01-01","date": "2020-01-02","date": "2020-01-03"]');
INSERT INTO MyTable(id, dates) values(2, '["date": "2021-01-01","date": "2021-01-02","date": "2021-01-03"]');
INSERT INTO MyTable(id, dates) values(3, '["date": "2022-01-01","date": "2022-01-02","date": "2022-01-03"]');
你可以使用这个 SQL
SELECT * FROM MyTable
WHERE EXISTS (
SELECT *
FROM JSON_TABLE(
JSON_EXTRACT(dates, "$[*].date")
, "$[*]"
COLUMNS(jsondate date path '$'
)
) as fn
WHERE jsondate >= '2021-06-01'
);
父查询实际上是直截了当的,但子查询更复杂。它首先使用 MySQL 中的 JSON_EXTRACT()
函数,该函数采用 JSON 和“路径”来指定要返回的 JSON 值:在我们的例子中,我们的 json 对象中的每个 date
字段,例如对于第一行,它将返回: ["2020-01-01", "2020-01-02", "2020-01-03"]
.
有了这个 JSON 数组,您现在可以使用 JSON_TABLE()
函数创建一个匿名表,该表也采用 JSON 和新表的定义。我们现在有一个表,其中一列 jsondate
每一行是一个日期,因此我们可以使用一个简单的 WHERE 子句来检查日期是否大于您想要的日期。
Example
【讨论】:
【参考方案3】:您可以使用多个orWhereJsonContains()
约束
$query->where(function ($query) use ($dates)
foreach ($dates as $date)
$query->orWhereJsonContains('dates', ['date' => $date]);
);
【讨论】:
如果您只过了开始日期或结束日期,这将不起作用。 您需要遍历日期。$dates = new DatePeriod( new DateTime('2010-10-01'), new DateInterval('P1D'), new DateTime('2010-10-05') );
【参考方案4】:
您应该尝试 whereRaw 和 JSON_EXTRACT -
$this->whereRaw('JSON_EXTRACT(`dates` , "$[*].date") <= ?', [date($value)]);
我认为这将适用于您的情况,请尝试让我知道它是否有效。
代码链接-https://phpsandbox.io/n/calm-term-y1yo-upqak
要运行代码,请使用终端打开修补程序
php artisan tinker
app(App\Http\Controllers\UserController::class)->testingJsonExtract()
供您参考 - https://dev.mysql.com/doc/refman/5.7/en/json.html#json-paths
【讨论】:
【参考方案5】:$this->whereDate("json_extract('dates', '$.date')", '>', $date)->get();
正如您在上面看到的,我使用了json_extract()
方法,这是 MySQL 的从 JSON 列中提取字段的函数,因为我不能简单地使用箭头运算符指示 eloquent。
$this->whereDate('dates->date', '>', $date)->get();
我必须明确告诉 eloquent 提取 json 字段然后继续。
【讨论】:
这会假设有一个直接的孩子称为 date of dates 但 dates 是一个对象数组。 @MarcusChristiansen,尝试改用$[*].date
【参考方案6】:
JSON_TABLE
可能是唯一(好的)纯 SQL 方法。这需要 MySQL 8.0.4 或更高版本。这是 Eloquent:
$this->whereExists(function($query)
$query->fromRaw(
'json_table(json_extract(`dates`, "$[*].date"), "$[*]" columns(`json_date` date path "$")) as `sub`'
)
->where('json_date','>=','2021-01-01');
)->get();
解释:
json_extract
以$[*].date
作为路径参数将数据转换为常规数组["2020-01-01", "2020-01-02", "2020-01-03"]
)
json_table
将 json 数据转换为常规 sql 行:
json_date |
---|
2020-01-01 |
2020-01-02 |
2020-01-03 |
应用 where 子句并将结果行过滤到仅在 json 表中具有现有 json_date
行的行。
【讨论】:
我当然不知道;我不确定我是否遗漏了什么?其他答案没有考虑日期范围和每行/json列的多个日期,这就是我建议json_table
的原因。
你能提供一个可行的例子吗?
我要做的是在模型上创建一个范围,让您查询日期而无需每次都编写原始 sql:gist.github.com/LowSociety/4ef7a901b8733a7d3d359fad82c7b6c7 然后像这样查询模型:gist.github.com/LowSociety/921c8cf91ba613180043075c479ae080【参考方案7】:
尝试以下方法:
Model::whereJsonContains('dates', ['starts_at' => '2021-01-01'])->get();
类似地,对于不超过某个值的日期:
Model::whereJsonContains('dates', ['ends_at' => '2021-01-01'])->get();
【讨论】:
以上是关于Laravel Eloquent 在 JSON 列中查找日期在某个范围内的所有记录的主要内容,如果未能解决你的问题,请参考以下文章
Laravel Eloquent 支持 MariaDb 动态专栏