5

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

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

该程序在引用中搜索字符串并确定它应该在哪个 postid 下。因此,例如帖子 id = 1234,那么下一个帖子可能是 1235,然后 1236 可能是对 1234 的回复。

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

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

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

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

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

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

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

这里有一些与虚拟树视图无关的信息: 在数据库中实现分层数据结构

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

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

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

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

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

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

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

4

2 回答 2

2

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

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

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

 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列是可选的,我需要它,因为我的树是特定于订单的。

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

ID, ParentID(-1 表示没有父母),OrderNo

于 2011-12-19T05:26:58.133 回答
1

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

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

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

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

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

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

于 2011-12-19T09:23:29.280 回答