如何在没有重复行或JOIN子查询的情况下加入

时间:2017-09-28 17:37:41

标签: mysql join query-optimization

我有2个表,交易标识符。每个事务都有一个user_id,每个user_id可以有多个标识符,例如

交易

user_id    |    amount     |    timestamp
12              10.00           1234567890
17              5.00            1234567890
12              7.00            1234567890 
3               2.50            1234567890

标识符

identifier     |     user_id
AEFT67                12
JHDASJK               12
KJSIDJ6               3
LKSDLK                5
HSDJH8                17
IUSDI5                17

我想得到这样的结果:

结果

user_id      |     identifier       |       amount      |     timestamp
12                  AEFT67                  10.00            1234567890
17                  HSDJH8                   5.00            1234567890
12                  AEFT67                   7.00            1234567890
3                   KJSIDJ6                  2.50            1234567890

这两个表都有数百万行,重要的是我在使用连接时不会重复(否则数量会有误)。

我最初尝试过:

SELECT t.user_id, t.amount, i.identifier
      FROM transactions AS t 
      LEFT JOIN identifiers AS i ON i.id = (
        SELECT
          i2.id
        FROM identifiers i2 
        WHERE i2.user_id = t.user_id
        LIMIT 1
      )
WHERE t.timestamp BETWEEN 1234567890 AND 1234567890

注意 - 我实际上并不介意为用户提供哪种标识,但用户可能有很多标识。嵌套的JOIN虽然在大型数据集上非常慢(大约40秒),所以我尝试了:

SELECT t1.user_id, t1.amount, i1.identifier FROM
    (SELECT *
      FROM transactions as t
    WHERE t.timestamp BETWEEN 1234567890 AND 1234567890) as t1
LEFT JOIN
    (SELECT * FROM identifiers GROUP BY user_id) i1
    ON i1.user_id =t1.user_id

这实际上把时间减少了一半,但仍然很慢。

我觉得我错过了一些明显的东西,在每种情况下我都在搜索标识符表中的大量数据,这会减慢它的速度(数百万行而不是1000行左右)需要)。我觉得如果我能够将第一个查询的结果作为参数在第二个中使用它会更快,即:。

SELECT * FROM
    (SELECT *
      FROM transactions 
    WHERE t.timestamp BETWEEN 1234567890 AND 1234567890) as t1
LEFT JOIN
    (SELECT * FROM identifiers WHERE user_id in (t1.user_id))

有没有更好的方法来通过引用单个(任何)标识符来获取过滤后的交易?

编辑:这是一个sql小提琴设置:http://sqlfiddle.com/#!9/ecad23/6

EDIT2:为了进一步说明,我需要保留每个事务的记录,因此如果where查询仅应用于事务,则返回的行数应该与您期望的完全相同。表。用户可以拥有多个事务,因此对最终结果进行分组将无法正常工作

2 个答案:

答案 0 :(得分:1)

执行所需操作的简单查询是:

SELECT 
  t.user_id
  , amount
  , timestamp
  , identifier
FROM 
  transactions AS t 
LEFT JOIN identifiers AS i 
  ON i.user_id = t.user_id
WHERE 
  t.timestamp BETWEEN 1234567890 AND 1234567890  
GROUP BY 
  t.user_id
  , amount
  , timestamp

由于查询应该相当容易由DBMS执行和优化,我猜你在某些列上缺少索引,如果它不快。

它的核心是两个表的简单连接。如果确保结果没有任何变化,那么人们可能会在LEFT JOIN之间交换JOIN,数据的一致性是完整的,这意味着每个交易都有一个用户。

GROUP BY再次删除联接生成的重复项。 identifier上没有聚合函数,因此MySql只会选择一个。如果ONLY_FULL_GROUP_BY标志处于活动状态,这可能会中断,这需要我们在聚合函数中使用identifier。由于identifiervarchar,因此不能简单地使用MINMAX。但如果没有设置标志,则没有问题。

<强>校正 实际上我试过,似乎也可以使用例如varchar上的MAX。我不知道。

答案 1 :(得分:1)

这个可能更快:

SELECT  user_id,
        amount,
        timestamp,
        (
            SELECT identifier FROM identifiers
                    WHERE user_id = t.user_id LIMIT 1
        )   AS identifier
    FROM  transactions AS t
    WHERE  timestamp BETWEEN 1234567890 AND 1234567890

所需索引:

 transactions: INDEX(timestamp)
 identifiers:  INDEX(user_id)

一点额外的提升将涉及使用&#34;覆盖&#34;索引代替:

 transactions: INDEX(timestamp, user_id, amount)
 identifiers:  INDEX(user_id, identifier)

检查您的BETWEEN - 您可能会在结尾处加入额外的秒数。

相关问题