Dju*_*uka 5 mysql performance subquery query-performance
我需要用他们的历史来存储价格。价格可以具有不同的特异性。它可以指单个用户(最具体)、组(不太具体)和产品价格(全局 - 最不具体)。还有两种不同类型的价格(产品价格和交货价格)。现在我遇到了这个问题。我让它工作了,但是当我编写查询时我不知何故感觉不好(它们往往变得冗长而复杂)。
我设计了两个这样的表(它们是相同的):
| id | referent_id | product_id | valid_from | valid_until | amount | pieces_per_lot |
Run Code Online (Sandbox Code Playgroud)
| id | referent_id | product_id | valid_from | valid_until | amount | pieces_per_lot |
Run Code Online (Sandbox Code Playgroud)
对于用户特定的价格参考 id 将是一个正整数,匹配用户 id。对于特定于组的价格参考 id 将是一个负整数,匹配组 id(我将组 id 设为负整数)。对于全球产品价格,参考 ID 将为 NULL。这样,当我查询特定项目和特定用户及其组的价格时,我需要做的就是按 item_id 过滤表并按 referent_id 列按降序排列结果。这样,特定于用户的在顶部,组之后和全局在最后。
两行的日期范围 valid_from-valid_until 不能重叠。
现在,获取当前价格的查询将如下所示:
SELECT
*
FROM
base_prices AS outer
WHERE
id = (
SELECT
id
FROM
base_prices AS inner
WHERE
NOW() BETWEEN valid_from AND valid_until
AND inner.item_id = outer.item_id
AND (
inner.referent_id = # HERE GOES USER ID
OR inner.referent_id = # HERE GOES GROUP ID
OR inner.referent_id IS NULL
)
ORDER BY
inner.referent_id DESC # FOR SPECIFICITY
Run Code Online (Sandbox Code Playgroud)
困扰我的是我总是必须编写这种子查询来获取项目,这对我来说似乎不是一个好的设计。当数据开始建立时,此查询将花费更多。此外,我必须编写一些触发器以在插入和更新期间保持数据完整性。
我的观点是当前价格和历史价格需要分开,但我很着急,所以这就是出现的原因。我正在考虑在基于 valid_from 列的插入期间安排事件,该列会将数据从历史价格表移动到(新)当前价格表。
我没有经验,所以这对我来说似乎有点奇怪。不过,这可能没问题,但我希望看到一些建议和意见。
如果我遗漏了什么或没有足够的信息,请发表评论。
有几种方法可以将当前值与历史值分开:您可以简单地包含一个对于最新价格为 true 的布尔字段并对其进行过滤,或者您可以将当前价格保留在单独的表中。这两种选择都需要一些额外的工作来保持完整性,但会使当前价格的查询更加高效。这并不能消除子查询选择与用户/组/NULL 条件匹配的第一行的需要,尽管在处理内部和外部查询时将扫描更少的行。
如果基表中只有当前价格,则以下内容作为直接查询可能会更有效(如果将历史值保留在同一个表中,请将is_latest=true支票或当前日期过滤器添加到whereand子句中):on
SELECT CASE WHEN usermatch.id IS NOT NULL THEN usermatch.field1 WHEN groupmatch.id IS NOT NULL THEN groupmatch.field1 ELSE nullmatch.field1 END AS field1
, CASE WHEN usermatch.id IS NOT NULL THEN usermatch.field2 WHEN groupmatch.id IS NOT NULL THEN groupmatch.field2 ELSE nullmatch.field2 END AS field2
(... and so on ...)
, CASE WHEN usermatch.id IS NOT NULL THEN usermatch.fieldN WHEN groupmatch.id IS NOT NULL THEN groupmatch.fieldN ELSE nullmatch.fieldN END AS fieldN
FROM base_prices AS nullmatch
LEFT OUTER JOIN
base_prices AS groupmatch ON groupmatch.id=nullmatch.id AND nullmatch.referent_id = @GroupIDHere
LEFT OUTER JOIN
base_prices AS usermatch ON usermatch.id=nullmatch.id AND nullmatch.referent_id = @UserIDHere
WHERE nullmatch.referent_id IS NULL
Run Code Online (Sandbox Code Playgroud)
仅当每个产品始终有一行referent_id 为空时,这才有效,否则您可以尝试:
SELECT CASE WHEN usermatch.id IS NOT NULL THEN usermatch.field1 WHEN groupmatch.id IS NOT NULL THEN groupmatch.field1 ELSE nullmatch.field1 END AS field1
, CASE WHEN usermatch.id IS NOT NULL THEN usermatch.field2 WHEN groupmatch.id IS NOT NULL THEN groupmatch.field2 ELSE nullmatch.field2 END AS field2
(... and so on ...)
, CASE WHEN usermatch.id IS NOT NULL THEN usermatch.fieldN WHEN groupmatch.id IS NOT NULL THEN groupmatch.fieldN ELSE nullmatch.fieldN END AS fieldN
FROM users
CROSS JOIN
base_prices AS nullmatch
LEFT OUTER JOIN
base_prices AS groupmatch ON groupmatch.id=nullmatch.id AND nullmatch.referent_id = users.groupid
LEFT OUTER JOIN
base_prices AS usermatch ON usermatch.id=nullmatch.id AND nullmatch.referent_id = users.id
WHERE users.id = @UserIDHere
AND nullmatch.referent_id IS NULL
Run Code Online (Sandbox Code Playgroud)
在这两种情况下,在视图中使用此构造时都要小心,因为通过这些 case 语句导出的字段进一步过滤将无法使用您可能定义的任何索引。
显然,每个字段的 case 语句有点麻烦,但它应该停止查询运行程序为返回的每一行搜索一次表,就像子查询安排一样。
在其他数据库中,您可能可以使用窗口函数(如ROW_NUMBER)来更方便、更高效地完成此操作,但据我所知,mySQL 不支持这些功能。
| 归档时间: |
|
| 查看次数: |
831 次 |
| 最近记录: |