如何在SQLite中连接两个具有相同行数的表?

时间:2018-05-01 12:29:35

标签: sql join sqlite sql-order-by

我遇到与this question中描述的问题几乎相同的问题。我有两个具有相同行数的表,我想一个接一个地加入它们。

这些表是有序的,如果可能,我想在加入后保留此订单。

对于MSSql有一个基于rowid的解决方案,但是如果表来自WITH语句(或RECURSIVE WITH),则不能使用SQLite 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

8 个答案:

答案 0 :(得分:1)

由于表是有序的,您可以通过比较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;

答案 1 :(得分:1)

<强> 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没有提供。{/ li>
  5. 不允许对表结构进行进一步更改。 (如果是这样的话,为排序预先填充的列会更有效。)

答案 2 :(得分:1)

这在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小提琴,显示结果。

我应该注意到,使用窗口函数(或MySQL中的变量)几乎可以在任何其他数据库中更简单。

答案 3 :(得分:0)

无论哪种方式......

使用类似

的内容
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 );
使用递归WITH的“虚拟”表的

(在这个例子中,值是其他的,然后是你的,但我想你明白了)和

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)重用 - 当某些行被删除而其他行被插入时,插入的行从旧行中获取rowid s(即{{1在SQLite中,在行的生命周期内并不是唯一的,而例如Oracle的rowid AFAIR是 - )和b)对应于插入的顺序。但我不知道,也没有在文档中找到线索,如果这有保证或在其他/未来的实现中可能会有变化。或许这只是我测试环境中的巧合。

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

答案 4 :(得分:0)

您可以创建临时表来携带@Valid数据行。然后通过sqlite CTEJOIN使用它们。

row_id

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

答案 5 :(得分:0)

可以在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 :(得分:0)

可以在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;

答案 7 :(得分:0)

问题说明指出:

  

表已排序

如果这意味着该排序是由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的行号增加了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语句的顺序不提供任何保证(也就是说,如果问题基于错误的假设),那么有充分根据的情况将很有趣。重新配制。