3.12的函数学习班报名结束,感谢大家的支持,下次函数班3个月后。


作者:无言的人


考勤统计提示,这个难度很大,卢子先分享源文件出来。


Excel在很多时候不仅用于统计财务、销售方面,在人力资源方面更多的用于统计职员的考勤时间,今天就分享考勤统计的一小部统计。


下面为某公司的职员打卡时间,该时间由考勤系统导出。该系统虽然能导出明细,但是未能对职员的考勤信息进行必要的统计——迟到、早退、未刷卡。


该公司的考勤制度要求为每天只需要打卡2次,其打卡时间分为两种(夏制和冬制)分列为:

夏制:8:30-17:30

冬制:9:00-18:00


接着根据以上的两个时间段进行必要选择,并通过对应的打卡具体事件来进行信息统计输入,操作后的效果。


源数据有600+行,如果通过函数进行判断会比较麻烦,而且如果数据更多时,函数的大量计算将会造成卡顿(假死)的状态,这样将会影响效率。本文将采用VBA(宏)过程来完成本次考勤信息的提示,其代码如下:

Sub KaoqinTime()

   Dim TimeArr,CouF As Integer, TimeL

   Dim MaxR AsInteger, Kaoq() As String, MaxC As Byte, TimeP As Boolean

   Dim TimeStr01As String, TimeStr02 As String

   MaxR =Cells(Rows.Count, 1).End(xlUp).Row

   Cells(1,7).Resize(MaxR) = ""

   MaxC = Cells(1,Columns.Count).End(xlToLeft).Column

   TimeStr01 ="输入 0 为 8:30-17:30"

   TimeStr02 ="输入非 0 数字为 9:00-18:00"

   TimeArr =Cells(2, 1).Resize(MaxR - 1, 6).Value

   ReDim Kaoq(1 ToMaxR - 1, 1 To 1)

   TimeP =Application.InputBox("考勤时间区段选择" & vbCr & TimeStr01 & _

        vbCr& TimeStr02, "上班时间", , , , , , 1)

   For CouF = 1 ToMaxR - 1

     TimeL =Split(TimeArr(CouF, 6), ";")

     Rem 判断有几个打卡时间,一个就判断哪个时间段没有刷卡

     If UBound(TimeL)= 0 Then

       SelectCase TimeValue(CDate(TimeL(0)))

       Case Is< 0.5

           IfTimeP = False And TimeValue(CDate(TimeL(0))) >= TimeValue("08:36")And _

             TimeValue(CDate(TimeL(0))) < TimeValue("09:00") Then

             Kaoq(CouF, 1) = "下班未刷卡并迟到"

          Else

             Kaoq(CouF, 1) = "下班未刷卡"

           EndIf

       Case Is> 0.5

           IfTimeP = True And TimeValue(CDate(TimeL(0))) >= TimeValue("09:06")And _

             TimeValue(CDate(TimeL(0))) <TimeValue("18:00") Then

             Kaoq(CouF, 1) = "上班未刷卡并早退"

          Else

             Kaoq(CouF, 1) = "上班未刷卡"

           EndIf

       EndSelect

     Else

     Rem 判断迟到早退,先判断上班时间段8:35 或者 9:05 迟到、17:30前或者 18前早退

        SelectCase TimeP

          Case False       '8:30-17:30

             If TimeValue(CDate(TimeL(0))) >= TimeValue("08:36") And _

               TimeValue(TimeL(UBound(TimeL))) < TimeValue("17:30") Then

               Kaoq(CouF, 1) = "迟到并早退"

             ElseIf TimeValue(CDate(TimeL(0))) <= TimeValue("08:35") And_

               TimeValue(TimeL(UBound(TimeL))) < TimeValue("17:30") Then

                Kaoq(CouF, 1) = "早退"

             ElseIf TimeValue(CDate(TimeL(0))) >= TimeValue("08:36") And_

               TimeValue(TimeL(UBound(TimeL))) >= TimeValue("17:30") Then

               Kaoq(CouF, 1) = "迟到"

             End If

          Case True       ' 9:00-18:00

             If TimeValue(CDate(TimeL(0))) >= TimeValue("09:05") And _

               TimeValue(TimeL(UBound(TimeL))) < TimeValue("18:00") Then

               Kaoq(CouF, 1) = "迟到并早退"

             ElseIf TimeValue(CDate(TimeL(0))) <= TimeValue("09:05") And_

               TimeValue(TimeL(UBound(TimeL))) < TimeValue("18:00") Then

               Kaoq(CouF, 1) = "早退"

             ElseIf TimeValue(CDate(TimeL(0))) >= TimeValue("09:06") And_

               TimeValue(TimeL(UBound(TimeL))) >= TimeValue("18:00") Then

               Kaoq(CouF, 1) = "迟到"

             End If

        EndSelect

     End If

   Next CouF

   With Cells(1,MaxC + 1)

     .Resize(MaxR) = ""

     .Value ="备注"

     .Offset(1,0).Resize(MaxR - 1, 1) = Kaoq

   End With

End Sub

现在对该考勤信息统计KaoqinTime过程中的几个要点进行解释。


Step 01 过程一开始定义了几个名称变量,变量的作用在于规定制定数据的类型,以及通过名称方便调用储存在其内的数据内容。


Step 02 给各变量赋值需要的数据值。


变量的赋值都是通过在变量名称后用【=】给定需要的数据,其中:

MaxR变量的赋值结果为获取激活工作表A列已用的最大行号;


赋值MaxR变量后通过Cells(1, 7).Resize(MaxR) = ""语句将第7列整列有效范围的单元格内容赋值为空白;


MaxC变量主要获取已使用区域的最末列的列号(从工作表的最右侧想左侧获取);


TimeStr01和TimeStr02两个变量的作用的作为夏制和冬制打卡时间的提示文本;


TimeArr变量为一个变体变量,其通过赋值获取考勤表从第2行开始的连续单元格区域存入其中,最后该变量成为一个平时说的数组(二维数组)变量;


ReDim Kaoq(1 To MaxR - 1, 1 To 1)语句的作用是重新声明定义上面已有的Kaoq数组变量的具体维数信息,通过结合MaxR变量的有效行数,将该数组声明为一个多行1列的二维数组;


TimeP变量为一个布尔值变量(即Excel中常说的True 或 False类型),通过结合Application.InputBox方法让用户选择是按照夏制或者冬制进行考勤信息统计。

Step 03 通过循环For的指数循环,进行考勤具体单元格内容的时间数据进行判断,该循环中结束的判断点为MaxR-1。接下来重点解释循环中对于时间点界定和信息对应。


TimeL = Split(TimeArr(CouF, 6),";")语句为通过Split函数将单元格中的时间信息通过【;】(英文分号)将原本多个隔离的时间拆分为各自独立的数据存入TimeL这个一维变量,此时TimeL为一个数组;若为空或者存在一个时,该变量就只能存入该单元格的唯一值或0。


对TimeL赋值后通过If…Else…End选择语句判断该变量是否存在多个数据——UBound函数判断指定数组变量指定维数的数量,结合If语句若UBound(TimeL)返回的值0则说明单元格内容只存在一个打卡时间或者无刷卡两种情况,属于这种情况时,使用If选择语句中第1层中间语句执行对考勤时间的判断写入。


当选择采用第1层中间语句进行时间判断时,该层采用Select Case …End Select选择语句进行根据TimeValue(CDate(TimeL(0)))语句中获取TimeL数组中的第1个数据的时间判断——

当TimeL(0)的时间小于0.5(12点)时,选择Case Is < 0.5语句下的If…Else…End选择语句进行具体判断。

其中If语句中关系为And,即所有条件必须满足才能判断该打卡时间为夏制的且为<下班未刷卡并迟到>;


TimeP = False语句为判断为夏制或冬制时间;


TimeValue(CDate(TimeL(0))) >=TimeValue("08:36")语句为判断具体的打卡时间是否超过夏制上班规定打卡时间——判断为迟到;


TimeValue(CDate(TimeL(0))) < TimeValue("09:00")语句则是判断这个唯一的事件是否小于9点,若小于9点则判断没有刷下班卡。


以上三个语句用And运算符号链接起来,则总的判断该时间为<下班未刷卡并迟到>,若以上判断不成立则判断<下班未刷卡>


当TimeL(0)的时间大于0.5(12点)时,选择Case Is > 0.5语句下的If…Else…End选择语句进行具体判断,该语句与小于0.5(12点)时的作用差不多——同样通过判断夏冬制时间并判断打卡时间是否超过9:06分,且该时间是否小于规定的冬制下班时间,则判断为<上班未刷卡并早退>,若都不是则判断<上班未刷卡(下班)>。


当通过If UBound(TimeL) =0 语句判断该单元格中存在的多个打卡时间后,If选择语句选择Else语句内的中间语句执行时间的判断。


Else语句中直接判断TimeP的夏冬制时间,通过该时间制并运用Select Case …End Select选择语句进行对于时间段的判断语句。


当TimeP为False时则采用其中间的If…ElsdiF..End If语句进行多层次语句判断,其分解过程如下(夏制时间)_Case False:


If判断语句为判断第1个打卡时间是否大于等于8:36,且最后一个打卡时间小于17:30时则判断<迟到并早退>;


若判断不成立则进入ElseIf的判断——同样判断第1个打开时间是否小于夏制上班时间且最后一个打开时间小于夏制下班时间,则判断为<早退>;


若判断不成立则进入ElseIf的判断——同样判断第1个打开时间是否大于夏制上班时间且最后一个打开时间大于夏制下班时间,则判断为<迟到>;


当以上所有判断均不满足时,则当行Kaoq数组将不写入任何数据,则说明该职员当天的打卡时间是正常无瑕疵的。


当用户选择的冬制时间是则TimeP为True,Select Case语句将运行其中的Case True语句下的中间代码,它们作用和上面的Case False语句中If…ElsdiF..End If语句的作用的相同,只是判断的时间点变化了。以上的所有判断结果都写入Kaoq数组中。


当循环结束后通过语句将数据写入到指定位置:


其中Cells(1, MaxC + 1)指激活工作表的第1行的使用区域的最后一列偏移1列后的单元格,并通过.Resize(MaxR) = ""语句将该区中原有数据赋值为空白(即类似于清空);接着在Cells(1, MaxC + 1)单元格通过.Value = "备注"语句写入标题名称,最后.Offset(1, 0).Resize(MaxR - 1, 1) = Kaoq语句将Kaoq数组的数据内容写入到的标题后的的单元格中,最后的效果。


实际上考勤的统计中该示例只占了冰山一角,该示例主要是想让大家认识——在使用学习VBA中对于对象及其属性、方法、事件等运用是必要理解过程,而且在编程中对于条件选择语句的运用If…Eles…Endi、Select Case …End


Select以及循环语句运用的经常遇到的,所以对于这类常用语句的学习和运用也必须。


KaoqinTime过程的运行时间可以说是稍纵即逝——几百条考勤信息在一眨眼间的统计提示信息已经列出写入在单元格中,这个效果大量Excel函数公式所不能比拟的,但是这个还是需要结合编程中常用的一个数据类型——数组。数组充分的运用到了系统内存的优势,将数据写入内存数组中,并在最后一次性写入到单元格,比直接运用单元格对象一次次的写入更快。


该示例同时可通过再对Kaoq数组的二维的列数扩展,从而统计出迟到及或早退的具体时间值,方便后面的统计。

作者:卢子,清华畅销书作者;个人公众号:Excel不加班(ID:Excelbujiaban)