MySQL从特定的相关记录中获取所有数据

bat*_*tad 7 mysql join

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

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

我想从exchange_rates表中检索每种货币的完整记录,但能够定义关于选择哪个相关记录的特定标准.不仅是每种货币的最新交易额,也可能是每种货币的最新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`
Run Code Online (Sandbox Code Playgroud)

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`
Run Code Online (Sandbox Code Playgroud)

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

另外,它让我身体上的痛苦不得不诉诸可怕的字符串操作来从关系数据库中获取我想要的数据 - 必须有更好的方法!

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

Jon*_*ler 3

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

您的第一个查询是:

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`
Run Code Online (Sandbox Code Playgroud)
  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 查询,我通常会逐步构建和测试整个查询,为之前开发的已知可以工作并产生正确输出的查询添加额外的材料。如果它很简单和/或我收集了太多的傲慢,我会首先尝试一个复杂的查询,但是当(复仇者)它不起作用时,然后我回到构建和测试过程。将其视为测试驱动(查询)开发。

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

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
Run Code Online (Sandbox Code Playgroud)

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

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

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

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
Run Code Online (Sandbox Code Playgroud)

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

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

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
Run Code Online (Sandbox Code Playgroud)

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

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

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

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