1

ユーザーとそれぞれのドメインを IP アドレスに割り当てるクエリを作成しています。重複するユーザーを持つ IP アドレスはありません。

SQL Fiddle でこれまでに得たものは次のとおりです。http://sqlfiddle.com/#!2/39c51/2/0

現在のすべての割り当て (数十万) を含むテーブルがあります。小規模な例は次のようになります。

mysql> select * from test.usermap;
+-------------+-------+-------------------+
| vip         | user  | domain            |
+-------------+-------+-------------------+
| 100.50.20.1 | joe   | joesdomain.com    |
| 100.50.20.1 | bob   | joesdomain.com    |
| 100.50.20.2 | tom   | domain2.com       |
| 100.50.20.2 | fred  | domain2.com       |
| 100.50.20.2 | sally | domain2.com       |
| 100.50.20.3 | admin | athriddomain.com  |
| 100.50.20.4 | admin | numberfour.com    |
| 100.50.20.3 | sally | fivewithsally.com |
| 100.50.20.4 | jim   | thesix.com        |
| 100.50.20.1 | admin | seven.com         |
| 100.50.20.1 | sally | seven.com         |
| 100.50.20.1 | sue   | seven.com         |
| 100.50.20.5 |       |                   |
| 100.50.20.6 |       |                   |
+-------------+-------+-------------------+
14 rows in set (0.00 sec)

まだ割り当てられていないユーザーを含む別のテーブルがあります。これも小規模な例です。

mysql> select * from test.newusers;
+-------+-----------+
| user  | domain    |
+-------+-----------+
| jim   | eight.com |
| sally | eight.com |
| admin | nine.com  |
| james | ten.com   |
| jane  | ten.com   |
+-------+-----------+
5 rows in set (0.00 sec)

ここでの考え方は、「jim」も「sally」も持たない最も古い IP であるため、eight.com の下のすべてのユーザーを .5 に割り当て、次に、nine.com を .2 に、ten.com を .1 に割り当てることです。それぞれのユーザーの競合 (またはその欠如)。

私が探している結果は次のようになります。

+-------------+-------+-----------+
| vip         | user  | domain    |
+-------------+-------+-----------+
| 100.50.20.1 | james | ten.com   |
| 100.50.20.1 | jane  | ten.com   |
| 100.50.20.2 | admin | nine.com  |
| 100.50.20.5 | jim   | eight.com |
| 100.50.20.5 | sally | eight.com |
+-------------+-------+-----------+
5 rows in set (0.01 sec)

次のように、相関サブクエリ内のサブクエリでこれを行うことができます。

mysql> select  
(
    select vip 
    from test.usermap
    where vip not in
    (
        select distinct vip 
        from test.usermap  
        where user in
        (
            select user 
            from test.newusers 
            where domain = n.domain
        )
    )
    order by inet_aton(vip) asc
    limit 1
) as vip, n.user, n.domain 
from test.newusers n
order by inet_aton(vip) asc;
+-------------+-------+-----------+
| vip         | user  | domain    |
+-------------+-------+-----------+
| 100.50.20.1 | james | ten.com   |
| 100.50.20.1 | jane  | ten.com   |
| 100.50.20.2 | admin | nine.com  |
| 100.50.20.5 | jim   | eight.com |
| 100.50.20.5 | sally | eight.com |
+-------------+-------+-----------+
5 rows in set (0.00 sec)

しかし、それは非常に非効率的であり、私のプロダクション マッピングと newusers テーブルはそれぞれ 30 万行と 5 万行なので、これは問題外です。

ネストされたサブクエリの代わりに結合を使用してこれをより効率的にしようとしているので、内側のクエリを結合に置き換え、外側のクエリの列を ON 句にリストしましたが、これは不可能のようです:

mysql> select 
(
    select distinct vip 
    from test.usermap u 
    join test.newusers r
        on r.domain = n.domain
        and r.user != u.user
    order by inet_aton(vip) asc limit 1
) as vip, n.user, n.domain
from test.newusers n;
ERROR 1054 (42S22): Unknown column 'n.domain' in 'on clause'
mysql> 

クエリ自体のロジックは理にかなっていますが、外側のクエリ参照をそれが表す文字列定数に置き換えるとうまく機能するためです。

mysql> select
(
    select distinct vip 
    from test.usermap u 
    join test.newusers r
        on r.domain = 'ten.com'
        and r.user != u.user
    order by inet_aton(vip) asc limit 1
) as vip, n.user, n.domain
from test.newusers n
where domain = 'ten.com';
+-------------+-------+---------+
| vip         | user  | domain  |
+-------------+-------+---------+
| 100.50.20.1 | james | ten.com |
| 100.50.20.1 | jane  | ten.com |
+-------------+-------+---------+
2 rows in set (0.00 sec)

私の質問は: 内部クエリの結合内で外部クエリから列を参照する方法はありますか? そうでない場合、非効率的な方法でサブクエリをネストせずに、どのような代替手段が存在しますか?

ここでもフィドルがあります: http://sqlfiddle.com/#!2/39c51/2/0

4

1 に答える 1

3

これがどれだけ効率的かはわかりませんが、複数のサブクエリをネストせずにクエリを書き直すことができます。

SELECT  INET_NTOA(MIN(INET_ATON(UserMap.VIP))) AS VIP,
        NewUsers.User, 
        NewUsers.Domain
FROM    NewUsers
        CROSS JOIN UserMap
        LEFT JOIN
        (   SELECT  u.Domain, m.VIP
            FROM    NewUsers u
                    INNER JOIN UserMap m
                        ON u.User = m.User
        ) ex
            ON ex.Domain = NewUsers.Domain
            AND ex.VIP = UserMap.VIP
WHERE   ex.Domain IS NULL
GROUP BY NewUsers.User, NewUsers.Domain
ORDER BY VIP ASC;   

SQL Fiddle の例

補遺

上記のクエリは、利用可能な VIP がない行を返しません。たとえば、クエリから100.50.20.5100.50.20.1が削除された場合は、次のようになります。UserMap

VIP             USER    DOMAIN
NULL            jim     eight.com
NULL            sally   eight.com
100.50.20.1     james   ten.com
100.50.20.1     jane    ten.com
100.50.20.2     admin   nine.com

私が作成したクエリは、VIP が null でない行のみを返します。

VIP             USER    DOMAIN
100.50.20.1     james   ten.com
100.50.20.1     jane    ten.com
100.50.20.2     admin   nine.com

これを回避するには、UNION を使用できます。

SELECT  INET_NTOA(MIN(INET_ATON(a.VIP))) AS VIP,
        a.User, 
        a.Domain
FROM    (   SELECT  UserMap.VIP,
                    NewUsers.User, 
                    NewUsers.Domain
            FROM    NewUsers
                    CROSS JOIN UserMap
                    LEFT JOIN
                    (   SELECT  u.Domain, m.VIP
                        FROM    NewUsers u
                                INNER JOIN UserMap m
                                    ON u.User = m.User
                    ) ex
                        ON ex.Domain = NewUsers.Domain
                        AND ex.VIP = UserMap.VIP
            WHERE   ex.Domain IS NULL
            UNION ALL
            SELECT  NULL AS VIP,
                    NewUsers.User,
                    NewUsers.Domain
            FROM    NewUsers
        ) a
GROUP BY a.User, a.Domain
ORDER BY VIP ASC;

SQL Fiddle の改訂された例

利用可能な VIP がないケースを処理するためのロジックが何であるかわかりません。そのため、この部分の解決策を実際に提案することはできません。ただし、これを使用して次の VIP を取得できます。

SELECT  INET_NTOA(MAX(INET_ATON(UserMap.VIP)) + 1) AS NextVIP
FROM    UserMap

この問題のもう 1 つの問題は、NewUsers の競合です。たとえば、NewUsers テーブルに次のレコードが含まれている場合:

('jim','eight.com'),
('sally','eight.com'),
('jim','eleven.com'),
('sally','eleven.com');

あなたのクエリと私のクエリの両方が、これらすべてを VIP に割り当てます100.50.20.5。これが発生する可能性がある場合、これを回避する最善の方法は、一度に 1 つのドメインのユーザー名のみを挿入することだと思います。ただし、JOIN のみを使用して実行できます。

クエリを簡素化するために、2 つのビューを作成しました

CREATE VIEW UsedVIP
AS
    SELECT  u.Domain, m.VIP
    FROM    NewUsers u
            INNER JOIN UserMap m
                ON u.User = m.User;

CREATE VIEW NewUserMap 
AS
    SELECT  UserMap.VIP,
            NewUsers.User, 
            NewUsers.Domain
    FROM    NewUsers
            CROSS JOIN UserMap
            LEFT JOIN UsedVIP ex
                ON ex.Domain = NewUsers.Domain
                AND ex.VIP = UserMap.VIP
    WHERE   ex.Domain IS NULL;

最後のクエリは次のとおりです。

SELECT  INET_NTOA(MIN(INET_ATON(a.VIP))) AS VIP,
        a.User, 
        a.Domain
FROM    NewUserMap a
        LEFT JOIN NewUserMap b
            ON a.User = b.user
            AND a.VIP = b.VIP
            AND a.Domain > b.domain
        LEFT JOIN NewUserMap c
            ON a.User = c.user
            AND b.Domain = c.domain
            AND b.VIP < c.VIP
WHERE   c.user IS NULL
GROUP BY a.User, a.Domain
ORDER BY VIP ASC;

どちらが返されますか:

VIP             USER    DOMAIN
100.50.20.1     jane    ten.com
100.50.20.1     james   ten.com
100.50.20.2     admin   nine.com
100.50.20.5     sally   eight.com
100.50.20.5     jim     eight.com
100.50.20.6     jim     eleven.com
100.50.20.6     sally   eleven.com

SQL Fiddle の例

于 2013-05-09T22:33:22.710 に答える