按星期几和时间过滤 Elasticsearch

Filter Elasticsearch by day of week and time

我在 Elasticsearch 中有一个企业索引。索引中的每个文档代表一个业务,每个业务有 business_hours。我正在尝试允许使用星期几和时间过滤营业时间。例如,我们希望能够对 进行筛选,向我们展示周二晚上 6:00PM 之后营业的所有商家 我在想我们应该有一个具有以下映射的字段:

  {
      "mappings": {
        "properties": {
          
          "business_hours": {
                             "type": "date_range",
                            "format": "w'T'hh:mma"
          }
        }
      }
    }

每个文档都会有一个 business_hours 的数组。 因此,周一 9:00AM - 5:00PM 和周二 9:30AM - 5:00PM 营业的商店将如下所示:

POST my-index/_doc
    {
      "name": "My Store",
      "business_hours": [
        {
        "gte": "1T09:00AM",
        "lte": "1T05:00PM"
        },
        {
        "gte": "2T09:30AM",
        "lte": "2T05:00PM"
        }
      ]
    }

我尝试搜索并查询此文档,但时间过滤器不起作用,它们看起来像是被忽略了.... Elasticsearch 支持按星期几过滤还是必须是实际的日期时间?

这是我使用的查询。它应该过滤周三营业的业务,但它返回了上面只有周一和周二营业时间的文件

GET my-index/_search
{
  "query": {
    "bool": {
      "filter": [
        {
          "range": {
            "business_hours": {
              "gte": "3T10:00AM",
              "lte": "3T05:00PM",
              "relation": "CONTAINS"
            }
          }
        }
      ]
    }
  }
}

我能够通过您的映射、示例数据和查询重现该问题,当我在查询中使用 explain 时,它解释了为什么它获取第 1 天和第 2 天的结果。

带解释的搜索查询输出

"hits": [
            {
                "_shard": "[64883176][0]",
                "_node": "kL6FUU3RT5GGzu_mqRs8NA",
                "_index": "64883176",
                "_type": "_doc",
                "_id": "1",
                "_score": 0.0,
                "_source": {
                    "name": "My Store",
                    "business_hours": [
                        {
                            "gte": "1T09:00AM",
                            "lte": "1T05:00PM"
                        },
                        {
                            "gte": "2T09:30AM",
                            "lte": "2T05:00PM"
                        }
                    ]
                },
                "_explanation": {
                    "value": 0.0,
                    "description": "ConstantScore(business_hours:<ranges:[36000000 : 61199999]>)^0.0",
                    "details": []
                }
            }
        ]

如果您仔细观察,查询将转换为纪元格式,如下所示

ConstantScore(business_hours:<ranges:[36000000 : 61199999]>)^0.0",

现在,当您使用 epoch converter 时,您会注意到它实际上在完全不同的日期范围内进行范围查询,起始范围是 Friday, 10 December 1971 07:59:59

根据日期字段的范围查询,Elasticsearch 添加了 missing date component,这似乎是导致问题的原因。

如果您提供正确的数据范围(即包括年、月等的完整日期),显然它可以工作,但我同意,这会导致复杂性,我将看看我们如何使用给定的格式实现同样的事情。

使用 range 字段的想法很好。但是,我建议使用 integer_range 字段,而不是适用于绝对日期的 date_range

由于每天有 1440 分钟,我的建议是将营业时间编码为自午夜以来的分钟数,并在该数字前加上当天的索引(星期一 = 1,星期二 = 2,等等)。将给定小时转换为自午夜以来的分钟数的公式非常简单:

(60 * HH) + MM 

Note: HH is in 24 hours format, not AM/PM, but that's a detail

以你上面的例子为例,它会产生这样的结果:

POST my-index/_doc
{
  "business_hours": [
    {
      "gte": 10540,       <--- Monday (1), 540 minutes after midnight
      "lte": 11020        <--- Monday (1), 1020 minutes after midnight
    },
    {
      "gte": 20570,       <--- Tuesday (2), 570 minutes after midnight
      "lte": 21020        <--- Tuesday (2), 1020 minutes after midnight
    }
  ]
}

这样,range 查询就变得简单了,可以消除任何与日期相关的问题。例如,下面的查询通过搜索周一早上 6 点到下午 5 点营业的企业来检索上面的文档

GET my-index/_search
{
  "query": {
    "bool": {
      "filter": [
        {
          "range": {
            "business_hours": {
              "gte": "10600",         <--- Monday (1), 600 minutes after midnight
              "lte": "11020",         <--- Monday (1), 1020 minutes after midnight
              "relation": "CONTAINS"
            }
          }
        }
      ]
    }
  }
}