【问题标题】:Laravel Eloquent find all records where a date is within a range in JSON columnLaravel Eloquent 在 JSON 列中查找日期在某个范围内的所有记录
【发布时间】: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 完成。您可以创建日期索引,然后与之匹配

标签: php mysql json laravel eloquent


【解决方案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】:

    您应该尝试 whereRawJSON_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 行的行。

      【讨论】:

      【解决方案7】:

      尝试以下方法:

      Model::whereJsonContains('dates', ['starts_at' => '2021-01-01'])->get();
      

      类似地,对于不超过某个值的日期:

      Model::whereJsonContains('dates', ['ends_at' => '2021-01-01'])->get();
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2023-04-10
        • 1970-01-01
        • 1970-01-01
        • 2021-01-29
        • 2017-08-28
        • 1970-01-01
        • 2018-07-01
        • 2019-05-08
        相关资源
        最近更新 更多