注册 登录
编程论坛 VFP论坛

如何由 开支明细帐.dbf 动态生成 开支统计表.dbf

王咸美 发布于 2025-10-20 15:58, 849 次点击
现有 开支明细帐.dbf 我想动态生成 开支统计表.dbf
要求:
1、开支统计表中除“姓名”、“合计”字段外,其它字段由 开支明细帐.dbf中“支出明细”动态生成,具体字段数量不固定;
2、“合计”字段要放在最右边;
3、最后统计毎个学生支出金额,写入“合计”中。
【由于本人是新手,对vfp纯属个人爱好,不喜勿喷!】
请各位高手不吝赐教,万分感谢!!!
只有本站会员才能查看附件,请 登录

原始表:开支明细帐.dbf
只有本站会员才能查看附件,请 登录

结果表:开支统计表.dbf
只有本站会员才能查看附件,请 登录


[此贴子已经被作者于2025-10-20 16:00编辑过]

53 回复
#2
my23182025-10-20 18:32
数据表设计不好,不便于统计
#3
laowan0012025-10-21 08:45
跟费用明细账那个贴是一个意思
看上去格式规范了,但看到后面,逗号又变成全角了,不知是否还有别的问题
楼上说的正是,数据表设计不好,其实就是源头没设计好
对于很随意的用户,你永远也不知道下一步他会做出什么来,所以你会有永远解决不完的问题
#4
王咸美2025-10-21 10:20
数据表已作修改,不知是否规范
只有本站会员才能查看附件,请 登录

只有本站会员才能查看附件,请 登录
#5
wengjl2025-10-21 10:31
程序代码:


       ***************
       * 名称:将开支明细转换成按物品清单列表
       * 来源:https://bbs.bc-/thread-514547-1-1.html
       * 时间:2025-10-21
       * 思路:因原表开支明细中的记录缺少规律,第一步将“,”号和“,”号进行统一,使之有规律性;
       *       第二步提取“物品”的名称;第三步创建统计表;第四步进行统计;第五步算合计。
       * ★★★★★★★★以上五步中有一个BUG,即 当“物品”的长度超过5个汉字,会出错★★★★★★★★
       ***************
       CLEAR
       SET SAFETY off
       SET engi 70
       CLOSE DATABASES
      
       SELECT * from 开支明细帐 into table ls
       SELECT ls
       REPLACE 支出明细 with STRTRAN(支出明细,[],[,]) ALL   &&& 第一步完成
       zfc=[,]
       SELECT ls
       GO top
       SCAN
         fymx=支出明细
         n=OCCURS([],fymx)
         k1=1
         FOR i=1 to n
           k2=AT([],fymx,i)
           c_zf=SUBSTR(fymx,k1,k2-k1+2)               &&& 得到如“巧克力9元”的消费内容(后续作字段名用的)
           FOR j=1 to LEN(c_zf)
             IF SUBSTR(c_zf,j,1)$[0123456789.]
               k=j                                    &&& 得到 金额数字开始的位置值
               EXIT for
             ENDIF
           ENDFOR  
           aa=SUBSTR(c_zf,1,k-1)                      &&& 得到 物品名称
           IF !aa$zfc
             zfc=zfc+[0000000.00 as ]+aa+[,]          &&& 形成SQL语句中的字段列表
           ENDIF
           k1=k2+3
         ENDFOR
         SELECT ls
       ENDSCAN                                        &&& 第二步完成
       c_zdm=[姓名]+zfc+[合计]
       SELECT &c_zdm. from ls into table 开支统计表   &&& 第三步完成
       SELECT 开支统计表
       USE 开支统计表 alia bmk
       SELECT ls
       GO top
       SCAN
         fymx=支出明细
         n=OCCURS([],fymx)                          &&& 记录要循环的次数
         k1=1
         FOR i=1 to n
           k2=AT([],fymx,i)
           c_zf=SUBSTR(fymx,k1,k2-k1+2)               &&& 得到如“巧克力9元”的消费内容(后续作字段名用的)
           FOR j=1 to LEN(c_zf)
             IF SUBSTR(c_zf,j,1)$[0123456789.]
               k=j                                    &&& 得到 金额 数字开始的位置值
               EXIT for
             ENDIF
           ENDFOR  
           zdm=SUBSTR(c_zf,1,k-1)                     &&& 得到 物品名称
           aa=[]
           FOR ii=1 to LEN(c_zf)
             IF SUBSTR(c_zf,ii,1)$[0123456789.]
               aa=aa+SUBSTR(c_zf,ii,1)                &&& 得到 金额 文本
             ENDIF
           ENDFOR
           n_rmb=VAL(aa)                              &&& 金额文本转数值
           SELECT bmk
           LOCATE for bmk.姓名=ls.姓名
           IF FOUND()
             REPLACE ([bmk.]+zdm) with n_rmb          &&& 记录消费的金额
           ENDIF
           k1=k2+3                                    &&& 确定 下一个 取词的起始位置值班
         ENDFOR
         SELECT ls
       ENDSCAN                                        &&& 第四步完成
       SELECT bmk
       GO top
       SCAN
         sn=0
         FOR i=2 to FCOUNT()-1
           sn=sn+EVALUATE(FIELD(i))                   &&& 求得某人的消费金额和
         ENDFOR
         REPLACE 合计 with ALLTRIM(TRANSFORM(sn,[]))+[] &&& 写入合计数(王老师这次的开支明细表上合计字段 用了 “字符型” !!!★★★ 真 的 讨 厌 ★★★)
         SELECT bmk
       ENDSCAN                                        &&& 第五步完成
       CLOSE DATABASES
       QUIT
       * 边试边写,已成功
       * 原始表中的开支明细记载方法 属于不可取的方法



王老师,要在初始数据表设计上下功夫,代码可以很简单的!
上午没什么事,帮你做了。其他人做代码是可以再精简一些的!
#6
王咸美2025-10-21 10:39
谢谢!!!
#7
wengjl2025-10-21 10:43
只有本站会员才能查看附件,请 登录


原始设计成这样,统计会很简单的
#8
王咸美2025-10-21 11:27
如何确保“合计”字段放在所有字段的最右边,请指点!
#9
wengjl2025-10-21 12:31
只有本站会员才能查看附件,请 登录

5楼代码中的这一句,让合计在最右边

你把这一句子中的姓名 与  合计  交换一下位置执行一下,打开表看看就知道了

[此贴子已经被作者于2025-10-21 12:36编辑过]

#10
王咸美2025-10-21 16:16
谢谢!我想将生成的“开支统计表.dbf”中的“合计”放到表的最后(最右边)显示,不知可行?另外,可否去掉小数部分,请赐教!

[此贴子已经被作者于2025-10-21 16:30编辑过]

#11
吹水佬2025-10-21 19:23
回复 4楼 王咸美
每个费用项目有个","分隔算是结构化了。
只有本站会员才能查看附件,请 登录

程序代码:

DECLARE long sscanf IN msvcr71 long, string, single@, long@
DECLARE long strcpy IN msvcr71 string@, string
buffer = REPLICATE(0h00,256)
a = 0.00
n = 0
CREATE CURSOR tt (姓名 c(10), 项目 v(10), 金额 n(6,2))
USE c:\_temp\开支明细帐.dbf ALIAS tb IN 0
SELECT tb
SCAN
    FOR i=1 TO ALINES(arr,tb.支出明细,5,",")
        p  = strcpy(@buffer, arr[i])
        ph = p
        DO WHILE SYS(2600,p,1) != 0h00
            IF sscanf(p,"%f%n",@a,@n) == 1
                INSERT INTO tt VALUES (tb.姓名, SYS(2600,ph,p-ph), a)
                EXIT
            ELSE
                p = p+1
            ENDIF
        ENDDO
    ENDFOR
ENDSCAN
cmd = "CREATE TABLE 开支统计表 (姓名 c(10)"
SELECT DISTINCT 项目 FROM tt INTO CURSOR tmp
SCAN
    cmd = cmd + "," + 项目 + " n(6,2)"
ENDSCAN
cmd = cmd + ", 合计 n(6,2))"
EXECSCRIPT(cmd)
SELECT DISTINCT 姓名 FROM tt INTO CURSOR tmp
SELECT 开支统计表
APPEND FROM DBF("tmp")
INDEX on 姓名 TAG 姓名
SELECT tt
SET RELATION TO 姓名 INTO "开支统计表"
SCAN
    REPLACE (tt.项目) WITH tt.金额, 合计 WITH 合计 + tt.金额 IN "开支统计表"
ENDSCAN
SELECT * FROM 开支统计表
#12
schtg2025-10-21 19:24
#13
sam_jiang2025-10-21 19:55
这么多狐友给出了程序,你应该好好读一下,碰到类似需求尝试一下自己解决。

既是初学者,更应该去尝试,尝试过程中,如果运行有问题,自己找不出来,再发上来求助,这样对你的才有帮助!


#14
王咸美2025-10-21 20:37
谢谢各位大佬的热心指导!!!
奇怪的是我在电脑上运行后“费用汇总统计”只有“姓名”和“合计”,并且都是空的,是软件的问题,还是其他原因?
只有本站会员才能查看附件,请 登录
#15
吹水佬2025-10-21 20:40
回复 14楼 王咸美
是“开支统计表”
#16
王咸美2025-10-21 21:06
同样是空的
#17
吹水佬2025-10-21 21:23
回复 16楼 王咸美
将你的代码放上来看看好了
#18
王咸美2025-10-21 21:45
DECLARE long sscanf IN msvcr71 long, string, single@, long@
DECLARE long strcpy IN msvcr71 string@, string
buffer = REPLICATE(0h00,256)
a = 0.00
n = 0
CREATE CURSOR tt (姓名 c(10), 项目 v(10), 金额 n(6,2))
USE E:\temp9\开支明细帐.dbf ALIAS tb IN 0
SELECT tb
SCAN
    FOR i=1 TO ALINES(arr,tb.支出明细,5,",")
        p  = strcpy(@buffer, arr[i])
        ph = p
        DO WHILE SYS(2600,p,1) != 0h00
            IF sscanf(p,"%f%n",@a,@n) == 1
                INSERT INTO tt VALUES (tb.姓名, SYS(2600,ph,p-ph), a)
                EXIT
            ELSE
                p = p+1
            ENDIF
        ENDDO
    ENDFOR
ENDSCAN
cmd = "CREATE TABLE 开支统计表 (姓名 c(10)"
SELECT DISTINCT 项目 FROM tt INTO CURSOR tmp
SCAN
    cmd = cmd + "," + 项目 + " n(6,2)"
ENDSCAN
cmd = cmd + ", 合计 n(6,2))"
EXECSCRIPT(cmd)
SELECT DISTINCT 姓名 FROM tt INTO CURSOR tmp
SELECT 开支统计表
APPEND FROM DBF("tmp")
INDEX on 姓名 TAG 姓名
SELECT tt
SET RELATION TO 姓名 INTO "开支统计表"
SCAN
    REPLACE (tt.项目) WITH tt.金额, 合计 WITH 合计 + tt.金额 IN "开支统计表"
ENDSCAN
SELECT * FROM 开支统计表
#19
吹水佬2025-10-21 21:56
回复 18楼 王咸美
运行结果截图看看  
#20
王咸美2025-10-21 22:21
明天发截图
#21
王咸美2025-10-21 22:32
生成文件名称为“开支统计表.dbf",打开后连标题都变了
只有本站会员才能查看附件,请 登录
#22
王咸美2025-10-21 22:38
可能是电脑中病毒,明天重装系统看一看。
#23
吹水佬2025-10-21 22:40
回复 21楼 王咸美
肯定不是那个统计表
估计是路径问题,试试在文件头设置默认路径为当前prg路径。
如:
PUBLIC cDefPath
cDefPath = ADDBS(JUSTPATH(SYS(16)))
SET DEFAULT TO (cDefPath)
#24
王咸美2025-10-21 22:43
谢谢!明天再试一下,实在不行,重装系统。
#25
吹水佬2025-10-21 22:43
最后这样看看“开支统计表”文件存放在哪里:
MESSAGEBOX(DBF("开支统计表"))
#26
王咸美2025-10-22 00:00
重装WINDOWS XP系统后生成的“开支统计表.dbf”只有 姓名、合计两个字段,且为空表。是不是系统老旧的问题?

[此贴子已经被作者于2025-10-22 00:01编辑过]

#27
吹水佬2025-10-22 00:17
回复 26楼 王咸美
把你的文件打包上来测试
#28
sam_jiang2025-10-22 01:40
回复 27楼 吹水佬
你们俩的文件不在一个目录
#29
王咸美2025-10-22 07:59
@吹水佬 这是供测试用的程序和表文件,请赐教!
只有本站会员才能查看附件,请 登录
#30
chychychy2025-10-22 08:25
吹版程序运行没问题,猜测是你个人路径的问题:你用桌面的“Visual FoxPro 9.0”的快捷方式打开prg文件,他的实际路径"C:\Program Files (x86)\VFP9",而你的原文件的实际路径是在“E:\temp9\开支明细帐.dbf”,可以在原始文件夹中先打开数据表,再从这里面运行prg程序,在吹版程序开头上一句CLOSE DATABASES,再修改一下路径即可。注意你安装程序和原始文件的路径,否则很容易将结果生成在安装程序所在目录。程序和运行截图如下
只有本站会员才能查看附件,请 登录

程序代码:

CLOSE DATABASES
DECLARE long sscanf IN msvcr71 long, string, single@, long@
DECLARE long strcpy IN msvcr71 string@, string
buffer = REPLICATE(0h00,256)
a = 0.00
n = 0
CREATE CURSOR tt (姓名 c(10), 项目 v(10), 金额 n(6,2))
*USE c:\_temp\开支明细帐.dbf ALIAS tb IN 0
USE 开支明细帐.dbf ALIAS tb IN 0
SELECT tb
SCAN
    FOR i=1 TO ALINES(arr,tb.支出明细,5,",")
        p  = strcpy(@buffer, arr[i])
        ph = p
        DO WHILE SYS(2600,p,1) != 0h00
            IF sscanf(p,"%f%n",@a,@n) == 1
                INSERT INTO tt VALUES (tb.姓名, SYS(2600,ph,p-ph), a)
                EXIT
            ELSE
                p = p+1
            ENDIF
        ENDDO
    ENDFOR
ENDSCAN
cmd = "CREATE TABLE 开支统计表 (姓名 c(10)"
SELECT DISTINCT 项目 FROM tt INTO CURSOR tmp
SCAN
    cmd = cmd + "," + 项目 + " n(6,2)"
ENDSCAN
cmd = cmd + ", 合计 n(6,2))"
EXECSCRIPT(cmd)
SELECT DISTINCT 姓名 FROM tt INTO CURSOR tmp
SELECT 开支统计表
APPEND FROM DBF("tmp")
INDEX on 姓名 TAG 姓名
SELECT tt
SET RELATION TO 姓名 INTO "开支统计表"
SCAN
    REPLACE (tt.项目) WITH tt.金额, 合计 WITH 合计 + tt.金额 IN "开支统计表"
ENDSCAN
SELECT * FROM 开支统计表
#31
王咸美2025-10-22 09:50
谢谢指点!我已在程序中加入下列语句,仍不能生成“开支统计表.dbf"
PUBLIC cPath
cPath=ADDBS(JUSTPATH(SYS(16)))
SET DEFAULT TO (cPath)

[此贴子已经被作者于2025-10-22 09:55编辑过]

#32
chychychy2025-10-22 09:54
回复 5楼 wengjl
学习了,写程序的习惯非常值得我这样新生学习。细节满满(王老师这次的开支明细表上合计字段 用了 “字符型” !!!★★★ 真 的 讨 厌 ★★★)但是帖主第二版有改成数值型了,按道理数值型更方便
#33
chychychy2025-10-22 10:11
回复 31楼 王咸美
感觉还是路径问题,你用桌面快捷方式先打开vfp程序和先在文件夹里打开dbf数据表或者直接打开prg文件,不同方式导致的默认路径是不一样的。你用吹版的MESSAGEBOX(DBF("开支统计表"))一测试就知道

[此贴子已经被作者于2025-10-22 10:12编辑过]

#34
吹水佬2025-10-22 10:44
以下是引用王咸美在2025-10-22 07:59:50的发言:

@吹水佬 这是供测试用的程序和表文件,请赐教!

不清楚你的代码是不是改过
其中:
FOR i=1 TO ALINES(arr,tb.支出明细,5,",")
注意那个","号,你原数据是全角","号。
应该是:
ALINES(arr,tb.支出明细,5,",")


测试时在命令窗口输入:
do ?
选择那个 cx.prg 就行



[此贴子已经被作者于2025-10-22 10:47编辑过]

#35
王咸美2025-10-22 10:52
谢谢!有空再试一下。
#36
chychychy2025-10-22 13:33
吹版真是高手,我用调试器发现中间运行的数据是不完整的,感觉帖主上传程序有问题,肉眼没发现,就用豆包比对发现就是下面这句中逗号分隔符的问题
  *FOR i=1 TO ALINES(arr,tb.支出明细,5,",")&&&帖主用的是英文逗号
    FOR i=1 TO ALINES(arr,tb.支出明细,5,",")&&&吹版源程序中文逗号。
因为数据表里就是中文逗号,数据源的分隔符不匹配,导致程序运行数据提取不准确了

[此贴子已经被作者于2025-10-22 13:37编辑过]

#37
王咸美2025-10-22 14:45
按要求修改以后,仍然没有各项目及有关数据。
#38
chychychy2025-10-22 15:19
回复 37楼 王咸美
按道理是不会的,我这边测试了没有任何问题
只有本站会员才能查看附件,请 登录

程序代码:

CLOSE DATABASES
CLEAR ALL
PUBLIC cPath
cPath=ADDBS(JUSTPATH(SYS(16)))
SET DEFAULT TO (cPath)
DECLARE long sscanf IN msvcr71 long, string, single@, long@
DECLARE long strcpy IN msvcr71 string@, string
buffer = REPLICATE(0h00,256)
a = 0.00
n = 0
CREATE CURSOR tt (姓名 c(10), 项目 v(10), 金额 n(6,2))
USE 开支明细帐.dbf ALIAS tb IN 0
SELECT tb
SCAN
    *FOR i=1 TO ALINES(arr,tb.支出明细,5,",")
    FOR i=1 TO ALINES(arr,tb.支出明细,5,",")
        p  = strcpy(@buffer, arr[i])
        ph = p
        DO WHILE SYS(2600,p,1) != 0h00
            IF sscanf(p,"%f%n",@a,@n) == 1
                INSERT INTO tt VALUES (tb.姓名, SYS(2600,ph,p-ph), a)
                EXIT
            ELSE
                p = p+1
            ENDIF
        ENDDO
    ENDFOR
ENDSCAN
cmd = "CREATE TABLE 开支统计表 (姓名 c(10)"
SELECT DISTINCT 项目 FROM tt INTO CURSOR tmp
SCAN
    cmd = cmd + "," + 项目 + " n(6,2)"
ENDSCAN
cmd = cmd + ", 合计 n(6,2))"
EXECSCRIPT(cmd)
SELECT DISTINCT 姓名 FROM tt INTO CURSOR tmp
SELECT 开支统计表
APPEND FROM DBF("tmp")
INDEX on 姓名 TAG 姓名
SELECT tt
SET RELATION TO 姓名 INTO "开支统计表"
SCAN
    REPLACE (tt.项目) WITH tt.金额, 合计 WITH 合计 + tt.金额 IN "开支统计表"
ENDSCAN
SELECT * FROM 开支统计表
#39
王咸美2025-10-22 15:40
谢谢!可能我的vfp9.0缺少外部DLL函数(msvcr71.DLL)。谁有完整的vfp软件请发送一份,谢谢!!!

[此贴子已经被作者于2025-10-22 19:23编辑过]

#40
chychychy2025-10-22 15:59
回复 39楼 王咸美
测试是没问题,可以直接下载使用。我个人总结:得向论坛内高手上传原始数据和准确表达想达到的目的。你数据表(文件夹等)如果没变直接下载吹版的就可以,如果有变化得自己将他们高手反馈的适当修改(比如你第一次上传合计是文本型、第二次上传合计是数值型)
#41
csyx2025-10-22 16:09
以下是引用王咸美在2025-10-22 15:40:03的发言:

真是无语了。

这句:DO WHILE SYS(2600,p,1) != 0h00
改成:DO WHILE !(SYS(2600,p,1) == 0h00)

用改后循环在一台xp上运行了30次,只遇到一次提取失败

在自己本本VM虚拟机上安装个xp,用改后循环运行100次,无失败
两台机的 set('exact') 默认设置均为 ON



[此贴子已经被作者于2025-10-22 18:40编辑过]

#42
chychychy2025-10-22 17:06
回复 41楼 csyx
厉害,高手看问题透彻,原来与操作系统有关。
#43
王咸美2025-10-22 19:38
@csyx 谢谢!问题完美解决。
#44
sam_jiang2025-10-22 19:59
为了避免中英文逗号问题,可以考虑在alines函数中同时使用中英文的“,”
FOR i=1 TO ALINES(arr,tb.支出明细,5,",",",")
这样无论是中文的英文的都可以了。

简单测试一下:
cstr="1,2,3,5,6,7"
n=ALINES(atemp,cstr,5,",",",") &&前面一个逗号半角的,后面一个全角的。
?n &&得到n=6
#45
王咸美2025-10-22 20:04
感谢+抱歉,分数有限实在分不开,不少指点我的人没有得到分数,实在不好意思,多谢了!!!
#46
王咸美2025-10-22 20:07
@sam_jiang 这个方法好!
#47
吹水佬2025-10-22 21:59
以下是引用csyx在2025-10-22 16:09:05的发言:


这句:DO WHILE SYS(2600,p,1) != 0h00
改成:DO WHILE !(SYS(2600,p,1) == 0h00)

用改后循环在一台xp上运行了30次,只遇到一次提取失败

在自己本本VM虚拟机上安装个xp,用改后循环运行100次,无失败
两台机的 set('exact') 默认设置均为 ON

应该与XP关系不大。
可能是SYS(2600)抛出的异常,用指针操作要谨慎。

不清楚vfp调用API按“@字符串”作为字符串in/out参数的内部处理方法是怎样,每次调用“@字符串”后这个“字符串”的内存分配应该是变动的。

或者可以这样试试看还会不会出错:每次获取字符串指针都要重新定义字符串buffer
        buffer = REPLICATE(0h00,256)        
        p  = strcpy(@buffer, arr[i])

最好的方法应该是先分配一块堆内存块作为字符串buffer,这样得到的buffer指针是固定不变的。

执行 SYS(2600) 前也可以先调用相关api检测:
   IsBadCodePtr()
   IsBadReadPtr()
   IsBadWritePtr()
   IsBadStringPtrA()


#48
schtg2025-10-23 06:05
回复 44楼 sam_jiang
#49
hsfisher2025-10-23 08:28
学习了
#50
chychychy2025-10-23 08:41
回复 47楼 吹水佬
原来原来如此,学习了
#51
csyx2025-10-23 20:13
以下是引用sam_jiang在2025-10-22 19:59:12的发言:
为了避免中英文逗号问题,可以考虑在alines函数中同时使用中英文的“,”
FOR i=1 TO ALINES(arr,tb.支出明细,5,",",",")
这样无论是中文的英文的都可以了。

此法处理中文虽简单,却不安全,需视使用场景而定
试试:? ALines(arr, "郑琯芸,祝琣远", ",",",")



[此贴子已经被作者于2025-10-23 20:29编辑过]

12