自我加入并仅忽略一个 table / 侧的空值

self join and ignore nulls on one table / side only

我有 2 个 table。

一个 table 定义客户连接:

    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;

其他table定义装备:

     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

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

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

我正在进行左(自)连接以匹配源和目标 fnn 与设备 fnn。不幸的是,我的客户 fnns 必须能够有空值。

如果设备 table 中没有空值,我的查询会很好用。 我的查询是:

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 出现同样的问题。

[这个答案是在早期版本的 qeustion 的上下文中,其中示例输入不清楚地将空字符串 ie '' 描述为 NULL/< NULL >/null 所以它看起来像 "" 值是 NULL .如果他们为 NULL,则应用此答案。 Sqlfiddle]

错误"desired output"

你的 "desired output" 中的 equip4 1/2 是一个错误,你想要 equip1 1/2 来自端口 id 2 通过 4444。在你更正之后,所需的输出就是你在别处描述的(不清楚) .

NULL port_fnn不是问题

null port_fnn 没有将任何内容放入查询中的 table。 LEFT JOIN 永远不会匹配它。

你想要什么行?

你不清楚。唯一没有处理的行是非 NULL source_fnn 或 dest_fnn 没有匹配的 port_fn。然后在 LEFT JOIN 输出中它与 NULL port_fnn info.

配对

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

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

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

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

您可以添加一个 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 列为空的行。

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

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

所以我为每一列都做了一个 IF(表达式,结果为真,结果为假)。因此,如果 port_fnn 为 NULL,则结果显示为 null,否则 return 无论列是什么(系统名称、插槽、端口等)

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

''(空字符串)不为 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 NULL and for 2 & 3 using ''。这些添加了第二个 cust_port 行,缺少 port_fnn 以表明以上给出了正确的结果。您与 '' 一起使用的查询错误地从中生成了其他虚假行。

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

IFNULL(dst.port_fnn,'') AS dst_port_fnn