Как объединить две таблицы с одинаковым количеством строк в SQLite? - PullRequest
0 голосов
/ 01 мая 2018

У меня почти такая же проблема, как описано в этот вопрос . У меня есть две таблицы с одинаковым количеством строк, и я хотел бы объединить их одну за другой.

Таблицы упорядочены, и я хотел бы сохранить этот порядок после объединения, если это возможно.

Существует решение на основе rowid для MSSql, но в SQLite rowid не может использоваться, если таблица поступает из оператора WITH (или RECURSIVE WITH).

Гарантируется, что две таблицы имеют одинаковое количество строк, но это число заранее неизвестно. Также важно отметить, что один и тот же элемент может встречаться более двух раз. Результаты упорядочены, но ни один из столбцов не является уникальным.

Пример кода:

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

Ответы [ 7 ]

0 голосов
/ 12 мая 2018

Это довольно сложно в SQLite - потому что вы разрешаете дубликаты. но ты можешь сделать это. Вот идея:

  • Суммируйте таблицу по значениям.
  • Для каждого значения получить счетчик и смещение от начала значений.
  • Затем используйте join, чтобы связать значения и определить перекрытие.
  • Наконец, используйте рекурсивный CTE для извлечения нужных значений.

В следующем коде предполагается, что n и s заказаны - как вы указали в своем вопросе. Однако это будет работать (с небольшими изменениями), если в другом столбце указан порядок.

Вы заметите, что я включил дубликаты в пример данных:

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;

Здесь - это БД скрипка, показывающая результаты.

Я должен отметить, что это было бы намного проще почти в любой другой базе данных, используя оконные функции (или, возможно, переменные в MySQL).

0 голосов
/ 14 мая 2018

Можно использовать rowid внутри оператора with, но вам нужно выбрать его и сделать доступным для запроса, использующего его. Примерно так:

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;
0 голосов
/ 10 мая 2018

Можно использовать rowid внутри оператора with, но вам нужно выбрать его и сделать доступным для запроса, использующего его. Примерно так:

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;

Однако, поскольку они уже предупредили вас о действительно плохой идее. Что делать, если приложение ломается после вставки в одну таблицу, но перед другой? Что если вы удалите старую строку? Если вы хотите объединить две таблицы, вам нужно указать поле для этого. Есть много вещей, которые могут пойти не так с этим дизайном. Наиболее похожим на это будет поле добавочного идентификатора, которое вы сохраните в таблице и будете использовать в своем приложении. Еще проще сделать их из одной таблицы. Прочтите эту ссылку для получения дополнительной информации о rowid: https://www.sqlite.org/lang_createtable.html#rowid

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

0 голосов
/ 09 мая 2018

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 .

Предположения

  1. Один столбец используется для упорядочения в каждой таблице. (Но запрос можно легко изменить, чтобы разрешить несколько столбцов порядка).
  2. Порядковые значения в каждой таблице уникальны.
  3. Значения в столбце заказа не обязательно совпадают между двумя таблицами.
  4. Известно, что table_a содержит столько же или больше строк, как table_b. (Если это не так, то a FULL OUTER JOIN необходимо будет эмулировать , поскольку SQLite не предоставляет его.)
  5. Дальнейшие изменения в структуре таблицы не допускаются. (Если это так, было бы более эффективно иметь предварительно заполненные столбцы для заказа).
0 голосов
/ 09 мая 2018

Вы можете создавать временные таблицы для переноса строки данных 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

0 голосов
/ 08 мая 2018

Поскольку таблицы упорядочены, вы можете добавить значения row_id, сравнив n значений.

Но все же лучшим способом повышения производительности было бы добавление значений идентификаторов при создании таблиц.

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;
0 голосов
/ 01 мая 2018

В любом случае ...

Используйте что-то вроде

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 s (в этом примере значения - другие, чем ваши, но я думаю, вы поняли) и

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 а) используются повторно - когда некоторые строки удаляются, а другие вставляются, вставленные строки получают rowid s из старых строк (то есть rowid в SQLite isn ' t уникально за время жизни строки, тогда как, например, Oracle rowid AFAIR is) - и b) соответствует порядку вставки. Но я не знаю и не нашел подсказки в документации, если это гарантировано или может быть изменено в других / будущих реализациях. Или, может быть, это просто совпадение в моей тестовой среде.

(Как правило, физический порядок строк может изменяться (даже в пределах одной и той же базы данных с использованием одной и той же DMBS в результате некоторой реорганизации), и поэтому не является хорошим выбором, на который можно положиться. И это не гарантируется, запрос будет вернуть результат, упорядоченный по физической позиции в таблице (он может вместо этого использовать порядок некоторого индекса или иметь частичный результат, упорядоченный другим способом, влияющим на порядок вывода). Рассмотрите возможность разработки таблиц с использованием общих (сортирующих) ключей в соответствующих строках для заказа и присоединиться.)

...