JSON 查询 DSL

JSON 请求中提供的查询和过滤器可以使用丰富、强大的查询 DSL 来指定。

查询 DSL 结构

JSON 请求 API 接受三种不同格式的查询值

  • 使用默认 deftype(大多数情况下为 lucene)的有效 查询字符串,例如 title:solr

  • 指定其 deftype 的有效 本地参数查询字符串,例如 {!dismax qf=title}solr

  • 具有查询解析器名称和任何相关参数的有效 JSON 对象,例如 { "lucene": {"df":"title", "query":"solr"}}

    • 顶级“查询”JSON 块通常只有一个属性,表示要使用的查询解析器的名称。查询解析器属性的值是一个子块,其中包含任何相关参数作为 JSON 属性。整个结构类似于“本地参数”查询字符串。查询本身(通常使用名称 v 在本地参数中表示)使用键 query 指定。

所有这些语法都可用于为 JSON 请求 API 的 queryfilter 属性指定查询。

查询 DSL 示例

以下示例展示了如何使用上面讨论的每种语法来表示查询。每个代码段都表示相同的基本搜索:名为 name 的字段中的术语 iPod

使用标准查询 API,使用简单的查询字符串

curl

curl -X GET "http://localhost:8983/solr/techproducts/query?q=name:iPod"

SolrJ

final SolrQuery query = new SolrQuery("name:iPod");
final QueryResponse response = solrClient.query(COLLECTION_NAME, query);

使用 JSON 请求 API,使用简单的查询字符串

curl

curl -X POST http://localhost:8983/solr/techproducts/query -d '
{
  "query" : "name:iPod"
}'

SolrJ

final JsonQueryRequest query = new JsonQueryRequest().setQuery("name:iPod");
final QueryResponse response = query.process(solrClient, COLLECTION_NAME);

使用 JSON 请求 API,使用 local-params 字符串

curl

curl -X POST http://localhost:8983/solr/techproducts/query -d '
{
  "query": "{!lucene df=name v=iPod}"
}'

SolrJ

final JsonQueryRequest query = new JsonQueryRequest().setQuery("{!lucene df=name}iPod");
final QueryResponse response = query.process(solrClient, COLLECTION_NAME);

使用 JSON 请求 API,使用完全展开的 JSON 对象

curl

curl -X POST http://localhost:8983/solr/techproducts/query -d '
{
  "query": {
    "lucene": {
      "df": "name",
      "query": "iPod"
    }
  }
}'

SolrJ

final Map<String, Object> queryTopLevel = new HashMap<>();
final Map<String, Object> luceneQueryProperties = new HashMap<>();
queryTopLevel.put("lucene", luceneQueryProperties);
luceneQueryProperties.put("df", "name");
luceneQueryProperties.put("query", "iPod");
final JsonQueryRequest query = new JsonQueryRequest().setQuery(queryTopLevel);
final QueryResponse response = query.process(solrClient, COLLECTION_NAME);

嵌套查询

Solr 的许多查询解析器允许将查询彼此嵌套。当使用这些查询时,使用标准查询 API 的请求很快变得难以编写、阅读和理解。在 JSON 请求 API 中,通常更容易处理此类查询。

嵌套提升查询示例

作为一个示例,考虑下面的三个请求,它们将一个简单查询(字段 name 中的术语 iPod)包装在一个提升查询中

使用标准查询 API

curl

curl -X GET "http://localhost:8983/solr/techproducts/query?q={!boost b=log(popularity) v=\'{!lucene df=name}iPod\'}"

SolrJ

final SolrQuery query =
    new SolrQuery("{!boost b=log(popularity) v=\'{!lucene df=name}iPod\'}");
final QueryResponse response = solrClient.query(COLLECTION_NAME, query);

使用 JSON 请求 API,混合使用完全展开的查询和 local-params 查询。如您所见,特殊键 v 被键 query 替换

curl

curl -X POST http://localhost:8983/solr/techproducts/query -d '
{
    "query" : {
        "boost": {
            "query": {!lucene df=name}iPod,
            "b": "log(popularity)"
        }
    }
}'

SolrJ

final Map<String, Object> queryTopLevel = new HashMap<>();
final Map<String, Object> boostQuery = new HashMap<>();
queryTopLevel.put("boost", boostQuery);
boostQuery.put("b", "log(popularity)");
boostQuery.put("query", "{!lucene df=name}iPod");
final JsonQueryRequest query = new JsonQueryRequest().setQuery(queryTopLevel);
final QueryResponse response = query.process(solrClient, COLLECTION_NAME);

使用 JSON 请求 API,将所有查询完全展开为 JSON

+

curl

curl -X POST http://localhost:8983/solr/techproducts/query -d '
{
    "query": {
        "boost": {
            "query": {
                "lucene": {
                    "df": "name",
                    "query": "iPod"
                }
            },
            "b": "log(popularity)"
        }
    }
}'

SolrJ

final Map<String, Object> queryTopLevel = new HashMap<>();
final Map<String, Object> boostProperties = new HashMap<>();
final Map<String, Object> luceneTopLevel = new HashMap<>();
final Map<String, Object> luceneProperties = new HashMap<>();
queryTopLevel.put("boost", boostProperties);
boostProperties.put("b", "log(popularity)");
boostProperties.put("query", luceneTopLevel);
luceneTopLevel.put("lucene", luceneProperties);
luceneProperties.put("df", "name");
luceneProperties.put("query", "iPod");
final JsonQueryRequest query = new JsonQueryRequest().setQuery(queryTopLevel);
final QueryResponse response = query.process(solrClient, COLLECTION_NAME);

嵌套布尔查询示例

当使用 BoolQParser 将多个查询子句使用伪布尔逻辑组合在一起时,通常会看到查询嵌套。

以下示例展示了如何使用 BoolQParser 创建强大的嵌套查询。在此示例中,用户在 name 字段中搜索包含 iPod 且不在 popularity 排名的下半部分中的结果。

curl

curl -X POST http://localhost:8983/solr/techproducts/query -d '
{
    "query": {
        "bool": {
            "must": [
                {"lucene": {"df": "name", query: "iPod"}}
            ],
            "must_not": [
                {"frange": {"l": "0", "u": "5", "query": "popularity"}}
            ]
        }
    }
}'

SolrJ

final Map<String, Object> queryTopLevel = new HashMap<>();
final Map<String, Object> boolProperties = new HashMap<>();
final List<Object> mustClauses = new ArrayList<>();
final List<Object> mustNotClauses = new ArrayList<>();
final Map<String, Object> frangeTopLevel = new HashMap<>();
final Map<String, Object> frangeProperties = new HashMap<>();

queryTopLevel.put("bool", boolProperties);
boolProperties.put("must", mustClauses);
mustClauses.add("name:iPod");

boolProperties.put("must_not", mustNotClauses);
frangeTopLevel.put("frange", frangeProperties);
frangeProperties.put("l", 0);
frangeProperties.put("u", 5);
frangeProperties.put("query", "popularity");
mustNotClauses.add(frangeTopLevel);

final JsonQueryRequest query = new JsonQueryRequest().setQuery(queryTopLevel);
final QueryResponse response = query.process(solrClient, COLLECTION_NAME);

如果 lucene 是默认查询解析器,则可以将上面的示例简化为

curl

curl -X POST http://localhost:8983/solr/techproducts/query -d '
{
    "query": {
        "bool": {
            "must": [
                "name:iPod"
            ],
            "must_not": "{!frange l=0 u=5}popularity"
        }
    }
}'

SolrJ

final Map<String, Object> queryTopLevel = new HashMap<>();
final Map<String, Object> boolProperties = new HashMap<>();
queryTopLevel.put("bool", boolProperties);
boolProperties.put("must", "name:iPod");
boolProperties.put("must_not", "{!frange l=0 u=5}popularity");

final JsonQueryRequest query = new JsonQueryRequest().setQuery(queryTopLevel);
final QueryResponse response = query.process(solrClient, COLLECTION_NAME);

引用 附加查询标记排除 的示例

curl -X POST http://localhost:8983/solr/techproducts/query -d '
{
    "queries": {
       "query_filters":[                                            // 1.
           {"#size_tag":{"field":{"f":"size","query":"XL"}}},
           {"#color_tag":{"field":{"f":"color","query":"Red"}}}     // 2.
       ]
    },
    "query": {
        "bool": {
            "must": {"param":"query_filters"},                      // refer both of 1.
            "excludeTags": "color_tag"                              // excluding 2.
        }
    }
}'

因此,上面的查询将仅返回与 size:XL 匹配的文档。

筛选查询

上面讨论的语法除了可以用于指定主查询之外,还可以用于指定查询筛选器(在 filter 键下)。

例如,可以使用筛选子句将上述查询重写为

curl

curl -X POST http://localhost:8983/solr/techproducts/query -d '
{
    "query": {
        "bool": {
            "must_not": "{!frange l=0 u=5}popularity"
        }
    },
    "filter: [
        "name:iPod"
    ]
}'

SolrJ

final Map<String, Object> queryTopLevel = new HashMap<>();
final Map<String, Object> boolProperties = new HashMap<>();
queryTopLevel.put("bool", boolProperties);
boolProperties.put("must_not", "{!frange l=0 u=5}popularity");

final JsonQueryRequest query =
    new JsonQueryRequest().setQuery(queryTopLevel).withFilter("name:iPod");
final QueryResponse response = query.process(solrClient, COLLECTION_NAME);

附加查询

可以在 queries 键下指定多个附加查询,语法与上面描述的语法相同。每个条目在数组中可以有多个值。

要引用这些查询,请使用 {"param":"query_name"} 或旧式引用 "{!v=$query_name}"。注意这些引用的元数。

根据上下文,引用可能会解析为数组中的第一个元素,而忽略后面的元素,例如,如果将下面的引用从 {"param":"electronic"} 更改为 {"param":"manufacturers"},则相当于查询 manu:apple,忽略后面的查询。在明确引用之前,这些查询不会影响查询结果。

curl -X POST http://localhost:8983/solr/techproducts/query -d '
{
    "queries": {
        "electronic": {"field": {"f":"cat", "query":"electronics"}},
        "manufacturers": [
           "manu:apple",
           {"field": {"f":"manu", "query":"belkin"}}
        ]
    },
    "query":{"param":"electronic"}
}'

总体而言,此示例并没有多大意义,但只是演示了语法。此功能在 JSON Facet API 的 过滤域 中很有用 域更改。请注意,这些声明会添加下面的请求参数,因此与其他参数使用相同的名称可能会导致意外的行为。

JSON Query DSL 中的标记

查询和过滤器子句也可以单独“标记”。标记用作查询子句的句柄,允许它们从请求中的其他位置引用。这最常由 传统JSON 分面提供的过滤器排除功能使用。

通过将查询和过滤器包装在周围的 JSON 对象中来标记它们。标记的名称指定为 JSON 键,查询字符串(或对象)成为与该键关联的值。标记名称属性以哈希为前缀,并且可以包含多个标记,用逗号分隔。例如:{"#title,tag2,tag3":"title:solr"}。请注意,与使用宽松 JSON 解析规则的其余 JSON 请求 API 不同,由于前导 # 字符,标记必须用双引号引起来。下面的示例创建了两个标记子句:titleTaginStockTag

curl

curl -X POST http://localhost:8983/solr/techproducts/select -d '
{
  "query": "*:*",
  "filter": [
    {
      "#titleTag": "name:Solr"
    },
    {
      "#inStockTag": "inStock:true"
    }
  ]
}'

SolrJ

final Map<String, Object> titleTaggedQuery = new HashMap<>();
titleTaggedQuery.put("#titleTag", "name:Solr");
final Map<String, Object> inStockTaggedQuery = new HashMap<>();
inStockTaggedQuery.put("#inStockTag", "inStock:true");
final JsonQueryRequest query =
    new JsonQueryRequest()
        .setQuery("*:*")
        .withFilter(titleTaggedQuery)
        .withFilter(inStockTaggedQuery);
final QueryResponse response = query.process(solrClient, COLLECTION_NAME);

请注意,在上面示例中创建的标记不会影响搜索的执行方式。除非它们被请求的其他部分引用,否则标记不会影响查询。

分面嵌套文档

此段落将许多功能结合在一起。基本上,它是嵌套文档的过滤器排除示例,当对子文档字段进行分面计数时,分面计数会汇总到父文档计数中。您可能需要执行此操作的典型场景是,如果您要显示具有各种颜色/尺寸选项(子级)的 SKU 的产品(父级)的过滤器。让我们逐项进行

  • 过滤器排除通常在可以将多个过滤器值应用于每个字段时需要。这也称为钻取侧分面。另请参见标记和过滤器排除

  • 嵌套文档或子文档在索引嵌套文档中进行描述。在下面的示例中,它们被称为 SKU,因为这是此功能的常见用例。

  • 对子文档进行分面计数意味着即使搜索结果通常是父文档,子文档及其字段也会用于分面。

  • 分面计数汇总意味着子文档会增加分面点击量,因为它们链接到的父文档会被计数。例如,如果父文档有两个 (2) 个红色子文档,则由于汇总,它会被计数为一个 (1)。

{
    "queries": {
        "parents":"content_type:parentDocument",             (1)
        "sku_fqs": [{"#sku_attr1_tag":"sku_attr1:foo"},      (2)
                    {"#sku_attr2_tag":"sku_attr2:bar"}
                   ]
    },
    "filter": {
            "#sku_filters":{                                 (3)
                "parent": {
                    "which": {"param":"parents"},
                    "filters": {"param":"sku_fqs"}
                  }}
    },
    "facet": {
        "sku_attr1": {                                       (4)
            "type":"terms",
            "field":"sku_attr1",
            "limit":-1,
            "domain": {       (5)
                 "excludeTags":"sku_filters",
                 "blockChildren":"{!v=$parents}",
                 "filter":
                     "{!bool filter=$sku_fqs excludeTags=sku_attr1_tag}"
            },
            "facet": {
                "by_parent":"uniqueBlock({!v=$parents})"    (6)
            }
        }
    }
}
1 通过附加查询为父过滤器定义,以便以后使用。
2 根据JSON 查询 DSL 中的标记中所述,在不同的 # 标签下声明两个 SKU 过滤器。
3 定义标签块联接查询,以便以后排除。这会将 SKU 联接到顶级文档,并引用前面定义的 parents 查询和 SKU 过滤器查询。
4 计算 SKU 文档上的钻取侧 terms 分面。SKU 字段定义为 sku_attr1,并且我们已设置 limit=-1,以便我们获取所有分面值。
5 使用 excludeTagsdomain 中移除顶级父过滤器。blockChildren 值引用 all-parents 查询。然后我们定义一个过滤器来限制到 SKU 文档,但排除一个标签,只保留 sku_attr2:bar
6 计算父文档中的子分面。另请参见uniqueBlock() 和块联接计数