【问题标题】:Redis get member where score is between min and maxRedis获取分数在最小值和最大值之间的成员
【发布时间】:2015-08-24 15:19:18
【问题描述】:

我在 sql 中有一个包含 3 列的表:BIGINT StartNumber, BIGINT EndNumber, BIGINT LocationId,我需要能够做这样的事情

Select LocationId where StartNumber < @number and EndNumber > @number.

例如:

StartNumber EndNumber LocationId
1             5          1
6             9          1
10            16         2

当我有 @number = 7 时,我应该得到 LocationId = 1

如何在 redis 中做到这一点?

我正在考虑将此表移动到 redis,使用排序集和ZRANGEBYSCORE,但它对我不起作用: 1) 当我使用 ZADD key score member [score] [member] 时,即使使用 nx 参数,我也无法添加具有相同成员和不同分数的 2 个元素: zadd myset nx 1 "17" 2 "17" - 它将添加一个元素,然后更新其分数,而不是添加两个元素。 2)当我添加这个时:zadd set1 2 "a" 4 "b" 6 "c" 10 "d" 然后尝试做 zrangebyscore set1 3 3 (想要获得分数包括 3 的成员)我得到空结果

附:所有命令都在redis网站的示例页面上执行。

【问题讨论】:

  • 那么添加两次或多次不同分数的会员的目的是什么?
  • @MatíasFidemraizer in sql 我有一系列与位置 id 相关的数字,例如从 1 到 5 的数字与 locationid = 1 相关,并且作为排序集,每个元素只能有 2 列(得分-成员)我想,我需要为 sql 表中的每一行添加元素,例如 StartIp - Location 和 EndIp - Location。或者我应该像下面建议的那样创建 2 个排序集

标签: redis


【解决方案1】:

因此,据我了解,您没有重叠,并且每个间隔仅映射到一个位置(?),并且间隔没有间隙。基于此,您只能使用一个具有下限(或上限)值的排序列表:

ZADD StartNumber 1 "1:5:1" 6 "6:9:1" 10 "10:16:2"

那么你可以使用:

ZREVRANGEBYSCORE StartNumber 7 -inf LIMIT 0 1

它将是 O(log(N))。

【讨论】:

  • 我同意 - 这也是我意识到的 :)
【解决方案2】:

换句话说,您的问题是“如何将 N 个数字范围映射到一个位置”。一种方法是使用两个排序集,一个用于StartNumber,另一个用于EndNumber。由于成员必须是唯一的,我们还需要通过使用开始/结束值作为成员的一部分来确保这一点。例如,对于您的示例数据,可以这样做:

ZADD StartNumber 1 "1:5:1" 6 "6:9:1" 10 "10:16:2"
ZADD EndNumber 5 "1:5:1" 9 "6:9:1" 16 "10:16:2"

要找到@number=7 的位置,请执行ZRANGEBYSCORE StartNumber -inf 7ZRANGEBYSCORE EndNumber 7 +inf 并将结果相交。剩下的就是在冒号 (:) 上拆分 intesect 的结果,并使用第三个元素作为位置。

注意:如果您的应用确保没有重叠范围,并且每个“数字”只能有一个位置,则只需一组即可获得相同的结果。

【讨论】:

  • 感谢您的回答。我是否理解正确,如果我有 500 万条记录并且 @number 位于中间某个位置(或者这甚至不重要)ZRANGEBYSCORE StartNumber -inf @numberZRANGEBYSCORE EndNumber @number +inf 将返回所有 500 万条记录,我需要将它们相交网站的服务器端没有使用任何额外的 Redis 命令?
  • 您可以使用 Lua 脚本在 Redis 端完成所有操作,但客户端(即网站服务器)也是一种有效的方法。但是,如果您实际上有 500 万个这样的值,您可能需要考虑一种不同的方法,无论相交发生在哪里……您需要管理多少个值、位置和数字范围?
  • 这张表大约有5M条记录,每条记录有3个bigint列——StartNumber、EndNumber和LocationId。 LocationId 会重复,因为某些位置可以分配有多个数字范围。
  • 嗯。所以我假设范围不重叠(即每个给定的 bigint 最多一个位置)。除了这个查询之外的操作怎么样 - 数据是否大量更新和/或是否有其他类型的读取?我可能有一两个想法
  • 哦,关于这些开始和结束数字 - 您实际上是否为这些范围使用了完整的 bigint 空间(假设为 -2^63..2^63)?
【解决方案3】:

(这是我第一次对同一个问题给出两个答案 - 也许我会得到一个徽章或 sumthin' ;))

双排序集方法是一种概括,因此,旨在解决比 OP 需要的更大的问题集(如第一个答案中的 cmets 所示)。这种方法也无效,因为查询是 O(logn)+O(N),所以当 N 很大(例如 5M)时,这可能不是一个好主意。

但是,为了满足要求并且考虑到范围不重叠,实际上可以只使用一个排序集和一个更简单的查询。应该通过连接EndNumberLocationId 来添加集合的成员,并且应该将它们的分数设置为各自的StartNumber,因此为了示例:

ZADD ranges 1 "5:1" 6 "9:1" 10 "16:2"

给定@number,获取相关的LocationId,其Redis Lua代码如下(O(logn)):

-- rangelookup.lua
-- http://stackoverflow.com/questions/32185898/redis-get-member-where-score-is-between-min-and-max/32186675
-- A **non inclusive** range search on a Sorted Set with the following data:
--   score = <StartNumber>
--   member = <EndNumber>:<LocationId>
--
-- KEYS[1] - Sorted Set key name
-- ARGV[1] - the number to search
--
-- reply - the relevant id, nil if range doesn't exist
-- 
-- usage example: redis-cli --eval rangelookup.lua ranges , 7

local number = tonumber(ARGV[1])
local data = redis.call('ZREVRANGEBYSCORE', KEYS[1], number, '-inf', 'WITHSCORES', 'LIMIT', 0, 1)
local reply = nil

if data ~= nil and number > tonumber(data[2]) then
  local to, id = data[1]:match( '(.*):(.*)' )
  if tonumber(to) > number then
    reply = id
  end
end

return reply

样本输出:

$ redis-cli --eval rangelookup.lua ranges , 7
"1"
$ redis-cli --eval rangelookup.lua ranges , 9
(nil)
$ redis-cli --eval rangelookup.lua ranges , 99
(nil)

【讨论】:

    猜你喜欢
    • 2018-08-10
    • 2018-11-15
    • 2021-02-19
    • 2020-04-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多