Laravel по-русски

Русское сообщество разработки на PHP-фреймворке Laravel.

Ты не вошёл. Вход тут.

#1 23.06.2017 11:42:38

Laravel Relationship: Таблица из id двух других

Здравствуйте!
В ДБ есть таблицы : 'tours' и 'tourists'

У 'tour' может быть МНОГО 'tourist'-ов, и 'tourist' может принимать участие в МНОГИХ 'tour'-ах. Я успешно связал их через  'belongsToMany" r (при добавлении строк в  "tours' и 'tourists" создается строка и  intermediate таблице 'tour_tourist' .

Структура "tour_tourist":

tour_id | tourist_id
         1 | 37
         1 | 38
         1 | 39
(туристы с  id = 37, 38 и 39 приписаны к туру с  id = 1, но могут в будущем быть приписаны и к другим турам)

ПРОБЛЕМА: Один из  'tourists' (37/ 38 / 39 из примера выше) может быть 'buyer'  (покупателем) тура . Отношения тура и покупателя  1-1. К тому же этот "покупатель" может ехать в тур с остальным, а может не ехать. Все данные передаются при отправке одной веб-формы.

Я хочу создать еще таблицу, с именем 'buyers':

tour_id | tourist_id | is_tourist
     1    |        39     |     0

(1. одному 'tour_id' может соответствовать только 1  'tourist_id')
(2. 'tourist_id' должен быть из принадлежащих соотвествующему 'tour_id' в предыдущей таблице "tour_tourist"  - в моем примере турист 39 принадлежит туру 1).
(3. 'is_tourist' это булево 1/0,  едет/только платит - в моем примере  0 - только платит).

Мне интересно, как я могу создать 'buyer'-а через отношения (relationship) в Laravel'е (one-to-one? hasManyThrough?) .

Спасибо заранее!

Не в сети

#2 23.06.2017 12:33:36

Re: Laravel Relationship: Таблица из id двух других

Можно в той же контировочной (pivot) таблице tour_tourist и добавить нужные поля (для лучшей семантики лучше добавить 2 поля - is_tourist и is_buyer, потому как если buyer едет (=1), то его невозможно отличить от других туристов).
В итоге получается всего 3 таблицы - туры, туристы и расширенная pivot таблица.

Не в сети

#3 23.06.2017 12:37:23

Re: Laravel Relationship: Таблица из id двух других

Sergant210 пишет:

Можно в той же контировочной (pivot) таблице tour_tourist и добавить нужные поля (для лучшей семантики лучше добавить 2 поля - is_tourist и is_buyer, потому как если buyer едет (=1), то его невозможно отличить от других туристов).
В итоге получается всего 3 таблицы - туры, туристы и расширенная pivot таблица.

соглсасен, это был мой первый вариант) но там много лишних единиц у is_tourist и много лишних нулей у buyer. Т.е. конечно, у меня не будет гигабайтов данных, но мне просто всегда не нравилась избыточность информации. Поэтому решил написать такой вопрос, думаю, это сложнее сделать, но в итоге будет "красивее":) Это, так сказать, Challenge для прокачки программерских скиллов для меня:)

Не в сети

#4 23.06.2017 12:41:10

Re: Laravel Relationship: Таблица из id двух других

Тогда можно добавить только одно поле - tourist_type:
0 - турист (значит едет)
1 - покупатель (тоже едет)
2 - спонсор (купил, но не едет)

Не в сети

#5 23.06.2017 12:51:31

Re: Laravel Relationship: Таблица из id двух других

Sergant210 пишет:

Тогда можно добавить только одно поле - tourist_type:
0 - турист (значит едет)
1 - покупатель (тоже едет)
2 - спонсор (купил, но не едет)

да, но тогда будет много лишних нулей:) Я люблю математику, там повторяющуюся инфу выносят за скобки. 2aх+3bх лучше написать как x(2a+3b). Ну вот тут вынесение инфы о покупателя в отдельную таблицу - это как раз вынесение х за скобки, чтобы не было избыточной информации.
Как так сделать - вопрос:)

Не в сети

#6 23.06.2017 13:08:49

Re: Laravel Relationship: Таблица из id двух других

Ну если вы в одном поле нашли повторяющуюся информацию и считаете, что вместо него лучше создать таблицу buyers с 3 полями, которая практически полностью копирует таблицу tour_tourist, то мы с вами любим разные математики и я уж точно вам не помощник. smile

П.С. А ещё часто в таблицу статей добавляют поле deleted, в котором 1 значит "удалена", но в нём почти всегда нули. Это поле тоже может поломать математическое восприятие мира smile)

Не в сети

#7 23.06.2017 14:11:49

Re: Laravel Relationship: Таблица из id двух других

Sergant210 пишет:

Ну если вы в одном поле нашли повторяющуюся информацию и считаете, что вместо него лучше создать таблицу buyers с 3 полями, которая практически полностью копирует таблицу tour_tourist, то мы с вами любим разные математики и я уж точно вам не помощник. smile

smile ну вы смотрите шире на вещи) У вас допустим в один тур поедет 1001 турист:). и только один из них - плательщик. Значит вам нужно создать в поле 'tourist_type' 1000 нулей (для туристов неплательщиков).
Вместо этого я создам одну строчку:
tour_id | tourist_id | is_tourist
   1      |    1001     |    1

в ней всего 1 1001 1 = 6 знаков, вместо 1000 знаков у вас)

Можете поставить вместо туристов гены и ДНК, там счет пойдет на миллиарды уже нуликов.

Математики мы, возможно, действительно разные:)

Изменено Serge83 (23.06.2017 14:14:04)

Не в сети

#8 23.06.2017 14:49:46

Re: Laravel Relationship: Таблица из id двух других

ну вы смотрите шире на вещи) У вас допустим в один тур поедет 1001 турист:)

Как в анекдоте: "Пошлём X танков... Нет, икс мало. Пошлём Y  танков". smile
Я-то представлял себе, что в тур едет, например, семья. Ну группа друзей. А тут оказывается 1 человек покупает тур сразу для 2 жд составов туристов или 6 самолётов или хз сколько автобусов. А если уж представить, что он оплачивает тур для 100500 миллионов туристов, то счет пойдёт на тысячи серверов. smile
Удачи!

Не в сети

#9 23.06.2017 15:15:49

Re: Laravel Relationship: Таблица из id двух других

Математика здесь ни при чём. В БД добавление лишнего поля во все строки — нормальная практика. Особенно если это поле с фиксированной длиной (число или строка типа CHAR/BINARY). В противоположность этому, использование новой таблицы, где «нулей не будет» — плохой подход.

  1. Когда требуется получить значение этого поля, то в любом случае в запросе появляется WHERE. Например, sqlSELECT is_buyer FROM tourists WHERE tourist_id = ?.
  2. Когда БД читает и ищет строку, то она использует индекс. То есть у вас есть, к примеру, индекс (первичный ключ это тоже индекс) по tourist_id; когда БД видит sqlWHERE tourist_id = ? — то она ищет это значение в индексе и если находит, то получает смещение в файле данных этой таблицы. Переход по смещению в файле чисто теоретически быстрее на маленьких таблицах при использовании HDD, но, во-первых, SSD постепенно занимают и серверный рынок, а, во-вторых, эта скорость ничтожна по сравнению со скоростью выполнения скриптов, накладных расходов на сеть и т.д.

Соответственно, разницы между отдельной таблицей «без нулей» и таблицей пользователей с дополнительным полем в лучшем случае нет, поскольку лишнее поле влияет только на размер общей таблицы, а операции по поиску выше одинаковы для обеих таблиц.

Размер таблицы в вашем случае значения точно не имеет: если у вас 10000 пользователей, то это при длине строки в 200 байт — около 2 Мб. Минимальный размер числового поля — 1 байт, соответственно добавив 1 байт к длине строки в 200 байт вы увеличиваете размер такой таблицы на 10 Кб. Это мизер. MySQL, который любят ругать за неповоротливость, отлично работает с таблицами размером в гигабайты. Десятки гигабайт!

Почему выше я сказал «в лучшем случае нет» смысла в отдельной таблице? Потому что почти наверняка кроме простого определения «покупатель это или нет» (т.е. sqlSELECT is_buyer FROM buyers_tourists WHERE tourist_id = ?) вам потребуется получить информацию об этом пользователе. И вот здесь у вас простой запрос к таблице пользователей напрямую становится запросом с JOIN:

sqlSELECT t.*
  FROM buyers_tourists bt
  JOIN tourists t
    ON bt.tourist_id = t.tourist_id
 WHERE bt.tourist_id = ?
   AND is_buyer = 1

А запрос с JOIN это новый WHERE, новое сканирование индекса, новое чтение данных с диска. То есть сэкономив 10 Кб на диске вы увеличили ресурсозатратность запроса в 2 раза.

(Хотя справедливости ради надо сказать, что БД умеет бороться и с такими умными ходами, поэтому разница в реальности обычно меньше — маленькая таблица полностью загружается в память и обращения к диску не происходит.)

Советую вам придерживаться нескольких простых правил:

  1. Предоптимизация — зло, она усложняет код, тормозит работу и ничего не даёт взамен.
  2. Создавайте простую структуру БД — см. п. 1.
  3. Избегайте дублирования данных в БД — дублирование автоматически означает, что данные в разных местах могут рассинхронизироваться, а это сложная проблема, которая требует больше кода, создаёт неуловимые баги и так далее. Также см. п. 1 (обновить поле «баланс пользователя» в нескольких таблицах не есть проще, чем обновить его в одной).
  4. Когда вам нужно добавить 1 поле — добавлйте и не думайте, что «в будущем когда у нас будет 100500 пользователей это поле будет занимать лишних аж целых 1 мегабайтов». Когда у вас будет столько пользователей — последнее, что вас будет волновать — это влияние поля TINYINT на размер БД.

В контексте проблемы с покупателями, я бы вообще советовал отказаться от отдельного поля (и тем более таблицы), см. пп. 1 и 3. Кто такой покупатель? Это тот, у которого есть «заказы» (оплаченные туры). Заказы где хранятся? Очевидно, в той же БД, в другой таблице. Так зачем вам отдельное поле, если вы можете сделать запрос в таблицу с заказами и определить, покупатель он или нет?

Отдельное поле это дублирование данных. К примеру, пользователь оформил заказ, потом его по какой-то причине отменили и пользователь больше не есть «покупатель», а сбросить статус пользователя вы забыли. Тогда, к примеру, если у вас есть отчёт по среднему размеру заказов, то вместо учёта только пользователей с заказами вы учитываете и таких нулевых пользователей, то есть итоговое значение резко проседает. С другой стороны, удалить заказ из таблицы вы никак не можете забыть и эта проблема будет замечена при первом беглом тестировании.

Это только один из примеров, на практике баги вследствие дублирования данных встречаются самые неожиданные. Дублирование данных в 90% случаев это предоптимизация.

  1. В ДБ есть таблицы : ’tours’ и ’tourists’

«ДБ» это сокращение для «д…л», а не для «базы данных»

  1. Можно в той же контировочной (pivot) таблице

Pivot table = сводная таблица.

Не в сети

#10 23.06.2017 15:31:56

Re: Laravel Relationship: Таблица из id двух других

а я бы пивот не трогал, а добавил бы поле buyer_id на tours и сделал бы связь buyer с App\Tour на App\Tourist один-ко-многим естественно. а то что buyer – это один из tourists проверял бы уже в коде

Не в сети

Подвал раздела