# 쿼리 구문

**URL:** https://heroiclabs.com/docs/kr/nakama/concepts/multiplayer/query-syntax/
**Summary:** Matchmaker 속성 및 대결 레이블을 쿼리하여 원하는 대결만 반환할 수 있습니다. 이러한 결과를 필터링하는 데 사용되는 쿼리 구문에 대해 알아봅니다.

---


# 쿼리 구문

쿼리는 플레이어가 [매치메이커](../matchmaker/)를 사용할 때 가장 관련성이 높은 상대방을 찾고, [사용 가능한 대결](../match-listing/)을 나열할 때 가장 적합한 멀티플레이어 대결을 찾을 수 있는 강력한 도구입니다. 쿼리 표현식은 상대방 또는 사용 가능한 대결을 찾기 위한 사용자의 기준을 정의합니다. 이 쿼리는 결과를 제공하기 위해 쿼리 사용 후 Matchmaker `properties` 또는 대결 `label`을(를) 검사하는 큰 `AND` 명령으로 생각하십시오.

{{< note "important" >}}
대결 `label` 쿼리는 레이블이 JSON 값인 경우에만 가능합니다.
{{< / note >}}

쿼리는 구문 `field:value`을(를) 사용하는 하나 이상의 쿼리 용어로 구성됩니다. 쿼리에는 다수의 용어가 포함될 수 있으며 각 용어는 공백으로 구분됩니다(예: `field:value field1:value1`).

모든 값 유형에 동일한 구문을 사용할 수 있습니다. 예:

* 스트링: `region:europe`
* 숫자: `level:10`
* 숫자 범위: `rank:<=5`
* 날짜 범위: `created:>"2021-12-25"`

쿼리 용어 작성에 대한 자세한 내용은 아래 [연산자](#operators) 섹션을 참조하고 개별 쿼리 용어에 부여된 가중치를 기반으로 결과를 정렬하는 방법에 대한 자세한 내용은 [부스팅](#boosting)을 참조하세요.

## 일치

표준 구문은 전체 값과 일치합니다. 즉, 유사성뿐만 아니라 쿼리 값과 반환된 결과 간에 동일성도 있어야 합니다. 예를 들어 `mode:free`의 쿼리는 `freeforall`의 모드와 일치하는 대결을 반환하지 않습니다.

쿼리는 또한 대결 배열 내부의 개별 요소를 검사합니다. 예를 들어, 색인된 값: `{"field": [5, 10, 15]}`은(는) `field:5`, `field:10` 또는 `field:15` 용어를 포함하는 모든 쿼리에 대한 대결로 반환됩니다.

배열 내에서 여러 잠재적 값을 검색하려면 쿼리에서 [정규식](#regular-expressions)을 사용합니다. 예를 들어 [사용자 그룹의 매치메이킹](#matchmake-from-users-groups)을 참조하세요. 

## 연산자

쿼리 용어를 생성할 때 사용할 수 있는 세 가지 연산자: 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 >}}

## 정규식

쿼리는 표현식을 슬래시(`/`)로 감싸서 지정하는 방식으로 쿼리 용어의 일부로 정규식을 사용할 수 있습니다. 정규식에 대한 자세한 내용은 [regexr](https://regexr.com/) 및 [regex101](https://regex101.com/) 웹 사이트를 참조하세요.

예를 들어, `freeforall` 또는 `capturetheflag`의 게임 `mode`과(와) 일치하는 대결을 찾으려면 다음 쿼리를 사용할 수 있습니다. `mode:/(freeforall|capturetheflag)/` 

## 부스팅

위에서 설명한 연산자를 사용하면 지정된 결과가 반환되는지 여부를 설명하기 위해 여러 용어로 쿼리를 작성할 수 있습니다. 또한 쿼리에서 용어의 상대적 중요도를 "부스팅"하여, 반환된 결과의 순서를 선택적으로 정의할 수 있습니다.

이것은 쿼리 용어 끝에 `^` 및 임의의 부스터 번호를 사용하여 수행됩니다.

예를 들어, `mode:freeforall^2 mode:capturetheflag` 쿼리를 사용하여 대결을 검색하는 경우 결과에는 모두 무료 및 깃발 뺏기 대결이 전부 포함되지만 모두 무료 결과는 깃발 뺏기 결과보다 위에 나열됩니다(즉, "더 나은" 일치).

`+` 연산자를 사용하지 않았기 때문에 이 쿼리는 모두 무료 또는 깃발 뺏기 대결이 아닌 결과를 반환할 수도 있지만 이러한 결과는 가장 낮은 결과가 될 것입니다.

## 이스케이핑

정확한 쿼리 결과를 얻으려면 다음 문자 집합을 이스케이프해야 합니다. `+-=&|><!(){}[]^\"~*?:\\/ `

공백 문자가 포함되어 있음에 주의합니다. 예를 들어, "깃발 뺏기" 대결을 쿼리하려면 `mode:Capture The Flag` 대신 `mode:Capture\ The\ Flag`을 사용합니다. 후자의 쿼리는 여전히 유효할 수 있지만 "뺏기" `mode`과(와) 일치하고 어디에나 "깃발"이 포함된 대결을 검색하므로 잘못된 결과가 생성됩니다.

## 예

### 친구의 대결 찾기

대결 `label`에 현재 대결에 있는 모든 플레이어의 사용자 ID가 있는 배열 `players`이(가) 포함된 경우 다음 쿼리를 사용하여 지정된 친구가 속한 대결을 찾을 수 있습니다:

{{< 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`에 사용자가 속한 모든 그룹의 ID가 있는 배열 `groups`이(가) 포함된 경우 다음 쿼리를 사용하여 해당 그룹에서만 대결을 찾을 수 있습니다:

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

local query = "label.groups:/(<groupID>|<groupID2>|<groupID3)/"
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.groups:/(<groupID>|<groupID2>|<groupID3)/"
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 findGroupsMatch(context: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama) {
  const query = "label.groups:/(<groupID>|<groupID2>|<groupID3)/";
  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 >}}

### 플레이어 수준에 따른 매치메이킹

이 예에서는 수준이 13에서 17 사이인 2-4명의 상대방을 찾는 `Skill` 수준 15의 플레이어를 위한 매치메이커 티켓을 만듭니다.

{{< 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 >}}

{{< missing type="client" lang="lua" framework="defold" />}}
{{< 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 >}}

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