在规范化数据库模式中访问数据的最佳方法是什么?

时间:2009-02-08 17:59:27

标签: php sql database postgresql

我遇到了一个不断出现规范化数据库的问题,并且正在寻找最佳解决方案。

假设我有一个专辑信息数据库。我想以规范化的方式设置架构,所以我设置了两个表 - 专辑,每个专辑有一个列表,还有歌曲,列出了专辑中包含的所有歌曲。

albums
------
aid
name

songs
-----
aid
sid
length

此设置适用于以标准化方式存储数据,因为相册可以包含任意数量的歌曲。但是,以直观的方式访问数据现在变得更加困难。只收集单个相册信息的查询很简单,但如何在一个查询中同时获取多个相册?

到目前为止,我提出的最佳答案是通过辅助分组并将歌曲信息转换为数组。例如,结果看起来像这样:

aid, sids,      lengths
1,   [1, 2],    [1:04, 5:45]
2,   [3, 4, 5], [3:30, 4:30, 5:30]

当我想处理数据时,我必须解析sids和length,这似乎是一个毫无意义的练习:我正在使数据库连接一堆值,以便稍后将它们分开。

我的问题:使用这种模式访问数据库的最佳方法是什么?我是否坚持使用多个阵列?我应该将一个歌曲的整个信息存储在一个对象中,然后将这些歌曲存储到一个阵列中,而不是拥有多个阵列吗?或者有没有办法在结果集中添加任意数量的列(无限连接类型),以容纳N个歌曲?我对如何最好地访问数据的任何想法持开放态度。

我也关注效率,因为这些查询会经常运行。

如果它有所不同,我正在使用PostgreSQL数据库和PHP前端。

6 个答案:

答案 0 :(得分:3)

我很难看到你的观点。 “你如何在一次查询中一次抓取多张专辑”究竟是什么意思?你究竟有什么困难?

我会直截了当地说:

SELECT
  a.aid    album_id,
  a.name   album_name,
  s.sid    song_id,
  s.name   song_name,
  s.length song_length
FROM
  albums a
  INNER JOIN songs s ON a.aid = s.aid
WHERE
  a.aid IN (1, 2, 3)

SELECT
  a.aid         album_id,
  a.name        album_name,
  COUNT(s.sid)  count_songs,
  SUM(s.length) sum_length   /* assuming you store an integer seconds value  */
FROM                         /* here, not a string containing '3:18' or such */
  albums a
  INNER JOIN songs s ON a.aid = s.aid
WHERE
  a.aid IN (1, 2, 3)
GROUP BY
  a.aid

取决于您想知道/显示的内容。您可以在数据库中查询聚合信息,也可以在应用程序的查询结果#1中自行计算。

根据您的应用中缓存的数据量以及查询所需的时间,一种策略可能比另一种策略更快。不过,我建议查询数据库。 DB是为这种东西而制作的。

答案 1 :(得分:2)

  

我明白了你的观点,但我对第一个查询有问题,因为你最终会有大量的重复数据 - 专辑名称会重复多次。我也想尝试吃蛋糕 - 我希望数据尽可能紧凑,但如果没有聚合,这是不现实的。

啊,我现在明白你的问题了。你问的是如何最好地微观优化大多数情况下实际上并不昂贵的东西。你正在解决的解决方案实际上要比它试图解决的“问题”效率低得多。

我的建议是加入表格并返回所需的列。对于少于10,000条记录返回的内容,您不会注意到将AlbumName与每首歌曲记录一起交回时会有任何重大的电汇时间损失。

如果您发现它在现场表现缓慢,请进行优化。但请记住,许多聪明人花了大约50年的时间研究,使“加入表格并快速返回您需要的”解决方案。我怀疑你会用你的家庭滚动字符串连接/去连接策略来打败它。

答案 2 :(得分:1)

我同意Jason Kester的观点,因为我认为即使你有10列重复数据,这在实践中也不太可能成为性能瓶颈。但是,如果您倾向于删除重复的数据,那么我建议使用2个查询:

查询#1:

SELECT sid, length     -- And whatever other per-song fields you want
FROM songs
ORDER BY aid

查询#2:

SELECT aid, a.name, COUNT(*)
FROM albums a
JOIN songs s USING (aid)
GROUP BY aid, a.name
ORDER BY aid, a.name

第二个查询使您可以将第一个查询的输出分解为适当的段。请注意,只有在您认为不会对这两个查询之间的表进行任何更改时,这才能可靠地运行 - 否则您将需要与SET TRANSACTION ISOLATION LEVEL SERIALIZABLE进行交易。

同样,您使用两个单独查询这一事实可能会使整体速度变慢,因为在大多数情况下,网络延迟加倍+查询解析+查询计划可能会淹没网络吞吐量的有效增长。但至少你不会有那种发送重复数据的令人讨厌的可怕感觉......:)

答案 3 :(得分:0)

连接查询将要求数据库将表放在一起,匹配id并返回单个表。这样,数据可以动态配置为当前任务,非规范化数据库无法做到这一点。

答案 4 :(得分:0)

SELECT aid,GROUP_CONCAT(sid) FROM songs GROUP BY aid; 

+----+-------------------------+
|aid | GROUP_CONCAT(sid)       |
+----+-------------------------+
|  3 | 5,6,7                   |
+----+-------------------------+

答案 5 :(得分:-1)

我不会打破你的正常化。保留表格,然后使用以下内容查询 - How to concatenate strings of a string field in a PostgreSQL 'group by' query?