标签: excel

  • 使用ExcelVBA审计钉钉原始打卡记录(代码优化)

    Sub FindMultiUserDevicesWithCount()
        ' 声明变量
        Dim wsSource As Worksheet, wsResult As Worksheet
        Dim dict As Object ' 用于存储设备号和对应的员工集合
        Dim lastRow As Long, i As Long
        Dim deviceID As String, employeeName As String
        Dim key As Variant, empKey As Variant
        Dim outputRow As Long
        Dim nameList As String
    
        ' 设置源数据工作表 (假设数据在原始记录)
        On Error Resume Next
        Set wsSource = ThisWorkbook.Worksheets("原始记录")
        On Error GoTo 0
    
        If wsSource Is Nothing Then
            MsgBox "找不到工作表 '原始记录',请修改代码中的工作表名称。", vbExclamation
            Exit Sub
        End If
    
        ' 创建或清空结果工作表
        Application.DisplayAlerts = False
        On Error Resume Next
        ThisWorkbook.Worksheets("异常设备报告").Delete
        On Error GoTo 0
        Application.DisplayAlerts = True
    
        Set wsResult = ThisWorkbook.Worksheets.Add(After:=ThisWorkbook.Worksheets(ThisWorkbook.Worksheets.Count))
        wsResult.Name = "异常设备报告"
    
        ' 在结果表创建标题
        With wsResult
            .Range("A1").Value = "设备编号"
            .Range("B1").Value = "使用员工数量"
            .Range("C1").Value = "使用员工名单及打卡次数"
            .Range("D1").Value = "设备持有人"
            .Range("E1").Value = "代打卡员工"
            .Range("A1:E1").Font.Bold = True
            .Columns("A:E").AutoFit
        End With
    
        outputRow = 2 ' 从第2行开始输出结果
    
        ' 创建字典对象来存储数据
        Set dict = CreateObject("Scripting.Dictionary")
    
        ' 找出源数据的最后一行 (使用P列确定行数)
        lastRow = wsSource.Cells(wsSource.Rows.Count, "P").End(xlUp).Row
    
        ' 检查是否有数据
        If lastRow < 2 Then
            wsResult.Cells(2, 1).Value = "源数据表中没有找到有效数据。"
            wsResult.Columns("A:E").AutoFit
            wsResult.Activate
            Exit Sub
        End If
    
        ' 遍历所有数据行 (假设第1行是标题,从第2行开始)
        For i = 2 To lastRow
            On Error Resume Next ' 防止类型转换错误
            deviceID = Trim(CStr(wsSource.Cells(i, "P").Value)) ' P列是设备号
            employeeName = Trim(CStr(wsSource.Cells(i, "A").Value)) ' A列是员工姓名
            On Error GoTo 0
    
            ' 跳过空设备号或空姓名的行
            If deviceID = "" Or employeeName = "" Then GoTo NextRow
    
            ' 如果字典中没有这个设备号,则添加一个新字典
            If Not dict.Exists(deviceID) Then
                dict.Add deviceID, CreateObject("Scripting.Dictionary")
            End If
    
            ' 如果该设备号的字典中没有这个员工,则添加并初始化计数为1
            If Not dict(deviceID).Exists(employeeName) Then
                dict(deviceID).Add employeeName, 1
            Else
                ' 如果已存在,则计数加1
                dict(deviceID)(employeeName) = dict(deviceID)(employeeName) + 1
            End If
    
    NextRow:
        Next i
    
        ' 遍历字典,找出使用员工数 > 1 的设备
        For Each key In dict.Keys
            If dict(key).Count > 1 Then
                ' 输出到结果表
                wsResult.Cells(outputRow, 1).Value = key ' 设备号
                wsResult.Cells(outputRow, 2).Value = dict(key).Count ' 员工数量
    
                ' 将员工姓名和打卡次数集合连接成一个字符串
                nameList = ""
                For Each empKey In dict(key).Keys
                    nameList = nameList & empKey & "(" & dict(key)(empKey) & "次), "
                Next empKey
    
                ' 去掉最后一个逗号和空格
                If Len(nameList) > 0 Then
                    nameList = Left(nameList, Len(nameList) - 2)
                End If
    
                wsResult.Cells(outputRow, 3).Value = nameList ' 员工名单及打卡次数
    
                ' 新增功能:识别设备持有人和代打卡员工
                Dim maxCount As Long
                Dim holder As String
                Dim proxyEmployees As String
    
                ' 找出打卡次数最多的员工
                maxCount = 0
                holder = ""
                For Each empKey In dict(key).Keys
                    If dict(key)(empKey) > maxCount Then
                        maxCount = dict(key)(empKey)
                        holder = empKey
                    End If
                Next
    
                ' 构建代打卡员工名单(包含打卡次数)
                proxyEmployees = ""
                For Each empKey In dict(key).Keys
                    If empKey <> holder Then
                        proxyEmployees = proxyEmployees & empKey & "(" & dict(key)(empKey) & "次), "
                    End If
                Next
    
                ' 去掉最后一个逗号和空格
                If Len(proxyEmployees) > 0 Then
                    proxyEmployees = Left(proxyEmployees, Len(proxyEmployees) - 2)
                End If
    
                ' 写入持有人和代打卡员工信息
                wsResult.Cells(outputRow, 4).Value = holder & "(" & maxCount & "次)"
                wsResult.Cells(outputRow, 5).Value = proxyEmployees
    
                outputRow = outputRow + 1
            End If
        Next key
    
        ' 如果没有找到异常设备,提示用户
        If outputRow = 2 Then
            wsResult.Cells(2, 1).Value = "未发现一个设备对应多个员工的情况。"
        Else
            ' 对结果表进行排序(按员工数量降序)
            With wsResult
                If outputRow > 2 Then
                    .Range("A1:E" & outputRow - 1).Sort Key1:=.Range("B2"), Order1:=xlDescending, Header:=xlYes
                End If
            End With
        End If
    
        ' 自动调整列宽
        wsResult.Columns("A:E").AutoFit
        wsResult.Activate ' 切换到结果工作表
    
        MsgBox "分析完成!共找到 " & (outputRow - 2) & " 个异常设备。结果已输出到工作表【异常设备报告】。", vbInformation
    End Sub

    主要功能:

    • 数据源处理:从名为“原始记录”的工作表中读取数据,其中设备编号位于P列,员工姓名位于A列。
    • 数据统计:使用字典对象高效地统计每个设备编号对应的员工及其打卡次数。
    • 报告生成:创建一个名为“异常设备报告”的新工作表,列出所有异常设备(即被多个员工使用的设备),包括以下信息:
    • 设备编号:设备的唯一标识。
    • 使用员工数量:使用该设备的员工总数。
    • 使用员工名单及打卡次数:列出所有使用该设备的员工姓名及其打卡次数(例如:张三(5次), 李四(3次))。
    • 设备持有人:打卡次数最多的员工(被视为主要使用者)。
    • 代打卡员工:其他使用该设备的员工(可能涉及代打卡行为)。
    • 排序和格式化:报告按员工数量降序排序,并自动调整列宽以便阅读。
    • 用户反馈:运行完成后,显示消息框提示找到的异常设备数量。

    适用场景:

    • 企业考勤系统分析,检测异常打卡行为。
    • 设备使用审计,确保设备分配合规。
    • 人力资源或安全管理,防止代打卡或未授权设备使用。

    如何使用:

    1. 确保Excel工作簿中有一个名为“原始记录”的工作表,且包含员工姓名(A列)和设备编号(P列)数据。
    2. 在Excel中运行此宏(通过VBA编辑器或按钮触发)。
    3. 宏将自动生成“异常设备报告”工作表,并提供分析结果。
  • Excel VBA 审计钉钉原始打卡记录(优化版代码)

    Sub FindMultiUserDevicesWithCount()
        ' 声明变量
        Dim wsSource As Worksheet, wsResult As Worksheet
        Dim dict As Object ' 用于存储设备号和对应的员工集合
        Dim lastRow As Long, i As Long
        Dim deviceID As String, employeeName As String
        Dim key As Variant, empKey As Variant
        Dim outputRow As Long
        Dim nameList As String
    
        ' 设置源数据工作表 (假设数据在原始记录)
        On Error Resume Next
        Set wsSource = ThisWorkbook.Worksheets("原始记录")
        On Error GoTo 0
    
        If wsSource Is Nothing Then
            MsgBox "找不到工作表 '原始记录',请修改代码中的工作表名称。", vbExclamation
            Exit Sub
        End If
    
        ' 创建或清空结果工作表
        Application.DisplayAlerts = False
        On Error Resume Next
        ThisWorkbook.Worksheets("异常设备报告").Delete
        On Error GoTo 0
        Application.DisplayAlerts = True
    
        Set wsResult = ThisWorkbook.Worksheets.Add(After:=ThisWorkbook.Worksheets(ThisWorkbook.Worksheets.Count))
        wsResult.Name = "异常设备报告"
    
        ' 在结果表创建标题
        With wsResult
            .Range("A1").Value = "设备编号"
            .Range("B1").Value = "使用员工数量"
            .Range("C1").Value = "使用员工名单及打卡次数"
            .Range("A1:C1").Font.Bold = True
            .Columns("A:C").AutoFit
        End With
    
        outputRow = 2 ' 从第2行开始输出结果
    
        ' 创建字典对象来存储数据
        Set dict = CreateObject("Scripting.Dictionary")
    
        ' 找出源数据的最后一行 (使用P列确定行数)
        lastRow = wsSource.Cells(wsSource.Rows.Count, "P").End(xlUp).Row
    
        ' 检查是否有数据
        If lastRow < 2 Then
            wsResult.Cells(2, 1).Value = "源数据表中没有找到有效数据。"
            wsResult.Columns("A:C").AutoFit
            wsResult.Activate
            Exit Sub
        End If
    
        ' 遍历所有数据行 (假设第1行是标题,从第2行开始)
        For i = 2 To lastRow
            On Error Resume Next ' 防止类型转换错误
            deviceID = Trim(CStr(wsSource.Cells(i, "P").Value)) ' P列是设备号
            employeeName = Trim(CStr(wsSource.Cells(i, "A").Value)) ' A列是员工姓名
            On Error GoTo 0
    
            ' 跳过空设备号或空姓名的行
            If deviceID = "" Or employeeName = "" Then GoTo NextRow
    
            ' 如果字典中没有这个设备号,则添加一个新字典
            If Not dict.Exists(deviceID) Then
                dict.Add deviceID, CreateObject("Scripting.Dictionary")
            End If
    
            ' 如果该设备号的字典中没有这个员工,则添加并初始化计数为1
            If Not dict(deviceID).Exists(employeeName) Then
                dict(deviceID).Add employeeName, 1
            Else
                ' 如果已存在,则计数加1
                dict(deviceID)(employeeName) = dict(deviceID)(employeeName) + 1
            End If
    
    NextRow:
        Next i
    
        ' 遍历字典,找出使用员工数 > 1 的设备
        For Each key In dict.Keys
            If dict(key).Count > 1 Then
                ' 输出到结果表
                wsResult.Cells(outputRow, 1).Value = key ' 设备号
                wsResult.Cells(outputRow, 2).Value = dict(key).Count ' 员工数量
    
                ' 将员工姓名和打卡次数集合连接成一个字符串
                nameList = ""
                For Each empKey In dict(key).Keys
                    nameList = nameList & empKey & "(" & dict(key)(empKey) & "次), "
                Next empKey
    
                ' 去掉最后一个逗号和空格
                If Len(nameList) > 0 Then
                    nameList = Left(nameList, Len(nameList) - 2)
                End If
    
                wsResult.Cells(outputRow, 3).Value = nameList ' 员工名单及打卡次数
                outputRow = outputRow + 1
            End If
        Next key
    
        ' 如果没有找到异常设备,提示用户
        If outputRow = 2 Then
            wsResult.Cells(2, 1).Value = "未发现一个设备对应多个员工的情况。"
        Else
            ' 对结果表进行排序(按员工数量降序)
            With wsResult
                If outputRow > 2 Then
                    .Range("A1:C" & outputRow - 1).Sort Key1:=.Range("B2"), Order1:=xlDescending, Header:=xlYes
                End If
            End With
        End If
    
        ' 自动调整列宽
        wsResult.Columns("A:C").AutoFit
        wsResult.Activate ' 切换到结果工作表
    
        MsgBox "分析完成!共找到 " & (outputRow - 2) & " 个异常设备。结果已输出到工作表【异常设备报告】。", vbInformation
    End Sub

  • 使用Excel VBA高效审计钉钉打卡记录,快速识别代打卡行为

    近期,我们在某项目的考勤核查中发现,存在员工通过他人手机登录钉钉进行虚假定位打卡的违规情况。经深入调查,该问题已得到证实。

    在处理相关违纪员工的同时,我们意识到集团拥有7000余名在职员工,此类作弊行为存在的风险较高。因此,决定对全部员工的原始打卡记录展开全面审计。

    image-20250828163228328

    每月7000名员工产生的打卡记录接近20万条,数据量庞大,人工核对几乎不可行。通过分析代打卡行为的业务逻辑,我们发现:如果单一设备编码(手机硬件标识)对应多个不同的员工账号,即可判定存在代打卡嫌疑。

    基于这一规律,开发了以下Excel VBA解决方案,实现快速、自动化的审计流程:

    Sub FindMultiUserDevices()
        ' 声明变量
        Dim wsSource As Worksheet, wsResult As Worksheet
        Dim dict As Object ' 用于存储设备号和对应的员工集合
        Dim lastRow As Long, i As Long
        Dim deviceID As String, employeeName As String
        Dim key As Variant, empKey As Variant
        Dim outputRow As Long
        Dim nameList As String
    
        ' 设置源数据工作表 (假设数据在原始记录)
        On Error Resume Next
        Set wsSource = ThisWorkbook.Worksheets("原始记录")
        On Error GoTo 0
    
        If wsSource Is Nothing Then
            MsgBox "找不到工作表 '原始记录',请修改代码中的工作表名称。", vbExclamation
            Exit Sub
        End If
    
        ' 创建或清空结果工作表
        Application.DisplayAlerts = False
        On Error Resume Next
        ThisWorkbook.Worksheets("异常设备报告").Delete
        On Error GoTo 0
        Application.DisplayAlerts = True
    
        Set wsResult = ThisWorkbook.Worksheets.Add(After:=ThisWorkbook.Worksheets(ThisWorkbook.Worksheets.Count))
        wsResult.Name = "异常设备报告"
    
        ' 在结果表创建标题
        With wsResult
            .Range("A1").Value = "设备编号"
            .Range("B1").Value = "使用员工数量"
            .Range("C1").Value = "使用员工名单"
            .Range("A1:C1").Font.Bold = True
            .Columns("A:C").AutoFit
        End With
    
        outputRow = 2 ' 从第2行开始输出结果
    
        ' 创建字典对象来存储数据
        Set dict = CreateObject("Scripting.Dictionary")
    
        ' 找出源数据的最后一行 (使用P列确定行数)
        lastRow = wsSource.Cells(wsSource.Rows.Count, "P").End(xlUp).Row
    
        ' 检查是否有数据
        If lastRow < 2 Then
            wsResult.Cells(2, 1).Value = "源数据表中没有找到有效数据。"
            wsResult.Columns("A:C").AutoFit
            wsResult.Activate
            Exit Sub
        End If
    
        ' 遍历所有数据行 (假设第1行是标题,从第2行开始)
        For i = 2 To lastRow
            On Error Resume Next ' 防止类型转换错误
            deviceID = Trim(CStr(wsSource.Cells(i, "P").Value)) ' P列是设备号
            employeeName = Trim(CStr(wsSource.Cells(i, "A").Value)) ' A列是员工姓名
            On Error GoTo 0
    
            ' 跳过空设备号或空姓名的行
            If deviceID = "" Or employeeName = "" Then GoTo NextRow
    
            ' 如果字典中没有这个设备号,则添加一个新集合
            If Not dict.Exists(deviceID) Then
                dict.Add deviceID, CreateObject("Scripting.Dictionary")
            End If
    
            ' 将员工姓名添加到该设备号对应的集合中
            If Not dict(deviceID).Exists(employeeName) Then
                dict(deviceID).Add employeeName, Nothing
            End If
    
    NextRow:
        Next i
    
        ' 遍历字典,找出使用员工数 > 1 的设备
        For Each key In dict.Keys
            If dict(key).Count > 1 Then
                ' 输出到结果表
                wsResult.Cells(outputRow, 1).Value = key ' 设备号
                wsResult.Cells(outputRow, 2).Value = dict(key).Count ' 员工数量
    
                ' 将员工姓名集合连接成一个字符串,用逗号隔开
                nameList = ""
                For Each empKey In dict(key).Keys
                    nameList = nameList & empKey & ", "
                Next empKey
    
                ' 去掉最后一个逗号和空格
                If Len(nameList) > 0 Then
                    nameList = Left(nameList, Len(nameList) - 2)
                End If
    
                wsResult.Cells(outputRow, 3).Value = nameList ' 员工名单
                outputRow = outputRow + 1
            End If
        Next key
    
        ' 如果没有找到异常设备,提示用户
        If outputRow = 2 Then
            wsResult.Cells(2, 1).Value = "未发现一个设备对应多个员工的情况。"
        Else
            ' 对结果表进行排序(按员工数量降序)
            With wsResult
                If outputRow > 2 Then
                    .Range("A1:C" & outputRow - 1).Sort Key1:=.Range("B2"), Order1:=xlDescending, Header:=xlYes
                End If
            End With
        End If
    
        ' 自动调整列宽
        wsResult.Columns("A:C").AutoFit
        wsResult.Activate ' 切换到结果工作表
    
        MsgBox "分析完成!共找到 " & (outputRow - 2) & " 个异常设备。结果已输出到工作表【异常设备报告】。", vbInformation
    End Sub

    实际运行结果如下图:

    image-20250828163810601
  • Excel规划求解演示

    Excel规划求解演示

    Excel规划求解演示

    这个演示,是在新项目进场前的筹备期,HR需要对人工成本进行测算,根据员工实发反推工资标准的示例。

  • Excel基础知识与简单函数

    Excel基础知识与简单函数

    Excel基础知识与简单函数

    PPT课件

    Document Preview

    学员版课件

    Document Preview
    CC-BY-NC-SA

    Excel基础知识与简单函数 is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International license.

  • Excel中通过vlookup与indirect嵌套对数据进行查询汇总

    Excel中通过vlookup与indirect嵌套对数据进行查询汇总

    Excel中通过vlookup与indirect嵌套对数据进行查询汇总

    需求

    image-20241009143042903

    某项目交付后,物管处需对验收问题进行跟进并汇总数据。

    各sheet是以楼栋号为单位的问题表,一共有20余个sheet,需要将所有问题汇总到一个单独的汇总sheet中,同时确保各sheet的数据发生变化时汇总表可以同步提现。

    解决方案

    VLOOKUP($D3,INDIRECT("'"&$C3&"'!D:J"),MATCH(E$2,$D$2:$J$2,0),0)

    1. $D3:这是 VLOOKUP 函数中的查找值,即您想要在目标工作表中查找的数据。
    2. INDIRECT("'" & $C3 & "'!D:J")
      • $C3:这个单元格包含目标工作表的名称。
      • INDIRECT 函数根据文本字符串构建对单元格的引用。这里,它构建了对 $C3 单元格中指定的工作表内的区域 D:J 的引用。
      • 例如,如果 $C3 包含文本 “Sheet1″,则 INDIRECT 函数将返回对 “Sheet1” 工作表中 D:J 区域的引用。
    3. MATCH(E$2, $D$2:$J$2, 0)
      • E$2:这个单元格包含您想要查找的列标题。
      • $D$2:$J$2:这是包含列标题的区域,MATCH 函数在这个区域中查找与 E$2 相匹配的列标题。
      • 0:表示精确匹配。MATCH 函数返回匹配项在 $D$2:$J$2 中的相对位置(列号)。
    4. 0:这是 VLOOKUP 函数的第四个参数,表示查找时需要精确匹配。

    公式的作用:

    • 查找值$D3 是您想要查找的值。
    • 查找范围INDIRECT 函数根据 $C3 单元格中的工作表名称和指定的区域 "D:J" 来确定查找范围。
    • 列索引号MATCH 函数返回 E$2 单元格中指定的标题在 $D$2:$J$2 标题行中的列索引号。
    • 精确匹配VLOOKUP 的最后一个参数设置为 0,表示查找时需要精确匹配。
  • VBA处理excel工作表中的注释信息

    VBA处理excel工作表中的注释信息

    以下代码可实现:

    将当前工作表中的所有注释内容复制到新工作表的单元格中,复制后的内容在同一列由上向下排列

    Attribute VB_Name = "当前工作表内所有注释复制到新工作表"
    Sub CopyCellCommentsToNewSheet()
        Dim wsSource As Worksheet
        Dim wsTarget As Worksheet
        Dim i As Long
        Dim r As Range
        Dim lastRow As Long
    
        ' 设置源工作表为当前活动工作表
        Set wsSource = ActiveSheet
    
        ' 创建新工作表并设置为目标工作表
        Set wsTarget = Sheets.Add(After:=Sheets(Sheets.Count))
        wsTarget.Name = "Comments"
    
        ' 遍历源工作表中的所有单元格
        i = 1
        For Each r In wsSource.UsedRange
            If Not r.comment Is Nothing Then
                ' 如果单元格有注释,将其内容复制到新工作表的A列
                wsTarget.Cells(i, 1).Value = r.comment.Text
                ' 更新i以指向新行
                i = i + 1
            End If
        Next r
    
        ' 计算新工作表中最后一个非空单元格的行号
        lastRow = wsTarget.Cells(wsTarget.Rows.Count, 1).End(xlUp).Row
    
        ' 可选:自动调整列宽以适应内容
        wsTarget.Columns("A").AutoFit
    
        ' 可选:清除新工作表中的空白单元格
        If lastRow < wsTarget.Rows.Count Then
            wsTarget.Range(wsTarget.Cells(lastRow + 1, 1), wsTarget.Cells(wsTarget.Rows.Count, 1)).ClearContents
        End If
    
        ' 可选:提示用户完成操作
        MsgBox "所有注释已成功复制到新工作表。", vbInformation, "完成"
    End Sub

    线程式批注和注释之间的区别 – Microsoft 支持

  • 通过excel计算身份证号校验位

    通过excel计算身份证号校验位

    通过excel计算身份证号校验位

    自从2004年1月1日《中华人民共和国居民身份证法》施行开始,居民身份证号码由15位升为18位,其中年份由2位扩展位4位,同时增加最后一位作为校验位。

    在人事管理的实际操作过程中,曾经遇到过持假身份证入职的情况,当时我正在企业里牵头EHR系统实施,员工电子档案还只是通过Excel管理,所以只能通过Excel来校验身份证号码的真伪。

    • 校验位是根据〖中华人民共和国国家标准 GB 11643-1999〗中有关公民身份号码的规定,前面十七位数字码,按照ISO 7064:1983.MOD 11-2校验码计算出来的检验码。

    计算过程如下:

    1. 身份证第 1-17 位分别乘以对应的权重因子
    权重
    1. 将乘积之和取模 11(即取除以11后的余数),根据下表得到对应的第 18 位校验码
    校验码

    其中的X,实际上是罗马数字中的10,即Ⅹ

    image-20220929120613175

    在不同的字体里,两者可能会存在非常相似的情况,但在Excel中,用code函数可以很清楚的看出两者之间的差别。

    在实际工作中,为了输入方便,往往也是直接输入大写英文字母“X”。

    通过Excel数组公式求身份证号校验位:

    =MID("10X98765432",MOD(SUM(MID(A1,ROW(INDIRECT("1:17")),1)*2^(18-ROW(INDIRECT("1:17")))),11)+1,1)

    Excel中的数组公式需要使用SHIFT+CTRL+ENTER键入

    通过VBA自定义函数处理身份证号校验位:

    Function ID(num)
        Dim X(17)  '储存身份证号码分割后的每位字符
        Dim Y()  '储存计算相乘的系数
        Dim LastNum()  '储存身份证最后一位验证码    
        '储存身份证前17位
        For i = 0 To 16
            X(i) = Mid(num, i + 1, 1)
        Next   
        '如果第18位是字母X或x,将数字10储存到X(17)中
        '如果第18位是数字,直接储存到X(17)中
        '如果是其他情况,什么都不做
        If Mid(num, 18, 1) = "X" Or Mid(num, 18, 1) = "x" Then
            X(17) = 10
        ElseIf IsNumeric(Mid(num, 18, 1)) Then
            X(17) = Mid(num, 18, 1)
        End If    
        'Y中最后一位11是取模的除数,其余是相乘系数
        Y = Array(7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2, 11)
        LastNum = Array(1, 0, 10, 9, 8, 7, 6, 5, 4, 3, 2)    
        '判断位数,18位且前17位都是数字才可能正确,再计算校验码判断
        If Len(num) = 18 And Application.WorksheetFunction.IsNumber(Mid(num, 1, 17)) Then
            Sum = 0
            For i = 0 To 16
                Sum = Sum + X(i) * Y(i)
            Next
            Code = Sum Mod Y(17)
            If LastNum(Code) - X(17) = 0 Then
                ID = "正确"
            Else
                ID = "请检查身份证号码!"
            End If    
        '非18位情况身份证号码错误
        Else
            ID = "请检查身份证号码!"
        End If        
    End Function

    以下是经优化后的VBA代码:

    Function ID(num)
    Dim X(0 To 16) As String '储存身份证号码分割后的每位字符
    Dim Y() As Integer '储存计算相乘的系数
    Dim LastNum() As Integer '储存身份证最后一位验证码
    Dim i As Integer
    Dim Sum As Long
    Dim Code As Integer
    
    '将字符串转成字符数组并储存
    X = Split(Left(num, 17), "")
    
    '如果第18位是字母X或x,将数字10储存到X(17)中
    '如果第18位是数字,直接储存到X(17)中
    If Mid(num, 18, 1) Like "[0-9]" Then
        X(17) = Mid(num, 18, 1)
    ElseIf LCase(Mid(num, 18, 1)) = "x" Then
        X(17) = "10"
    End If
    
    'Y中最后一位11是取模的除数,其余是相乘系数
    Y = Array(7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2, 11)
    LastNum = Array(1, 0, 10, 9, 8, 7, 6, 5, 4, 3, 2)
    
    '判断位数,18位且前17位都是数字才可能正确,再计算校验码判断
    If Len(num) = 18 And IsNumeric(Left(num, 17)) Then
        Sum = 0
        For i = 0 To 16
            Sum = Sum + X(i) * Y(i)
        Next
        Code = Sum Mod 11
        If LastNum(Code) = CInt(X(17)) Then
            ID = "正确"
        Else
            ID = "请检查身份证号码!"
        End If
    '非18位情况身份证号码错误
    Else
        ID = "请检查身份证号码!"
    End If
    End Function
    
    优化说明:
    
    将变量声明放置在函数开始处,提高代码的可读性;
    将字符分割为字符数组,简化代码;
    使用Like运算符代替Or,简化代码;
    将字符转换为数值类型,提高代码执行效率;
    简化校验码的计算过程;
    若校验码计算错误,提示后缀不必添加“位身份证号码”。
  • 【Excel】SUMPRODUCT函数-条件求和

    【Excel】SUMPRODUCT函数-条件求和

    【Excel】SUMPRODUCT函数-条件求和

    单条件求和

    =SUMPRODUCT(A:A*(B:B=C1))
    • 求和区域与条件区域不需要区分前后顺序;

    例:如果B列中的数值等于C1,则将其在A列的数值进行求和计算;

    多条件求和

    判断条件位于不同列中:

    Sumproduct(求和区域,(条件区域1=条件1)*(条件区域2=条件2)*..)
    • 求和区域在最前或者最后都可以,条件判断区域用*链接;

    如:

    =SUMPRODUCT(C2:C7,(A2:A7=E1)*(B2:B7=F1))

    判断条件位于同一列中:

    =SUMPRODUCT([对第一组数据求和]+[对第二组数据求和])

    如:

    =SUMPRODUCT((A2:A7=E1)*(B2:B7)+(A2:A7=E2)*(B2:B7))

    对同一主体不同比例的结果求和

    =SUMPRODUCT(基数区域,比例区域)

    此类数据统计常见于对销售业绩的管理,另外在HR的日常业务中,关于社保金额的计算也会涉及到相似的需求。

    在传统计算方法中,需要分别计算三个基数对应三个比例的小计值,最后对小计值进行求和,往往需要数据辅助列来完成。在使用SUMPRODUCT后,可直接对该结果进行求和:

    =SUMPRODUCT(B2:B4,C2:C4)

    SUMPRODUCT的计算过程和传统先求小计再求合计的过程是相同的,比较好理解。

    其他

    当然,SUMPRODUCT函数还有很多更高级的使用方式,比如与FIND函数协作进行模糊统计等等,但由于在日常工作中的应用相对较少,在此就不再多做介绍。

  • 根据类别列数据,将不同类别拆分成独立excel文件

    根据类别列数据,将不同类别拆分成独立excel文件

    根据类别列数据,将不同类别拆分成独立excel文件

    Sub 根据类别列数据,将不同类别拆分成独立excel文件()
        Dim d As Object, arr, brr, r, kr, i&, j&, k&, x&, Mystr$
        Dim Rng As Range, Rg As Range, tRow&, tCol&, aCol&, pd&, mypath$
        Dim Cll As Range, sht As Worksheet '第一部分,用户选择保存分表工作簿的路径。
            With Application.FileDialog(msoFileDialogFolderPicker)
            '选择保存工作薄的文件路径
                .AllowMultiSelect = False
                '不允许多选
                If .Show Then
                   mypath = .SelectedItems(1)
                    '读取选择的文件路径
                  Else
                 Exit Sub
    '如果没有选择保存路径,则退出程序
                  End If
            End With
    
            If Right(mypath, 1) <> "\" Then mypath = mypath & "\"
            ' '
            '第二部分遍历总表数据,通过字典将指定字段的不同明细行过滤保存
                Set d = CreateObject("scripting.dictionary") 'set字典
                Set Rg = Application.InputBox("请框选拆分依据列!只能选择单列单元格区域!", Title:="提示", Type:=8)
                '用户选择的拆分依据列
                tCol = Rg.Column '取拆分依据列列标
                tRow = Val(Application.InputBox("请输入总表标题行的行数?"))
                '用户设置总表的标题行数
                If tRow < 0 Then MsgBox "标题行数不能为负数,程序退出。": Exit Sub
                    Set Rng = ActiveSheet.UsedRange '总表的数据区域
                    Set Cll = ActiveSheet.Cells '用于在分表粘贴和总表同样行高列宽的数据格式
                    arr = Rng '数据范围装入数组arr
                    tCol = tCol - Rng.Column + 1 '计算依据列在数组中的位置
                    aCol = UBound(arr, 2) '数据源的列数
                For i = tRow + 1 To UBound(arr) '遍历数组arr
                    If arr(i, tCol) = "" Then arr(i, tCol) = "单元格空白"
                        Mystr = arr(i, tCol) '统一转换为字符串格式
                        If Not d.exists(Mystr) Then
                            d(Mystr) = i '字典中不存在关键词则将行号装入字典
                        Else
                            d(Mystr) = d(Mystr) & "," & i '如果存在则合并行号,以逗号间隔
                End If
            Next
    ' '
    '第三部分遍历字典取出分表数据明细,建立不同工作簿保存数据。
        Application.ScreenUpdating = False '关闭屏幕刷新
        Application.DisplayAlerts = False '关闭系统警告信息
        kr = d.keys '字典的key集
        For i = 0 To UBound(kr) '遍历字典key值
            If kr(i) <> "" Then '如果key不为空
                r = Split(d(kr(i)), ",") '取出item里储存的行号
                ReDim brr(1 To UBound(r) + 1, 1 To aCol) '声明放置结果的数组brr
                k = 0
                For x = 0 To UBound(r)
                    k = k + 1'累加记录行数
                    For j = 1 To aCol '遍历读取列
                        brr(k, j) = arr(r(x), j)
                     Next
                 Next
                    With Workbooks.Add
                '新建一个工作簿
                        With .Sheets(1).[a1]
                            Cll.Copy '复制粘贴总表的单元格格式
                            .PasteSpecial Paste:=xlPasteFormats, Operation:=xlNone, SkipBlanks:=False, Transpose:=False
                            Cells.NumberFormat = "@" '设置文本格式,防止文本值变形
                            If tRow > 0 Then .Resize(tRow, aCol) = arr '放标题行
                                .Offset(tRow, 0).Resize(k, aCol) = brr '放置数据区域
                                .Select'激活A1单元格
                        End With
            .SaveAs mypath & kr(i), xlWorkbookDefault '保存工作簿
            .Close True '关闭工作簿
                    End With
            End If
        Next
    '收尾巴。
    Set d = Nothing '释放字典
    Erase arr: Erase brr '释放数组
    MsgBox "处理完成。", , "提醒"
    Application.ScreenUpdating = True '恢复屏幕刷新
    Application.DisplayAlerts = True '恢复显示系统警告和消息
    End Sub
    
AI 助手