PHP / MySQL-即使在另一个表中不存在记录,在JOIN查询上也显示行

时间:2018-10-22 15:48:28

标签: php mysql

我有一个PHP / MySQL应用程序,该应用程序连接到包含2个名为'displays''display_substances'的表的数据库。这些表的结构如下:

mysql> DESCRIBE displays;
+----------+----------------------+------+-----+---------+----------------+
| Field    | Type                 | Null | Key | Default | Extra          |
+----------+----------------------+------+-----+---------+----------------+
| id       | smallint(5) unsigned | NO   | PRI | NULL    | auto_increment |
| label    | varchar(255)         | NO   |     | NULL    |                |
+----------+----------------------+------+-----+---------+----------------+

mysql> DESCRIBE display_substances;
+--------------+-----------------------+------+-----+---------+----------------+
| Field        | Type                  | Null | Key | Default | Extra          |
+--------------+-----------------------+------+-----+---------+----------------+
| id           | mediumint(8) unsigned | NO   | PRI | NULL    | auto_increment |
| display_id   | smallint(5) unsigned  | NO   | MUL | NULL    |                |
| substance_id | mediumint(8) unsigned | NO   | MUL | NULL    |                |
| value        | text                  | NO   |     | NULL    |                |
+--------------+-----------------------+------+-----+---------+----------------+

还有一个'substances'表,外键display_substances.substance_idsubstances.id关联。

'displays'表正好包含400行,'display_substances'表大约有150万行。

我要做的是将所有400 'displays.label'输出到HTML表中,然后在第二列中显示'display_substances.value'。由于该页面一次仅显示一种物质,因此还需要基于'display_substances.substance_id'

我遇到的问题是,只有我们有适用于'display_substances'的数据时,记录才存在于'displays'中。但是,输出必须显示'displays'中的 all 条记录,然后在'display_substances'中没有对应记录的所有内容旁边放置文本“ Not Listed”。

我已经完成了以下操作-为我提供了想要的输出-但存在缺陷(请参见下面的“问题”部分)。

  1. 选择“显示”表中的所有记录:SELECT label FROM displays ORDER BY label ASC

  2. 为当前显示的物质选择所有display_substances.display_idSELECT display_id FROM display_substances WHERE substance_id = 1(假设1是当前物质ID)。将ID存储在名为$display_substances

  3. 的数组中
  4. 遍历(1),然后使用in_array()查看是否存在(2):

foreach ($displays as $display) { // step (1)
    $display_substances = // See step (2)
    if (in_array($display['id'], $display_substances)) { // step (3)
        $display_value = // See step (4)
    } else {
        $display_value = 'Not listed';
    }
    $output[] = ['display_label' => $display['label'], 'display_value' => $display_value]; // See step (5)
}
  1. 如果in_array()条件为true,则我进行查询以选择“ display_substances”的相应行:SELECT value FROM display_substances WHERE display_id = $display['id'] AND substance_id = 1

  2. $output变量将缓冲所有数据,然后稍后将其输出到HTML表中。我得到的输出正是我想要的。

问题:

尽管输出是正确的,但我想作为1个查询(如果可能的话)来完成全部操作,因为我需要添加要通过displays.labeldisplay_substances.value-或两者结合进行搜索的功能。第一部分相当琐碎,因为我可以将(1)中的查询修改为:

SELECT label FROM displays WHERE label LIKE '% Foo %' ORDER BY label ASC

但是,这不会使display_substances.value可搜索,因为到了执行步骤(3)时,我们只处理display_substances的一行整个桌子。不过,我看不到如何写不同的东西,因为我们需要知道该表中存在哪些关于所装载物质的记录。

我还编写了以下查询-但这不会有效,因为它会丢失“未列出”的任何内容:

SELECT displays.label, display_substances.`value` FROM displays JOIN display_substances ON displays.id = display_substances.display_id WHERE display_substances.substance_id = 1

任何帮助表示赞赏。我已经读过How do I get the records from the left side of a join that do not exist in the joined table?,但没有帮助。

为了进一步说明

假设display_substances中有120行对应于物质ID 1(WHERE display_substances.substance_id = 1)。 查询的输出应始终有400行。在此示例中,120应该在其旁边有display_substances.value,而280应该具有文本“ Not Listed”。

4 个答案:

答案 0 :(得分:2)

我的理解是,您需要以下条件:

  • 您的页面按物质ID限制结果
  • 您想要每行一种物质
  • 如果显示的内容中没有任何物质,它们仍应在页面上显示为“未列出”作为物质值

我相信这应该对您有用:

SELECT
    d.id AS display_id,
    d.label AS display_label,
    IFNULL(ds.value, 'Not Listed') AS substance_value
FROM displays AS d
LEFT JOIN display_substances AS ds ON (ds.display_id = d.id)
WHERE ds.substance_id = 1 OR ds.substance_id IS NULL;

答案 1 :(得分:1)

您需要一个左联接和一个group_concat来获得左表上的所有记录以及group by。

但是请记住,group_concat有一个限制,因此您可能不会获得所有关联的记录,因为它通常用于较小的字段,但是由于您的值有一个“文本”字段,因此很可能达到限制

反正这就是查询

SELECT d.*, GROUP_CONCAT(ds.value) `substances`
FROM displays `d`
LEFT JOIN display_substances `ds` ON `d`.`id` = `ds`.`display_id`
GROUP BY `d`.`id`

如果我理解正确,那么类似的事情可能会起作用

SELECT d.*, IFNULL((SELECT GROUP_CONCAT(value) FROM display_substances `ds` WHERE `ds`.`display_id` = `d`.`id` GROUP BY `ds`.`display_id`), 'Not Listed') `substances`
FROM displays `d`

您可以更新位置并添加AND substance_id = 1

答案 2 :(得分:1)

我已经意识到Ramy拥有大约98%的解决方案。

第一次,这个问题只是一个一直在发生的变化。

search for 'left outer join with where clause' -- that address the problem. One example is this question.

会在SO上找到其他答案

最终,您将具有多对多分辨率表(display_substances),用于解析物质和显示之间的多对多关系。您只是从两个父表之一中寻找外部联接,而且还要求您按特定物质过滤结果。

SELECT
    d.id AS display_id,
    d.label AS display_label,
    IFNULL(ds.value, 'Not Listed') AS substance_value
FROM displays AS d
LEFT JOIN display_substances AS ds ON (ds.display_id = d.id AND ds.substance_id = 1); 

此查询不会生成“未列出”的值,但会为没有相应的display_substance值的那些显示行生成NULL列。您可以使用ahmad演示的IF_NULL()函数对其进行修饰,但是当您使用PHP遍历结果集时,就可以像在过程循环中一样轻松地处理它,以获取结果。

答案 3 :(得分:0)

我正在发布用作解决方案的内容。我认为不可能在MySQL中作为一个查询来执行此操作。有几个人回答,但没有一个答案起作用。

为了进一步澄清-尽管从问题上显而易见-应用程序中的输出是一个包含2列的表。这些列的第一列应包含displays中的所有400行:

displays.label    |  display_substances.value
------------------|--------------------------
Display 1         
------------------|--------------------------
...
------------------|--------------------------
Display 400   
------------------|--------------------------

这是相当琐碎的,因为此时它只是SELECT * FROM displays

当我们想用display_substances.value填充表的第二列时,挑战就开始了。给定物质(假设物质ID为1)的数据可能如下所示:

id   |   display_id   |   substance_id  |  value
-----|----------------|-----------------|-------------
206  |      1         |        1            Foo
-----|----------------|-----------------|-------------
361  |      3         |        1            Bar
-----|----------------|-----------------|-------------
555  |      5         |        1            Baz
-----|----------------|-----------------|-------------

问题:在这种情况下,我们只有3条关于物质ID 1的记录。因此,如果我们执行JOIN查询,它将仅返回3行。但是我们在应用程序中显示的表需要显示displays中的所有400行,并将文本“未列出”放在display_substances中没有对应行的任何行上。因此,在上面的示例中,当遇到display_id 2、4、6 ... 400时,应该说“未列出”(因为我们只有三个display_substances.display_id [1,3,5]的数据对于物质ID 1)。

两列也都需要搜索。

我的解决方案

我认为不可能在MySQL中执行此操作,因此我求助于使用PHP。现在的逻辑如下:

  1. 如果用户正在第2列(display_substances.value)上进行搜索:SELECT display_id FROM display_substances WHERE value LIKE '% Search term column 2 %'。将此存储为数组$ds

  2. displays中选择所有400条记录。如果用户正在第1列(displays.label)上进行搜索,则该搜索必须构成查询的一部分:SELECT * FROM displays WHERE label LIKE '% Search term column 1 %'。至关重要-如果步骤(1)中的$ds数组不为空,则以下内容必须成为查询的一部分:WHERE displays.id IN (2, 4, 6...400)。将此存储为数组$displays

  3. 获取与正在查看的物质关联的所有display_id'sSELECT display_id FROM display_substances WHERE substance_id = 1

  4. 按照原始问题的要点(3) 循环。

结果是页面在<2秒内加载,每一列都是可搜索的。

答案中给出的SQL查询最多只花了大约15-20秒的时间来执行,却从未给出全部400行。

如果有人可以对此进行改进或使用纯SQL解决方案,请发布。