【问题标题】:SQLite - Aggregating related data (tree-like) between 2 tables with multiple potential intermediate relationshipsSQLite - 在具有多个潜在中间关系的 2 个表之间聚合相关数据(树状)
【发布时间】:2014-08-24 21:49:31
【问题描述】:

我有一个 SQLite 数据库 (v. 3.8.1),其架构有些不寻常,无法更改。

出于这个问题的目的,有 5 个表(t1 到 t5),我需要使用来自 t1 和 t5 的数据创建一个汇总报告,但是我需要在 t5 中引用的数据只能基于收集与 t1 到 t4 中的记录的关系。

为了帮助澄清 - 假设 t1 保存有关文档的数据。该文档随后可以再进行 1 到 4 次迭代(每次迭代中都有不同的字段可用,因此有 5 个不同的表,而不仅仅是 1 个表中的一个标志来表示它所处的迭代)。

我对初始记录/文档(保存在 t1 中)是否已达到其最终迭代感兴趣(在 t5 中存在一个 ParentGUID,当跟进表链时,最终到达 t1 或不) .

t1 有一个 GUID(文本)字段,t2 到 t5 有 GUID 和 ParentGUID 字段(也是文本)。不必填充 t2 到 t5 中的 ParentGUID 字段(在某些情况下可以跳过文档迭代),但是当 ParentGUID 有一个值时,它将始终是来自前一个表的 GUID(例如,如果 t5 有一个 ParentGuid值,它将是来自 t1、t2、t3 或 t4 的 GUID)。

这意味着我想要来自 t1 的所有不同记录,然后对于每个来自 t5 的值(或多个值)(如果存在),否则为 null。

如果 t5 记录中的 ParentGuid 字段值是 t4 中记录的 GUID,并且该 t4 记录中的 ParentGuid 字段值是 t1 中记录的 GUID,则认为该特定 t1 记录已达到其最终迭代。

同样,ParentGUID > 将被视为 t1 > t5、初始 > 最终迭代的 GUID 链接包括:

t1 > t2 > t3 > t4 > t5
t1 > t2 > t3 > t5
t1 > t2 > t4 > t5
t1 > t2 > t5
t1 > t3 > t4 > t5
t1 > t3 > t5
t1 > t4 > t5
t1 > t5

或以图形表示:

考虑以下测试模式:

CREATE TABLE Table1
    ("GUID" TEXT, "Name" TEXT)
;

CREATE TABLE Table2
    ("GUID" TEXT, "ParentGUID" TEXT)
;

CREATE TABLE Table3
    ("GUID" TEXT, "ParentGUID" TEXT)
;

CREATE TABLE Table4
    ("GUID" TEXT, "ParentGUID" TEXT)
;

CREATE TABLE Table5
    ("GUID" TEXT, "Name" TEXT, "Amount" REAL, "ParentGUID" TEXT)
;

INSERT INTO Table1
    ("GUID", "Name")
VALUES
    ('ABC', 'A1')
;


INSERT INTO Table1
    ("GUID", "Name")
VALUES
    ('DEF', 'A2')
;

INSERT INTO Table1
    ("GUID", "Name")
VALUES
    ('GHI', 'A3')
;

INSERT INTO Table2
    ("GUID", "ParentGUID")
VALUES
    ('JKL', 'GHI')
;

INSERT INTO Table2
    ("GUID", "ParentGUID")
VALUES
    ('MNO', '')
;

INSERT INTO Table2
    ("GUID", "ParentGUID")
VALUES
    ('PQR', 'GHI')
;

INSERT INTO Table3
    ("GUID", "ParentGUID")
VALUES
    ('STU', 'MNO')
;

INSERT INTO Table3
    ("GUID",  "ParentGUID")
VALUES
    ('STU', 'GHI')
;

INSERT INTO Table3
    ("GUID", "ParentGUID")
VALUES
    ('VWX', 'PQR')
;


INSERT INTO Table4
    ("GUID", "ParentGUID")
VALUES
    ('YZA', 'VWX')
;

INSERT INTO Table4
    ("GUID", "ParentGUID")
VALUES
    ('BCD', '')
;

INSERT INTO Table4
    ("GUID", "ParentGUID")
VALUES
    ('EFG', 'GHI')
;

INSERT INTO Table5
    ("GUID", "ParentGUID", "Amount", "Name" )
VALUES
    ('HIJ', 'EFG', -500, 'E3')
;


INSERT INTO Table5
    ("GUID", "ParentGUID", "Amount", "Name" )
VALUES
    ('KLM', 'YZA', -702, 'E2')
;


INSERT INTO Table5
    ("GUID", "ParentGUID", "Amount", "Name" )
VALUES
    ('NOP', '', 220, 'E8')
;

INSERT INTO Table5
    ("GUID", "ParentGUID", "Amount", "Name" )
VALUES
    ('QRS', 'GHI', 601, 'E4')
;

我想做的是获取 t1 中的所有记录,然后显示来自 t5 的所有相关 Amount 字段的总和(以上面列出的任何方式相关),以及来自所有相关 Name 字段的 group_concat t5.

使用上面的示例架构,它看起来像:

t1.Name   total(t5.Amount)   group_concat(t5.Name)
--------------------------------------------------
A1                   0.00  
A2                   0.00  
A3                -601.00    E2,E3,E4

我尝试了一堆不同的连接,但没有任何效果......要么我的 Total/Group_Concat 单元格中的项目太多(由于多次添加项目而总数太高,并且多个重复名称,例如“ E4,E4,E4,E4,E2,E3,E3,E4,E4..."),或者我只能从 t5 (601.00, E4) 中获取一个直接链接到 t1 的项目。

例如,查询只给了我 t1 记录 GHI 的 E4/601.00 结果:

SELECT DISTINCT t1.guid "OriginalGuid", t1.name "OriginalName", TOTAL(t5."Amount") as "TotalAmount", group_concat(t5.Name) AS "FinalNames"
FROM 
Table1 t1
LEFT  JOIN Table5 t5 ON (t1.GUID=t5.ParentGUID)
LEFT  JOIN Table4 t4 ON (t1.GUID=t4.ParentGuid AND t5.ParentGuid=t4.Guid)
LEFT  JOIN Table3 t3 ON (t1.GUID=t3.ParentGuid AND (t4.ParentGuid=t3.Guid OR t5.ParentGuid=t3.Guid))
LEFT  JOIN Table2 t2 ON (t1.GUID=t2.ParentGuid AND (t3.ParentGuid=t2.Guid AND ((t4.ParentGuid=t3.Guid And t5.ParentGuid=t4.guid) or (t5.ParentGuid=t3.Guid)) OR (t4.ParentGuid=t2.Guid and t5.ParentGuid=t4.Guid) OR (t5.ParentGuid=t2.Guid)))
GROUP BY t1.GUID;

遗憾的是,我周末的大部分时间都在研究这个问题,但我一直没能找到可行且性能合理的东西(我有一些东西似乎适用于小型数据集,但对我的完整数据集花费了 分钟,这太长了 - 虽然不幸的是,我已经丢失了 SQL)。

我现在正在继续研究解决方案,如果我找到它,我会在此处发布答案,但如果有任何帮助/想法,我将不胜感激!

这是我的 SQL Fiddle:http://sqlfiddle.com/#!5/1a2ac/55

提前感谢您的帮助。

【问题讨论】:

  • 对于表 5 中的 E3 条目,您的 SQL 小提琴数据为 -500,而不是上面示例中的 -50。

标签: sql sqlite join aggregate-functions multiple-tables


【解决方案1】:

cha 的回答还可以,但是可以通过添加一个临时表来存储从 table2 到 table5 的所有关系来进行优化。

CREATE TABLE TableRel
    ("GUID" TEXT, "ParentGUID" TEXT, "TB" TEXT);

insert into TableRel
select GUID, ParentGUID, 'TABLE2'
FROM TABLE2
UNION ALL
select GUID, ParentGUID, 'TABLE3'
FROM TABLE3
UNION ALL
select GUID, ParentGUID, 'TABLE4'
FROM TABLE4
UNION ALL
select GUID, ParentGUID, 'TABLE5'
FROM TABLE5
;

更新

然后您可以使用递归查询从 table1 获取所有后代。

WITH RECURSIVE Table1Descendants(GUID, DescendantGUID,generation) as (
  select t1.GUID, Rel.GUID ,1
  from Table1 t1
  inner join TableRel rel
  on t1.GUID= Rel.ParentGUID
  UNION ALL
  select td.GUID, Rel.GUID, td.generation+1
  from TableRel Rel
  inner join Table1Descendants td
  on td.DescendantGUID= Rel.ParentGUID
  ) 
select t1.guid , t1.name , coalesce(sum(t5.Amount) ,0)
from Table1 as t1
left join Table1Descendants
on t1.GUID = Table1Descendants.GUID
left join Table5 as t5
on t5.GUID = Table1Descendants.DescendantGUID
group by t1.guid,t1.name
order by t1.name;

或者您可以从 table5 中获取所有祖先。

WITH RECURSIVE Table1Ancestors(GUID, AncestorGUID) as (
  select t5.GUID, Rel.ParentGUID 
  from Table5 t5
  inner join TableRel rel
  on t5.GUID= Rel.GUID
  UNION ALL
  select ta.GUID, Rel.ParentGUID
  from TableRel Rel
  inner join Table1Ancestors ta
  on ta.AncestorGUID= Rel.GUID
  ) 
select t1.guid , t1.name , coalesce(sum(t5.Amount) ,0)
from Table1 as t1
left join Table1Ancestors
on t1.GUID = Table1Ancestors.AncestorGUID
left join Table5 as t5
on t5.GUID = Table1Ancestors.GUID
group by t1.guid,t1.name
order by t1.name;

但由于SQLite 3.8.3 才支持递归CTE,我没有这个版本的SQLite,这里是用PostgreSQL 测试的SQLFidle,它们与recursive query 有相似的语法,但没有total 和@ PostgreSQL 中的 987654328@ 函数。

如果您没有 SQLite 3.8.3 或更高版本,这是一个非递归查询 (SqlFiddle):

select t1.guid "OriginalGuid", t1.name "OriginalName", TOTAL(t5."Amount") as "TotalAmount", group_concat(t5.Name) AS "FinalNames"
from Table1 as t1
left join
(
  select t1.GUID, Rel.GUID as DescendantGUID, 1
  from Table1 t1
  inner join TableRel rel
  on t1.GUID= Rel.ParentGUID
  UNION ALL
  select t1.GUID, Rel2.GUID, 2
  from Table1 t1
  inner join TableRel rel1
  on t1.GUID= Rel1.ParentGUID
  inner join TableRel rel2
  on Rel1.GUID= Rel2.ParentGUID
  UNION ALL
  select t1.GUID, Rel3.GUID, 3
  from Table1 t1
  inner join TableRel rel1
  on t1.GUID= Rel1.ParentGUID
  inner join TableRel rel2
  on Rel1.GUID= Rel2.ParentGUID
  inner join TableRel rel3
  on Rel2.GUID= Rel3.ParentGUID
  UNION ALL
  select t1.GUID, Rel4.GUID, 4
  from Table1 t1
  inner join TableRel rel1
  on t1.GUID= Rel1.ParentGUID
  inner join TableRel rel2
  on Rel1.GUID= Rel2.ParentGUID
  inner join TableRel rel3
  on Rel2.GUID= Rel3.ParentGUID
  inner join TableRel rel4
  on Rel3.GUID= Rel4.ParentGUID
  ) as Table1Descendants
on t1.GUID = Table1Descendants.GUID
left join Table5 as t5
on t5.GUID = Table1Descendants.DescendantGUID
group by t1.guid,t1.name

结果:

OriginalGuid    OriginalName    TotalAmount FinalNames
ABC             A1              0.0 
DEF             A2              0.0 
GHI             A3              -601.0      E3,E2,E4

【讨论】:

  • 这似乎过于复杂。这可能适用于任意级别的层次结构,但 OP 具​​有特定的 5 个级别,并且无法更改它。
  • @simo.3792095 所以我提供了另一个非递归版本。
  • @simo.3792095 顺便说一句,你的答案的逻辑是正确的,但是当与or 连接时可能会产生很多重复的行。如果数据增长,则很难调整性能。
  • 感谢 Jaugar。我已经更新了我的帖子,表明我目前只能访问 SQLite 3.8.1,所以很遗憾,我将无法使用递归方法,即使它看起来对于解决此类问题的更通用方法很有趣,其中可能的相关表的数量可能是可变的。我还将针对我的更大数据集测试您的解决方案,以查看它与其他数据集相比的表现。
  • @JaugarChang 我可能在其他地方提到过,我使用了很多 SQL,但没有使用 SQLite,所以可能会略有不同。但是,与您的解决方案相比,我查看了我的执行计划,它明显更短,这意味着 SQL 引擎可以更有效地优化它。
【解决方案2】:

此查询将执行此操作。基本上,您需要 UNION ALL 所有组合(可能您的可能组合数量有限),然后只需将它们 LEFT JOIN 到 T1 和 group_concat 名称:

SQL Fiddle

SQLite (SQL.js) 架构设置

CREATE TABLE Table1
    ("GUID" TEXT, "Name" TEXT)
;

CREATE TABLE Table2
    ("GUID" TEXT, "ParentGUID" TEXT)
;

CREATE TABLE Table3
    ("GUID" TEXT, "ParentGUID" TEXT)
;

CREATE TABLE Table4
    ("GUID" TEXT, "ParentGUID" TEXT)
;

CREATE TABLE Table5
    ("GUID" TEXT, "Name" TEXT, "Amount" REAL, "ParentGUID" TEXT)
;

INSERT INTO Table1
    ("GUID", "Name")
VALUES
    ('ABC', 'A1')
;


INSERT INTO Table1
    ("GUID", "Name")
VALUES
    ('DEF', 'A2')
;

INSERT INTO Table1
    ("GUID", "Name")
VALUES
    ('GHI', 'A3')
;

INSERT INTO Table2
    ("GUID", "ParentGUID")
VALUES
    ('JKL', 'GHI')
;

INSERT INTO Table2
    ("GUID", "ParentGUID")
VALUES
    ('MNO', '')
;

INSERT INTO Table2
    ("GUID", "ParentGUID")
VALUES
    ('PQR', 'GHI')
;

INSERT INTO Table3
    ("GUID", "ParentGUID")
VALUES
    ('STU', 'MNO')
;

INSERT INTO Table3
    ("GUID",  "ParentGUID")
VALUES
    ('STU', 'GHI')
;

INSERT INTO Table3
    ("GUID", "ParentGUID")
VALUES
    ('VWX', 'PQR')
;


INSERT INTO Table4
    ("GUID", "ParentGUID")
VALUES
    ('YZA', 'VWX')
;

INSERT INTO Table4
    ("GUID", "ParentGUID")
VALUES
    ('BCD', '')
;

INSERT INTO Table4
    ("GUID", "ParentGUID")
VALUES
    ('EFG', 'GHI')
;

INSERT INTO Table5
    ("GUID", "ParentGUID", "Amount", "Name" )
VALUES
    ('HIJ', 'EFG', -500, 'E3')
;


INSERT INTO Table5
    ("GUID", "ParentGUID", "Amount", "Name" )
VALUES
    ('KLM', 'YZA', -702, 'E2')
;


INSERT INTO Table5
    ("GUID", "ParentGUID", "Amount", "Name" )
VALUES
    ('NOP', '', 220, 'E8')
;

INSERT INTO Table5
    ("GUID", "ParentGUID", "Amount", "Name" )
VALUES
    ('QRS', 'GHI', 601, 'E4')
;

查询 1

SELECT t1.GUID, group_concat(o.Name), COALESCE(SUM(o.Amount), 0.0) TotalAmount
FROM Table1 t1 LEFT JOIN
(
SELECT t1.GUID, t5.Name, t5.Amount
FROM 
Table1 t1
INNER JOIN Table5 t5 ON (t1.GUID=t5.ParentGUID)
UNION ALL
SELECT t1.GUID, t5.Name, t5.Amount
FROM 
Table1 t1
INNER JOIN Table4 t4 ON (t1.GUID=t4.ParentGuid)
INNER JOIN Table5 t5 ON (t5.ParentGuid=t4.Guid)
UNION ALL
SELECT t1.GUID, t5.Name, t5.Amount
FROM 
Table1 t1
INNER JOIN Table3 t3 ON (t1.GUID=t3.ParentGuid)
INNER JOIN Table5 t5 ON (t5.ParentGuid=t3.Guid)
UNION ALL
SELECT t1.GUID, t5.Name, t5.Amount
FROM 
Table1 t1
INNER JOIN Table3 t3 ON (t1.GUID=t3.ParentGuid)
INNER JOIN Table4 t4 ON (t4.ParentGuid=t3.Guid)
INNER JOIN Table5 t5 ON (t5.ParentGuid=t4.Guid)
UNION ALL
SELECT t1.GUID, t5.Name, t5.Amount
FROM 
Table1 t1
INNER JOIN Table2 t2 ON (t1.GUID=t2.ParentGuid)
INNER JOIN Table5 t5 ON (t5.ParentGuid=t2.Guid)
UNION ALL
SELECT t1.GUID, t5.Name, t5.Amount
FROM 
Table1 t1
INNER JOIN Table2 t2 ON (t1.GUID=t2.ParentGuid)
INNER JOIN Table4 t4 ON (t4.ParentGuid=t2.Guid)
INNER JOIN Table5 t5 ON (t5.ParentGuid=t4.Guid)
UNION ALL
SELECT t1.GUID, t5.Name, t5.Amount
FROM 
Table1 t1
INNER JOIN Table2 t2 ON (t1.GUID=t2.ParentGuid)
INNER JOIN Table3 t3 ON (t3.ParentGuid=t2.Guid)
INNER JOIN Table5 t5 ON (t5.ParentGuid=t3.Guid)
UNION ALL
SELECT t1.GUID, t5.Name, t5.Amount
FROM 
Table1 t1
INNER JOIN Table2 t2 ON (t1.GUID=t2.ParentGuid)
INNER JOIN Table3 t3 ON (t3.ParentGuid=t2.Guid)
INNER JOIN Table4 t4 ON (t4.ParentGuid=t3.Guid)
INNER JOIN Table5 t5 ON (t5.ParentGuid=t4.Guid)
) o ON t1.GUID = o.GUID
GROUP BY t1.GUID

Results

| GUID | group_concat(o.Name) | TotalAmount |
|------|----------------------|-------------|
|  ABC |                      |         0.0 |
|  DEF |                      |         0.0 |
|  GHI |             E2,E3,E4 |      -601.0 |

【讨论】:

  • 非常感谢!通过联合所有可能的链接,很容易看出这个解决方案是如何工作的,我只是针对我的更大数据集测试它的性能,看看它是如何工作的。感谢您的帮助。
【解决方案3】:

据我所知,您需要先应用 DISTINCT,然后再执行 TOTAL 和 CONCAT。这些函数被应用到所有行,然后 DISTINCT 被应用。因为从 t1 到 t5 有很多路径,所以需要先在子查询中排除这些路径。

SELECT sq1.guid "OriginalGuid", sq1.name "OriginalName", TOTAL(sq1."Amount") as "TotalAmount", group_concat(sq1.FinalNames) AS "FinalNames"
FROM
(SELECT DISTINCT t1.guid, t1.name, t5."Amount", t5.Name AS "FinalNames"
  FROM Table1 t1
  LEFT JOIN  Table2 t2 ON (t2.ParentGUID = t1.GUID)
  LEFT JOIN  Table3 t3 ON (t3.ParentGUID = t2.GUID
                           OR (t3.ParentGUID = t1.GUID))
  LEFT JOIN  Table4 t4 ON (t4.ParentGUID = t3.GUID 
                           OR (t4.ParentGUID = t2.GUID) 
                           OR (t4.ParentGUID = t1.GUID))
  LEFT JOIN  Table5 t5 ON (t5.ParentGUID = t4.GUID 
                           OR (t5.ParentGUID = t3.GUID) 
                           OR (t5.ParentGUID = t2.GUID) 
                           OR (t5.ParentGUID = t1.GUID))) sq1
GROUP BY sq1.guid;

关于性能问题。 我不熟悉 SQLite 字段类型,但在 SQL Server 中,text 类型是一个可变对象,最多可存储 2GB,不能用于索引。在 SQLite 中似乎有所不同,但如果您想在任何阶段将其移植到另一个 SQL 引擎,建议将其定义为 VARCHAR。

在不知道您的完整结构的情况下,我猜想每个表都有定义为主键的 GUID,并且每个表的 ParentGUID 都存在一个索引。如果正确定义了键和索引,则上述 JOINS 不应因任何特定原因而固有地变慢。

【讨论】:

  • 谢谢西莫!我喜欢这个解决方案,因为它很紧凑。我正在针对我的一些较大的数据集对其进行测试,以查看它是否适用于所有情况并具有良好的性能。
  • 顺便说一句,SQLite 仅具有类似于数据类型的 TEXT、REAL、INTEGER、BLOB 和 NULL“存储类”。如果您有兴趣,请在此处了解更多信息:sqlite.org/datatype3.html
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-10-31
  • 1970-01-01
  • 2019-12-17
  • 1970-01-01
  • 2021-06-05
  • 2014-05-31
  • 1970-01-01
相关资源
最近更新 更多