Elasticsearch中endDate为空时计算起止日期月数的报错排查
解决Elasticsearch runtime_mapping日期类型转换错误
问题场景
计算startDate与endDate的月份差:用户非在职时(isEmployed为false)endDate有值,计算正常;用户在职时endDate为null,尝试通过runtime_mapping将endDate替换为当前时间时,触发类型转换错误。
索引映射
PUT {{elasticUrl}}/user-experiences { "mappings": { "properties": { "userId": { "type": "integer" }, "experienceId": { "type": "integer" }, "startDate": { "type": "date" }, "endDate": { "type": "date" }, "isEmployed": { "type": "boolean" // 原映射遗漏该字段,需补充否则无法访问doc['isEmployed'] } } } }
原错误查询
POST {{elasticUrl}}/user-experiences/_search { "runtime_mappings": { "endDate": { "type": "date", "script": { "source": "if (doc['isEmployed'].value == true) { emit(new Date().getTime()) } else { emit (params._source['endDate']) }" } } }, "size": 0, "aggregations": { "group_by": { "terms": { "field": "userId" }, "aggregations": { "start": { "sum": { "field": "startDate" } }, "end": { "sum": { "field": "endDate" } }, "duration": { "bucket_script": { "buckets_path": { "start": "start.value", "end": "end.value" }, "script": { "params": { "month_in_milliseconds": 2628000000 }, "source": "Math.round((params.end - params.start) / params.month_in_milliseconds)" } } }, "duration_bucket_filter": { "bucket_selector": { "buckets_path": { "durationBucket": "duration" }, "script": { "params": { "number_of_months": 1 }, "source": "params.durationBucket >= params.number_of_months" } } } } } } }
错误信息
{ "error": { "root_cause": [ { "type": "script_exception", "reason": "runtime error", "script_stack": [ "emit (params._source['endDate']) }", " ^---- HERE" ], "script": "if (doc['isEmployed'].value == true) { emit(new Date().getTime()) } else { emit (params._source['endDate']) }", "lang": "painless", "position": { "offset": 95, "start": 75, "end": 109 } } ], "type": "search_phase_execution_exception", "reason": "all shards failed", "phase": "query", "grouped": true, "failed_shards": [ { "shard": 0, "reason": { "type": "script_exception", "reason": "runtime error", "script_stack": [ "emit (params._source['endDate']) }", " ^---- HERE" ], "script": "if (doc['isEmployed'].value == true) { emit(new Date().getTime()) } else { emit (params._source['endDate']) }", "lang": "painless", "position": { "offset": 95, "start": 75, "end": 109 }, "caused_by": { "type": "class_cast_exception", "reason": "class java.lang.String cannot be cast to class java.lang.Number (java.lang.String and java.lang.Number are in module java.base of loader 'bootstrap')" } } } ] }, "status": 400 }
问题原因
runtime脚本中,当isEmployed为false时,emit(params._source['endDate'])传入的是字符串格式的日期(如"2024-05-01"),而emit要求传入数字类型的时间戳(与new Date().getTime()输出类型一致),类型不匹配导致强制转换失败。
修复方案
修改runtime脚本,使用doc['endDate'].value.getTime()获取已解析的日期时间戳,避免字符串类型问题:
POST {{elasticUrl}}/user-experiences/_search { "runtime_mappings": { "endDate": { "type": "date", "script": { "source": "if (doc['isEmployed'].value) { emit(new Date().getTime()); } else { emit(doc['endDate'].value.getTime()); }" } } }, "size": 0, "aggregations": { "group_by": { "terms": { "field": "userId" }, "aggregations": { "start": { "sum": { "field": "startDate" } }, "end": { "sum": { "field": "endDate" } }, "duration": { "bucket_script": { "buckets_path": { "start": "start.value", "end": "end.value" }, "script": { "params": { "month_in_milliseconds": 2628000000 }, "source": "Math.round((params.end - params.start) / params.month_in_milliseconds)" } } }, "duration_bucket_filter": { "bucket_selector": { "buckets_path": { "durationBucket": "duration" }, "script": { "params": { "number_of_months": 1 }, "source": "params.durationBucket >= params.number_of_months" } } } } } } }
额外说明
doc['endDate'].value是Elasticsearch已解析的ZonedDateTime对象,调用getTime()可直接获取毫秒级时间戳,与new Date().getTime()输出类型一致。- 若必须使用
params._source,需将字符串日期解析为时间戳:
此方式需确保日期格式符合ISO标准,可靠性低于直接使用emit(ZonedDateTime.parse(params._source['endDate']).toInstant().toEpochMilli())doc字段。
内容的提问来源于stack exchange,提问作者Dženan Džafić
相关产品推荐
相关产品推荐

