2023-08
10

开始VBA+ADO+SQL

By xrspook @ 8:20:30 归类于: 烂日记

几天之前我第一次听说VBA+ADO+SQL可以组成一个非常牛逼的东西。当我听说要在VBA里面运行SQL的时候,我有怀疑过这是不是我以前遇到的那一款?但是在SQL的外面又有一个ADO,这就很不一样,因为ADO这个东西你甚至可以控制只执行一次,然后就自动消失.当然你可你也可以用打开的方式去折腾,但是对低端用户来说,光是执行就可以完成大多数事情。

在学习VBA的时候,首先会告诉你,如果要操作工作簿,就得先把它打开。用完不用了,你还得把它关上,不打开工作簿是没办法进行操作的。同样,在学习用python控制Excel的时候,也是这么个思路。按照正常人的想法,你要获取里面的东西,当然得开门,哪怕实际上你只是进去看一看,你并不改动里面的东西。比如在做查询的时候,实际上你不过是收集里面的资料,你不需要改动里面的任何摆设,把资料收集回来了以后你可以再加工,但是原始的那个东西是不变的。为什么跨表表查询的时候,我首先想到的是Power Query,就是因为那个东西本来就是开发出来做数据清洗的。核心的观点就是不改动原始数据,通过可重复的步骤,你就能得到规范的东西,然后再给其它玩意调用。但是VBA+ADO+SQL彻底颠覆了我过往的认知。因为这三个东西加起来就意味着你要获取工作簿里面的东西,你根本不需要打开它。通过链接的方式,就可以到达那个地方,虽然实际上这样是无法修改文件内容的,但是跟PQ一样,可以对已经获取的东西进行再加工。所以准确来说,虽然做出来的菜是不一样的,但实际上原料还是那些,牛逼的地方在于,那三个东西甚至连文件都不用打开,但是PQ在运行的时候,实际上是要打开文件。如果你的查询设置比较复杂,他还得把同一个源文件一次又一次打开。因为PQ里的查询是并行的,你没办法控制哪个先哪个后,所以当它们抢着打开同一个源文件的时候就会出现错误,就会出现刷新失败,然后说不准什么时候就会告诉你原始的数据格式不对,反正最终结果就是刷新失败,但是当你再次刷新的时候又好了。

VBA这个东西在我听说有PQ和PP之前就已经存在。大家可以通过VBA的方式批量打开文件夹批量合并里面的文件,但问题是合并是合并了,但是却非常不智能,因为最简单的模式只能保证内容都给你贴上去了,但是对不对得上你自己碰运气。比如第一个表跟第二个表某些列的顺序是不一样的,结果你就会拼出个寂寞,但是PQ和PP就不会犯这种错误。因为他们有连接方式这种概念,同时,它们所处理的不是某个单元格,主要操作的是字段,又或者说是列。当然,如果你条件足够,可以精确到记录。我在python里,只要把从Excel读取到的数据赋予pandas,接下来你就可以用 dataframe的方式去处理那些东西。我个人觉得,只要格式化了,只要那个数据框架定下来了,SQL、PP、PQ、python这4个好像不太一样的东西都能干非常类似的活儿。

首先是Power Query版本,接着是Power Pivot版本,第三个是python版本,现在我做的是VBA+ADO+SQL的版本。SQL in Excel是一个非常屌丝的东西,相比于其它软件里面的SQL,Excel里可用的公式我个人觉得会让人非常抓狂,是阉割版的SQL。本来就很难找到一个标准的说法告诉人你SQL in Excel到底有什么可以怎么用,当你用其它方式套在Excel里发现用不了的时候,才知道原来根本不能这样。对我这个新手来说,我只是大概知道SQL可以这样,应该这么整,但是套了一个Excel的条件以后就变成了大概应该是这样的事情实际上不行。

一天到晚都在为代码纠结。反而让我日子过得很实在,虽然运动和打游戏的时间被严重侵占了。

2023-08
8

在Excel VBA里折腾SQL

By xrspook @ 8:54:30 归类于: 烂日记

又花了几乎一天的时间去研究在VBA里使用SQL。上午我主要卡在为什么我运行不了SQL这个东西。最主要的原因是我贴过去的那个例子是2014年的,那个时候的office跟现在的很不一样,所以是不是代码里的某些参数要改过来?4跟8改成12,我的确改了,但还是不行。吃过午饭以后我我把ExcelHome论坛前天大家才写出来的SQL查询复制过去,发现果然就可以了,因为除了12要都改以外,还得改一个Microsoft之后的单词…… 为了这个运行不了我还折腾的一番到底我需不需要装32位的AccessDatabaseEngine.exe,因为我发现自己的电脑是默认的那个是64位的。一开始我还以为自己的Microsoft 365是32位的,因为以前自动安装的的确是32位的,但是当我安装了32位的AccessDatabaseEngine.exe以后,发现还是不行,接着我就去看自己的office,原来已经是对64位了。因为据说64位要比32位快那么一点点,我已经不记得自己到底什么时候换了。换一个office对我来说毫无难度,因为只要登录我的账号,随时都能把那个离线文件下载回来。

总算可以在Excel里运行有SQL语句的VBA。据说要在Excel里运行SQL有三个方法,第1个是用远古的MS Query,这种方案在我用Office 2003的时候尝试过。接着是在新建连接那里输入一大串的SQL语句,这种事情我好像也干过。最后也就是现在主流推荐得最多的在VBA+ADO+SQL。这是我之前完全没有接触过的。你说我完全不懂SQL吧,也不是,实际上我也是有一点点懂的,因为高中的电脑课程里面就有Access,那就是一门电脑课,因为用得不多,所以印象不深刻。相对于其它编程语言,我觉得SQL的单词算是非常简洁了。入门是简单的,但是你要把它玩得很溜,一点都不简单。

从别人的代码语句构造来说,我觉得SQL要比VBA的数组好理解一些,但是当我自己要写的时候发现哪哪都不对。主要是虽然是SQL,但是不同软件里面的限制可以称得上是五花八门。当你搜索出一种SQL写法以后,发现在Excel里面无论如何都不行,最后发现原来是Excel不支持这种玩法。比如我要做一个多条件判断,我肯定毫不犹豫会想到用switch的方法,SQL里面都有case解决方案,但问题是Excel里不行,所以如果是多条件的,你只能不断地嵌套if。因为我多条件最终实现的是一个文本替换,所以我就足足套叠了5层replace才实现了我的功能。

用过VSCode你肯定会觉得Excel VBA最让人吐槽的就是VBE的各种奇奇怪怪的限制。如果你要在VBA里面用SQL语句,你就得用双引号把那圈起来,而且在双引号里面是不允许换行的。这就很让人抓狂了,不能硬换行可以,但编辑器为什么就不能给我一个软的自动换行?视觉上给我换一下行有什么问题吗?当你不得不把那串SQL圈起来,就意味着里面所有成对的符号自动识别一律失效,全部变成了白开水字符串。本来那就是很大串的东西,还没有方法帮你自动确认格式有没有错误。如果可以换行可以缩进,你可能一眼就看出自己在哪里出毛病了,有些配对没配对上,但是因为你只能一笔写到底,所以你怎么可能不犯错呢?犯的这些错误有可能是你不知道规则原来是不允许的。比如如果你只有一层select,字段里有别名,那么group by的时候你就不能用别名,因为group by比select先执行。在某些软件的SQL里,是允许加having语句,从而改变 group by和select的先后顺序,但在Excel里面加having是没有用的。如果你非得要在group by的时候使用别名,那么你就得嵌套select。内层select先别名,外层用group by。这些神神经经的限制实在让人抓狂死了。同样是分组,如果我是在PQ里进行,重命名列这种事情放前面放后面都无所谓,变形改造列这种事情放前面放后面也无所谓。因为重命名、变形、分组这件事情全部可以分步。但是在Excel的这个SQL里面,所有东西都得一次到位。明明可以短句实现的功能不得不写一大串。实际上在Excel以外的SQL写法里,是不是也非得这样?VBA的古板是显而易见的,但是当你见识过他们把SQL阉割限制成这个模样。你就会立马觉得python的自由奔放实在太好了。

死磕得越多,我越发现自己喜欢VSCode+pyhon。

2023-08
6

VBA字典多条件单汇总

By xrspook @ 11:41:00 归类于: 烂日记

昨天说到用VBA做多条件单汇总的分组,要怎么个做法呢?

VBA这个东西其实我并不太熟悉,我没有系统学习过,或者说其实是系统过的,但只是皮毛,而且已经是多年以前了,之后用得不多,所以忘记得差不多了。所以现在我只是需要用什么的时候就去研究。在开始之前实现我的目标之前,我也先去搜索了,得出大概两个方案,一个是用字典,另外一个是用SQL。就思路来说,我感觉SQL比较直接,因为那调用的是列的信息,各种聚合会非常方便。我不知道VBA里面的SQL到底是怎么个执行法。因为2003或者以前的office,如果要做跨表查询就得用SQL。我已经不记得那些跨表查询到底只是一个工作簿里面不同表,还是直接可以跨不同的工作簿,但可以肯定的是,如果某Excel文件里运用了SQL,那么这个Excel文件一旦移动,比如发给了别人或者是U盘拷贝到另外一台电脑就会查询失败。之所以这样是因为实际上Excel在电脑的某个地方存储了与SQL有关的某些东西。虽然你把Excel文件挪走了,但是存储在你电脑里面的某个东西还在原来的地方,那是一个绝对地址。不管那个到底是绝对地址还是相对地址,简而言之就是实际上你只能挪走了一部分的东西,另外一部分还在那个地方,但即便你那么高难度,那个电脑深处的东西也挪走了。即便把它像放在另外一台电脑相对位置一样的地方,但还是识别不出来。所以最终你只能放弃那个查询,然后贴同样的代码,在另外一台电脑上重新建一个,这个操作很逆天,非常不方便。正是因为我知道这个,所以虽然在合并查询上SQL很方便,但是不到迫不得已我不会这么干。如果我真要这么干,我就不用Excel,我用了Access。以前的 Office 2003专业版都是自带Access。说起数据库的操作,在那个地方用SQL就很自然。

想到分组的时候,我当然有考虑过SQL,但是我知道以前的Excel对这个很不友好。所以别人做的某些方案,SQL版本干的事情很简单,但前面一大堆的参数引用会让你眼花缭乱。相对而言,字典是VBA的原生,所以说VBA一定能很好支持,因为VBA的字典是提升VBA性能的一个重要体现。我觉得不仅仅是在VBA,在python里,用字典和不用字典做同样的查询,效果差别也非常明显。我想要的多条件单汇总要怎么做字典呢?我首先想到的当然是把条件都合并起来作为键,然后把要汇总的那个作为键值。这就保证了前面那些条件一定是唯一的,后面的键值做累加就行了。当然,你也可以不把前面所有条件都合并成键,你只需要把一些特征条件合并就行。把多条件合并起来,做一个循环,用分隔符把它们连起来就可以了。耗费我比较长时间的是,我建立了这个字典以后,我就需要把键和键值都输出重新组合成一个数组,那就是我想要的多条件单汇总结果。但问题是怎么才能把合并起来的键重新分解输出呢?我研究了半天。理论上被我连接起来的那些条件放的键实际上是一个字符串,而且里面有规律的分隔符。我利用分隔符就能把它重新打开,然后我新建一个数组接收这些被打断的字符串。这个接收了打断字符串的数组外加字典的键值,合并起来再复制给新的数组就是我想要的东西。思路是这样的,但是当我要执行的时候,我却一直卡在那个地方。关键就我在定义这个接受打断字符串数组的时候加了括号。为什么其它数组在定义的时候就得在名称后面加括号,但是这种接收字符串的数组在定义的时候就不能加呢?这个是我搞不懂的。事实是不加我就可以实现功能,加了就会说类型错误。这种接收打断字符串的东西不应该称作数组?我已经不记得到底我是从哪里学回来的这一招接收打断字符串形成数组的这种做法。生成一个表格标题的时候,这样做非常简单高效。

先把多条件合并成一个字符串,然后再把这个字符串打断。这种方案我在网上也看到了,别人也是怎么干,但是别人之所以没有像我这样卡住,是因为把字符串打断的时候他们并不是赋值给数组,而是直接在单元格里输出了,于是他们也就没有定义数组名称时加了个括号就卡住的烦恼。

最终,我实现了自己的目标用VBA接收源数据,然后进行加工处理,做一个多条件单汇总,最终在一个固定格式的表格里输出并另存为一个新文件。整个过程你只需要在VBA所在的文件里面按一下初始化按钮,然后把需要整理的数据贴进去,然后再按一个相应的生成文件按钮。文件生成的过程可以说是秒杀,几乎不需要时间。生成的文件会自动保存在VBA文件的相同目录。这个VBA文件你可以点保存,整理后的文件内容也会在VBA文件的相应页面输出。如果你需要查询,直接可以在那里看,如果你根本不想看,这个VBA文件你甚至不需要按保存。因为如果粘贴源数据前内容是空的,下一次你再进来的时候就不需要初始化,所以步骤就更为简单,下一次你只需要做粘贴以及点一下生成。明明转换这些数据很简单,VBA可以秒杀完成,为什么PQ就得转那么好几秒呢?之所以在做这个事情上我不做python方案,是因为你还得把源数据贴到Excel里面,保存关闭,再按python生成。这就意味着。虽然你只打开关闭了Excel文件一次,但是python在运行的时候又得把Excel文件打开关闭一次,这样就得耗双倍的开关时间。如果数据量很大,VBA很慢,又或者那得进行多个文件的拼接才能取得一个很大的数据,汇总的数据超过了VBA的处理范围,那么可能我会选择python方案,但现在,顶多每次就几百条数据,难度是非常低的,高效完成是我最看重的,因此VBA是最佳选择。

用过VSCode以后,当我在Excel的VBA里编写代码,真的非常痛苦,因为当你一句话没写完,就要在其它地方复制某些东西过去的时候,就会给你弹窗报错。为什么你非得给我弹窗呢?你给我下面波浪红线不好吗?又或者当我要运行的时候,你才告诉我那个不对不行吗?我写一半就不写了,并不是因为那是我写错了,而是因为我需要做其它复制粘贴,又或者是切换个窗口做一下别的事情。如果能在VSCode面直接编写VBA,然后能直接在Excel里面调试运行,不需要把代码贴回VBA那么折腾该多好。

我可以忍受VBA的各种规则,但是我真的不喜欢Excel自带的VBE。

VBA多条件单汇总字典方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
'crr(i, 1 To 7)为数组,多行7列,前6列为条件,最后1列为数据,需要汇总
Set dic = CreateObject("scripting.dictionary") '建立字典
    For i = 1 To UBound(crr)
        x = crr(i, 1)
        For j = 2 To 6
            x = x & "|" & crr(i, j) '合并分组依据
        Next
    dic(x) = dic(x) + crr(i, 7)
    Next
Dim err, frr() As Variant
ReDim frr(1 To dic.Count, 1 To 7) '新汇总数组
i = 1
For Each x In dic.Keys
    err = Split(x, "|") '打碎分组依据
    For j = 0 To 5
        frr(i, j + 1) = err(j)
    Next
    frr(i, 7) = dic(x)
    i = i + 1
Next
2020-10
23

我实现这些

By xrspook @ 10:16:14 归类于: 烂日记

渐渐地我越发有点明白自己想要的到底是什么。之所以让我纠结了半天,不知道该如何计算的某些数据,实际上是因为两种性质的东西混搭在一起了。我不知道别人是否真的搞得清,他们是有意糊弄我们的吗?是因为他们知道我们算不清这个,所以这么搞吗?这个问题很困扰我,是因为那个东西把正常的批次跟没有写明批次但要先进先出的混搭在一起。有明确批次的数量计算是不分仓号的,因为通常那意味着好几个仓进同一批货,开始计费的日期定在第1个仓刚开始进货的那天。所以,如果同批次还有其他仓,最后一个仓进货的时候可能已经是3天后,那个仓的免堆期等于要减去三天。如果某个客户所有东西都这么弄,不会有烦恼,因为无论进货还是出货,肯定会带入批次。

在Power Query的世界里,这就意味着要以批次和日期分组。各种仓号数据可以全部加在一起,无所谓。但同时,这个客户又有另外一种情况。如果他进货不是用船,而是用车,每天几千几千吨进货的话,我们跟他们签订的合同是按每天为一个批次。一个批次到达免堆期以后开始计费。现在我们的系统总的来说,根本没分清这种批次,计费完全是人肉计算的。但实际上,这种没有批次的批次,理论上也应该自动带入,入库的时候以日期为批次。所以有可能发生同一个批次有几个仓出库的时候,要以先进先出进行批次划分。进货的时候,一个批次可能有几个仓,但是出货的时候肯定是指定的。所以在这里要以仓好为分类,然后在对入库批次分割数量。比如某一个仓里第1个日期批次用完了再开始用第2个,第2个没用完,就留到以后继续分割。据说用SQL和Power Pivot能解决这个先进先出的问题。但貌似,我看到的Power Pivot的例子没有看到我想要的最终结果,也就是把那个出库数量自动分割成入库批次。先进先出是非常经典的会计问题,只不过我们的人从来都是我觉得我要指定这个,没有用过这些规则,当然也就没有思考该怎么自己动手去计算。一开始没有批次,用先进先出的规则,入库的时候自动添加批次,出库的时候按照入库的批次风格,最终得出来的数据,跟之前一开始就已经有批次的东西可以用同样的规则继续演算。

对我来说,有两个点我没想明白。首先,要怎么在PQ里以先进先出的规则,对出库数量进行批次分割呢?第二,因为实际上我们得到的信息是一个入库日期和出库日期。而这些日期通常都是不连续的,在不连续的日子里也要计算每天库存,到达一定时间之后,要每天计算堆存费。所以,该怎么在不连续的日子里插入一些日期,然后向下填充信息呢?之前我想到的办法是先建立一个日期索引。然后把有数据的东西合并到索引里,这就意味着有些日期可能是没有数据的。接着,把这些东西透视展开,把数据为空的那一列删掉,余下的东西向下填充,然后再把零数据替换为空,接下来再用一个逆透视恢复。这个方法比较笨。但可以一次性实现N个批次的插入和填充,至于速度如何,估计跟电脑的性能有关。我总觉得高手一定不需要用到这种先透视再逆透视的方法。他们是怎么插入日期,然后向下填充数据的呢?

想清楚了这些,离我想要的最终结果就会又近了一些。

2020-10
20

我要优化提速

By xrspook @ 8:36:19 归类于: 烂日记

当我终于把功能做出来以后,我却嫌弃出结果太慢了,居然要好几分钟。明明最终我想要的是一个表的合并,为了更快,我不得不拆分为两个查询。第2个查询以第1个查询的结果为基础。其实这么操作,无非我是想利用第1个查询已经得到的缓存结果。那个结果已经被我用表格输出。之前我试过从零开始弄第2个查询,结果发现实在太慢了。如果没有那么多的分组,速度还会那么慢吗?如果只是一个求和,根本无需分组,但问题是,每个批次的东西必须分开计算,然后才可以出现分段的结果。说白了,让我纠结的是一个累计求和。

累计求和这种东西的思路在PQ里通常都意味着新增一列,参数设定匹配某行的某些东西,符合条件就把某列的数据求和。所以实际上这是一个筛选的过程。如果数据很多,筛选肯定会很慢,但除了这样,还能有什么方法吗?据说可以用索引的方法。据说索引的方法比筛选的方法快非常多。如果用python的思路去考虑,我觉得筛选是一个列表的操作,而另外一个是字典的操作。如果不用二分法。历遍列表是非常慢的,但如果要立片字典,历遍是轻而易举的事,而且字典的效率比二分法还要高。所以我应该如何建立索引呢?如果筛选的是多条件,索引大法还能继续管用吗?我觉得现在我遇到的问题那些经常接触数据库的人估计已经纠结过了。这不仅仅是Power Query的问题,这是如何运用数据进行弯曲折叠的问题。只要是数据库,无论是SQL还是其他形式,都会有这种烦恼。

昨天我终于经历了一个Excel要跑好几分钟甚至十几分钟才能出结果的东西,我感觉那没多少数据。我曾经试过把那些东西输出,结果发现输出速度非常慢,每秒钟只处理了不到100个。那些数据粗略计算了一下,可能有超过2万条。为什么加载2万条数据会这么慢呢?这是一个令我纠结的结果,如果把最后的分组都做了,输出的数据只有365条,但如果不做最后的分组,有超过2万条。不做分组的话,那个结果可以在软件里直接展示出来,顶多只需要几秒的运算时间,但是不做分组,把数据输出却有超过2万条,即便我不输出表格只输出数据透视表,依然在输出的时候速度非常慢。为什么对2万条数据进行分组会这么慢呢?除了分组,还有其他快速的方式可以对某条件进行求和吗?整个操作之所以这么慢,除了因为分组,还有排序,还有一些,null转化为0,或者把0转化为null的操作,最后,还有一条我自己都觉得应该会很作死的向下填充。那个结果我花了好几分钟才计算出来,如果让高手去解答,估计运行时间会会是毫秒级的,顶多不会超过三秒钟。

一方面,我很想知道如何提升运行速度,直接拿去问人显然是最显而易见的办法,但在这之前,我想自己先思考一下,毕竟走到这一步已经很不容易,我不想在最后一步认输。这让我想起了高中数学老师的某句经典语录,学习数学几个境界里的最后一句——全而不好(前几句是“不懂不会,会而不对,对而不全”)。

© 2004 - 2026 我的天 | Theme by xrspook | Power by WordPress