# 查询语法

**URL:** https://heroiclabs.com/docs/zh/nakama/concepts/multiplayer/query-syntax/
**Summary:** 可查询匹配程序属性和比赛标签，仅返回所需比赛。了解用于筛选这些结果的查询语法。

---


# 查询语法

查询是一个强大的工具，可让玩家在使用[匹配程序](../matchmaker/)时找到最相关的对手，在[列出现有比赛](../match-listing/)时找到最合适的 多人游戏比赛。查询表达式定义用户条件，以便查找对手或与查询匹配的现有比赛（将其视为大 `AND` 语句），然后分别检查匹配程序 `properties` 或比赛 `label`，提供结果。

{{< note "important" >}}
查询比赛`label`仅在标签为 JSON 值时才可行。
{{< / note >}}

查询由一个或多个查询词（使用语法 `field:value`）组成。查询可包含任意多个词语，每个词语用空格分隔，例如 `field:value field1:value1`。

同一语法可用于任意值类型，例如：

* 字符串： `region:europe`
* 数值： `level:10`
* 数值范围： `rank:<=5`
* 日期范围： `created:>"2021-12-25"`

请参阅下文[运算符](#operators)部分，进一步了解如何编写查询词；参阅 [Boosting](#boosting)，详细了解如何依据给予各个查询词的权重对结果进行排序。

## 匹配

标准语法与整个值匹配，这意味着查询值和返回结果必须相等，而不仅仅是相似。例如，`mode:free` 查询不会返回 `freeforall` 模式的匹配。

查询还检查数组中的单个元素，寻找匹配。例如，索引值：`{"field": [5, 10, 15]}`将被作为包含词语 `field:5`、`field:10` 或 `field:15` 的任何查询的匹配项而返回。

## 运算符

创建查询词时可以使用三个运算符：SHOULD、MUST 和 MUST NOT。

### SHOULD

Should 是用于查询词的默认运算符。在实用中，其意为_应当有_给定值，但这不是严格要求。

例如，如果您使用 `region:europe` 查询来搜索对手，结果会包括合适的结果（即欧洲地区的对手，如有），但如果找不到足够多的合适结果，也会包括任何其他地区的对手。

### MUST

MUST 运算符以查询词开头的 `+` 指示，强制执行给定值的字符串要求。使用上例，但改用 MUST 运算符 (`+region:europe`)，意味着将在结果中仅返回欧洲地区的对手，如果不够多，则不返回结果。

### MUST NOT

MUST NOT 运算符由查询词开头的 `-` 指示。它强制执行_排除_查询词的字符串要求，即返回的结果绝对不含有此值。

例如，如果用户不想看到任何级别高于 10 的比赛，则查询词的形式为 `-level:>10`。

## 范围

查询可将数值和日期范围作为查询词。

可在带有 `>`、`>=`、`<` 和 `<=` 运算符的查询词中使用数值范围。可将这些运算符用于日期范围，区别在于日期带有引号，例如 `created:>"2022-01-01"` 将返回 2022 年 1 月 1 日后创建的结果。

{{< note "important" >}}
对于日期范围查询，我们建议在可行时使用 UTC 秒/毫秒。
{{< / note >}}

## Boosting

使用上面介绍的运算符，您可以创建有多个词语的查询，以描述是否返回给定结果。此外，您还可以通过“提升”（Boosting）查询中词语的相对重要性，为返回的结果定义排序。

这通过在查询词末尾使用 `^` 和一个任意提升编号来完成。

例如，如果使用查询 `mode:freeforall^2 mode:capturetheflag` 来搜索比赛，结果将包含全部免费和夺标比赛，但是全部免费结果将在高于夺标结果的位置列出（即“更好”的匹配）。

请注意，由于我们没有使用 `+` 运算符，此查询也可能返回既不是全部免费比赛也不是夺标比赛的结果，但这些结果将是所有结果中最低的。

## 转义

必须对以下字符集进行转义，才能获得准确的查询结果：`+-=&|><!(){}[]^\"~*?:\\/ `。

请注意包含了空格字符。例如，如果想要查询“夺标”(Capture The Flag) 比赛，请使用 `mode:Capture\ The\ Flag` 而非 `mode:Capture The Flag`。后一个查询可能仍然有效，但会产生错误的结果，因为它搜索带有“Capture”的 `mode` 的匹配项，其中包含任意位置“The”和任意位置“Flag”。

## 示例

### 查找好友的比赛

如果比赛 `label` 包含数组 `players`，其中有目前在比赛中的所有玩家的用户 ID，我们可以使用以下查询查找某个好友所在的任何比赛：

{{< code type="server" >}}
```lua
local nk = require("nakama")

local query = "label.groups:<friend_user_id>"
local matches = nk.match_list(query)

for _, match in ipairs(matches) do
  nk.logger_info(string.format("Match id %s", match.match_id))
end
```
{{< / code >}}

{{< code type="server" >}}
```go
query := "+label.players:<friend_user_id>"
matches, err := nk.MatchList(ctx, query)

if err != nil {
    logger.WithField("err", err).Error("Match listings error.")
    return
}

for _, match := range matches {
    logger.Info("Match id %s", match.MatchId)
}
```
{{< / code >}}

{{< code type="server" >}}
```typescript
function findFriendMatch(context: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama) {
  const query = "+label.players:<friend_user_id>";
  var matches = nk.matchList(query);

  matches.forEach(function (match) {
    logger.info("Match id '%s'", match.matchId);
  });
}
```
{{< / code >}}

### 查找开放比赛

在本示例中，比赛 `label` 包含一个 `open` 值，指示比赛是否接受新玩家。满足合适的条件后（例如足够多的玩家加入后或指示他们可以开始后），比赛处理程序中的此值将得到更新。

{{< code type="server" >}}
```lua
local nk = require("nakama")

local query = "+label.open:true label.game_mode:deathmatch"
local matches = nk.match_list(query)

for _, match in ipairs(matches) do
  nk.logger_info(string.format("Match id %s", match.match_id))
end
```
{{< / code >}}

{{< code type="server" >}}
```go
query := "+label.open:true label.game_mode:deathmatch"
matches, err := nk.MatchList(ctx, query)

if err != nil {
    logger.WithField("err", err).Error("Match listings error.")
    return
}

for _, match := range matches {
    logger.Info("Match id %s", match.MatchId)
}
```
{{< / code >}}

{{< code type="server" >}}
```typescript
function findOpenMatches(context: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama) {
  const query = "+label.open:true label.game_mode:deathmatch";
  var matches = nk.matchList(query);

  matches.forEach(function (match) {
    logger.info("Match id '%s'", match.matchId);
  });
}
```
{{< / code >}}

### 围绕玩家级别匹配

在本例中，我们为玩家创建匹配程序门票，玩家 `Skill` 级别为 15，寻找级别介于 13 和 17 之间的 2-4 个对手。

{{< code type="client" >}}
```javascript
const query = "+properties.skill:>=13 +properties.skill:<=17";
const minCount = 2;
const maxCount = 4;

const numericProperties = {
  skill: 15
};

var ticket = await socket.addMatchmaker(query, minCount, maxCount, numericProperties);
```
{{< / code >}}

{{< code type="client" >}}
```csharp
var query = "+properties.skill:>=13 +properties.skill:<=17";

var numericProperties = new Dictionary<string, int>() {
    {"skill", 15}
};

var matchmakerTicket = await socket.AddMatchmakerAsync(query, 2, 4, numericProperties);
```
{{< / code >}}

{{< code type="client" >}}
```cpp
auto successCallback = [](const NMatchmakerTicket& ticket)
{
    std::cout << "Matchmaker ticket: " << ticket.ticket << std::endl;
};

int32_t minCount = 2;
int32_t maxCount = 4;
string query = "+properties.skill:>=13 +properties.skill:<=17";
NStringDoubleMap numericProperties;

numericProperties.emplace("skill", 15.0);

rtClient->addMatchmaker(minCount, maxCount, query, numericProperties, successCallback);
```
{{< / code >}}

{{< code type="client" >}}
```java
String query = "+properties.skill:>=13 +properties.skill:<=17";
int minCount = 2;
int maxCount = 4;

Map<String, Double> numericProperties = new HashMap<String, Double>() {{
    put("skill", 15.0);
}};

MatchmakerTicket matchmakerTicket = socket.addMatchmaker(query, minCount, maxCount, numericProperties).get();
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var query = "+properties.skill:>=13 +properties.skill:<=17"
var numeric_properties = { "skill": 15 }

var matchmaker_ticket : NakamaRTAPI.MatchmakerTicket = yield(
    socket.add_matchmaker_async(query, 2, 4, numeric_properties),
    "completed"
)

if matchmaker_ticket.is_exception():
    print("An error occurred: %s" % matchmaker_ticket)
    return

print("Got ticket: %s" % [matchmaker_ticket])
```
{{< / code >}}

{{< code type="client" lang="lua" framework="defold" >}}
```lua
local min_players = 2
local max_players = 4
local query = "+properties.skill:>=13 +properties.skill:<=17"
local ticket = socket.matchmaker_add(min_players, max_players, query)
```
{{< / code >}}

{{< missing type="client" lang="bash" />}}
{{< missing type="client" lang="shell" />}}

### 提升地区偏好

在本例中，我们的玩家位于欧洲地区，首选与同一地区的其他玩家匹配，其次是亚洲和非洲地区的玩家：

{{< code type="client" >}}
```javascript
const query = "properties.region:europe^3 properties.region:asia^2 properties.region:africa";
const minCount = 2;
const maxCount = 4;

const stringProperties = {
  region: "europe"
};

var ticket = await socket.addMatchmaker(query, minCount, maxCount, stringProperties);
```
{{< / code >}}

{{< code type="client" >}}
```csharp
var query = "properties.region:europe^3 properties.region:asia^2 properties.region:africa";

var stringProperties = new Dictionary<string, string>() {
    {"region", "europe"}
};

var matchmakerTicket = await socket.AddMatchmakerAsync(query, 2, 4, stringProperties);
```
{{< / code >}}

{{< code type="client" >}}
```cpp
auto successCallback = [](const NMatchmakerTicket& ticket)
{
    std::cout << "Matchmaker ticket: " << ticket.ticket << std::endl;
};

int32_t minCount = 2;
int32_t maxCount = 4;
string query = "properties.region:europe^3 properties.region:asia^2 properties.region:africa";
NStringMap stringProperties;

stringProperties.emplace("region", "europe");

rtClient->addMatchmaker(minCount, maxCount, query, stringProperties, successCallback);
```
{{< / code >}}

{{< code type="client" >}}
```java
String query = "properties.region:europe^3 properties.region:asia^2 properties.region:africa";
int minCount = 2;
int maxCount = 4;

Map<String, String> stringProperties = new HashMap<String, String>() {{
    put("region", "europe");
}};

MatchmakerTicket matchmakerTicket = socket.addMatchmaker(query, minCount, maxCount, stringProperties).get();
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var query = "properties.region:europe^3 properties.region:asia^2 properties.region:africa"
var string_properties = { "region": "europe" }

var matchmaker_ticket : NakamaRTAPI.MatchmakerTicket = yield(
    socket.add_matchmaker_async(query, 2, 4, string_properties),
    "completed"
)

if matchmaker_ticket.is_exception():
    print("An error occurred: %s" % matchmaker_ticket)
    return

print("Got ticket: %s" % [matchmaker_ticket])
```
{{< / code >}}

{{< code type="client" lang="lua" framework="defold" >}}
```lua
local min_players = 2
local max_players = 4
local query = "properties.region:europe^3 properties.region:asia^2 properties.region:africa"
local string_properties = { region = "europe" }
local ticket = socket.matchmaker_add(min_players, max_players, query, string_properties)
```
{{< / code >}}

{{< missing type="client" lang="bash" />}}
{{< missing type="client" lang="shell" />}}
