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

OpenSearch优化查询:找出共享地址的客户(匹配数>1)

问题描述

现有一个客户索引,其中addressList字段存储的是逗号分隔的地址字符串,示例数据如下:

id:c1, addressList:a1,a2,a3
id:c2, addressList:a4,a5,a6
id:c3, addressList:a1,a7
id:c4, addressList:a2,a5
id:c5, addressList:a8

标准化后的地址字符串格式示例:

1234_water_dr_suite_10_montgomery_ny_23134

需求是找出所有共享同一地址的客户,预期结果:

a1 - c1,c3
a2 - c1,c4
a5 - c2,c4 

当前采用的暴力解法:

  1. 先通过以下查询获取所有地址并分词得到地址集合:
{
    "size": 10000,
    "_source": false,
    "fields": ["addressList"],
    "query": {
        "exists": {
            "field": "addressList"
        }
    }
}

这部分效率尚可,无需优化。

  1. 遍历每个地址,逐个调用查询:
{
    "size": 10000,
    "query": {
        "match" : {
            "addressList": "<currAddress>"
        }
    } 
}

问题在于这种逐个查询的方式效率低下,希望能传入地址列表,直接返回匹配文档数大于1的地址对应的客户。由于addressList是逗号分隔的文本字段,无法设为term字段,因此无法直接使用带min_doc_count的聚合。

优化解决方案

方案1:使用脚本字段+嵌套聚合(无需修改索引映射)

可以通过脚本聚合一次性拆分地址、统计每个地址对应的客户,并过滤出被多个客户共享的地址,无需多次请求。

查询示例:

{
  "size": 0,
  "aggs": {
    "shared_addresses": {
      "scripted_metric": {
        "init_script": "state.address_map = new HashMap()",
        "map_script": """
          def addresses = doc['addressList'].value.split(',');
          def customer_id = doc['id'].value;
          for (def addr : addresses) {
            def trimmed_addr = addr.trim();
            if (!state.address_map.containsKey(trimmed_addr)) {
              state.address_map.put(trimmed_addr, new ArrayList());
            }
            state.address_map.get(trimmed_addr).add(customer_id);
          }
        """,
        "combine_script": """
          def filtered = new HashMap();
          for (def entry : state.address_map.entrySet()) {
            if (entry.getValue().size() > 1) {
              filtered.put(entry.getKey(), entry.getValue());
            }
          }
          return filtered;
        """,
        "reduce_script": """
          def final_result = new HashMap();
          for (def map : states) {
            for (def entry : map.entrySet()) {
              if (!final_result.containsKey(entry.getKey())) {
                final_result.put(entry.getKey(), new ArrayList());
              }
              final_result.get(entry.getKey()).addAll(entry.getValue());
            }
          }
          // 对客户ID去重,避免分片数据重复
          def deduplicated = new HashMap();
          for (def entry : final_result.entrySet()) {
            deduplicated.put(entry.getKey(), entry.getValue().stream().distinct().collect(Collectors.toList()));
          }
          return deduplicated;
        """
      }
    }
  }
}

查询返回结果格式示例:

{
  "aggregations": {
    "shared_addresses": {
      "value": {
        "a1": ["c1", "c3"],
        "a2": ["c1", "c4"],
        "a5": ["c2", "c4"]
      }
    }
  }
}

方案2:修改索引映射(长期最优解)

如果允许修改索引映射,建议将addressList改为keyword类型的数组字段,这样可以直接使用标准聚合实现需求,性能更稳定高效。

1. 更新字段映射

PUT /customers/_mapping
{
  "properties": {
    "addressList": {
      "type": "keyword",
      "fields": {
        "text": {
          "type": "text"
        }
      }
    }
  }
}

(保留text子字段是为了兼容原有的文本查询需求,若不需要可省略)

2. 重新索引数据

将原逗号分隔的字符串转换为数组格式,比如把"a1,a2,a3"转为["a1","a2","a3"]。

3. 执行聚合查询

{
  "size": 0,
  "aggs": {
    "address_groups": {
      "terms": {
        "field": "addressList",
        "min_doc_count": 2
      },
      "aggs": {
        "customer_ids": {
          "top_hits": {
            "size": 10000,
            "_source": false,
            "fields": ["id"]
          }
        }
      }
    }
  }
}

查询会直接返回每个共享地址对应的客户ID列表,只需在客户端整理成预期格式即可。

方案3:批量查询减少请求次数

如果暂时无法修改映射或使用脚本聚合,可以将地址列表分批,通过bool查询的should子句批量查询,减少HTTP请求次数。

示例(批量查询a1、a2、a5):

{
  "size": 10000,
  "_source": ["id", "addressList"],
  "query": {
    "bool": {
      "should": [
        {"match": {"addressList": "a1"}},
        {"match": {"addressList": "a2"}},
        {"match": {"addressList": "a5"}}
      ],
      "minimum_should_match": 1
    }
  }
}

拿到结果后,在客户端自行整理每个地址对应的客户ID列表,过滤出客户数大于1的地址即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 23:43:31