怎样把树形结构的数据存储在二维表的数据库中(100分)

  • 主题发起人 主题发起人 黎永欢
  • 开始时间 开始时间

黎永欢

Unregistered / Unconfirmed
GUEST, unregistred user!
大家好!
请问:怎样把树形结构的数据存储在二维表的数据库中(最好有一些例子和说明)。
 
嘻嘻,又有容易分骗了:)
在库中建立一张表,设立两个字段:
一个是ID,用来保存当前结点的唯一编号
另一个是PARENT_ID,用来保存当前结点的你结点的编号,
至于其他字段,则与你要保存的信息相对应就行了

 
我是用矩阵方式存贮的
象 smartkid 的这种方式以前也考虑过,但一方面查找起来,算法麻烦了点,
而且想删除一个节点,有的还不能在数据库中删除

不过我的方式也有缺点,除记录量大以外,节点层数和数据库结构的定义大小有关。
只不过用起来挺方便的
 

我用SQL SERVER 7。0的表为数据源,想设计一个动态菜单生成的平台,在维护动态菜单的时候,用到了TREEVIEW技术,首先将表结构告诉您:

/* ============================================================ */
/* Table: MENU */
/* ============================================================ */
create table MENU
(
MENU_ID numeric(6) not null,
USER_ID numeric(6) null ,
MENU_LOC numeric(6) null ,
MODEL_ID numeric(6) null ,
MENU_NAME varchar(100) null ,
MODEL_FUNCTION varchar(100) null ,
MENU_GRADE numeric(2) null ,
HOTKEY varchar(20) null ,
WIN_PARA varchar(100) null ,
SAFE numeric null ,
constraint PK_MENUS primary key (MENU_ID)
)
go

/* ============================================================ */
/* Index: PK_LOC */
/* ============================================================ */
create index PK_LOC on MENU (MENU_LOC)
go

alter table MENU
add constraint FK_MENU_REF_114_USERS foreign key (USER_ID)
references USERS (USER_ID)
go

alter table MENU
add constraint FK_MENU_REF_120_MODEL foreign key (MODEL_ID)
references MODEL (MODEL_ID)
go
====================

MENU表中:MENU_ID,MENU_LOC,MENU_GRADE是与TREEVIEW相关的关键字段.

<待续>


 
如果对动态菜单构架有兴趣,这就是个基础了。注意,一定要注意MENU——LOC字段,要以他为索引才行。
 
然后用下列程序。他可以显示MENU的数据到TREEVIEW,并开始做维护。我只做了增加子菜单,其余的比较简单。
type
Pmyrec = ^TMyRec;
TMyRec = record
menu_id: string;
end;

//关键是这段
procedure Tw_menu_wf.FormCreate(Sender: TObject);
var
j1,j2,n:integer;
mynode,subnode:ttreenode;
p:p_menu_wf.PMYREC;
begin
dm1.menu1.Open;
dm1.models.open;
dm1.menu1.IndexName:='PK_LOC';
dm1.menu1.First;

//if dm1.usersUSER_ID.AsInteger <> user_id
j1:=0;
tv1.Items.Clear;
NEW(P);
P^.menu_id:=('999999');
subnode:=tv1.items.Addobject(nil,'开始',P);
mynode:=subnode;
while not dm1.menu1.Eof do
begin
if dm1.menu1user_id.AsInteger = user_id then
begin
NEW(P);
//--------------
j2:= dm1.menu1menu_grade.asinteger;
case j2-j1 of
0: //同级
begin

P^.menu_id:=(dm1.menu1menu_id.ASSTRING);
subnode:=tv1.items.Addchildobject(myNode,dm1.menu1menu_name.asstring,P);
end;
1: //下级
begin
mynode:=subnode;
P^.menu_id:=(dm1.menu1menu_id.ASSTRING);
subnode:=tv1.items.Addchildobject(myNode,dm1.menu1menu_name.asstring,P);
end;
-300000..-1: //上级
begin
for n :=0 to j1-j2 do
begin
subnode:=subnode.Parent;
end;
mynode:=subnode;
P^.menu_id:=(dm1.menu1menu_id.ASSTRING);
subnode:=tv1.items.Addchildobject(myNode,dm1.menu1menu_name.asstring,P);
end;
else //数据有错
begin
showmessage('数据有错误');
end;
end;
//--------------
j1:=j2;
end;
dm1.menu1.next;
if dm1.menu1.Eof then break;
end;
tv1.FullExpand;
end;

...
 
兄弟,给点分救急,我快穷死了....
 
来自北大未名站:

发信人: aeroboy (三静), 信区: Delphi
标 题: 用树型结构表示科目代码的一种高效算法
发信站: 北大未名站 (2000年06月29日23:28:14 星期四), 站内信件


---- 在很多常见的财务软件中,科目代码一般都用树型结构来显示。要实现这一点,通
常的做法是用多个(嵌套)循环,甚至递归等算法,将科目表中的代码"织"成树,但这
样不但算法复杂,而且执行效率低。本人在实际的开发应用中,摸索出一种简单高效的
算法,在此和盆托出,只在抛砖引玉,找出最佳解决方案。下面介绍在Delphi中的实现
方法。
一.表结构
---- 首先建立如下结构的数据表Code.DB,并输入一些测试数据:
字段名 类型 长度 说明
aCode 字符型 20 科目代码
aName 字符型 30 科目代码名称
...... ...... ...... ......
表(一)
---- 其中,科目代码aCode的数据类型一定要字符型(一定),长度按具体要求而定,
假如要支持六级编码,且代码结构是"3-2-2-2-2-2",则该字段的长度不小于18,而其他
字段则不作要求 。另外,要为字段aCode建一索引(切记),因为要用它来排序。
二.编写程序
---- 1.新建一Project:CodeTree.drp,主窗体命名为frmMain,单元存为Main.Pas。
在frmMain上添加一TtreeView控件,命名为tveCode,一个TImageList,命名为imgIcon
,并装入三个Icon和Bmp,最后添加一Ttable控件,命名tblCode。
frmMain和各控件的属性按表(二)设置:
组件 属性 设置
FrmMain Caption '科目代码'
Font 宋体 9号
BorderStyle BsDialog
TvwCode Images ImgIcon
ReadOnly True
ImgIcon ImageList 装入三个图标
BtnClose Caption 关闭(C)
表(二)
---- 2. 单元main.pas的完整源代码如下:
unit Main;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics,
Controls, Forms, Dialogs,
Db, DBTables, ComCtrls, ImgList, StdCtrls;
type
TForm1 = class(TForm)
tvwCode: TTreeView;
tblCode: TTable;
ImageList1: TImageList;
btnClose: TButton;
procedure FormCreate(Sender: TObject);
procedure btnCloseClick(Sender: TObject);
private
{ Private declarations }
function LoadCode(crTbl:TDBDataSet):Integer;
function GetLevel(sFormat,sCode:String):Integer;
public
{ Public declarations }
end;
var
Form1: TForm1;
const
SCodeFormat = '322222'; //科目代码结构
SFirstNodeTxt = '科目代码'; //首节点显示的文字
implementation
{$R *.DFM}
//以下函数是本文的重点部分,
其主要功能是用一循环将Code.db表中的
//科目代码和科目代码名称显示出来
function TForm1.LoadCode(crTbl:TDBDataSet):Integer;
var NowID,sName,ShowTxt:String;
i,Level:Integer;
MyNode:array[0..6]of TTreeNode;
//保存各级节点,最长支持6级(重点)
begin
Screen.Cursor:=crHourGlass;
Level:=0;
With crTbl do
begin
try
if not Active then Open;
First;
tvwCode.Items.Clear;
//以下是增加第一项
MyNode[Level]:=tvwCode.Items.Add
(tvwCode.TopItem,SFirstNodeTxt);
MyNode[Level].ImageIndex:=0;
MyNode[Level].SelectedIndex:=0;
//以上是增加第一项
While Not Eof do
begin
NowID:=Trim(FieldByName('aCode').AsString);
ShowTxt:=NowID+' '+FieldByName('aName').AsString;
Level:=GetLevel(SCodeFormat,NowID);
//返回代码的级数
//以下是增加子项
//以下用上一级节点为父节点添加子节点
if Level>0 then//确保代码符合标准
begin
MyNode[Level]:=tvwCode.Items.AddChild
(MyNode[Level-1],ShowTxt);
MyNode[Level].ImageIndex:=1;
MyNode[Level].SelectedIndex:=2;
end;
//以上是增加子项
Next;
end;
finally
Close;
end;
end;
MyNode[0].Expand(False);//将首节点展开
Screen.Cursor:=crDefault;
end;
//以上函数将Code.db表中的科目代码和科目代码名称显示出来
//下面函数的功能是返回一代码的级数,
参数sFormat传递科目代码结构;
//参数sCode传递某一科目代码
function TForm1.GetLevel
(sFormat,sCode:String):Integer;
var i,Level,iLen:Integer;
begin
Level:=-1;//如果代码不符合标准,则返回-1
iLen:=0;
if (sFormat< >'')and(sCode< >'')then
for i:=1 to Length(sFormat) do
begin
iLen:=iLen+StrToInt(sFormat);
if Length(sCode)=iLen then
begin
Level:=i;
Break;
end;
end;
Result:=Level;
end;
//上面函数的功能是返回一代码的级数
procedure TForm1.FormCreate(Sender: TObject);
begin
with tblCode do
begin
DatabaseName:=ParamStr(1);
//使tblCode的DatabaseName指向应用程序所在的路径
TableName:='Code.DB'; //指向数据表Code.DB
Open;
IndexFieldNames:='aCode';
//按字段aCode排序(不要漏掉)
end;
LoadCode(tblCode);
end;
procedure TForm1.btnCloseClick(Sender: TObject);
begin
Close;
end;
end.
---- 其中,常量ScodeFormat是科目代码的代码结构,其定义的规则一定要和数据表Co
de..DB中的字段aCode的值相符。所以在实际应用中,让用户新增科目代码时,必须严格
检查其规范性,只有完全符合事先定义的代码结构,才能添加入库。
---- 函数GetLevel是求某一科目代码的级数,例如,有一科目代码"10102",在代码结
构是"322222"的情况下,调用函数GetLevel('322222','10102')将返回整数2 。
---- 当然,本文的核心是LoadCode函数,该函数用了一个循环来遍历数据表Code.DB的
所有记录,将字段aCode和aName的内容按层次显示出来。而在该函数中定义的二维数组
MyNode[0..6],则显得优为重要,在这里,它作用类似于递归中的栈。因为在TTreeVie
w添加子节点的方法AddChild(Node: TTreeNode; const S: string)中,要为其指定一父
节点作为参数,而父节点的代码级数一定是要添加节点的代码级数减1,所以只要用一数
组来动态保存和指定父节点就成功了。
三.运行结果
---- 好了,现在把Code.DB复制到和可执行文件相同的目录下,按下F9键编译运行,本
人运行的效果如图(一)。只要在以上的基础上加以完善,如增加维护功能,就可搬到
实际应用中了。当然,本算法不单能用在科目代码上,其他类似的树型结构都能奏效。
本人就已将此算法应用于[科目代码]、[物料清单(BOM)]、[库房管理]和[物料主文件
]等多个模块中,取得令人满意的效果。
---- 以上程序在中文Windows 9x、Delphi 4 C/S环境下编译通过。
 
恩,不错,跟我的差不多,区别只在于:我的表中,层次之间没有用“-”分开。
 
嘻嘻!我的方法支持无限层次!
 
luby 你的方法真的能支持无限层次吗?我愿意另加100分给你,只要你把方法发到我的
邮箱中。kens_@263.net 或 email2hui@263.net
 
多人接受答案了。
 
后退
顶部