复杂的MySQL查询1-N关系

时间:2012-01-03 01:23:26

标签: mysql sql

我目前正在为饮料配方设计一个数据库(MySQL),并希望用户能够输入他们所拥有的成分,并且我将向他们提供包含这些成分的所有食谱。我需要的是一个查询,我可以从表drinks中选择所有饮料,其中唯一的成分喜欢原因是有人可能会进入伏特加或smirnoff伏特加,但我将它们视为同一事物)表user_ingredients

中的成分

以下是此查询中涉及的三个表:

饮料

CREATE TABLE `drinks` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `drink` varchar(64) NOT NULL DEFAULT '',
  `glass` int(11) unsigned NOT NULL,
  `instructions` text,
  PRIMARY KEY (`id`),
  UNIQUE KEY `un_drink` (`drink`),
  KEY `in_id` (`id`),
  KEY `fk_glass` (`glass`),
  CONSTRAINT `fk_glass` FOREIGN KEY (`glass`) REFERENCES `glasses` (`id`) ON DELETE NO         ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=11946 DEFAULT CHARSET=utf8;

recipe_ingredients

CREATE TABLE `drink_ingredients` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `drink` int(11) unsigned NOT NULL,
  `ingredient` int(11) unsigned NOT NULL,
  `amount` int(11) unsigned DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `in_drink` (`drink`),
  KEY `in_ingredient` (`ingredient`),
  KEY `fk_amount` (`amount`),
  CONSTRAINT `fk_amount` FOREIGN KEY (`amount`) REFERENCES `amounts` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
  CONSTRAINT `fk_drink` FOREIGN KEY (`drink`) REFERENCES `drinks` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
  CONSTRAINT `fk_ingredient` FOREIGN KEY (`ingredient`) REFERENCES `ingredients` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=46026 DEFAULT CHARSET=utf8;

user_ingredients

CREATE TABLE `user_ingredients` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `user` int(11) unsigned DEFAULT NULL,
  `ingredient` int(11) unsigned DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `fk_user_ingredient` (`ingredient`),
  CONSTRAINT `fk_user_ingredient` FOREIGN KEY (`ingredient`) REFERENCES `ingredients` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

谢谢你,我已经坚持了一段时间。 -Stefan

2 个答案:

答案 0 :(得分:2)

您可以使用外部联接找到用户拥有所有成分的食谱。如果在用户的供应中找不到给定的成分,ui.*将为NULL。

然后计算饮料的成分,与用户供应中的匹配成分数量进行比较(NULL不计算),如果计数相等,则所有成分都与用户的供应相匹配。

SELECT d.*
FROM drinks d
INNER JOIN drink_ingredient di ON d.id = di.drink
LEFT OUTER JOIN user_ingredients ui 
    ON di.ingredient = ui.ingredient AND ui.user = :user
GROUP BY d.id
HAVING COUNT(di.ingredient) = COUNT(ui.ingredient);

即使用户有其他成分不属于这种饮料的配方,我认为没关系,这些成分将会留在橱柜中,以备下次使用。 : - )

将用户的成分输入与ingredients表中的规范行匹配是另一回事。也就是说,填充user_ingredients表,以便数字外键引用正确的行。您可以在用户的​​数据输入期间执行此操作,列出他们的成分。

INSERT INTO user_ingredients (user, ingredient)
SELECT :user, i.id
FROM ingredients i
WHERE :ingredient REGEXP CONCAT('[[:<:]]', i.name, '[[:>:]]');

因此,用户的输入'smirnoff vodka'与正则表达式'[[:<:]]vodka[[:>:]]'

匹配

请注意,像这样的子字符串匹配很难优化。它必须运行全表扫描。但我假设你在配料表中只有几百行,所以它不会太糟糕。否则,您必须使用全文索引解决方案。

答案 1 :(得分:0)

我认为你的两个标准是:

  1. 配方用户输入的成分,
  2. 将含糊不清的成分与'喜欢'相匹配
  3. 不兼容。想象一下,你有一个(可怕的)鸡尾酒红色&amp;白葡萄酒(不太可能我知道,只是一个例子),你的用户输入'葡萄酒'。现在你不知道他们是否有合适的饮料成分

    我想如果可能的话,建立一个允许的成分列表(例如,数据库中所有可用成分的明确列表)更有意义,并为用户提供一种非常简单的方法来建议添加。