工作簿中名称为“合并数据”的工作表表头占三行,数据行从第四行开始,。用JS宏查找当前工作簿中名称为“合并数据”的工作表中第三列重复的数据所在行中的所有内容,并将其输出在一张新工作表中,命名为”日期重复“,并在最后添加统计重复数据个数
时间: 2025-07-20 16:30:08 浏览: 8
<think>我们已知“合并数据”工作表的表头占三行,数据从第四行开始。我们需要检查第三列(即C列)的重复数据。
步骤:
1. 获取“合并数据”工作表。
2. 读取数据(从第4行开始到有数据的最后一行,注意跳过最后三行?根据问题描述,这里并没有提到跳过最后三行,所以不需要跳过,但原问题中合并时跳过了最后三行,而这里我们处理的是已经合并后的表,所以按照要求,这里只关注表头占三行,数据从第四行开始,所以数据范围是第4行至最后一行(不包括表头))。
3. 检查第三列(索引为2,因为数组从0开始)的重复值。
4. 将重复行(重复出现的行,包括第一次和后续重复的行)输出到新工作表“日期重复”。
5. 在新工作表的最后添加统计信息,包括每个重复日期出现的次数,以及总重复行数(注意:重复行数是指重复出现的次数,比如一个日期出现3次,那么重复行数为2,因为第一次不算重复,后续两次算重复)。
但是注意:在输出重复数据时,我们通常需要输出所有重复的行(包括第一次出现)。所以,当我们发现一个日期重复时,需要把之前出现的相同日期的行也输出(如果之前还没有被输出的话)。
实现思路:
1. 读取“合并数据”工作表的数据区域。
2. 获取表头(前三行),但新工作表的表头我们只需要使用第一行表头(或者根据需求,但问题没有明确,我们假设新表只需要一行表头,即原表的第三行表头?因为原表头三行,合并后表头占两行,但这里我们输出重复数据,为了简洁,我们只使用一行表头,即原表第三行,并加上“来源工作表”列?不过原合并数据表中已经包含了来源列,所以这里我们直接使用原表头第三行作为新表的表头)。
3. 遍历数据行(从第4行开始),记录每个日期出现的行,当发现重复时,将该行以及之前相同日期的行都标记为重复行(注意不要重复输出同一行多次)。
具体步骤:
- 使用一个对象或Map来记录每个日期出现的行索引(在数据数组中的索引)和次数。
- 同时,我们用一个数组来收集重复行(包括第一次出现和后续重复的行)。但是注意:如果一个日期只出现一次,那么它不算重复,我们不需要输出。所以,我们可以在第一次遇到一个日期时先不收集,当第二次遇到时,将当前行和之前相同日期的行都加入重复行数组(同时要避免重复加入,比如之前已经因为重复加入过了,那么第二次遇到时只需要加入当前行)。
另一种做法:
1. 第一次遍历:记录每个日期出现的所有行索引。
2. 第二次遍历:对于每个日期,如果出现次数大于1,则将这些行索引对应的行都加入重复行数组。
3. 注意:为了避免重复加入,我们可以使用一个Set来记录已经加入的行索引。
但是,由于我们可能希望保持行的顺序,我们可以:
- 创建一个数组`duplicateRows`用于存放重复行数据(包括表头)。
- 创建一个Map:`dateToRows`,键为日期值,值为一个数组,存放该日期出现的行索引(在原始数据数组中的索引,注意原始数据数组不包括表头)。
- 遍历完成后,再遍历这个Map,对于每一个出现次数大于1的日期,将其对应的所有行数据加入`duplicateRows`(注意行索引对应的行数据是原始数据数组中的行,原始数据数组是从第4行开始的数据)。
- 然后对`duplicateRows`按行号排序(如果需要保持原表顺序,可以按行号排序,但也可以不排序,因为我们是按行索引顺序加入的,但注意Map的遍历顺序不一定是行顺序,所以我们需要按行索引排序)。
然而,我们也可以不排序,因为我们在遍历数据时是按行顺序的,所以我们在收集重复行时也可以按行顺序:我们可以先收集所有重复行索引,然后排序,再按顺序取出行数据。
但是,为了简单,我们可以:
- 先遍历所有行,构建`dateToRows`。
- 然后创建一个Set,包含所有重复日期对应的行索引(即出现次数>1的日期的所有行索引)。
- 然后我们按行索引顺序(从0到n-1)遍历,如果行索引在Set中,则将该行数据加入`duplicateRows`。这样顺序就与原始表一致。
步骤:
1. 读取数据:从第4行开始(即原始数据数组的第0行对应工作表的第4行)。
2. 构建`dateToRows`:对于每一行(索引i),获取第三列的值,如果Map中没有,则创建一个数组,将行索引i加入;如果已有,则加入数组。
3. 创建一个Set:`duplicateRowIndices`,遍历`dateToRows`,如果某个日期对应的行数组长度>1,则将这些行索引全部加入Set。
4. 创建一个数组`duplicateData`,用于存放重复行的数据(包括表头,但表头单独处理)。
5. 将表头(即原表第三行)作为新表的第一行。
6. 遍历原始数据数组的行索引(0到n-1),如果当前索引在`duplicateRowIndices`中,则将该行数据加入`duplicateData`。
7. 将`duplicateData`写入新工作表。
统计信息:
- 总重复行数:就是`duplicateData.length-1`(因为有一行表头)。
- 每个重复日期的出现次数:我们可以统计每个重复日期的出现次数(即`dateToRows`中长度>1的项,然后输出每个日期及其次数)。
但是注意:重复行数并不等于每个重复日期的出现次数的和,因为一个重复日期出现n次,那么重复行数就是n(但注意,我们统计的是重复行,包括第一次和后续重复,所以重复行数就是该日期出现的总行数,而重复日期的个数是出现重复的日期值的个数)。
我们可以在新工作表的最后输出一个统计表:
- 第一行:重复日期值,重复次数
- 然后最后一行:总重复行数(即所有重复行数之和,也就是`duplicateData`的行数-1)
但是注意:同一个日期重复多次,我们只统计一次这个日期,但是重复行数就是该日期出现的次数(全部)。
所以,我们可以在新工作表的数据区域下方,隔几行开始写统计信息。
具体代码步骤:
1. 获取“合并数据”工作表。
2. 获取整个数据区域(包括表头),然后分离出表头(前三行)和数据(从第4行开始)。
3. 构建`dateToRows`(键为日期字符串,值为行索引数组)。
4. 构建重复行索引Set。
5. 创建新工作表“日期重复”。
6. 写入表头(原表第三行)。
7. 遍历原始数据数组(0到n-1),如果索引在重复行索引Set中,则写入该行数据。
8. 在数据区域下方添加统计信息:先列出每个重复日期的出现次数,然后计算总重复行数(即重复行的总行数,也就是我们写入的数据行数)。
注意:重复日期的统计信息中,重复次数就是该日期出现的总次数(大于1次),我们只统计重复的日期。
代码实现:
由于我们已经有`dateToRows`,我们可以遍历它,输出每个重复日期的统计。
步骤:
- 在数据区域下方空几行(比如空3行),然后写“重复统计”。
- 然后写表头:日期值、重复次数。
- 然后遍历`dateToRows`,如果某个日期对应的行数大于1,则输出该日期和行数。
- 最后一行输出“总重复行数”和实际重复行数(即我们输出的重复数据行数,也就是`duplicateData.length-1`)。
但是注意:同一个日期在重复行中出现了多次,但我们在统计每个日期重复次数时,已经统计了该日期出现的总次数。
所以,总重复行数等于所有重复日期的总行数(即每个重复日期的出现次数之和)?不对,因为重复行数就是重复数据的总行数,我们已经得到了。所以我们可以直接使用`duplicateData.length-1`。
但是,我们也可以这样计算:总重复行数 = 所有重复日期的出现次数之和(因为每个重复日期出现的所有行都是重复行,包括第一次出现)。
所以,两种方式都可以。
代码:
注意:在WPS宏中,数组索引从0开始,工作表行号从1开始。
由于数据量可能大,我们使用数组操作以提高效率。
具体代码:</think>### 解决方案:处理表头占三行的重复数据查找与统计
以下代码实现了在表头占三行的"合并数据"工作表中查找第三列重复数据,并输出到"日期重复"工作表,同时添加详细统计信息:
```javascript
function 查找日期重复数据() {
try {
const workbook = ThisWorkbook;
const sourceSheet = workbook.Sheets.Item("合并数据");
if (!sourceSheet) throw new Error("找不到名为'合并数据'的工作表");
// 获取数据区域(跳过前三行表头)
const dataRange = sourceSheet.UsedRange;
const totalRows = dataRange.Rows.Count;
const totalCols = dataRange.Columns.Count;
// 验证数据行数
if (totalRows <= 3) {
console.log("数据行不足,无法处理");
return;
}
// 获取数据(从第4行开始)
const dataValues = sourceSheet.Range(
`A4:${String.fromCharCode(64 + totalCols)}${totalRows}`
).Value2;
// 获取表头(第三行作为新表头)
const headerRow = sourceSheet.Range("A3").Resize(1, totalCols).Value2[0];
// 统计重复数据
const { duplicateRows, dateStats } = findDuplicateRows(dataValues, 2); // 第三列索引为2
// 创建新工作表
const dupSheet = workbook.Sheets.Add(null, workbook.Sheets(workbook.Sheets.Count));
dupSheet.Name = "日期重复";
// 写入重复数据
if (duplicateRows.length > 0) {
dupSheet.Range("A1").Resize(1, headerRow.length).Value2 = headerRow;
dupSheet.Range("A2").Resize(duplicateRows.length, headerRow.length).Value2 = duplicateRows;
}
// 添加统计信息
addStatistics(dupSheet, duplicateRows.length, dateStats);
return "处理完成!";
} catch (error) {
console.error("处理出错: " + error.message);
return "处理失败!";
}
}
// 查找重复行并统计
function findDuplicateRows(data, colIndex) {
const duplicateRows = [];
const dateStats = new Map();
const seen = new Map();
// 遍历所有数据行
for (let i = 0; i < data.length; i++) {
const dateValue = data[i][colIndex];
// 初始化统计信息
if (!dateStats.has(dateValue)) {
dateStats.set(dateValue, {
count: 0,
firstRow: i + 4, // 实际行号(表头占3行)
rows: []
});
}
// 更新统计信息
const stat = dateStats.get(dateValue);
stat.count++;
stat.rows.push(i + 4); // 记录实际行号
// 收集重复行(重复次数>1)
if (seen.has(dateValue)) {
// 如果是首次发现重复,添加之前出现的行
if (seen.get(dateValue) !== -1) {
duplicateRows.push(data[seen.get(dateValue)]);
seen.set(dateValue, -1); // 标记为已处理
}
duplicateRows.push(data[i]);
} else {
seen.set(dateValue, i);
}
}
return { duplicateRows, dateStats };
}
// 添加统计信息到工作表
function addStatistics(sheet, duplicateCount, dateStats) {
const startRow = duplicateCount + 3; // 数据行数 + 表头行 + 2行空白
// 添加统计标题
sheet.Range(`A${startRow}`).Value2 = "日期重复统计";
sheet.Range(`A${startRow}`).Font.Bold = true;
sheet.Range(`A${startRow}`).Font.Size = 14;
// 添加表头
const statHeaders = ["日期值", "出现次数", "首次出现行", "所有行号"];
sheet.Range(`A${startRow + 1}`).Resize(1, statHeaders.length).Value2 = statHeaders;
// 添加统计数据
let rowIndex = startRow + 2;
let totalDuplicates = 0;
let duplicateDates = 0;
for (const [dateValue, stat] of dateStats) {
if (stat.count > 1) {
sheet.Range(`A${rowIndex}`).Value2 = dateValue;
sheet.Range(`B${rowIndex}`).Value2 = stat.count;
sheet.Range(`C${rowIndex}`).Value2 = stat.firstRow;
// 合并行号显示(避免过多列)
const rowNumbers = stat.rows.join(", ");
sheet.Range(`D${rowIndex}`).Value2 = rowNumbers;
// 高亮重复次数多的行
if (stat.count > 2) {
sheet.Range(`A${rowIndex}:D${rowIndex}`).Interior.Color = RGB(255, 200, 200);
}
rowIndex++;
totalDuplicates += stat.count;
duplicateDates++;
}
}
// 添加汇总信息
const summaryRow = rowIndex + 1;
sheet.Range(`A${summaryRow}`).Value2 = "汇总统计";
sheet.Range(`A${summaryRow}`).Font.Bold = true;
sheet.Range(`A${summaryRow + 1}`).Value2 = "重复日期总数";
sheet.Range(`B${summaryRow + 1}`).Value2 = duplicateDates;
sheet.Range(`A${summaryRow + 2}`).Value2 = "总重复行数";
sheet.Range(`B${summaryRow + 2}`).Value2 = totalDuplicates;
sheet.Range(`A${summaryRow + 3}`).Value2 = "实际重复数据行";
sheet.Range(`B${summaryRow + 3}`).Value2 = duplicateCount;
// 添加说明
sheet.Range(`A${summaryRow + 5}`).Value2 = "注:实际重复数据行 = 总重复行数 - 重复日期数";
sheet.Range(`A${summaryRow + 5}`).Font.Italic = true;
// 自动调整列宽
sheet.UsedRange.Columns.AutoFit();
}
// 执行函数
查找日期重复数据();
```
### 代码功能详解
#### 1. 主函数 `查找日期重复数据()`
- **工作表定位**:查找名为"合并数据"的工作表
- **数据处理**:
- 跳过前三行表头(第1-3行)
- 从第4行开始读取数据
- 使用第三行作为新工作表的表头
- **重复检测**:
- 查找第三列(索引2)的重复值
- 收集所有重复行(包括首次出现的行)
- **结果输出**:
- 创建"日期重复"工作表
- 写入重复数据行
- 添加详细统计信息
#### 2. 辅助函数 `findDuplicateRows()`
- **数据统计**:
- 使用Map记录每个日期值的出现次数
- 记录首次出现行号和所有行号
- **重复行收集**:
```javascript
if (seen.has(dateValue)) {
// 添加之前出现的行(如果是首次发现重复)
if (seen.get(dateValue) !== -1) {
duplicateRows.push(data[seen.get(dateValue)]);
seen.set(dateValue, -1);
}
duplicateRows.push(data[i]);
}
```
- **返回结果**:
- `duplicateRows`:所有重复数据行
- `dateStats`:日期统计信息
#### 3. 统计函数 `addStatistics()`
- **布局设计**:
- 在数据区域下方添加2行空白
- 添加统计标题和表头
- **统计内容**:
- 日期值
- 出现次数
- 首次出现行号
- 所有行号列表
- **汇总信息**:
- 重复日期总数
- 总重复行数
- 实际重复数据行数
- 关系说明:`实际重复行 = 总重复行 - 重复日期数`
### 关键处理说明
#### 1. 表头处理
```javascript
// 获取第三行作为新表头
const headerRow = sourceSheet.Range("A3").Resize(1, totalCols).Value2[0];
```
#### 2. 数据范围处理
```javascript
// 从第4行开始获取数据(跳过前三行表头)
const dataValues = sourceSheet.Range(
`A4:${String.fromCharCode(64 + totalCols)}${totalRows}`
).Value2;
```
#### 3. 行号计算
```javascript
// 实际行号 = 数组索引 + 表头行数(3) + 1
stat.rows.push(i + 4);
```
#### 4. 重复检测逻辑
```javascript
// 检测重复值
if (seen.has(dateValue)) {
// 处理重复行...
} else {
// 记录首次出现
seen.set(dateValue, i);
}
```
#### 5. 统计信息展示
```javascript
// 添加行号列表
const rowNumbers = stat.rows.join(", ");
sheet.Range(`D${rowIndex}`).Value2 = rowNumbers;
// 添加汇总公式说明
sheet.Range(`A${summaryRow + 5}`).Value2 = "注:实际重复数据行 = 总重复行数 - 重复日期数";
```
### 输出结果说明
1. **"日期重复"工作表结构**:
- 第一部分:重复数据行(使用原表第三行作为表头)
- 第二部分:详细统计信息(间隔2行)
- 日期值、出现次数、首次出现行、所有行号
- 第三部分:汇总统计
- 重复日期总数
- 总重复行数
- 实际重复数据行数
- 关系说明
2. **统计信息示例**:
```
日期值 出现次数 首次出现行 所有行号
2023-01-01 3 5 5, 12, 18
2023-01-05 2 7 7, 22
汇总统计
重复日期总数: 2
总重复行数: 5
实际重复数据行: 4
注:实际重复数据行 = 总重复行数 - 重复日期数
```
3. **可视化效果**:
- 统计标题加粗加大
- 重复次数>2的行标记为浅红色
- 说明文字使用斜体
- 自动调整列宽优化显示
### 使用说明
1. 在WPS中打开包含"合并数据"工作表的文件
2. 确保"合并数据"工作表:
- 表头占1-3行
- 数据从第4行开始
3. 打开JS宏编辑器,粘贴完整代码
4. 运行`查找日期重复数据()`函数
5. 查看结果:
- 新建的"日期重复"工作表
- 重复数据行
- 详细的重复统计信息
- 汇总数据
阅读全文
相关推荐



















