【问题标题】:How to structure database for quick node access如何构建数据库以快速访问节点
【发布时间】:2012-01-23 02:29:59
【问题描述】:

我正在寻找一种方法来构建具有 VirtualTreeView 和 SQLite 数据库的数据库,以便快速检索数据。 VirtualTreeView 有一个 OnNodeInit 事件,但它并不总是适用于此目的。

数据是从 Usenet 新闻组获取的,需要线程化。对线程有用的数据是帖子 id(int64,也是主键)、引用(引用线程中以前帖子的字符串)。

程序在引用中搜索字符串并确定它应该在哪个 postid 下。比如post id = 1234,那么下一个post可能是1235,那么1236可能是回复1234。

这是一个可能的数据库示例:

post id    references    parent id
  1234      .... ....       0
  1235      .... ....       0
  1236      .... ....      1234

所以现在这就是它现在的样子。

现在,问题是如何构建这些数据以加快检索速度。如果只有一个根节点,我可以根据数据库条目分配 RootNodeCount,然后在 OnNodeInit 中按要求一一读取。当有子节点时,我需要以某种方式重新排列数据库,以便它知道如何根据打开的节点更快地获取子节点。

我正在考虑为附加字段“has_subnodes”分配以下子节点的 ID。当单击一个节点时,它会读取该节点和每个链接的节点。

你将如何组织这个数据库以便它可以在 OnNodeInit 中很好地读取,或者你会使用那个事件吗?节点也可以使用 AddChildNoInit() 方法启动。欢迎任何想法或指示。

更新(以及我是如何解决的)

这里有一些与虚拟树视图无关的信息: Implementing a hierarchical data structure in a database

我最终做的是使用 Modified Preorder Tree Traversal 将有关节点的信息存储在数据库中,并且每次首先请求某个节点时:

a) 它在内部缓存中查找,该缓存基本上与 VirtualTreeView 结构具有相同的结构。

b) 如果在缓存中找到,则删除此缓存条目(它永远不会超过 100 项)

c) 如果未找到,则在缓存中添加额外的 100 项(请求节点向上 50 项,向下 50 项)。如果需要,当然可以将此数量修改为 500 或 1000 个项目。有一些额外的检查来查看它需要读取多少向上/向下以避免读取过多的重复条目。

d) 如果我需要更快的速度,我可能会应用额外的技术 - 根据用户滚动 virtualtreeview 的数量从数据库加载节点 - 类似于 std::vector 分配内存的方式 - 首先我只加载 100 个节点,然后如果用户滚动很多,我加载 200,然后 400 等等......用户滚动越多,加载整个树的速度越快,但如果他/她从不滚动,仍然不会加载它。

这样,从未见过的节点永远不会从数据库中加载。它适用于使用鼠标滚轮滚动(当它通过缓存为空且需要来自磁盘的更多数据的点时偶尔会有短暂的延迟)和使用箭头按钮/键滚动。当您将滚动条拖动到某个位置(例如从底部到中间)时,速度会稍慢一些,但这是意料之中的,因为无法立即从磁盘获取数据。

最好在加载缓存/项目之前预先确定要为缓存/项目使用多少内存,滚动越快越快,但如果数据从不显示,它当然会使用更多内存。

【问题讨论】:

  • 家长。您需要家长参考
  • 基本上,最简单的树状数据有IDParentID,其中ParentID 指向它作为孩子所属的ID。将子节点放在适当的父节点下(以最简单的形式)需要遍历所有现有节点,直到找到一个 ID 等于 ParentID 的节点。尽管遍历所有 VirtualTreeView 节点非常快,但随着添加更多节点,它可能会变得非常慢。一种更快的方法是将所有节点添加为平面列表,然后将它们移动到适当的位置,尽管算法可能会更复杂一些。
  • @LightBulb 但是我失去了树的虚拟性并且不动态添加它们?如果节点和子节点很多,还没有打开的不需要添加吗?
  • 我想我在这里找到了一些有用的东西,这不是一个严格的 virtualtreeview 问题:stackoverflow.com/questions/544632/…
  • VirtualTreeView 可以处理数十万个节点而不会造成任何性能损失,即使是一百万个节点也不算多。我建议您先尝试几种不同的方法,然后再选择一种可以解决您的问题的方法。我建议的只是一个基本想法,未经测试,但如果实施得当,应该可以工作。当然,还有更好的算法。我只是给了你基本的想法。如果我有时间,我会自己进行模拟,如果我发现更适合您的情况,我会在此处发布。

标签: delphi sqlite c++builder hierarchical-data virtualtreeview


【解决方案1】:

不是最优雅的,但这是我用来填充树的方法。

它只需要两个简单查询的数据访问,其余的都在客户端完成。

它将轻松加载数万个节点。 (现在看,我可能只需要一个查询就可以逃脱 - 它有点旧!):

 procedure TFrameComponentViewer.LoadComponentTree;
var
RootNodeData : PMasterComponent;
CompQ,ParentQ : TMyQuery;

procedure PopulateNodeData(Node: PVirtualNode;ComponentID : integer);
var NodeData : PMasterComponent;
begin
   if CompQ.Locate('ComponentID',ComponentID,[loCaseInsensitive]) then
   begin
     NodeData := TreeComponents.GetNodeData(Node);
     //Populate your desired TreeData
     NodeData.ComponentID := CompQ.Fields[fldComponentID].AsInteger;
     NodeData.ComponentCode := CompQ.Fields[fldComponentCode].AsString;
     NodeData.ComponentType := CompQ.Fields[fldComponentType].AsInteger;
     NodeData.IsPipeline := CompQ.Fields[fldComponentIsPipeline].AsBoolean;
     NodeData.Description := CompQ.Fields[fldComponentDescription].AsString;
     NodeData.StartKP := CompQ.Fields[fldComponentStartKP].AsFloat;
     NodeData.EndKP := CompQ.Fields[fldComponentEndKP].AsFloat;
     NodeData.Diameter := CompQ.Fields[fldComponentDiameter].AsFloat;
     NodeData.WallThickness := CompQ.Fields[fldComponentWallThickness].AsFloat;
     NodeData.CriticalSpanLength := CompQ.Fields[fldComponentCSL].AsFloat;
     NodeData.Historical := CompQ.Fields[fldComponentHistorical].AsBoolean;
   end;
end;

procedure AddNodesRecursive(ParentNode : PVirtualNode;ParentNodeID : Integer);
var AddedNode : PVirtualNode;
AddedNodeData : PMasterComponent;
Children : Array of Integer;
i : Integer;
begin
     try
        ParentQ.Filtered := False;
        ParentQ.Filter := 'Parent_ID = '+InttoStr(ParentNodeID);
        ParentQ.Filtered := True;
        ParentQ.First;
        SetLength(Children,ParentQ.RecordCount);
        for i:=0 to ParentQ.RecordCount-1 do
        begin
             Children[i] := ParentQ.Fields[0].AsInteger;
             ParentQ.Next;
        end;
        for i:=0 to High(Children) do
        begin
             AddedNode := TreeComponents.AddChild(ParentNode);
             AddedNodeData := TreeComponents.GetNodeData(AddedNode);
             System.Initialize(AddedNodeData^); //initialize memory
             PopulateNodeData(AddedNode,Children[i],CompQ);
             AddNodesRecursive(AddedNode,AddedNodeData.ComponentID);
         end;
     finally
     end;
end;

begin
   TreeComponents.BeginUpdate;
   treeComponents.Clear;
   CompQ := TMyQuery.Create(nil);
   ParentQ := TMyQuery.Create(nil);
   try
      CompQ.Connection := DataBaseline.BaseLineConnection;
      CompQ.SQL.Add('SELECT * FROM Components');
      CompQ.Open;
      ParentQ.Connection := DataBaseline.BaseLineConnection;
      ParentQ.Close;
      ParentQ.SQL.Clear;
      ParentQ.SQL.Add('SELECT ComponentID,Parent_ID FROM Components ORDER BY OrderNo');
      ParentQ.Open;
      RootNode := TreeComponents.AddChild(nil);
      RootNodeData := TreeComponents.GetNodeData(RootNode);
      System.Initialize(RootNodeData^); //initialize memory
      RootNodeData.ComponentID := -1;
      AddNodesRecursive(RootNode,-1);
   finally
     TreeComponents.EndUpdate;
     TreeComponents.FullExpand;
     CompQ.Close;
     ParentQ.Close;
     FreeandNil(CompQ);
     FreeandNil(ParentQ);
   end;
end;

注意:OrderBy 列是可选的,我需要它,因为我的树是特定于订单的。

所以数据库有这三列,加上您需要的任何自定义数据:

IDParentID(-1 表示没有父级)、OrderNo

【讨论】:

  • 这个解决方案很好用,我不想失去虚拟树视图的虚拟性。我目前使用的是将项目添加到缓存中,然后首先在 OnNodeInit 中查看缓存,然后如果缓存不够且不包含所需的节点,则我使用修改的预排序树遍历数据从数据库中填充更多项目。这似乎工作得足够快,并且不会将不需要的数据加载到整个树中。
  • 没问题,很高兴你有解决方案
【解决方案2】:

您希望将分层​​数据存储在数据库中。
问题是 SQL 不能很好地处理这种数据。

您有许多解决方案,每个解决方案都有其优缺点。
如果您想了解每种方法,请点击以下链接:

http://www.sitepoint.com/hierarchical-data-database/
http://www.sitepoint.com/hierarchical-data-database-2/

我个人最喜欢的是Modified Preorder Tree Traversal

在这里,您以一种非常反直觉的方式将左右节点存储在数据库中,这使得节点的插入有点慢,但检索却快如闪电。

您可以在 Delphi 中编写您的逻辑,但我更喜欢在我选择的数据库中使用存储过程。
这样,您在 Delphi 中的逻辑就保持简单,如果数据库更改您的 Delphi 代码,则不必这样做。 如果您愿意,我可以包含存储过程的 SQL 代码,但现在不行,因为该代码不在我现在随身携带的笔记本电脑上。

【讨论】:

  • 我也喜欢Modified Preorder Tree Traversal,因为数据添加一次,然后很少修改,但搜索速度很快。
  • 另外,lineage 方法似乎工作得很好 - ferdychristant.com/blog/archive/DOMM-7QJPM7 - 它没有使用 David Chandler 的专利方法(无论如何这对于可变数量的子节点是无用的)。
猜你喜欢
  • 1970-01-01
  • 2023-03-25
  • 2015-04-07
  • 2017-03-29
  • 2020-10-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多