我正在尝试针对特定数据结构考虑性能最高的数据库架构。主要有两个实体:课程和主题。 课程是主题的集合。 主题具有诸如视频,资源和视频总时间之类的字段。
直观地表示此数据结构:
- 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查询获得一个 Course (课程)并将来自 Themes (主题)的所有字段分组。换句话说,当我得到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的答案将我们引向同一条道路,但方法略有不同。 Tim的答案非常有用,它有助于您了解如何正确设计数据库架构,考虑多对多关系以及如何组织查询。但是,由于此问题的主要重点是提高性能,因此Paul的答案更加侧重于此,它具有有关主键和索引的特定详细信息(这是提高查询性能的基础)。
无论如何,我学到了很多有关设计数据库模式的知识。这是我学到的教训:
我不知道我是否对上述所有内容都正确,但这是我到目前为止所学到的。希望这也会对其他人有所帮助。
答案 0 :(得分:1)
以最简单的形式,假设不存在多对多关系:
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.
现在查询。尽管可以将聚合函数与group by一起使用,但我认为保持简单并一次只提取一个东西更有意义。
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 = ?
答案 1 :(得分:1)
如果主题可以共享视频和资源,那么这将是多对多关系。
在这种情况下,您需要为这些关系使用单独的表。
从theme_id
和videos
中删除ressources
列,并添加以下表格:
在这里,您应该在(theme_id, video_id)
和(theme_id, ressource_id)
上定义复合主键。
还要在(video_id, theme_id)
和(ressource_id, theme_id)
上创建反向索引。
假设您知道课程的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查询获取所有数据并不总是一个好主意。 您只是使您的代码和架构过于复杂。 执行几个查询并不是世界末日。 您应该避免的是循环运行查询 (例如:为每个主题选择相关的视频)。
答案 2 :(得分:-1)
使表如图所示,并使用json输入/输出的编码/解码时间。在查询中,您可以从表中获得总时间。