Erlang Thursday - 限制返回结果的ETS查询

上星期的Erlang Thursday我们继续研究 ets:select/2 并且用 ets:fun2ms/1生成匹配规则来和它配合使用。

这个星期我们将看看ets模块提供的select函数的其它版本。

还是老样子,我们将设置好我们新的ETS表的环境,以便我们的shell崩溃了我们的表不会丢失。

1
2
3
4
5
6
7
8
Fun = fun() -> receive after infinity -> ok end end.
% #Fun<erl_eval.20.54118792>
SomeProcess = spawn(Fun).
% <0.52.0>
TestTable = ets:new(ets_table, [public]).
% 16402
ets:give_away(TestTable, SomeProcess, []).
% true

接下来我们将装载我们的测试ETS表,它是一些测试“产品”。为了例子的简单,我们将仅用一个数字代表一个产品id,然后用一个100以内的随机整数加上0.99作为价格。

1
2
3
4
5
[[ets:insert(TestTable, {ProductId, random:uniform(100) + 0.99})
|| ProductId <- lists:seq(1, 10000) ]].
% [[true,true,true,true,true,true,true,true,true,true,true,
% true,true,true,true,true,true,true,true,true,true,true,true,
% true,true,true,true,true|...]]

我们将创建一个匹配规则(价格在19.99至30之间)来查找数据。

1
2
3
4
5
6
7
ProductsInTheTwenties = ets:fun2ms(fun({Product, Price})
when Price >= 19.99 andalso Price < 30
-> {Product, Price}
end).
% [{{'$1','$2'},
% [{'andalso',{'>=','$2',19.99},{'<','$2',30}}],
% [{{'$1','$2'}}]}]

如果我们用 ets:select/2 和上面这个匹配规则在我们的表上,我们在一个查询里得到所有结果就和前面我们看到的一样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
ets:select(TestTable, ProductsInTheTwenties).
% [{4351,29.99},
% {635,19.99},
% {6005,20.99},
% {3742,27.99},
% {5956,29.99},
% {3753,28.99},
% {6653,25.99},
% {5151,28.99},
% {2693,27.99},
% {4253,21.99},
% {7636,23.99},
% {1935,19.99},
% {9044,22.99},
% {7797,22.99},
% {2147,23.99},
% {2574,26.99},
% {7575,29.99},
% {2130,28.99},
% {4908,27.99},
% {2218,22.99},
% {9848,21.99},
% {7632,26.99},
% {3562,21.99},
% {3130,27.99},
% {575,26.99},
% {4622,28.99},
% {5678,25.99},
% {4022,...},
% {...}|...]

不过ets模块也给我们一个限制结果集的方式如果我们愿意的话,用 ets:select/3 并传入一个要一次返回结果数的限制。

那么我们来用 ets:select/3 并给它的限制是10,然后看看结果是什么。

1
2
3
4
5
6
7
8
9
10
11
12
ets:select(TestTable, ProductsInTheTwenties, 10).
% {[{9027,27.99},
% {7347,29.99},
% {7282,20.99},
% {9386,24.99},
% {5415,25.99},
% {4032,29.99},
% {8105,25.99},
% {4634,24.99},
% {1275,20.99},
% {234,20.99}],
% {16402,576,10,<<>>,[],0}}

我们的结果是一个元组而不是一个结果的列表。第一个元组元素是一个我们期望的10个结果组成的列表,第二个元素是一个奇怪的元组,我们查阅官方文档中 ets:select/3 的描述,这个奇怪的元组表示一个概念:continuation 。

所以我们再运行我们的查询,这次我们把结果绑定到变量。

1
2
3
4
5
6
7
8
9
10
11
12
{Results, Continuation} = ets:select(TestTable, ProductsInTheTwenties, 10).
% {[{9027,27.99},
% {7347,29.99},
% {7282,20.99},
% {9386,24.99},
% {5415,25.99},
% {4032,29.99},
% {8105,25.99},
% {4634,24.99},
% {1275,20.99},
% {234,20.99}],
% {16402,576,10,<<>>,[],0}}

现在我们有了这个continuation,不过它是什么?它对我们来说有什么用?

简而言之,它可以被认为是一个不可变的书签。它不仅表示我们在查询结果的哪一页,也表示我们正在读的内容(我们的查询)。

它允许我们把这个continuation传给 ets:select/1 ,就能快速获取我们前面看过的结果内容。

1
2
3
4
5
6
7
8
9
10
11
12
ets:select(Continuation).
% {[{2533,24.99},
% {1357,22.99},
% {564,21.99},
% {9086,22.99},
% {5265,25.99},
% {4030,22.99},
% {2802,25.99},
% {8254,27.99},
% {7088,26.99},
% {3062,27.99}],
% {16402,960,10,<<>>,[{6792,29.99},{9295,29.99}],2}}

因为它是我们的特殊的不可变的书签,每次我们用这个书签它都带我们到这同样书的相同的地方,并且我们仅能读到我们原先设置的每页最大纪录数。

所以不管我们在我们同一个continuation上调用多少次 ets:select/1 ,每次我们都将获得相同的结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
ets:select(Continuation).
% {[{2533,24.99},
% {1357,22.99},
% {564,21.99},
% {9086,22.99},
% {5265,25.99},
% {4030,22.99},
% {2802,25.99},
% {8254,27.99},
% {7088,26.99},
% {3062,27.99}],
% {16402,960,10,<<>>,[{6792,29.99},{9295,29.99}],2}}
ets:select(Continuation).
% {[{2533,24.99},
% {1357,22.99},
% {564,21.99},
% {9086,22.99},
% {5265,25.99},
% {4030,22.99},
% {2802,25.99},
% {8254,27.99},
% {7088,26.99},
% {3062,27.99}],
% {16402,960,10,<<>>,[{6792,29.99},{9295,29.99}],2}}
ets:select(Continuation).
% {[{2533,24.99},
% {1357,22.99},
% {564,21.99},
% {9086,22.99},
% {5265,25.99},
% {4030,22.99},
% {2802,25.99},
% {8254,27.99},
% {7088,26.99},
% {3062,27.99}],
% {16402,960,10,<<>>,[{6792,29.99},{9295,29.99}],2}}

而如果我们仔细看结果的元组,我们看到得到一个不同的下一个continuation的元组。

1
2
3
4
5
6
7
8
9
10
11
12
{SecondResults, SecondContinuation} = ets:select(Continuation).
% {[{2533,24.99},
% {1357,22.99},
% {564,21.99},
% {9086,22.99},
% {5265,25.99},
% {4030,22.99},
% {2802,25.99},
% {8254,27.99},
% {7088,26.99},
% {3062,27.99}],
% {16402,960,10,<<>>,[{6792,29.99},{9295,29.99}],2}}

我们可以用这个新的continuation用在我们下一次调用 ets:select/1 上,来得到下一个结果集和另一个continuation。

1
2
3
4
5
6
7
8
9
10
11
12
ets:select(SecondContinuation).
% {[{8569,19.99},
% {1805,28.99},
% {6819,23.99},
% {9313,28.99},
% {9527,27.99},
% {1737,29.99},
% {700,26.99},
% {142,25.99},
% {6792,29.99},
% {9295,29.99}],
% {16402,513,10,<<>>,[],0}}

如果我们在获取完结果集后再执行一次查询,我们得到一个 ‘$end_of_table’ 原子。

1
2
ets:select(TestTable, [{{'$1', '$2'}, [{'<', '$2', 0}], ['$$']}], 10).
% '$end_of_table'

指定一个限制并有一个continuation的能力也可以用在 ets:match/3 和 ets:match/1 上,同时也可以用在 ets:match_object/3 和 ets:match_object/1 上。

下星期,我们将继续研究ets模块里的不同select函数,同时看看它们的行为方式和有序集合,将比较一下 select 函数和 select_reverse函数的不同,也研究一下如果我们当我们用一个continuation的时候在结果集里插入一些数据,continuation将会怎样。

原文链接: https://www.proctor-it.com/erlang-thursday-using-ets-select-with-a-limit/