-1

Hello for the first time!

In my effort to become a better programmer I decided to build an online booking web application. As you can see in the picture below, I have created this database with the following things in mind.

  1. each customer may book an appointment or request an appointment.

    i) Booked appointments have a fixed time and day.

    ii) Appointment requests do not have a fixed time and day until they are booked manually through the back office. They must have at least two time-ranges between two days, or more which declare when the customer is available, so the appointment must be booked inside one of these time ranges.

  2. each appointment may have more than one customers ( probably max to 3 )

  3. each customer may have more than one services ( haven't decided the limit here )

my database

The desired outcome for the front-end, I have decided upon is the following (which will have pagination and ajax as well but I am not there yet):

front-end table

The first successful attempt on this, was not efficient at all as I needed to run 4 queries to the database for each appointment, so I immediately started thinking of something better but I wasted almost two weeks and I have nothing yet.

I can't understand in which part I am missing knowledge MySQL or PHP(forgot to mention that I am writing this with PHP but better late than never)....very likely on both sides but I don't know what to study to solve this one, while so many other stuff I managed to find something around.

Thanks in advance!

UPDATE: Follows the code I use (including the queries):

MODEL

 public function getAppointments()
    {
        $db = \Config\Database::connect();

        // get future appointments
        $query = $db->query('
                            SELECT DISTINCT`appointment_id` 
                            FROM `appointments_schedule`
                            WHERE `datetime_from` > CURRENT_TIMESTAMP
                            ');
        $appointments = $query->getResultArray();

        return $appointments;
    }

    public function getAppointmentDateTimes($appointment_id)
    {
        $db = \Config\Database::connect();

        $query = $db->query('
                            SELECT `datetime_from`, `datetime_to`
                            FROM `appointments_schedule`
                            WHERE `appointment_id` = ' . $appointment_id
                            );
        $appointmentDateTimes = $query->getResultArray();

        return $appointmentDateTimes;
    }

    public function getAppointmentCustomers($appointment_id)
    {
        $db = \Config\Database::connect();

        $query = $db->query('
                            SELECT `id`, `last_name`, `first_name`, customers.status, `is_booking`
                            FROM customers
                            INNER JOIN appointment_customer_services ON appointment_customer_services.customer_id = customers.id
                            WHERE id IN(
                                        SELECT `customer_id`
                                        FROM appointment_customer_services
                                        WHERE appointment_id = ' . $appointment_id . ')
                            GROUP BY customers.last_name'                                
                            );
        $appointmentCustomers = $query->getResultArray();

        return $appointmentCustomers;
    }

    public function getCustomerServices($appointment_id, $customer_id)
    {
        $db = \Config\Database::connect();

        $query = $db->query('
                            SELECT `name` AS `service_name`
                            FROM services
                            WHERE id IN(SELECT `service_id`
                                        FROM appointment_customer_services
                                        WHERE `customer_id` =' . $customer_id . ' 
                                        AND `appointment_id` =' . $appointment_id . ')
                            ');
        $CustomerServices = $query->getResultArray();

        return $CustomerServices;
    }

CONTROLLER

$data['appointments'] = [];

    foreach ( $model->getAppointments() as $appointment )
    {
        foreach ( $appointment as $id )
        {
            foreach ( $model->getAppointmentDateTimes($id) as $key => $timeDates )
            {
                $data['appointments'][$id]['timeDates'][] = $timeDates;
            }

            foreach ( $model->getAppointmentCustomers($id) as $appointmentCustomers )
            {                   
                foreach ( $model->getCustomerServices($id, $appointmentCustomers['id']) as $services )
                {
                    foreach ( $services as $service )
                    {
                        $appointmentCustomers['services'][] = $service;
                    }
                }
                $data['appointments'][$id]['customers'][] = $appointmentCustomers;
            }
        }
    }

PRINT_R OUTPUT

Array
(
    [1] => Array
        (
            [timeDates] => Array
                (
                    [0] => Array
                        (
                            [datetime_from] => 2020-04-28 14:00:00
                            [datetime_to] => 2020-04-28 17:00:00
                        )

                    [1] => Array
                        (
                            [datetime_from] => 2020-05-06 12:00:00
                            [datetime_to] => 2020-05-06 17:00:00
                        )

                    [2] => Array
                        (
                            [datetime_from] => 2020-05-30 17:00:00
                            [datetime_to] => 2020-05-30 20:00:00
                        )

                )

            [customers] => Array
                (
                    [0] => Array
                        (
                            [id] => 1
                            [last_name] => Jolie 
                            [first_name] => Angelina 
                            [status] => 1
                            [is_booking] => 1
                            [services] => Array
                                (
                                    [0] => service1
                                )

                        )

                )

        )

    [2] => Array
        (
            [timeDates] => Array
                (
                    [0] => Array
                        (
                            [datetime_from] => 2020-05-29 14:00:00
                            [datetime_to] => 2020-05-29 16:00:00
                        )

                )

            [customers] => Array
                (
                    [0] => Array
                        (
                            [id] => 2
                            [last_name] => Lopez
                            [first_name] => Jennifer
                            [status] => 1
                            [is_booking] => 1
                            [services] => Array
                                (
                                    [0] => service1
                                    [1] => service2
                                    [2] => service3
                                )

                        )

                )

        )

    [3] => Array
        (
            [timeDates] => Array
                (
                    [0] => Array
                        (
                            [datetime_from] => 2020-05-28 15:00:00
                            [datetime_to] => 2020-05-27 17:00:00
                        )

                )

            [customers] => Array
                (
                    [0] => Array
                        (
                            [id] => 3
                            [last_name] => Charlize
                            [first_name] => Theron
                            [status] => 1
                            [is_booking] => 1
                            [services] => Array
                                (
                                    [0] => service1
                                    [1] => service2
                                    [2] => service3
                                )

                        )

                    [1] => Array
                        (
                            [id] => 4
                            [last_name] => Bullock
                            [first_name] => Sandra
                            [status] => 1
                            [is_booking] => 0
                            [services] => Array
                                (
                                    [0] => Service1
                                    [1] => Service2
                                )

                        )

                    [2] => Array
                        (
                            [id] => 5
                            [last_name] => Aniston
                            [first_name] => Joe
                            [status] => 1
                            [is_booking] => 0
                            [services] => Array
                                (
                                    [0] => service1
                                )

                        )

                )

        )

)

VIEW(the table i fill with the data above)

<table class="table table-hover">
                      <thead class="text-warning">
                        <?php foreach ( $table_head as $th) : ?>
                            <th <?php if ($th == "timeDates"){echo "style="."text-align:center;";} ?>>
                              <?php echo $th; ?>
                            </th>
                        <?php endforeach; ?>
                      </thead>
                      <tbody>
                      <?php
                      /*  the table has 3 cells and does not contain nested tables
                          each row represents one customer, with his services in a unordered list
                          the times cell has a rowspan = the number of the appointment customers                              
                      */
                      ?>
                      <?php foreach ( $appointments as $appointment ) : ?>
                      <tbody class="appointments-table">
                          <?php foreach ( $appointment['customers'] as $index => $customer) : ?>
                          <tr class="appointments-table">
                              <td <?php echo $customer['is_booking'] ? "style='font-weight: bold;'" : ''; ?>><?php echo $customer['last_name'] . ' ' . $customer['first_name'];?></td>
                              <td>
                                  <?php foreach ( $customer['services'] as $service ) : ?>
                                  <ul style="list-style-type:none; padding-left: 0; margin-bottom: 0;">
                                      <li><?php echo $service; ?></li>
                                  </ul>
                                  <?php endforeach; ?>
                              </td>
                              <?php if ( $index == 0 ) : ?>
                              <td class="appointments-table" rowspan=<?php echo count($appointment['customers']); ?>>
                                  <ul class="pl-0 mb-0" style="list-style-type:none; text-align: center; margin-bottom: 0;">
                                    <?php foreach ( $appointment['timeDates'] as $timeDate ) : ?>
                                    <li><?php echo $timeDate['datetime_to'] ? 
                                                  ( date_format(date_create($timeDate['datetime_from']), "d/m/Y H:i") . ' until ' . 
                                                  date_format(date_create($timeDate['datetime_to']), "d/m/Y H:i") ) : 
                                                  date_format(date_create($timeDate['datetime_from']), "d/m/Y H:i"); ?></li>
                                    <?php endforeach; ?>
                                  </ul>
                              </td>
                              <?php endif; ?>
                          </tr>
                          <?php endforeach; ?>
                      </tbody>
                      <?php endforeach; ?>
                    </table>

NOTE: i intend to improve the view also to move as much logic as possible to the controller

supermod
  • 1
  • 2
  • 2
    The gist of this question troubles me. 1st, if you have a working system that does what you want, but makes 4 queries, that might not be great, but it's better than nothing. In trying to improve that, I assume what you mean is that you want to get the data you need in less queries. So if you're missing that, then it sounds to me like you simply need to study SQL and joins in particular. – gview May 03 '20 at 00:31
  • @Strawberry thank you for your attention, however, do you mean that my description is not detailed enough? What else can I provide? I added the whole context of what I am trying to build, the database schema I created, the desired output, and where I have gotten so far. – supermod May 03 '20 at 01:05
  • @gview thank you for your attention and the direction you provided. – supermod May 03 '20 at 01:07
  • 4 queries per page is not necessarily anything to worry about - unless it is in your circumstances. Is it running at scale? Are the queries very slow? It all depends on your particular situation. If you want to try to make it more efficient purely for the sake of improving your SQL or as an exercise, and are asking for help with that, then we'd need to see the queries. – Don't Panic May 03 '20 at 01:18
  • @Don'tPanic sorry for not being clear enough. They are 4 queries per appointment, so if I have 100 appointments, then I would run 400 queries. Isn't this unacceptable as a practice? Unless pagination solves this by loading 5 appointments per page for example. But again I would 'hit' the DB 20 times for only 5 appointments. I don't have a problem sharing the queries, I will update the description asap. Thank you for your attention. Additionally, the complete page will not have only a table, there will be more elements which possibly means more data might have to come from the DB. – supermod May 03 '20 at 01:39
  • I realise that I'm biased, but my sense is that the accepted answer at the linked question is self-explanatory – Strawberry May 03 '20 at 02:05
  • I think @Strawberry is correct - we need to see what you are talking about to be able to offer anything useful. If you are talking about 4 queries per row on an admin list, then yes that seems like there is room for improvement - surely you'd want to be using some joins etc. Also note you are surely going to need pagination, as the number of appointments will presumably increase over time, and you don't want to show them all, all the time. – Don't Panic May 03 '20 at 03:23
  • I updated my question by adding the relevant code blocks and the output. Thanks again for your attention. – supermod May 03 '20 at 11:25
  • @Don'tPanic thank you for your attention! It does and it does not! I'will come back with my best queries asap and tell update you with my status. Thank you very much again! – supermod May 05 '20 at 20:29

1 Answers1

0

Now we can see your queries, and yes, your hunch was right - this is not an efficient approach. This is an example of what is known as the n+1 problem, where you do an initial SELECT for your parent record (appointments), and then for each of those (n) do another SELECT (or several more, in this case), to find related data about those records.

Another note, SELECT .. WHERE IN ( ... subquery ... ) is very slow. Try to avoid that if you can.

Your schema seems to be well normalised, so why not use some JOINs? AFAICT you should be able to find what you need with a single query. All joins are on primary keys which are all INTs, so this should be efficient.

This is the simple, naivest join of your tables I came up with by just eyeballing your queries. You may need to do some GROUP BYing, or change a JOIN to INNER JOIN, or some other tuning ... I don't know your data or schema so this is just a first stab to get you started. And of course, it won't generate results in the format you currently have, I mean the nested arrays etc - you will have to update your front end to display it. But it should end up much simpler.

SELECT ... FROM appointments
    JOIN appointments_schedule ON appointments.id = appointments_schedule.appointment_id
    JOIN appointment_customer_services ON appointments.id = appointment_customer_services.appointment_id
    JOIN customers ON appointment_customer_services.customer_id = customers.id
    JOIN services ON appointment_customer_services.service_id = services.id
WHERE appointments_schedule.datetime_from > CURRENT_TIMESTAMP;
Don't Panic
  • 13,965
  • 5
  • 32
  • 51