【问题标题】:JOIN each row only once — arrange (distribute) rows 1 to 1每行只加入一次——排列(分布)行 1 到 1
【发布时间】:2015-05-13 02:41:34
【问题描述】:

我有两个表要加入,我希望每行只加入一次。这是示例数据:

CREATE TABLE A (id smallint, val varchar(1) );
CREATE TABLE B (id smallint, val varchar(1) );
INSERT INTO A VALUES (1, 'a'), (2, 'b'), (3, 'c'), (3, 'd');
INSERT INTO B VALUES (2, 'x'), (3, 'y'), (4, 'z'), (3, 'k');

当我们加入 id 时,我们获得:

mysql> SELECT * FROM A JOIN B ON A.id = B.id;
+------+------+------+------+
| id   | val  | id   | val  |
+------+------+------+------+
|    2 | b    |    2 | x    |
|    3 | c    |    3 | y    |
|    3 | d    |    3 | y    |
|    3 | c    |    3 | k    |
|    3 | d    |    3 | k    |
+------+------+------+------+

我想要的是:

+------+------+------+------+            +------+------+------+------+
| id   | val  | id   | val  |            | id   | val  | id   | val  |
+------+------+------+------+     or     +------+------+------+------+
|    2 | b    |    2 | x    |            |    2 | b    |    2 | x    |
|    3 | c    |    3 | y    |            |    3 | d    |    3 | y    |
|    3 | d    |    3 | k    |            |    3 | c    |    3 | k    |
+------+------+------+------+            +------+------+------+------+

顺序和安排无关紧要。

有可能吗?怎么样?

根据this answer我需要指定如何选择匹配的行。在这种情况下,我想如果已使用连接表的行,则需要检查子查询;或者一种与id相关的计数器......但我不知道如何写这个。

编辑:

为了澄清我希望 ID 为 3 的每一行与连接表中的另一行映射,例如每一行只映射一次(我也有兴趣知道当具有相同 ID 的行数不同时会发生什么在两个表中):

(3, c) -> (3, y) [join only with the first row such as B.id = 3]
(3, d) -> (3, k) [the first row has been used, so map with (and only with) the second row such as B.id = 3]

但正如我所说的,映射可以是任何其他顺序(例如,以相反的顺序映射行)。

【问题讨论】:

  • 您可以使用部分GROUP BY 来实现结果,但重点是什么?结果将是不确定的。
  • 部分 GROUP BY 看起来像 SELECT a.id, a.val, b.val FROM a JOIN b ... GROUP BY a.id, a.val。不确定的部分是不能保证每个 (a.id, a.val) 对返回哪个 b.val。
  • “没关系”这句话通常是设计不佳的症状。这应该很重要!
  • 我也很想知道当两个表中具有相同 id 的行数不同时会发生什么 - 那么应该发生什么,为什么在你的例子中是相同Id相同的行数?
  • 我认为酒吧可以向上移动几个梯级,而不会在这里引起太多恐慌。 ;-)

标签: mysql sql join row-number


【解决方案1】:

SQL Fiddle

MySQL 5.6 架构设置

CREATE TABLE A (id smallint, val varchar(1) );
CREATE TABLE B (id smallint, val varchar(1) );
INSERT INTO A VALUES (1, 'a'), (2, 'b'), (3, 'c'), (3, 'd');
INSERT INTO B VALUES (2, 'x'), (3, 'y'), (4, 'z'), (3, 'k');

查询 1

select
        aa.id  as aid
      , aa.val as aval
      , bb.id  as bid
      , bb.val as bval
from (
      select
            @row_num :=IF(@prev_value=a.id,@row_num+1,1)AS RowInGroup
          , a.id
          , a.val
          , @prev_value := a.id
      from (
            SELECT id, val 
            FROM A
            group by id, val
            /* order by ?? */
              ) a
            CROSS JOIN (
                        SELECT @row_num :=1,  @prev_value :=''
                       ) vars
      ) aa
INNER JOIN (
          select
                @row_num :=IF(@prev_value=b.id,@row_num+1,1)AS RowInGroup
              , b.id
              , b.val
              , @prev_value := b.id
          from (
                SELECT id, val 
                FROM B
                group by id, val
                /* order by ?? */
                  ) b
                CROSS JOIN (
                            SELECT @row_num :=1,  @prev_value :=''
                           ) vars
          ) bb on aa.id = bb.id and aa.RowInGroup = bb.RowInGroup
order by
        aa.id
      , aa.val

Results

| id | val | id | val |
|----|-----|----|-----|
|  2 |   b |  2 |   x |
|  3 |   c |  3 |   k |
|  3 |   d |  3 |   y |

nb:您可以通过在计算序列RowInGroupgroup by id, val 的子查询中引入order by 来影响最终结果。

【讨论】:

  • 你不需要为两个不同的row_nums使用两个不同的变量吗?
  • 没有。由于交叉连接,子查询是自包含的,它们在其中获取初始值。如果您愿意,当然可以使用单独的变量。
  • 谢谢,这行得通!但这很长,所以我更喜欢我的答案。此外,虽然在我的情况下性能不是要求,但您的解决方案在复杂性方面似乎并没有更好。
  • 我没有使用您的解决方案作为参考点。我的解决方案基于ROW_NUMBER() 满足的类似需求,这是一个遗憾地从 MySQL 中缺失的函数。这是一种产生 row_number 效果的技术。它实际上并没有那么“长”,但为了便于阅读,我已将其隔开。
  • 在同一个 select 语句中读取和写入同一个用户变量是未定义的行为——请参阅手册 re variables & assignment。
【解决方案2】:

我终于做到了!

SELECT T.ID_A,
       T.VAL_A,
       T.XXXX,
       T.ID_B,
       T.VAL_B,
       T.YYYY
FROM (

SELECT A.id AS ID_A,
       A.VAL AS VAL_A,
       ROW_NUMBER() OVER (PARTITION BY A.ID, A.VAL ORDER BY A.ID, A.VAL) AS XXXX,
       B.ID AS ID_B,
       B.VAL AS VAL_B,
       ROW_NUMBER() OVER (PARTITION BY B.ID, B.VAL ORDER BY B.ID DESC, B.VAL) AS YYYY
FROM A INNER JOIN B ON A.id = B.id) AS T
WHERE T.YYYY = 1

【讨论】:

  • 谢谢。按原样复制粘贴,它给了我一个 SQL 语法错误。我明天去调查。
  • 可能 OVER 分区在 MySQL 中有不同的语法。
  • 确实,看起来 ROW_NUMBER 不在 MySQL 中。我试图用一个计数变量来模拟它,但没有成功。
  • 它有帮助,因为它让我搜索ROW_NUMBER,这让我找到了答案。
  • 我 dv'd,但这只是因为这显然是不同 RDBMS 的解决方案
【解决方案3】:

感谢blog post

SELECT A2.id, A2.val, B2.val FROM (
    SELECT l.id, l.val, COUNT(*) AS n1 FROM A AS l JOIN A AS r ON l.id = r.id AND l.val >= r.val GROUP BY l.id, l.val
) AS A2 JOIN (
    SELECT l.id, l.val, COUNT(*) AS n2 FROM B AS l JOIN B AS r ON l.id = r.id AND l.val >= r.val GROUP BY l.id, l.val
) AS B2 ON
A2.id = B2.id AND n1 = n2;

结果是:

+------+------+------+
| id   | val  | val  |
+------+------+------+
|    2 | b    | x    |
|    3 | c    | k    |
|    3 | d    | y    |
+------+------+------+

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-03-23
    • 1970-01-01
    • 2016-11-21
    • 1970-01-01
    • 1970-01-01
    • 2018-02-05
    • 1970-01-01
    相关资源
    最近更新 更多