# Sets

A set is a collection type of deduplicated and ordered values that can have a maximum size limit.

> [!NOTE]
> Before version 3.0.0, sets were simply arrays that deduplicated their items. To emulate the former behaviour, add the clause `VALUE $value.distinct()` to a `DEFINE FIELD` definition.

A set is similar to an array, but with two key differences:

* The values in a set are automatically deduplicated.
* The values in a set are automatically ordered.

## Set syntax and casting

A set can be created using the literal syntax `{}`.

```surql
RETURN {1, 6, 6, 2};
-- {1, 2, 6}
```

To create a set with zero items or a single item, add a comma.

```surql
{,}.is_set();  -- true
{9,}.is_set(); -- true

{}.is_set();   -- false
{9}.is_set();  -- false
```

In addition to the `{}` literal syntax, an array can be cast into a set.

```surql
DEFINE FIELD bank_accounts ON TABLE customer TYPE array<int>;
DEFINE FIELD languages ON TABLE customer TYPE set<string>;

CREATE customer SET
    bank_accounts = [
      55555,
      55555,
      98787
    ],
    languages = <set>[
        "en",
        "ja",
        "kr",
        "kr"
    ];
```

```surql title="Output"
[
	{
		bank_accounts: [
			55555,
			98787
		],
		id: customer:uv6mn62t8td9vzvfogh4,
		languages: {
			'en',
			'ja',
			'kr'
		}
	}
]
```

Casting into a `set` and back into an array can be a convenient way to deduplicate items in the same way that the [`array::distinct()`](/docs/reference/query-language/functions/database-functions/array.md#arraydistinct) and [`array::sort()`](/docs/reference/query-language/functions/database-functions/array.md#arraysort) functions are used.

```surql
<array><set>[18,7,6,6,6,6,5,4,3,9];
[18,7,6,6,6,6,5,4,3,9].distinct().sort();
```

```surql title="Output"
[3, 4, 5, 6, 7, 9, 18]
```

## Using the index operator on sets

The `[]` index operator can be used on a set in the same manner as an array. Note however that due to a set's automatic ordering, its individual values are technically not assigned an index. This can be seen in the following example in which `[0]` for an array will return whichever item happens to be at that location, while for a set `[0]` will automatically be the item with the least value.

To return the greatest value of a set, use `[$]` (since 3.2.0) or the [`set::last()`](/docs/reference/query-language/functions/database-functions/set.md#setlast) function.

```surql
-- Array: returns 4 at index 0
[4,6,2][0];

-- Set: returns 2, the least item
(<set>{4,6,2})[0];

-- Set: returns 6, the greatest item
{4,6,2}[$];
{4,6,2}.last();
```

## Filtering and mapping with set functions

SurrealDB also includes a number of methods for sets that make it easier to filter and map. These methods take a closure (an anonymous function) that works in a similar way to the `$this` parameter above.

Here is an example of the `set::filter()` method being used. Note that the parameter name inside the closure is named by the user, so `$val` in the example below could be `$v` or `$some_val` or anything else.

```surql
{1,3,5}.filter(|$val| $val > 2);

-- {3,5}
```

## Adding sets

An set can be added to another set or array, resulting in a single set consisting of the items of the first followed by those of the second.

```surql
{1,2} + [3,4];
{1,2} + {3,4};
```

```surql title="Output"
{1,2,3,4}
```

## Sets on schemafull fields

A field typed as a set accepts a value that can be coerced into one, so the `+=` operator adds a single item to it.

```surql
DEFINE TABLE test SCHEMAFULL;
DEFINE FIELD tags ON test TYPE set<string>;

CREATE test:one SET tags += 'admin';
UPDATE test:one SET tags += 'editor';
```

When the field has a [`VALUE`](/docs/reference/query-language/statements/define/field.md#using-the-value-clause-to-set-a-fields-value) clause on its items, that clause is applied before the set removes duplicates. Two entries that only differ in a way the clause normalises therefore collapse into one on the write that adds them.

```surql
DEFINE FIELD OVERWRITE tags.* ON test TYPE string VALUE string::trim($value);

UPDATE test:one SET tags += ' admin ';
-- tags is still { 'admin', 'editor' }: the trimmed value already exists
```

> [!NOTE]
> Before SurrealDB 3.3.0, a set field rejected `+=` because the single item could not be coerced into a set, and deduplication ran before the `VALUE` clause. A value that only became a duplicate after the clause was applied stayed in the set until the next write to that record.
