<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
  <channel>
    <title><![CDATA[ES|QL - Elasticsearch Labs]]></title>
    <description><![CDATA[Articles and tutorials from the Search team at Elastic]]></description>
    <copyright><![CDATA[© 2026. Elasticsearch B.V. All Rights Reserved]]></copyright>
    <image>
      <title><![CDATA[ES|QL - Elasticsearch Labs]]></title>
      <url>https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1121c0bf0e8a6e65/6a88da6340a1841030ef456f/search-labs-thumbnail.png</url>
      <link>https://www.elastic.co/cn/search-labs/blog/category/esql</link>
    </image>
    <link>https://www.elastic.co/cn/search-labs/blog/category/esql</link>
    <atom:link href="https://www.elastic.co/cn/search-labs/rss/category/esql.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[cn]]></language>
    <lastBuildDate>Mon, 28 Sep 2026 12:01:23 GMT</lastBuildDate>
  <item>
    <title><![CDATA[Elasticsearch ES|QL 可将全文本搜索功能应用于从未建立索引的数据]]></title>
    <description><![CDATA[MATCH 和 TO_TEXT 可将全文本搜索功能应用于从未建立索引的数据。在 ES|QL 中搜索计算列、未映射字段和联合源。]]></description>
    <content:encoded><![CDATA[<p>ES|QL MATCH 现在可以对从未建立索引的数据进行全文本搜索。计算列、未映射字段、实时汇编的字符串，甚至存储在 S3 中的联合数据。新的 TO_TEXT 函数告诉 ES|QL 将任何字符串视为可分析的文本，因此 MATCH 可以对仅在查询生命周期内存在的值进行分词、大小写折叠和词元匹配。这超越了大多数查询引擎为未建立索引的字符串提供的 LIKE 和 RLIKE 模式匹配：这是真正的分析。目前已在 Elastic Cloud Serverless 中提供，并在 Elasticsearch 9.5 中作为技术预览版提供。</p><h2>MATCH 和 TO_TEXT 如何启用任何 ES|QL 表达式的全文本搜索</h2><p>让我们从一个在 Elasticsearch 9.4 中无法实现的查询开始，该查询使用了<a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/eval">EVAL 命令</a>：</p><p>在这个例子中，摘要没有映射或分析器配置。它也没有与任何倒排索引关联。它仅在此查询的生命周期内存在，但现在无论如何您都可以搜索它。两项新增功能使其生效。</p><p>首先，<a href="https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/search-functions/match">MATCH</a> 现在接受任何表达式作为其第一个参数，而不仅仅是映射字段。这包括由 EVAL 生成的列和内联使用的函数结果。它还包括直接从原始文档加载的未映射字段。此外，MATCH 通常接受的所有数据类型在此新用例中均受支持。</p><p>其中的第二部分是新的 <a href="https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/type-conversion-functions/to_text">TO_TEXT</a> 函数，它是第一个生成文本类型输出的 ES|QL 转换函数。此前，文本列只能来自索引映射字段，而 ES|QL 表达式生成的所有字符串都是关键字值，而不是文本。这种区别很重要，因为 MATCH 对两者处理方式不同：文本值被分析，而关键字值被精确比较，这类似于 MATCH 查询在索引关键字字段上的重写为词元查询的方式。TO_TEXT(x) 是告诉 ES|QL：<em>将此字符串视为全文本</em>。</p><p>此版本作为 Elasticsearch 9.5 的技术预览版，因此存在一些限制：</p><ul><li><p>当前仅在进行筛选。表达式上的 MATCH 还不会影响相关性得分；只有索引字段匹配才会影响得分。</p></li><li><p>匹配表达式时，尚不支持模糊性等查询选项。</p></li><li><p>运行时文本使用标准分析仪进行分析。此功能目前尚不可配置。</p></li></ul><p>目前正在努力解决这些限制。</p><h2>为什么在 ES|QL 中使用全文本搜索而不是 LIKE 或 RLIKE？</h2><p>ES|QL 已经提供了两种无需索引即可搜索字符串的方法：LIKE（通配符模式）和 RLIKE（正则表达式）。两者都适用于任何字符串表达式，所以问 MATCH 增加了什么功能是很合理的。答案是<a href="https://www.elastic.co/docs/manage-data/data-store/text-analysis">分析</a>，这是一种更高级的搜索方式，采用词干提取和同义词等技术。它还使用了停用词处理。</p><p>LIKE 是简单的子字符串匹配，无需理解组成字符串的词语。例如，假设您正在寻找有关 fox 的日志消息：</p><p>由于大小写问题，这句话漏掉了“Fox spotted near the henhouse”，而与“Outfoxed by the competition”相符，但后者与狐狸根本无关。它在两个方面都存在缺陷，对大小写存在漏报，对隐藏在其他单词中的子字符串存在误报。</p><p>正则表达式可以解决大小写问题，但单词边界问题很快就会变得很棘手。类似这样的内容：</p><p>而且这还不完全正确。它漏掉了句末带 ! 或 ? 的 fox 符号，而且没有提到制表符、引号或括号。每次修复都会使模式变得更长，下一个阅读该查询的人必须反向工程才能弄清楚它实际在做什么。</p><p>MATCH 可以解决这个问题，因为它会将查询和值都通过<a href="https://www.elastic.co/docs/reference/text-analysis/analyzer-reference">分析器</a>进行处理，分析器会将文本分词为小写词元，然后将词元与词元进行匹配：</p><p>此查询将匹配“The quick brown fox”和“FOX spotted near the henhouse”之类的值，但不会匹配“Outfoxed by the competition”或“FOXTROT protocol enabled”，无论词语周围是否有任何标点符号。当然，这一切也适用于多词元查询，比如 MATCH(TO_TEXT(message), “brown fox”)，正如您所期望的那样。</p><p>目前正在努力启用 <a href="https://www.elastic.co/docs/reference/text-analysis/analysis-lang-analyzer">36 个专用语言分析器</a>，以支持对从未建立索引或映射的数据使用自然语言。</p><h2>未建立索引和未映射数据的全文本搜索用例</h2><p>上述示例搜索的是从<a href="https://www.elastic.co/docs/manage-data/data-store/mapping">映射字段</a>计算出的数值。ES|QL MATCH 在表达式上的更有趣的用例涉及以前根本无法搜索的数据。我们来看几个例子。</p><h3>如何在不添加映射的情况下，在 ES|QL 中搜索未映射字段</h3><p>有时您会故意在映射中省略某个字段，例如详细的堆栈跟踪或原始请求有效负载。您甚至可以省略故障排查块。对其中一个字段进行索引会占用每个文档的磁盘和堆空间，对于一个可能每季度查询一次的字段来说，这样做并不值得。</p><p>这一决定一直是最终的，因为<a href="https://www.elastic.co/search-labs/blog/esql-unmapped-fields">未映射字段</a>对查询完全不可见。在 Elasticsearch 9.5 中，您可以使用 SET unmapped_fields="load" 让 ES|QL 直接从源文档加载未映射的字段作为关键字。接下来，将其包装在 TO_TEXT 函数中，现在就可以对其进行全文本搜索了：</p><p>这里，stack_trace 从未被映射。所有数值均取自原始文档并进行实时分析。它们逐行匹配。这才是真正的工作，速度永远比不上<a href="https://www.elastic.co/docs/manage-data/data-store/index-basics">倒排索引</a>的查找速度。但现在，您之前未建立索引的字段已经可以搜索了。您可以保持映射在日常用例下的小规模，同时在关键时刻仍能回答季度性问题。</p><h3>无需重新索引即可对关键字字段进行全文本搜索</h3><p>关键字字段可以做很多事情。它们提供精确匹配、快速聚合和排序功能，这就是为什么很多字段最终都以这种方式映射的原因。但是映射关系是在数据到达时确定的，很容易出现这样的情况：您想对数据做一些与最初设想不同的事情。也许 product_name 被映射为关键字是因为仪表板会根据它进行聚合，然后，在收到一年的产品数据后，有人希望能够搜索 product_name 的值。</p><p>以前的做法是将映射更改为文本（或添加多字段），然后重新索引所有内容。这既费时又费钱，在很多情况下，用户根本不想费这个劲。新答案只需一次函数调用：</p><p>TO_TEXT 函数会将关键字值即时转换为文本，因此 MATCH 会分析这些文本，而不是对它们进行精确比较。这允许您查询关键字字段，而无需创建映射或重新索引源文档。如果搜索成为日常查询，那么将字段索引为文本仍然是正确的长期举措，但是 TO_TEXT 今天无需任何额外工作即可为您找到答案。</p><h3>在不同映射的索引上搜索同一字段</h3><p><a href="https://www.elastic.co/docs/reference/query-languages/esql/esql-multi-index">ES|QL 可以跨越许多索引</a>，而且同一个字段不必在所有索引中看起来都一样。当同一个字段在不同的索引中具有不同的类型时，ES|QL 将其视为联合类型，转换函数可以解决冲突。我们来看一个例子，今年的索引模板中消息字段的类型是文本，但去年的索引模板中消息字段的类型是关键字：</p><p>每个值在查询时都会被分析，无论是来自文本索引还是关键字索引。旧索引中的关键字值被分词并小写，因此“connection reset”无论存在哪个索引，都能找到“Connection RESET by peer”。</p><p>另一个有趣的例子是，一个字段仅映射在一个索引中，但也存在于另一个索引中（且未映射）：</p><p>这里有一个细微差别值得一提。如果 error_details 已映射到 logs-2026 但未映射到 logs-2025，Elasticsearch 无法将此查询下推到 <a href="https://lucene.apache.org/">Lucene</a>，因为未映射该字段的索引将默默地返回无匹配项。相反，计划者注意到该字段可能未映射，并逐行评估整个 MATCH，无论这些行来自何处。您无需知道哪些索引映射了该字段；查询只需回答这个问题即可。</p><h2>ES|QL 如何在不使用倒排索引的情况下在查询时分析文本</h2><p>当 ES|QL 计划对表达式执行 MATCH 时，它会预先将查询字符串分析成一组词元。每一行的具体求值方式取决于表达式的类型：</p><p><strong>表达式类型</strong></p><p><strong>处理</strong></p><p><strong>匹配行为</strong></p><p>文本（通过 TO_TEXT）</p><p>分析器将值分词为小写词元</p><p>词元对词元比较；如果任何词元等于任何查询词，则该行匹配（OR 语义）</p><p>关键字、IP、日期、数字</p><p>未进行分析；查询常量已转换为原生类型一次</p><p>每行精确比较</p><p>两条路径都完全绕过 Lucene，逐行评估值。非文本路径与针对这些字段类型向下推送到 Lucene 的匹配查询完全一致，因此无论查询是否命中索引，语义都保持一致。</p><p>倒排索引查找在数据摄取时进行，在查询时从不触及不匹配的文档。运行时 MATCH 会在查询时，对到达的每一行进行分析。一种方法速度快，因为工作已经完成；另一种方法灵活，因为数据根本不需要建立索引。</p><h2>ES|QL 全文本搜索的下一步是什么</h2><p>这篇文章中的所有内容都是更大努力的第一部分，目的是让 ES|QL 中的搜索适用于任何内容，而不仅仅是您提前索引的内容。之前提到的限制正在积极解决，路线图还会进一步改进：</p><ul><li><p><strong>得分。</strong>运行时匹配将计入 _score，因此即使数据从未被索引，您也可以按相关性排序。</p></li><li><p><strong>MATCH_PHRASE</strong><strong> 表达式。</strong>已在 Elastic Cloud Serverless 中提供，并将于 9.6 中引入 Elastic Stack。</p></li><li><p><strong>可配置分析器。</strong>分析器支持对表达式使用 MATCH 和 MATCH_PHRASE，从而在查询时启用语言分析器、词干提取和同义词。</p></li><li><p><strong>匹配选项。</strong>运行时匹配的选项，如模糊度和操作符。</p></li><li><p><strong>向量搜索。</strong>为每一行生成嵌入，并对运行时 dense_vector 表达式运行 k 近邻 (kNN)，从而将语义搜索也带到未建立索引的数据。</p></li></ul><h2>立即尝试对表达式进行 ES|QL 全文本搜索</h2><p>今天就可以尝试运行时搜索。它现已在 Elastic Cloud Serverless 中推出，其中首先推出了新的 ES|QL 功能，并在 Elasticsearch 9.5 中作为技术预览版发布。从<a href="https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/search-functions">搜索函数</a>参考开始，查看 <a href="https://www.elastic.co/docs/reference/query-languages/esql/limitations">ES|QL 限制</a>页面，以了解当前边界。这是一个技术预览版，因为我们需要您的反馈：如果您搜索的内容从未被编入索引，无论是正面还是负面让您感到惊讶，<a href="https://www.elastic.co/cn/community">我们非常希望听到您的反馈</a>。</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/full-text-search-unindexed-data</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/full-text-search-unindexed-data</guid>
    <category><![CDATA[ES|QL]]></category>
    <category><![CDATA[映射]]></category>
    <dc:creator><![CDATA[Kevin Corcoran,Ioana Tagirta]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9a74e633d05585a8/6a730774b8c2e64c3ebe0fd4/image1.jpg" length="0" type="image/jpeg"/>
    <pubDate>Wed, 05 Aug 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[不到一分钟即可提示进入仪表板，价格便宜 5 倍：Kibana 中的 AI 仪表板和自定义 Vega-Lite 图表]]></title>
    <description><![CDATA[用自然语言描述您的指标，Kibana 的 AI 聊天功能即可生成 ES|QL 支持的仪表板和 Vega-Lite 图表，从散点图到条件格式和自定义工具提示，应有尽有。]]></description>
    <content:encoded><![CDATA[<p>Kibana 的<a href="https://www.elastic.co/docs/explore-analyze/ai-features/agent-builder/chat"> AI 聊天功能</a>现在可以在不到一分钟的时间内根据自然提示构建完整的 <a href="https://www.elastic.co/docs/explore-analyze/visualize/esorql">Elasticsearch 查询语言 (ES|QL) 支持的</a>仪表板。在 Elastic 9.5 中，这转向了正式可用性 (GA)（<a href="https://www.elastic.co/cn/search-labs/blog/ai-dashboard-generation-elastic-agent-kibana">9.4 的技术预览版</a>），错误恢复可重试失败的查询，通过分层模型路由和交互式筛选控件将 ES|QL 生成成本降低 5 倍。此版本还增加了通过自然语言创建 <a href="https://www.elastic.co/docs/explore-analyze/visualize/custom-visualizations-with-vega">Vega-Lite</a> 图表的功能，包括散点图、箱形图、条件格式和通常需要在 JSON 中手动编码的自定义工具提示。</p><h2>Kibana AI 仪表板创建的新增功能</h2><p><strong>功能</strong></p><p><strong>技术预览版 (9.4)</strong></p><p><strong>GA (9.5)</strong></p><p>错误处理能力</p><p>无法重试失败的 ES|QL 查询</p><p>自动重试最多三次，并进行查询检查和调整</p><p>ES|QL 生成成本</p><p>所有查询均通过主模型路由</p><p>分层模型路由，价格最多可降低 5 倍</p><p>时间范围</p><p>固定默认窗口</p><p>基于数据时间分布的自动选择</p><p>筛选控件</p><p>不支持</p><p>自动为相关性最高的字段添加</p><p>Vega-Lite 图表</p><p>不支持</p><p>自然语言创建，包括散点图、箱形图、条件格式、自定义工具提示</p><p>图表编辑</p><p>不支持</p><p>通过自然语言编辑现有的 Vega-Lite 面板</p><h3>AI 仪表板生成的自动错误恢复</h3><p>在技术预览版中，代理不会重试失败的 ES|QL 查询。在 9.5 中，它检测查询错误并重试最多三次，检查每个错误并调整查询，然后再放弃。实际上，这消除了大部分空面板问题，并生成首次尝试即可正确渲染的仪表板。</p><h3>为什么在 Elastic 9.5 中创建 AI 仪表板更便宜？</h3><p>并非仪表板生成过程中的每一步都需要同等程度的推理。在 9.5 中，ES|QL 生成默认使用较轻量级模型，仅在需要时才回退到主模型。如果您的<a href="https://www.elastic.co/docs/explore-analyze/ai-features/agent-builder/connectors">连接器</a>使用 Anthropic 的 Claude Opus 4.8，这意味着所有面板的 ES|QL 生成成本将降低 5 倍。</p><h3>根据您的数据自动选择时间范围</h3><p>仪表板只有在显示正确的数据窗口时才有用。现在，代理会应用改进的逻辑来选择适配查询数据的合理时间范围，除非用户要求特定的时间范围。它会考虑数据的时间分布并进行相应的调整，无论是实时事件的最后一小时，还是趋势分析的过去 90 天，而不是默认使用固定的时间窗口。</p><h3>AI 生成的仪表板上的自动筛选控件</h3><p>仪表板创建现在支持控件；也就是说，交互式筛选器允许查看者按字段值缩小仪表板范围，而无需编辑底层查询。生成仪表板时，代理会自动在顶部添加控件，用于筛选最相关的字段。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4a5520e66ae76001/6a719a218a155220ed6498e7/image3.png" alt="Kibana AI chat generating an ES|QL-backed host metrics dashboard with automatic filter controls in 71 seconds" /><h2>使用自然语言创建 Vega-Lite 图表：图表类型和默认之外的格式</h2><p><a href="https://vega.github.io/vega/">Vega</a> 和 <a href="https://vega.github.io/vega-lite/examples/">Vega-Lite</a> 支持 Kibana 中的各种图表类型和自定义功能。使用 9.5，您可以直接用纯语言构建它们，而无需自己编写代码。</p><h3>散点图、箱形图以及更多 Vega-Lite 图表类型</h3><p>散点图、箱形图、分面小倍数、气泡图和组成图（类似将直方图与热图结合）等多种图表均由 <a href="https://vega.github.io/vega-lite/examples/">Vega-Lite</a> 支持。类似“<em>向我显示响应时间与请求大小的散点图，按服务名称着色</em>”这样的提示会生成一个具有正确数据映射的 Vega-Lite 面板。他们使用 Kibana 的默认调色板与仪表板的其余部分融为一体。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf1651fe7ac903d47/6a719a49f124649f746fc1b4/image5.png" alt="Kibana dashboard with four Vega-Lite charts: box plot, bubble chart, faceted small multiples, and heatmap." /><h3>标准图表上的条件格式、自定义工具提示和标签</h3><p>即使对于仪表板中已有的图表类型（如柱形图、折线图或面积图），有时您也需要比默认功能提供的更多控制。Vega-Lite 通过聊天功能填补了这一空白。一些示例包括：</p><ul><li><p><strong>条件颜色格式：</strong>以不同的颜色标记高于阈值的数据点；例如，当某些指标的峰值超过您的服务级别目标 (SLO) 时，将折线或柱状图中的数据点标记为红色。您可以向代理提出类似的问题：<em>将折线图中所有高于 500 毫秒的点都变成红色。</em> </p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc1211784e9033557/6a719a6ab966e1736163d7d1/image1.png" alt="Vega-Lite line and bar charts in Kibana with conditional colour formatting showing data points above a threshold in red" /><p></p></li><li><p><strong>自定义标记和标签：</strong>向数据点添加表情符号、符号或行内文本标签，使状态指示器一目了然。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte3c59c3748484bc1/6a719aa02888394fdc07bac1/image2.png" alt="Lite horizontal bar chart in Kibana with emoji flag labels and custom tooltip showing requests by country" /><p></p></li><li><p><strong>自定义工具提示：</strong>使用不属于图表轴的其他指标、上下文或计算值来丰富悬停状态。可以提出类似这样的问题：<em>添加一个工具提示，显示总记录数和每条柱状图的百分比。</em></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt43f241f4382b5b90/6a719ab7ded0cf3367f49275/image4.png" alt="Vega-Lite stacked bar chart in Kibana with custom tooltip showing total records and percentage of total by extension" /><p></p></li></ul><p>这也适用于编辑现有的 Vega 图表。如果您的 Vega-Lite 面板需要进行一些调整，例如更改颜色标度、调整坐标轴或切换标记类型，请在聊天中描述更改，而不是深入研究 JSON 代码。</p><h2>我们如何在 Kibana 中构建自然语言 Vega-Lite 生成</h2><p>从句子生成 Vega-Lite 图表并不是一次性 <em>向模型请求 JSON</em> 提示。我们构建了一个小型的代理管道，将自然语言意图转化为经过验证、数据支持的图表。</p><p>当收到请求时，代理首先确定 Vega-Lite 是否合适。对于 Vega-Lite 请求，它将可视化建立在针对 Elasticsearch 的真实 ES|QL 查询之上，然后使用模型生成 Vega-Lite 代码。在渲染之前，结果会经过一个规范化层，纠正模式并绑定规范查询。它还应用了渲染安全转换。 </p><p>有几个设计选择使该工作流可靠：</p><ul><li><p><strong>类型化工具调用</strong>：图表创建是一个结构化的工具调用，而不是将自由格式的 Vega-Lite 粘贴到对话中。</p></li><li><p><strong>受限生成</strong>：模型在定义的模式内生成 Vega-Lite 代码，使输出更可预测且易于验证。</p></li><li><p><strong>精选示例：</strong>结构模式（如分面、分层标记和热图）可在不复制底层数据的情况下提供指导。</p></li><li><p><strong>执行和验证循环</strong>：在图表创作之前执行查询，验证失败会触发 ES|QL 生成的纠正性重试。</p></li></ul><h2>尝试在 Kibana 中创建 AI 仪表板和 Vega-Lite 图表</h2><p>要尝试使用自然语言仪表板创建和 Vega-Lite 图表，请升级到 <strong>Elastic 9.5</strong>（或<a href="https://cloud.elastic.co/registration">开始免费试用</a>），然后在 Kibana 中打开<strong>聊天</strong>。然后要求它根据您的数据构建仪表板。对于 Vega-Lite，您可以尝试请求创建想要但从未创建过的图表类型，例如散点图或气泡图。如果结果不完全准确，请告诉代理需要修改的内容。它会与您一起迭代。</p><p>这需要企业许可证。<a href="https://www.elastic.co/docs/explore-analyze/ai-features/agent-builder/chat#get-started">开始使用</a>。</p><p><em>本文中描述的任何功能或功能性的发布和时间均由 Elastic 自行决定。当前尚未发布的任何功能或功能性可能无法按时提供或根本无法提供。</em></p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/ai-dashboards-kibana-vega-lite</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/ai-dashboards-kibana-vega-lite</guid>
    <category><![CDATA[Kibana]]></category>
    <category><![CDATA[ES|QL]]></category>
    <dc:creator><![CDATA[Marta Bondyra,Teresa Alvarez Soler]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt54406ad0378bc5fc/6a7199ffed03ccee0dac9d7c/image6.png" length="0" type="image/png"/>
    <pubDate>Tue, 04 Aug 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[LINQ to Elasticsearch ES|QL：编写 C# 代码，查询 Elasticsearch]]></title>
    <description><![CDATA[探索 Elasticsearch .NET 客户端中全新的 LINQ to Elasticsearch ES|QL 提供程序。借助该程序，您可以编写会自动转换为 ES|QL 查询的 C# 代码。]]></description>
    <content:encoded><![CDATA[<p>从 <strong>v9.3.4</strong> 和 <strong>v8.19.18</strong> 开始，Elasticsearch .NET 客户端包含一个<a href="https://learn.microsoft.com/en-us/dotnet/csharp/linq/">语言集成查询 (LINQ) </a>提供程序，可在运行时将 C# LINQ 表达式转换为 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/esql.html">Elasticsearch 查询语言 (ES|QL)</a> 查询。您可以使用<code>Where</code>、<code>Select</code>、<code>OrderBy</code>、<code>GroupBy</code> 和其他标准操作符来编写查询，而无需手工编写 ES|QL 字符串。提供程序负责转换、参数化和结果反序列化，包括按行流式传输，无论结果集大小如何，都能保持稳定的内存使用量。</p><h2>您的第一个查询</h2><p>首先定义一个映射到 Elasticsearch 索引的普通旧 CLR 对象 (POCO)。属性名称通过标准<code>System.Text.Json</code> 属性（如<code>[JsonPropertyName]</code>）或配置的<code>JsonNamingPolicy</code> 解析为 ES|QL 列名。适用于客户端其他部分的<a href="https://www.elastic.co/docs/reference/elasticsearch/clients/dotnet/source-serialization">源序列化</a>规则在这里也同样适用。</p>using System.Text.Json.Serialization;

public class Product
{
    [JsonPropertyName("product_id")]
    public string Id { get; set; }

    public string Name { get; set; }

    public string Brand { get; set; }

    [JsonPropertyName("price_usd")]
    public double Price { get; set; }

    [JsonPropertyName("in_stock")]
    public bool InStock { get; set; }
}<p>类型设置完成后，查询语句如下所示：</p>var minPrice = 100.0;
var brand = "TechCorp";

await foreach (var product in client.Esql.QueryAsync&lt;Product&gt;(q =&gt; q
    .From("products")
    .Where(p =&gt; p.InStock &amp;&amp; p.Price &gt;= minPrice &amp;&amp; p.Brand == brand)
    .OrderByDescending(p =&gt; p.Price)
    .Take(10)))
{
    Console.WriteLine($"{product.Name}: ${product.Price}");
}<p>该提供程序将此转换为以下 ES|QL：</p><p>需要注意的一些细节：</p><ul><li><p><strong>属性名称解析：</strong>由于 <code>[JsonPropertyName]</code> 属性，<code>p.Price</code> 变成了 <code>price_usd</code>，根据默认 camelCase 命名策略，<code>p.Brand</code> 变成 <code>brand</code>。</p></li><li><p><strong>参数捕获：</strong>C# 变量 <code>minPrice</code> 和 <code>brand</code> 被捕获为命名参数 (<code>?minPrice</code>，<code>?brand</code>)。它们与 JSON 有效负载中的查询字符串分开发送，这样可以防止注入，并实现服务器端查询计划缓存。</p></li><li><p><strong>流式传输：</strong><code>QueryAsync&lt;T&gt;</code> 返回 <code>IAsyncEnumerable&lt;T&gt;</code>。从 Elasticsearch 返回数据时，数据会逐行具体化。</p></li></ul><p>您还可以在不执行的情况下检查生成的查询及其参数：</p>var query = client.Esql.CreateQuery&lt;Product&gt;()
    .Where(p =&gt; p.InStock &amp;&amp; p.Price &gt;= minPrice &amp;&amp; p.Brand == brand)
    .OrderByDescending(p =&gt; p.Price)
    .Take(10);

Console.WriteLine(query.ToEsqlString());
// FROM products | WHERE (in_stock == true AND price_usd &gt;= 100) | SORT price_usd DESC | LIMIT 10

Console.WriteLine(query.ToEsqlString(inlineParameters: false));
// FROM products | WHERE (in_stock == true AND price_usd &gt;= ?minPrice AND brand == ?brand) | SORT price_usd DESC | LIMIT 10

var parameters = query.GetParameters();
// { "minPrice": 100.0, "brand": "TechCorp" }<h2>这如何运作？快速回顾一下 LINQ</h2><p>使 LINQ 提供程序成为可能的机制是 <code>IEnumerable&lt;T&gt;</code> 和 <code>IQueryable&lt;T&gt;</code> 之间的区别。</p><p>在 <code>IEnumerable&lt;T&gt;</code> 上调用 <code>.Where(p =&gt; p.Price &gt; 100)</code> 时，lambda 会编译为 <code>Func&lt;Product, bool&gt;</code>，即一个由运行时在进程内执行的常规委托。这就是 LINQ-to-Objects。</p><p>当您在<code>IQueryable&lt;T&gt;</code> 上调用相同的方法时，C# 编译器会将 lambda 封装在<code>Expression&lt;Func&lt;Product, bool&gt;&gt;</code> 中。这是一种数据结构，表示代码的<em>结构</em>，而不是代码的可执行形式。在运行时，该表达式树可被检查、分析，并转换为另一种语言。</p>// IEnumerable: the lambda is a compiled delegate
IEnumerable&lt;Product&gt; local = products.Where(p =&gt; p.Price &gt; 100);

// IQueryable: the lambda is an expression tree, a data structure
IQueryable&lt;Product&gt; remote = queryable.Where(p =&gt; p.Price &gt; 100);<p><code>IQueryProvider</code> 接口是扩展点。任何提供程序均可通过实现 <code>CreateQuery&lt;T&gt;</code> 和 <code>Execute&lt;T&gt;</code>，将这些表达式树转换为目标语言。实体框架就是利用此机制生成 SQL 语句。LINQ to ES|QL 提供程序使用它来生成 ES|QL 查询。</p><p>上述查询的表达式树如下所示：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt521838e8b9c36649/6a1705b1839dfa5f40dcfdfe/f864cd18a390831f8d28503a29b5835efb1842f7-1000x720.png" alt="示例查询的表达式树。" /><p><em>示例查询的表达式树。</em></p><p>此表达式树由内而外嵌套：<code>Take</code> 包裹着 <code>OrderByDescending</code>，它又包裹着 <code>Where</code>，而后者再包裹着 <code>From</code>，而最内层是根节点 <code>EsqlQueryable&lt;Product&gt;</code> 常量。对于 <code>&amp;&amp;</code>、<code>&gt;=</code> 和 <code>==</code> 这几种操作符而言，<code>Where</code> 谓词本身是一个由 <code>BinaryExpression</code> 个节点构成的子树，其中包含 <code>MemberExpression</code> 个叶子节点，这些叶子节点用于属性访问，以及对 <code>minPrice</code> 和 <code>brand</code> 变量的闭包捕获。提供程序会遍历这一数据结构，从而生成最终的 ES|QL 查询。</p><h2>深入了解：转换管道</h2><p>从 LINQ 表达式到查询结果的路径遵循六阶段管道：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt930670a505dd61ea/6a1705b3b339d58a54769ecf/2a2c772b63d720f61fc9a28b2f85668fa2db8d38-1999x1036.png" alt="转换管道概述。" /><p><em>转换管道概述。</em></p><h3>1. 表达式树捕获</h3><p>当在一个 <code>IQueryable&lt;T&gt;</code> 对象上串联使用 <code>.Where()</code>、<code>.OrderBy()</code>、<code>.Take()</code> 及其他操作符时，标准的 LINQ 基础架构会构建一个表达式树。<code>EsqlQueryable&lt;T&gt;</code> 实现了 <code>IQueryable&lt;T&gt;</code> 接口，并将处理委托给 <code>EsqlQueryProvider</code>。</p><h3>2. 翻译</h3><p>当查询被执行 (通过枚举、调用 <code>ToList()</code>，或使用 <code>await foreach)</code> 时)，<code>EsqlExpressionVisitor</code>自内而外遍历表达式树。它会将每个 LINQ 方法调用分派给一个专门的访问器进行处理：</p><p>访客</p><p>翻译</p><p>进入</p><p>WhereClauseVisitor</p><p>.Where(predicate)</p><p>WHERE 条件</p><p>SelectProjectionVisitor</p><p>.Select(selector)</p><p>评估 + 保留 + 重命名</p><p>GroupByVisitor</p><p>.GroupBy().Select()</p><p>统计信息 ... 依据</p><p>OrderByVisitor</p><p>.OrderBy() / .ThenBy()</p><p>SORT 字段 [ASC\|DESC]</p><p>EsqlFunctionTranslator</p><p>EsqlFunctions.*、Math.*、字符串方法</p><p>80+ ES|QL 函数</p><p>在翻译过程中，表达式中引用的 C# 变量被捕获为命名参数。</p><h3>3. 查询模型</h3><p>访问器不会直接生成字符串。相反，它们会产生 <code>QueryCommand</code> 对象，一个不可变的中间表征。一个 <code>FromCommand</code>、一个 <code>WhereCommand</code>、一个 <code>SortCommand</code> 和一个 <code>LimitCommand</code>，各代表一条 ES|QL 处理命令。这些数据被收集到<code>EsqlQuery</code> 模型中。</p><p></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt788c9936976f2f62/6a1705b50e2e4910da419ff0/2adc349b6cf655b96b7b3e826a134e8a17fe42fd-1999x1036.png" alt="查询模型和命令模式。" /><p><em>查询模型和命令模式。</em></p><p>该中间模型与表达式树和输出格式均解耦。它可以被检查、拦截（通过 <code>IEsqlQueryInterceptor</code>）或在格式化前进行修改。</p><h3>4. 格式化</h3><p><code>EsqlFormatter</code> 依次访问每个<code>QueryCommand</code> ，并生成最终的 ES|QL 字符串。每条命令占一行，通过 ES|QL 中用于串联处理命令的管道 (|) 运算符分隔。若标识符包含特殊字符，系统会自动用反引号进行转义处理。</p><h3>5. 执行</h3><p>格式化后的 ES|QL 查询字符串及捕获的参数会以 JSON 数据载荷的形式发送至 Elasticsearch 的 <code>/_query</code> 终端。而 <code>IEsqlQueryExecutor</code> 接口则对传输层进行了抽象封装，这正是分层包架构发挥作用的关键环节。</p><h3>6. 实现</h3><p><code>EsqlResponseReader</code> 流式传输JSON响应，但不会将整个结果集缓冲到内存中。以流式方式传输 JSON 响应数据，无需将整个结果集缓存至内存。针对每次查询预先计算生成的 <code>ColumnLayout</code> 树结构，会将扁平化的 ES|QL 列名（如 <code>address.street</code>、<code>address.city</code>）映射到嵌套的 POCO 属性。每行数据会被组装为 <code>T</code> 实例，并通过 <code>IEnumerable&lt;T&gt;</code> 或 <code>IAsyncEnumerable&lt;T&gt;</code> 逐个返回。</p><h2>分层架构</h2><p>LINQ to ES|QL 功能分为三个软件包：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt662bd0dd8861b6b6/6a1705b7a929cf7086ae08a2/41b8aae860ecdc2480edcb1c1d4cc9b03cfb78c9-1999x1036.png" alt="软件包架构。" /><p><em>软件包架构。</em><a href="https://www.nuget.org/packages/Elastic.Esql"><strong><code>Elastic.Esql</code></strong></a> 是纯转换引擎。该组件完全不依赖 HTTP 协议栈，集成了表达式访问器、查询模型、格式化器及响应解析器等核心模块。您可独立使用它来构建和检查 ES|QL 查询（无需连接 Elasticsearch），这在测试验证、查询日志记录或自定义执行层开发等场景中极具实用价值。翻译要点解析：</p>// Translation-only: no Elasticsearch connection needed
var provider = new EsqlQueryProvider();
var query = new EsqlQueryable&lt;Product&gt;(provider)
    .From("products")
    .Where(p =&gt; p.InStock)
    .OrderByDescending(p =&gt; p.Price);

Console.WriteLine(query.ToEsqlString());
// FROM products | WHERE in_stock == true | SORT price_usd DESC<p><a href="https://www.nuget.org/packages/Elastic.Clients.Esql"><strong><code>Elastic.Clients.Esql</code></strong></a> 是一款轻量级的独立 ES|QL 客户端。该组件通过 <code>Elastic.Transport</code> 在 <code>Elastic.Esql</code> 之上扩展了 HTTP 协议执行能力。如果您的应用程序仅需使用 ES|QL 而无需其他 Elasticsearch API，此方案可实现最小化依赖集成。</p><p><a href="https://www.nuget.org/packages/Elastic.Clients.Elasticsearch"><strong><code>Elastic.Clients.Elasticsearch</code></strong></a> 是完整的 Elasticsearch.NET 客户端。它还建立在<code>Elastic.Esql</code> 的基础上，并通过<code>client.Esql</code> 命名空间公开 LINQ 提供程序接口。这是大多数应用程序的推荐入口点。</p><p>两个执行层组件包均提供了针对 <code>IEsqlQueryExecutor</code> 接口的独立实现。该策略接口作为转换与传输层的桥梁。</p><p>当与源码生成的 <code>JsonSerializerContext</code> 配合使用时，这三个组件包均支持原生 AOT 编译。如需完整客户端集成方案，请参阅<a href="https://www.elastic.co/docs/reference/elasticsearch/clients/dotnet/source-serialization#native-aot">原生 AOT 文档</a>。</p><h2>不只使用基础功能</h2><p>上面的例子涵盖了筛选、排序和分页。该提供程序支持更广泛的操作范围。</p><h3>聚合</h3><p><code>GroupBy</code>结合 <code>Select</code> 中的聚合函数，转换为 ES|QL <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/stats-by"><code>STATS ... BY</code></a>：</p>var stats = client.Esql.Query&lt;Product, object&gt;(q =&gt; q
    .GroupBy(p =&gt; p.Brand)
    .Select(g =&gt; new
    {
        Brand = g.Key,
        Count = g.Count(),
        AvgPrice = g.Average(p =&gt; p.Price),
        MaxPrice = g.Max(p =&gt; p.Price)
    }));

// -&gt; FROM products | STATS COUNT(*), AVG(price_usd), MAX(price_usd) BY brand<h3>投影</h3><p><code>Select</code>，使用匿名类型生成 <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/eval"><code>EVAL</code></a>、<a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/keep"><code>KEEP</code></a> 和 <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/rename"><code>RENAME</code></a> 命令：</p>var query = client.Esql.CreateQuery&lt;Product&gt;()
    .Select(p =&gt; new { ProductName = p.Name, p.Price, p.InStock });

// -&gt; FROM products | KEEP name, price_usd, in_stock | RENAME name AS ProductName<h3>丰富的函数库</h3><p>通过 <code>EsqlFunctions</code> 类，可以使用超过 80 个 ES|QL 函数，涵盖日期/时间、字符串、数学、IP、模式匹配和评分。标准 <code>Math.*</code> 和 <code>string.*</code> 方法也已转换：</p>.Where(p =&gt; p.Name.Contains("Pro"))       // -&gt; WHERE name LIKE "*Pro*"
.Where(p =&gt; EsqlFunctions.CidrMatch(      // -&gt; WHERE CIDR_MATCH(ip, "10.0.0.0/8")
    p.IpAddress, "10.0.0.0/8"))<h3>查找连接</h3><p>跨索引查找转换为 ES|QL <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/lookup-join"><code>LOOKUP JOIN</code></a>：</p>var enriched = client.Esql.Query&lt;Product, object&gt;(q =&gt; q
    .LookupJoin&lt;Product, CategoryLookup, string, object&gt;(
        "category-lookup-index",
        product =&gt; product.Id,
        category =&gt; category.CategoryId,
        (product, category) =&gt; new { product.Name, category!.CategoryLabel }));<h3>原生 ES|QL 直通接口</h3><p>对于 LINQ 提供程序尚未涵盖的 ES|QL 功能，您可以添加原始片段：</p>var results = client.Esql.Query&lt;Product&gt;(q =&gt; q
    .Where(p =&gt; p.InStock)
    .RawEsql("| EVAL discounted = price_usd * 0.9"));<h3>服务器端异步查询</h3><p>对于长时间运行的查询，可将其提交给服务器进行后台处理：</p>await using var asyncQuery = await client.Esql.SubmitAsyncQueryAsync&lt;Product&gt;(
    q =&gt; q.Where(p =&gt; p.InStock),
    asyncQueryOptions: new EsqlAsyncQueryOptions
    {
        WaitForCompletionTimeout = TimeSpan.FromSeconds(5),
        KeepAlive = TimeSpan.FromMinutes(10)
    });

await asyncQuery.WaitForCompletionAsync();
await foreach (var product in asyncQuery.AsAsyncEnumerable())
    Console.WriteLine(product.Name);<p>服务器端异步查询对于长时间运行的分析型查询/大规模数据集处理尤其有用，这类操作可能会超出常规的超时阈值；在存在负载均衡器、API 网关或代理（这些组件会强制执行严格的 HTTP 超时设置）的超时敏感环境中，异步查询同样优势显著。异步查询通过将查询提交与结果获取解耦，避免了连接中断的情况。</p><h2>开始使用</h2><p>LINQ to ES|QL 自以下版本起可用：</p><ul><li><p><strong>Elastic.Clients.Elasticsearch v9.3.4</strong>（9.x 分支）</p></li><li><p><strong>Elastic.Clients.Elasticsearch v8.19.18</strong>（8.x 分支）</p></li></ul><p>从 NuGet 安装：</p><p><code>dotnet add package Elastic.Clients.Elasticsearch</code></p><p>入口点位于<code>client.Esql</code>上：</p><p>方法</p><p>返回值</p><p>用例</p><p>查询&lt;T&gt;(...)</p><p>IEnumerable&lt;T&gt;</p><p>同步执行</p><p>QueryAsync&lt;T&gt;(...)</p><p>IAsyncEnumerable&lt;T&gt;</p><p>异步流式处理</p><p>CreateQuery&lt;T&gt;()</p><p>IEsqlQueryable&lt;T&gt;</p><p>高级结构分析和检测</p><p>SubmitAsyncQueryAsync&lt;T&gt;(...)</p><p>EsqlAsyncQuery&lt;T&gt;</p><p>长时间运行的服务器端查询</p><p>有关完整的功能参考，包括查询选项、多字段访问、嵌套对象和多值字段处理，请参阅<a href="https://www.elastic.co/docs/reference/elasticsearch/clients/dotnet/linq-to-esql">LINQ to ES|QL 文档</a>。</p><h2>结论</h2><p>LINQ 转 ES|QL 将 C# LINQ 的强大表达能力引入到 Elasticsearch 的 ES|QL 查询语言中，让您无需手工编写查询字符串，就能生成强类型、可组合的查询。它具备自动参数捕获、流式物化功能，还拥有分层式的软件包架构，既能满足独立转换需求，也能适配完整的 Elasticsearch 客户端，可自然融入任意规模的 .NET 应用程序。安装最新客户端，将 LINQ 表达式指向索引，剩下的就交给该提供程序来处理。</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/linq-esql-c-elasticsearch-net-client</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/linq-esql-c-elasticsearch-net-client</guid>
    <category><![CDATA[ES|QL]]></category>
    <category><![CDATA[向量数据库]]></category>
    <dc:creator><![CDATA[Florian Bernd,Martijn Laarman]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdfa35fbcbbf4959f/6a1705b9dc55de19a4e00d07/e54132e915217063e9ed0ec45059c6cfc38e31dd-1280x720.png" length="0" type="image/png"/>
    <pubDate>Wed, 01 Apr 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[基于 Swiss 式哈希表实现更快的 ES|QL 统计数据]]></title>
    <description><![CDATA[受 Swiss 启发的哈希和 SIMD 友好型设计如何为 Elasticsearch 查询语言 (ES|QL) 中提供稳定、可衡量的速度提升。]]></description>
    <content:encoded><![CDATA[<p>我们近期将 Elasticsearch 哈希表实现的核心组件替换为 Swiss 式设计，在均匀高基数工作负载下观察到构建与迭代速度提升 2-3 倍。最终使 Elasticsearch 查询语言 (ES|QL) 的统计数据与分析操作实现更低延迟、更高吞吐量，且性能表现更可预测。</p><h2>为什么这很重要</h2><p>绝大多数典型分析流程最终都归结为数据分组操作。无论是计算每台主机的平均字节数、统计每个用户的事件数量，还是跨维度聚合指标，其核心操作始终如一，那就是将键映射到分组并更新累计聚合值。</p><p>在小规模场景下，几乎任何合理的哈希表都能良好运行。但在大规模场景（数亿文档、数百万独立分组）中，细节决定成败。负载因素、探测策略、内存布局和缓存行为，这些因素可能让性能呈现线性增长，也可能导致严重的缓存未命中问题。</p><p>Elasticsearch 多年来一直支持这些工作负载，但我们一直在寻找机会来更新核心算法。因此，我们评估了一种受 Swiss 表启发的新方法，并将其应用于 ES|QL 如何计算统计数据。</p><h2>到底什么是 Swiss 表？</h2><p>Swiss 表是一类由 Google SwissTable 推广的现代哈希表系列，后被 Abseil 等资料库采纳。</p><p>传统哈希表在探测过程中需频繁追踪指针或加载键值，却发现大量不匹配情况。Swiss 哈希表的核心创新在于通过独立于键值存储的微型缓存驻留数组结构（称为<em>控制字节</em>），可拒绝大多数探测，从而显著降低内存流量。</p><p>每个控制字节对应一个哈希槽，在我们的应用中编码两类信息：槽是否为空，以及从哈希值派生的短指纹。这些控制字节在内存中连续存储（通常以 16 字节为一组），使其非常适合<a href="https://en.wikipedia.org/wiki/Single_instruction,_multiple_data">单指令多数据</a> (SIMD) 并行处理。</p><p>Swiss 表摒弃逐槽探测的传统方式，转而通过向量指令一次性扫描整个控制字节块。CPU 在单次操作中，将待插入键的指纹与 16 个槽位的指纹进行批量比对，并过滤掉空条目。仅当少数候选键通过这一快速通道后，才需要加载并比对实际键值。</p><p>该设计通过引入少量额外元数据，换取了更高的缓存命中率和大幅减少的随机内存访问。随着哈希表规模扩大及探测链长度增加，这些特性将愈发凸显其价值。</p><h2>以SIMD为中心</h2><p>真正的主角是 SIMD。</p><p>控制字节不仅结构紧凑，更专门针对向量指令处理进行优化设计。单条 SIMD 比对指令可同时校验 16 个指纹，将传统循环操作转化为数条高效宽指令处理。例如：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1f710e87dd749ab3/6a170cc46234e052dadb1a49/bd418778f0c6144f8f5f18419f6220ac0c935c7a-903x407.png" alt="SIMD 是 Elasticsearch 的核心" /><p>实际上，这意味着：</p><ul><li><p>更少的分支。</p></li><li><p>更短的探针链。</p></li><li><p>减少键值存储的内存加载次数。</p></li><li><p>更好地利用了CPU的执行单元。</p></li></ul><p>绝大多数查询在控制字节扫描阶段即可完成过滤。当需要进一步处理时，剩余操作高度集中且可预测，而这正是现代 CPU 所擅长的负载类型。</p><h2>深入了解 SIMD</h2><p>对于喜欢探究底层实现细节的读者，以下是向表中插入新键时的具体流程：我们使用 Panama Vector API 配合 128 位向量，因此可并行处理 16 个控制字节。</p><p>以下代码片段展示了在配备 AVX-512 的 Intel Rocket Lake 处理器上生成的代码。虽然这些指令反映了当前硬件环境，但该设计并不依赖 AVX-512。在其他平台上会生成等效指令（如 AVX2、SSE 或 NEON）来实现相同的高层向量操作。</p>; Load 16 control bytes from the control block
vmovdqu xmm0, XMMWORD PTR [r9+r10*1+0x10]

; Broadcast the 7-bit fingerprint of the new key across the vector
vpbroadcastb xmm1, r11d

; Compare all 16 control bytes to the new fingerprint
vpcmpeqb k7, xmm0, xmm1
kmovq rbx, k7

; Check if any matches were found
test rbx, rbx
jne &lt;handle_match&gt;<p>每条指令在插入过程中都起着明确的作用：</p><ul><li><p><code>vmovdqu</code>：将 16 个连续的控制字节加载到 128 位 <code>xmm0</code> 寄存器中。</p></li><li><p><code>vpbroadcastb</code>：将新键的 7 位指纹复制到<code>xmm1</code>寄存器的所有向量通道中。</p></li><li><p><code>vpcmpeqb</code>：将每个控制字节与广播后的指纹进行并行比较，生成潜在匹配的掩码。</p></li><li><p><code>kmovq</code> + <code>test</code>：将掩码移动到通用寄存器，并快速检查是否存在匹配。</p></li></ul><p>最终，我们决定一次探测 16 个控制字节组，因为基准测试表明，扩展到 32 或 64 个字节并使用更宽的寄存器并没有带来明显的性能提升。</p><h2>ES|QL 中的集成</h2><p>在 Elasticsearch 中采用 Swiss 式哈希算法并非简单的替换操作。ES|QL 对内存核算、安全性以及与计算引擎其他部分的集成有着严苛要求。</p><p>我们将新型哈希表与 Elasticsearch 的内存管理机制深度集成，包括分页回收器和熔断器核算模块，确保内存分配始终透明且受控。Elasticsearch 的聚合数据采用密集存储方式并通过组 ID 索引，在保持内存布局紧凑、迭代高效的同时，通过支持随机访问实现了特定性能优化。</p><p>对于可变长度字节键，我们在存储组 ID 的同时缓存完整哈希值。此设计避免了探测过程中重复计算高开销的哈希码，并通过将关联元数据集中存储提升了缓存命中率。在重新哈希时，系统可直接利用缓存的哈希值和控制字节，无需检查键值本身，从而将容量调整成本降至最低。</p><p>我们实施中的一个重要简化策略是永不删除条目。这一设计消除了对<em>“墓碑”标记</em>（用于标识已释放槽位的占位符）的需求，使空槽保持真正空闲状态。这种优化进一步改善了探测行为，并确保控制字节扫描始终保持高效。</p><p>这样的设计在完美契合 Elasticsearch 执行模型的同时，保留了使 Swiss 表具吸引力的高性能特性。</p><h2>它的表现如何？</h2><p>在小规模数据量下，Swiss 表的性能与现有实现基本持平。这符合预期，当哈希表较小时，缓存效应的影响减弱，且待优化的探测操作本就较少。</p><p>随着数据规模扩大，性能特征迅速发生质变。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt09b13af2fe162f59/6a170cc66f7f04485c9148b8/24900afc47ab07b0e9933f6117b99d0f4613f794-962x599.png" alt="ES|QL 统计数据使用瑞士风格哈希表" /><p>上方热图展示了不同键大小（8、32、64 和 128 字节）在数据规模从 1,000 至 10,000,000 组变化时的时间优化倍数。随着数据规模扩大，优化倍数呈稳定上升趋势，在均匀分布场景下最高可达 2-3 倍。</p><p>这一趋势完全符合设计预期。传统哈希表在数据规模扩大时会导致探测链长度增加，而 Swiss 式探测仍能在支持 SIMD 指令的控制字节块内完成绝大多数查询操作。</p><h2>缓存行为说明了一切</h2><p>为深入分析加速效果，我们在 Linux <code>perf</code>环境下运行相同的 JMH <a href="https://github.com/elastic/elasticsearch/pull/139343/files#diff-d0e0cc91a7495bf36b2d44eacce95f5185d01879e5f6c38089ac7a89aad17da7"><code>benchmarks</code></a>基准测试，并采集缓存与 TLB 统计数据。</p><p>与原始实施相比，Swiss 版实现的总缓存引用量减少约 60%，末级缓存（LLC）加载次数下降超 4 倍，LLC 加载未命中次数更是降低超 6 倍。由于 LLC 未命中通常直接导致主存访问，仅此一项优化就解释了端到端性能提升的绝大部分原因。</p><p>在更靠近 CPU 的层级，我们观察到 L1 数据缓存未命中次数显著减少，数据 TLB 未命中次数更降低近 6 倍，这表明数据空间局部性增强且内存访问模式更具可预测性。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb987a5bd98c0d7eb/6a170cc8a929cf9655ae0a25/6e49b7609fba83e33692cb9834552b6ca7e42a83-998x499.png" alt="缓存执行：原始实施与采用 Swiss 式哈希表的 ES|QL 统计数据" /><p>这正是 SIMD 友好型控制字节带来的实际效益。无需反复从分散的内存位置加载键和值，大多数探测操作仅需扫描紧凑、驻留缓存的结构体即可完成。内存访问量减少意味着缓存未命中率降低，而未命中率降低则直接提升查询速度。</p><h2>总结</h2><p>通过采用 Swiss 式哈希表设计并深度融合 SIMD 友好型探测机制，我们在高基数 ES|QL 统计工作负载中实现了 2-3 倍的速度提升，同时获得了更稳定且可预测的系统表现。</p><p>本研究揭示了现代 CPU 感知型数据结构如何为哈希表等老问题带来显著性能提升。该领域仍有广阔探索空间，例如扩展至更多基础数据类型的特化实现，以及在连接等高基数操作路径中的应用。这些工作均属于 Elasticsearch 内核持续现代化这一长期工程的重要组成部分。</p><p>如需了解详细信息或跟进项目进展，可查看 GitHub 上的该<a href="https://github.com/elastic/elasticsearch/pull/139343">拉取请求</a>及追踪进度的<a href="https://github.com/elastic/elasticsearch/issues/138799">元议题</a>。</p><p>祝您哈希愉快！</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/esql-swiss-hash-stats</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/esql-swiss-hash-stats</guid>
    <category><![CDATA[ES|QL]]></category>
    <dc:creator><![CDATA[Chris Hegarty,Matthew Alp,Nik Everett]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf76dd688c5b737e6/6a170cc9839dfa7fc7dcff40/21036e031070f14faccb2b53b22723de2750c391-1280x720.png" length="0" type="image/png"/>
    <pubDate>Mon, 19 Jan 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[在 Google MCP Toolbox for Databases 中引入 Elasticsearch 支持]]></title>
    <description><![CDATA[了解 Google MCP Toolbox for Databases 现在如何提供 Elasticsearch 支持，并利用 ES|QL 工具将您的索引安全地集成到任何 MCP 客户端中。]]></description>
    <content:encoded><![CDATA[<p>在本文中，我们将介绍如何使用带有 <a href="https://github.com/elastic/elasticsearch">Elasticsearch</a> 的 Google MCP Toolbox 来构建一个用于从 Elasticsearch 索引中提取信息的简单工具。</p><p>我们最近为 <a href="https://github.com/googleapis/genai-toolbox">Google MCP Toolbox for Databases</a> 开源项目做出了贡献，为其添加了对 Elasticsearch 数据库的支持。</p><p>有了这项新功能，您现在可以使用 Google MCP Toolbox 连接到 Elasticsearch，并直接与数据“对话”。</p><h2>Elasticsearch</h2><p>我们需要运行一个 Elasticsearch 实例。您可以在 <a href="https://www.elastic.co/cloud">Elastic Cloud</a> 上激活免费试用版，或使用 <a href="https://github.com/elastic/start-local">start-local</a> 脚本在本地安装：</p>curl -fsSL https://elastic.co/start-local | sh<p>这将在计算机上安装 Elasticsearch 和 Kibana，并生成用于配置 Google MCP Toolbox 的 API 密钥。</p><p>API 密钥将显示为上一条命令的输出，并存储在 elastic-start-local 文件夹的 .env 文件中。</p><h2>安装示例数据集</h2><p>安装完成后，您可以使用启动本地脚本（存储在 .env 文件中）生成的用户名 <em>elastic</em> 和密码登录 Kibana。</p><p>您可以安装 Kibana 提供的<strong>电子商务订单</strong>数据集。它包含一个名为 <strong>kibana_sample_data_ecommerce</strong> 的单个索引，其中包含来自一家电子商务网站的 4,675 个订单的信息。对于每笔订单，我们都有以下信息：</p><ul><li><p>客户信息（姓名、ID 号码、出生日期、电子邮件等）</p></li><li><p>订单日期</p></li><li><p>订单编号</p></li><li><p>产品（包含价格、数量、ID、类别、折扣等信息的所有产品列表）</p></li><li><p>SKU</p></li><li><p>总价（不含税，含税）</p></li><li><p>总数量</p></li><li><p>地理信息（城市、国家、洲、位置、地区）</p></li></ul><p>要安装示例数据，请在 Kibana 中打开“<strong>集成</strong>”页面（在顶部搜索栏中搜索“集成”），然后安装“示例数据”。有关详细信息，请参阅此处的文档：<a href="https://www.elastic.co/docs/explore-analyze/#gs-get-data-into-kibana">https://www.elastic.co/docs/explore-analyze/#gs-get-data-into-kibana</a>。</p><p>本文旨在展示如何轻松配置 Google MCP Toolbox 以连接到 Elasticsearch，并使用自然语言与 <strong>kibana_sample_data_ecommerce</strong> 索引进行交互。</p><h2>Google MCP 工具箱</h2><p>Google MCP Toolbox 是一款开源 MCP 服务器，旨在使应用程序和 AI 代理能够轻松、安全、高效地与数据库进行交互。该项目以前称为“GenAI Toolbox for Databases”，在与<a href="https://www.anthropic.com/news/model-context-protocol">模型上下文协议</a> (MCP) 完全兼容后重新命名。其目的是通过在幕后处理连接池、身份验证、可观察性和其他操作问题，消除传统上需要将代理连接到数据库的繁重工作。</p><p>Toolbox 的核心功能是允许开发人员定义可重用的高级工具，封装数据库交互操作。然后，任何兼容 MCP 的客户端（如 AI 代理）都可以调用这些工具，而无需客户端执行低级 SQL 查询或管理数据库连接。这种方法大大减少了构建数据库感知代理所需的模板代码量，只需几行应用程序逻辑就能集成高级数据操作。一旦定义工具，就可以在多个代理、框架或语言之间共享（图 1）。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte90070297ee83546/6a16fa29964cea694b08b972/137cea290bb70ad5da21853f9a6358cef4cf7451-1248x1056.png" alt="" /><p>使用 Toolbox 的一大优势是内置的安全模型。原生支持 OAuth2 和 OIDC 等身份验证流程，使开发者避免在代理中处理或存储敏感的数据库凭据。该平台还通过 OpenTelemetry 提供可观测性功能（包括指标和跟踪），这对于调试、监控和生产部署至关重要。总而言之，MCP Toolbox 是一个统一、安全和可扩展的接口，可从任何支持 MCP 的系统与您的数据进行交互。</p><h2>如何安装 MCP Toolbox</h2><p>您可以使用以下命令在 Linux 上安装 MCP Toolbox 服务器：</p>export VERSION=0.21.0
curl -L -o toolbox https://storage.googleapis.com/genai-toolbox/v$VERSION/linux/amd64/toolbox
chmod +x toolbox<p>如果您想将其安装在 macOS 或 Windows 上，您可以按照<a href="https://googleapis.github.io/genai-toolbox/getting-started/introduction/#installing-the-server">此处</a>的详细说明进行操作。</p><h2>配置适用于 Elasticsearch 的 Toolbox</h2><p>要为 Elasticsearch 配置 MCP Toolbox，我们需要创建一个 <strong>tools.yaml</strong> 文件，如下所示：</p>sources:
  my-cluster:
    kind: elasticsearch
    addresses:
      - http://localhost:9200
    apikey: &lt;insert-here-api-key&gt;

tools:
  customer-orders:
    kind: elasticsearch-esql
    source: my-cluster
    description: Get the orders made by a customer identified by name.
    query: |
    	FROM kibana_sample_data_ecommerce | WHERE MATCH(customer_full_name, ?name, {"operator": "AND"})
    parameters:
      - name: name
        type: string
        description: The customer name.

toolsets:
  elasticsearch-tools:
    - customer-orders<p>您需要使用有效的 Elasticsearch API 密钥替换 <strong>&lt;insert-here-api-key&gt;</strong> 值。如果您使用 start-local 在本地运行 Elasticsearch，则可以在.env 文件中找到由 start-local 生成的 API 密钥，位于 <strong>ES_LOCAL_API_KEY</strong> 变量下。如果您正在使用 Elastic Cloud，则可以按照<a href="https://www.elastic.co/docs/deploy-manage/api-keys/elastic-cloud-api-keys">此处</a>所描述的步骤生成 API 密钥。</p><p>之前的工具包含以下适用于 Elasticsearch 的 ES|QL 查询：</p><p>如果您不熟悉 ES|QL，它是由 Elastic 开发的一种类似于 SQL 的查询语言，可用于在一个或多个索引中进行搜索。您可以在<a href="https://www.elastic.co/docs/reference/query-languages/esql">此处</a>的正式文档中阅读有关 ES|QL 的更多信息。</p><p>上述查询使用 <strong>?name</strong> 参数（问号表示参数）搜索存储在 <strong>kibana_sample_data_ecommerce</strong> 索引中所有包含指定客户姓名的订单。</p><p>在之前的 YAML 配置中，客户名称使用字符串类型并附带描述“客户名称”来定义。</p><p>此工具可用于回答有关客户订单的问题——例如：<em>客户 Foo 在 2025 年 10 月下了多少订单？</em></p><p>对工具及其参数的描述对于从用户的自然语言请求中提取相关信息至关重要。这种提取是通过大型语言模型 (LLM) 的<strong>函数调用</strong>功能实现的。在实践中，LLM 可以确定需要执行哪个函数（工具）以获取必要的信息，并为该函数指定适当的参数。</p><p>有关函数调用的更多信息，我们建议阅读 Ashish Tiwari 撰写的《<a href="https://www.elastic.co/search-labs/blog/function-calling-with-elastic">使用 Elasticsearch 进行 OpenAI 函数调用</a>》。</p><h2>运行 Toolbox 服务器</h2><p>您可以使用之前的 tools.yaml 文件，通过以下命令运行 MCP 工具箱：</p>./toolbox --tools-file tools.yaml --ui<p><strong>—ui</strong> 参数在 <a href="http://127.0.0.1:5000/ui">http://127.0.0.1:5000/ui</a> 上运行 Web 应用程序（图 2）。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0fb3953fae603572/6a16fa2aa6c2b92763e794fb/3caf2339b632bafd5847af1ed8b33b518a25b8a2-1600x314.png" alt="" /><p>您可以选择<strong>工具</strong> &gt; <strong>客户订单</strong>，并在参数<strong>名称</strong>（例如，Gwen Sanders）中插入客户名称。然后点击<strong>“运行工具”</strong>按钮。您应该会看到如图 3 所示的 JSON 响应。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltca02df8c78cb39ff/6a16fa2c961e6909b5c4cd22/b167e0142afb8919d9cedf6d0fa431d33d0e55f8-1600x933.png" alt="" /><p>设置已完成，MCP Toolbox 可以执行<strong>客户订单</strong>工具与 Elasticsearch 进行通信，运行 ES|QL 查询。</p><h2>将 MCP Toolbox 与 Gemini CLI 结合使用</h2><p>我们可以使用任何 MCP 客户端与 MCP Toolbox for Databases 进行通信。例如，我们可以使用命令行工具 <a href="https://github.com/google-gemini/gemini-cli">Gemini CLI</a> 来使用 Gemini。您可以按照<a href="https://geminicli.com/docs/get-started/installation/">此处</a>提供的说明安装 Gemini CLI。</p><p>Gemini CLI 为 MCP Toolbox 提供了一个预配置扩展程序，可在 <a href="https://github.com/gemini-cli-extensions/mcp-toolbox">gemini-cli-extensions/mcp-toolbox</a> 上获取。您可以通过运行以下命令来安装此扩展程序：</p>gemini extensions install https://github.com/gemini-cli-extensions/mcp-toolbox<p>安装完成后，您需要进入为 MCP Toolbox 存储 tools.yaml 配置文件的目录，并按如下步骤执行 Gemini CLI（此步骤是 Gemini CLI 与 MCP Toolbox 自动配置所必需的）：</p>gemini<p>您应该会看到图 4 中所示的输出广告。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf7245c10b9e32cd6/6a16fa2d964cea073208b976/0f22df6d3da13c1dc50dcb560414fa7c630eb9a7-1434x341.png" alt="" /><p>您可以使用以下命令检查 MCP Toolbox 是否已连接：</p>/mcp list<p>您应该能看到已列出<strong>客户订单</strong>工具的 <strong>mcp_toolbox</strong>（图 5）。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte857b42dbe604203/6a16fa2f8b73cb531b189e33/97edbc40de9e44f469f6f3a09427532be167de0e-493x155.png" alt="" /><p>如果 MCP Toolbox 已连接到 Gemini CLI，我们现在可以尝试问一些问题，例如：“<em>给我客户 Gwen Sanders 的订单</em>。”然后，Gemini CLI 将向 mcp_toolbox 服务器请求执行客户订单工具的权限（参见图 6）。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfb7ea752c1a39da7/6a16fa30cdacbfdf937d27ff/c052f3b5e49436903b804280c0065f67ee02444b-1432x284.png" alt="" /><p>确认后，Gemini CLI 将向 MCP Toolbox 执行请求，得到 JSON 响应结果，并使用它来格式化响应（图 7）。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb9f6e04987137a9f/6a16fa320811ae2297e9fef7/7ea5128f1705951c2757af6da4b456d394d4a080-1432x734.png" alt="" /><p>Gemini CLI 的响应将报告 Gwen Sanders 只下了一个订单，包含 2 件产品，总价为 132 欧元。</p><h2>MCP 工具箱 SDK</h2><p>Google MCP Toolbox 还提供一个 SDK，可用于访问用 Go、Python 和 Javascript 编写的程序中的所有功能。</p><p>例如，Python SDK 可在 Github 上获取，页面如下：<a href="https://github.com/googleapis/mcp-toolbox-sdk-python">https://github.com/googleapis/mcp-toolbox-sdk-python</a>。</p><p>我们需要创建一个简单的代理来连接 MCP 工具箱。我们需要安装以下软件包：</p>pip install toolbox-core
pip install google-adk<p>然后使用以下命令创建一个新的代理项目：</p>adk create my_agent<p>这会创建一个名为 <strong>my_agent</strong> 的新目录，其中包含文件 <strong>agent.py</strong>。</p><p>使用以下内容更新 <strong>my_agent/agent.py</strong>，以连接到 Toolbox：</p>from google.adk import Agent
from google.adk.apps import App
from toolbox_core import ToolboxSyncClient

client = ToolboxSyncClient("http://127.0.0.1:5000")

root_agent = Agent(
    name='root_agent',
    model='gemini-2.5-flash',
    instruction="You are a helpful AI assistant designed to search information about a dataset of ecommerce orders.",
    tools=client.load_toolset(),
)

app = App(root_agent=root_agent, name="my_agent")<p>创建一个 <strong>.env</strong>文件，其中包含您的 Google API 密钥：</p>echo 'GOOGLE_API_KEY="YOUR_API_KEY"' &gt; my_agent/.env<p>最后，我们可以运行代理并观察结果。要执行代理，您可以运行以下命令：</p>adk run my_agent<p>或者，您也可以通过 Web 接口提供服务：</p>adk web --port 8000<p>在这两种情况下，您都可以使用问答接口与 MCP Toolbox 进行交互。例如，您可以提出前一个问题：<em>给我客户 Gwen Sanders 的订单</em>。</p><p>有关不同 SDK 的更多信息，可以参考<a href="https://googleapis.github.io/genai-toolbox/sdks/">此文档页面</a>。</p><h2>结论</h2><p>在本文中，我们演示了 Elasticsearch 与 Google MCP Toolbox for Databases 的集成。使用简单的 YAML 配置文件，我们可以定义一组工具，这些工具使用 ES|QL 语言将自然语言问题转换为 Elasticsearch 查询。</p><p>我们展示了如何与 kibana_sample_data_ecommerce 数据集进行交互，该数据集包含来自电子商务网站的订单。通过这个配置文件，我们可以简单地运行 MCP Toolbox 服务器并从任何 MCP 客户端连接到它。</p><p>最后，我们演示了如何使用 Gemini CLI 作为客户端连接到 MCP Toolbox for Databases 并查询存储在 Elasticsearch 中的电子商务数据。我们执行了自然语言查询，以检索有关特定客户（以姓名标识）的订单信息。</p><p>随着 MCP 生态系统的不断发展，这种模式——轻量级工具定义，由安全、生产就绪的基础架构支持——为构建越来越强大、数据感知的代理提供了新的机会，且所需努力最小。无论您是在本地尝试 Elastic 的示例数据集，还是将搜索功能集成到更大的应用程序中，MCP 工具箱都为使用自然语言与 Elasticsearch 数据进行交互提供了可靠、可扩展的基础。</p><p>有关代理 AI 应用程序开发的更多信息，您可以阅读 Anish Mathur 和 Dana Juratoni 撰写的<a href="https://search-labs-redesign.vercel.app/search-labs/blog/ai-agentic-workflows-elastic-ai-agent-builder">《使用 Elasticsearch 构建 AI 代理工作流》</a>。</p><p>有关 Google MCP Toolbox 的更多信息，请访问 <a href="https://googleapis.github.io/genai-toolbox/getting-started/introduction/">https://googleapis.github.io/genai-toolbox/getting-started/introduction/</a>。</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/google-mcp-toolbox-elasticsearch-support</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/google-mcp-toolbox-elasticsearch-support</guid>
    <category><![CDATA[ES|QL]]></category>
    <category><![CDATA[智能体 AI]]></category>
    <dc:creator><![CDATA[Enrico Zimuel,Laurent Saint-Félix]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt72d49893c51407cf/6a16fa33cf4f2502bab2cf7e/425a48691f436ed47c9bdfaf5d561ac122b2c472-1062x668.png" length="0" type="image/png"/>
    <pubDate>Fri, 12 Dec 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[9.2 中的 ES|QL：Smart Lookup Joins 和时间序列支持]]></title>
    <description><![CDATA[探索 Elasticsearch 9.2 中 ES|QL 的三个独立更新：增强的 LOOKUP JOIN 用于更具表现力的数据关联、新的 TS 命令用于时间序列分析，以及灵活的 INLINE STATS 命令用于聚合。]]></description>
    <content:encoded><![CDATA[<p>10 月发布的 Elasticsearch 9.2 版本包含诸多重大改进，使数据分析比以往任何时候都更快、更灵活、更易访问。该版本的核心是对我们的管道式查询语言 ES|QL 的重要增强，旨在直接为终端用户带来更多价值。</p><p>下面将介绍 Elasticsearch 9.2 中的功能，这些功能将利用 ES|QL 改变您的数据分析工作流。</p><h2>数据关联的革命：更智能、更快速、更灵活的 Lookup Join</h2><p>在 Elasticsearch 9.2 中，ES|QL 中的 <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/lookup-join">LOOKUP JOIN</a> 命令发生了重大变化，变得更加高效和多功能。LOOKUP JOIN 将 ES|QL 查询结果表中的数据与指定查找模式索引中的匹配记录结合起来。它会根据连接字段中的匹配值，将查找索引中的字段作为新列添加到结果表中。以前，连接数据仅限于单一字段和简单相等。不再是这样了！这些增强功能使您能够轻松应对复杂的数据关联方案。</p><p><strong>Lookup Join 的主要增强功能包括：</strong></p><ul><li><p><strong>多字段连接：</strong>轻松连接多个字段。例如，要将 <code>application_logs</code> 与 <code>service_registry</code> 连接到 <code>service_name</code>，<code>environment</code> 和 <code>version:</code></p></li></ul>FROM application_logs
| LOOKUP JOIN service_registry ON service_name, environment, version<ul><li><p><strong>使用表达式释放复杂的连接谓词（技术预览）：</strong></p></li></ul><p>您不再局限于简单相等。LOOKUP JOIN 现在允许您指定<strong>多个相关性标准</strong>，并纳入一系列<strong>二进制运算符</strong>，包括 ==、 !=、&lt;、&gt;、&lt;= 和 &gt;=。这意味着您可以创建高度细致的连接条件，从而能够对数据提出更复杂的问题。</p><p>示例 1：使用按服务 SLA 阈值查找应用程序指标</p>FROM application_metrics
| LOOKUP JOIN sla_thresholds
      ON service_name == sla_service AND response_time &gt; sla_response_time<p>示例 2：此查询根据随时间变化的地区定价政策计算应付金额。它根据复杂的日期范围和相等条件连接三个数据集，计算出最终的 <code>due_amount</code>。第二个查找连接使用 <code>meter_readings</code> 索引中的<code>measurement_date</code> 字段和<code>customers</code> 索引中的<code>region_id</code> 字段连接到<code>pricing_policies</code> 索引，并为特定 <code>region</code> 和 <code>measurement_date</code> 查找正确的定价策略。</p>FROM meter_readings
| LOOKUP JOIN customers
      ON meter_id
| LOOKUP JOIN pricing_policies
      ON
        region_id == region AND
          measurement_date &gt;= policy_begin_date AND
          measurement_date &lt; policy_end_date
| EVAL due_amount = (kwh_consumed * rate_per_kwh + base_charge) * (1 + tax_rate)
| EVAL period = policy_name
| KEEP customer_name, period, due_amount, measurement_date, kwh_consumed,
    rate_per_kwh, base_charge, tax_rate
| SORT measurement_date<ul><li><p><strong>过滤连接带来的巨大性能提升： </strong></p></li></ul><p>我们提高了使用查找表条件筛选的“扩展连接”的性能。扩展连接会使每条输入行产生多个匹配项，从而产生较大的中间结果集。当后续筛选器丢弃其中许多行时，情况会变得更糟。在 9.2 中，我们通过在对查找数据应用筛选器时筛选掉不必要的行来优化这些连接，而避免处理将被丢弃的行。在某些情况下，这些连接的速度最多可以<strong>快 1000 倍</strong>！</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltce2c948a9a00a348/6a17f1a20b0bed8c08dd368b/002c014ee29b1aaf9ddeb8c554bb76efe3ed180c-1572x954.png" alt="过滤连接性能提升" /><p>这种优化在处理“扩展连接”时至关重要，因为在这种情况下，查找最初可能会产生许多潜在的匹配项。通过智能推送筛选器，仅处理相关数据，从而大幅缩短查询执行时间，实现对海量数据集的实时分析。这意味着，即使是大型或复杂的连接操作，您也能更快地获得见解。</p><p><strong>Lookup Join 跨集群搜索 (CCS) 兼容性：</strong></p><p>当 Lookup Join 在 8.19 和 9.1 版本中正式发布时，它缺少跨集群搜索 (CCS) 支持。对于在多个集群中运行的组织，LOOKUP JOIN 现在可与 9.2 中的 CCS 无缝集成。只需在要执行连接的所有远程集群上放置查找索引，ES|QL 就会自动利用这些远程查找索引来连接远程数据。这简化了分布式数据分析，并确保在整个 Elasticsearch 部署中实现一致的丰富性。</p><p>这些改进意味着您可以以前所未有的精度、速度和便捷性关联各种数据集，从而发现更深入、更具可操作性的见解，而无需复杂的替代方案或预处理步骤。</p><h2>轻松丰富您的数据：适用于 Lookup 索引的 Kibana Discover 用户体验</h2><p>数据丰富应该简单，而不是障碍。我们在 Kibana 的 Discover 中为创建和管理查找索引引入了绝佳的全新用户体验。</p><p><strong>直观的工作流：</strong>Discover 全面的自动完成功能将引导您完成整个流程，并在 ES|QL 编辑器中建议查找索引和连接字段，使您可以非常轻松地将上传的数据与现有索引连接起来。键入一个不存在的查找索引名称，然后直接访问 Lookup 编辑器，只需单击一下即可创建索引。键入现有查找索引的名称，我们将为您提供编辑该索引的选项：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt20a75bcfd25eb156/6a17f1a46864a4864db688a1/d36fd6ffd6bc0bf8d31067f6266445c68d15c71c-1400x184.png" alt="" /><p><strong>在线管理 (CRUD)：</strong>直接在 Discover 中使用行内编辑功能（创建、读取、更新、删除），使参考数据集保持最新状态。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6263c5dd8c2c9d7e/6a17f1a5af47b614dbcde095/a0e4aa66540b1f725c24ccb0519d978415073bb6-1453x842.png" alt="LOOKUP 查询示例" /><p><strong>轻松上传文件：</strong>现在，您可以在 Discover 中直接上传 CSV 等文件，并立即在 <code>LOOKUP JOIN</code> 中使用这些文件。再也不需要在 Kibana 的不同区域之间来回切换上下文了！</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltddfcc3580af0e5c6/6a17f1a73e9e45965aba1587/0f5dc2c712af4c4cada50292a7c8b836eb02aa67-1600x748.png" alt="将数据轻松添加到查找索引，并在 LOOKUP JOIN 中使用它们" /><p>无论您是将用户 ID 映射到名称、添加业务元数据，还是连接静态参考文件，此功能都能让数据丰富化变得触手可及，将连接的强大功能直接交到每个用户的手中——快速、简单且集中于一处。</p><h2>保留上下文：INLINE STATS 简介（技术预览）</h2><p>数据聚合至关重要，但有时您需要在查看原始数据的<em>同时</em>查看聚合数据。我们很高兴将 <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/inlinestats-by">INLINE STATS</a> 作为<strong>技术预览</strong>功能推出。</p><p><code>STATS</code> 命令会将输入字段替换为聚合输出，而<code>INLINE STATS</code> 命令则不同，它保留了所有原始输入字段，只是添加了新的聚合字段。这样，您就可以在聚合<em>后</em>对原始输入字段执行进一步操作，从而提供更连续、更灵活的分析工作流。</p><p>例如，要在计算平均飞行距离的同时保留单个飞行记录行：</p>FROM kibana_sample_data_flights
 | KEEP Carrier, Dest, DistanceMiles
 | INLINE STATS avgDist = ROUND(AVG(DistanceMiles))
       BY Dest
 | WHERE DistanceMiles &gt; avgDist<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9e0c7209db8a67ad/6a17f1a9e8fbcee5233a1a53/6eea943035e0ab371270084c504a06bb89f8b82b-1496x290.png" alt="使用 avgDist 筛选飞行距离大于平均值的飞行结果 " /><p>在此查询中，将 <code>avgDist</code> 与我们分组的相应 <code>Dest</code> (ination) 添加到每一行中，然后，由于我们仍有航班信息列，我们可以将结果筛选为飞行距离大于平均值的航班。</p><h2>ES|QL 中的时间序列支持（技术预览版）</h2><p>Elasticsearch 使用<a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/time-series-data-stream-tsds">时序数据流</a>来存储指标。我们将通过 <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/ts"><code>TS</code></a> 源代码命令在 ES|QL 中添加对时间序列聚合的支持。此功能在 Elastic Cloud serverless 和 9.2 基础版中以技术预览版的形式提供。</p><p>时间序列分析主要基于聚合查询，这些查询按照一个或多个筛选维度对时间分桶的指标值进行汇总。大多数聚合查询依靠两步处理，包括：(a) 内部聚合函数对每个时间序列的值进行汇总，以及 (b) 外部聚合函数将 (a) 的结果在时间序列之间进行组合。</p><p><a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/ts"><code>TS</code></a> 源命令与 <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/stats-by"><code>STATS</code></a> 结合使用，为表达对时间序列的查询提供了一种简洁而有效的方法。具体来说，请考虑以下计算每个主机和每小时的总请求率的示例：</p>TS my_metrics
| WHERE @timestamp &gt; NOW() - 1 day
| STATS SUM(RATE(requests))
      BY host, TBUCKET(1h)<p>在这种情况下，首先对每个时间序列和每个小时的时间序列聚合函数 <code>RATE</code> 进行评估。生成的部分聚合值随后使用 <code>SUM</code> 进行组合，以计算每个主机和每小时的最终聚合值。</p><p>您可以在<a href="https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/time-series-aggregation-functions">此处</a>查看可用的时间序列聚合函数列表。现在支持<a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/time-series-data-stream-tsds#time-series-metric">计数</a>率，这可以说是处理计数器最重要的聚合功能。</p><p><code>TS</code> 源命令旨在与<code>STATS</code> 结合使用，其执行经过调整，可有效支持时间序列聚合。例如，数据在进入<code>STATS</code> 之前要进行排序。目前不允许在 <code>TS</code> 和 <code>STATS</code> 之间执行可能丰富或改变时序数据或其顺序的处理命令，例如 <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/fork"><code>FORK</code></a> 或 <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/inlinestats-by"><code>INLINE STATS</code></a>。将来可能会取消这一限制。</p><p><code>STATS</code> 表格输出可使用任何适用命令进行进一步处理。例如，以下查询计算每台主机和每小时的平均 <code>cpu_usage</code> 与最大值的比率：</p>TS my_metrics
| STATS avg_usage = AVG(AVG_OVER_TIME(cpu_usage))
      BY host, time_bucket = TBUCKET(1h)
| INLINE STATS max_avg_usage = MAX(avg_usage)
      BY host
| EVAL ratio = avg_usage / max_avg_usage
| KEEP host, time_bucket, ratio
| SORT host, time_bucket DESC<p>时序数据存储在我们基于 Lucene 文档值的底层列式存储引擎中。TS 命令通过 ES|QL 计算引擎增加了向量化查询执行功能。与等效的<a href="https://www.elastic.co/docs/reference/query-languages/querydsl">DSL</a>查询相比，查询性能往往提高一个数量级以上，并可与已建立的特定度量系统相媲美。我们将在未来提供详细的架构和性能分析，敬请期待。</p><h2>扩展您的工具包：ES|QL 新功能</h2><p>为了进一步增强 ES|QL 的实用性和多功能性，我们添加了一套新<a href="https://www.elastic.co/docs/reference/query-languages/esql/esql-functions-operators">功能</a>：</p><p><strong>字符串操作：</strong><a href="https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/string-functions#esql-contains">CONTAINS</a>、<a href="https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/mv-functions#esql-mv_contains">MV_CONTAINS</a>、<a href="https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/string-functions#esql-url_encode">URL_ENCODE</a>、<a href="https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/string-functions#esql-url_encode_component">URL_ENCODE_COMPONENT</a>、<a href="https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/string-functions#esql-url_decode">URL_DECODE</a>，用于更强大的文本和 URL 处理。</p><p><strong>时间序列和地理空间：</strong> <a href="https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/grouping-functions#esql-tbucket">TBUCKET</a>用于灵活的时间分桶，TO_DENSE_VECTOR 用于矢量运算，以及一套全面的<a href="https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/spatial-functions">地理空间函数，</a>如<code>ST_GEOHASH</code> 、 <code>ST_GEOTILE</code> 、 <code>ST_GEOHEX</code> 、 <code>TO_GEOHASH</code> 、 <code>TO_GEOTILE</code> 、 <code>TO_GEOHEX</code>用于高级基于位置的分析。</p><p><strong>日期格式：</strong>使用 <a href="https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/date-time-functions#esql-day_name">DAY_NAME</a>、<a href="https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/date-time-functions#esql-month_name">MONTH_NAME</a>，以获得更易读的日期表示形式。</p><p>这些功能为您提供了一套更丰富的工具，可直接在 ES|QL 中操作和分析您的数据。</p><h2>内在优势：更高的性能和效率</h2><p>除了上述突出的功能外，Elasticsearch 9.2 还对 ES|QL 进行了大量性能优化。在函数能够替代多个类似 RLIKE 查询的情况下，我们使用 pushdown 加快了 <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/where#like-and-rlike">RLIKE(LIST)</a> 的执行速度。通过 <code>RLIKE</code> (LIST)，我们可以将这些查询合并为一个自动机，并应用一个自动机而不是多个自动机。我们还通过索引排序加快了关键字字段的加载速度，并对一般查询进行了优化——这些改进可确保您的 ES|QL 查询比以往更高效地运行。</p><h2>立即开始！</h2><p>Elasticsearch 9.2 标志着 ES|QL 的重大飞跃，为您的数据分析工作流带来了前所未有的强大功能和灵活性。我们鼓励您探索这些新功能，并体验它们带来的不同。</p><p>有关 Elasticsearch 9.2 中所有更改和增强功能的完整列表，请参阅<a href="https://www.elastic.co/guide/en/elasticsearch/reference/9.2/release-notes-9.2.0.html">正式发布说明</a>。祝您查询愉快！</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/esql-elasticsearch-9-2-multi-field-joins-ts-command</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/esql-elasticsearch-9-2-multi-field-joins-ts-command</guid>
    <category><![CDATA[ES|QL]]></category>
    <dc:creator><![CDATA[Tyler Perkins,Kostas Krikellas,Julian Kiryakov]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd30ea8cac3809cc3/6a17f1aa4b055dd8734322f8/415894e21e7758c907d6e60d4efc94230349beef-2012x1164.png" length="0" type="image/png"/>
    <pubDate>Tue, 02 Dec 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elasticsearch 的 ES|QL 编辑器体验与 OpenSearch 的 PPL 事件分析器对比]]></title>
    <description><![CDATA[了解 ES|QL Editor 的高级功能如何加速您的工作流程，与 OpenSearch 的 PPL 事件分析器的手动方法形成直接对比。 
]]></description>
    <content:encoded><![CDATA[<p><a href="https://www.elastic.co/blog/getting-started-elasticsearch-query-language">Elasticsearch 查询语言</a>（ES|QL）自 8.14 版起全面可用，它引入了一种专为搜索、可观察性和安全调查而设计的查询语言和引擎。与大量借鉴现有管道式语言的 OpenSearch 管道式处理语言 (PPL) 不同，ES|QL 是从头开始构建的，专注于 Kibana 平台的完善性、可用性和无缝集成。</p><p>在本博客中，我们将通过比较 Elasticsearch 9.1 中的 ES|QL 编辑器和 OpenSearch 3.2 中事件分析器（简称 PPL）中的 PPL，来探讨开发人员对 ES|QL 编辑器的体验。</p><p>两者之间的差异很快就显现出来：ES|QL 编辑器提供智能自动完成、上下文帮助、推荐查询和跨集群查询支持，不仅能帮助初学者，还能帮助专家级用户。针对 ES|QL 编写的周到设计还体现在通过 Kibana 工作流（例如最近查询）进行的集成查询检查和整体集成。</p><p>相比之下，PPL 缺乏对自动完成、上下文引导和分布式查询的类似支持，因此学习曲线更陡峭，试错更多。</p><h2>使 ES|QL 更易于学习和使用</h2><p>开始使用一种新的查询语言往往会让人感到不知所措。ES|QL编辑器直接内置于<strong> Kibana Discover</strong> 中，不仅支持查询创建和调试，还能加快熟悉和掌握该语言的速度，从而简化这一过程。由于编辑器有助于减少日常工作中的摩擦，您可以将注意力从语法和试错转移到解决问题上。您可以<a href="https://www.elastic.co/search-labs/blog/improving-esql-editor-experience-in-kibana">在这里</a>了解更多有关这些原则以及我们如何将其融入编辑器的信息。</p><p>这种编辑器体验并不局限于 Discover；它是一种可重复使用的代码模块，我们正在努力<strong>将其集成到 Kibana 的其他部分</strong>，如仪表盘、Kibana 警报和 Kibana 地图。</p><h3>智能自动完成：加速创建查询</h3><p>ES|QL 编辑器中的自动完成功能非常全面，可为兼容函数、参数、字面量甚至嵌套函数提供建议，而这正是 PPL 所缺乏的功能。事实上，它已经从头开始重建，正如<a href="https://www.elastic.co/search-labs/blog/esql-autocomplete-rebuilt">这里所</a>概述的那样。</p><p>如<a href="https://www.elastic.co/search-labs/blog/improving-esql-editor-experience-in-kibana">这里</a>所述，验证会在用户键入时运行，并对字段提出建议，还会在出现错误时通知用户。这减轻了用户的心理负担，有助于在查询创建过程的早期防止错误。</p><p>举例说明：在此嵌套中建议使用字段和兼容函数：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdb186fc80dcc66f5/6a17f311e9ea8737a5a9c720/a4d7b2819c34fab31bced7873257b8932b623fba-1502x473.png" alt="字段和兼容函数嵌套建议。" /><p>这是 PPL 不支持的：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt250ae79e8f33b598/6a17f3127f6f150d3dc09c74/6f3a89b1255b8a3a762022a2704fdd1c2987e5f9-1013x335.png" alt="PPL 不支持的功能。" /><p>即使有智能自动完成功能指导你使用兼容函数、参数和嵌套函数，你可能还是希望对可用选项有更深入的了解。这正是 ES|QL 编辑器的上下文帮助变得非常有价值的地方，它提供即时的编辑帮助，以澄清和加强您的查询开发。</p><h3>触手可及的上下文帮助</h3><p>点击 Ctrl 键-空格键，即可获得有关自动完成功能生成的命令的其他信息。立即出现一个面板，显示有关函数、参数或字段的详细信息。这种轻量级交互让开发人员始终处于工作流程中，为他们提供及时指导，而不会迫使他们离开编辑器或搜索外部文档。这样可以减少在语法查找上浪费的时间，并有助于在常见错误发生之前加以预防。</p><p>下面是它的实际效果：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt407e42acc7f53f15/6a17f3134b055d128d43231a/2797f9b5e002dbd83c46475c4ed4dcdc86144a01-1343x522.gif" alt="由自动完成功能生成的命令，带有 Ctrl-空格，可提供更多上下文信息。" /><p>PPL 缺乏这种级别的嵌入式指导，用户只能依赖外部文档或反复试验。这种缺失不仅仅是功能上的缺失，它还凸显了设计理念上更广泛的差异。ES|QL 优先考虑贴心的上下文感知体验，以适应用户的数据和工作流程。随着查询复杂度的增加，这种差异会变得更加明显，从而使 ES|QL Editor 成为一个更高效、更可靠的学习和生产环境。</p><h3>建议使用可感知数据上下文的查询</h3><p>ES|QL 编辑器会根据您正在处理的数据（如日志）自动提供推荐查询。它不是提供一个空白的编辑器，而是为常见用例提供最相关的出发点。选择 "推荐查询 "可生成一个标准查询，该查询可立即使用，并可根据需要进一步完善。这种方法加快了查询的开发速度，尤其是对于尚未掌握全部语法的新用户而言。</p><p>下面是一个用户选择 "检测变化点 "查询的示例：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfc6c41f9e1e3cb40/6a17f3156864a43791b688d0/3284c9340d41298820fbf8c7702abad946b48248-925x370.gif" alt="当用户选择 &quot;检测变化点 &quot;查询时会发生什么？" /><p>将其与 PPL 的经验进行比较：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6511bb51659feaf3/6a17f3166864a427c2b688d4/5c3e59dadc6210aede3366bdd081887bcbae7a54-969x798.png" alt="只有基本自动完成功能的 PPL 体验。" /><p>相比之下，PPL 在这里只提供基本的自动完成功能，让您在没有上下文或结构的情况下拼凑查询。缺乏指导可能会导致挫败感和反复试验。有了 ES|QL Editor 的数据感知推荐查询，您就可以避免从头开始或死记硬背常规任务的语法。编辑器可减少认知负荷，有助于防止出错，让你专注于解决问题和更广泛的目标，如运行跨集群搜索，而不是费力地构建查询。</p><h2>直观的跨集群查询</h2><p>ES|QL 编辑器的自动完成功能依然卓越，即使在使用<a href="https://elastic.aiops.work/search-labs/blog/esql-cross-cluster-search">CCS</a> 处理多个远程群集时也是如此。原因就在这里：</p><h3>ES|QL 编辑器甚至可跨集群提供无缝自动完成功能</h3><p>ES|QL 编辑器中的自动完成功能不仅支持群组名称，还支持<strong> 本地和远程索引</strong>。正如<a href="https://www.elastic.co/search-labs/blog/esql-cross-cluster-search">本文</a>所介绍的那样，这要归功于协调器节点架构，它可以帮助验证和生成查询计划，然后发送给本地节点，执行查询并汇总结果，最后再发送给用户。在不输入远程群集全名的情况下，键入": "可启动远程索引的自动完成程序。而且，您也不局限于前缀。</p><p>这样就可以轻松地在分布式数据集上发现和查询数据，而无需记住命名约定或切换上下文。</p><p>下面是一个示例，用户只需键入 "clu:g "即可定位远程索引：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4fadd61aa6d2a212/6a17f3186864a4a385b688d8/bae1fbacb2320e4d07f41291ea57c9bcf15bf8a5-1092x523.gif" alt="例如，用户只需键入 &quot;clu:g &quot;即可找到远程索引。" /><p>与此形成鲜明对比的是，PPL 只为本地索引提供基本的补全功能，建议仅限于前缀匹配。远程群组必须手动键入，这会增加出错的可能性，并减慢查询创建速度。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3949402084cf303a/6a17f31a6864a482b1b688dc/e38793c0cc7c6cc7dc0fd4779a3e24ffbb6e0838-1094x263.gif" alt="举例说明 PPL 如何仅为本地索引提供基本的补全，建议仅限于前缀匹配。" /><p>PPL 仅为本地索引提供补全，建议仅限于前缀：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcaa81c403f354e1f/6a17f31c6864a4e620b688e0/5310f824942f94485cace2558ea72c56a0971e22-862x197.png" alt="另一个例子说明了 PPL 如何仅为本地索引提供补全，并且建议仅限于前缀。" /><p>ES|QL 更进一步，<a href="https://www.elastic.co/docs/solutions/search/cross-cluster-search#exclude-problematic-clusters"> 允许</a> 直接使用负号 排除 ，让您可以精细控制哪些集群参与探索。这种功能在处理混合环境时尤为重要，因为在混合环境中，您可能希望在跨集群调查时包含或省略特定的数据集。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf0ca78bfbceb60ae/6a17f31d6864a4199bb688e4/f23ca17f58fbf8e6d27419c028274cb91f30a549-937x78.png" alt="跨专题组调查的编码示例。" /><p>这些增强功能反映了 Elasticsearch 对减少跨集群搜索摩擦的广泛关注。通过使分布式查询更易于构建和管理，ES|QL Editor 使分析师和开发人员能够专注于洞察力而不是语法，而 PPL 则将更多的负担留给了用户。ES|QL 编辑器不仅简化了跨集群查询的创建，还提供了检查这些查询执行情况的工具，确保跨多个集群的透明度和性能监控。</p><h3>使用 Inspect 工具分析跨群集搜索详情</h3><p>检查工具可从 ES|QL 编辑器访问，旨在提供元数据，明确说明所有集群的查询执行情况。该功能已在 Kibana Discover 中启用，并可在查询检查器中直接访问，使您能够分析搜索进度和详细信息，这对于<strong>跨集群搜索</strong><a href="https://www.elastic.co/docs/reference/query-languages/esql/esql-cross-clusters">(CCS</a>) 尤为重要。该功能可帮助您监控搜索进度，了解查询在分布式数据集上的执行情况。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf79eaeedac3600b9/6a17f31fabe0f26f06dfeb4b/5d1c204f70171526fff924c30ea8ad08121a0f8d-919x523.gif" alt="分析跨集群搜索细节的检查工具。" /><p>这种对查询执行的详细可视性，尤其是对复杂的分布式搜索的可视性，使您能够确保最佳性能和故障排除。</p><p>除了了解单个查询的机制外，ES|QL 编辑器还通过在整个 Kibana 平台上深度嵌入基本功能，促进无缝、不间断的工作流程，从而进一步增强用户体验。</p><h2>使用 ES|QL 和 Kibana 的统一查询体验</h2><p>查询驱动分析中最常见的摩擦源之一是上下文切换。您经常需要回忆已经写过的查询。每一次中断都会打断注意力，延缓调查进度。ES|QL Editor 通过在 Kibana 中集成查询历史记录来解决这一问题。</p><h3>最近的查询</h3><p>ES|QL 编辑器中的 "<a href="https://www.elastic.co/search-labs/blog/esql-piped-query-language-goes-ga">最近查询 "</a>功能可让您即时访问过去的工作，从而帮助您保持工作流畅。在 Discover 的 ES|QL 编辑器中，您可以查看、重新运行最近的 20 个查询，并将其设为星级，确保只需点击一下就能完成常用或复杂的查询。这些保存的查询还可在 Kibana 中使用，并与仪表盘、可视化、警报和地图集成，因此您无需离开当前屏幕或从头开始重新输入命令。这减少了重复性工作，加快了调查速度，并最大限度地降低了出错风险。</p><p>例如，用户可以在发现中使用 ES|QL 编辑器中的最近查询（并将其设为星级）：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt49033a46a0805ec8/6a17f321e9ea87a505a9c724/eb0f9fe37b92dec421c394d31ae7d90afebe062e-1421x793.png" alt="在 discover 中使用 ES|QL 编辑器中的最近查询的示例（以及如何将其列为星级查询）。" /><p>最近的查询已集成到仪表板中：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta2f7c745cdc5e5f1/6a17f323a2929932a1d02da9/b84cd3a9bdec58812360d2aba4fc7713363ee3cc-1411x797.png" alt=" 将最近的查询整合到仪表板中。" /><p>PPL 不提供类似功能，用户只能依靠手动复制粘贴或外部注释来重复使用查询。这种区别不仅仅是为了方便；它反映了 Elastic 将 ES|QL 打造为 Kibana 生态系统中真正集成语言的战略。通过最近查询等功能，ES|QL 编辑器不仅简化了日常工作流程，还为技术预览版中更高级的功能奠定了基础，确保用户体验不断发展。</p><h2>结论</h2><p>ES|QL 不仅仅是一种语法，它还反映了 Elastic 改进用户搜索、探索和分析数据方式的战略。通过智能自动完成、上下文感知的推荐查询、编辑器内指导以及检查等工具，ES|QL Editor 可加快学习速度、减少错误并简化跨集群分析等复杂的工作流程。它与 Kibana 集成，可将查询与仪表盘、警报和可视化无缝连接，从而实现不间断的工作流程。</p><p>总之，ES|QL 不仅仅是另一种管道语言；它是一个经过深思熟虑设计的查询引擎，搭配直观的用户界面，从根本上重新定义了您与数据交互的方式，提供了一种集成、智能和不断发展的体验，与 OpenSearch PPL 通常按部就班、缺乏指导的性质形成鲜明对比。</p><h2>下一步行动</h2><p>本博客仅涉及 ES|QL 的表面。今后的文章将深入探讨与 OpenSearch PPL 的比较，并探索地理空间、可视化和即将推出的编辑器功能，如<a href="https://www.elastic.co/docs/explore-analyze/dashboards/add-controls">控制</a>（已在仪表板中提供）、多数据探索选项卡、后台搜索、更丰富的查询历史和 FUSE。</p><h2>立即试用 ES|QL</h2><p>您可以通过<a href="https://www.elastic.co/docs/deploy-manage/deploy/elastic-cloud/create-serverless-project"> 免费试用 在完全托管的</a> Elasticsearch<a href="https://www.elastic.co/cloud/serverless"> Serverless</a> 项目中查看 ES|QL。从 8.11 开始的版本中也有该功能，但在<a href="https://www.elastic.co/blog/whats-new-elastic-9-1-0">8.19 和 9.1</a> 中体验最佳。</p><p>只需一条命令，几分钟即可在本地环境中开始使用：</p>curl -fsSL https://elastic.co/start-local | sh]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/opensearch-vs-elasticsearch-ppl-esql</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/opensearch-vs-elasticsearch-ppl-esql</guid>
    <category><![CDATA[ES|QL]]></category>
    <dc:creator><![CDATA[Libby Lin,George Kobar]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte51b193824ff8084/6a17f3257f6f154b7bc09c7c/f1ff4ff4a00b3e5b084d4116cea6cabc82a2d816-720x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 18 Sep 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[介绍用于 Elasticsearch Ruby 客户端的 ES|QL 查询生成器]]></title>
    <description><![CDATA[了解如何使用最近发布的用于 Elasticsearch Ruby 客户端的 ES|QL 查询生成器。这是一款使用 Ruby 代码更轻松地构建 ES|QL 查询的工具。]]></description>
    <content:encoded><![CDATA[<p>我们最近发布了<a href="https://github.com/elastic/esql-ruby/"><code>elastic-esql</code></a> ，这是一个根据 Apache 2 许可证发布的 Ruby gem。有了这个 gem，你就可以用惯用的 Ruby 语言创建 Elastic 的<a href="https://www.elastic.co/docs/explore-analyze/query-filter/languages/esql">ES|QL</a>查询，然后将其用于 ES|QL 查询 API。ES|QL 允许开发人员通过查询过滤、转换和分析存储在 Elasticsearch 中的数据。它使用"管道" (<code>|</code> ) 来逐步处理数据。该 gem 使用 Ruby 函数，你可以将这些函数链入原始对象，以建立更复杂的查询：</p><p><strong>ESQL：</strong></p><p><strong>鲁比</strong></p>Elastic::ESQL.from('sample_data').limit(2).sort('@timestamp').descending<h2>安装</h2><p>该 gem 可通过以下方式从 RubyGems 安装：</p>gem install elastic-esql<p>或者将其添加到项目的 Gemfile 中：</p>gem 'elastic-esql'<h2>使用方法</h2><p>您可以一次性建立一个完整的查询，也可以使用<code>from</code> 或<code>row</code> 等源命令创建一个查询对象，然后使用 ES|QL 方法链在其上建立查询。</p>query = Elastic::ESQL.from('sample_data')
query.limit(2).sort('@timestamp')<p>在<code>to_s</code> 方法中，gem 将代码转换为 ES|QL，因此在打印输出或转换为字符串时会返回 ES|QL 查询：</p>query = Elastic::ESQL.from('sample_data').limit(2).sort('@timestamp').descending
query.to_s
# =&gt; "FROM sample_data | LIMIT 2 | SORT @timestamp DESC"<p>您可以使用<code>!</code> 中每个函数的对应函数来实例化查询对象并更改其初始状态：</p>query = Elastic::ESQL.from('sample_data')
query.to_s
# =&gt; "FROM sample_data"
query.limit!(2).sort!('@timestamp')
query.to_s
# =&gt; "FROM sample_data | LIMIT 2 | SORT @timestamp"<p>该工具提供了将额外步骤链入 ES|QL 函数的便捷方法，如<code>enrich</code> 和<code>sort</code> 。一旦在<code>Elastic::ESQL</code> 对象上调用<code>enrich</code> ，就可以将<code>on</code> 和<code>with</code> 与之连锁：</p>esql.enrich!('policy').on('a').with({ name: 'language_name' })<p>在使用<code>sort</code> 之后，您还可以将<code>desc</code> 、<code>asc</code> 、<code>nulls_first</code> 和<code>nulls_last</code> 链入您的查询：</p>Elastic::ESQL.from('sample_data').sort('@timestamp').asc.to_s
# =&gt; 'FROM sample_data | SORT @timestamp ASC'

Elastic::ESQL.from('sample_data').sort('@timestamp').desc.nulls_first.to_s
# =&gt; 'FROM sample_data | SORT @timestamp DESC NULLS FIRST'<p>它还支持自定义字符串，以备您自己编写 ES|QL 查询或使用尚未添加到库中的功能。<code>custom</code> 会在查询结束时连接字符串。它将在发送到函数时添加这些字符，而不会添加任何管道字符。它们将通过一个空格字符与查询的其余部分合并。</p>esql = Elastic::ESQL.from('sample_data')
esql.custom('| MY_VALUE = "test value"').to_s
# =&gt; 'FROM sample_data | MY_VALUE = "test value"'<p>您还可以将<code>custom</code> 功能串联起来：</p>esql.custom('| MY_VALUE = "test value"').custom('| ANOTHER, VALUE')
'FROM sample_data | MY_VALUE = "test value" | ANOTHER, VALUE'<h2>在 Ruby 客户端使用 ES|QL 查询生成器</h2><p>您可以通过发送查询对象，直接在<a href="https://github.com/elastic/elasticsearch-ruby">elasticsearch-ruby</a>和<code>esql.query</code> API 中使用查询生成器：</p>require 'elasticsearch'
require 'elastic/esql'

client = Elasticsearch::Client.new
index = 'sample_data'

query = Elastic::ESQL.from(index)
                     .sort('@timestamp')
                     .desc
                     .where('event_duration &gt; 5000000')
                     .limit(3)
                     .eval({ duration_ms: 'ROUND(event_duration/1000000.0, 1)' })
client.esql.query(body: { query: query })<p>您还可以将其与 Elasticsearch Ruby 客户端中的 ES|QL Helper 结合使用，<a href="https://www.elastic.co/search-labs/blog/esql-ruby-helper-elasticsearch">了解更多详情</a>：</p>require 'elasticsearch/helpers/esql_helper'

Elasticsearch::Helpers::ESQLHelper.query(client, query)<h2>作为独立工具</h2><p>该 gem 被设计为一个独立工具，用于以惯用方式构建 ES|QL 查询。它没有运行时依赖性，可以与官方 Elasticsearch Ruby 客户端一起使用，也可以单独使用。</p><p>生成的查询可在<a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-esql-query"><code>esql.query</code></a> API 中使用，无论应用程序以何种方式与 Elasticsearch API 交互（无论是否使用 Ruby）。使用<code>elastic-esql</code> 创建查询后，生成的字符串就可以作为<code>query</code> 请求正文中的参数发送给应用程序接口。 </p><p>我曾写过一篇关于<a href="https://www.elastic.co/search-labs/blog/elasticsearch-ruby-tools">将 Elasticsearch 与流行的 Ruby 工具结合使用的</a>文章。该 gem 可与任何流行的 Ruby 工具一起使用，以通过 ES|QL 查询 Elasticsearch。</p><h2>结论</h2><p>该库正在积极开发中，最终的应用程序接口尚未完成。目前发布的是技术预览版。如果您对当前的应用程序接口或一般使用方法有任何反馈，请随时<a href="https://github.com/elastic/esql-ruby/issues">打开新问题</a>。有关 Ruby ES|QL 查询生成器的更多信息，请参阅<a href="https://github.com/elastic/esql-ruby/?tab=readme-ov-file#ruby-esql-query-builder">README</a>。</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/esql-query-builder-elasticsearch-ruby-client</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/esql-query-builder-elasticsearch-ruby-client</guid>
    <category><![CDATA[ES|QL]]></category>
    <category><![CDATA[Ruby]]></category>
    <dc:creator><![CDATA[Fernando Briano]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt85d112ccca541b9e/6a17dccb4b055d6bfd4320cc/f8e1263ab53d356824a4fc539084151be80899db-720x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Wed, 17 Sep 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[使用 Kibana 将地理空间数据输入 Elasticsearch，以便在 ES|QL 中使用]]></title>
    <description><![CDATA[如何使用 Kibana 和 csv 摄取处理器将地理空间数据摄取到 Elasticsearch 中，以便在 Elasticsearch 查询语言 (ES|QL) 中进行搜索。Elasticsearch 具有强大的地理空间搜索功能，ES|QL 将大幅提高易用性和 OGC 熟悉度。但要使用这些功能，我们需要地理空间数据。]]></description>
    <content:encoded><![CDATA[<p>我们最近发布了一篇博客，介绍了如何使用 Elasticsearch 新的强大<a href="https://www.elastic.co/search-labs/blog/esql-piped-query-language-goes-ga"> 管道式查询语言</a><a href="https://www.elastic.co/search-labs/blog/esql-geospatial-search-part-one"> ES|QL</a><a href="https://www.elastic.co/search-labs/blog/esql-geospatial-search-part-one"> 中 的新</a><a href="https://www.elastic.co/search-labs/blog/esql-geospatial-search-part-one"> 地理空间搜索 功能</a> 。要使用这些功能，您需要在 Elasticsearch 中保存地理空间数据。因此，在本博客中，我们将向您展示如何获取地理空间数据，以及如何在 ES|QL 查询中使用这些数据。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfc041ebca11476c6/6a16f70560084b31b93c4344/01fde3b1d714f12bf8673140c9f2f940d443de31-1440x823.jpg" alt="ESQL 地理空间搜索" /><h2>使用 Kibana 导入地理空间数据</h2><p>我们在上一篇博客的示例中使用的数据是基于我们内部用于集成测试的数据。为方便起见，我们在此以 CSV 文件的形式提供了这些信息，您可以使用 Kibana 轻松导入这些信息。数据包括机场、城市和城市边界。您可以从以下网址下载数据</p><ul><li><p><a href="https://raw.githubusercontent.com/elastic/elasticsearch-labs/refs/heads/main/supporting-blog-content/geospatial-data-ingest/airports/airports.csv">机场.csv</a></p><ul><li><p>其中包含三个数据集的合并：</p><ul><li><p>来自<a href="https://www.naturalearthdata.com/downloads/10m-cultural-vectors/airports/">Natural Earth</a>的机场（名称、位置和相关数据</p></li><li><p>来自<a href="https://simplemaps.com/data/world-cities">SimpleMaps</a>的城市位置</p></li><li><p><a href="https://www.partow.net/miscellaneous/airportdatabase/">全球机场数据库</a>中的机场标高</p></li></ul></li></ul></li><li><p><a href="https://raw.githubusercontent.com/elastic/elasticsearch-labs/refs/heads/main/supporting-blog-content/geospatial-data-ingest/airports/airport_city_boundaries.csv">airport_city_boundaries.csv</a></p><ul><li><p>其中包含上述机场和城市名称与一个新来源的合并：</p><ul><li><p>来自<a href="https://www.openstreetmap.org/">OpenStreetMap</a>的城市边界</p></li></ul></li></ul></li></ul><p>您可以猜到，我们花了一些时间将这些数据源合并到上述两个文件中，目的是测试 ES|QL 的地理空间特性。这可能与您的具体数据需求不尽相同，但希望这能让您对可能的情况有所了解。我们特别想展示几件有趣的事情：</p><ul><li><p>将包含地理空间字段的数据与其他可索引数据一起导入</p></li><li><p>同时导入<code>geo_point</code> 和<code>geo_shape</code> 数据并在查询中一起使用</p></li><li><p>将数据导入可使用空间关系连接的两个索引中</p></li><li><p>创建摄取管道以促进未来的导入（超越 Kibana）</p></li><li><p>摄取处理器的一些示例，如<code>csv</code> 、<code>convert</code> 和 <code>split</code></p></li></ul><p><a href="https://www.elastic.co/guide/en/kibana/current/import-geospatial-data.html">虽然我们将在本博客中讨论 如何</a><a href="https://www.elastic.co/guide/en/kibana/current/import-geospatial-data.html"> 使用</a> CSV 数据，但重要的是要了解 使用<a href="https://www.elastic.co/guide/en/kibana/current/import-geospatial-data.html"> Kibana</a><a href="https://www.elastic.co/guide/en/kibana/current/import-geospatial-data.html"> 添加地理数据的 几种方法</a> 。在地图应用程序中，您可以上传 CSV、GeoJSON 和 ESRI ShapeFiles 等分隔数据，还可以直接在地图中绘制图形。在本博客中，我们将重点介绍从 Kibana 主页导入 CSV 文件。</p><h3>导入机场</h3><p>第一个文件是<a href="https://raw.githubusercontent.com/elastic/elasticsearch-labs/refs/heads/main/supporting-blog-content/geospatial-data-ingest/airports/airports.csv">airports.csv</a>、我们需要处理一些有趣的怪癖。首先，列之间有额外的空白分隔，这不是 CSV 文件的典型特征。其次，<code>type</code> 字段是一个多值字段，我们需要将其分割成不同的字段。最后，有些字段不是字符串，需要转换为正确的类型。所有这些都可以使用 Kibana 的 CSV 导入功能来完成。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt393bc416bf425301/6a16f707839dfa1e86dcfca9/b1afd8c95973bec32f229a9adfaef14680b09a8e-1944x478.png" alt="Kibana 上传 - 预览" /><p>从 Kibana 主页开始。有一个名为"添加集成入门" 的部分，其中有一个名为"上传文件" 的链接：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte12fc741edab20d3/6a16f709acf088600fbe98bf/996372bb3a52859cc840bd1d8f984a33a0e7ada3-1180x410.png" alt="Kibana 主页 - 上传文件" /><p>点击该链接，您将进入"Upload file" 页面。在此，您可以拖放<code>airports.csv</code> 文件，Kibana 将分析该文件并为您提供数据预览。它应该会自动检测到分隔符是逗号，第一行是标题行。但是，假设所有字段都是<code>text</code> 或<code>keyword</code> ，它可能没有修剪列之间多余的空白，也没有确定字段的类型。我们需要解决这个问题。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf65a09ec0c7a61ad/6a16f70ad7c0227aedde626f/1d9ffa6cdbc0d67a228be1a747ec1ebda6a63099-1800x538.png" alt="Kibana 上传 - 预览" /><p>单击<code>Override settings</code> 并选中<code>Should trim fields</code> 复选框 ，然后<code>Apply</code> 关闭设置。现在，我们需要确定字段的类型。可在下一页查看，请点击<code>Import</code> 。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltda9a5f85d8e693dc/6a16f70c60084b72f53c4348/a97aceffb1858eae26a213336913505ae9923b03-1800x526.png" alt="Kibana 上传 - 导入" /><p>首先选择一个索引名称，然后选择<code>Advanced</code> ，进入字段映射和摄取处理器页面。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt56705083cdd9eb18/6a16f70d66c4f93350f8bdbb/058b0fb09da0b8d7f9c9b0a9c83b8b485ce43925-1440x629.png" alt="Kibana 上传 - 字段映射" /><p>在此，我们需要对索引的字段映射和导入数据的摄取管道进行更改。首先，虽然 Kibana 可能会将<code>scalerank</code> 字段自动检测为<code>long</code> ，但却误将<code>location</code> 和<code>city_location</code> 字段视为<code>keyword</code> 。将它们编辑为<code>geo_point</code> ，最后得到类似的映射：</p>{
  "properties": {
    "abbrev":        { "type": "keyword" },
    "city":          { "type": "keyword" },
    "city_location": { "type": "geo_point" },
    "country":       { "type": "keyword" },
    "elevation":     { "type": "double" },
    "location":      { "type": "geo_point" },
    "name":          { "type": "text" },
    "scalerank":     { "type": "long" },
    "type":          { "type": "keyword" }
  }
}<p>您在这里有一定的灵活性，但要注意的是，您选择的类型会影响字段的索引方式和可能的查询类型。例如，如果将<code>location</code> 设置为<code>keyword</code> ，则无法对其执行任何地理空间搜索查询。同样，如果将<code>elevation</code> 设置为<code>text</code> ，则无法对其执行数值范围查询。</p><p>现在是修复摄取管道的时候了。如果 Kibana 自动检测到<code>scalerank</code> 如上所示<code>long</code> ，它还会添加一个处理器，将字段转换为<code>long</code> 。我们需要为<code>elevation</code> 字段添加一个类似的处理器，这次将其转换为<code>double</code> 。编辑管道，确保您已将此转换到位。在保存之前，我们还需要进行一次转换，将<code>type</code> 字段分成多个字段。在管道中添加<code>split</code> 处理器，配置如下</p>{
  "split": {
    "field": "type",
    "separator": ":",
    "ignore_missing": true
  }
}<p>最终的摄取管道应该是这样的</p>{
  "description": "Ingest pipeline created by text structure finder",
  "processors": [
    {
      "csv": {
        "field": "message",
        "target_fields": [
          "abbrev",
          "name",
          "scalerank",
          "type",
          "location",
          "country",
          "city",
          "city_location",
          "elevation"
        ],
        "ignore_missing": false,
        "trim": true
      }
    },
    {
      "convert": {
        "field": "scalerank",
        "type": "long",
        "ignore_missing": true
      }
    },
    {
      "convert": {
        "field": "elevation",
        "type": "double",
        "ignore_missing": true
      }
    },
    {
      "split": {
        "field": "type",
        "separator": ":",
        "ignore_missing": true
      }
    },
    {
      "remove": {
        "field": "message"
      }
    }
  ]
}<p>请注意，我们没有为<code>location</code> 和<code>city_location</code> 字段添加转换处理器。这是因为字段映射中的<code>geo_point</code> 类型已经了解这些字段中数据的WKT格式。<code>geo_point</code> 类型可以理解一系列格式，包括<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/geo-point.html">WKT、GeoJSON 等</a>。例如，如果我们在 CSV 文件中有<code>latitude</code> 和<code>longitude</code> 两列，我们就需要添加<code>script</code> 或<code>set</code> 处理器，将这两列合并为一个<code>geo_point</code> 字段（例如。<code>"set": {"field": "location", "value": "{{lat}},{{lon}}"}</code>).</p><p>现在我们可以导入文件了。点击<code>Import</code> ，数据就会按照我们刚刚定义的映射和摄取管道导入索引。如果在摄取数据时出现任何错误，Kibana 会在这里报告，这样你就可以编辑源数据或摄取管道，然后再试一次。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte81621425c7289c5/6a16f70f839dfaf608dcfcad/55dde2940c7aba66ce8257d15007ef797b8a5107-1440x415.png" alt="Kibana 上传 - 导入" /><p>请注意，新的摄取管道已经创建。可进入 Kibana 的<code>Stack Management</code> 部分，选择<code>Ingest pipelines</code> 查看。在这里，您可以看到我们刚刚创建的管道，如有必要，还可以对其进行编辑。事实上，<code>Ingest pipelines</code> 部分可用于创建和测试摄取管道，如果你计划进行更复杂的摄取，这是一项非常有用的功能。</p><p>如果您想立即探索这些数据，请跳到后面的章节，但如果您还想导入城市边界，请继续阅读。</p><h3>导入城市边界</h3><p>与前一个示例相比，<a href="https://raw.githubusercontent.com/elastic/elasticsearch-labs/refs/heads/main/supporting-blog-content/geospatial-data-ingest/airports/airport_city_boundaries.csv">airport_city_boundaries.csv</a>中的城市边界文件导入更为简单。它包含一个<code>city_boundary</code> 字段和一个<code>city_location</code> 字段，前者是城市边界的 WKT 表示法，如<code>POLYGON</code> ，后者是城市位置的<code>geo_point</code> 表示法。我们可以用与机场数据类似的方式导入这些数据，但有一些不同之处：</p><ul><li><p>我们需要选择覆盖设置<code>Has header row</code> ，因为这不是自动检测到的</p></li><li><p>我们不需要修剪字段，因为数据中已经没有多余的空白了</p></li><li><p>由于所有类型都是字符串或空间类型，因此我们无需编辑采集管道</p></li><li><p>不过，我们必须编辑字段映射，将<code>city_boundary</code> 字段设置为<code>geo_shape</code> ，将<code>city_location</code> 字段设置为 <code>geo_point</code></p></li></ul><p>我们最终的字段映射如下</p>{
  "properties": {
    "abbrev":        { "type": "keyword" },
    "airport":       { "type": "keyword" },
    "city":          { "type": "keyword" },
    "city_boundary": { "type": "geo_shape" },
    "city_location": { "type": "geo_point" },
    "region":        { "type": "text" }
  }
}<p>与之前的<code>airports.csv</code> 导入一样，只需单击<code>Import</code> 即可将数据导入索引。数据将通过我们编辑的映射和 Kibana 定义的摄取管道导入。</p><h3>使用开发工具探索地理空间数据</h3><p>在 Kibana 中，通常使用"Discover" 探索索引数据。不过，如果您打算使用 ES|QL 查询编写自己的应用程序，尝试访问原始 Elasticsearch API 可能会更有趣。Kibana 有一个方便的控制台，可用于尝试编写查询。这就是所谓的<code>Dev Tools</code> 控制台，可以在 Kibana 侧边栏中找到。该控制台直接与 Elasticsearch 集群对话，可用于运行查询、创建索引等。</p><p>试试以下方法：</p>POST /_query?error_trace=true&amp;format=txt
{
  "query": """
FROM airports
| EVAL distance = ST_DISTANCE(city_location, TO_GEOPOINT("POINT(12.565 55.673)"))
| WHERE distance &lt; 1000000 AND scalerank &lt; 6 AND distance &gt; 10000
| SORT distance ASC
| KEEP distance, abbrev, name, location, country, city, elevation
| LIMIT 10
  """
}<p>结果如下</p><p>距离</p><p>缩写</p><p>名字</p><p>位置</p><p>国家</p><p>城市</p><p>标高</p><p>273418.05776847183</p><p>火腿</p><p>汉堡</p><p>点 (10.005647830925 53.6320011640866)</p><p>德国</p><p>诺德施泰特</p><p>17.0</p><p>337534.653466062</p><p>TXL</p><p>柏林泰格尔国际机场</p><p>点 (13.2903090925074 52.5544287044101)</p><p>德国</p><p>霍恩诺恩多夫</p><p>38.0</p><p>483713.15032266214</p><p>OSL</p><p>奥斯陆加德尔莫尼</p><p>点 (11.0991032762581 60.1935783171386)</p><p>挪威</p><p>奥斯陆</p><p>208.0</p><p>522538.03148094116</p><p>BMA</p><p>Bromma</p><p>点（17.9456175406145 59.3555902065112）</p><p>瑞典</p><p>斯德哥尔摩</p><p>15.0</p><p>522538.03148094116</p><p>ARN</p><p>阿尔兰达</p><p>点 (17.9307299016916 59.6511203397372)</p><p>瑞典</p><p>斯德哥尔摩</p><p>38.0</p><p>624274.8274399083</p><p>DUS</p><p>杜塞尔多夫国际机场</p><p>点 (6.76494446612174 51.2781820420774)</p><p>德国</p><p>杜塞尔多夫</p><p>45.0</p><p>633388.6966435644</p><p>PRG</p><p>鲁兹恩</p><p>点 (14.2674849854076 50.1076511703671)</p><p>捷克</p><p>布拉格</p><p>381.0</p><p>635911.1873311149</p><p>AMS</p><p>史基浦机场</p><p>点 (4.76437693232812 52.3089323889822)</p><p>荷兰</p><p>霍夫多尔普</p><p>-3.0</p><p>670864.137958866</p><p>法国</p><p>法兰克福国际机场</p><p>点 (8.57182286907608 50.0506770895207)</p><p>德国</p><p>法兰克福</p><p>111.0</p><p>683239.2529970079</p><p>WAW</p><p>奥克西国际机场</p><p>点 (20.9727263383587 52.171026749259)</p><p>波兰</p><p>Piaseczno</p><p>111.0</p><h2>使用 Kibana 地图可视化地理空间数据</h2><p>Kibana 地图是可视化地理空间数据的强大工具。它可用于创建具有多个图层的地图，每个图层代表不同的数据集。数据可以通过各种方式进行过滤、汇总和样式化。在本节中，我们将向您展示如何使用上一节导入的数据在 Kibana 地图中创建地图。</p><p>在 Kibana 菜单中，导航至<code>Analytics</code>-&gt;<code>Maps</code> ，打开新的地图视图。单击<code>Add Layer</code> ，选择<code>Documents</code> ，选择数据视图<code>airports</code> ，然后编辑图层样式，使用<code>elevation</code> 字段为标记着色，这样我们就可以很容易地看到每个机场的高度。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdcc4736d88c1abc9/6a16f7102b835f7353f4afca/9e63726d7c059331e6e20e474f8abee53dc2cbb4-840x388.png" alt="Kibana 地图 - 机场图层样式" /><p>单击 "保持更改 "保存地图：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8ec3cc535b5c21d8/6a16f71375879ec091fe15dc/32ad1dadb5d341a2b66662c58638b781122fc22c-2852x1528.png" alt="Kibana 地图 - 机场" /><p>现在添加第二个图层，这次选择<code>airport_city_boundaries</code> 数据视图。这次，我们将使用<code>city_boundary</code> 字段对图层进行样式设置，并将填充颜色设为淡蓝色。这将在地图上显示城市边界。确保对图层重新排序，以确保机场标记位于顶部。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2932d7ff4a7206c9/6a16f7156f7f0409f09145c7/53dd65026f9a98c13f2cc4f9328a79242f0b70de-2854x1510.png" alt="Kibana 地图 - 城市边界图层样式" /><h2>空间连接</h2><p>ES|QL 不支持<code>JOIN</code> 命令，但可以使用<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/esql-commands.html#esql-enrich"><code>ENRICH</code></a> <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/esql-commands.html#esql-enrich">命令</a>实现连接的特殊情况。该命令的操作类似于 SQL 中的 "左连接"，允许您根据两个数据集之间的空间关系，用另一个索引中的数据来丰富一个索引的结果。</p><p>例如，我们可以通过查找包含机场位置的城市边界，用机场服务城市的附加信息来丰富机场表的结果，然后对结果进行一些统计：</p>FROM airports
| ENRICH city_boundaries ON city_location WITH airport, region, city_boundary
| STATS
    centroid = ST_CENTROID_AGG(location),
    count = COUNT(city_location)
    BY region
| SORT count DESC
| LIMIT 10<p>如果不先准备 enrich 索引就运行该查询，将会收到类似的错误信息：</p>cannot find enrich policy [city_boundaries]<p>如前所述，这是因为 ES|QL 不支持真正的<code>JOIN</code> 命令。其中一个重要原因是 Elasticsearch 是一个分布式系统，而连接是一种昂贵的操作，很难扩展。不过，<code>ENRICH</code> 命令的效率相当高，因为它利用了专门编制的在整个集群中复制的丰富索引，从而可以在每个节点上执行本地连接。</p><p>为了更好地理解这一点，让我们把重点放在上面查询中的<code>ENRICH</code> 命令上：</p>FROM airports
| ENRICH city_boundaries ON city_location WITH airport, region, city_boundary<p>该命令指示 Elasticsearch 丰富从<code>airports</code> 索引中获取的结果，并在原始索引的<code>city_location</code> 字段和<code>airport_city_boundaries</code> 索引的<code>city_boundary</code> 字段之间执行<code>intersects</code> 连接，我们在前面的几个示例中使用了该连接。但其中一些信息在该查询中并不清晰。我们看到的是丰富策略的名称<code>city_boundaries</code> ，缺失的信息被封装在该策略定义中。</p>{
  "geo_match": {
    "indices": "airport_city_boundaries",
    "match_field": "city_boundary",
    "enrich_fields": ["city", "airport", "region", "city_boundary"]
  }
}<p>在这里我们可以看到，它将执行<code>geo_match</code> 查询（<code>intersects</code> 是默认值），要匹配的字段是<code>city_boundary</code> ，而<code>enrich_fields</code> 是我们要添加到原始文档中的字段。其中一个字段<code>region</code> 实际上被用作<code>STATS</code> 命令的分组键，如果没有这种 "左连接 "功能，我们是做不到这一点的。有关 enrich 策略的更多信息，请参阅<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/enrich-setup.html">enrich 文档</a>。</p><p>Elasticsearch 中的丰富索引和策略最初是为在索引时使用另一个准备好的丰富索引中的数据来丰富数据而设计的。不过，在 ES|QL 中，<code>ENRICH</code> 命令在查询时工作，不需要使用摄取管道。这实际上使它与 SQL<code>LEFT JOIN</code> 非常相似，只是不能连接任何两个索引，只能在左侧连接一个普通索引，在右侧连接一个专门编制的丰富索引。</p><p>无论在哪种情况下，无论是用于摄取管道还是在 ES|QL 中使用，都有必要执行一些准备步骤来设置丰富索引和策略。上文我们已经导入了<code>airport_city_boundaries</code> 索引，但在<code>ENRICH</code> 命令中，它不能直接用作丰富索引。我们首先需要执行两个步骤：</p><ul><li><p>创建上述丰富策略，以定义源索引、源索引中要匹配的字段以及匹配后要返回的字段。</p></li><li><p>执行该策略可创建丰富索引。这将建立一个特殊的内部索引，将原始源索引读取到一个更高效的数据结构中，并在整个集群中复制。</p></li></ul><p>可以使用以下命令创建丰富策略：</p>PUT /_enrich/policy/city_boundaries
{
  "match": {
    "indices": "airport_city_boundaries",
    "match_field": "city_boundary",
    "enrich_fields": ["city", "airport", "region", "city_boundary"]
  }
}<p>可以使用以下命令执行该策略：</p>POST /_enrich/policy/city_boundaries/_execute<p>请注意，如果您更改了<code>airport_city_boundaries</code> 索引的内容，则需要重新执行该策略，才能在丰富索引中看到更改的内容。现在，让我们再次运行原始 ES|QL 查询：</p>FROM airports
| ENRICH city_boundaries ON city_location WITH airport, region, city_boundary
| STATS
    centroid = ST_CENTROID_AGG(location),
    count = COUNT(city_location)
    BY region
| SORT count DESC
| LIMIT 10<p>这将返回拥有最多机场的前 5 个区域，以及所有匹配区域机场的中心点和这些区域内城市边界的 WKT 表示长度范围：</p><p>中心点</p><p>计数</p><p>地区</p><p>点（-12.13908685930073331.024386116624648)</p><p>126</p><p>无效</p><p>点（-83.1039831787347842.300230911932886)</p><p>3</p><p>底特律</p><p>点 (39.74537850357592 47.21613017376512)</p><p>3</p><p>городской округ Батайск</p><p>点（-156.8098678719252320.476673701778054)</p><p>3</p><p>夏威夷</p><p>点（-73.9451533276587740.70366442203522)</p><p>3</p><p>纽约市</p><p>点（-83.1039831787347842.300230911932886)</p><p>3</p><p>底特律</p><p>点（-76.6687301918864324.306286952923983)</p><p>2</p><p>新普罗维登斯</p><p>点 (-3.0252167768776417 51.39245774131268)</p><p>2</p><p>加的夫</p><p>点（-115.4099348466843432.73126147687435)</p><p>2</p><p>墨西卡利市</p><p>点 (41.790108773857355 50.302146775648)</p><p>2</p><p>Центральный район</p><p>点（-73.8890273217111845.57078813901171)</p><p>2</p><p>蒙特利尔</p><p>您可能还会注意到，最常见的地区是<code>null</code> 。这意味着什么？回想一下，我曾将此命令比作 SQL 中的 "左连接"，也就是说，如果没有为某个机场找到匹配的城市边界，则仍会返回该机场，但<code>airport_city_boundaries</code> 索引中的字段值为<code>null</code> 。结果发现，有 125 个机场没有找到匹配的<code>city_boundary</code> ，有一个机场找到了匹配的<code>region</code> 字段，但该字段是<code>null</code> 。这样就统计出了 126 个机场，结果中没有<code>region</code> 。如果您的用例要求所有机场都能与城市边界相匹配，那就需要获取更多数据来填补空白。有必要确定两件事：</p><ul><li><p><code>airport_city_boundaries</code> 索引中哪些记录没有<code>city_boundary</code> 字段</p></li><li><p><code>airports</code> 索引中哪些记录与<code>ENRICH</code> 命令不匹配（即"......"）。不相交）</p></li></ul><h2>在 Kibana 地图中使用 ES|QL 获取地理空间数据</h2><p>Kibana 在地图应用程序中添加了对空间 ES|QL 的支持。这意味着您现在可以使用 ES|QL 在 Elasticsearch 中搜索地理空间数据，并在地图上将结果可视化。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt91922eb4ef43d290/6a16f7161949f737bfe7a7b5/bd78470bd8a4bc60f0db7006bd804b8fe87e2fea-1440x683.png" alt="Kibana 层 ES|QL" /><p>在添加图层菜单中有一个新的图层选项，名为"ES|QL" 。与迄今为止介绍的所有地理空间功能一样，该功能在"技术预览版" 中。选择该选项可根据 ES|QL 查询结果在地图上添加图层。例如，您可以在地图上添加一个图层，显示世界上所有的机场。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5185ef7461d7e81d/6a16f718839dfa1559dcfcb1/1dd28d3d0509f92d26b0bb5320a2925f7a54c5d9-1440x736.png" alt="Kibana ES|QL - 机场" /><p>或者您也可以添加一个图层，显示<code>airport_city_boundaries</code> 索引中的多边形，或者更好的办法是使用上面那个复杂的<code>ENRICH</code> 查询，生成每个地区有多少个机场的统计数据？</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt51ee3543bb811d98/6a16f71a8b73cbbe63189df1/679a0a401faa613c7cedddd07c64f614ac2b7144-1440x727.png" alt="Kibana ES|QL - 区域统计" /><h2>后续工作计划</h2><p>上一篇<a href="https://www.elastic.co/search-labs/blog/esql-geospatial-search-part-one">地理空间搜索</a>博客主要介绍了使用<code>ST_INTERSECTS</code> 等函数进行搜索的方法，Elasticsearch 自 8.14 版起提供了这些功能。本博客将向您展示如何导入我们用于这些搜索的数据。不过，Elasticsearch 8.15 提供了一个特别有趣的功能：<code>ST_DISTANCE</code> ，可用于执行高效的空间距离搜索，这将是下一篇博客的主题！</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/geospatial-data-ingest-for-esql</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/geospatial-data-ingest-for-esql</guid>
    <category><![CDATA[Kibana]]></category>
    <category><![CDATA[ES|QL]]></category>
    <dc:creator><![CDATA[Craig Taverner]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfc041ebca11476c6/6a16f70560084b31b93c4344/01fde3b1d714f12bf8673140c9f2f940d443de31-1440x823.jpg" length="0" type="image/jpeg"/>
    <pubDate>Fri, 25 Oct 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[使用 ES|QL 进行 Elasticsearch 地理空间搜索]]></title>
    <description><![CDATA[Elasticsearch 查询语言 (ES|QL) 中的地理空间搜索。Elasticsearch 具有强大的地理空间搜索功能，ES|QL 将大幅提高易用性和 OGC 熟悉度。]]></description>
    <content:encoded><![CDATA[<p>多年来，Elasticsearch 一直拥有强大的<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/geospatial-analysis.html">地理空间搜索和分析功能</a>，但其应用程序接口与典型 GIS 用户习惯的应用程序接口大相径庭。去年，我们<a href="https://www.elastic.co/search-labs/blog/esql-piped-query-language-goes-ga"> 添加了 ES|QL 查询语言</a> ，这是一种管道式 查询语言 ，与 SQL 一样简单，甚至更容易。它特别适合 Elastic 擅长的搜索、安全和可观察性用例。我们还在 ES|QL 中添加了对地理空间搜索和分析的支持，使其更易于使用，特别是对于来自 SQL 或<a href="https://en.wikipedia.org/wiki/Geographic_information_system">GIS</a>社区的用户。</p><p>Elasticsearch 8.12 和 8.13 为 ES|QL 带来了对地理空间类型的基本支持。8.14 版增加了地理空间搜索功能后，这一功能大大增强。更重要的是，这种支持的设计与其他空间数据库（如<a href="https://en.wikipedia.org/wiki/Simple_Features"> </a><a href="https://en.wikipedia.org/wiki/Open_Geospatial_Consortium">PostGIS ）</a> 使用的 开放地理空间联盟（OGC） 的 简单地物访问 标准非常一致，使熟悉这些标准的 GIS 专家更容易使用。</p><p>在本博客中，我们将向您介绍如何使用 ES|QL 执行地理空间搜索，以及它与 SQL 和查询 DSL 的等效功能的比较。我们还将向您展示如何使用 ES|QL 执行空间连接，以及如何在 Kibana 地图中将结果可视化。请注意，此处描述的所有功能都在"技术预览版" 中，我们非常乐意听取您对我们如何改进这些功能的反馈意见。</p><h2>搜索地理空间数据</h2><p>让我们从一个查询示例开始：</p>FROM airport_city_boundaries
| WHERE ST_INTERSECTS(
      city_boundary,
      "POLYGON((109.4 18.1, 109.6 18.1, 109.6 18.3, 109.4 18.3, 109.4 18.1))"::geo_shape
  )
| KEEP abbrev, airport, region, city, city_location
<p>这将搜索与三亚凤凰国际机场 (SYX) 周围矩形搜索多边形相交的任何城市边界多边形。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3897df6bed5d6061/6a17d7c2abe0f29eccdfe861/e48bac8f246c8842f2ea97ddd54910045262aeb1-1440x808.png" alt="ESQL 地理空间搜索" /><p>在一个包含机场、城市和城市边界的示例数据集中，该搜索可找到相交的多边形，并从匹配文档中返回所需字段：</p><p>缩写</p><p>机场</p><p>地区</p><p>城市</p><p>城市位置</p><p>SYX</p><p>三亚凤凰国际机场</p><p>天涯区</p><p>三亚</p><p>point(109.5036 18.2533)</p><p>这很容易！现在将其与经典的 Elasticsearch 查询 DSL 进行比较：</p>GET /airport_city_boundaries/_search
{
  "_source": ["abbrev", "airport", "region", "city", "city_location"],
  "query": {
    "geo_shape": {
      "city_boundary": {
        "shape": {
          "type": "polygon",
          "coordinates" : [[
            [109.4, 18.1],
            [109.6, 18.1],
            [109.6, 18.3],
            [109.4, 18.3],
            [109.4, 18.1]
          ]]
        }
      }
    }
  }
}
<p>这两个查询的意图都相当明确，但 ES|QL 查询与 SQL 非常相似。同样的查询在 PostGIS 中是这样的：</p>SELECT abbrev, airport, region, city, city_location
FROM airport_city_boundaries
WHERE ST_INTERSECTS(
    city_boundary,
    'SRID=4326;POLYGON((109.4 18.1, 109.6 18.1, 109.6 18.3, 109.4 18.3, 109.4 18.1))'::geometry
);
<p>请看 ES|QL 的例子。如此相似，不是吗？</p>FROM airport_city_boundaries
| WHERE ST_INTERSECTS(
      city_boundary,
      "POLYGON((109.4 18.1, 109.6 18.1, 109.6 18.3, 109.4 18.3, 109.4 18.1))"::geo_shape
  )
| KEEP abbrev, airport, region, city, city_location
<p>我们发现，Elasticsearch API 的现有用户发现 ES|QL 更易于使用。现在，我们希望现有的 SQL 用户，尤其是空间 SQL 用户，会发现 ES|QL 与他们习惯看到的感觉非常相似。</p><h4>为什么不是 SQL？</h4><p>Elasticsearch SQL 如何？它已经存在了一段时间，并具有一些地理空间功能。不过，Elasticsearch SQL 是作为原始查询 API 的封装器编写的，这意味着只支持可转译到原始 API 的查询。ES|QL 没有这种限制。作为一个全新的堆栈，它可以进行许多 SQL 无法实现的优化。我们的基准测试表明，ES|<a href="https://elasticsearch-benchmarks.elastic.co/#tracks/esql/nightly/default/6M">QQL 通常比查询 API 更快</a>，尤其是在聚合方面！</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt18bda964c8b24e36/6a17d7c3e3179155d22d568a/b8b6c2b2e45850d832805ed1e71e522f4955f53c-1440x813.png" alt="多边形交叉点基准" /><h2>与 SQL 的区别</h2><p>显然，从前面的例子来看，ES|QL 与 SQL 有些相似，但也有一些重要的区别。例如，ES|QL 是一种管道式查询语言，它以 FROM 等源命令开始，然后用管道 | 字符将所有后续命令串联起来。这样就很容易理解每条命令是如何接收数据表并对该表执行某些操作的，例如使用<code>WHERE</code> 进行过滤，使用<code>EVAL</code> 添加列，或使用<code>STATS</code> 执行聚合。与从<code>SELECT</code> 开始定义最终输出列不同，可以有一个或多个<code>KEEP</code> 命令，最后一个命令指定最终输出结果。这种结构简化了查询的推理过程。</p><p>仔细观察上述示例中的<code>WHERE</code> 命令，我们会发现它与 PostGIS 示例非常相似：</p><p><em>ES|QL</em></p>WHERE ST_INTERSECTS(
    city_boundary,
    "POLYGON((109.4 18.1, 109.6 18.1, 109.6 18.3, 109.4 18.3, 109.4 18.1))"::geo_shape
)
<p><em>PostGIS</em></p>WHERE ST_INTERSECTS(
    city_boundary,
    'SRID=4326;POLYGON((109.4 18.1, 109.6 18.1, 109.6 18.3, 109.4 18.3, 109.4 18.1))'::geometry
)
<p>除了字符串引号字符的不同，最大的区别在于我们如何将字符串类型转换为空间类型。在 PostGIS 中，我们使用<code>::geometry</code> 后缀，而在 ES|QL 中，我们使用<code>::geo_shape</code> 后缀。这是因为 ES|QL 在 Elasticsearch 中运行，而类型转换操作符<code>::</code> 可用于将字符串转换为<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/esql-limitations.html#_supported_types">ES|QL 支持的</a>任何类型，在本例中就是<code>geo_shape</code> 。此外，Elasticsearch 中的<code>geo_shape</code> 和<code>geo_point</code> 类型意味着空间坐标系为 WGS84，通常使用 SRID 编号 4326。在 PostGIS 中，这一点需要明确，因此在 WKT 字符串中使用了<code>SRID=4326;</code> 前缀。如果删除该前缀，SRID 将被设置为 0，这更像 Elasticsearch 类型<code>cartesian_point</code> 和<code>cartesian_shape</code> ，它们与任何特定坐标系无关。</p><p>ES|QL 和 PostGIS 也提供了类型转换函数语法：</p><p><em>ES|QL</em></p>WHERE ST_INTERSECTS(
    city_boundary,
    TO_GEOSHAPE("POLYGON((109.4 18.1, 109.6 18.1, 109.6 18.3, 109.4 18.3, 109.4 18.1))")
)
<p><em>PostGIS</em></p>WHERE ST_INTERSECTS(
    city_boundary,
    ST_SetSRID(
      ST_GeomFromText('POLYGON((109.4 18.1, 109.6 18.1, 109.6 18.3, 109.4 18.3, 109.4 18.1))'),
      4326
    )
)
<h2>OGC 功能</h2><p>Elasticsearch 8.14 引入了以下四种 OGC 空间搜索功能：</p><p>ES|QL</p><p>PostGIS</p><p>描述</p><p>ST_INTERSECTS</p><p>ST_Intersects</p><p>如果两个几何图形相交，则返回 true，否则返回 false。</p><p>ST_DISJOINT</p><p>ST_Disjoint</p><p>如果两个几何图形不相交，则返回 true，否则返回 false。ST_INTERSECTS 的倒数。</p><p>ST_CONTAINS</p><p>ST_Contains</p><p>如果一个几何体包含另一个几何体，则返回 true，否则返回 false。</p><p>ST_WITHIN</p><p>ST_Within</p><p>如果一个几何体位于另一个几何体内，则返回 true，否则返回 false。ST_CONTAINS 的倒数。</p><p>这些函数的行为与 PostGIS 的对应函数类似，使用方法也相同。例如，如果两个几何图形相交，<code>ST_INTERSECTS</code> 返回 true，否则返回 false。如果您跟踪上表中的文档链接，可能会发现所有 ES|QL 示例都在<code>FROM</code> 子句之后的<code>WHERE</code> 子句中，而所有 PostGIS 示例都使用了字面几何图形。事实上，这两个平台都支持在查询的任何部分使用这些函数。</p><p><code>ST_INTERSECTS</code> 的 PostGIS 文档中的第一个示例是</p>SELECT ST_Intersects(
    'POINT(0 0)'::geometry,
    'LINESTRING ( 2 0, 0 2 )'::geometry
);
<p>ES|QL 相当于此：</p>ROW ST_INTERSECTS(
    "POINT(0 0)"::geo_point,
    "LINESTRING ( 2 0, 0 2 )"::geo_shape
)
<p>请注意我们在 PostGIS 示例中没有指定 SRID。这是因为在 PostGIS 中使用<code>geometry</code> 类型时，所有计算都是在平面坐标系上进行的，因此如果两个几何图形具有相同的 SRID，SRID 是什么并不重要。在 Elasticsearch 中，大多数函数也是如此，但也有例外，<code>geo_shape</code> 和<code>geo_point</code> 使用球形计算，我们将在下一篇关于空间距离搜索的博客中看到这一点。</p><h2>ES|QL 多功能性</h2><p>因此，我们已经看到了在<code>WHERE</code> 子句和<code>ROW</code> 命令中使用空间函数的示例。它们还能在哪里发挥作用？<code>EVAL</code> 命令就是一个非常有用的地方。该命令允许您评估表达式并返回结果。例如，让我们确定按国名分组的所有机场的中心点是否都在勾勒出国家的边界内：</p>FROM airports
| EVAL in_uk = ST_INTERSECTS(location, TO_GEOSHAPE("POLYGON((1.2305 60.8449, -1.582 61.6899, -10.7227 58.4017, -7.1191 55.3291, -7.9102 54.2139, -5.4492 54.0078, -5.2734 52.3756, -7.8223 49.6676, -5.0977 49.2678, 0.9668 50.5134, 2.5488 52.1065, 2.6367 54.0078, -0.9668 56.4625, 1.2305 60.8449))"))
| EVAL in_iceland = ST_INTERSECTS(location, TO_GEOSHAPE("POLYGON ((-25.4883 65.5312, -23.4668 66.7746, -18.4131 67.4749, -13.0957 66.2669, -12.3926 64.4159, -20.1270 62.7346, -24.7852 63.3718, -25.4883 65.5312))"))
| EVAL within_uk = ST_WITHIN(location, TO_GEOSHAPE("POLYGON((1.2305 60.8449, -1.582 61.6899, -10.7227 58.4017, -7.1191 55.3291, -7.9102 54.2139, -5.4492 54.0078, -5.2734 52.3756, -7.8223 49.6676, -5.0977 49.2678, 0.9668 50.5134, 2.5488 52.1065, 2.6367 54.0078, -0.9668 56.4625, 1.2305 60.8449))"))
| EVAL within_iceland = ST_WITHIN(location, TO_GEOSHAPE("POLYGON ((-25.4883 65.5312, -23.4668 66.7746, -18.4131 67.4749, -13.0957 66.2669, -12.3926 64.4159, -20.1270 62.7346, -24.7852 63.3718, -25.4883 65.5312))"))
| STATS centroid = ST_CENTROID_AGG(location), count=COUNT() BY in_uk, in_iceland, within_uk, within_iceland
| SORT count ASC
<p>结果在意料之中，英国机场的中心点在英国边界内，而不在冰岛边界内，反之亦然：</p><p>中心点</p><p>计数</p><p>英国</p><p>冰岛</p><p>within_uk</p><p>冰岛境内</p><p>点（-21.94663446396589364.13187285885215)</p><p>1</p><p>错误</p><p>true</p><p>错误</p><p>true</p><p>点 (-2.597342072712148 54.33551226578214)</p><p>17</p><p>true</p><p>错误</p><p>true</p><p>错误</p><p>点 (0.04453958108176276 23.74658354606057)</p><p>873</p><p>错误</p><p>错误</p><p>错误</p><p>错误</p><p>事实上，这些函数可用于查询的任何部分，只要其签名是合理的。它们都接收两个参数，参数可以是一个字面空间对象，也可以是一个空间类型的字段，并且都返回一个布尔值。一个重要的考虑因素是，几何图形的坐标参考系（CRS）必须匹配，否则将返回错误信息。这意味着不能在同一个函数调用中混合使用<code>geo_shape</code> 和<code>cartesian_shape</code> 类型。不过，您可以混合使用<code>geo_point</code> 和<code>geo_shape</code> 类型，因为<code>geo_point</code> 类型是<code>geo_shape</code> 类型的特例，两者共享相同的坐标参考系。上文定义的每个函数的文档都列出了支持的类型组合。</p><p>此外，任一参数都可以是空间字面量或字段，顺序不限。您甚至可以指定两个字段、两个字面量、一个字段和一个字面量或一个字面量和一个字段。唯一的要求是类型必须兼容。例如，此查询比较同一索引中的两个字段：</p>FROM airport_city_boundaries
| EVAL in_city = ST_INTERSECTS(city_location, city_boundary)
| STATS count=COUNT(*) BY in_city
| SORT count ASC
| EVAL cardinality = CASE(count &lt; 10, "very few", count &lt; 100, "few", "many")
| KEEP cardinality, count, in_city
<p>该查询基本上是询问城市位置是否在城市边界内，一般来说应该是正确的，但总有例外：</p><p>基数</p><p>计数</p><p>城市</p><p>很少</p><p>29</p><p>错误</p><p>很多</p><p>740</p><p>true</p><p>一个更有趣的问题是，机场所在地是否在机场服务城市的边界之内。不过，机场位置所在的索引与包含城市边界的索引不同。这就需要一种方法来有效查询和关联这两个独立索引中的数据。</p><h2>空间连接</h2><p>ES|QL 不支持<code>JOIN</code> 命令，但可以使用<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/esql-commands.html#esql-enrich"><code>ENRICH</code></a><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/esql-commands.html#esql-enrich"> 命令</a>实现连接的特殊情况，其行为类似于 SQL 中的 "左连接"。该命令的操作类似于 SQL 中的 "左连接"，允许您根据两个数据集之间的空间关系，用另一个索引中的数据来丰富一个索引的结果。</p><p>例如，我们可以通过查找包含机场位置的城市边界，用机场服务城市的附加信息来丰富机场表的结果，然后对结果进行一些统计：</p>FROM airports
| ENRICH city_boundaries ON city_location WITH airport, region, city_boundary
| MV_EXPAND city_boundary
| EVAL boundary_wkt_length = LENGTH(TO_STRING(city_boundary))
| STATS centroid = ST_CENTROID_AGG(location), count = COUNT(city_location), min_wkt = MIN(boundary_wkt_length), max_wkt = MAX(boundary_wkt_length) BY region
| SORT count DESC
| LIMIT 5
<p>这将返回拥有最多机场的前 5 个区域，以及所有匹配区域机场的中心点和这些区域内城市边界的 WKT 表示长度范围：</p><p>中心点</p><p>计数</p><p>min_wkt</p><p>max_wkt</p><p>地区</p><p>点（-32.5609347096071932.598117914802714)</p><p>90</p><p>207</p><p>207</p><p>无效</p><p>点（-73.9451533276587740.70366442203522)</p><p>9</p><p>438</p><p>438</p><p>纽约市</p><p>点（-83.1039831787347842.300230911932886)</p><p>9</p><p>473</p><p>473</p><p>底特律</p><p>点（-156.302024586126220.176383580081165)</p><p>5</p><p>307</p><p>803</p><p>夏威夷</p><p>点（-73.8890273217111845.57078813901171)</p><p>4</p><p>837</p><p>837</p><p>蒙特利尔</p><p>那么，这里到底发生了什么？所谓的<code>JOIN</code> 发生在哪里？问题的关键在于<code>ENRICH</code> 命令：</p>FROM airports
| ENRICH city_boundaries ON city_location WITH airport, region, city_boundary
<p>该命令指示 Elasticsearch 丰富从<code>airports</code> 索引中获取的结果，并在原始索引的<code>city_location</code> 字段和<code>airport_city_boundaries</code> 索引的<code>city_boundary</code> 字段之间执行<code>intersects</code> 连接，我们在前面的几个示例中使用了该连接。但其中一些信息在该查询中并不清晰。我们看到的是丰富策略的名称<code>city_boundaries</code> ，缺失的信息被封装在该策略定义中。</p>{
  "geo_match": {
    "indices": "airport_city_boundaries",
    "match_field": "city_boundary",
    "enrich_fields": ["city", "airport", "region", "city_boundary"]
  }
}
<p>在这里我们可以看到，它将执行<code>geo_match</code> 查询（<code>intersects</code> 是默认值），要匹配的字段是<code>city_boundary</code> ，而<code>enrich_fields</code> 是我们要添加到原始文档中的字段。其中一个字段<code>region</code> 实际上被用作<code>STATS</code> 命令的分组键，如果没有这种 "左连接 "功能，我们是做不到这一点的。有关 enrich 策略的更多信息，请参阅<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/enrich-setup.html">enrich 文档</a>。在阅读这些文档时，你会注意到它们描述了如何通过配置摄取管道，在索引时使用丰富索引来丰富数据。ES|QL 不需要这样做，因为<code>ENRICH</code> 命令在查询时起作用。只需用必要的数据和丰富策略准备丰富索引，然后在 ES|QL 查询中使用<code>ENRICH</code> 命令即可。</p><p>您可能还会注意到，最常见的地区是<code>null</code> 。这意味着什么？回想一下，我曾将此命令比作 SQL 中的 "左连接"，也就是说，如果没有为某个机场找到匹配的城市边界，则仍会返回该机场，但<code>airport_city_boundaries</code> 索引中的字段值为<code>null</code> 。结果发现，有 89 个机场没有找到匹配的<code>city_boundary</code> ，有一个机场找到了匹配的<code>region</code> 字段，但该字段是<code>null</code> 。这样就统计出了 90 个机场，结果中没有<code>region</code> 。另一个有趣的细节是<code>MV_EXPAND</code> 命令的必要性。这样做是必要的，因为<code>ENRICH</code> 命令可能会为每个输入行返回多个结果，而<code>MV_EXPAND</code> 则有助于将这些结果分成多行，每个结果一行。这也解释了为什么"Hawaii" 会显示不同的<code>min_wkt</code> 和<code>max_wkt</code> 结果：存在多个名称相同但边界不同的区域。</p><h2>Kibana 地图</h2><p>Kibana 在地图应用程序中添加了对空间 ES|QL 的支持。这意味着您现在可以使用 ES|QL 在 Elasticsearch 中搜索地理空间数据，并在地图上将结果可视化。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt91922eb4ef43d290/6a16f7161949f737bfe7a7b5/bd78470bd8a4bc60f0db7006bd804b8fe87e2fea-1440x683.png" alt="Kibana 层 ES|QL" /><p>在添加图层菜单中有一个新的图层选项，名为"ES|QL" 。与迄今为止介绍的所有地理空间功能一样，该功能在"技术预览版" 中。选择该选项可根据 ES|QL 查询结果在地图上添加图层。例如，您可以在地图上添加一个图层，显示世界上所有的机场。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5185ef7461d7e81d/6a16f718839dfa1559dcfcb1/1dd28d3d0509f92d26b0bb5320a2925f7a54c5d9-1440x736.png" alt="Kibana ES|QL - 机场" /><p>或者您也可以添加一个图层，显示<code>airport_city_boundaries</code> 索引中的多边形，或者更好的办法是使用上面那个复杂的<code>ENRICH</code> 查询，生成每个地区有多少个机场的统计数据？</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt51ee3543bb811d98/6a16f71a8b73cbbe63189df1/679a0a401faa613c7cedddd07c64f614ac2b7144-1440x727.png" alt="Kibana ES|QL - 区域统计" /><h2>后续工作计划</h2><p>你可能已经注意到，在上面的两个例子中，我们又加入了一个空间函数<code>ST_CENTROID_AGG</code> 。这是<code>STATS</code> 命令中使用的聚合函数，也是我们计划添加到 ES|QL 中的众多空间分析功能中的第一个。当我们有更多展示时，我们会在博客上介绍！</p><p>在此之前，我们想向您详细介绍我们正在开发的一项特别令人兴奋的功能：执行空间距离搜索的能力，这是 Elasticsearch 最常用的空间搜索功能之一。你能想象距离搜索的语法会是什么样子吗？或许类似于 OGC 功能？敬请关注本系列的下一篇博客，一探究竟！</p><p>剧透警告：Elasticsearch 8.15 刚刚发布，其中包含使用 ES|QL 的空间距离搜索！</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/esql-geospatial-search-part-one</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/esql-geospatial-search-part-one</guid>
    <category><![CDATA[Python]]></category>
    <category><![CDATA[ES|QL]]></category>
    <dc:creator><![CDATA[Craig Taverner]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd05627be20e89dfb/6a17d7c6414c640256944fdb/de886289dcb56494920875303b622b030b9b810f-720x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Mon, 12 Aug 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[从 ES|QL 到 PHP 对象]]></title>
    <description><![CDATA[学习如何在 PHP 中执行和管理 ES|QL 查询。请按照本指南将 ES|QL 结果映射到 PHP 对象或自定义类。]]></description>
    <content:encoded><![CDATA[<p>从 elasticsearch-php<a href="https://github.com/elastic/elasticsearch-php/releases/tag/v8.13.0">v8.13.0</a>开始，您可以执行<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/esql.html">ES|QL</a>查询，并将结果映射到<a href="https://www.php.net/manual/en/class.stdclass.php">stdClass</a>或自定义类的 PHP 对象。</p><h2>ES|QL</h2><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/esql.html">ES|QL</a>是 Elasticsearch 8.11.0 中引入的一种新的 Elasticsearch 查询语言。目前，它还处于技术预览阶段。它为过滤、转换和分析存储在 Elasticsearch 中的数据提供了一种强大的方法。</p><p>它利用"管道" (<code>|</code>) 来逐步操作和转换数据。通过这种方法，用户可以进行一系列操作，其中一个操作的输出将成为下一个操作的输入，从而实现复杂的数据转换和分析。</p><p>例如，下面的查询返回<code>sample_data</code> 索引的前 3 个文档（行）：</p>FROM sample_data
| LIMIT 3
<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8b0310cb335872b7/6a17d7eab1e113258679f0df/c23aee777bacdf90c63b717fd9458207dfc0511d-864x284.png" alt="ES|QL 生成表格" /><h2>使用案例：官方 PHP 客户端中的 ES|QL 功能</h2><p>为了说明在官方 PHP 客户端中开发的 ES|QL 功能，我们在 Elasticsearch 中存储了一个包含 81,828 本图书（54.4 MB）的<a href="https://github.com/elastic/elasticsearch-php-examples/blob/main/examples/ESQL/data/books.csv">CSV 文件</a>，其中包括以下信息：</p>Title;Descrition;Author;Year;Publisher;Ratings
<p>我们从公开的<a href="https://www.kaggle.com/datasets/mohamedbakhet/amazon-books-reviews">亚马逊图书评论数据集中</a>提取了这份清单。</p><p>我们使用以下 Elasticsearch 映射创建了<code>books</code> 索引：</p>'mappings' : {
    'properties': {
        'title': {
            'type': 'text'
        },
        'description': {
            'type': 'text'
        },
        'author': {
            'type': 'text'
        },
        'year': {
            'type': 'short'
        },
        'publisher': {
            'type': 'keyword'
        },
        'rating': {
            'type': 'half_float'
        }
    }
}
<p><code>rating</code> 值是从 2.9 GB 的<a href="https://www.kaggle.com/datasets/mohamedbakhet/amazon-books-reviews?select=Books_rating.csv">Books_rating.csv</a>文件中提取的评论排名平均值。</p><p><a href="https://github.com/elastic/elasticsearch-php-examples/blob/main/examples/ESQL/bulk.php">在这里</a>，你可以找到我们用来批量导入 Elasticsearch 中所有图书的 PHP 脚本。使用 PHP 8.2.17 进行批量操作耗时 7 秒，占用 28 MB 内存。使用建议的映射后，Elasticsearch 中的索引大小约为 62 MB。</p><h2>将 ES|QL 结果映射到 PHP 对象或自定义类</h2><p>我们可以使用<code>esql()-&gt;query()</code> 端点在 PHP 中执行 ES|QL 查询。查询结果是一个表格数据结构。这在 JSON 中使用<code>columns</code> 和<code>values</code> 字段来表示。在<code>columns</code> 字段中，我们有<code>name</code> 和<code>type</code> 的定义。</p><p>下面是一个 ES|QL 查询示例，用于检索斯蒂芬-金著作中按用户评论排名排序的前 10 本书籍：</p>$query = &lt;&lt;&lt;EOD
    FROM books
    | WHERE author == "Stephen King"
    | SORT rating DESC
    | LIMIT 10
EOD;

$result = $client-&gt;esql()-&gt;query([
    'body' =&gt; ['query' =&gt; $query]
]);
<p>来自 Elasticsearch 的 JSON 结果如下：</p>{
    "columns": [
        { "name": "author", "type": "text" },
        { "name": "description", "type": "text" },
        { "name": "publisher", "type": "keyword" },
        { "name": "rating", "type": "double" },
        { "name": "title", "type": "text" },
        { "name": "year", "type": "integer" }
    ],
    "values": [
        [
            "Stephen King",
            "The author ...",
            "Turtleback",
            5.0,
            "How writers write",
            2002
        ],
        [
            "Stephen King",
            "In Blockade Billy, a retired coach...",
            "Simon and Schuster",
            5.0,
            "Blockade",
            2010
        ],
        [
            "Stephen King",
            "A chilling collection of twenty horror stories.",
            "Signet Book",
            4.55859375,
            "Night Shift (Signet)",
            1979
        ],
        ...
    ]
}
<p>在这个例子中，我们有 6 个与一本书相关的属性（作者、描述、出版商、评分、标题、年份）和 10 个结果，都是斯蒂芬-金的书。</p><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/esql-limitations.html#esql-supported-types">这里</a>列出了 ES|QL 支持的所有类型。</p><p><code>$result</code> 响应对象可以数组、字符串或对象的形式访问（更多信息，请参见<a href="https://www.elastic.co/guide/en/elasticsearch/client/php-api/current/connecting.html#client-usage">此处</a>）。</p><p>使用对象接口，我们可以使用属性和索引访问值。例如，<code>$result-&gt;values[0][4]</code> 返回列表中第一本书（0）的书名（4），<code>$result-&gt;values[1][3]</code> 返回第二本书（1）的等级分（3），等等。请记住，PHP 中数组的索引是从 0 开始的。</p><p>对于某些使用情况，这个接口已经足够好了，但大多数情况下，我们希望得到一个对象数组的结果。</p><p>要将结果映射到对象数组中，我们可以使用 elasticsearch-php 的新功能<a href="https://github.com/elastic/elasticsearch-php/issues/1398">mapTo()。</a></p><p>该功能可直接在<a href="https://github.com/elastic/elasticsearch-php/blob/main/src/Response/Elasticsearch.php">Elasticsearch 响应对象</a>中使用。这意味着您可以通过以下方式访问它：</p>$books = $result-&gt;mapTo(); // Array of stdClass
foreach ($books as $book) {
    printf(
        "%s, %s, %d, Rating: %.2f\n",
        $book-&gt;author,
        $book-&gt;title,
        $book-&gt;year,
        $book-&gt;rating
    );
}
<p>如果您有一个自定义的图书类，可以使用它来映射结果，如下所示：</p>class Book
{
    public string $author;
    public string $title;
    public string $description;
    public int $year;
    public float $rating;
}

$books = $result-&gt;mapTo(Book::class); // Array of Book
<p>如果您的类除了 ES|QL 结果中包含的属性外还有其他属性，这也同样有效。<code>mapTo()</code> 函数将只使用作为 ES|QL 结果列返回的属性。</p><p>您可以<a href="https://github.com/elastic/elasticsearch-php-examples/tree/main/examples/ESQL">在此</a>下载本文中报告的所有示例。</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/esql-php-map-object-class</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/esql-php-map-object-class</guid>
    <category><![CDATA[ES|QL]]></category>
    <category><![CDATA[PHP]]></category>
    <dc:creator><![CDATA[Enrico Zimuel]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt99f5ab85c0713977/6a17d7ebfbc5f8072b491918/aea56270f48cb64130d1b515b983434e0960dc2f-500x500.png" length="0" type="image/png"/>
    <pubDate>Mon, 08 Apr 2024 00:00:00 GMT</pubDate>
  </item>
  </channel>
</rss>