# Why is there no ordered\_bag type ETS table?

**URL:** <https://erlangforums.com/t/why-is-there-no-ordered-bag-type-ets-table/2621>\
**Category:** Questions / Help\
**Tags:** erlang\
**Created:** [May 21, 2023, 8:01am UTC](https://erlangforums.com/t/why-is-there-no-ordered-bag-type-ets-table/2621 "2023-05-21T08:01:10Z")\
**Posts on this page:** 10\
**Page:** 1

<div class="post-metadata">

**Author:** ![erlangys](https://erlangforums.com/letter_avatar_proxy/v4/letter/e/c6cbf5/32.png) [@erlangys](https://erlangforums.com/u/erlangys)\
**Post date:** [May 21, 2023, 8:01am UTC](https://erlangforums.com/t/why-is-there-no-ordered-bag-type-ets-table/2621/1 "2023-05-21T08:01:10Z")

</div>

It looks like ordered\_bag is also very useful. For example, to record the persons with the max score, then the persons with the next max score etc. I wonder why such a type is not introduced in ETS. Or this kind of use case can be mimic using other types of ETS?

---

<div class="post-metadata">

**Author:** ![jimdigriz](https://erlangforums.com/user_avatar/erlangforums.com/jimdigriz/32/1885_2.png) [@jimdigriz](https://erlangforums.com/u/jimdigriz)\
**Post date:** [May 21, 2023, 8:33am UTC](https://erlangforums.com/t/why-is-there-no-ordered-bag-type-ets-table/2621/2 "2023-05-21T08:33:17Z")

</div>

I probably would suggest just a simple `set` with `{Key,gb_trees:tree({Score,...}) | list({Score, ...})}`; if you use a `list()` then manually sort it using `lists:keysort(1, List)`.

Two problems though:

1. slower as you have to read in the whole object, update it, and then write it out
2. you will need to marshal updates through a single process as `ets:select_replace/2` uses a matchspec, not an arbitrary function

This should be fine up to a couple of 100-\>1000 updates per second per ETS table though so depends on your use case…so unless you are doing a pachinko simulator…

---

<div class="post-metadata">

**Author:** ![jimdigriz](https://erlangforums.com/user_avatar/erlangforums.com/jimdigriz/32/1885_2.png) [@jimdigriz](https://erlangforums.com/u/jimdigriz)\
**Post date:** [May 21, 2023, 5:09pm UTC](https://erlangforums.com/t/why-is-there-no-ordered-bag-type-ets-table/2621/3 "2023-05-21T17:09:08Z")

</div>

Oh, as I am an idiot (I blame pre-coffee and not having had my breakfast sausage roll) you will not be able to use `gb_trees` as you cannot have duplicate keys; so use `list()` instead with `lists:keysort/2`.

Sorry if that sent you off on a wild goose chase!

---

<div class="post-metadata">

**Author:** ![max-au](https://erlangforums.com/user_avatar/erlangforums.com/max-au/32/1627_2.png) [@max-au](https://erlangforums.com/u/max-au)\
**Post date:** [May 22, 2023, 3:32pm UTC](https://erlangforums.com/t/why-is-there-no-ordered-bag-type-ets-table/2621/4 "2023-05-22T15:32:34Z")

</div>

> [@erlangys](#):
>
> Or this kind of use case can be mimic using other types of ETS?

Usually it’s done by storing a “list of people with the same score” in an `ordered_set` table.

---

<div class="post-metadata">

**Author:** ![erlangys](https://erlangforums.com/letter_avatar_proxy/v4/letter/e/c6cbf5/32.png) [@erlangys](https://erlangforums.com/u/erlangys)\
**Post date:** [May 25, 2023, 5:44pm UTC](https://erlangforums.com/t/why-is-there-no-ordered-bag-type-ets-table/2621/5 "2023-05-25T17:44:30Z")

</div>

That solution may have some performance issue: To add a member, the list of people with the same score need to be serialized and read out from ETS into the current process, add the member to the list, then the new list will have to be serialized and copied back into ETS. If ETS can support some kind of update\_spec (similar to match\_spec but for updating) which can be used to precisely update the terms within ETS that will greatly help.

---

<div class="post-metadata">

**Author:** ![LeonardB](https://erlangforums.com/user_avatar/erlangforums.com/leonardb/32/1856_2.png) [@LeonardB](https://erlangforums.com/u/LeonardB)\
**Post date:** [May 25, 2023, 7:02pm UTC](https://erlangforums.com/t/why-is-there-no-ordered-bag-type-ets-table/2621/6 "2023-05-25T19:02:00Z")

</div>

You thinking of `ets:select_replace/2` ?

---

<div class="post-metadata">

**Author:** ![erlangys](https://erlangforums.com/letter_avatar_proxy/v4/letter/e/c6cbf5/32.png) [@erlangys](https://erlangforums.com/u/erlangys)\
**Post date:** [May 25, 2023, 9:48pm UTC](https://erlangforums.com/t/why-is-there-no-ordered-bag-type-ets-table/2621/7 "2023-05-25T21:48:21Z")

</div>

Didn’t notice this method. Yes it looks quite promising. I played around with it and find that it doesn’t support updating map in place.  
For example, the change to a list works:  
ets:fun2ms(fun({K, L}) when is\_list(L) → {K, [marker | L]} end).  
Output: [{{‘$1’,‘$2’},[{is\_list,‘$2’}],[{{‘$1’,[marker|‘$2’]}}]}]  
But for something similar with map:  
ets:fun2ms(fun({K, Map}) when is\_map(Map) → {K, Map#{marker =\> true}} end).  
Error: the language element map (in body) cannot be translated into match\_spec  
{error,transform\_error}

---

<div class="post-metadata">

**Author:** ![mikpe](https://erlangforums.com/letter_avatar_proxy/v4/letter/m/b487fb/32.png) [@mikpe](https://erlangforums.com/u/mikpe)\
**Post date:** [May 26, 2023, 12:47pm UTC](https://erlangforums.com/t/why-is-there-no-ordered-bag-type-ets-table/2621/8 "2023-05-26T12:47:27Z")

</div>

Let’s say you have a bag with primary key K and attribute A. If you have frequent updates (insert or delete `{K,A}`) or membership tests (is `{K,A}` present?) and less frequent “get all with key K”, then you may want to emulate the bag as an `ordered_set` with `{K,A}` keys. Of course, if the list of As always are short then don’t bother.

It’s perhaps less of an issue for plain ETS, but we’ve had write-amplification issues with Mnesia tables using the naive key-plus-list approach. Before we migrated off Mnesia we tended use a mix of emulated bags as above, and tables where the “set of As” were represented as a B-tree.

---

<div class="post-metadata">

**Author:** ![LeonardB](https://erlangforums.com/user_avatar/erlangforums.com/leonardb/32/1856_2.png) [@LeonardB](https://erlangforums.com/u/LeonardB)\
**Post date:** [May 26, 2023, 6:34pm UTC](https://erlangforums.com/t/why-is-there-no-ordered-bag-type-ets-table/2621/9 "2023-05-26T18:34:36Z")

</div>

Not sure if I’m just repeating what @mikpe was saying, but using an order\_set with ‘Score’ being part of the key should give you fast, sorted access.

Pseudo-code

```erlang
-define(OPS, #{eq => '=:=',
               lt => '<',
               gt => '>',
               lte => '=<',
               gte => '>='}).
-type name() :: binary().
-type key() :: {number(), name()}.
-record(person, {key :: key(), dob :: calendar:date()}).

find_by_score(Score, Comp) when is_map_key(Comp, ?OPS) ->
  #{Comp := Operator} = ?OPS,
  Match = [{#person{key = {'$1', '_'}, dob = '_'},
           [{Operator, '$1', {const, Score}}],
           ['$_']}],
  ets:select(scores, Match).

```

---

<div class="post-metadata">

**Author:** ![erlangys](https://erlangforums.com/letter_avatar_proxy/v4/letter/e/c6cbf5/32.png) [@erlangys](https://erlangforums.com/u/erlangys)\
**Post date:** [May 26, 2023, 6:40pm UTC](https://erlangforums.com/t/why-is-there-no-ordered-bag-type-ets-table/2621/10 "2023-05-26T18:40:55Z")

</div>

Thank you for providing such a detailed code snippet. That helps a lot to clarify things. This topic also leads me to open a proposal to add some support to in place add/remove key/values of map in ets:select\_replace with match spec:

> <https://github.com/erlang/otp/issues/7309>
>
> \*\*Is your feature request related to a problem? Please describe.\*\*
> 
> ets:select…\_replace/2 can use match spec to do in place replacement with List, which is efficient. But it doesn’t support updating map in place.
> 
> For example, the change to a list works:
> \`ets:fun2ms(fun({K, L}) when is\_list(L) → {K, \[marker | L\]} end).\`
> Output: 
> \`\[{{‘$1’,‘$2’},\[{is\_list,‘$2’}\],\[{{‘$1’,\[marker|‘$2’\]}}\]}\]\`
> 
> But for something similar with map:
> \`ets:fun2ms(fun({K, Map}) when is\_map(Map) → {K, Map#{marker =\> true}} end).\`
> Output:
> \`Error: the language element map (in body) cannot be translated into match\_spec
> {error,transform\_error}\`
> 
> Is Erlang/OTP team considering adding support to map as well?
> 
> \*\*Describe the solution you'd like\*\*
> 
> ets:select\_replace/2 can use match spec to efficiently in place add/remove key/values from a map.
> 
> \*\*Describe alternatives you've considered\*\*
> 
> Right now in order to update a map, it needs to read from ets, make the changes, then copy the new map back to ets, which is very inefficient for bigger map. Since Erlang/OTP already support select by key with match spec, it would be consistent to support modification as well. 
> 
> \*\*Additional context\*\*
