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

时间:2012-05-03 15:48:22

标签: mysql join

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

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

我想从每个货币的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()以获取组内的排序顺序,然后使用SUBSTRING_INDEX()在父查询中解析这些字段。问题是这个方法只有在我可以在连接字段上使用MIN()MAX()时才有效。如果我想按字符串排序或按多个条件排序但限制为单个记录,那就不理想了。

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

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

4 个答案:

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

  4. 你的问题是:

      

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

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

    使用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阶段:符合指定标准的每种货币的最近汇率时间的货币信息和汇率记录

    这需要将currency表加入上一个查询的输出:

    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以不同方式处理查询的优化器提示。

答案 1 :(得分: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

答案 2 :(得分: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` 

答案 3 :(得分: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

拥有我们可能想要的所有信息的域名是date_we_want,currency和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

选择我们想要的记录:

  • 匹配currency_codes

    dates_we_want.currency_code = er.currency_core AND dates_we_want.currency_code = c.currency_core

  • 最近的费率

    dates_we_want.datestamp = er.datestamp

投射到结果中。你

  

想要从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'