我有一个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_id
与substances.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”。
我已经完成了以下操作-为我提供了想要的输出-但存在缺陷(请参见下面的“问题”部分)。
选择“显示”表中的所有记录:SELECT label FROM displays ORDER BY label ASC
为当前显示的物质选择所有display_substances.display_id
:SELECT display_id FROM display_substances WHERE substance_id = 1
(假设1是当前物质ID)。将ID存储在名为$display_substances
遍历(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)
}
如果in_array()
条件为true,则我进行查询以选择“ display_substances”的相应行:SELECT value FROM display_substances WHERE display_id = $display['id'] AND substance_id = 1
$output
变量将缓冲所有数据,然后稍后将其输出到HTML表中。我得到的输出正是我想要的。
尽管输出是正确的,但我想作为1个查询(如果可能的话)来完成全部操作,因为我需要添加要通过displays.label
或display_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”。
答案 0 :(得分:2)
我的理解是,您需要以下条件:
我相信这应该对您有用:
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%的解决方案。
第一次,这个问题只是一个一直在发生的变化。
会在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。现在的逻辑如下:
如果用户正在第2列(display_substances.value
)上进行搜索:SELECT display_id FROM display_substances WHERE value LIKE '% Search term column 2 %'
。将此存储为数组$ds
。
从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
获取与正在查看的物质关联的所有display_id's
:SELECT display_id FROM display_substances WHERE substance_id = 1
按照原始问题的要点(3) 循环。
结果是页面在<2秒内加载,每一列都是可搜索的。
答案中给出的SQL查询最多只花了大约15-20秒的时间来执行,却从未给出全部400行。
如果有人可以对此进行改进或使用纯SQL解决方案,请发布。