【问题标题】:Get all accounts linked to a certain account (via IP)获取链接到某个帐户的所有帐户(通过 IP)
【发布时间】:2021-03-20 15:57:48
【问题描述】:

数据库表

我有两张桌子:

他们的关系是:

  • 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
        )
    )
);

但最后一个只持续到 2 级链接。

问题

给定一个帐户 ID,我如何在任何链接级别获取与该帐户相关的所有帐户/IP?

例如,如果我有这种链接关系:

12 13 14 15 16

给定帐户12,我想要一个查询,提供所有帐户和 12、13、14、15 和 16 的 IP。

【问题讨论】:

    标签: mysql sql database mariadb azerothcore


    【解决方案1】:

    这是一个图遍历问题。 . .这需要递归 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), concat_ws(',', 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。

    【讨论】:

    • 表格是account,没有最终的s,但即使在解决了这个问题之后我也得到了Unknown column 'account' in 'field list'
    • @FrancescoBorzi 。 . .我添加了 dbfiddle 以显示代码正常工作。你有工作代码开始。您可以根据您的特定列和表名称对其进行调整。无论如何,您的数据模型比您在问题中的复杂得多。
    【解决方案2】:

    正如 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` VARCHAR(15) NOT NULL,
            PRIMARY KEY(`account`, `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 脚本上进行了测试。

    【讨论】:

      猜你喜欢
      • 2017-05-12
      • 2021-09-22
      • 2022-01-11
      • 2020-03-27
      • 2018-01-22
      • 1970-01-01
      • 2023-02-15
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多