如何构建数据库以进行快速节点访问

时间:2011-12-18 16:50:39

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

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

数据来自Usenet新闻组,需要进行线程化。对线程有用的数据是post id(int64,也是主键),引用(引用线程中先前帖子的字符串)。

程序在引用中搜索字符串并确定它应该在哪个位置。所以例如post id = 1234,那么下一篇文章可能是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)如果我需要更高的速度,我可以应用额外的技术 - 根据用户滚动虚拟视图的数量从数据库加载节点 - 类似于std :: vector如何分配内存 - 首先我只加载100个节点,然后如果用户滚动很多,我加载200,然后400等...越多的用户滚动加载整个树的速度越快,但如果他/她从不滚动,仍然不会加载它。

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

最好是在加载之前预先确定我想要用于缓存/项目的内存量,滚动速度越快但当然如果数据永远不会显示则使用更多内存。

2 个答案:

答案 0 :(得分: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

答案 1 :(得分: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代码,但现在不能,因为那些代码不在笔记本电脑上,我现在已经和我在一起了。