Архитектура таблиц

Общие вопросы по использованию второй версии фреймворка. Если не знаете как что-то сделать и это про Yii 2, вам сюда.
Ответить
myks1992@mail.ru
Сообщения: 147
Зарегистрирован: 2017.11.15, 23:54

Архитектура таблиц

Сообщение myks1992@mail.ru »

Всем привет)

Прощу помощи и критики в архитектуре таблиц.
Небольшое слово о ТЗ.

Необходимо реализовать Календарь мероприятий с регистрацией на них. Примерно 800 мероприятий в год. Около 1000 заявок на каждый период. База mysql
Нюансы:
- мероприятия могут быть разовые, а могут быть переодичные (ежедневные, ежегодные, ежемесячные...). Период указывается датой и временем. Дубликаты мероприятий запрещены.
- На мероприятие есть несколько типов регистраций: соревнование, мастер-класс, батл. Типы регистрации закрепляются за периодом мероприятия.
- В личном кабинете участников выводится история участия. В личном кабинете регистратора выводится список заявок.

Вот что у меня получилось. https://dbdiagram.io/d/5cfad2ca09a99609d6145a6a


<spoiler title="Архитектура таблиц">
<code>
events (мероприятия)
- id
- organization_id (Организация)
- slug
- poster
- name
- url_json
- contact_email
- contact_phone
- description
- status
- view_count

evt_periods (периоды проведения)
-id
- event_id
- date_from //дата и время начала периода
- date_to

registrations системы регистраций
- id
- event_id //мероприятие
- period_id /на какой период
- name
- type
- date_from //дата открытия регистрации
- date_to //дата закрытия
- status

reg_turnt_requests //заявки на турнир
- id
- registration_id //ID системы регистрации
- number //номер участия
- organization_id
- discipline_id // дисциплина
- cancel_reason
- current_status
- status_json
- mentor_count
- participant_count
- created_at

reg_turnt_request_musics //Музыкальные треки заявки
- request_id
- file_name
- extension
- play_point //воспроизвести с точки

reg_turnt_mentors //наставники
- request_id
- person_id

reg_turnt_participants //участники
- request_id
- person_id
</code>
</spoiler>

————————————/
<b>В чем собствннно сложность?</b>
1. Это архитектура. На сколько правильно она выстроена. Есть ли какие-то косяки?
2. Запросы. Меня больше именно волнует этот фактор. Так как данные разбросаны по разным таблицам и будет возня по join-нами. Нужно будет много таблиц подгружать чтобы вывести, например, у участника список мероприятий, в которых он принимал участие, где нужно будет вывести название мероприятия, период, дисциплина... что посоветуете в этом случае?

Мои мысли это добавить event_id и period_id в таблицу с заявками. Так бы сразу будем напрямую получать название мероприяти и период. Если же оставить так, то нам придётся сначала делать запрос в систему регистраций, затем в периоды и только тогда мы по связи получим название мероприятия.

Посоветуйте что нибудь ещё. Давно голову ломаю( Очень нужна помощь специалистов!!!
Последний раз редактировалось myks1992@mail.ru 2019.06.08, 12:25, всего редактировалось 2 раза.
Аватара пользователя
leonenco
Сообщения: 155
Зарегистрирован: 2017.01.30, 22:42

Re: Архитектура таблиц

Сообщение leonenco »

Код: Выделить всё

"Мои мысли это добавить event_id и period_id в таблицу с заявками."
Вы начинаете обратно возвращятся к дублированию записей. Это в свою очередь приведет к поддтягиванию новых моделей присохранении. Вы должны как можно детально нормализовать свои таблицы чтобы было как можно меньше дупликатов.
myks1992@mail.ru
Сообщения: 147
Зарегистрирован: 2017.11.15, 23:54

Re: Архитектура таблиц

Сообщение myks1992@mail.ru »

leonenco писал(а): 2019.06.08, 05:57

Код: Выделить всё

"Мои мысли это добавить event_id и period_id в таблицу с заявками."
Вы начинаете обратно возвращятся к дублированию записей. Это в свою очередь приведет к поддтягиванию новых моделей присохранении. Вы должны как можно детально нормализовать свои таблицы чтобы было как можно меньше дупликатов.
То есть такая структура рабочая и ничего плохого в ней нет? Нормально будет от заявки персоне получать названия мероприятия через таблицу $request->registrations—>periods->events—>name

И тогда вопрос с таблицей регистрации. Там тоже получается дублирование id event_id. По периоду можно получить этот ИД. МОЖЕТ БЫТЬ ЕГО ТОЖЕ УБРАТЬ?

Есть ли ещё какие-то косяки?)
Loveorigami
Сообщения: 977
Зарегистрирован: 2014.08.27, 21:54

Re: Архитектура таблиц

Сообщение Loveorigami »

Мне не нравятся двойные связи туда и обратно.

Изображение

Их же нужно как-то сохранять еще...

Архитектуру вы должны строить, держа в голове
Country - Region -> Town -> Address -> Person

В таблицу с персоной вы же не будете добавлять region_id и country_id, если нужно узнать из какой страны человек.
Второй момент - не полагайтесь сильно на AR.
Есть простые mysql запросы. Если нужно узнать, из какой страны человек, не нужно тянуть по связям регионы, города и адреса.

Пишете в Query нужные методы под конкретные задачи.
$model = Person::find()->withCountry()->one();
myks1992@mail.ru
Сообщения: 147
Зарегистрирован: 2017.11.15, 23:54

Re: Архитектура таблиц

Сообщение myks1992@mail.ru »

Loveorigami писал(а): 2019.06.08, 18:55 Мне не нравятся двойные связи туда и обратно.

Изображение

Их же нужно как-то сохранять еще...

Архитектуру вы должны строить, держа в голове
Country - Region -> Town -> Address -> Person

В таблицу с персоной вы же не будете добавлять region_id и country_id, если нужно узнать из какой страны человек.
Второй момент - не полагайтесь сильно на AR.
Есть простые mysql запросы. Если нужно узнать, из какой страны человек, не нужно тянуть по связям регионы, города и адреса.

Пишете в Query нужные методы под конкретные задачи.
$model = Person::find()->withCountry()->one();
Огромное спасибо за комментарий! Очень полезно)

По поводу связей туда обратно немного не понял)) Вы имеете ввиду о таблице registrations? То что там ссылаюсь на event_id, когда его можно получить через таблицу event_periods... ?

Верно говорите по дублированию полей. Просто я думал, что лишняя таблица в запросе будет лишней связи. И поэтому ради оптимизации думал выносить эти id в другую связь. Логичнее же, конечно, что бы их не было. Чтобы с сохранениями не возиться сильно.

На AR особо не надеюсь. Я делаю везде свои repositories, которая служит обёрткой над AR. в любой момент можно будет написать туда хоть голый SQL. Так же в Query пишу методы типо active(), ->withCountry()... Это упрощает жизнь и косяки)

Меня именно больше интересует архитектура таблиц. Не будет ли проблем с запросами и получения связей. Так как не сильно разбираюсь в этом)) А судя по проекту таблиц будет много разбросанных. И к ним нужно будет обращаться большой связью)
myks1992@mail.ru
Сообщения: 147
Зарегистрирован: 2017.11.15, 23:54

Re: Архитектура таблиц

Сообщение myks1992@mail.ru »

Loveorigami писал(а): 2019.06.08, 18:55 Мне не нравятся двойные связи туда и обратно.

Изображение

Их же нужно как-то сохранять еще...

Архитектуру вы должны строить, держа в голове
Country - Region -> Town -> Address -> Person

В таблицу с персоной вы же не будете добавлять region_id и country_id, если нужно узнать из какой страны человек.
Второй момент - не полагайтесь сильно на AR.
Есть простые mysql запросы. Если нужно узнать, из какой страны человек, не нужно тянуть по связям регионы, города и адреса.

Пишете в Query нужные методы под конкретные задачи.
$model = Person::find()->withCountry()->one();
Огромное спасибо за комментарий! Очень полезно)

По поводу связей туда обратно немного не понял)) Вы имеете ввиду о таблице registrations? То что там ссылаюсь на event_id, когда его можно получить через таблицу event_periods... ?

Верно говорите по дублированию полей. Просто я думал, что лишняя таблица в запросе будет лишней связи. И поэтому ради оптимизации думал выносить эти id в другую связь. Логичнее же, конечно, что бы их не было. Чтобы с сохранениями не возиться сильно.

На AR особо не надеюсь. Я делаю везде свои repositories, которая служит обёрткой над AR. в любой момент можно будет написать туда хоть голый SQL. Так же в Query пишу методы типо active(), ->withCountry()... Это упрощает жизнь и косяки)

Меня именно больше интересует архитектура таблиц. Не будет ли проблем с запросами и получения связей. Так как не сильно разбираюсь в этом)) А судя по проекту таблиц будет много разбросанных. И к ним нужно будет обращаться большой связью)
Loveorigami
Сообщения: 977
Зарегистрирован: 2014.08.27, 21:54

Re: Архитектура таблиц

Сообщение Loveorigami »

У меня от страны до свойств обьекта с прайсами и ценами 9 таблиц. Все достаю одним запросом
https://www.gintur.com/service
Только то, что надо
Последний раз редактировалось Loveorigami 2019.06.10, 09:32, всего редактировалось 1 раз.
myks1992@mail.ru
Сообщения: 147
Зарегистрирован: 2017.11.15, 23:54

Re: Архитектура таблиц

Сообщение myks1992@mail.ru »

Loveorigami писал(а): 2019.06.08, 23:17 У меня от страны до свойств обьекта с прайсами и ценами 9 таблиц. Все достаю одним запросом
https://www.gintur.com/service
Толко то, что надо
Ну значит я зря парюсь над этим)) И ничего страшного в этом нет, что будет много join запросов))

А вы AR используете в этих запросах? Если так, то наверное не очень красиво получается лазать из одной сущности в другие через другие сущности. Получается примерно такая цепочка $participant->request->period->event->name etc...

Как с таким бороться?
Loveorigami
Сообщения: 977
Зарегистрирован: 2014.08.27, 21:54

Re: Архитектура таблиц

Сообщение Loveorigami »

Нет. Все вытягивается одним запросом в select (country.name as countryName). В модель добавил трейт с этими ссвойствами. Обращаюь как $event->countryName
Loveorigami
Сообщения: 977
Зарегистрирован: 2014.08.27, 21:54

Re: Архитектура таблиц

Сообщение Loveorigami »

Или же сделайте хелпер и обрашайтесь как EventHelper::country($model), внутри которого будет вся ваша цепочка
myks1992@mail.ru
Сообщения: 147
Зарегистрирован: 2017.11.15, 23:54

Re: Архитектура таблиц

Сообщение myks1992@mail.ru »

Loveorigami писал(а): 2019.06.09, 09:22 Или же сделайте хелпер и обрашайтесь как EventHelper::country($model), внутри которого будет вся ваша цепочка
Понял) Благодарю))
Loveorigami
Сообщения: 977
Зарегистрирован: 2014.08.27, 21:54

Re: Архитектура таблиц

Сообщение Loveorigami »

Ну значит я зря парюсь над этим)) И ничего страшного в этом нет, что будет много join запросов))
Нет, запрос должен быть один со многими join-связями ;) .
Например (в продолжении поста выше).

Код: Выделить всё

# Выборка цен по объектам
SELECT *
FROM (SELECT `minPrice`,
             `objId`,
             `roomId`,
             `roomName`,
             `roomOsn`,
             `roomDop`,
             `dateFrom`,
             `dateTo`,
             `mode`
      FROM ((SELECT `q1`.*
             FROM (SELECT 1                 AS mode,
                          `d`.`data`        AS `minPrice`,
                          `r`.`object_id`   AS `objId`,
                          `r`.`id`          AS `roomId`,
                          `r`.`name`        AS `roomName`,
                          `r`.`osn`         AS `roomOsn`,
                          `r`.`dop`         AS `roomDop`,
                          `row`.`id`        AS `rowId`,
                          `row`.`date_from` AS `dateFrom`,
                          `row`.`date_to`   AS `dateTo`
                   FROM `price__tbl_data` `d`
                          INNER JOIN `price__tbl_col` `col` ON `d`.`col_id` = `col`.`id`
                          INNER JOIN `price__tbl_row` `row` ON `d`.`row_id` = `row`.`id`
                          INNER JOIN `price__tbl_item` `tbl` ON `row`.`tbl_id` = `tbl`.`id`
                          INNER JOIN `object__room` `r` ON `tbl`.`room_id` = `r`.`id`
                   WHERE (`d`.`data` > 0)
                     AND ((`row`.`date_from` <= '2018-12-05') AND (`row`.`date_to` >= '2018-12-05'))
                     AND ((`col`.`type_id` = 1) AND (`col`.`status` = TRUE))
                     AND ((`row`.`status` = TRUE) AND (`row`.`date_to` >= '2018-12-05') AND ((`tbl`.`status` = TRUE) AND (`r`.`status` = TRUE)))) `q1`
                    INNER JOIN (SELECT r.object_id, MIN(d.data) AS minData
                                FROM `price__tbl_data` `d`
                                       INNER JOIN `price__tbl_col` `col` ON `d`.`col_id` = `col`.`id`
                                       INNER JOIN `price__tbl_row` `row` ON `d`.`row_id` = `row`.`id`
                                       INNER JOIN `price__tbl_item` `tbl` ON `row`.`tbl_id` = `tbl`.`id`
                                       INNER JOIN `object__room` `r` ON `tbl`.`room_id` = `r`.`id`
                                WHERE (`d`.`data` > 0)
                                  AND ((`row`.`date_from` <= '2018-12-05') AND (`row`.`date_to` >= '2018-12-05'))
                                  AND ((`col`.`type_id` = 1) AND (`col`.`status` = TRUE))
                                  AND ((`row`.`status` = TRUE) AND (`row`.`date_to` >= '2018-12-05') AND ((`tbl`.`status` = TRUE) AND (`r`.`status` = TRUE)))
                                GROUP BY `r`.`object_id`) `q2` ON q1.objId = q2.object_id AND q1.minPrice = q2.minData
             ORDER BY `q1`.`minPrice`)
            UNION ALL
            (SELECT `q1`.*
             FROM (SELECT 2                 AS mode,
                          `d`.`data`        AS `minPrice`,
                          `r`.`object_id`   AS `objId`,
                          `r`.`id`          AS `roomId`,
                          `r`.`name`        AS `roomName`,
                          `r`.`osn`         AS `roomOsn`,
                          `r`.`dop`         AS `roomDop`,
                          `row`.`id`        AS `rowId`,
                          `row`.`date_from` AS `dateFrom`,
                          `row`.`date_to`   AS `dateTo`
                   FROM `price__tbl_data` `d`
                          INNER JOIN `price__tbl_col` `col` ON `d`.`col_id` = `col`.`id`
                          INNER JOIN `price__tbl_row` `row` ON `d`.`row_id` = `row`.`id`
                          INNER JOIN `price__tbl_item` `tbl` ON `row`.`tbl_id` = `tbl`.`id`
                          INNER JOIN `object__room` `r` ON `tbl`.`room_id` = `r`.`id`
                   WHERE (`d`.`data` > 0)
                     AND ((`col`.`type_id` = 1) AND (`col`.`status` = TRUE))
                     AND ((`row`.`status` = TRUE) AND (`row`.`date_to` >= '2018-12-05') AND ((`tbl`.`status` = TRUE) AND (`r`.`status` = TRUE)))) `q1`
                    INNER JOIN (SELECT r.object_id, MIN(d.data) AS minData
                                FROM `price__tbl_data` `d`
                                       INNER JOIN `price__tbl_col` `col` ON `d`.`col_id` = `col`.`id`
                                       INNER JOIN `price__tbl_row` `row` ON `d`.`row_id` = `row`.`id`
                                       INNER JOIN `price__tbl_item` `tbl` ON `row`.`tbl_id` = `tbl`.`id`
                                       INNER JOIN `object__room` `r` ON `tbl`.`room_id` = `r`.`id`
                                WHERE (`d`.`data` > 0)
                                  AND ((`col`.`type_id` = 1) AND (`col`.`status` = TRUE))
                                  AND ((`row`.`status` = TRUE) AND (`row`.`date_to` >= '2018-12-05') AND ((`tbl`.`status` = TRUE) AND (`r`.`status` = TRUE)))
                                GROUP BY `r`.`object_id`) `q2` ON q1.objId = q2.object_id AND q1.minPrice = q2.minData
             ORDER BY `q1`.`minPrice`)) `uq`
      ORDER BY `mode`, `minPrice`) `sq`
GROUP BY `objId`

Последний раз редактировалось Loveorigami 2019.06.10, 09:31, всего редактировалось 1 раз.
Loveorigami
Сообщения: 977
Зарегистрирован: 2014.08.27, 21:54

Re: Архитектура таблиц

Сообщение Loveorigami »

В AR примерно так (часть метода)

Код: Выделить всё

  /**
         * @var TblItemQuery $query
         */
        $query = TblItem::find()
            ->alias('tbl')
            ->select([
                'obj.id AS objId',
                'obj.name AS objName',
                'obj.slug AS objSlug',
                'obj.text_price AS objTextPrice',
                'town.slug AS townSlug',
                'type.slug AS typeSlug',
                'room.id AS roomId',
                'room.name AS roomName',
                'room.osn AS roomOsn',
                'room.dop AS roomDop',
                'roomcat.name AS roomCatName',
                'tbl.id AS tblId',
                'tbl.kf AS tblAdultKf', // коэффициент взрослых
                'tbl.text AS tblTextPrice', // текст под таблицей
                'txt.text AS tblTextTplPrice', // текст под таблицей
                'l.name AS lechenieName',
                'p.name AS pitanieName',
                'spoS.spoStandartTurist',
                'spoS.spoStandartAgent',
                'spoS.spoStandartMalutki',
                'spoA.spoAverageTurist',
                'spoA.spoAverageAgent',
                'spoA.spoAverageMalutki',
                'spoA.spoDays',
                'colAge.minAge',
                'colAge.maxAgeFree',
                'col.is_free AS isFree',
                new Expression("$this->totalDays AS totalDays"),
                new Expression("$kfSum AS kfSum"),
                new Expression("$this->kidAge AS kidAge"),
                new Expression('MIN(`row`.`date_from`) AS `date_min`'),
                new Expression('MAX(`row`.`date_to`) AS `date_max`'),
                $this->sumExpression('osnTblPriceSpo', $kfSum, $sum === self::AS_OSN),
                $this->sumExpression('osnTblPriceNetto', $kfSum, $sum === self::AS_OSN),
                $this->sumExpression('osnTblPriceFreeSpo', $kfSum, $sum === self::AS_FREE),
                $this->sumExpression('osnTblPriceFreeNetto', $kfSum, $sum === self::AS_FREE),
                $this->sumExpression('dopTblPriceSpo', $kfSum, $sum === self::AS_DOP),
                $this->sumExpression('dopTblPriceNetto', $kfSum, $sum === self::AS_DOP),
                $this->sumExpression('numTblPriceSpo', $kfSum, $sum === self::AS_MIX),
            ])
            ->innerJoinWith([
                'rows row' => static function (TblRowQuery $query) {
                    $query->innerJoinWith([
                        'data d' => static function (TblDataQuery $query) {
                            $query->innerJoinWith([
                                'col col' => static function (TblColQuery $query) {
                                    $query->innerJoinWith([
                                        'type coltype',
                                    ])->published();
                                },
                            ]);
                        },
                    ])->published();
                },
            ])
            ->innerJoinWith([
                'room room' => static function ($query) {
                    /** @var ActiveQuery $query */
                    $query
                        ->innerJoinWith([
                            'item obj' => static function ($query) {
                                /** @var ActiveQuery $query */
                                $query->innerJoinWith(['town town', 'type type']);
                            },
                        ])
                        ->innerJoinWith(['category roomcat'])
                        ->published();
                },
            ])
            ->joinWith(['lechenie l', 'pitanie p', 'textRelation txt'])
            ->leftJoin(['colAge' => $this->getMinChildAgeQuery()], 'colAge.tblId = tbl.id')
            ->leftJoin(['spoS' => $this->getSpoStandartQuery()], 'spoS.tblId = tbl.id')
            ->leftJoin(['spoA' => $this->getSpoAverageQuery()], 'spoA.tblId = tbl.id')
            ->where(['in', 'col.id', $colIds])
            ->andWhere(
                [
                    'OR',
                    [
                        'AND',
                        ['=', 'col.is_free', TblCol::IS_FREE],
                        ['>=', 'd.data', 0],
                    ],
                    ['>', 'd.data', 0],
                ]
            )
            ->andWhere(
                [
                    'OR',
                    ['tbl.kf' => $this->adults], // убираем таблицы с коэффициентом не для расчета
                    ['tbl.kf' => null],
                ]
            )
            ->andWhere(
                [
                    'OR',
                    [
                        'AND',
                        ['>=', 'row.date_to', $this->dateFrom],
                        ['<=', 'row.date_from', $this->dateTo],
                    ],
                    [
                        'AND',
                        ['>=', 'row.date_from', $this->dateFrom],
                        ['<=', 'row.date_to', $this->dateTo],
                    ],
                ]
            )
            ->andWhere(['obj.raschet_online' => 1])
            ->groupBy('row.tbl_id')
            ->having(['>=', 'date_max', $this->dateTo])
            ->andHaving(['<=', 'date_min', $this->dateFrom])
            ->andFilterWhere(['room.object_id' => $this->objId])
            ->andFilterWhere(['room.id' => $this->roomId])
            ->andFilterWhere(['tbl.id' => $this->tblId])
            ->byMinMaxPeriod($this->totalDays)
            ->published();
            
        //print_r($query->createCommand()->rawSql);
        
        $this->setUnion($query);

myks1992@mail.ru
Сообщения: 147
Зарегистрирован: 2017.11.15, 23:54

Re: Архитектура таблиц

Сообщение myks1992@mail.ru »

Вот это запросик ахахха)) Никогда такогого не встречал во фреймворке)) Буду изучать и делать подобные запросы))) Благодарю!
Ответить