如果连接参数按 id 顺序不唯一,则创建唯一的连接 id-id 矩阵
Create unique join id-id matrix if joins parameters is not unique in order of id
问题是 加入 2 table 具有相似数据的连接键并不总是唯一的(将新数据从临时导入到实时 table)
在这种情况下,我需要 按 id 的顺序解决重复问题(每个 id 应该只有一次 ,意味着接下来每个 table1.id先取未使用的table2.id,反之亦然)。
注:
- 用户可以创建新记录,这会创建更多重复项
- 查询 必须在 table1(导入数据) 和数千 table2(实时数据)
考虑这些data/tables
|| Imported | || Live |
| Id | guid | key1 | key2 | unimportant | | Id | origGuid | key1 | key2 | important |
| 1 | 1001 | 1 | '01' | 'a' | | 15 | 1001 | 1 | '01' | 'imported' |
| 2 | 1002 | null | '02' | 'b' | | 16 | 1002 | null | '02' | 'imported' |
| 3 | 1003 | null | '02' | 'c' | | 17 | null | null | '02' | 'user restor' |
| 5 | 1005 | 5 | '05' | 'd' | | 18 | 1004 | 4 | '04' | 'imported' |
| 19 | null | null | '02' | 'user new' |
我想得到:
- 导入的 ID 为 1 的记录等于实时中的 ID 15(唯一)
- 导入的 id 为 2 的记录等于实时 id 16
- keys null & '02' 不是唯一的,所以这是第一次出现并使用相同的键从 live 中获取第一个 id -> 16
- 导入的 id 为 3 的记录等于实时 id 17
- 与 id 为 2 的行相同的键,这是第二次出现,因此使用相同的键从 live 中获取第二个 id ->17(我的意思是第一个未使用)
- 导入数据中 ID 为 5 的记录是新记录
- 实时数据中没有具有相同键的行 -> 空
- 实时数据中id为18,19的记录标记为删除
- 导入的数据中没有具有相同键的行 -> 空
这里我放了查询来准备数据
CREATE TEMPORARY TABLE imported (id serial, guid decimal(30,0), key1 integer, key2 varchar, unimportant varchar);
INSERT INTO imported VALUES (1, 1001, 1, '01', 'a');
INSERT INTO imported VALUES (2, 1002, null, '02', 'b');
INSERT INTO imported VALUES (3, 1003, null, '02', 'c');
INSERT INTO imported VALUES (5, 1005, 5, '05', 'd');
CREATE TEMPORARY TABLE live (id serial, orig_guid integer, key1 integer, key2 varchar, important varchar);
INSERT INTO live VALUES (15, 1001, 1, '01', 'imported');
INSERT INTO live VALUES (16, 1002, null, '02', 'imported');
INSERT INTO live VALUES (17, null, null, '02', 'user restor');
INSERT INTO live VALUES (18, 1004, 4, '04', 'imported');
INSERT INTO live VALUES (19, null, null, '02', 'user new');
我像这样使用 old query。但速度很慢(嵌套循环连接),结果也不完美(未解决口是心非)
SELECT DISTINCT imported.id AS imported_id, live.id AS live_id
FROM live
INNER JOIN imported ON
live.orig_guid = imported.guid OR (
(live.orig_guid IS NULL OR imported.guid IS NULL) AND
(live.key1 IS NULL AND imported.key1 IS NULL OR live.key1 = imported.key1) AND
(live.key2 IS NULL AND imported.key2 IS NULL OR live.key2 = imported.key2)
)
ORDER BY live.id ASC, imported.id ASC
在 优化查询 中,我使用 UNION 命令将 SELECT 拆分为 2,并使用 COALESCE 减少 OR 以加速
WITH
liveT AS (SELECT id, COALESCE(orig_guid,0) AS guid, COALESCE(key1,0) AS key1, COALESCE(key2,'null') AS key2 FROM live),
importedT AS (SELECT id, COALESCE(guid,0) AS guid, COALESCE(key1,0) AS key1, COALESCE(key2,'null') AS key2 FROM imported),
join1 AS (
SELECT imported.id AS imported_id, live.id AS live_id FROM imported
INNER JOIN live ON imported.guid = live.orig_guid AND imported.guid <> 0 AND live.orig_guid <> 0
),
joins AS (
SELECT imported.id AS imported_id, live.id AS live_id FROM importedT imported
INNER JOIN liveT live ON
(live.guid = 0 OR imported.guid = 0) AND
live.key1 = imported.key1 AND
(live.key2 = imported.key2) -- I have in one key "OR imported.key2 = 'null'" because is new property and is not so strict
-- To reduce records i use AntiJoin
LEFT OUTER JOIN join1 ON join1.imported_id = imported.id
WHERE join1.imported_id IS NULL
UNION
SELECT imported_id, live_id FROM join1
)
SELECT DISTINCT imported_id, live_id FROM joins
ORDER BY imported_id ASC NULLS LAST, live_id ASC NULLS LAST
但结果并不完美,使用了3个类似的查询
- 这是为了获得连接(在一个模块中直播 table2 直播 table3在另一个模块中)
- 其次,我将标记为从实时 table3 中删除的记录连接到导入的 table1
- 为了找到删除标记,我使用第一个命令来获得(未)连接
- 并且需要从导入的 table 中获取新信息,只是更改了所有者
- 存在于导入的table1和table3中,但不转移到table2 因为所有者不匹配
- 并最后在导入的 table1 上下文中搜索 table3 中使用的值
- table3 与新的 table (id, code, 姓名)
- imported table1 有 属性 code 和 name
- 我需要相关的记录table add/update/delete
查询的结果是:
|| Old | || Optimized | || Expected |
import_id | live_id import_id | live_id import_id | live_id
1 | 15 1 | 15 1 | 15
2 | 16 2 | 16 2 | 16
2 | 17 3 | 17 3 | 17
2 | 19 3 | 19 5 | null
3 | 17 null | 18
3 | 19 null | 19
你的问题不是很清楚。
您已将样本数据包含为 INSERT
语句 - 这很好,有助于回答问题。您已经显示了预期的结果——这也很好。通常,如果您用简单的英语解释此结果背后所需的逻辑,它会有所帮助。这部分问题不是很清楚
查看您尝试的查询,我猜测 Imported
和 Live
表应该在 key1
和 key2
上连接。最重要的是,如果一对 (key1, key2)
不是唯一的,则应按照 id
列定义的顺序逐行连接表。
此外,key1
和key2
都可以是NULL
,所以NULL
值应该用0
和"null"
代替。
查询
rn_imported
和 rn_live
是具有额外列的子查询,其中包含由 ROW_NUMBER()
函数生成的行号。
然后这些子查询在 key1, key2, rn
上被 FULL 连接在一起。
参见 SQL Fiddle。
SELECT
imported_id
,live_id
FROM
(
SELECT
id AS imported_id
,COALESCE(key1, 0) AS key1
,COALESCE(key2, 'null') AS key2
,ROW_NUMBER() OVER (PARTITION BY key1, key2 ORDER BY id) AS rn
FROM imported
) AS rn_imported
FULL JOIN
(
SELECT
id AS live_id
,COALESCE(key1, 0) AS key1
,COALESCE(key2, 'null') AS key2
,ROW_NUMBER() OVER (PARTITION BY key1, key2 ORDER BY id) AS rn
FROM live
) AS rn_live
ON rn_imported.key1 = rn_live.key1
AND rn_imported.key2 = rn_live.key2
AND rn_imported.rn = rn_live.rn
ORDER BY imported_id ASC NULLS LAST, live_id ASC NULLS LAST
结果
| imported_id | live_id |
|-------------|---------|
| 1 | 15 |
| 2 | 16 |
| 3 | 17 |
| 5 | (null) |
| (null) | 18 |
| (null) | 19 |
为了使这种方法尽可能高效,您应该使 key1
和 key2
列 NOT NULL
以避免调用 COALESCE
函数。函数本身很快,但是这样使用函数通常会导致无法使用索引。删除函数调用的需要后,您应该在两个表中的 (key1, key2, id)
上添加索引。一个索引按此顺序在三列上。让它成为唯一索引不会有什么坏处。它可能会给优化器一些额外的提示。使用此索引 ROW_NUMBER
应该能够生成所需的数字而无需额外排序。拥有两组已排序的数据也应该有助于连接。
我想重复一遍。仅添加索引而不创建列 NOT NULL
很可能是无用的。
问题是 加入 2 table 具有相似数据的连接键并不总是唯一的(将新数据从临时导入到实时 table)
在这种情况下,我需要 按 id 的顺序解决重复问题(每个 id 应该只有一次 ,意味着接下来每个 table1.id先取未使用的table2.id,反之亦然)。
注:
- 用户可以创建新记录,这会创建更多重复项
- 查询 必须在 table1(导入数据) 和数千 table2(实时数据)
考虑这些data/tables
|| Imported | || Live |
| Id | guid | key1 | key2 | unimportant | | Id | origGuid | key1 | key2 | important |
| 1 | 1001 | 1 | '01' | 'a' | | 15 | 1001 | 1 | '01' | 'imported' |
| 2 | 1002 | null | '02' | 'b' | | 16 | 1002 | null | '02' | 'imported' |
| 3 | 1003 | null | '02' | 'c' | | 17 | null | null | '02' | 'user restor' |
| 5 | 1005 | 5 | '05' | 'd' | | 18 | 1004 | 4 | '04' | 'imported' |
| 19 | null | null | '02' | 'user new' |
我想得到:
- 导入的 ID 为 1 的记录等于实时中的 ID 15(唯一)
- 导入的 id 为 2 的记录等于实时 id 16
- keys null & '02' 不是唯一的,所以这是第一次出现并使用相同的键从 live 中获取第一个 id -> 16
- 导入的 id 为 3 的记录等于实时 id 17
- 与 id 为 2 的行相同的键,这是第二次出现,因此使用相同的键从 live 中获取第二个 id ->17(我的意思是第一个未使用)
- 导入数据中 ID 为 5 的记录是新记录
- 实时数据中没有具有相同键的行 -> 空
- 实时数据中id为18,19的记录标记为删除
- 导入的数据中没有具有相同键的行 -> 空
这里我放了查询来准备数据
CREATE TEMPORARY TABLE imported (id serial, guid decimal(30,0), key1 integer, key2 varchar, unimportant varchar);
INSERT INTO imported VALUES (1, 1001, 1, '01', 'a');
INSERT INTO imported VALUES (2, 1002, null, '02', 'b');
INSERT INTO imported VALUES (3, 1003, null, '02', 'c');
INSERT INTO imported VALUES (5, 1005, 5, '05', 'd');
CREATE TEMPORARY TABLE live (id serial, orig_guid integer, key1 integer, key2 varchar, important varchar);
INSERT INTO live VALUES (15, 1001, 1, '01', 'imported');
INSERT INTO live VALUES (16, 1002, null, '02', 'imported');
INSERT INTO live VALUES (17, null, null, '02', 'user restor');
INSERT INTO live VALUES (18, 1004, 4, '04', 'imported');
INSERT INTO live VALUES (19, null, null, '02', 'user new');
我像这样使用 old query。但速度很慢(嵌套循环连接),结果也不完美(未解决口是心非)
SELECT DISTINCT imported.id AS imported_id, live.id AS live_id
FROM live
INNER JOIN imported ON
live.orig_guid = imported.guid OR (
(live.orig_guid IS NULL OR imported.guid IS NULL) AND
(live.key1 IS NULL AND imported.key1 IS NULL OR live.key1 = imported.key1) AND
(live.key2 IS NULL AND imported.key2 IS NULL OR live.key2 = imported.key2)
)
ORDER BY live.id ASC, imported.id ASC
在 优化查询 中,我使用 UNION 命令将 SELECT 拆分为 2,并使用 COALESCE 减少 OR 以加速
WITH
liveT AS (SELECT id, COALESCE(orig_guid,0) AS guid, COALESCE(key1,0) AS key1, COALESCE(key2,'null') AS key2 FROM live),
importedT AS (SELECT id, COALESCE(guid,0) AS guid, COALESCE(key1,0) AS key1, COALESCE(key2,'null') AS key2 FROM imported),
join1 AS (
SELECT imported.id AS imported_id, live.id AS live_id FROM imported
INNER JOIN live ON imported.guid = live.orig_guid AND imported.guid <> 0 AND live.orig_guid <> 0
),
joins AS (
SELECT imported.id AS imported_id, live.id AS live_id FROM importedT imported
INNER JOIN liveT live ON
(live.guid = 0 OR imported.guid = 0) AND
live.key1 = imported.key1 AND
(live.key2 = imported.key2) -- I have in one key "OR imported.key2 = 'null'" because is new property and is not so strict
-- To reduce records i use AntiJoin
LEFT OUTER JOIN join1 ON join1.imported_id = imported.id
WHERE join1.imported_id IS NULL
UNION
SELECT imported_id, live_id FROM join1
)
SELECT DISTINCT imported_id, live_id FROM joins
ORDER BY imported_id ASC NULLS LAST, live_id ASC NULLS LAST
但结果并不完美,使用了3个类似的查询
- 这是为了获得连接(在一个模块中直播 table2 直播 table3在另一个模块中)
- 其次,我将标记为从实时 table3 中删除的记录连接到导入的 table1
- 为了找到删除标记,我使用第一个命令来获得(未)连接
- 并且需要从导入的 table 中获取新信息,只是更改了所有者
- 存在于导入的table1和table3中,但不转移到table2 因为所有者不匹配
- 并最后在导入的 table1 上下文中搜索 table3 中使用的值
- table3 与新的 table (id, code, 姓名)
- imported table1 有 属性 code 和 name
- 我需要相关的记录table add/update/delete
查询的结果是:
|| Old | || Optimized | || Expected |
import_id | live_id import_id | live_id import_id | live_id
1 | 15 1 | 15 1 | 15
2 | 16 2 | 16 2 | 16
2 | 17 3 | 17 3 | 17
2 | 19 3 | 19 5 | null
3 | 17 null | 18
3 | 19 null | 19
你的问题不是很清楚。
您已将样本数据包含为 INSERT
语句 - 这很好,有助于回答问题。您已经显示了预期的结果——这也很好。通常,如果您用简单的英语解释此结果背后所需的逻辑,它会有所帮助。这部分问题不是很清楚
查看您尝试的查询,我猜测 Imported
和 Live
表应该在 key1
和 key2
上连接。最重要的是,如果一对 (key1, key2)
不是唯一的,则应按照 id
列定义的顺序逐行连接表。
此外,key1
和key2
都可以是NULL
,所以NULL
值应该用0
和"null"
代替。
查询
rn_imported
和 rn_live
是具有额外列的子查询,其中包含由 ROW_NUMBER()
函数生成的行号。
然后这些子查询在 key1, key2, rn
上被 FULL 连接在一起。
参见 SQL Fiddle。
SELECT
imported_id
,live_id
FROM
(
SELECT
id AS imported_id
,COALESCE(key1, 0) AS key1
,COALESCE(key2, 'null') AS key2
,ROW_NUMBER() OVER (PARTITION BY key1, key2 ORDER BY id) AS rn
FROM imported
) AS rn_imported
FULL JOIN
(
SELECT
id AS live_id
,COALESCE(key1, 0) AS key1
,COALESCE(key2, 'null') AS key2
,ROW_NUMBER() OVER (PARTITION BY key1, key2 ORDER BY id) AS rn
FROM live
) AS rn_live
ON rn_imported.key1 = rn_live.key1
AND rn_imported.key2 = rn_live.key2
AND rn_imported.rn = rn_live.rn
ORDER BY imported_id ASC NULLS LAST, live_id ASC NULLS LAST
结果
| imported_id | live_id |
|-------------|---------|
| 1 | 15 |
| 2 | 16 |
| 3 | 17 |
| 5 | (null) |
| (null) | 18 |
| (null) | 19 |
为了使这种方法尽可能高效,您应该使 key1
和 key2
列 NOT NULL
以避免调用 COALESCE
函数。函数本身很快,但是这样使用函数通常会导致无法使用索引。删除函数调用的需要后,您应该在两个表中的 (key1, key2, id)
上添加索引。一个索引按此顺序在三列上。让它成为唯一索引不会有什么坏处。它可能会给优化器一些额外的提示。使用此索引 ROW_NUMBER
应该能够生成所需的数字而无需额外排序。拥有两组已排序的数据也应该有助于连接。
我想重复一遍。仅添加索引而不创建列 NOT NULL
很可能是无用的。