9

我正在寻找一种方法来为 MySQL 表中的每条记录输出选定的相关记录。我会进一步解释...

我有 2 个表currencyexchange_rates。这些表由currency_code字段连接,每条货币记录有多个相关的汇率记录,每条汇率记录代表不同的一天。所以货币和 exchange_rates 之间存在 1:many 的关系。

我想从exchange_rates表中检索每种货币的完整记录,但能够定义选择哪个相关记录的特定标准。不仅是每种货币的最新 exchange_rate,还可能exchange_rates是具有该字段的每种货币的最新记录criteria_x=NULL

很遗憾您不能LIMIT在派生表中使用,否则这样的东西将是一个简洁易读的解决方案......

SELECT `currencies`.`currency_code`, `currencies`.`country`, `exchange_rates`.`id`,
       FROM_UNIXTIME(`exchange_rates`.`datestamp`), `rate` 
FROM `currencies` 
INNER JOIN (
SELECT `id`, `currency_code`, `invoice_id`, `datestamp`, `rate` 
FROM `exchange_rates` 
WHERE `criteria_x`=NULL AND `criteria_y` LIKE 'A' 
ORDER BY `datestamp` DESC
LIMIT 0, 1
) AS `exchange_rates` ON `currencies`.`currency_code`=`exchange_rates`.`currency_code`
ORDER BY `currencies`.`country`

LIMIT子句应用于父查询而不是派生表。

这是我发现这样做的唯一方法......

SELECT `currencies`.`currency_code`, `currencies`.`country`, 
FROM_UNIXTIME( SUBSTRING_INDEX( SUBSTRING_INDEX(`exchange_rates`.`concat`, '-', 1), '-', -1)) AS `datestamp`,
SUBSTRING_INDEX( SUBSTRING_INDEX(`exchange_rates`.`concat`, '-', 2), '-', -1) AS `id`, 
SUBSTRING_INDEX( SUBSTRING_INDEX(`exchange_rates`.`concat`, '-', 3), '-', -1) AS `rate` 
FROM `currencies`
INNER JOIN (
SELECT `currency_code`, MAX(CONCAT_WS('-', `datestamp`, `id`, `rate`)) AS `concat`
FROM `exchange_rates` 
WHERE `criteria_x`=NULL AND `criteria_y` LIKE 'A' 
GROUP BY `exchange_rates`.`currency_code`
) AS `exchange_rates` ON `currencies`.`currency_code`=`exchange_rates`.`currency_code`
ORDER BY `currencies`.`country`

因此,将一堆字段连接在一起并在MAX()其上运行 a 以获得我在组内的排序顺序,然后在父查询中使用SUBSTRING_INDEX(). 问题是这种方法仅在我可以使用 a MIN()orMAX()连接字段时才有效。如果我想对字符串进行排序或按多个条件排序但仅限于单个记录,那将是不理想的。

此外,我不得不求助于可怕的字符串操作来从关系数据库中获取我想要的数据,这也让我感到身体上的痛苦——必须有更好的方法!

有人对更好的方法有任何建议吗?

4

4 回答 4

4

在尝试提供答案之前,有一些一般性问题需要(简要)讨论。

您的第一个查询是:

SELECT `currencies`.`currency_code`, `currencies`.`country`, `exchange_rates`.`id`,
       FROM_UNIXTIME(`exchange_rates`.`datestamp`), `rate` 
FROM `currencies` 
INNER JOIN (
SELECT `id`, `currency_code`, `invoice_id`, `datestamp`, `rate` 
FROM `exchange_rates` 
WHERE `criteria_x`=NULL AND `criteria_y` LIKE 'A' 
ORDER BY `datestamp` DESC
LIMIT 0, 1
) AS `exchange_rates` ON `currencies`.`currency_code`=`exchange_rates`.`currency_code`
ORDER BY `currencies`.`country`
  1. 我认为您不需要使用尽可能多的反引号。它们并没有完全错,但我不会在我的答案中输入它们。
  2. SQL 标准不认可这种criteria_x = NULL表示法;那应该写成criteria_x IS NULL。MySQL 可能允许它;只要你知道它是非标准的,你就可以使用。
  3. 如果标准不包含元字符(或在标准 SQL 中),则该标准LIKE 'A'是不明智的。使用简单的相等会更好:.%_= 'A'

你的问题说:

我想从exchange_rates表中检索每种货币的完整记录,但能够定义选择哪个相关记录的特定标准。不仅是每种货币的最新汇率,还可能是具有该字段的每种货币的最新汇率criteria_x IS NULL

因此,您希望为满足所需其他条件的每种货币选择最近的汇率记录。我们可以假设汇率表中currency_code和的组合存在唯一约束;datestamp这意味着总是最多有一个匹配的行。如果没有匹配的行,您没有指定应该显示的内容;当然,内部连接根本不会列出该货币。

对于 SQL 查询,我通常分步构建和测试整个查询,为之前开发的查询添加额外的材料,这些材料已知可以工作并产生正确的输出。如果它很简单和/或我收集了太多的狂妄自大,我会先尝试一个复杂的查询,但是当(克星)它不起作用时,我会回到构建和测试过程。将其视为测试驱动(查询)开发。

第 1 阶段:符合指定条件的汇率记录

SELECT id, currency_code, invoice_id, datestamp, rate 
  FROM exchange_rates 
 WHERE criteria_x IS NULL AND criteria_y = 'A' 
 ORDER BY currency_code, datestamp DESC

第 2 阶段:符合指定条件的每种货币的最近汇率时间

SELECT currency_code, MAX(datestamp) 
  FROM exchange_rates 
 WHERE criteria_x IS NULL AND criteria_y = 'A' 
 GROUP BY currency_code

第 3 阶段:符合指定条件的每种货币的最近汇率时间的汇率记录

SELECT x.id, x.currency_code, x.invoice_id, x.datestamp, x.rate 
  FROM exchange_rates AS x
  JOIN (SELECT currency_code, MAX(datestamp) AS datestamp
          FROM exchange_rates 
         WHERE criteria_x IS NULL AND criteria_y = 'A' 
         GROUP BY currency_code
       ) AS m
    ON x.currency_code = m.currency_code AND x.datestamp = m.datestamp

第 4 阶段:货币信息和符合指定条件的每种货币的最近汇率时间的汇率记录

这需要将货币表与上一个查询的输出连接起来:

SELECT c.currency_code, c.country, r.id,
       FROM_UNIXTIME(r.datestamp), r.rate
  FROM currencies AS c 
  JOIN (SELECT x.id, x.currency_code, x.invoice_id, x.datestamp, x.rate 
          FROM exchange_rates AS x
          JOIN (SELECT currency_code, MAX(datestamp) AS datestamp
                  FROM exchange_rates 
                 WHERE criteria_x IS NULL AND criteria_y = 'A' 
                 GROUP BY currency_code
               ) AS m
            ON x.currency_code = m.currency_code AND x.datestamp = m.datestamp
       ) AS r
    ON c.currency_code = r.currency_code
 ORDER BY c.country

除了 Oracle 只允许 ' ) r' 而不是 ' ) AS r' 用于表别名和使用之外FROM_UNIXTIME(),我相信它应该可以与您提到的几乎所有 SQL DBMS 的当前版本正常工作。

由于在最终查询中未返回发票 ID,我们可以将其从中间查询的选择列表中删除。一个好的优化器可能会自动执行此操作。

如果即使没有符合条件的汇率也想查看货币信息,则需要将最外层查询中的 JOIN 更改为 LEFT JOIN(又名 LEFT OUTER JOIN)。如果您只想查看货币的子集,您可以在最后(最外层)查询阶段应用该过滤器,或者(如果过滤器基于汇率表中可用的信息,例如货币代码)最里面的子查询(最有效)或中间子查询(效率不高,除非优化器意识到它可以将过滤器向下推到最里面的子查询)。

正确性通常是主要标准;性能是次要标准。但是,问题中提到了性能。第一条规则是衡量此处显示的“简单”查询。只有当这被证明太慢时,您才需要进一步担心。当您确实需要担心时,您可以检查查询计划,例如是否缺少关键索引。只有当查询仍然不够快时,您才开始尝试使用其他技巧。这些技巧往往是特定于特定 DBMS 的。例如,您可以使用优化器提示使 DBMS 以不同方式处理查询。

于 2012-05-13T20:39:01.090 回答
2

如果我正确理解了您的问题,您需要做的就是自行加入exchange_rates以选择利率:

SELECT   currencies.currency_code,
         currencies.country,
         exchange_rates.id,
         FROM_UNIXTIME(exchange_rates.datestamp),
         exchange_rates.rate
FROM     currencies
  JOIN   (
    SELECT   currency_code, MAX(datestamp) AS datestamp
    FROM     exchange_rates
    WHERE    criteria_x IS NULL AND criteria_y LIKE 'A'
    GROUP BY currency_code
  )   AS exchange_wantd USING (currency_code)
  JOIN   exchange_rates USING (currency_code, datestamp)
ORDER BY currencies.country
于 2012-05-09T12:45:52.763 回答
1

试试这个查询。预计可以正常工作,但如果您提供一些数据,我将能够正确完成

SELECT  `currencies`.`currency_code` as `CurrencyCode`,
    `currencies`.`country`, 
    FROM_UNIXTIME( SUBSTRING_INDEX( SUBSTRING_INDEX(`exchange_rates`.`concat`, '-', 1), '-', -1)) AS `datestamp`,
    SUBSTRING_INDEX( SUBSTRING_INDEX(`exchange_rates`.`concat`, '-', 2), '-', -1) AS `id`, 
    SUBSTRING_INDEX( SUBSTRING_INDEX(`exchange_rates`.`concat`, '-', 3), '-', -1) AS `rate`,
    (SELECT 
            MAX(CONCAT_WS('-', `datestamp`, `id`, `rate`)) AS `concat` 
            FROM `exchange_rates` 
            WHERE `criteria_x`= NULL 
            AND `criteria_y` LIKE 'A' 
            GROUP BY `exchange_rates`.`currency_code`
            HAVING `exchange_rates`.`currency_code` =`CurrencyCode`
    ) as `Concat`
FROM    `currencies`
ORDER BY `currencies`.`country` 
于 2012-05-04T06:11:21.363 回答
0

如果我正确理解你,如果你不强迫数据库像人类一样思考,答案很简单。与 Jonathan Leffler 一样,我理解您的意图是“为每种货币选择符合其他所需标准的最新汇率记录”。

“最新”当然是问题所在,因为该信息并未明确存储在数据库中,因此我们首先确定该信息。

SELECT currency_code, MAX(datestamp) AS datestamp FROM exchange_rates GROUP BY currency_code

我们将重用它,所以我们给结果一个名字

(SELECT currency_code, MAX(datestamp) AS datestamp FROM exchange_rates GROUP BY currency_code) AS dates_we_want

包含我们可能想要的所有信息的域是 dates_we_want、货币和 exchange_rates 表的每个可能组合的记录

(SELECT currency_code, MAX(datestamp) AS datestamp FROM exchange_rates GROUP BY currency_code) AS dates_we_want, currencies AS c, exchange_rates AS er

选择我们想要的记录:

  • 匹配货币代码

    dates_we_want.currency_code=er.currency_core 和 dates_we_want.currency_code=c.currency_core

  • 最近的费率

    dates_we_want.datesamp=er.datesamp

投影到结果中。你

想要从 exchange_rates 表中检索完整记录

简单地转化为

er.*

将所有内容放在一个 SELECT 语句中,并为您提供更改以放置您的任意约束:

SELECT er.*
FROM
    (SELECT currency_code, MAX(datestamp) AS datestamp
       FROM exchange_rates GROUP BY currency_code
    ) AS dates_we_want,
    currencies AS c, exchange_rates AS er
WHERE
    dates_we_want.currency_code=er.currency_core
AND
    dates_we_want.currency_code=c.currency_core
AND
    dates_we_want.datestamp=er.datestamp
AND
    `criteria_x`=NULL AND `criteria_y` LIKE 'A' 
于 2012-05-15T13:34:17.393 回答