【问题标题】:How to join two tables with the same number of rows in SQLite?如何在 SQLite 中连接两个具有相同行数的表?
【发布时间】:2018-10-11 11:48:12
【问题描述】:

我遇到了与this question 中描述的几乎相同的问题。我有两个行数相同的表,我想将它们一一连接在一起。

表是有序的,如果可能的话,我想在加入后保持这个顺序。

MSSql 有一个基于 rowid 的解决方案,但在 SQLite 中,如果表来自 WITH 语句(或 RECURSIVE WITH),则无法使用 rowid。

保证两个表有完全相同的行数,但这个数字是事先不知道的。同样重要的是要注意,相同的元素可能出现两次以上。结果是有序的,但没有一列是唯一的。

示例代码:

WITH
table_a (n) AS (
  SELECT 2
  UNION ALL
  SELECT 4
  UNION ALL
  SELECT 5
),
table_b (s) AS (
  SELECT 'valuex'
  UNION ALL
  SELECT 'valuey'
  UNION ALL
  SELECT 'valuez'
)
SELECT table_a.n, table_b.s
FROM table_a
LEFT JOIN table_b ON ( table_a.rowid = table_b.rowid )

我想要达到的结果是:

(2, 'valuex'),
(4, 'valuey'),
(5, 'valuez')

SQLFiddle:http://sqlfiddle.com/#!5/9eecb7/6888

【问题讨论】:

  • 回复:“结果是有序的,但没有一列是唯一的。” - 不确定您是否已经知道这一切,但在 SQL 中,如果没有明确定义 ORDER BY,则未定义行返回的顺序。如果指定了ORDER BY ,但不同行之间的值相同,则未定义这些行返回的顺序。因此,鉴于此,连接两个表的方式将无法预测/确定。
  • (我对上述评论的看法:)“桌子是有序的”是什么意思?表没有排序,结果集[原文如此]。没有“保留”的命令。即使有实现顺序,也无法通过查询访问。请说清楚。请注意,您似乎有一些误解。 (例如,random(),不保证每个查询在给定参数上多次调用运算符。)

标签: sql join sqlite sql-order-by


【解决方案1】:

不管怎样……

使用类似的东西

WITH
v_table_a (n, rowid) AS (
  SELECT 2, 1
  UNION ALL
  SELECT 4, 2
  UNION ALL
  SELECT 5, 3
),
v_table_b (s, rowid) AS (
  SELECT 'valuex', 1
  UNION ALL
  SELECT 'valuey', 2
  UNION ALL
  SELECT 'valuez', 3
)
SELECT v_table_a.n, v_table_b.s
FROM v_table_a
LEFT JOIN v_table_b ON ( v_table_a.rowid = v_table_b.rowid );

对于“虚拟”表(有WITH 或没有),

WITH RECURSIVE vr_table_a (n, rowid) AS (
  VALUES (2, 1)
  UNION ALL
  SELECT n + 2, rowid + 1 FROM vr_table_a WHERE rowid < 3
)
, vr_table_b (s, rowid) AS (
  VALUES ('I', 1)
  UNION ALL
  SELECT s || 'I', rowid + 1 FROM vr_table_b WHERE rowid < 3
)
SELECT vr_table_a.n, vr_table_b.s
FROM vr_table_a
LEFT JOIN vr_table_b ON ( vr_table_a.rowid = vr_table_b.rowid );

对于使用递归 WITHs 的“虚拟”表(在此示例中,值是其他值,然后是您的值,但我想您明白了)和

CREATE TABLE p_table_a (n INT);
INSERT INTO p_table_a VALUES (2), (4), (5);
CREATE TABLE p_table_b (s VARCHAR(6));
INSERT INTO p_table_b VALUES ('valuex'), ('valuey'), ('valuez');

SELECT p_table_a.n, p_table_b.s
FROM p_table_a
LEFT JOIN p_table_b ON ( p_table_a.rowid = p_table_b.rowid );

用于物理表。

我会小心最后一个。快速测试表明,rowid 的数量被 a) 重用——当删除一些行并插入其他行时,插入的行从旧行中获取 rowids(即 SQLite 中的 rowid 不是' t 在行的生命周期之后是唯一的,而例如 Oracle 的 rowid AFAIR 是) -- 并且 b) 对应于插入顺序。但我不知道,也没有在文档中找到线索,如果这得到保证或在其他/未来的实现中可能会发生变化。或者这只是我的测试环境中的巧合。

(一般来说,行的物理顺序可能会发生变化(即使在同一个数据库中由于某些重组而使用相同的 DMBS),因此不是可以依赖的好选择。而且不能保证,查询会也返回按表中的物理位置排序的结果(它可能会使用某些索引的顺序,或者以其他方式对部分结果进行排序,从而影响输出的顺序)。考虑使用相应行中的公共(排序)键来设计表订购和加入。)

【讨论】:

    【解决方案2】:

    由于表格是有序的,您可以通过比较 n 个值来添加 row_id 值。

    但为了获得更好的性能,最好的方法仍然是在创建表时插入 ID 值。

    http://sqlfiddle.com/#!5/9eecb7/7014

    WITH
    table_a_a (n, id) AS 
    (
      WITH table_a (n) AS 
      (
      SELECT 2
      UNION ALL
      SELECT 4
      UNION ALL
      SELECT 5
      )
    SELECT table_a.n, (select count(1) from table_a b where b.n <= table_a.n) id
    FROM table_a
    ) ,
    table_b_b (n, id) AS 
    (
      WITH table_a (n) AS 
      (
       SELECT 'valuex'
      UNION ALL
      SELECT 'valuey'
      UNION ALL
      SELECT 'valuez'
      )
    SELECT table_a.n, (select count(1) from table_a b where b.n <= table_a.n) id
    FROM table_a
    ) 
    select table_a_a.n,table_b_b.n  from table_a_a,table_b_b where table_a_a.ID = table_b_b.ID
    

    或将输入集转换为逗号分隔列表并尝试如下:

    http://sqlfiddle.com/#!5/9eecb7/7337

    WITH RECURSIVE  table_b( id,element, remainder ) AS (
                SELECT 0,NULL AS element, 'valuex,valuey,valuz,valuz' AS remainder
                    UNION ALL
                SELECT id+1,
                    CASE
                        WHEN INSTR( remainder, ',' )>0 THEN 
                            SUBSTR( remainder, 0, INSTR( remainder, ',' ) )
                        ELSE
                            remainder
                    END AS element,
                    CASE
                        WHEN INSTR( remainder, ',' )>0 THEN 
                            SUBSTR( remainder, INSTR( remainder, ',' )+1 )
                        ELSE
                            NULL
                    END AS remainder
                FROM table_b
                WHERE remainder IS NOT NULL
            ),
              table_a( id,element, remainder ) AS (
                SELECT 0,NULL AS element, '2,4,5,7' AS remainder
                    UNION ALL
                SELECT id+1,
                    CASE
                        WHEN INSTR( remainder, ',' )>0 THEN 
                            SUBSTR( remainder, 0, INSTR( remainder, ',' ) )
                        ELSE
                            remainder
                    END AS element,
                    CASE
                        WHEN INSTR( remainder, ',' )>0 THEN 
                            SUBSTR( remainder, INSTR( remainder, ',' )+1 )
                        ELSE
                            NULL
                    END AS remainder
                FROM table_a
                WHERE remainder IS NOT NULL
            )
             SELECT table_b.element, table_a.element FROM table_b, table_a WHERE table_a.element IS NOT NULL and table_a.id = table_b.id;
    

    【讨论】:

    • 谢谢。由于ns 字段不是唯一的,如果相同的值出现多次怎么办?
    • 好点,在这种情况下,我假设 s 和 n 值都会出现更多,对吧?检查这个小提琴是你需要的结果集吗? sqlfiddle.com/#!5/9eecb7/7018
    • 不行,唯一的保证是两张表的行数相同。此示例返回 3 行而不是 4 行:sqlfiddle.com/#!5/9eecb7/7019
    • 如果方便您将输入列表转换为逗号分隔的列表,这个小提琴应该可以帮助您实现所需的结果集:sqlfiddle.com/#!5/9eecb7/7337我也添加了这个解决方案来回答.
    • 你是什么意思,“桌子是有序的”?你有什么理由认为你的意思就是问题的意思? “既然表格是有序的,你可以……”中的“since”总结的理由是什么?
    【解决方案3】:

    您可以创建临时表来携带CTE 数据行。然后通过 sqlite row_idJOIN 他们。

    CREATE TEMP TABLE temp_a(n integer);
    CREATE TEMP TABLE temp_b(n VARCHAR(255));
    
    WITH table_a(n) AS (
      SELECT 2 n
      UNION ALL
      SELECT 4
      UNION ALL
      SELECT 5
      UNION ALL
      SELECT 5
    ) 
    INSERT INTO temp_a (n) SELECT n FROM table_a;
    
    WITH table_b (n) AS 
    (
      SELECT 'valuex'
      UNION ALL
      SELECT 'valuey'
      UNION ALL
      SELECT 'valuez'
      UNION ALL
      SELECT 'valuew'
    )
    INSERT INTO temp_b (n) SELECT n FROM table_b;
    
    SELECT * 
    FROM temp_a a 
    INNER JOIN temp_b b on a.rowid = b.rowid;
    

    sqlfiddle:http://sqlfiddle.com/#!5/9eecb7/7252

    【讨论】:

    • 它有效,但在这种情况下,它不再是一个单一的语句。
    【解决方案4】:

    SQL

    SELECT a1.n, b1.s
    FROM table_a a1
    LEFT JOIN table_b b1
    ON (SELECT COUNT(*) FROM table_a a2 WHERE a2.n <= a1.n) =
       (SELECT COUNT(*) FROM table_b b2 WHERE b2.s <= b1.s)
    

    说明

    查询只是计算每个表的行数,直到当前行数(基于排序列)并根据该值进行连接。

    演示

    SQL Fiddle demo

    假设

    1. 用于每个表中的排序的单个列。 (但可以轻松修改查询以允许多个排序列)。
    2. 每个表中的排序值都是唯一的。
    3. 两个表的排序列中的值不一定相同。
    4. 已知table_a 包含与table_b 相同或更多的行。 (如果不是这种情况,那么 a FULL OUTER JOIN would need to be emulated 因为 SQLite 没有提供。)
    5. 不允许对表结构进行进一步更改。 (如果是,则为排序预先填充列会更有效)。

    【讨论】:

    • 如果列o不是唯一的(它仍然是有效的排序),并且相同的元素出现多个,此方法失败:sqlfiddle.com/#!5/9eecb7/7343
    • 我尝试在表中有重复的情况下引入一个随机列,但我得到一个奇怪的结果,值为 NULL:sqlfiddle.com/#!5/9eecb7/7394
    • 你的查询每次执行的结果都不一样。我不认为RANDOM() 在 SQLite 中的 CTE 中使用时的行为可能与您预期的一样 - 在加入表时似乎会重新评估。为了证明这一点,请查看 SELECT a1.r, b1.r FROM table_ar a1 CROSS JOIN table_br b1 的结果 - 预计其中一些值是相同的,但它们都是不同的。
    • @philipxy 我从上面的理解是,Iter Ator 希望连接的行尽可能遵循排序列的顺序。但是在排序列有一组重复的情况下,连接应该仍然与另一个表是一对一的,但是从这个组中随机选择行。
    • @IterAtor Re:奇怪的RANDOM() 行为,我做了一些进一步的调查并在这里提出了一个新问题:stackoverflow.com/questions/50289527
    【解决方案5】:

    可以在 with 语句中使用 rowid,但您需要选择它并使其可用于使用它的查询。 像这样的:

    with tablea AS (
      select id, rowid AS rid from someids),
      tableb AS (
      select details, rowid AS rid from somedetails)
    select tablea.id, tableb.details
    from
        tablea
        left join tableb on tablea.rid = tableb.rid;
    

    然而,他们已经警告过你一个非常糟糕的主意。如果应用程序在插入一个表之后但在另一个表之前中断怎么办?如果删除旧行怎么办?如果要连接两个表,则需要指定字段。这种设计有很多可能出错的地方。与此最相似的是增量 id 字段,您可以将其保存在表中并在应用程序中使用。更简单的是,把它们放在一张桌子上。 阅读此链接以获取有关 rowid 的更多信息:https://www.sqlite.org/lang_createtable.html#rowid

    sqlfiddle:http://sqlfiddle.com/#!7/29fd8/1

    【讨论】:

      【解决方案6】:

      这在 SQLite 中相当复杂——因为您允许重复。但你可以做到。思路如下:

      • 按值汇总表。
      • 对于每个值,从值的开头获取计数和偏移量。
      • 然后使用join 关联这些值并找出重叠。
      • 最后使用递归 CTE 来提取您想要的值。

      以下代码假定 ns 是有序的——正如您在问题中指定的那样。但是,如果另一列指定了排序,它会起作用(稍作修改)。

      您会注意到我在示例数据中包含了重复项:

      WITH table_a (n) AS (
            SELECT 2 UNION ALL
            SELECT 4 UNION ALL
            SELECT 4 UNION ALL
            SELECT 4 UNION ALL
            SELECT 5
           ),
           table_b (s) AS (
            SELECT 'valuex' UNION ALL
            SELECT 'valuey' UNION ALL
            SELECT 'valuey' UNION ALL
            SELECT 'valuez' UNION ALL
            SELECT 'valuez'
           ),
           a as (
            select a.n, count(*) as a_cnt,
                   (select count(*) from table_a a2 where a2.n < a.n) as a_offset
            from table_a a
            group by a.n
           ),
           b as (
            select b.s, count(*) as  b_cnt,
                   (select count(*) from table_b b2 where b2.s < b.s) as b_offset
            from table_b b
            group by b.s
           ),
           ab as (
            select a.*, b.*,
                   max(a.a_offset, b.b_offset) as offset,
                   min(a.a_offset + a.a_cnt, b.b_offset + b.b_cnt) - max(a.a_offset, b.b_offset) as cnt
            from a join
                 b
                 on a.a_offset + a.a_cnt - 1 >= b.b_offset and
                    a.a_offset <= b.b_offset + b.b_cnt - 1
           ),
            cte as (
            select n, s, offset, cnt, 1 as ind
            from ab
            union all
            select n, s, offset, cnt, ind + 1
            from cte
            where ind < cnt
           )
      select n, s
      from cte
      order by n, s;
      

      Here 是一个显示结果的 DB Fiddle。

      我应该注意到,这在几乎任何其他数据库中都会简单得多,使用窗口函数(或者可能是 MySQL 中的变量)。

      【讨论】:

        【解决方案7】:

        可以在 with 语句中使用 rowid,但您需要选择它并使其可用于使用它的查询。像这样的:

        with tablea AS (select id, rowid AS rid from someids),
          tableb AS (select details, rowid AS rid from somedetails)
        select tablea.id, tableb.details
        from
            tablea
            left join tableb on tablea.rid = tableb.rid;
        

        【讨论】:

          【解决方案8】:

          问题陈述表明:

          表格是有序的

          如果这意味着顺序是由UNION ALL 语句中的值的顺序定义的,并且如果 SQLite 尊重该顺序,那么下面的解决方案可能会引起人们的兴趣,因为除了对最后三行的小调整在示例程序中,它只添加了两行:

          A(rid,n) AS (SELECT ROW_NUMBER() OVER ( ORDER BY 1 ) rid, n FROM table_a),
          B(rid,s) AS (SELECT ROW_NUMBER() OVER ( ORDER BY 1 ) rid, s FROM table_b)
          

          也就是说,表 A 是用 rowid 扩充的 table_a,表 B 也是如此。

          不幸的是,有一个警告,尽管这可能只是我没有找到相关规范的结果。然而,在深入研究之前,这里是完整的建议解决方案:

          WITH
          table_a (n) AS (
            SELECT 2
            UNION ALL
            SELECT 4
            UNION ALL
            SELECT 5
          ),
          table_b (s) AS (
            SELECT 'valuex'
            UNION ALL
            SELECT 'valuey'
            UNION ALL
            SELECT 'valuez'
          ),
          A(rid,n) AS (SELECT ROW_NUMBER() OVER ( ORDER BY 1 ) rid, n FROM table_a),
          B(rid,s) AS (SELECT ROW_NUMBER() OVER ( ORDER BY 1 ) rid, s FROM table_b)
          
          SELECT A.n, B.s
          FROM A LEFT JOIN B
          ON ( A.rid = B.rid );
          

          警告

          建议的解决方案已经使用 sqlite 3.29.0 版本针对各种数据集进行了测试,但我不清楚它是否,并且将继续“保证”工作。

          当然,如果 SQLite 不能保证UNION ALL 语句的顺序(也就是说,如果问题基于不正确的假设),那么看到有根据的重新表述会很有趣。

          【讨论】:

            猜你喜欢
            • 2022-01-11
            • 2016-07-22
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2021-07-29
            • 2021-11-25
            • 2020-11-26
            相关资源
            最近更新 更多