Я пытаюсь придумать наиболее эффективную схему базы данных для конкретной структуры данных. Есть два основных объекта: Курсы и Темы. Курс представляет собой набор Темы. Тема имеет такие поля, как Ролики, Ресурсы и Общее время видео.
Визуальное представление этой структуры данных:
- Course
|_ ID: 12345
|_ Themes: [A, B] (an array of UIDs)
- Theme A
|_ Courses: [12345,67890] (an array of UIDs)
|_ Videos: [1,2,3,4,5,7] (an array of UIDs)
|_ Resources: [10,11,12] (an array of UIDs)
|_ Video Total Time: 10000 (probably stored as seconds as tinyint field)
- Theme B
|_ Courses: [12345,98765] (an array of UIDs)
|_ Videos: [5,6,7,8] (an array of UIDs)
|_ Resources: [12,13,14] (an array of UIDs)
|_ Video Total Time: 20000 (probably stored as seconds as tinyint field)
Чего я пытаюсь добиться, так это схемы базы данных для двух таблиц, одной для Курсы и одной для Темы. Идея состоит в том, чтобы запрос MySQL получал Курс и группировал все поля из Темы. Другими словами, когда я получаю результат запроса MySQL (используя PHP), я получаю такой массив или объект:
Array(
'ID' => 12345
'themes' => [A,B]
'videos' => [1,2,3,4,5,6,7,8]
'resources' => [10,11,12,13,14]
'video_total_time' => 30000
)
Итак, дело в том, что это две реляционные базы данных. Когда я отправляю запрос в БД с запросом данных из видео, мне нужно извлечь данные из всех тем и объединить их вместе.
Поскольку я не эксперт по SQL/MySQL, я пытаюсь немного узнать об этом, пока пытаюсь понять:
1) Какова наилучшая схема базы данных для этих двух объектов? Курсы и темы? Особое внимание к производительности
2) Могу ли я получить окончательные данные с помощью SQL? Или я должен извлечь некоторые данные из базы данных, а затем проанализировать данные с помощью PHP? Что обычно быстрее?
3) Как лучше всего хранить массив UID? В виде строки? Или есть лучший способ хранения?
Основной целью этого является производительность. У меня есть такие данные в другой схеме базы данных, объединенные с тысячами других типов данных (базы данных WP, таблицы wp_posts / wp_postmeta), но сейчас очень медленно получить нужную мне информацию.
Любые советы и предложения более чем приветствуются!
Было сложно решить, какой ответ лучше всего соответствует моим потребностям, потому что ответы @TimMorton и @PaulSpiegel ведут нас по одному и тому же пути, но с немного разными подходами. Ответ Тима отлично подходит для понимания того, как правильно проектировать схемы базы данных с учетом отношений «многие ко многим» и как организовывать ваши запросы. Но поскольку основное внимание в этом вопросе уделяется повышению производительности, ответ Пола больше сосредоточен на этом, с конкретными подробностями о первичных ключах и индексах (которые имеют основополагающее значение для повышения производительности запросов).
В любом случае, я многое узнал о разработке схемы базы данных. Вот уроки, которые я усвоил:
Я не знаю, правильно ли я со всем вышеизложенным, но это то, что я узнал до сих пор. Надеюсь, это поможет кому-то еще.
Привет, @TimMorton, спасибо за комментарий. Итак, вы думаете, что наиболее эффективный способ получить все данные, которые мне нужны, — это разбить информацию на несколько таблиц? Идея иметь поле video_total_time для тем состоит в том, чтобы получить эту информацию быстрее. Но, поскольку я новичок в SQL/MySQL, может быть, запрос к базе данных всех видео по теме, и они делают сумму всех видео, не будет таким медленным, как я думаю?
См. это-хранение-списка-разделителей-в-колонке-базы-данных-действительно-это-плохо. Затем вы можете понять, что проектируете катастрофу производительности.
Эй, @PaulSpiegel, спасибо за ссылку, очень информативно. Как я уже сказал, я новичок в разработке правильной схемы базы данных. Как бы вы сделали эти реляционные поля? Должен ли я построить реляционную таблицу для каждой комбинации? IOW: одна таблица для тем курса <=>, одна для тематических <=> видео, одна для тематических <=> ресурсов?
Нормализованная схема будет иметь одну таблицу для каждого объекта и одну таблицу для отношения «многие ко многим». Отношения «один ко многим» решаются с помощью ссылки (внешнего ключа) в дочерней таблице (поэтому для этих отношений нет дополнительных таблиц). Насколько я вижу, у вас нет отношений «многие ко многим». Итак, вам понадобятся четыре таблицы: courses, themes, video и ressources.
Если я правильно понимаю (исходя из желаемого результата), в курсе может быть много тем, в курсе может быть много видео, в курсе может быть много ресурсов. Однако, глядя на визуальное представление данных, тема также может содержать много видео, ресурсов и курсов. Это означало бы отношение многие ко многим между темами и курсами, не так ли? И тогда вопрос: видео привязаны к теме или к курсу, или к тому и другому? Так же и с ресурсами...
@TimMorton, ты прав, это отношения многие ко многим. У курса может быть много тем, а у темы может быть много курсов. Кроме того, у темы может быть много видео и ресурсов, а у видео/ресурса может быть много тем. Я обновлю визуальное представление, чтобы оно было более понятным.
Я обновлю свой ответ, когда у меня будет время, хотя между ответом Пола и моим ответом у вас должно быть достаточно информации, чтобы экстраполировать то, что вам нужно.






Создайте таблицы, подобные изображению, как показано, и используйте время кодирования/декодирования json для ввода/вывода. В запросе вы можете получить общее время из таблицы.
Я лично нормализовал бы это больше, чем это. Вы сохраняете внешние ключи в varchar, предположительно, в формате csv? Это совсем не полезно в реляционной базе данных.
В простейшей форме, предполагая отношения не многие ко многим:
Course Theme
-------- --------
CourseID <--+ ThemeId
Name | Name
+------ CourseID
|
|
| Video
| --------
| VideoID
| Name
| Length
+------ CourseID
|
|
| Resource
| --------
| ResourceID
| Name
+------ CourseID
В этой форме курс может иметь много тем, много видео и много ресурсов; но у каждой темы, видео и ресурса может быть только один курс.
Однако я не думаю, что вы этого хотите.
я бы больше склонялся к
Course Theme
-------- --------
+----> CourseId +---> ThemeId
| Name | Name
| ThemeId ----+
|
|
| Video
| --------
| VideoID
| Name
| Length
+------ CourseID
|
|
| Resource
| --------
| ResourceID
| Name
+------ CourseID
Это позволяет курсу иметь только одну тему, но много видео и ресурсов. Это позволяет темам иметь более одного курса.
Но все равно не совсем подходит...
Это позволяет многим курсам использовать одну и ту же тему, а также иметь более одной темы:
Course Course_Theme Theme
-------- ------------ --------
+----> CourseId <----- CourseId +--> ThemeId
| Name ThemeId ---+ Name
| ThemeId
|
|
| Video
| --------
| VideoID
| Name
| Length
+------ CourseID
|
|
| Resource
| --------
| ResourceID
| Name
+------ CourseID
В настоящее время каждый курс может иметь множество тем, видео и ресурсов. Каждая тема может иметь много курсов. Каждое видео и ресурс принадлежат курсу (т. е. могут иметь только один курс).
Если видео или ресурс может относиться к более чем одному курсу, вам придется расширить его так же, как я сделал с темами.
Согласно комментарию, все много ко многим. Заметьте, у меня нет прямой связи между темами и видео, а также между темами и ресурсами. Я не думаю, что они будут необходимы; вы должны быть в состоянии подобрать то, что вам нужно, проходя курсы.
Course Course_Theme Theme
-------- ------------ --------
+----> CourseId <---- CourseId
| Name ThemeId ----------> ThemeId
| Name
|
| Course_Video Video
| ------------ --------
+---------------------- CourseId
| VideoId ----------> VideoId
| Name
| Length
|
| Course_Resource Resource
| --------------- --------
+----------------------- CourseId
ResourceId -------> ResourceId
Name
Url, etc.
Теперь о запросах. Хотя можно использовать агрегатные функции вместе с группировкой, я думаю, что гораздо разумнее сделать это проще и просто вытаскивать элементы по одному.
Themes per course
SELECT T.*
FROM COURSE C
INNER JOIN COURSE_THEME CT ON CT.COURSEID=C.COURSEID
INNER JOIN THEME T ON CT.THEMEID=T.THEMEID
WHERE {insert your search conditions on course}
or, if you know CourseId:
SELECT T.*
FROM THEME T
INNER JOIN COURSE_THEME CT ON T.THEMEID = CT.THEMEID
WHERE CT.COURSEID = ?
likewise,
Videos per course
SELECT V.*
FROM COURSE C
INNER JOIN COURSE_VIDEO CV ON CV.COURSEID=CV.COURSEID
INNER JOIN VIDEO ON CV.VIDEOID=V.VIDEOID
WHERE {insert your search conditions on course}
or, if you know the CourseId:
SELECT V.*
FROM VIDEO V
INNER JOIN COURSE_VIDEO CV ON CV.VIDEOID = V.VIDEOID
WHERE CV.COURSEID = ?
to select the sum of the video lengths per course,
SELECT SUM(LENGTH) AS TOTAL
FROM VIDEO
INNER JOIN COURSE_VIDEO CV ON CV.VIDEOID = V.VIDEOID
WHERE CV.COURSEID = ?
GROUP BY CV.COURSEID
Now, the tricky part is videos per theme. I am making an assumption here: the set of videos per theme is the same as the set of videos per course per theme.
The long way around:
SELECT V.*
FROM VIDEO V
INNER JOIN COURSE_VIDEO CV ON VIDEO.VIDEOID = CV.VIDEOID
INNER JOIN COURSE C ON COURSEID = CV.COURSEID
INNER JOIN COURSE_THEME CT ON C.COURSEID = CT.COURSEID
INNER JOIN THEME T ON CT.THEMEID = T.THEMEID
WHERE THEMEID = ?
Blech. You can cut out the middlemen:
SELECT V.*
FROM VIDEO V
INNER JOIN COURSE_VIDEO CV ON VIDEO.VIDEOID = CV.VIDEOID
INNER JOIN COURSE_THEME CT ON CV.COURSEID = CT.COURSEID
WHERE CT.THEMEID = ?
Когда ваши таблицы нормализованы, вы можете получить любую информацию из любой исходной точки, которую вы выберете. FWIW, ваш пример довольно сложный, поскольку все отношения многие ко многим.
Обновлять
Несмотря на то, что у меня были курсы в качестве корневых, даже когда темы являются корневыми, ничего не меняется:
Theme Course_Theme Course
-------- ------------ --------
+----> ThemeId <---- ThemeId
| Name CourseId ---------> CourseId
| Name
|
| Theme_Video Video
| ------------ --------
+---------------------- ThemeId
| VideoId ---------> VideoId
| Name
| Length
|
| Theme_Resource Resource
| -------------- --------
+----------------------- ThemeId
ResourceId ------> ResourceId
Name
Url, etc.
В этой конфигурации курсы имеют видео и ресурсы через ThemeId, то есть:
SELECT V.*
FROM COURSE_THEME CT
INNER JOIN VIDEO_THEME VT ON VT.THEMEID = CT.THEMEID
INNER JOIN VIDEO V ON V.VIDEOID = VT.VIDEOID
WHERE CT.THEMEID = ?
TimMorton здорово, мне очень нравится, как ты все подробно описал. Но ответ @PaulSpiegel правильно сказал о темах. На самом деле они являются основной частью головоломки. Тема представляет собой набор видео и ресурсов. А курс — это набор тем. В этом проекте вы создаете курс, выбирая группу тем. Здесь вы подключили видео и ресурсы напрямую к курсам. Извините, это моя вина, я должен был лучше объяснить все. Можете ли вы обновить свой ответ с этими требованиями?
Если темы может совместно использовать ролики и ресурсы, то это будут отношения многие ко многим.
В этом случае вам понадобятся отдельные таблицы для этих отношений.
Удалите столбец theme_id из videos и ressources и добавьте следующие таблицы:
Здесь вы должны определить составной первичные ключи на (theme_id, video_id) и (theme_id, ressource_id).
Также создайте обратные индексы для (video_id, theme_id) и (ressource_id, theme_id).
Предполагая, что вы знаете идентификатор курса (который равен 123), затем вы можете получить соответствующие данные (из схемы многие ко многим) со следующими запросами (которые вы выполняете один за другим):
select c.*
from courses c
where c.id = 123;
select t.*
from themes t
where t.course_id = 123;
select distinct v.*
from themes t
join themes_videos tv on tv.theme_id = t.id
join videos v on v.id = tv.video_id
where t.course_id = 123;
select distinct r.*
from themes t
join themes_ressources tr on tr.theme_id = t.id
join ressources r on r.id = tr.ressource_id
where t.course_id = 123;
Затем составьте свой массив/объект из полученных данных в PHP.
Попытка получить все данные с помощью одного запроса SQL не всегда является хорошей идеей. Вы просто делаете свой код и схему слишком сложными. Выполнение пары запросов — это не конец света. Чего следует избегать, так это выполнения запроса в цикле. (например: для каждой темы выберите соответствующие видео).
Хороший совет по производительности. Это является возможно получить все это в одном запросе (при условии, что MySQL имеет агрегатные функции), но это было бы... волосатым. Лучше сделать это простым, чтобы вы могли объяснить это в коде для следующего парня.
@paulSpiegel спасибо за ответ, очень полезно! Могу ли я попросить разъяснения по поводу таблиц «многие ко многим»? Например, в themes_videos вы говорите, что и theme_id, и video_id являются первичными ключами, а также внешними ключами, верно? Итак, должен ли я настроить таблицу, чтобы добавить их в качестве первичных ключей, а ТАКЖЕ создать составной первичный ключ? Кроме того, я слышал, что обратные индексы работают только для MySQL >= 8.0. Если я использую более старую версию (например, 5.x), можно ли создать обратный индекс?
@DiegodeOliveira Я имею в виду первичный ключ один (на таблицу) с двумя столбцами. Например. PRIMARY KEY (theme_id, video_id), INDEX (video_id, theme_id). Под обратным индексом я имею в виду не DESC, а обратный порядок столбцов.
@PaulSpiegel, о, понял! Спасибо за объяснение! Теперь это имеет больше смысла для меня!
Было бы ошибкой думать об этом как о двух таблицах. Я вижу темы, курсы, видео и ресурсы. Подсказка заключается в том, что каждый раз, когда вы видите массив, думайте о таблице. Например, видео: видео будет иметь идентификатор, название и продолжительность. Вам не понадобится поле под названием «Общее время видео»; это будет просто суммировано в вашем запросе.