【问题标题】:MySQL get all data from specific related recordMySQL 从特定的相关记录中获取所有数据
【发布时间】:2012-05-03 15:48:22
【问题描述】:

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

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

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

很遗憾您不能在派生表中使用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() 以获取我在组内的排序顺序,然后使用SUBSTRING_INDEX() 在父查询中解析这些字段。问题是这种方法只有在我可以在连接字段上使用MIN()MAX() 时才有效。如果我想对字符串进行排序或按多个条件排序但限制为单个记录,那将是不理想的。

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

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

【问题讨论】:

  • 请提供您的表结构和一些数据,以便我们了解如何解决它

标签: mysql join


【解决方案1】:

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

您的第一个查询是:

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. 我认为您不需要使用尽可能多的back-quotes。它们并没有完全错,但我不会在答案中输入它们。
  2. SQL 标准不认可criteria_x = NULL 表示法;应该写成criteria_x IS NULL。 MySQL 可能允许它;只要你知道它是非标准的,你就可以使用。
  3. 如果条件LIKE 'A' 不包含元字符(标准SQL 中的%_),则它是不合理的。使用简单的相等会更好:= 'A'

你的问题是:

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

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

对于 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

第 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 以不同方式处理查询。

【讨论】:

  • 一个绝妙的答案,完美地工作并且解释得很好。我知道必须有一种体面的方式,而无需连接/爆炸一串数据。关于反引号,是的,这是 MySQL 的习惯和坏习惯。我可以省略其中的大多数,但您永远不知道在站点升级时会发生什么,如果引入新关键字会破坏查询。还有 IS NULL/=NULL,是的,我意识到......我只是忘记在答案中粘贴我的演示查询时更改它。谢谢!!!
  • 谢谢。有关反引号等的讨论,另请参阅What does the SQL Standard say about the usage of `?
【解决方案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

【讨论】:

    【解决方案3】:

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

    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` 
    

    【讨论】:

    • 谢谢,但这看起来与我上面的相似,只是使用子选择而不是派生表上的内部连接。我上面的第二个工作正常,但我试图找到一种不连接字段然后推断字符串的方法。
    • 您可能没有注意到,我在外部查询中选择了 CurrencyCode,然后在有子查询中使用它来限制搜索
    【解决方案4】:

    如果我理解正确,如果你不强迫数据库像人类一样思考,答案很简单。与 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 AND 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' 
    

    【讨论】:

    • 您必须将criteria_xcriteria_y 条件下推到子查询中。你问的是一个微妙不同的问题。您正在询问最近一行恰好符合子标准的行,而不是最近符合子标准的行。我建议尽可能避免使用水平滚动条。就个人而言,我认为关键字 AND 不需要单独成行。我还强烈建议在 WHERE 子句中使用显式 JOIN 表示法,而不是使用逗号分隔的表表达式列表。
    • 我认为你在争论解决错误的问题。我们不想将问题逐字传递给 DBMS,我们想“从 exchange_rates 表中检索每种货币的完整记录 [满足一组约束]”。我的查询正是这样做的;故意的,不是偶然的或偶然的。这就是我的第一句话的重点:“不要强迫数据库像人类一样思考”。因为它不会,而且 DBMS 在执行前非常擅长组合、切片、选择和解释查询,因此考虑中间结果几乎没有回报。
    猜你喜欢
    • 2017-04-22
    • 2012-03-23
    • 2019-06-12
    • 2019-02-24
    • 1970-01-01
    • 2010-09-22
    • 2011-06-17
    • 2014-10-21
    • 1970-01-01
    相关资源
    最近更新 更多