访问文档字段和特殊变量

编辑

访问文档字段和特殊变量

编辑

根据脚本的使用位置,它将有权访问某些特殊变量和文档字段。

更新脚本

编辑

updateupdate-by-queryreindex API 中使用的脚本将有权访问 ctx 变量,该变量公开了

ctx._source

访问文档的 _source 字段

ctx.op

应该应用于文档的操作:indexdelete

ctx._index

访问 文档元数据字段,其中一些可能是只读的。

这些脚本无权访问 doc 变量,必须使用 ctx 来访问它们操作的文档。

搜索和聚合脚本

编辑

除了每个搜索命中执行一次的 脚本字段 外,搜索和聚合中使用的脚本将对每个可能匹配查询或聚合的文档执行一次。根据您拥有的文档数量,这可能意味着数百万或数十亿次的执行:这些脚本需要快速!

可以使用 doc-values _source 字段存储字段 从脚本访问字段值,下面将分别解释每个字段值。

访问脚本中文档的分数

编辑

function_score 查询基于脚本的排序聚合 中使用的脚本可以访问 _score 变量,该变量表示文档的当前相关性得分。

以下是在 function_score 查询中使用脚本来更改每个文档的相关性 _score 的示例

resp = client.index(
    index="my-index-000001",
    id="1",
    refresh=True,
    document={
        "text": "quick brown fox",
        "popularity": 1
    },
)
print(resp)

resp1 = client.index(
    index="my-index-000001",
    id="2",
    refresh=True,
    document={
        "text": "quick fox",
        "popularity": 5
    },
)
print(resp1)

resp2 = client.search(
    index="my-index-000001",
    query={
        "function_score": {
            "query": {
                "match": {
                    "text": "quick brown fox"
                }
            },
            "script_score": {
                "script": {
                    "lang": "expression",
                    "source": "_score * doc['popularity']"
                }
            }
        }
    },
)
print(resp2)
response = client.index(
  index: 'my-index-000001',
  id: 1,
  refresh: true,
  body: {
    text: 'quick brown fox',
    popularity: 1
  }
)
puts response

response = client.index(
  index: 'my-index-000001',
  id: 2,
  refresh: true,
  body: {
    text: 'quick fox',
    popularity: 5
  }
)
puts response

response = client.search(
  index: 'my-index-000001',
  body: {
    query: {
      function_score: {
        query: {
          match: {
            text: 'quick brown fox'
          }
        },
        script_score: {
          script: {
            lang: 'expression',
            source: "_score * doc['popularity']"
          }
        }
      }
    }
  }
)
puts response
const response = await client.index({
  index: "my-index-000001",
  id: 1,
  refresh: "true",
  document: {
    text: "quick brown fox",
    popularity: 1,
  },
});
console.log(response);

const response1 = await client.index({
  index: "my-index-000001",
  id: 2,
  refresh: "true",
  document: {
    text: "quick fox",
    popularity: 5,
  },
});
console.log(response1);

const response2 = await client.search({
  index: "my-index-000001",
  query: {
    function_score: {
      query: {
        match: {
          text: "quick brown fox",
        },
      },
      script_score: {
        script: {
          lang: "expression",
          source: "_score * doc['popularity']",
        },
      },
    },
  },
});
console.log(response2);
PUT my-index-000001/_doc/1?refresh
{
  "text": "quick brown fox",
  "popularity": 1
}

PUT my-index-000001/_doc/2?refresh
{
  "text": "quick fox",
  "popularity": 5
}

GET my-index-000001/_search
{
  "query": {
    "function_score": {
      "query": {
        "match": {
          "text": "quick brown fox"
        }
      },
      "script_score": {
        "script": {
          "lang": "expression",
          "source": "_score * doc['popularity']"
        }
      }
    }
  }
}

访问脚本中文档的词项统计信息

编辑

script_score 查询中使用的脚本可以访问 _termStats 变量,该变量提供有关子查询中词项的统计信息。

在以下示例中,在 script_score 查询中使用 _termStats 来检索 text 字段中词项 quickbrownfox 的平均词项频率

resp = client.index(
    index="my-index-000001",
    id="1",
    refresh=True,
    document={
        "text": "quick brown fox"
    },
)
print(resp)

resp1 = client.index(
    index="my-index-000001",
    id="2",
    refresh=True,
    document={
        "text": "quick fox"
    },
)
print(resp1)

resp2 = client.search(
    index="my-index-000001",
    query={
        "script_score": {
            "query": {
                "match": {
                    "text": "quick brown fox"
                }
            },
            "script": {
                "source": "_termStats.termFreq().getAverage()"
            }
        }
    },
)
print(resp2)
const response = await client.index({
  index: "my-index-000001",
  id: 1,
  refresh: "true",
  document: {
    text: "quick brown fox",
  },
});
console.log(response);

const response1 = await client.index({
  index: "my-index-000001",
  id: 2,
  refresh: "true",
  document: {
    text: "quick fox",
  },
});
console.log(response1);

const response2 = await client.search({
  index: "my-index-000001",
  query: {
    script_score: {
      query: {
        match: {
          text: "quick brown fox",
        },
      },
      script: {
        source: "_termStats.termFreq().getAverage()",
      },
    },
  },
});
console.log(response2);
PUT my-index-000001/_doc/1?refresh
{
  "text": "quick brown fox"
}

PUT my-index-000001/_doc/2?refresh
{
  "text": "quick fox"
}

GET my-index-000001/_search
{
  "query": {
    "script_score": {
      "query": { 
        "match": {
          "text": "quick brown fox"
        }
      },
      "script": {
        "source": "_termStats.termFreq().getAverage()" 
      }
    }
  }
}

子查询用于推断词项统计信息中考虑的字段和词项。

该脚本使用 _termStats 计算查询中词项的平均文档频率。

_termStats 提供以下用于处理词项统计信息的功能

  • uniqueTermsCount:返回查询中唯一词项的总数。此值在所有文档中都相同。
  • matchedTermsCount:返回当前文档中匹配的查询词项计数。
  • docFreq:提供查询中词项的文档频率统计信息,指示每个词项包含多少个文档。此值在所有文档中都一致。
  • totalTermFreq:提供所有文档中词项的总频率,表示每个词项在整个语料库中出现的频率。此值在所有文档中都一致。
  • termFreq:返回当前文档中查询词项的频率,显示每个词项在该文档中出现的频率。

返回聚合统计信息的功能

docFreqtermFreqtotalTermFreq 函数返回表示子查询所有词项的统计信息的对象。

统计信息提供对以下方法的支持

getAverage():返回指标的平均值。getMin():返回指标的最小值。getMax():返回指标的最大值。getSum():返回指标值的总和。getCount():返回指标计算中包含的词项计数。

需要 Painless 语言

仅当使用 Painless 脚本语言时,_termStats 变量才可用。

Doc values

编辑

到目前为止,从脚本访问字段值最快、最有效的方法是使用 doc['field_name'] 语法,该语法从 doc values 检索字段值。Doc values 是一个按列存储的字段值存储,默认情况下在所有字段上启用,但 分析过的 text 字段 除外。

resp = client.index(
    index="my-index-000001",
    id="1",
    refresh=True,
    document={
        "cost_price": 100
    },
)
print(resp)

resp1 = client.search(
    index="my-index-000001",
    script_fields={
        "sales_price": {
            "script": {
                "lang": "expression",
                "source": "doc['cost_price'] * markup",
                "params": {
                    "markup": 0.2
                }
            }
        }
    },
)
print(resp1)
response = client.index(
  index: 'my-index-000001',
  id: 1,
  refresh: true,
  body: {
    cost_price: 100
  }
)
puts response

response = client.search(
  index: 'my-index-000001',
  body: {
    script_fields: {
      sales_price: {
        script: {
          lang: 'expression',
          source: "doc['cost_price'] * markup",
          params: {
            markup: 0.2
          }
        }
      }
    }
  }
)
puts response
const response = await client.index({
  index: "my-index-000001",
  id: 1,
  refresh: "true",
  document: {
    cost_price: 100,
  },
});
console.log(response);

const response1 = await client.search({
  index: "my-index-000001",
  script_fields: {
    sales_price: {
      script: {
        lang: "expression",
        source: "doc['cost_price'] * markup",
        params: {
          markup: 0.2,
        },
      },
    },
  },
});
console.log(response1);
PUT my-index-000001/_doc/1?refresh
{
  "cost_price": 100
}

GET my-index-000001/_search
{
  "script_fields": {
    "sales_price": {
      "script": {
        "lang":   "expression",
        "source": "doc['cost_price'] * markup",
        "params": {
          "markup": 0.2
        }
      }
    }
  }
}

Doc values 只能返回“简单”的字段值,如数字、日期、地理点、词项等,或者如果该字段是多值字段,则返回这些值的数组。它不能返回 JSON 对象。

缺失字段

如果映射中缺少 field,则 doc['field'] 将引发错误。在 painless 中,可以首先使用 doc.containsKey('field') 来检查以防止访问 doc 映射。不幸的是,无法在 expression 脚本中检查映射中是否存在该字段。

Doc values 和 text 字段

如果启用了 fielddata,则 doc['field'] 语法也可以用于 分析过的 text 字段,但请注意:在 text 字段上启用 fielddata 需要将所有词项加载到 JVM 堆中,这在内存和 CPU 方面都可能非常昂贵。从脚本访问 text 字段几乎没有意义。

文档 _source

编辑

可以使用 _source.field_name 语法访问文档的 _source_source 作为 map-of-maps 加载,因此可以访问对象字段中的属性,例如,_source.name.first

首选 doc-values 而不是 _source

访问 _source 字段比使用 doc-values 慢得多。_source 字段针对每个结果返回多个字段进行了优化,而 doc values 针对访问许多文档中特定字段的值进行了优化。

当从搜索结果为前十个命中生成 脚本字段 时,使用 _source 是有意义的,但对于其他搜索和聚合用例,始终首选使用 doc values。

例如

resp = client.indices.create(
    index="my-index-000001",
    mappings={
        "properties": {
            "first_name": {
                "type": "text"
            },
            "last_name": {
                "type": "text"
            }
        }
    },
)
print(resp)

resp1 = client.index(
    index="my-index-000001",
    id="1",
    refresh=True,
    document={
        "first_name": "Barry",
        "last_name": "White"
    },
)
print(resp1)

resp2 = client.search(
    index="my-index-000001",
    script_fields={
        "full_name": {
            "script": {
                "lang": "painless",
                "source": "params._source.first_name + ' ' + params._source.last_name"
            }
        }
    },
)
print(resp2)
response = client.indices.create(
  index: 'my-index-000001',
  body: {
    mappings: {
      properties: {
        first_name: {
          type: 'text'
        },
        last_name: {
          type: 'text'
        }
      }
    }
  }
)
puts response

response = client.index(
  index: 'my-index-000001',
  id: 1,
  refresh: true,
  body: {
    first_name: 'Barry',
    last_name: 'White'
  }
)
puts response

response = client.search(
  index: 'my-index-000001',
  body: {
    script_fields: {
      full_name: {
        script: {
          lang: 'painless',
          source: "params._source.first_name + ' ' + params._source.last_name"
        }
      }
    }
  }
)
puts response
const response = await client.indices.create({
  index: "my-index-000001",
  mappings: {
    properties: {
      first_name: {
        type: "text",
      },
      last_name: {
        type: "text",
      },
    },
  },
});
console.log(response);

const response1 = await client.index({
  index: "my-index-000001",
  id: 1,
  refresh: "true",
  document: {
    first_name: "Barry",
    last_name: "White",
  },
});
console.log(response1);

const response2 = await client.search({
  index: "my-index-000001",
  script_fields: {
    full_name: {
      script: {
        lang: "painless",
        source: "params._source.first_name + ' ' + params._source.last_name",
      },
    },
  },
});
console.log(response2);
PUT my-index-000001
{
  "mappings": {
    "properties": {
      "first_name": {
        "type": "text"
      },
      "last_name": {
        "type": "text"
      }
    }
  }
}

PUT my-index-000001/_doc/1?refresh
{
  "first_name": "Barry",
  "last_name": "White"
}

GET my-index-000001/_search
{
  "script_fields": {
    "full_name": {
      "script": {
        "lang": "painless",
        "source": "params._source.first_name + ' ' + params._source.last_name"
      }
    }
  }
}

存储字段

编辑

存储字段 — 在映射中显式标记为 "store": true 的字段 — 可以使用 _fields['field_name'].value_fields['field_name'] 语法进行访问

resp = client.indices.create(
    index="my-index-000001",
    mappings={
        "properties": {
            "full_name": {
                "type": "text",
                "store": True
            },
            "title": {
                "type": "text",
                "store": True
            }
        }
    },
)
print(resp)

resp1 = client.index(
    index="my-index-000001",
    id="1",
    refresh=True,
    document={
        "full_name": "Alice Ball",
        "title": "Professor"
    },
)
print(resp1)

resp2 = client.search(
    index="my-index-000001",
    script_fields={
        "name_with_title": {
            "script": {
                "lang": "painless",
                "source": "params._fields['title'].value + ' ' + params._fields['full_name'].value"
            }
        }
    },
)
print(resp2)
response = client.indices.create(
  index: 'my-index-000001',
  body: {
    mappings: {
      properties: {
        full_name: {
          type: 'text',
          store: true
        },
        title: {
          type: 'text',
          store: true
        }
      }
    }
  }
)
puts response

response = client.index(
  index: 'my-index-000001',
  id: 1,
  refresh: true,
  body: {
    full_name: 'Alice Ball',
    title: 'Professor'
  }
)
puts response

response = client.search(
  index: 'my-index-000001',
  body: {
    script_fields: {
      name_with_title: {
        script: {
          lang: 'painless',
          source: "params._fields['title'].value + ' ' + params._fields['full_name'].value"
        }
      }
    }
  }
)
puts response
const response = await client.indices.create({
  index: "my-index-000001",
  mappings: {
    properties: {
      full_name: {
        type: "text",
        store: true,
      },
      title: {
        type: "text",
        store: true,
      },
    },
  },
});
console.log(response);

const response1 = await client.index({
  index: "my-index-000001",
  id: 1,
  refresh: "true",
  document: {
    full_name: "Alice Ball",
    title: "Professor",
  },
});
console.log(response1);

const response2 = await client.search({
  index: "my-index-000001",
  script_fields: {
    name_with_title: {
      script: {
        lang: "painless",
        source:
          "params._fields['title'].value + ' ' + params._fields['full_name'].value",
      },
    },
  },
});
console.log(response2);
PUT my-index-000001
{
  "mappings": {
    "properties": {
      "full_name": {
        "type": "text",
        "store": true
      },
      "title": {
        "type": "text",
        "store": true
      }
    }
  }
}

PUT my-index-000001/_doc/1?refresh
{
  "full_name": "Alice Ball",
  "title": "Professor"
}

GET my-index-000001/_search
{
  "script_fields": {
    "name_with_title": {
      "script": {
        "lang": "painless",
        "source": "params._fields['title'].value + ' ' + params._fields['full_name'].value"
      }
    }
  }
}

存储 vs _source

_source 字段只是一个特殊的存储字段,因此性能与其他存储字段类似。_source 提供对已索引的原始文档主体的访问(包括区分 null 值与空字段、单值数组与普通标量等的能力)。

唯一真正有意义使用存储字段而不是 _source 字段的情况是,当 _source 非常大时,访问几个小的存储字段而不是整个 _source 的成本更低。