返回

如何在 Laravel 中基于日期查询数据库?

php

如何在 Laravel 中基于日期查询数据库,同时处理 JSON 字段和创建时间字段?

在 Laravel 应用开发中,我们常常需要根据日期筛选数据库记录。当日期信息存储在 JSON 字段中时,查询操作可能会变得棘手,尤其是一些记录的 JSON 字段中可能没有日期信息。本文将深入探讨这个问题,并提供一种高效的解决方案,帮助你轻松应对类似场景。

问题场景

假设我们有一个名为 services 的数据库表,用于存储服务记录。这些服务记录可能来自两种不同的途径:[预订] 和 [POS] 系统。

services 表的结构如下:idnumber,....,meta (JSON 字段)。

每个服务记录的 meta 字段存储了相关的元信息。然而,问题在于,从 [预订] 系统添加的服务记录的 meta 字段中 不包含日期信息 ,例如:

{"qty": 1, "category": "service", "services": [{"id": 2503, "qty": 1, "ttx": 0, "vat": 0, "price": "10", "category": "service", "statement": "beans", "sub_total": 10, "ttxIsChecked": false, "vatIsChecked": false, "totalGeneralSum": 10}], "statement": "beans", "sub_total": 10, "ttx_total": "0.00", "vat_total": "0.00", "payment_type": null, "total_with_taxes": "10.00"}

而从 [POS] 系统添加的服务记录的 meta 字段中 包含日期信息 ,例如:

{"pos": true, "qty": 1, "date": "2024-04-20 13:02", "from": "alex", "note": "1 × beans ,", "type": 107272, "address": null, "category": "service-deposit", "employee": "Mr man", "services": [{"id": 2503, "qty": 1, "ttx": 0, "vat": "1.50", "price": "10.00", "category": "service", "statement": "beans", "sub_total": 10, "ttxIsChecked": false, "vatIsChecked": true, "totalGeneralSum": 11.5}], "pay_later": true, "reference": null, "statement": "beans", "sub_total": "10.00", "ttx_total": "0.00", "vat_total": "1.50", "tax_number": null, "payment_type": null, "customer_name": null, "total_with_taxes": "11.50"}

现在,我们需要根据日期查询 services 表,并优先使用 meta 字段中的 date 值进行筛选。如果 meta 字段中不存在 date 值,则使用记录的 created_at 字段进行筛选。

解决方案剖析

在处理这个问题时,我们可能会尝试以下两种方法,但它们都存在缺陷,因为它们没有妥善处理 meta 字段中可能不存在 date 值的情况。

方法一:

$from = Carbon::parse($value)->format('Y-m-d H:i');
return $query->where('meta->date', '>=', $from)
        ->orWhere('created_at', '>=', $from);

这种方法的问题在于,如果 meta 字段中不存在 date 值,where('meta->date', '>=', $from) 条件就会返回 false,导致查询结果不准确。

方法二:

$from = Carbon::parse($value)->format('Y-m-d H:i');
return $query->where(function($query) use ($from) {
    $query->whereRaw("JSON_CONTAINS_PATH(meta, 'one', '\$.date')");
    if($query->exists()){
        $query->where('meta->date', '>=', $from);
    }else{
        $query->Where('created_at', '>=', $from);
    }
});

这种方法的问题在于,$query->exists() 方法会在执行子查询时立即获取结果,而不是在构建完整查询条件之后。因此,即使 meta 字段中存在 date 值,也可能会执行 $query->Where('created_at', '>=', $from) 条件,导致查询结果不准确。

高效解决方案

为了解决这个问题,我们需要借助 MySQL 的 JSON_EXTRACT 函数和 COALESCE 函数。

JSON_EXTRACT(meta, '$.date') 可以从 meta 字段中提取 date 值。

COALESCE(value1, value2, ...) 函数会返回第一个非 NULL 值。

我们可以使用以下代码构建查询:

$from = Carbon::parse($value)->format('Y-m-d H:i');
return $query->where(DB::raw("COALESCE(JSON_UNQUOTE(JSON_EXTRACT(meta, '$.date')), created_at)"), '>=', $from);

这段代码的逻辑如下:

  1. 使用 JSON_EXTRACT(meta, '$.date') 提取 meta 字段中的 date 值。
  2. 使用 JSON_UNQUOTE 函数去除 JSON_EXTRACT 返回值中的双引号。
  3. 使用 COALESCE 函数判断提取的 date 值是否为 NULL。如果为 NULL,则使用 created_at 字段的值。
  4. 最后,将结果与 $from 进行比较。

总结

通过巧妙地结合使用 MySQL 的 JSON_EXTRACTJSON_UNQUOTECOALESCE 函数,我们可以轻松解决在 Laravel 中基于日期查询数据库时,同时处理 JSON 字段和创建时间字段的问题。这种方法确保了查询结果的准确性,并避免了对数据库进行大量更新操作。

常见问题解答

  1. 为什么不能直接使用 where('meta->date', '>=', $from) 进行查询?

    因为如果 meta 字段中不存在 date 属性,该条件会返回 false,导致查询结果不准确。

  2. COALESCE 函数的作用是什么?

    COALESCE 函数会返回其参数列表中的第一个非 NULL 值。在本例中,它用于优先使用 meta 字段中的 date 值,如果该值不存在,则使用 created_at 字段的值。

  3. JSON_UNQUOTE 函数的作用是什么?

    JSON_EXTRACT 函数返回的值包含双引号,而 COALESCE 函数无法处理包含双引号的日期值。JSON_UNQUOTE 函数用于去除双引号,以便 COALESCE 函数能够正常工作。

  4. 这种方法的性能如何?

    这种方法的性能取决于数据库的大小和索引设置。在大多数情况下,它的性能都足以满足需求。如果性能成为瓶颈,可以考虑使用数据库索引或其他优化措施。

  5. 除了使用 COALESCE 函数,还有其他解决方案吗?

    是的,还可以使用其他的 MySQL 函数或 Laravel 查询构建器方法来解决这个问题。例如,可以使用 CASE 表达式或子查询。但是,使用 COALESCE 函数通常是更简洁、更高效的解决方案。