自联接并仅在一个表/侧忽略空值

时间:2015-01-21 06:07:50

标签: php mysql null left-join string

我有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为null,我想排除该行,但如果任何客户源或目标fnns为空,则显示该行。

我正在进行左(自)联接以匹配源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

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

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

4 个答案:

答案 0 :(得分:1)

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

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

答案 1 :(得分:1)

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

所以我为每一列做了一个IF(表达式,结果如果为true,结果为false)。因此,如果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

答案 2 :(得分: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;
    
  4. Sqlfiddle for 1 using NULLfor 2 & 3 using ''。它们添加了第二个cust_port行,缺少port_fnn以显示上面给出了正确的结果。您的查询与''一起使用时会错误地生成其他虚假行。

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

    IFNULL(dst.port_fnn,'')AS dst_port_fnn

答案 3 :(得分:0)

[这个答案是在qeustion的早期版本的上下文中,其中示例输入不清楚地描述了空字符串,即''为NULL /&lt; NULL&gt; / null所以看起来“”值为NULL。如果它们为NULL,则应用此答案。 Sqlfiddle]

错误的“期望输出”

“所需输出”中的equip4 1/2是一个错误,您需要从端口ID 2通过4444装备1 1/2。在您纠正之后,所需的输出就是您在其他地方描述的(不清楚)。

NULL port_fnn不是问题

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

您想要哪些行?

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

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

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

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

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