数据结构、表。这两个词随便拆开都看上去平平无奇,合在一起却让不少人在学习、面试、甚至日常开发里栽跟头。我这些年带过不少新人,也帮朋友的公司救过线上事故,几乎所有关于数据组织的问题,最后都能绕回到“表”这个概念上。但“表”从来不只等于C语言课设里的顺序表,也不只等于Excel那个单元格网格——从内存里一块连续数组,到HashMap的桶结构,再到MySQL里一行行业务记录,再到数据透视表拖出来的汇总结果,背后共享的是同一套思维模型:把数据按规则摆放整齐,让访问和操作变得可预测、可高效落地。
这篇文章我想把“表”从微观到宏观完整串一遍,不堆公式,而是站在一个干过底层、写过业务、也带人复习过考研专业课的人的角度,讲清楚每张表背后的为什么。适合正在啃严蔚敏、王道、准备软考和期末的人,也适合每天和MySQL、ABAP、Excel打交道的后端和运营同学。你会发现,表这个东西一旦想通,后面很多零散概念都会自动连成网。
1. 先想清楚一件事:数据结构里的“表”到底在解决什么问题
1.1 所有表都逃不过的三件套:逻辑结构、物理结构、操作集合
数据结构教材一上来就讲逻辑结构和物理结构,很多人觉得抽象,其实没有比这更接地气的概念了。你把一张Excel表打印在纸上,它是一个有行有列的矩形,这是逻辑结构;你把它存进电脑硬盘,可能是某个二进制文件里连续的一段,也可能分散在好几个扇区,这是物理结构。逻辑结构解决“人怎么理解数据”,物理结构解决“机器怎么存放数据”,中间负责衔接的就是操作集合:查找、插入、删除、遍历、排序。
这三个东西拆开想,很多困惑会立刻消失。为什么数组支持随机访问,链表不支持?因为数组的物理结构是连续内存,下标就是偏移量;链表逻辑上连续、物理上东一块西一块,只能靠指针找路。为什么哈希表的查找看着是O(1),却又不能替代数据库索引?因为哈希表的物理组织方式是为“精确匹配”服务的,它无法天然支持范围查找。所有表结构的设计,本质上都是在这三件套之间做取舍。
我在面试新人时特别喜欢问一个问题:给你一个“用户信息”数据集,查找频繁、删除不多,你会怎么组织?很多人第一反应是“用Redis哈希”,却说不清哈希本身是什么。其实不管用什么工具,底层还是在回答三个问题:一行数据长什么样、按什么键找、冲突了怎么处理。能把这三个问题讲清楚,工具选型自然就有了依据。
1.2 为什么几乎一切复杂结构都可以从“表”长出来
线性表这个概念看起来太简单了,以至于很多人低估它。但栈是什么?是只允许在一端操作的线性表。队列是什么?是只允许一端进、另一端出的线性表。字符串是什么?是元素类型为字符的线性表。再到后面的树,你可以理解为“一对多的表”,图则是“多对多的表”。教材里的知识不是一块块孤岛,而是从线性表这一个地基上长出来的。
严蔚敏那本C语言版教材,前几章死磕线性表,很多学生觉得太啰嗦。但我工作多年后的体会是:正是那段“无聊”的练习,决定了你能不能真正理解后面的树、图、查找、排序。因为树和图的遍历实现,底层几乎都依赖“把节点按某种顺序排成一张线性表”的思路,前序、中序、后序、层序,本质上都是对一棵树的线性化过程。
所以如果你现在正在复习数据结构,我建议你暂时放下“这道题会不会考”的功利心,先接纳一个观点:结构千变万化,核心组织思想就那几种,表是其中最基础、也最值得吃透的一种。把线性表想透了,后面的大楼就稳了。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 从顺序表到链表:先把手写实现这件事吃透
2.1 顺序表:一块连续内存上最朴素的“表”
顺序表说白了就是数组,但比数组多了一层“动态管理”的皮。C语言里你得手动管理容量,Java的ArrayList、Python的list、C++的vector,语言替你做了扩容,但底层逻辑一模一样:当元素个数达到容量上限时,重新申请一块更大的连续内存,把旧数据整体搬移过去。
这里面有个很有意思的细节:扩容的倍数一般取1.5倍到2倍,而不是“每次多分配10个”。原因是如果每次扩容只加固定长度,那么连续N次插入的搬移总代价会很高,均摊下来不是常数时间。而按比例扩容,每次扩容后需要重复插入的次数也按比例变多,平摊到每次插入上的“搬家成本”就变成了O(1)。这属于典型的均摊分析思想,考试不见得考,但对理解动态数组非常关键。
顺序表插入元素最需要注意的是位置移动的方向。写一个最简单的版本:
c复制#define INIT_SIZE 4
typedef struct {
int *data;
int length;
int capacity;
} SeqList;
void insert(SeqList *list, int pos, int value) {
// 这里默认 pos 在 [0, length] 范围内
if (list->length == list->capacity) {
list->capacity *= 2;
list->data = (int *)realloc(list->data, list->capacity * sizeof(int));
}
// 从末尾开始,逐个往后挪,给 pos 腾位置
for (int i = list->length; i > pos; i--) {
list->data[i] = list->data[i - 1];
}
list->data[pos] = value;
list->length++;
}
为什么必须从后往前挪?因为如果从前往后覆盖,后面的元素会提前把还没挪走的旧值覆盖掉,数据就丢了。这个细节初学特别容易错,和“把数组整体右移一位”是一样的道理。顺序表的插入平均要移动一半元素,所以插入频繁的场景直接用顺序表并不合适。
2.2 链表:用指针换灵活性,但别忽略三个隐藏代价
链表解决的是顺序表“插入删除要搬数据”这个痛点。既然是靠指针串起来,理论上只要知道前驱节点,插入和删除都是O(1)。但很多人只记住了这个结论,忽略了前提:“已知前驱节点”。如果你只知道要删第k个节点,单链表仍然需要从头遍历到第k-1个节点,复杂度还是O(n)。所以“链表插入删除快”这个话,必须加上“已经定位到目标位置”的限定条件,否则就是典型的半桶水理解。
单链表实现时还有个经典概念题:头指针和头结点。头指针是链表的入口,必须有;头结点是第一个元素节点前面的那个哑节点,可以不有。引入头结点的好处是让空表和非空表的处理逻辑统一起来,插入和删除不再需要单独判断“是不是改头指针”。严蔚敏教材里大量操作都基于带头结点的链表,这个设计目的恰恰是简化代码分支,不是故意为难学生。
我用代码写个最简单的“在prev节点后插入”:
c复制typedef struct Node {
int data;
struct Node *next;
} Node;
Node *insertAfter(Node *prev, int value) {
Node *newNode = (Node *)malloc(sizeof(Node));
newNode->data = value;
newNode->next = prev->next;
prev->next = newNode;
return newNode;
}
注意顺序:一定是先把新节点的next指到prev->next,再修改prev->next指向新节点。反过来写,原后继节点就找不到了,这就是教科书反复强调的“先接后断”。
链表还有两个实际工程里经常被忽略的代价。第一个是空间额外开销,每个节点都要存一个或两个指针,数据量小的时候存储浪费严重。第二个是缓存不友好,节点内存不连续,CPU缓存命中率远低于数组,所以很多真实场景下,ArrayList遍历反而比LinkedList快得多。Redis在列表元素较少时用压缩列表、在较新版本里用listpack,就是出于减少指针开销、提升缓存命中的考虑。这些不会写在考试大纲里,但面试和实战非常爱问。
2.3 顺序表和链表怎么选:一张表看懂取舍
两者没有谁取代谁,只有谁更适合当前场景。我习惯用下面这张表快速决策:
| 维度 | 顺序表 | 链表 |
|---|---|---|
| 随机访问 | O(1),下标直接定位 | O(n),必须遍历 |
| 已知位置插入/删除 | O(n),要搬移元素 | O(1),改指针即可 |
| 空间占用 | 有预分配容量,可能浪费 | 每个节点有额外指针开销 |
| 内存布局 | 连续,缓存友好 | 离散,缓存不友好 |
| 扩容 | 需要搬移整段数据 | 无需整体搬移,按需分配 |
从这个表能得出一个很实用的经验:读多写少、需要按下标访问的场景,选顺序表;写多读少、数据量波动大的场景,选链表。但这个结论只是起点,真正到工程里还要考虑内存上限、GC压力、并发访问粒度。你能把决策背后那套权衡逻辑讲清楚,就是合格的数据结构思维了。
3. 哈希表和树表:把“查找”做快的两种天才思路
3.1 哈希表:完美利用“下标就是地址”的直觉
顺序表的优势是O(1)随机访问,但要求下标是连续整数。现实世界里的主键往往是手机号、用户名、订单号,根本不是从0开始的连续整数。哈希表的核心思想,就是设计一个函数,把这些乱七八糟的键映射成数组下标。你看HashMap源码时如果觉得晕,就死死抓住这句话:它本质上还是一个数组,只是你存入时的那个“下标”,不是你自己算的,而是哈希函数算出来的。
但映射必然有碰撞:两个不同键算出了同一个下标。怎么处理?经典两种办法,拉链法和开放寻址法。Java的HashMap用的是拉链法,每个桶下面挂一条链表,当链表长度超过8且数组长度大于等于64时,链表会转换成红黑树,把极端情况下的查找从O(n)优化到O(log n)。负载因子默认0.75,意思是数组用了75%就触发扩容,这是时间成本和空间成本折中出来的值,不是拍脑袋定的。
哈希表在工程里最容易被忽略的优点是它能省掉大量预计算。你听过的“MD5彩虹表查询”、词频统计里的dict、甚至数码管显示字母的映射表,本质都在用哈希表思维:把某种输入提前映射到某种输出,等真正用的时候直接“查表”拿答案。我并不是说查表法永远正确,但遇到性能瓶颈时,多想想“能不能用离线计算好的表代替在线重复计算”,往往会有惊喜。
哈希表也不是万能钥匙。它天生无序,无法做范围查询,无法按大小排序输出。你要是想在几亿用户里查出手机尾号在某区间的所有人,哈希索引就无能为力了,这不是哈希表本身有bug,而是它的物理结构只为精确匹配服务。
3.2 树表:用层次关系换“有序+高效”的鱼和熊掌
树表这个词,现在可能不如哈希表那么热,但它对应的思想几乎统治了数据库索引。二叉搜索树的规则很简单:左子树都比根小,右子树都比根大,于是查找和有序遍历都能做。问题在于,如果插入数据本身有序,二叉搜索树会退化成一条链表,查找又变回O(n)。为了阻止退化,才出现了AVL树、红黑树这类“自动保持平衡”的树表结构。
工程里最经典的树表案例,是MySQL InnoDB索引用的B+树。很多人不理解:哈希表查找O(1)不是更快吗,为什么数据库不用它?因为数据库查询不止有单条记录精确查找,还有范围查询、排序、GROUP BY。B+树把所有数据都存在叶子节点,并且叶子节点之间用指针串成有序链表,这样范围查询就像遍历线性表一样顺滑。再加上一个节点能存很多键值,树的高度非常低,读一张千万级记录的表做索引查询,通常只要三到四次磁盘IO,代价可控。
所以你看,哈希表和树表并不是同一个问题的两个答案,而是两种不同问题的各自最优解。精确等值匹配请找哈希表;需要范围、排序、前缀匹配,请找树表。很多技术选型吵来吵去,最后比的不是谁更高端,而是谁更匹配真实操作模式。这也是面试官在问“HashMap和B+树有什么区别”时,真正想听到的层次。
3.3 哈希VS树表:一张决策表收尾
| 维度 | 哈希表 | 树表(平衡树/B+树) |
|---|---|---|
| 精确查找 | O(1)平均 | O(log n) |
| 范围查询 | 不支持 | 支持 |
| 有序遍历 | 不支持 | 天然支持 |
| 插入删除 | 平均O(1) | O(log n) |
| 典型应用 | 缓存、字典、词频统计 | 数据库索引、有序集合 |
这张表可以直接背,更重要的是理解背后的那个问题:“你的查询模式,是彻底随机的精确命中,还是带着顺序信息的范围定位?”后面这句话我几乎在每次技术评审里都会问一遍,因为它能帮团队少建很多没用的索引。
4. 当“表”落地到数据库:建表、更新、锁表,全是实战课
4.1 一张高质量业务表是怎么设计出来的
数据结构里的表只活在内存,而现实世界的表大多数活在数据库里。从教课书跳到MySQL建表,很多人的第一个问题就是“怎么建一张好表”。我会从这张最普通的员工表说起:
sql复制CREATE DATABASE IF NOT EXISTS demo DEFAULT CHARSET utf8mb4;
USE demo;
CREATE TABLE emp (
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
emp_no VARCHAR(32) NOT NULL,
name VARCHAR(64) NOT NULL,
base_salary DECIMAL(10,2) NOT NULL DEFAULT 0.00,
dept_id INT UNSIGNED DEFAULT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY uk_emp_no (emp_no),
KEY idx_dept (dept_id)
) ENGINE=InnoDB;
这段建表语句里有几个决策点值得说道说道。主键用自增int而不是UUID,原因很简单:InnoDB的聚簇索引按主键顺序组织数据,自增主键让新记录总是插入到末尾,页分裂概率低;UUID是随机字符串,插入时容易造成大量页分裂和碎片,写入性能会明显掉。当然分布式场景下用雪花算法之类的有序分布式ID也没问题,核心原则是“主键尽量有序且单调递增”。如果业务上希望清空表后id从1开始重新计数,用TRUNCATE比DELETE更快、也会重置自增,因为TRUNCATE会直接重建表而不是逐行删除。
字符集选utf8mb4而不是utf8,是因为MySQL的utf8只支持最多3个字节,像emoji这类4字节字符存不进去;utf8mb4才是真正的“完整UTF-8”。引擎选InnoDB,是为了事务、行级锁和崩溃恢复能力。刚入门时,表格设计只要抓住这几条,已经能规避掉大量线上问题。
关于建表还有一个很容易被忽视的学习材料:Spring Authorization Server官方提供的标准建表SQL。它把OAuth2授权服务器需要的那几张表一次性定义好了,字段命名、索引设计都很规范,值得当范本去读。而硬件工程师在OrCAD里导出网表、在 Allegro 里导入网表,本质上也是通过一张“元器件—网络”的关系表在描述电路连接关系,只不过这张表的载体不是MySQL,而是EDA工具。表这个组织形式,真的到处都是。
4.2 更新数据不一定简单:两个高频业务场景拆解
很多开发写了几年SQL,碰到稍微绕一点的更新需求还是会卡壳。第一个高频问题:怎么用一个表的值更新另外一个表?MySQL和SQL Server写法不一样,MySQL用JOIN直接更新:
sql复制UPDATE emp e
JOIN salary_adjust a ON e.emp_no = a.emp_no
SET e.base_salary = a.new_salary
WHERE a.is_effective = 1;
这里必须理解UPDATE JOIN的执行语义:先把emp表和salary_adjust按emp_no连接起来,得到一张虚拟的中间表,再对中间表里满足条件的行做更新。如果你在SQL Server的习惯是UPDATE emp SET base_salary = a.new_salary FROM emp e JOIN salary_adjust a ON ...,这套语法在MySQL里是报错的,因为MySQL不支持UPDATE...FROM这种写法。跨数据库迁移时这是最容易踩的语法坑。
第二个高频问题更刁钻:“除了某个字段不更新,其余字段都更新怎么办?”比如同步员工资料,但要求保留created_at创建时间不能变。常见做法是用INSERT ... ON DUPLICATE KEY UPDATE,主键冲突时只更新必要字段:
sql复制INSERT INTO emp (id, emp_no, name, base_salary, created_at)
VALUES (1001, 'E1001', '张三', 12000.00, NOW())
ON DUPLICATE KEY UPDATE
emp_no = VALUES(emp_no),
name = VALUES(name),
base_salary = VALUES(base_salary);
这样created_at就不在这次更新里被覆盖,实现了“除了它全更新”的效果。MySQL 8.0.20版本后推荐用别名写法替代VALUES(),但不影响这条SQL的核心思路。你要是用ABAP开发SAP系统,也有类似感受:内表相当于程序内存里的一张表,当你用MODIFY整行覆盖数据库表时,如果业务要求“某字段不动”,必须先读出来改完再整行更新,或者直接用UPDATE SET只指定需要变的列。数据同步里“保留审计字段”几乎是刚需,理解字段级更新和整行替换的差异,能少出很多事故。
4.3 MySQL锁表问题排查速查
锁表是实践中最让人头大的问题之一,明明一条update很慢,整个系统却卡死。常见元凶是事务长期不提交。比如程序里先执行了UPDATE,事务一直没有COMMIT或ROLLBACK,导致这行记录上的排他锁一直不释放,其他会话的更新只能等锁,等待超过innodb_lock_wait_timeout后直接报错。
排查起来其实不复杂,我一般按三步走:
sql复制-- 1. 先看当前所有连接状态,有没有大量 Waiting
SHOW PROCESSLIST;
-- 2. 查当前未提交事务
SELECT * FROM information_schema.innodb_trx\G;
-- 3. 找到线程ID后,确认无误再结束该事务
KILL 12345;
比如一次线上活动更新某张百万行表的排序字段,我当时脑子一热写了整表UPDATE,结果InnoDB要把涉及范围里的所有行都加锁,后面所有请求全部堵住。从那以后我给自己定了一条硬规矩:大批量更新一定要分批,按主键范围限制每次更新行数,比如每次只更新1000条,循环执行到结束为止。UPDATE本身不是错,错的是让一个超大事务长期占用资源。
还有一个高频操作是导出一张表的数据,简单用mysqldump指定单表就行:
bash复制mysqldump -uroot -p demo emp > emp.sql
想给现有表导出一张ER关系图,可以用MySQL Workbench的Reverse Engineer功能连接数据库,工具会自动分析外键关系生成ER图。这活儿不复杂,但能直观看到表之间的关系,跟你在代码里对着字段名猜完全两个效率。
4.4 MySQL锁与并发控制速查表
| 现象 | 可能原因 | 快速处理 |
|---|---|---|
| 更新超时 | 其他事务持有行锁 | 查innodb_trx,kill未提交事务 |
| 整表阻塞 | 无索引更新导致行锁升级 | 给WHERE条件加索引,分批更新 |
| 开启事务后没提交 | 代码忘写COMMIT/ROLLBACK | 补全事务管理,设置超时 |
| DDL卡住 | 元数据锁冲突 | show processlist找到持有MDL的会话并处理 |
这张表不是万能药,但能解决绝大多数“莫名其妙很慢”的运维现场。真正深入的并发控制原理,建议配合官方文档和《高性能MySQL》去啃。数据库锁这块,经验能帮你快速止血,原理能帮你不反复踩坑。
5. 考试与面试视角:严蔚敏、王道、软考里的表该怎么复习
5.1 抓主线:线性表是所有高级结构的地基
如果你正在备考数据结构,这里想给一个更实际的学习路线。很多人一上来就想着天天下载PPT、刷课后题,但我更建议先从“主线”入手。主线是什么?就是线性表。顺序表先写一遍,链表再写一遍,然后把它们改造成栈和队列。有了这个底子,再去看树、图,你会发现树的递归遍历就是借助系统栈,图的DFS/BFS也不过是栈和队列的应用场景。
严蔚敏《数据结构(C语言版)》的经典地位不用多说,但它的代码风格偏教材化,很多地方重逻辑正确、轻工程健壮性。学的时候要带着“我一会儿要在编译器里真跑”的心态去敲,而不是只看不练。王卓老师在网上的数据结构PPT课件、王道考研课、以及各个学校的期末复习资料,适合用来构建知识框架和刷考点;但动手部分,必须回到代码本身。你哪怕只是把线性表的基本操作敲一遍,对“指针”“内存分配”“边界处理”的理解都会上一个台阶。
这期间最推荐的办法是:每学完一个结构,就画一张“逻辑结构图”,把插入、删除时箭头怎么变化标清楚。顺序表就是连续格子里元素的挪动,链表就是带箭头的盒子之间重新连线。图像一旦在脑子里立起来,考试题再变形也不怕。
5.2 表相关的算法题和常见考点
考研、软考和面试都喜欢在这几个点上做文章:
- 顺序表平均移动次数,比如在第i个位置插入元素,平均要移动多少个位置,要会计算;
- 链表指针操作的顺序,比如反转链表、合并两个有序链表,考察的是指针修改不能丢节点;
- 哈希表平均查找长度,冲突率、荷载因子怎么影响查找长度;
- 排序算法在顺序表和链表上的不同表现。
排序算法这一块也逃不开表。冒泡、选择、插入本质上是“对表反复扫描和交换”,快排、归并、堆排序本质上是“把大表不断划分成更小的子表,最后再进行有序合并”。学排序时,不要只背时间复杂度的表格,那样一考变化题就懵。试着在数组上实现一遍快排,在链表上实现一遍归并,你就会发现排序不是孤立知识点,而是对表操作的综合运用。
如果时间紧,按重要性排优先级:线性表必拿,栈和队列必拿,树和二叉树能拿就拿,查找里的哈希/BST/折半查找必拿,排序至少掌握插排、快排、归并、堆排。软考里有不少数据流图和算法题,表面在考形式化描述,实际还是在考你能否把业务里的操作映射到已知的数据结构上。数据结构与算法C语言方向的同学,刷题时尤其不要跳过C语言的手写题,很多院校初试还保留着“不给IDE,纯手写链表操作”的考法。
6. 不只程序员需要“表”:Excel数据透视表与通用的查表思维
6.1 数据透视表就是不写代码的分组聚合
说到“数据结构之表”,如果只讲程序员的世界就太可惜了。运营、财务、销售每天跟Excel打交道,他们口中的数据透视表其实也是数据结构——一张交互式的分组聚合表。SQL里的GROUP BY和Excel数据透视表,底层思想完全一致:选定几列作为“维度”,把剩下的数值列按某种聚合方式(求和、平均、计数)汇总展示。
做一个数据透视表的第一步不是直接拖拽,而是先检查原始数据源。数据必须是一维表,也就是一行一条明细记录,列名要规范,不能出现合并单元格,不能在一个单元格里塞多条信息。表格做成这样,透视表才拖得动。很多人透视表做不出来,经常是因为源数据里有空行、标题重复或者合并单元格,这类问题在数据量大的时候特别难发现。
操作上很简单:鼠标点进数据源任意单元格,然后“插入—数据透视表”,把“月份”拖到行区域,“部门”拖到列区域,“工资”拖到值区域,选择平均值或者求和。你拖动的同时,Excel底层就在执行一次以月份和部门为分组键的聚合。这不就是哈希表按分组键分桶,最后对每个桶做reduce吗?只是你没写代码,Excel替你做了。
还有一个常用需求是“跨表匹配”,很多时候用VLOOKUP就能解决。VLOOKUP的本质是在一张旧表里按员工号查找对应工资,然后返回到新表,这不就是数据库的JOIN吗?比如想做一个员工调薪前后对比表,把调薪前表定义为旧表,调薪后表定义为新表,在新表旁边写:
excel复制=VLOOKUP(A2, 调薪前表!A:B, 2, 0)
意思是用当前行的员工编号,去“调薪前表”的第一列里找完全匹配的那一行,再返回该行第2列的旧工资。要注意VLOOKUP只从左往右查,且两个表的员工编号格式必须完全一致,否则会返回#N/A。对比表列可以这么排:员工编号、旧工资、新工资、差额、调整比例,一眼就能看出谁调得多、谁调得少。
6.2 词频表、寄存器参数表、字模表:它们都是同一个模型
电脑里到处都藏着这种“查表”模型。文本分析中统计单词出现频率,背后是一张“单词—次数”的哈希表;数码管显示里的字母对应表,是用一个驱动值表示具体要显示的字符,本质上是“字符—编码”映射表;摄像头的镜头矫正参数表、游戏修改工具里的CT表、C语言课程里常见的字模表,本质上也都是键值对。区别只在于这张表是放在程序代码里、内存哈希里,还是数据库里。
你会发现一个有意思的现象:只要你理解“表是一组具有相同结构的记录集合”,你就会在几乎每个领域都看到表的影子。词频表是一列存单词、一列存次数;CT表是一列存内存地址、一列存读取方式;寄存器参数表是一列存寄存器号、一列存配置值。它们都有同一个特征:需要一张“键—值”或者“键—多字段”的结构来承载业务关系。
遇到任何一个“感觉很乱”的需求,我都会先用三个问题对齐:每一行代表一个什么实体?每一列描述这个实体的什么属性?接下来要对这张表做什么操作,是精确查找、范围过滤、分组统计还是逐行更新?这三个问题问完,无论是写代码、建数据库表、还是用Excel透视表,方向基本不会跑偏。反过来,如果这些题没想清楚,哪怕工具再熟也容易做成烂表。这是我个人在工作里用过很多次、也带团队用过很多次的一套思考方式。
我把这些经验分享出来,不是想让你背下来哪张表更优、哪个函数怎么用,而是希望你在面对数据问题时,先习惯性问一句:这是哪种表?我该用哪种方式组织它?组织好之后能不能直接查出我要的结果?数据结构不是一门只能应付考试的课,它是一套真正能帮你把混乱变整齐的思维工具。
