列出表A中的记录,表B中只有一个关系,并且在表C中具有关系

时间:2015-01-14 13:45:04

标签: mysql

我有3张桌子:

ITEMS       ITEM_FILES_MAP     FILES
id          id                 id
name        item_id            filename
in_trash    file_id

FILES通过ITEM_FILES_MAP表与ITEMS建立了一对多的关系。

我需要一个select查询,它通过以下标准返回文件列表:

  • 仅返回与in_trash = 1
  • 项目相关的文件
  • 避免使用与in_trash = 0
  • 项目相关的文件

示例:

ITEMS
id     name     in_trash
1      Item A   0
2      Item B   0
3      Item C   1
4      Item D   1

FILES
id     filename
1      File A
2      File B
3      File C
4      File D
5      File E

ITEM_FILES_MAP
id     item_id  file_id
1      1        2
2      1        3
3      2        1
4      3        2
5      3        4
6      4        3
7      4        4

期望的结果: 返回文件D(id 4)。

文件B,C和D(FILES表中的id 2,3,4)将被返回,但由于文件B和C与in_trash = 0的项目相关,因此不会列出它们。

如果您想测试解决方案,这是一个示例转储:

CREATE TABLE `files` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `filename` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `files` (`id`, `filename`)
VALUES
    (1,'File A'),
    (2,'File B'),
    (3,'File C'),
    (4,'File D'),
    (5,'File E');

CREATE TABLE `item_files_map` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `item_id` int(11) DEFAULT NULL,
  `file_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `item_files_map` (`id`, `item_id`, `file_id`)
VALUES
    (1,1,2),
    (2,1,3),
    (3,2,1),
    (4,3,2),
    (5,3,4),
    (6,4,3),
    (7,4,4);

CREATE TABLE `items` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `in_trash` tinyint(1) DEFAULT '0',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `items` (`id`, `name`, `in_trash`)
VALUES
    (1,'Item A',0),
    (2,'Item B',0),
    (3,'Item C',1),
    (4,'Item D',1);

2 个答案:

答案 0 :(得分:0)

我没有在mysql中测试,但你可以这样做:

SELECT filename FROM
 (SELECT filename, sum(in_trash) AS s, count(*) AS c 
  FROM items, files, item_files_map 
  WHERE items.id = item_files_map.item_id AND files.id = item_files_map.file_id 
  GROUP BY filename) sub 
WHERE s = c

子查询为每个文件名计算引用它的项目数和垃圾箱中的项目数。对于您的示例,它返回:

"D"   2   2
"B"   1   2
"C"   1   2
"A"   0   1

如果这些计数相同,则仅在垃圾项目参考中。

编辑:按照axiac的建议,这是查询:

SELECT filename, files.id, sum(in_trash) AS s, count(*) AS c 
FROM items, files, item_files_map 
WHERE items.id = item_files_map.item_id AND files.id = item_files_map.file_id 
GROUP BY files.id
HAVING s = c

答案 1 :(得分:0)

制剂

首先,确保您在表UNIQUE INDEX上的字段item_idfile_id(按此顺序)中有item_files_map。无论您运行什么查询,如果它包含此表,索引将使事物飞行而不是爬行。 但是,在某些查询中,具有相反顺序的字段的索引会有所帮助,但对于此任务,我们需要按所示顺序使用它们。

ALTER TABLE item_files_map
ADD UNIQUE INDEX item_file_id(`item_id`, `file_id`);

另外,请确保INDEX items in_trash上有ALTER TABLE items ADD INDEX (`in_trash`);

1

对于大型表格,如果0in_trash=1值之间的比率介于0.05和20之间,则MySQL可能会忽略它(如果没有使用的值小于5%的行。)

可能具有in_trash=0的项目比具有items的项目要少得多(反之亦然),这将说服MySQL使用该表PK的一个实例的索引因为索引从检查中删除了很多行。

更多,因为查询只使用此表中的字段in_trash# Query #1 SELECT DISTINCT f.id, f.filename FROM items iit1 INNER JOIN item_files_map ifm1 ON iit1.id = ifm1.item_id INNER JOIN files f ON f.id = ifm1.file_id WHERE iit1.in_trash = 1 AND ifm1.file_id NOT IN ( SELECT ff.id FROM files ff INNER JOIN item_files_map ifm0 ON ff.id = ifm0.file_id INNER JOIN items iit0 ON iit0.id = ifm0.item_id WHERE iit0.in_trash = 0 ); ,MySQL将使用索引获取所需的信息,而不会读取表数据。由于索引小于表数据,因此从存储中读取较少的字节可以提高执行速度。

查询,首次尝试(遵循所有要求)

执行所需操作的查询是:

item_files_map

通过减少查询来改进查询

如果您完全确定表file_id不包含孤立files值(即id列中找不到的值,则此查询不如可以获得的那样好并且可以改进。 1}}。files)。这不应该在设计良好的应用程序上发生,数据库可以使用FOREIGN KEY constraints(仅限InnoDB)帮助您避免此类情况。

假设满足这个条件,我们可以从内部查询中删除表# Query #2 SELECT DISTINCT f.id, f.filename FROM items iit1 INNER JOIN item_files_map ifm1 ON iit1.id = ifm1.item_id INNER JOIN files f ON f.id = ifm1.file_id WHERE iit1.in_trash = 1 AND ifm1.file_id NOT IN ( SELECT ifm0.file_id FROM item_files_map ifm0 INNER JOIN items iit0 ON iit0.id = ifm0.item_id WHERE iit0.in_trash = 0 ); ,使其更简单,更快捷:

file

此查询将生成正确的结果。

最终查询(忽略一些要求但产生正确的结果)

可以通过仅选择id# Query #3 SELECT DISTINCT ifm1.file_id FROM items iit1 INNER JOIN item_files_map ifm1 ON iit1.id = ifm1.item_id WHERE iit1.in_trash = 1 AND ifm1.file_id NOT IN ( SELECT ifm0.file_id FROM item_files_map ifm0 INNER JOIN items iit0 ON iit0.id = ifm0.item_id WHERE iit0.in_trash = 0 ); 来完成另一项优化,并暂时删除文件名,将运行另一个查询来获取它:

JOIN

您可以将最后 INNER JOIN items iit0 FORCE INDEX(PRIMARY) ON iit0.id = ifm0.item_id 更改为:

PK

强制MySQL使用files进行该连接,但我不知道它是否会运行得更快。也许当桌子变得更大时。

此查询未选择文件名(因为它根本不访问files表)。它可以很容易地获取(与表PK中的其他字段一起使用,也可以使用从其他连接表中选择的字段),使用类似风的查询,因为它使用表# Query #3-extra SELECT * FROM files WHERE id IN (1, 2, 3) 来获取它需要的行:

1, 2, 3

Query #2替换为上一个查询返回的文件ID列表。

对于大表,这两个查询的运行速度可能比Query #2

备注

如上一节所述,Query #3file_id假设item_files_map表中没有孤立Query #3条目。如果存在此类孤立条目file_id可以返回无效的Query #3-extra值,但它们将被{{1}}过滤掉,并且它返回的最终结果集将仅包含有效结果。