【问题标题】:How to JOIN without duplicate rows or JOIN subquery如何在没有重复行或 JOIN 子查询的情况下加入
【发布时间】:2018-03-10 13:22:32
【问题描述】:

我有 2 个表,transactionsidentifiers。每个事务都有一个 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

这实际上将时间缩短了一半,但仍然很慢。

我觉得我遗漏了一些明显的东西,在每种情况下,我都在 identifiers 表中搜索大量数据,这会减慢它的速度(数百万行而不是 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 fiddle 设置:http://sqlfiddle.com/#!9/ecad23/6

EDIT2:为了进一步说明,我需要维护每个事务的记录,因此如果 where 查询仅应用于 事务,返回的行数应该正是您所期望的桌子。用户可以有多个事务,因此无法对最终结果进行分组

【问题讨论】:

  • 如果我理解正确,您希望为每个用户提取一个标识符。您对如何选择标识符有任何标准(字母顺序、字符串大小)或者是否有任何标准匹配?
  • @dragmosh 标识符不需要任何标准,任何都可以

标签: mysql join query-optimization


【解决方案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。我不知道。

【讨论】:

  • 对,这就是为什么查询不仅按 user_id 分组,而且按三元组分组:user_idamounttimestamp。因此它将保留在三列中的任何一列中具有不同值的所有规则。
  • 抱歉在刷新页面之前删除了我的评论——你说得对。我在我的实际数据集上进行了尝试,但直到我摆弄 GROUP BY 并获得了一个独特的价值,它才起作用 - 谢谢!顺便说一句,varchar 上的 MAX 是什么意思?
  • 您实际上可以在选择列表中将identifier 替换为MAX(identifier)。不知道那是如何计算的,但它似乎在小提琴中起作用。顺便说一句,查询的性能如何?
  • 疯了,我原来的调整是从 40 秒到 23 秒,现在我是 1.5 秒。快速回归基础。谢谢
  • MAX(identifier) 可以,但ANY_VALUE(identifier) 是“推荐”方式。见dev.mysql.com/doc/refman/5.7/en/group-by-handling.html
【解决方案2】:

可能会更快:

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)

一些额外的提升将涉及使用“覆盖”索引:

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

检查你的BETWEEN——最后你可能会多加一秒。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-06-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-07
    • 2014-01-27
    • 2021-12-27
    相关资源
    最近更新 更多