1

At a glance, the database schema looks like this:

enter image description here

The schema has to be in 3rd normal form (and I am aware that hotels.average_rating suggests otherwise, try to oversee that, since the database is not fully designed yet). This is for a tourist recommendation system.

The SQL:

SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";
SET time_zone = "+00:00";

CREATE TABLE `activities` (
  `activity_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `activity_name` varchar(277) COLLATE utf8_bin NOT NULL,
  PRIMARY KEY (`activity_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

CREATE TABLE `bookings` (
  `from_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `to_date` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
  `belong_user` int(10) unsigned NOT NULL,
  `belong_hotel` int(10) unsigned NOT NULL,
  `rating` int(3) unsigned NOT NULL,
  KEY `belong_user` (`belong_user`),
  KEY `belong_hotel` (`belong_hotel`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

CREATE TABLE `countries` (
  `cuntry_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `country_name` varchar(20) COLLATE utf8_bin NOT NULL,
  PRIMARY KEY (`cuntry_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

CREATE TABLE `hotels` (
  `hotel_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `hotel_name` varchar(128) COLLATE utf8_bin NOT NULL,
  `hotel_stars` int(3) NOT NULL,
  `hotel_description` text COLLATE utf8_bin NOT NULL,
  `average_price` float unsigned NOT NULL,
  `average_rating` float unsigned NOT NULL,
  `total_rooms` int(10) unsigned NOT NULL,
  `free_rooms` int(10) unsigned NOT NULL,
  `belong_region` int(10) unsigned NOT NULL,
  PRIMARY KEY (`hotel_id`),
  KEY `belong_region` (`belong_region`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

CREATE TABLE `hotels_activity_offers` (
  `belong_hotel` int(10) unsigned NOT NULL,
  `belong_activity` int(10) unsigned NOT NULL,
  UNIQUE KEY `belong_hotel_2` (`belong_hotel`,`belong_activity`),
  KEY `belong_hotel` (`belong_hotel`),
  KEY `belong_activity` (`belong_activity`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

CREATE TABLE `regions` (
  `region_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `belong_country` int(10) unsigned NOT NULL,
  `region_name` varchar(255) COLLATE utf8_bin NOT NULL,
  PRIMARY KEY (`region_id`),
  KEY `belong_country` (`belong_country`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

CREATE TABLE `regions_activity_offers` (
  `belong_region` int(10) unsigned NOT NULL,
  `belong_activity` int(10) unsigned NOT NULL,
  KEY `belong_region` (`belong_region`),
  KEY `belong_activity` (`belong_activity`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

CREATE TABLE `users` (
  `user_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `username` varchar(20) COLLATE utf8_bin NOT NULL,
  `password` varchar(40) COLLATE utf8_bin NOT NULL COMMENT 'MD5',
  `first_name` varchar(20) COLLATE utf8_bin NOT NULL,
  `last_name` varchar(20) COLLATE utf8_bin NOT NULL,
  `email` varchar(255) COLLATE utf8_bin NOT NULL,
  `is_admin` tinyint(1) NOT NULL,
  `is_active` tinyint(1) NOT NULL,
  PRIMARY KEY (`user_id`),
  KEY `is_active` (`is_active`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

CREATE TABLE `users_favourite_activities` (
  `belong_user` int(10) unsigned NOT NULL,
  `belong_activity` int(10) unsigned NOT NULL,
  UNIQUE KEY `belong_user_2` (`belong_user`,`belong_activity`),
  KEY `belong_user` (`belong_user`),
  KEY `belong_activity` (`belong_activity`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;


ALTER TABLE `bookings`
  ADD CONSTRAINT `bookings_ibfk_3` FOREIGN KEY (`belong_hotel`) REFERENCES `hotels` (`hotel_id`) ON DELETE CASCADE,
  ADD CONSTRAINT `bookings_ibfk_2` FOREIGN KEY (`belong_user`) REFERENCES `users` (`user_id`) ON DELETE CASCADE;

ALTER TABLE `hotels`
  ADD CONSTRAINT `hotels_ibfk_1` FOREIGN KEY (`belong_region`) REFERENCES `regions` (`region_id`) ON DELETE CASCADE;

ALTER TABLE `hotels_activity_offers`
  ADD CONSTRAINT `hotels_activity_offers_ibfk_2` FOREIGN KEY (`belong_activity`) REFERENCES `activities` (`activity_id`) ON DELETE CASCADE,
  ADD CONSTRAINT `hotels_activity_offers_ibfk_1` FOREIGN KEY (`belong_hotel`) REFERENCES `hotels` (`hotel_id`) ON DELETE CASCADE;

ALTER TABLE `regions`
  ADD CONSTRAINT `regions_ibfk_1` FOREIGN KEY (`belong_country`) REFERENCES `countries` (`cuntry_id`) ON DELETE CASCADE;

ALTER TABLE `regions_activity_offers`
  ADD CONSTRAINT `regions_activity_offers_ibfk_2` FOREIGN KEY (`belong_activity`) REFERENCES `activities` (`activity_id`) ON DELETE CASCADE,
  ADD CONSTRAINT `regions_activity_offers_ibfk_1` FOREIGN KEY (`belong_region`) REFERENCES `regions` (`region_id`) ON DELETE CASCADE;

ALTER TABLE `users_favourite_activities`
  ADD CONSTRAINT `users_favourite_activities_ibfk_1` FOREIGN KEY (`belong_user`) REFERENCES `users` (`user_id`) ON DELETE CASCADE,
  ADD CONSTRAINT `users_favourite_activities_ibfk_2` FOREIGN KEY (`belong_activity`) REFERENCES `activities` (`activity_id`) ON DELETE CASCADE;
  1. The question is: how to best add a "user activity log" feature which stores the activities a user has taken part to? Note that both regions and hotels can have activities, and I need to be able to tell whether that activity has taken place in a region or in a hotel. Referential integrity should be guaranteed.

  2. Present a query (it should use JOIN shouldn't it?) which lists all users and their activities along with the hotel id or region id. (the one which is not applicable can be NULL if required).

Simple solutions are better - so preferably without stored procedures or anything which digs too much in mysql-specific features.

Flavius
  • 13,566
  • 13
  • 80
  • 126
  • 1
    Similar, if not the same, to this: http://stackoverflow.com/questions/5537779/multiple-tables-need-one-to-many-relationship – bububaba Jan 12 '12 at 14:31
  • Hotels belong to regions. Is a region-activity-offer available to all hotels within the region, or are region- and hotel-activity-offers completely unrelated to each other? –  Jan 12 '12 at 14:36
  • an activity can belong to a region directly, as you can see in the picture. For instance "skiing". Of course there could also be a skiing offer of the hotel (example: all-inclusive). They are "not" related however. – Flavius Jan 12 '12 at 15:21
  • Some of your tables don't have keys declared with either PRIMARY KEY or NOT NULL UNIQUE. None of those tables can possibly be in 3NF until you fix that. Some "lookup" tables lack a unique constraint on their natural key. ("country_name", "activity_name".) Might as well fix that while you're at it. – Mike Sherrill 'Cat Recall' Jan 12 '12 at 16:43
  • @Catcall please elaborate. What are those tables for each issue? – Flavius Jan 12 '12 at 21:34
  • @Flavius: Bookings doesn't have a key. ("KEY" by itself means "index" in MySQL. It's not at all the same as PRIMARY KEY or NOT NULL UNIQUE *constraint*.) Activities doesn't have a unique constraint on its natural key. There are others. – Mike Sherrill 'Cat Recall' Jan 12 '12 at 22:05
  • Thanks, I'll document myself. – Flavius Jan 12 '12 at 22:43
  • @Catcall about activities (and others in similar situation): isn't AUTO_INCREMENT enough to guarantee uniqueness? – Flavius Jan 12 '12 at 22:47
  • @Flavius: No. Autoincrement alone allows 4 billion distinct rows all having activity_name equal to 'Oops'. Autoincrement is a surrogate key. *Surrogate* means "takes the place of". A surrogate mother takes the place (in birthing) of a natural mother. A surrogate key takes the place (in foreign keys and joins) of a natural key. – Mike Sherrill 'Cat Recall' Jan 12 '12 at 23:44

1 Answers1

-1

Your database is not normalized - and the way you've done it looks like a poster-child for why normalization is a good idea.

hotels.average_rating?

WTF?

While it can make sense to denormalize your data - this is not how to do it. Think about what you need to do when a user submits a hotel rating - you need to recalculate the value based on all the ratings submitted. If instead you held a sum_of_ratings (or even retained the current average) and a number of ratings then you could calculate the new values based on the hotel record and the new rating without having to look at the other ratings.

symcbean
  • 47,736
  • 6
  • 59
  • 94
  • Imagine the field is not there for the time being, while you think about a solution to the real problem. Please improve your answer so that it also answers my question. The > WTF will be fixed in the final database, do not worry :-) – Flavius Jan 12 '12 at 16:05