如何解决获取与某个帐户相关联的所有帐户通过 IP
数据库表
我有两张桌子:
-
account
表,具有id
和username
字段(以及其他不相关的字段 - account table full structure)。 -
account_ip
表,具有account
和ip
字段(以及其他不相关的字段 - account_ip table full structure)。
他们的关系是:
-
account_ip.account
字段与account.id
相关联。
account_ip
表的用途是
- 跟踪每个帐户已成功登录的所有 IP。
帐号 IP 关联
约定:
- 如果两个帐户在
account_ip
表中有一个共同的 IP,我们说这两个帐户是关联
例如:
在这种情况下,帐户 12
与 13
相关联,因为它们有共同的 127.0.0.1
,帐户 13
与 14
相关联,因为它们有 127.0.0.4
的共同点:
12 13 14
查询
我可以运行一些queries,例如:
- 级别 1:选择链接到给定帐户 ID 123 的所有帐户/IP
SELECT account.username,account_ip.* FROM account_ip
INNER JOIN account ON account.id = account_ip.account
WHERE ip IN (
SELECT ip FROM account_ip WHERE account = 123
);
- 第 2 级:选择链接到与给定帐户 ID 123 关联的所有帐户的所有帐户/IP
SELECT account.username,account_ip.* FROM account_ip INNER JOIN account ON account.id = account_ip.account
WHERE ip IN (
SELECT ip FROM account_ip WHERE account IN (
SELECT account FROM account_ip WHERE ip IN (
SELECT ip FROM account_ip WHERE account = 123
)
)
);
问题
给定一个帐户 ID,我如何才能在任何链接级别获取与该帐户相关的所有帐户/IP?
12 13 14 15 16
给定帐户 12
,我想要一个查询,提供 12、13、14、15 和 16 的所有帐户和 IP。
解决方法
这是一个图遍历问题。 . .这需要递归 CTE。有两个元素很棘手。一种方法是:
with recursive cte as (
select account,ip,cast(ip as char(1000)) as visited_ips,cast(account as char(1000)) as visited_accounts
from accounts
where account = 12
union all
select a.account,a.ip,concat_ws(',',cte.visited_ips,a.ip),cte.visited_accounts,a.account)
from cte join
accounts a
on a.ip = cte.ip or a.account = cte.account
where ((a.account = cte.account and find_in_set(a.ip,cte.visited_ips) = 0) or
(a.ip = cte.ip and find_in_set(a.account,cte.visited_accounts) = 0) )
)
select distinct ip
from cte;
Here 是一个 dbfiddle。
,正如 Gordon Linoff 所指出的,这需要递归 CTE。但是,CTE 在 MySQL 8+ 上可用。如果您的数据库支持 CTE,他的解决方案是一种可行的方法。
话虽如此,我已经设法创建了一个存储过程来获取帐户和 IP。
限制是:
- 它使用临时表 - 脚本不能在同一个会话中并行运行。
- 未针对性能进行优化
DROP PROCEDURE if EXISTS find_linked_accounts;
DELIMITER //
CREATE PROCEDURE find_linked_accounts(account_id INT)
BEGIN
SET @start_account_id = account_id;
SET @last_data_count = 0;
SET @data_count = 0;
DROP TEMPORARY TABLE if exists linked_accounts;
DROP TEMPORARY TABLE if exists temp_linked_accounts;
CREATE TEMPORARY TABLE linked_accounts(
`account` INT(10) UNSIGNED NOT NULL,`ip` VARCHAR(15) NOT NULL,PRIMARY KEY(`account`,`ip`)
);
CREATE TEMPORARY TABLE temp_linked_accounts(
`account` INT(10) UNSIGNED NOT NULL,`ip`)
);
INSERT INTO linked_accounts
SELECT account,ip
FROM account_ip
WHERE account_ip.account = @start_account_id;
REPEAT
SET @last_data_count = @data_count;
# insert all accounts connected to the previously found accounts in the temp_linked_accounts table
INSERT INTO temp_linked_accounts
SELECT distinct aip.account,aip.ip
FROM account_ip aip
INNER JOIN linked_accounts la ON la.account = aip.account OR la.ip = aip.ip;
INSERT INTO linked_accounts
SELECT account,ip
FROM temp_linked_accounts
ON DUPLICATE KEY UPDATE linked_accounts.account=linked_accounts.account;
SET @data_count = (SELECT COUNT(account) FROM linked_accounts);
DELETE FROM temp_linked_accounts;
UNTIL @data_count = @last_data_count
END REPEAT;
SELECT * FROM linked_accounts;
DROP TEMPORARY TABLE if exists linked_accounts;
DROP TEMPORARY TABLE if exists temp_linked_accounts;
END //
DELIMITER ;
要调用存储过程,请执行 call find_linked_accounts(account_id);
- 用整数值替换 account_id,例如call find_linked_accounts(12);
。
此脚本已在最新的 AzerothCore 数据库(截至撰写本文时)以及问题中提供的 SQL 脚本上进行了测试。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。