Help designing complicated restaurant/menu schema

Hi,

I am having trouble figuring out the best way to do this. My site has multiple restaurants. Each restaurant can have [b]multiple menus[b], each menu has multiple categories, and each category has multiple items.

There are similar menus between restaurants (breakfast, lunch, dinner, etc), and similar categories between menus (appetizers, entrees, desserts, drinks, etc), but items are unique to a specific restaurant, menu, and category.

Here is the schema I imagine. Is there a better way? (Colors are foreign key/primary key pairs)

restaurants
id
name, etc…

menus
id
name

categories
id
name

restaurants_menus
id
restaurant_id
menu_id

restaurants_menus_categories
category_id
restaurants_menus_id

items
id
menu_id
category_id
restaurant_id
name
description
price

1 Like

restaurants_menus
restaurant_id
menu_id

restaurants_menus_categories
restaurant_id
menu_id
category_id

restaurants_menus_categories_items
restaurant_id
menu_id
category_id
id
name
description
price

:cool:

1 Like

Thanks!

Interesting.

What is the point having “restaurants_menus” and “restaurants_menus_categories” tables?

Those foreign keys is already included in “restaurants_menus_categories_items” table.

the restaurant_menus table tells you which menus each restaurant can have, while the restaurants_menus_categories table tells you which categories are available in each menu in each restaurant

if you didn’t have those tables, and had only the restaurants_menus_categories_items table, then there would be no way to control which items were available in each category of a menu in a particular restaurant

the foreign keys to these tables implement an important concept called relational integrity

with the restaurants_menus and restaurants_menus_categories tables in place, and the foreign keys properly defined, you can now be assured that categories can only be added to menus that the restaurant offers, and items can only be added to categories that are available in the menus

without those tables, it would be possible to add ice cream to the entrees category in the breakfast menu

make sense?

BTW if anyone wants to see the various queries used to get things out of this DB schema, they are all in this class here:

<?php

class MenuFactory {
	private $db, $restaurant_id;
	
	/* constructor
	 * $db is a mysqli link, $restaurant_id is an int. */
	public function __construct($db, $restaurant_id) {
		$this->db = $db;
		$this->restaurant_id = $restaurant_id;
	}
	
	/* getAllMenus()
	 * returns numerical array with an index for each menu.
	 * each index contains an array of the basic menu information */
	public function getAllMenus() {
		$q = "SELECT menus.id, menus.name
		  	  FROM menus
		  	  LEFT JOIN restaurants_menus AS r_m ON r_m.restaurant_id =  '" . $this->restaurant_id . "'
		  	  AND r_m.menu_id = menus.id";
		$menus = array();
		if($result = $this->db->query($q)) {
			while($menu = $result->fetch_object()) {
				$menus[] = array('id' => $menu->id, 'name' => $menu->name);
			}
		} else { die($this->db->error); }
		
		return $menus;
	}
	
	/* getMenuCategories($menu_id)
	 * returns a numerical array of menu categories for the given menu.
	 * each index contains an array of basic category information */
	public function getMenuCategories($menu_id) {
		$q = "SELECT c.id, c.name, r_m_c.position
			  FROM menu_categories AS c
			  INNER JOIN restaurants_menus_categories AS r_m_c ON r_m_c.category_id = c.id
			  AND r_m_c.restaurant_id='" . $this->restaurant_id . "'
			  AND r_m_c.menu_id='$menu_id'";	
		$categories = array();
		if($result = $this->db->query($q)) {
			while($cat = $result->fetch_object()) {
				$categories[] = array('id' => $cat->id, 'name' => $cat->name);
			}
		} else { die($this->db->error); }
		
		return $categories;
	}
	
	/* getCategoryItems($menu_id, $cat_id)
	 * returns a numerical array of menu items for the given menu and category.
	 * each index contains an array of basic menu item information */
	public function getCategoryItems($menu_id, $cat_id) {
		$q = "SELECT id, name, description, image, price
			  FROM restaurants_menus_categories_items
			  WHERE restaurant_id='" . $this->restaurant_id . "'
			  AND menu_id='$menu_id'
			  AND category_id='$cat_id'";
		$items = array();
		if($result = $this->db->query($q)) {
			while($item = $result->fetch_object()) {
				$items[] = array('id' => $item->id,
								 'name' => $item->name,
								 'description' => $item->description,
								 'image' => $item->image,
								 'price' => $item->price
							);
			}
		} else { die($this->db->error); }
		
		return $items;
	}
	
	/* getFullMenu($menu_id)
	 * returns array containing ALL categories and ALL items associated with given menu
	 * return array contains indices: [id, name, categories = [id, name, items = [item info]]]
	*/
	public function getFullMenu($menu_id) {
		$menu = array();
		$menu['id'] = $menu_id;
		
		// basic menu info
		$q = "SELECT name FROM menus WHERE id='$menu_id' LIMIT 1";
		$result = $this->db->query($q) or die($this->db->error);
		$row = $result->fetch_object();
		$menu['name'] = $row->name;
		
		// categories
		$menu['categories'] = $this->getMenuCategories($menu_id);
		
		// items
		for($i = 0; $i < count($menu['categories']); $i++) {
			$menu['categories'][$i]['items'] =
				$this->getCategoryItems($menu_id, $menu['categories'][$i]['id']);
		}
		
		return $menu;
	}
	
	/* getAllFullMenus()
	 * returns array containing ALL menus and ALL categories and ALL items associated with restaurant
	 * return array numerical array with each index pointing to a return value of $this->getFullMenu($menu_id)
	*/
	public function getAllFullMenus() {
		$basicMenus = $this->getAllMenus();
		
		$menus = array();
		for($i = 0; $i < count($basicMenus); $i++) {
			$menus[$i] = $this->getFullMenu($basicMenus[$i]['id']);
		}
		
		return $menus;
	}
}
?>