SQLite 如何连接两个行数相同的表?

How to join two tables with the same number of rows in SQLite?

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

table已排序,如果可能的话,我想在加入后保留此顺序。

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

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

示例代码:

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

无论如何...

使用类似

的东西
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 );

for "virtual" tables(带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 );

for "virtual" tables 使用递归 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 );

物理 tables.

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

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

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

您可以创建临时表来承载 CTE 数据行。然后 JOIN 他们通过 sqlite row_id 列。

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

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)

说明

查询简单地计算每个table(基于排序列)到当前行的行数,并根据该值进行连接。

演示

SQL Fiddle demo

假设

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

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

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;

然而,他们已经警告过你一个非常糟糕的主意。如果应用程序在插入一个 table 之后但在另一个之前中断怎么办?如果删除旧行怎么办?如果你想加入两个 tables 你需要指定字段来这样做。这种设计可能会出错的地方太多了。与此最相似的是一个增量 id 字段,您可以将其保存在 table 中并在您的应用程序中使用。更简单的,将它们合二为一table。 阅读此 link 了解有关 rowid 的更多信息:https://www.sqlite.org/lang_createtable.html#rowid

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

这在 SQLite 中相当复杂——因为您允许重复。但你可以做到。这是想法:

  • 按值总结table。
  • 对于每个值,从值的开头获取计数和偏移量。
  • 然后使用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 是显示结果的数据库 Fiddle。

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

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

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;

问题陈述表明:

The tables are ordered

如果这意味着排序是由 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)

也就是说,table A table_a 增加了一个 rowid,table 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 针对各种数据集进行了测试,但我不清楚它是否并将继续 "guaranteed" 起作用。

当然,如果 SQLite 不保证 UNION ALL 语句的顺序(也就是说,如果问题是基于错误的假设),那么看到一口井会很有趣-重新制定。