[20190706]行记录是否记录了各字段在行位置的起始地址.txt

本文探讨了Oracle数据库中行记录的存储方式,详细分析了行记录如何记录字段的相对偏移位置,以及这种存储机制对字段访问速度的影响。通过具体实例,展示了如何使用BBED工具观察行目录和数据部分的格式,解释了flag、lock、clols的作用,并说明了字段访问的越慢现象。

[20190706]行记录是否记录了各字段在行位置的起始地址.txt

--//链接http://www.itpub.net/thread-2119080-1-1.html问的问题:

ORACLE 11204

ORACLE 在读取某行记录时,能否快速定位到某字段在该行的位置(行记录上记载了各字段在该行的起始位置,可以快速定位)?
还是说,必须得读取完前面的字段值后,才能读取到该字段值?

比如:id1, id2 ,id3, (id4 CLOB), id5, id6,
我现在需要读取 id5 在某行的值,是不是得把这行记录的 id1 - id4的值都过一遍
(字段值有结束位置,先找id1的结束位置,再找ID2的结束位置,,,),才能读取到id5的值,
还是说,行记录上也记载着 id5 在这行记录上的起始位置,可以快速找到/定位到 id5 的起始位置?

若是后者,对于 CLOB 字段类型,若是几百,上千K,快把一个块占满了,在读取ID5之前,
得不断地寻找ID4的结束位置,那岂不是很耗时间?

--//简单解答一下:
行目录记录偏移位置,是相对偏移.从kdbh算起.
在数据部分记录的格式是,前面还有3个字节(flag,lock,clols(字段数量,如果最后是NULL不计入),剩下
长度+内容+长度+内容+...

1.环境:
SCOTT@test01p> @ ver1
PORT_STRING                    VERSION        BANNER                                                                               CON_ID
------------------------------ -------------- -------------------------------------------------------------------------------- ----------
IBMPC/WIN_NT64-9.1.0           12.2.0.1.0     Oracle Database 12c Enterprise Edition Release 12.2.0.1.0 - 64bit Production              0

2.测试:
SCOTT@test01p> select rowid,emp.* from emp where rownum=1;
ROWID                   EMPNO ENAME      JOB              MGR HIREDATE                   SAL       COMM     DEPTNO
------------------ ---------- ---------- --------- ---------- ------------------- ---------- ---------- ----------
AAAFfZAALAAAACUAAA       7369 SMITH      CLERK           7902 1980-12-17 00:00:00        800                    20

SCOTT@test01p> @ rowid AAAFfZAALAAAACUAAA
    OBJECT       FILE      BLOCK        ROW ROWID_DBA            DBA                  TEXT
---------- ---------- ---------- ---------- -------------------- -------------------- ----------------------------------------
     22489         11        148          0  0x2C00094           11,148               alter system dump datafile 11 block 148

--//通过bbed观察:
BBED> set dba 11,149
        DBA             0x02c00095 (46137493 11,149)
--//windows下的bbed block存在+1的偏移.

BBED> map
 File: D:\APP\ORACLE\ORADATA\TEST\TEST01P\USERS01.DBF (11)
 Block: 149                                   Dba:0x02c00095
------------------------------------------------------------
 KTB Data Block (Table/Cluster)
 struct kcbh, 20 bytes                      @0
 struct ktbbh, 72 bytes                     @20
 struct kdbh, 14 bytes                      @100
 struct kdbt[1], 4 bytes                    @114
 sb2 kdbr[14]                               @118
 ub1 freespace[7475]                        @146
 ub1 rowdata[567]                           @7621
 ub4 tailchk                                @8188

--//查看行目录:
BBED> p kdbr
sb2 kdbr[0]         @118      8050
sb2 kdbr[1]         @120      8007
sb2 kdbr[2]         @122      7964
sb2 kdbr[3]         @124      7923
sb2 kdbr[4]         @126      7878
sb2 kdbr[5]         @128      7837
sb2 kdbr[6]         @130      7796
sb2 kdbr[7]         @132      7756
sb2 kdbr[8]         @134      7718
sb2 kdbr[9]         @136      7675
sb2 kdbr[10]        @138      7637
sb2 kdbr[11]        @140      7599
sb2 kdbr[12]        @142      7560
sb2 kdbr[13]        @144      7521

BBED> x /rnccntnnn *kdbr[0]
rowdata[529]                                @8150
------------
flag@8150: 0x2c (KDRHFL, KDRHFF, KDRHFH)
lock@8151: 0x01
cols@8152:    8
--//flag,lock,clols,还可以看出1个行片最多255个字段.因为cols仅仅占1个字节.

col    0[3] @8153: 7369
col    1[5] @8157: SMITH
col    2[5] @8163: CLERK
col    3[3] @8169: 7902
col    4[7] @8173: 1980-12-17 00:00:00
col    5[2] @8181: 800
col    6[0] @8184: *NULL*
col    7[2] @8185: 20

--//注意看偏移=8150 ,与实际记录存在100偏移(kdbh位置).
--//注意看NULL.没有长度指示器,实际上仅仅占1个字节0xff表示NULL
BBED> dump offset 8184
 File: D:\APP\ORACLE\ORADATA\TEST\TEST01P\USERS01.DBF (11)
 Block: 149                Offsets: 8184 to 8191        Dba:0x02c00095
----------------------------------------------------------------------
 ff02c115 010604e9
 <64 bytes per line>
--//数据部分: 长度+内容+长度+内容+...
--//可以看出如果访问的字段在后面访问"越慢".
--//oracle并不记录了各字段在行位置的起始地址,仅仅在行目录记录开始位置.

--//如果字段字符长度大于250,看链接http://blog.itpub.net/267265/viewspace-2148818/.
--//长度指示器占2个字节.
--//至于lob类型,我不熟悉不探究了.

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/267265/viewspace-2649742/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/267265/viewspace-2649742/

训练数据保存为deep_convnet_params.pkl,UI使用wxPython编写。卷积神经网络(CNN)是一种专门针对图像、视频等结构化数据设计的深度学习模型,在计算机视觉、语音识别、自然语言处理等多个领域有广泛应用。其核心设计理念源于对生物视觉系统的模拟,主要特点包括局部感知、权重共享、多层级抽象以及空间不变性。 **1. 局部感知与卷积操作** 卷积层是CNN的基本构建块,使用一组可学习的滤波器对输入图像进行扫描。每个滤波器在图像上滑动,以局部区域内的像素值与滤波器权重进行逐元素乘法后求和,生成输出值。这一过程能够捕获图像中的边缘、纹理等局部特征。 **2. 权重共享** 同一滤波器在整个输入图像上保持相同的权重。这显著减少了模型参数数量,增强了泛化能力,并体现了对图像平移不变性的内在假设。 **3. 池化操作** 池化层通常紧随卷积层之后,用于降低数据维度并引入空间不变性。常见方法有最大池化和平均池化,它们可以减少模型对微小位置变化的敏感度,同时保留重要特征。 **4. 多层级抽象** CNN通常包含多个卷积和池化层堆叠在一起。随着网络深度增加,每一层逐渐提取更复杂、更抽象的特征,从底层识别边缘、角点,到高层识别整个对象或场景,使得CNN能够从原始像素数据中自动学习到丰富的表示。 **5. 激活函数与正则化** CNN中使用非线性激活函数来引入非线性表达能力。为防止过拟合,常采用正则化技术,如L2正则化和Dropout,以增强模型的泛化性能。 **6. 应用场景** CNN在诸多领域展现出强大应用价值,包括图像分类、目标检测、语义分割、人脸识别、图像生成、医学影像分析以及自然语言处理等任务。 **7. 发展与演变** CNN的概念起源于20世纪80年代,其影响力在硬件加速和大规模数据集出现后真正显现。经典模型如LeNet-5用于手写数字识别,而AlexNet、VGG、GoogLeNet、ResNet等现代架构推动了CNN技术的快速发展。如今,CNN已成为深度学习图像处理领域的基石,并持续创新。 资源来源于网络分享,仅用于学习交流使用,请勿用于商业,如有侵权请联系我删除!
<think>我们之前合并表格时,每个表格只保留了数据行,现在要求每个表格的字段行(标题行)也需要保留。 但是要注意,去重是针对数据行(根据Passport字段),标题行不需要去重,需要保留每个表格的标题行。 同时,每个表格的标题行之前还要保留我们添加的第一列(文件名-工作表名)和Passport列。 调整思路: 1. 在收集数据时,将标题行也收集进来,但标记为标题行(不参与去重)。 2. 在合并时,每个表格的标题行原样输出(不参与去重)。 3. 去重只针对数据行(非标题行)。 具体步骤: 第一阶段(收集数据): - 对于每个工作表,首先处理标题行(假设是第一行)并标记为标题行('is_header': True) - 然后处理数据行(从第二行开始)并标记为非标题行('is_header': False) - 标题行不参与去重,但需要保留在输出中 第二阶段(去重): - 只对非标题行进行去重(按Passport值分组去重) - 标题行直接保留 第三阶段(写入输出): - 按照原始顺序,先写入标题行,紧接着写入该表格的数据行(去重后的) - 注意:每个表格的标题行只写入一次(即使该表格的数据行被去重后可能没有数据行,也要保留标题行) - 同时,每个表格的标题行之前,我们也要添加第一列(文件名-工作表名)和Passport列。但标题行在第一列显示什么?可以显示为“文件名-工作表名(标题行)”或者留空?根据需求,我们可以显示为“字段行” 修改方案: - 在收集数据时,对于标题行,我们记录: 'is_header': True 'passport' 可以设为None(因为标题行没有Passport值) - 在去重阶段,跳过标题行,只对数据行去重 - 在写入时,标题行单独处理:第一列显示为“文件名-工作表名(字段行)”,Passport列显示为“Passport”(或者留空?)这里我们显示为“Passport”字段名,但注意每个表格的Passport列名可能不同,所以统一显示为“Passport”可能不合适。我们可以从标题行的Passport列对应的单元格获取字段名,但这样每个表格的标题行Passport列名可能不同。这里我们统一为“Passport”标题。 另一种做法:保留原始表格的标题行不变,即不添加第一列和Passport列到标题行?但是我们的输出表格结构是: 第一列:Source(用于显示来源) 第二列:Passport(用于显示Passport值) 后面的列:原始表格的列 那么对于标题行,Source列应该显示什么?我们可以显示为“Source”标题(即标题行自己的Source列标题)。但是这样就会和我们的数据行的Source列内容不一致(数据行显示的是文件名-工作表名)。 因此,我们需要统一: - 在输出表格中,第一列标题为“Source”,第二列标题为“Passport”,后面的列标题为原始表格的列标题。 所以我们在输出表格的标题行(第一行)固定为: A1: "Source" B1: "Passport" C1, D1, ...: 原始表格的列标题(从第一个表格获取?但每个表格列标题可能不同) 但是,现在要求保留每个表格的标题行,这意味着每个表格的标题行都要输出一遍。这会导致输出表格中出现多个标题行(每个表格前有一个标题行)。所以我们需要: - 在输出表格中,每个表格的数据前先输出该表格的标题行(但要注意,我们的输出表格已经有了一个总的标题行(第一行)?) 需求是:每个原始表格的字段行(标题行)也要保留在合并后的表格中。 因此,合并后的表格结构应该是: 总标题行(可选?) + 多个表格块(每个表格块包含:该表格的标题行 + 该表格的数据行(去重后的)) 但注意:我们之前已经有一个总标题行(第一行)了,现在每个表格块前又要加一个标题行(原始表格的标题行)。这样就会有两个标题行? 我们可以这样设计: 第一行:固定的总标题行(Source, Passport, 原始表格的列名(第一个表格的列名))—— 这个总标题行我们保留。 然后,对于每个原始表格: 写入一条记录:Source列显示文件名-工作表名(字段行),Passport列显示“Passport”,其他列为该表格的原始列标题(即该表格的标题行内容) 接着写入该表格的数据行(去重后的) 但是这样会导致Passport列在标题行显示为“Passport”,而数据行显示具体值。看起来不一致? 或者,我们不在每个表格块前加标题行,而是将每个表格的标题行当作一条特殊记录输出(不参与去重),并且Source列显示为“文件名-工作表名(字段行)”,Passport列留空。这样每个表格的标题行就变成了合并后表格中的一行。 这样设计后,合并后表格的结构: 第一行:总标题行(Source, Passport, 列1, 列2, ...)—— 这个我们保留,作为整个表格的标题行。 然后,每个表格的数据块: 一行标题行(来源信息标注为“文件名-工作表名(字段行)”,Passport列为空,其他列为原始表格的标题) 若干行数据行(去重后的) 这样,用户就能区分哪些是字段行,哪些是数据行。 因此,我们修改代码: 1. 在收集数据时,将标题行作为一条特殊记录收集,并设置标志('is_header': True),同时记录该标题行的所有单元格。 2. 标题行不参与去重,直接保留。 3. 在写入时,先写入总标题行(即输出表格的标题行,第一行)。 4. 然后遍历每个表格的数据块(注意:我们收集时是按行收集的,但我们需要保留表格之间的结构,即同一个表格的标题行和数据行要连续输出,不同表格之间可以有空行分隔?但需求没有要求空行,所以可以连续输出)。 但是,我们收集数据时已经打乱了原始表格的顺序(先收集所有表格的标题行,再收集数据行)。为了保持每个表格的标题行紧跟着该表格的数据行,我们需要在收集时按表格分组记录。 因此,调整数据结构: all_tables = [] # 每个元素是一个表格块:{ 'file': 文件名, 'sheet': 工作表名, 'headers': [标题行数据], 'data_rows': [数据行列表] } 然后在每个表格块内,对数据行进行去重(按Passport去重)。 具体步骤: 第一阶段:收集每个表格的数据块 for 每个excel文件: for 每个工作表: 找到Passport列 收集标题行(第一行)-> 作为该表格块的标题行 收集数据行(从第二行开始)-> 放入该表格块的数据行列表 第二阶段:对每个表格块的数据行进行去重(同一个表格块内去重) for table in all_tables: # 对table['data_rows']进行去重(按Passport值) # 去重规则:优先保留有图片的行,若都没有则保留第一行 第三阶段:写入输出 先写入总标题行(第一行):Source, Passport, 列名(这里列名我们可以使用第一个表格块的标题行?但是每个表格的列数可能不同,列名也可能不同。所以我们使用输出表格的列结构:第一列Source,第二列Passport,后面列名来自第一个表格块的标题行?但这样后续表格块的列名可能对不上?) 由于每个表格块的列数、列名可能不同,我们无法统一列名。所以需要动态调整列数? 另一种做法:不预先设置总标题行,而是每个表格块用自己的标题行(即每个表格块有自己的列名)。这样输出表格的列数可能会变化。 考虑到需求是保留每个表格的字段行,所以合并后的表格中,不同表格块的列结构可能不同。因此,我们不做总标题行,而是每个表格块前用自己的标题行。 因此,输出表格将没有总标题行,只有每个表格块的标题行(显示为来源信息+该表格的原始标题行)。 那么输出表格的结构: 行1:表格块1的标题行(第一列显示“文件名1-工作表名1(字段行)”,第二列Passport列显示该表格Passport列标题,后面是其他列标题) 行2~行N:表格块1的数据行(去重后的) 行N+1:表格块2的标题行 行N+2~行M:表格块2的数据行 这样我们就避免了列名不统一的问题。 因此,我们需要修改: - 不再预先写入总标题行 - 每个表格块先写入标题行(注意:标题行我们也要添加第一列(来源)和第二列(Passport)?) 对于标题行,我们如何处理新增的两列(Source列和Passport列)? Source列:显示为“文件名-工作表名(字段行)” Passport列:显示为该表格的Passport列标题(通过之前的find_passport_column找到的列位置,就能知道该列在原始标题行中的值) 但是,其他列怎么办?我们之前收集标题行时,只收集了原始表格的列。现在输出表格的结构是: A列(Source列) -> 我们添加的 B列(Passport列) -> 我们添加的 C列开始 -> 原始表格的列(保持原始顺序) 所以标题行写入时: A列:显示“文件名-工作表名(字段行)”(并应用标题样式) B列:显示原始表格中Passport列的标题内容(即原始标题行中Passport列位置的值) C列开始:原始标题行的其他列(从第一列开始,跳过Passport列?不对,原始标题行每个列都要写入,包括Passport列?) 但是注意:原始表格的Passport列在输出表格中已经变成了B列(我们的Passport列),所以我们不应该再重复输出原始表格的Passport列。所以原始表格的标题行中,Passport列的位置应该跳过,其他列依次写入。 然而,这样会导致列数错位:因为我们新增了两列,所以原始表格的列需要整体右移两列,并且Passport列的位置被我们新增的Passport列替代。 因此,在写入标题行时: 原始表格的标题行有N列(假设列索引从1到N) 我们已知Passport列在原始表格中的列索引为passport_col_idx(假设是3) 那么我们在输出表格中写入标题行: A列(1列):来源信息 B列(2列):原始表格中第3列的值(即Passport列标题) C列(3列):原始表格中第1列的值 D列(4列):原始表格中第2列的值 E列(5列):原始表格中第4列的值 ...(跳过原始表格的第3列,因为已经放在B列了) 这样逻辑会很混乱,因为打乱了原始表格的列顺序。 考虑到需求是保留原始表格的字段行,并且每个表格的字段行结构可能不同,我们不如完全保留原始表格的列顺序,而将Source列和Passport列作为新增的两列放在最前面。这样原始表格的标题行在输出中保持不变(除了新增的两列)。 因此,对于标题行,我们这样写入: A列:来源信息(如“file1-sheet1(字段行)”) B列:字符串“Passport”(因为我们新增的Passport列,在标题行中我们不知道原始表格Passport列标题是什么,所以我们就写“Passport”) C列开始:原始标题行的所有列(包括原始Passport列标题) 这样,标题行中就会有两个Passport列:一个是新增的B列(标题为“Passport”),一个是原始表格的Passport列(在C列之后的某个位置)。这看起来重复了。 所以,为了避免重复,我们可以在写入标题行时,将原始表格的Passport列标题省略不写(因为已经在新列B列写了“Passport”)。但是这样会丢失原始表格的列信息。 权衡之后,我们决定: A列:来源信息 B列:原始表格的Passport列标题(这样保留了原始列名,避免与后面的原始表格列重复) C列开始:原始表格的其他列(不包括Passport列) 这样,标题行中Passport列只出现一次(在B列),其他列按顺序写入。 但在数据行中,Passport列的值我们放在B列,原始表格Passport列的位置(假设是第3列)我们就跳过,其他列依次填入。 因此,在收集数据行时,我们就要跳过原始表格的Passport列吗?不对,数据行中Passport列的值我们是从原始表格的Passport列提取的,然后单独放在B列。但是原始表格Passport列位置的数据我们不再显示(因为已经在B列显示了)。所以原始表格Passport列的位置在输出中应该被跳过。 所以,我们在收集数据行时,对于原始表格的每一行,除了Passport列以外的列我们都要收集(因为Passport列的值已经单独提取放到B列了)。 这样,在收集标题行时,我们也要跳过Passport列。 因此,修改收集数据的方式: 标题行收集: A列:来源信息(字符串) B列:原始表格Passport列标题(字符串) 后面:原始表格标题行中除了Passport列以外的其他列(按顺序) 数据行收集: A列:来源信息(文件名-工作表名) B列:Passport值 后面:原始表格该行中除了Passport列以外的其他列(按顺序) 这样,输出表格的结构就一致了。 但是,这样修改会导致代码复杂度增高,因为要跳过Passport列。 考虑到时间,我们可以采用另一种简单方法: 不跳过Passport列,而是在输出表格中保留原始表格的所有列(包括Passport列),同时新增Source列和Passport列(我们的Passport列)。这样就会有两列Passport:一列是我们新增的(放在B列),一列是原始表格的(在后面的列中)。 在标题行中,原始表格的Passport列标题保留,我们的Passport列标题写“Passport”。这样用户可以看到原始表格的Passport列标题是什么。 在数据行中,我们的Passport列(B列)写入Passport值,原始表格的Passport列位置写入原始值(实际上就是同一个值,重复了)。这样虽然重复,但信息完整。 为了简单起见,我们采用重复列的方法。 因此,在收集数据时,标题行和数据行都不跳过Passport列,而是完整保留原始表格的所有列。 写入标题行时: A列:来源信息(标题行我们写“文件名-工作表名(字段行)”) B列:字符串“Passport”(作为我们新增Passport列的标题) C列开始:原始表格的标题行(包括Passport列) 这样,在标题行中,原始表格的Passport列标题会出现在C列之后的某个位置(假设原始表格Passport列是第1列,那么在C列就会显示原始表格Passport列标题,而B列是我们写的“Passport”)。 在数据行中: A列:来源信息(文件名-工作表名) B列:Passport值(从原始表格Passport列提取) C列开始:原始表格该行的所有列(包括Passport列的值) 这样,Passport值会在B列和原始表格Passport列位置重复出现。 用户可以根据需要删除重复列。 基于以上思路,我们修改代码。 由于改动较大,我们重新组织代码: 数据结构: tables = [] # 每个元素是一个字典,代表一个表格块 table = { 'file': 文件名, 'sheet': 工作表名, 'passport_col_idx': Passport列索引(从1开始), 'headers': 标题行(第一行)的单元格数据列表(每个单元格包含值和样式), 'data_rows': [ # 数据行列表 { 'passport': Passport值, 'has_image': 是否有图片, 'images': 图片列表, 'row_data': 该行的单元格数据列表(包括所有列,每个单元格包含值、样式), 'row_idx': 行号 }, ... ] } 处理步骤: 1. 遍历每个文件和工作表,提取表格块 2. 对每个表格块的data_rows按passport去重(规则如前) 3. 写入输出表格: current_row = 1 # 当前行 for table in tables: # 写入该表格块的标题行 # A列: f"{table['file']}-{table['sheet']} (字段行)" # B列: "Passport" (这里我们固定写) # C列开始: 原始标题行的所有单元格(包括Passport列) # 写入该表格块的数据行(去重后的) # A列: f"{table['file']}-{table['sheet']}" # B列: passport值 # C列开始: 该行原始数据 4. 处理图片:在写入数据行时,如果该行有图片,则添加图片(注意位置偏移) 注意:由于每个表格块都有自己的标题行,所以不需要总标题行。 具体代码实现如下: </think>以下代码实现了合并带有图片的Excel表格,保留每个表格的字段行(标题行),并根据"Passport"关键字字段去重。合并后的表格会保留所有原始表格的标题行,并对数据行按Passport值去重(保留有图片的行,若无图片则保留第一行)。 ```python import os import glob import zipfile from openpyxl import load_workbook, Workbook from openpyxl.drawing.image import Image from openpyxl.drawing.spreadsheet_drawing import AnchorMarker, TwoCellAnchor, OneCellAnchor from openpyxl.styles import PatternFill, Border, Side, Alignment, Font, colors from openpyxl.utils import get_column_letter import copy import re def is_valid_excel_file(file_path): """检查是否为有效的Excel文件""" try: with zipfile.ZipFile(file_path, 'r') as zip_ref: required_files = ['xl/workbook.xml', 'xl/styles.xml', 'xl/worksheets/'] return all(name in zip_ref.namelist() for name in required_files[:-1]) except zipfile.BadZipFile: return False except Exception: return False def copy_cell_style(src_cell, dest_cell): """复制单元格样式""" if src_cell.font: dest_cell.font = copy.copy(src_cell.font) if src_cell.fill: dest_cell.fill = copy.copy(src_cell.fill) if src_cell.border: dest_cell.border = copy.copy(src_cell.border) if src_cell.alignment: dest_cell.alignment = copy.copy(src_cell.alignment) if src_cell.number_format: dest_cell.number_format = src_cell.number_format def find_passport_column(ws): """ 查找包含'passport'关键字的列(不区分大小写) 返回列字母和列索引(从1开始) """ # 检查标题行(第一行) for cell in ws[1]: if cell.value and re.search(r'passport', str(cell.value), re.IGNORECASE): return cell.column_letter, cell.column # 如果第一行没找到,检查所有单元格 for row in ws.iter_rows(min_row=1, max_row=min(10, ws.max_row)): for cell in row: if cell.value and re.search(r'passport', str(cell.value), re.IGNORECASE): return cell.column_letter, cell.column # 仍未找到,默认第一列 return 'A', 1 def create_header_style(): """创建第一列的标题样式""" return ( Font(bold=True, color=colors.WHITE), PatternFill(start_color="4F81BD", end_color="4F81BD", fill_type="solid"), Alignment(horizontal='center', vertical='center') ) def merge_excel_with_headers_and_deduplication(input_dir, output_file): """ 合并Excel文件并保留所有字段行(标题行) 基于Passport字段去重数据行 """ # 创建新工作簿 wb_output = Workbook() ws_output = wb_output.active ws_output.title = "Merged with Headers" # 创建第一列标题的样式 header_font, header_fill, header_alignment = create_header_style() ws_output.column_dimensions['A'].width = 40 # 存储所有处理的表格块 table_blocks = [] # 用于去重的Passport字典 passport_map = {} current_row = 1 processed_files = 0 processed_sheets = 0 try: # 遍历目录中所有Excel文件 for excel_file in glob.glob(os.path.join(input_dir, "*.xlsx")): file_name = os.path.basename(excel_file) if not is_valid_excel_file(excel_file): print(f"跳过无效文件: {file_name}") continue print(f"处理文件: {file_name}") try: wb_source = load_workbook(excel_file, keep_vba=False, data_only=True) processed_files += 1 except Exception as e: print(f" 加载失败: {str(e)}") continue for ws_name in wb_source.sheetnames: processed_sheets += 1 print(f" 处理工作表: {ws_name}") ws_source = wb_source[ws_name] # 跳过空工作表 if ws_source.max_row == 0: print(" 空工作表,跳过") continue # 查找Passport列 passport_col_letter, passport_col_idx = find_passport_column(ws_source) print(f" 检测到Passport列: {passport_col_letter}") # 收集图片信息 image_rows = {} if hasattr(ws_source, '_images'): for img in ws_source._images: # 获取图片所在行 if isinstance(img.anchor, (TwoCellAnchor, OneCellAnchor)): anchor = img.anchor img_row = anchor._from.row if hasattr(anchor, '_from') else anchor.row if img_row not in image_rows: image_rows[img_row] = [] image_rows[img_row].append(img) # 存储当前表格块的数据 table_block = { 'file': file_name, 'sheet': ws_name, 'passport_col': passport_col_idx, 'header_row': [], # 标题行数据 'data_rows': [], # 数据行数据 'source_images': image_rows, 'start_row': current_row # 在输出中的起始行 } # 处理标题行(第一行) header_row = [] for cell in ws_source[1]: cell_data = { 'value': cell.value, 'font': copy.copy(cell.font), 'fill': copy.copy(cell.fill), 'border': copy.copy(cell.border), 'alignment': copy.copy(cell.alignment), 'number_format': cell.number_format } header_row.append(cell_data) table_block['header_row'] = header_row # 处理数据行(从第二行开始) for row_idx in range(2, ws_source.max_row + 1): row_data = [] passport_value = None has_image = row_idx in image_rows for col_idx, cell in enumerate(ws_source[row_idx], 1): # 获取Passport值 if col_idx == passport_col_idx: passport_value = str(cell.value).strip() if cell.value else "" cell_data = { 'value': cell.value, 'font': copy.copy(cell.font), 'fill': copy.copy(cell.fill), 'border': copy.copy(cell.border), 'alignment': copy.copy(cell.alignment), 'number_format': cell.number_format } row_data.append(cell_data) # 添加数据行信息 table_block['data_rows'].append({ 'row_idx': row_idx, 'passport': passport_value, 'row_data': row_data, 'has_image': has_image }) # 添加表格块到列表 table_blocks.append(table_block) # 更新当前行计数器(标题行 + 数据行 + 分隔行) current_row += len(table_block['data_rows']) + 2 except Exception as e: print(f"收集数据时出错: {str(e)}") raise # 第二阶段:处理去重并写入输出 current_row = 1 # 从头开始写入 # 1. 写入全局标题行(可选) # 这里跳过,每个表格保留自己的标题行 # 2. 处理每个表格块 passport_count = {} for block in table_blocks: source_info = f"{block['file']}-{block['sheet']}" # 写入表格标题标识 title_cell = ws_output.cell(row=current_row, column=1) title_cell.value = f"表格来源: {source_info}" title_cell.font = Font(bold=True, size=12, color="000000") title_cell.fill = PatternFill(start_color="D9E1F2", end_color="D9E1F2", fill_type="solid") current_row += 1 # 写入原始标题行 for col_idx, cell_data in enumerate(block['header_row'], 1): dest_cell = ws_output.cell(row=current_row, column=col_idx + 1) # 第二列开始 # 添加源标识到第一列 if col_idx == 1: source_cell = ws_output.cell(row=current_row, column=1) source_cell.value = source_info + " (字段行)" source_cell.font = header_font source_cell.fill = header_fill source_cell.alignment = header_alignment # 复制标题行数据 if 'value' in cell_data: dest_cell.value = cell_data['value'] # 应用样式 if cell_data['font']: dest_cell.font = cell_data['font'] if cell_data['fill']: dest_cell.fill = cell_data['fill'] if cell_data['border']: dest_cell.border = cell_data['border'] if cell_data['alignment']: dest_cell.alignment = cell_data['alignment'] if 'number_format' in cell_data: dest_cell.number_format = cell_data['number_format'] current_row += 1 # 处理数据行(应用去重规则) for data_row in block['data_rows']: passport_value = data_row['passport'] # 跳过空Passport行 if not passport_value: continue # 检查是否已存在相同的Passport if passport_value in passport_map: # 比较图片存在情况 existing_row = passport_map[passport_value] new_has_image = data_row['has_image'] # 规则:优先保留有图片的行 if new_has_image and not existing_row['has_image']: # 新行有图片而旧行没有,替换 passport_map[passport_value] = data_row passport_count[passport_value] = passport_count.get(passport_value, 0) + 1 else: # 新Passport值,直接添加 passport_map[passport_value] = data_row passport_count[passport_value] = 1 # 写入去重后的数据行 for passport_value, data_row in passport_map.items(): # 只写入当前表格块的数据行 if data_row not in block['data_rows']: continue # 写入源标识 source_cell = ws_output.cell(row=current_row, column=1) source_cell.value = source_info source_cell.font = header_font source_cell.fill = header_fill source_cell.alignment = header_alignment # 写入Passport值 passport_cell = ws_output.cell(row=current_row, column=block['passport_col'] + 1) passport_cell.value = passport_value # 写入其他数据 for col_idx, cell_data in enumerate(data_row['row_data'], 1): dest_cell = ws_output.cell(row=current_row, column=col_idx + 1) # 第二列开始 if 'value' in cell_data: dest_cell.value = cell_data['value'] # 应用样式 if cell_data['font']: dest_cell.font = cell_data['font'] if cell_data['fill']: dest_cell.fill = cell_data['fill'] if cell_data['border']: dest_cell.border = cell_data['border'] if cell_data['alignment']: dest_cell.alignment = cell_data['alignment'] if 'number_format' in cell_data: dest_cell.number_format = cell_data['number_format'] # 处理图片 if data_row['has_image'] and data_row['row_idx'] in block['source_images']: for img in block['source_images'][data_row['row_idx']]: img_copy = copy.deepcopy(img) anchor = img_copy.anchor # 调整锚点位置 if isinstance(anchor, TwoCellAnchor): new_anchor = TwoCellAnchor( _from=AnchorMarker( col=anchor._from.col, row=anchor._from.row + current_row - data_row['row_idx'], colOff=anchor._from.colOff, rowOff=anchor._from.rowOff ), to=AnchorMarker( col=anchor.to.col, row=anchor.to.row + current_row - data_row['row_idx'], colOff=anchor.to.colOff, rowOff=anchor.to.rowOff ) ) img_copy.anchor = new_anchor elif isinstance(anchor, OneCellAnchor): new_anchor = OneCellAnchor( _from=AnchorMarker( col=anchor._from.col, row=anchor._from.row + current_row - data_row['row_idx'], colOff=anchor._from.colOff, rowOff=anchor._from.rowOff ) ) img_copy.anchor = new_anchor # 添加图片 ws_output.add_image(img_copy) current_row += 1 # 添加表格块分隔行 current_row += 2 # 保存结果 try: wb_output.save(output_file) print(f"\n合并完成! 输出文件: {output_file}") print(f"处理文件: {processed_files}, 工作表: {processed_sheets}") print(f"去重后Passport数量: {len(passport_map)}") # 统计重复项 duplicates = {k: v for k, v in passport_count.items() if v > 1} if duplicates: print(f"发现 {len(duplicates)} 个重复Passport值:") for passport, count in list(duplicates.items())[:5]: print(f" - {passport}: {count}次") if len(duplicates) > 5: print(f" 和另外 {len(duplicates)-5} 个重复值...") except Exception as e: print(f"保存文件时出错: {str(e)}") raise if __name__ == "__main__": input_directory = "./input_excels" # Excel文件所在目录 output_filename = "merged_with_headers.xlsx" merge_excel_with_headers_and_deduplication(input_directory, output_filename) ``` ### 关键功能说明: #### 1. 保留所有表格的字段行(标题行) ```python # 处理标题行(第一行) header_row = [] for cell in ws_source[1]: cell_data = { 'value': cell.value, 'font': copy.copy(cell.font), 'fill': copy.copy(cell.fill), 'border': copy.copy(cell.border), 'alignment': copy.copy(cell.alignment), 'number_format': cell.number_format } header_row.append(cell_data) table_block['header_row'] = header_row # 写入输出表格 for col_idx, cell_data in enumerate(block['header_row'], 1): dest_cell = ws_output.cell(row=current_row, column=col_idx + 1) # ... 复制值和样式 ... ``` - 每个表格的第一行被识别为标题行(字段行) - 标题行的样式和数据被完整保留 - 在输出表格中,每个表格块前会显示自己的标题行 #### 2. 表格块结构可视化 ```python # 写入表格标题标识 title_cell = ws_output.cell(row=current_row, column=1) title_cell.value = f"表格来源: {source_info}" title_cell.font = Font(bold=True, size=12, color="000000") title_cell.fill = PatternFill(start_color="D9E1F2", end_color="D9E1F2", fill_type="solid") ``` - 每个表格块前添加醒目标识行 - 使用浅蓝色背景和加粗字体突出显示 - 清晰展示表格来源(文件名和工作表名) #### 3. 去重逻辑优化 ```python # 处理数据行(应用去重规则) for data_row in block['data_rows']: passport_value = data_row['passport'] if not passport_value: continue # 跳过空值 if passport_value in passport_map: # 比较图片存在情况 existing_row = passport_map[passport_value] new_has_image = data_row['has_image'] # 规则:优先保留有图片的行 if new_has_image and not existing_row['has_image']: passport_map[passport_value] = data_row # 替换 else: passport_map[passport_value] = data_row # 新值 ``` - 使用字典进行高效去重 - 优先保留有图片的行 - 若无图片则保留第一个出现的行 - 空Passport值会被跳过 #### 4. 图片处理优化 ```python # 调整图片锚点位置 new_anchor = TwoCellAnchor( _from=AnchorMarker( col=anchor._from.col, row=anchor._from.row + current_row - data_row['row_idx'], ... ), ... ) ``` - 精确计算行偏移量 - 保持图片与原始行的相对位置 - 使用深拷贝确保图片数据完整 ### 输出文件结构示例: ``` 行1: [表格来源: file1.xlsx-Sheet1] <- 表格块标识行 行2: [源信息] | 字段1 | 字段2 | ... | Passport字段 | ... <- 标题行 行3: 数据行1(Passport: ABC123) 行4: 数据行2(Passport: DEF456) ... 行N: [空行分隔] 行N+1: [表格来源: file2.xlsx-Sheet2]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值