【问题标题】:SQL that matches all or nothing匹配全部或不匹配的 SQL
【发布时间】:2012-11-21 09:13:02
【问题描述】:

我在 PostgreSQL 数据库中有下表:

CREATE TABLE maclist (
    username character varying,
    mac macaddr NOT NULL,
    CONSTRAINT maclist_user_mac_key UNIQUE (username, mac)
);

我需要一种方法来检查 MAC 地址是否已分配给用户,或者用户根本没有分配 MAC 地址。基本上,如果所有条件都为真或没有条件为真,我需要一个返回行的查询,即 NOT a XOR b

编辑: 示例:

username | mac
john     | 11:22:33:44:55:66
john     | 11:22:33:44:55:67
doe      | 11:22:33:44:55:68

如果我查询:

username = john, mac = 11:22:33:44:55:66                     -> true, 1 whatever...
username = john, mac != 11:22:33:44:55:66                    -> 0, null or nothing...
username = jane, mac = no matter what except john's or doe's -> true, 1 whatever...
username = jane, mac = john's or doe's                       ->  0, null or nothing...

在两种情况下我需要true

  1. 该(用户,mac)组合有一行。没有行
  2. 对于该用户并且该 mac 没有行

到目前为止,我得到了这个:

SELECT yes
FROM
  (SELECT 1 AS yes) AS dummy
LEFT JOIN maclist ON (username = 'user'
                      OR mac = '11:22:33:44:55:66')
WHERE ((username = 'user'
        AND mac = '11:22:33:44:55:66')
       OR (username IS NULL
           AND mac IS NULL);

它可以工作,但在我看来就像一个 hack,而且随着数据库的增长,我也不知道这个查询的性能。 我的问题是是否有更好的方法来做到这一点。

【问题讨论】:

  • 请您明确写出您需要结果为true的条件吗?也许是一个逻辑表?或者只是导致 true 的场景的项目符号列表?
  • 两个用户可以共享同一个mac地址吗? (在现实生活中,多个用户从多个 mac 地址连接,mac 地址有多个用户从它们连接。)
  • True,因此您可以为每个用户添加多个 MAC。表中的约束在两个字段上,所以用户不是唯一的,如果用户是NULL,这算作黑名单 MAC(至少这是想法)
  • 那么,在两个条件下你只想要true1. 该(用户,mac)组合有一行。 2. 没有该用户的行并且该 mac 没有行?
  • 是的。这就是想法。对不起!英语不是我的母语。

标签: sql postgresql select


【解决方案1】:

编辑:我稍微修改了逻辑,我认为它符合您的要求

以下代码将在两种情况下返回1...

  • usermac 有一行
  • 该用户有0并且mac0

在所有其他条件下,查询返回0

  • user 对应的行,但没有一个对应mac
  • 有对应mac 的行,但没有对应user 的行


SELECT
  CASE WHEN COUNT(*) = 0                                THEN 1
       WHEN SUM(CASE WHEN mac = '11:22:33:44:55:66'
                      AND user = 'user' THEN 1
                                        ELSE 0 END) = 1 THEN 1
                                                        ELSE 0
  END
FROM
  maclist
WHERE
     username = 'user'
  OR mac      = '11:22:33:44:55:66'

【讨论】:

  • 我会使用你的解决方案,但我会用 COALESCE(SUM(CAST(mac = '11:22:33:44:55:66' AND username = 'user' AS integer)), 1) 替换奇怪的嵌套 CASE ... WHEN,这似乎更快。
【解决方案2】:

您可以将查询简单地编写为:

prepare query_mac(text, macaddr) as
select exists (select 1 from maclist where username = $1 and mac = $2)
       or (not exists (select 1 from maclist where username = $1)
           and not exists (select 1 from maclist where mac = $2)
          );

PostgreSQL 将分别评估三个查询中的每一个,并为前两个使用(username,mac) 上的唯一索引。如果需要,可以在(mac) 上为第三个添加索引。

【讨论】:

    【解决方案3】:

    当涉及OR 时,几乎所有数据库都不会使用索引,因此请避免使用连接中的那些。

    编辑:

    看来您确实在进行两个单独的查询:

    • 分配给谁的 mac(如果有)
    • 分配给用户的 mac(如果有)

    所以联合不会像子选择的集合那样简单。使用单独的查询将信息拆分为自己的简单查询,然后将它们合并为一个:

    select
        (select username from maclist
         where mac = '11.22:33:44:55:66') as mac_assigned_to,
        (select mac from maclist
         where username = 'user') as user_assigned_to
    

    如果当前没有分配任何内容,则两列可能都有空值,否则它们将显示为每个关注点分配的内容。

    【讨论】:

    • 我喜欢 UNION 的想法。我没有猜到,但我需要否定的异或。所以如果其他人有这个 MAC 我一定不能使用它。
    • @core1024 - 最后一条语句是什么意思? So if other have this MAC i can't use it.
    • 错误:用作表达式的子查询返回了不止一行你根本没有明白。 araqnid 和 Dems 的回答做了应该做的事情。
    猜你喜欢
    • 2023-01-10
    • 2012-03-26
    • 2012-03-26
    • 1970-01-01
    • 1970-01-01
    • 2013-01-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多