Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

gitwithravish's avatar

Database Design tip.

Let's say I have these tables for a hotel booking CMS.

/* hotels
=================*/
id
hotel_name


/* hotels_rooms
=================*/

id
hotel_id
room_name



/* hotels_packages
=================*/
id
room_id
package name



/* hotel_package_rates
=================*/
id
start_date
end_date
package_id
rate

If I want to get packages for a hotel with id 100, I need to traverse through hotel_rooms table. Similarly if I want rates of all packages for a perticular date, I need to traverse from hotel table to all the way down to hotel_package_rates table.

The simple solution to this could be the following...

/* hotels_packages
=================*/
id
hotel_id // added
room_id
package name



/* hotels_package_rates
=================*/
id
start_date
end_date
hotel_id // added
room_id // added
package_id
rate

With the 2nd approach I can perform query must faster.

However there is a potential loop hole. logical inconsistency. lets say hotel 1 has rooms 101 and 102. hotel 2 has rooms 201 and 202. But technically it is possible to have a wrong entry hotel_packages table.

For example hotel 1 and room 201. Room 201 does not belong to hotel 1, but still an entry can be made in hotel_packages table.

What are your thoughts on this ? Is my argument valid or it is better to go for approach 2 and make sure the source code don't fuck up the logical inconsistency ?

0 likes
5 replies
MichalOravec's avatar

Table structure should be

hotels

id
name

hotel_rooms

id
hotel_id
name

hotel_packages

id
hotel_room_id
name

hotel_package_rates

id
hotel_package_id
value
started_at
ended_at

You can use Has Many Through relationship for that

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Hotel extends Model
{
    public function packages()
    {
        return $this->hasManyThrough('App\Models\HotelPackage', 'App\Models\HotelRoom');
    }
}

To get packages by hotel:

$hotel = Hotel::with('packages')->findOrFail(100);

$packages = $hotel->packages;

To get rates by hotel:

$hotel = Hotel::with('packages')->findOrFail(100);

$packageRates = HotelPackageRate::wherenIn('hotel_package_id', $hotel->packages->pluck('id'))->get();

Docs: https://laravel.com/docs/8.x/eloquent-relationships#has-many-through

1 like
gitwithravish's avatar

I am aware of that @michaloravec . But behind the scene it will traverse through all tables right? For example, in order to get all packages of hotel 1, first it will fetch room IDs and then based on those IDs, fetch all packages.

In a very large project, where this tree of interrelated tables is large and the queries are also complex, this will be a bit slower compared to the second approach. I have already seen this happening, even after having a database design with good indexing.

So my question is that, is it feasible to go for first approach and ignore the logical inconsistency factor just for the sake of good performance ?

Tray2's avatar

With the correct indexes it's not a big chore for the database.

Please or to participate in this conversation.