【问题标题】:self join and ignore nulls on one table / side only自联接并仅在一个表/一侧忽略空值
【发布时间】:2015-01-21 06:07:49
【问题描述】:

我有 2 张桌子。

一张表定义客户关系:

    CREATE TABLE IF NOT EXISTS `cust_connections` (
    `id` int(11) NOT NULL,
      `short_name` char(15) COLLATE utf8_unicode_ci NOT NULL,
      `source_fnn` char(10) COLLATE utf8_unicode_ci NOT NULL,
      `dest_fnn` char(10) COLLATE utf8_unicode_ci NOT NULL,
      `service_type` char(32) COLLATE utf8_unicode_ci NOT NULL,
      `ladder_side` char(10) COLLATE utf8_unicode_ci NOT NULL
    ) ENGINE=InnoDB AUTO_INCREMENT=18 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

    INSERT INTO `cust_connections` (`id`, `short_name`, `source_fnn`, `dest_fnn`, `service_type`, `ladder_side`) VALUES
    (1, 'cust1', 'N2843453A', '', 'HD_300_Connect', 'src only'),
    (2, 'cust2', '', 'N2843600A', 'HD_300_Connect', 'dest only'),
    (3, 'cust3', 'N2720257O', 'N2731164O', 'DVB25_188byte', 'both'),
    (4, 'cust4', 'N27xxx7O', 'N2731164O', 'DVB25_188byte', 'src ukn'),
    (5, 'cust4', 'N27xxx7O', '', 'DVB25_188byte', 'ukn +blk');

ALTER TABLE `cust_connections`
 ADD PRIMARY KEY (`id`);

ALTER TABLE `cust_connections`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT,AUTO_INCREMENT=18;

另一张表定义设备:

     CREATE TABLE IF NOT EXISTS `cust_port` (
    `id` smallint(11) NOT NULL,
      `system_name` char(32) COLLATE utf8_unicode_ci NOT NULL,
      `slot_no` char(2) COLLATE utf8_unicode_ci NOT NULL,
      `port_no` char(2) COLLATE utf8_unicode_ci NOT NULL,
      `port_fnn` char(9) COLLATE utf8_unicode_ci NOT NULL
    ) ENGINE=InnoDB AUTO_INCREMENT=29 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

    INSERT INTO `cust_port` (`id`, `system_name`, `slot_no`, `port_no`, `port_fnn`) VALUES
    (1, '01-06C2:source', '7', '1', 'N2843453A'),
    (2, '01-27B4:dest', '1', '2', 'N2843600A'),
    (3, '01-27B6:source+dst', '17', '3', 'N2720257O'),
    (4, '01-27B6:dst+src', '17', '3', 'N2731164O'),
    (5, '01-32C6:dup_fnn1', '1', '2', 'N2845070O'),
    (26, '01-32C6:dup_fnn2', '1', '3', 'N2845070O'),
    (27, '01-32D6:no_fnn', '1', '4', ''),
    (28, '01-32D6:diff_fnn', '1', '4', 'x123456');

ALTER TABLE `cust_port`
 ADD PRIMARY KEY (`id`);

ALTER TABLE `cust_port`
MODIFY `id` smallint(11) NOT NULL AUTO_INCREMENT,AUTO_INCREMENT=29;

SQl 结果是:

 cc_id  short_name  source_fnn  dest_fnn    service_type    ladder_side     src_system_name     src_slot_no     src_port_no     src_port_fnn    dst_system_name     dst_slot_no     dst_port_no     dst_port_fnn    
1       cust1       N2843453A               HD_300_Connect  src only       01-06C2:source         7                  1          N2843453A       01-32D6:no_fnn       1              4   
2       cust2       N2843600A               HD_300_Connect  dest only      01-32D6:no_fnn         1                  4                          01-27B4:dest         1              2               N2843600A
3       cust3       N2720257O   N2731164O   DVB25_188byte   both           01-27B6:source+dst    17                  3          N2720257O       01-27B6:dst+src      17             3               N2731164O
4       cust4       N27xxx7O    N2731164O   DVB25_188byte   src ukn        NULL                   NULL               NULL       NULL            01-27B6:dst+src      17             3               N2731164O
5       cust4       N27xxx7O                DVB25_188byte   ukn +blk       NULL                   NULL               NULL       NULL            01-32D6:no_fnn       1              4

我正在对两个表进行连接。

问题是,如果 port_fnn 为空,我想排除该行,但如果任何客户源或目标 fnn 为空,则显示该行。

我正在做一个左(自我)加入来匹配源和目标 fnn 与设备 fnn。不幸的是,我的客户 fnns 必须能够具有空值。

如果设备表中没有空值,我的查询效果很好。 我的查询是:

SELECT 
cc.id AS cc_id, short_name,source_fnn, dest_fnn, service_type,ladder_side,
src.system_name AS src_system_name,
src.slot_no AS src_slot_no,
src.port_no AS src_port_no,
src.port_fnn AS src_port_fnn,
dst.system_name AS dst_system_name,
dst.slot_no AS dst_slot_no, 
dst.port_no AS dst_port_no,
dst.port_fnn AS dst_port_fnn
FROM cust_connections cc 
 LEFT JOIN cust_port src on cc.source_fnn=src.port_fnn 
 LEFT JOIN cust_port dst on cc.dest_fnn=dst.port_fnn 

在我的结果集中: 第 1 行 - 仅具有源 fnn。我希望目标字段的结果为空,即:

cc_id   short_name  source_fnn  dest_fnn    service_type    ladder_side     src_system_name     src_slot_no     src_port_no     src_port_fnn    dst_system_name     dst_slot_no     dst_port_no     dst_port_fnn    
1       cust1       N2843453A               HD_300_Connect  src only       01-06C2:source         7                  1          N2843453A       NULL                NULL             NULL            NULL

查询正在检测一个空 fnn 并使用没有关联 fnn 的设备进行填充。即:01-32D6:no_fnn.

第 2 行的 source_system_name 和第 5 行的 dst_system_name 出现同样的问题。

【问题讨论】:

  • 你想从设备表中排除 port_fnn 吗?
  • 这个例子你想要的结果是什么?并且您还可以在查询和数据之间保持表名相同吗?有点混乱
  • @Nigel,我刚刚看了你想要的结果。它与您的查询结果相同,除了一件事,在最后一行,您说您不想要任何数据的行中的数据。又名equip4 4/2 这是你说你希望它不显示的行,因为它有一个空的port_fnn
  • @philipxy 这就是我在评论中所说的,它也是equip1 不是equip4
  • 嗨,对不起,我在解释这个问题时遇到了很多麻烦。带有 55555 的行“cust_port”行与我的查询一起正常工作,因为 5555 不在任何客户连接行中。带有“null” fnn 的“cust_port”行是唯一的问题。如果“cust_port.port_fnn”包含空值,我希望该行不产生任何输出。

标签: php mysql null left-join string


【解决方案1】:

''(空字符串)不为 NULL。 (哪个 sqlfiddle 输出为“(null)”。)

在文本中,不要写“NULL”或“”或“null”或“(null)”来引用您最初在问题中所做的空字符串。清楚什么是'',什么是NULL。

'' = '' 但 NULL NULL。因此,当相等测试涉及“”时,您的 LEFT JOIN 会在列 cc.source_fnn 和 src.port_fnn 之间以及列 cc.dest_fnn 和 cust_port port_fnn 之间找到匹配项。但您不希望 LEFT JOIN 匹配这些行。

你可以这样说:

  1. 声明所有 _fnn 列可为空,即为 NULL(默认值)而不是 NOT NULL,并在您现在使用 ''(空字符串)的表中使用 NULL。那么你的查询就会给出正确的答案!

  2. 需要 port_fnn '':

    FROM cust_connections cc
    LEFT JOIN cust_port src
    ON cc.source_fnn=src.port_fnn AND cc.source_fnn <> ''
    LEFT JOIN cust_port  dst
    ON cc.dest_fnn=dst.port_fnn AND cc.dest_fnn <> '';
    
  3. 在您 LEFT JOIN 之前从 cust_port 中删除这些行:

    FROM cust_connections cc
    LEFT JOIN
        (SELECT * FROM cust_port WHERE port_fnn <> ''
        ) src
    ON cc.source_fnn=src.port_fnn
    LEFT JOIN
        (SELECT * FROM cust_port WHERE port_fnn <> ''
        ) dst
    ON cc.dest_fnn=dst.port_fnn;
    

Sqlfiddle for 1 using NULLfor 2 & 3 using ''。这些添加了第二个 cust_port 行,其中缺少 port_fnn,以表明上述给出了正确的结果。您与 '' 一起使用的查询错误地从中生成了其他虚假行。

如果您希望 LEFT JOIN 中的 NULL 在输出中显示为空字符串,那么您可以对这些列使用 IFNULL:

IFNULL(dst.port_fnn,'') AS dst_port_fnn

【讨论】:

  • 非常感谢 - 这行得通。我已经选择了 2 并且还添加了 IFNULL(dst.port_fnn,'') AS dst_port_fnn 以涵盖 NULL 和 '' 可能性。
【解决方案2】:

您可以添加 where 子句,例如:

where  (cc.source_fnn is null or cc.dest_fnn is null)
       or (src.port_fnn is not null or dst.port_fnn is not null)

现在它将始终显示带有空 source_fnndest_fnn 的行。当两者都被填充时,它将过滤掉匹配的port_fnn 列为空的行。

因此,您将获得缺少外键的行,但会抑制外键引用具有空列的行的行。至少这就是我认为你正在寻找的东西。如果不是,请澄清您的问题。

【讨论】:

  • 谢谢 - 我尝试了同样的结果。也试过FROM cust_connections cc LEFT JOIN cust_port src on cc.source_fnn=src.port_fnn and src.port_fnn is not null LEFT JOIN cust_port dst on cc.dest_fnn=dst.port_fnn and dst.port_fnn is not null
  • @Nigel & Andomar 对于输入中的 NULL:它已经“总是显示带有空 source_fnn 或 dest_fnn 的行”,因为它们位于 L JOIN 的左侧&“过滤掉匹配 port_fnn 的行列是空的”,因为它们位于 L JOIN 的右侧,并且 NULL 无法匹配。即查询是正确的。但是 Nigel 的“null”意味着 空字符串!如果有 0 或 1 个空 port_fnns,那么我们需要所有行。否则,如果 source_fnn 和 dest_fnn 在非空时表现得像外键,那么我们可以按 cc.id 分组 src.id MIN(src.id) AND dst.id MIN(dst.id)。 (然后我们为每个 DRapp 的答案设置 NULL 列。)
【解决方案3】:

我认为您可以使用 IF() 为每个相应的源/目标列屏蔽名称和端口。大多数时候人们会尝试做一些事情来防止空值并显示类似空字符串的东西......相反,你想要相反......如果“port_fnn”为空,你想要隐藏这些元素。

所以我为每列做了一个 IF( 表达式,如果为真,则为结果,如果为假,则为结果)。因此,如果 port_fnn 为 NULL,则显示 null 作为结果,否则返回列的任何内容(系统名称、插槽、端口等)

SELECT 
      cc.id AS cc_id, 
      short_name,
      source_fnn, 
      dest_fnn, 
      service_type,ladder_side,
      if( src.port_fnn = '', NULL, src.system_name ) AS src_system_name,
      if( src.port_fnn = '', NULL, src.slot_no ) AS src_slot_no,
      if( src.port_fnn = '', NULL, src.port_no ) AS src_port_no,
      if( src.port_fnn = '', NULL, src.port_fnn ) AS src_port_fnn,
      if( dst.port_fnn = '', NULL, dst.system_name ) AS dst_system_name,
      if( dst.port_fnn = '', NULL, dst.slot_no ) AS dst_slot_no, 
      if( dst.port_fnn = '', NULL, dst.port_no ) AS dst_port_no,
      if( dst.port_fnn = '', NULL, dst.port_fnn ) AS dst_port_fnn
   FROM 
      cust_connections cc 
          LEFT JOIN cust_port src 
             on cc.source_fnn = src.port_fnn
          LEFT JOIN cust_port dst 
             on cc.dest_fnn = dst.port_fnn 

我调整了上面的查询以处理您的数据... NULL 与空字符串不同。我将您的表和示例数据复制到 SQL Fiddle,然后是上述查询。它似乎分别为源和目标显示 NULLS。

SQLFiddle per your example structure and data

【讨论】:

  • 您好,我认为您走在正确的轨道上...虽然此查询不起作用,但当客户设备为空时,它仍然显示设备。语句 src.port_ffn 中的轻微错字也应该是 src.port_fnn
  • @Nigel,修改并创建了您的表/数据结果的 SQLFiddle。
  • 您的解决方案也有效,但我选择了 philipxy 解决方案,因为它实现起来更简单。非常感谢您的意见,非常感谢
  • @Nigel & DRapp:当空字符串用于不存在的 fnn 时,这不起作用。 (我的确实如此。)因为它不排除具有空 fnn 的 cust_port 行参与连接。您可以通过添加第二个带有空 port_fnn 的 cust_port 行来查看这一点。 Sqlfiddle。它适用于 NULL,因为那时原始查询有效。因为 OUTER JOIN 与那些行不匹配。所以这个答案只是告诉如何为 NULL 获取空字符串。 Nigel:你应该在你的测试中添加这样一行。
  • @Nigel & DRapp: SELECT DISTINCT 消除了对多个 cust_port 行的重复匹配,其中 port_fnn 为空。
【解决方案4】:

[这个答案是在较早版本的 qeustion 的上下文中,示例输入清楚地描述了空字符串,即 '' 为 NULL//null,因此看起来 "" 值是 NULL。如果他们是 NULL 这个答案适用。 Sqlfiddle]

错误的“期望输出”

“所需输出”中的equip4 1/2 是错误的,您希望通过4444 从端口id 2 获得equip1 1/2。更正后,所需的输出就是您在其他地方描述的(不清楚)。

NULL port_fnn 不是问题

空 port_fnn 没有从查询中将任何内容放入表中。 LEFT JOIN 永远不会匹配它。

你想要什么行?

你不清楚。您唯一没有解决的行是非 NULL source_fnn 或 dest_fnn 没有匹配的 port_fn。然后在 LEFT JOIN 输出中与 NULL port_fnn 信息配对。

如果您不想要这些行,那就太奇怪了。您不仅没有 cust_connection 的行,而且扩展信息的另一半也会被丢弃。也许您只会丢弃 source 和 dest 都为非 NULL 且匹配的行。还是很奇怪。你必须告诉我们你是否想要这些行。大概您想要它们,因为它们在您的查询中。

如果每个 source_fnn 和 dest_fnn 都有一个匹配的 port_fnn,即从 source_fnn 和 dest_fnn 到 port_fnn 有外键,那么这永远不会发生,并且您的查询是正确的。

所以您的查询似乎没问题。

您似乎错误地猜测 NULL port_fnn 无法解释为什么它与您错误的预期输出不同。

【讨论】:

  • 对不起,如果我不清楚。我已经更新了这个问题。 customer_connections 表可以有 3 个选项。仅源 FNN。仅目标 FNN 或源 FNN 和目标 FNN。这些 FNN 必须匹配 Equipment FNN 的 Equipment FNN 可以是 FNN 或 null。如果设备 fnn 为空。设备名称不应出现在 customer_connection 表的空条目中。对于当前的查询,它确实如此。亲切的问候奈杰尔
猜你喜欢
  • 2014-09-11
  • 1970-01-01
  • 2019-09-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-07-30
  • 2014-05-24
相关资源
最近更新 更多