用于查找缺少条目的SQL查询

时间:2014-04-11 22:23:53

标签: mysql sql sql-server

我有一个数据库,我需要在其中找到一些缺失的条目并将其填入。 我有一个名为" menu"的桌子,每个餐厅都有多个菜肴,每个菜有4种不同的语言条目(主要数据库实际上是8个,但为了简单起见,我只需要4个),我需要找出哪些菜肴特定餐厅缺少任何语言条目。

select * from menu where restaurantid = 1

我被卡在那里,某些东西沿着语言1或2或3或4不存在的地方,这是一个复杂的位,因为我需要看到存在的语言才能看到语言&#因为我无法展示那些不存在的东西而失踪了。我希望这有道理吗?

在下面的示例表中,2号餐馆的2号餐具缺少语言3,这就是我需要找到的内容。

+--------------+--------+----------+-----------+
| RestaurantID | DishID | DishName | Language  |
+--------------+--------+----------+-----------+
|            1 |      1 | Soup     |        1  |
|            1 |      1 | Soúp     |        2  |
|            1 |      1 | Soupe    |        3  |
|            1 |      1 | Soupa    |        4  |
|            1 |      2 | Bread    |        1  |
|            1 |      2 | Bréad    |        2  |
|            1 |      2 | Breade   |        3  |
|            1 |      1 | Breada   |        4  |
|            2 |      1 | Dish1    |        1  |
|            2 |      1 | Dísh1    |        2  |
|            2 |      1 | Disha1   |        3  |
|            2 |      1 | Dishe1   |        4  |
|            2 |      2 | Dish2    |        1  |
|            2 |      2 | Dísh2    |        2  |
|            2 |      2 | Dishe2   |        4  |
+--------------+--------+----------+-----------+

2 个答案:

答案 0 :(得分:2)

就性能而言,反连接模式通常是最有效的。

你的特殊情况有点棘手,因为你需要"生成"缺少的行。如果每个(ResturantID,DishID)应该有4行,语言值为1,2,3和4,我们可以使用CROSS JOIN操作生成所有行的集合。

下一步是将反连接... LEFT OUTER JOIN应用于菜单表中存在的行,这样我们就可以获得CROSS JOIN集中的所有行以及匹配的行。

"技巧"是在WHERE子句中使用谓词来过滤掉我们找到匹配的行,所以我们留下的行没有匹配。

(起初看起来有点奇怪,但是一旦你的大脑被反连接模式缠绕,它就会变得熟悉。)

因此,对此表单的查询应返回指定的结果集。

 SELECT d.RestaurantID
      , d.DishID
      , lang.id AS missing_language
   FROM (SELECT 1 AS id UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4
        ) lang
  CROSS
   JOIN (SELECT e.RestaurantID, e.DishID
           FROM menu e
          GROUP BY e.RestaurantID, e.DishID
        ) d
   LEFT
   JOIN menu m
     ON m.RestaurantID = d.RestaurantID
    AND m.DishID       = d.DishID
    AND m.Language     = lang.id
  WHERE m.RestaurantID IS NULL
 ORDER BY 1,2,3

让我们解开那一点。

首先我们得到一个包含数字1到4的集合。

接下来,我们得到一个包含(RestaurantID, DishID)个不同元组的集合。 (对于每个不同的餐厅,一个不同的DishID列表,只要该组合的任何语言至少有一行。)

我们执行CROSS JOIN,将第一行( lang )中的每一行与集合中的每一行( d )相匹配,生成一个完整的"我们希望拥有的每一套(RestaurantID,DishID,语言)。

下一部分是反连接...左外连接到菜单,以找到"完成"中的哪些行。 set在菜单中有一个匹配的行,并过滤掉所有匹配的行。

这可能有点令人困惑。如果我们想到CROSS JOIN操作生成一个看起来像菜单表的临时表,但包含所有可能的行...我们可以用伪代码来考虑它:

create temporary table all_menu_rows (RestaurantID, MenuID, Language) ; 
insert into all_menu_rows ... all possible rows, combinations ; 

然后反连接模式更容易看到:

 SELECT r.RestaurantID
      , r.DishID
      , r.Language
   FROM all_menu_rows r
   LEFT
   JOIN menu m
     ON m.RestaurantID = r.RestaurantID
    AND m.DishID       = r.DishID
    AND m.Language     = r.Language
  WHERE m.RestaurantID IS NULL
 ORDER BY 1,2,3

(但我们不必承担创建和填充临时表的额外开销,我们可以在查询中做到这一点。)


当然,这不是唯一的方法。我们可以使用NOT EXISTS谓词而不是反连接,尽管这通常不那么有效。查询的第一部分是相同的,以生成"完成"我们期望拥有的一组行;不同之处在于我们如何识别菜单表中是否存在匹配的行:

 SELECT d.RestaurantID
      , d.DishID
      , lang.id AS missing_language
   FROM (SELECT 1 AS id UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4
        ) lang
  CROSS
   JOIN (SELECT e.RestaurantID, e.DishID
           FROM menu e
          GROUP BY e.RestaurantID, e.DishID
        ) d
  WHERE NOT EXISTS ( SELECT 1
                       FROM menu m
                      WHERE m.RestaurantID = d.RestaurantID
                        AND m.DishID       = d.DishID
                        AND m.Language     = lang.id
                   )
 ORDER BY 1,2,3

对于"中的每一行" set(由CROSS JOIN操作生成),我们将运行一个相关子查询,检查是否找到匹配的行。如果未找到匹配的行,则NOT EXISTS谓词返回TRUE。 (这有点容易理解,但它通常不会像反连接模式那样表现。)

答案 1 :(得分:0)

如果每个菜单项都应包含每种语言的记录(例如现实生活中的8个),则可以使用以下语句。如果要查看每个餐厅的所有菜单项,并且没有全部8个条目,则可以将数字4更改为8。

  

SELECT RestaurantID,DishID,COUNT(*)   来自菜单   GROUP BY RestaurantID,DishID   有COUNT(*)< 4

相关问题