数据以不同的形式出现,每种形式都需要不同的存储和分析方法。本指南探讨了三种主要的数据类型:结构化、非结构化和半结构化。
我们将检查它们的特征,查看每种类型的实际示例,并了解像 ClickHouse 这样的现代数据系统如何处理它们。
结构化数据
结构化数据表示以最严格和可预测形式组织的信息。它遵循预定义的模式,其中每个记录都符合相同的格式,具有固定的列集和每个字段的清晰数据类型。虽然单个记录可能包含空值,但整体结构在所有条目中保持一致,使其高度组织化且易于查询。此数据类型通常驻留在关系数据库中,在插入数据之前必须定义表模式。
数据库系统的选择通常取决于预期的工作负载。在线事务处理 (OLTP) 系统处理日常事务,需要快速处理许多小型操作,通常使用像 PostgreSQL 这样的数据库。
相比之下,在线分析处理 (OLAP) 系统专为大型数据集的复杂查询和分析而设计,采用列式数据库,如 ClickHouse。
➡️ 有关 OLAP 和 OLTP 的更多信息,请参阅OLTP 与 OLAP 指南。
我们可以使用变更数据捕获 (CDC) 在这些系统之间同步数据,确保分析数据库保持最新,同时不影响运营性能。
除了传统数据库之外,结构化数据也在不断发展以适应现代存储需求。像 Apache Parquet 这样的开放数据格式已成为存储结构化数据的有效解决方案,尤其是在大数据环境中。这些格式通过按列而不是按行组织数据来优化分析工作负载,从而实现更快的查询性能和更好的压缩。
开放表格式 Apache Iceberg、Delta Lake 或 Apache Hudi 正变得越来越流行。这些表格式管理 Parquet 文件集合并提供额外的功能,如 ACID 事务、时间旅行(访问历史版本)以及高效处理大型数据集的更新和删除的能力。
结构化数据示例
结构化数据出现在许多日常业务运营和系统中。金融交易就是一个完美的例子,其中每个记录都必须包含特定的字段,如交易日期、金额、账号和交易类型。CRM 系统中的客户记录代表了另一个典型的案例,具有联系信息和客户状态的固定字段。产品库存系统跟踪 SKU、名称、价格和库存水平,而员工记录维护 ID、姓名、部门和薪资的统一字段。销售数据、网站用户注册、传感器读数和销售点交易代表了结构化数据的经典示例,其中每个记录都遵循相同的精确格式。
让我们看一个示例销售数据表,以说明结构化数据如何在所有记录中保持一致的列。
销售日期 | 产品 ID | 产品名称 | 数量 | 单价 | 总金额 |
---|---|---|---|---|---|
2024-01-15 | P001 | 游戏显示器 | 2 | 299.99 | 599.98 |
2024-01-15 | P002 | 无线鼠标 | 5 | 49.99 | 249.95 |
2024-01-16 | P001 | 游戏显示器 | 1 | 299.99 | 299.99 |
2024-01-16 | P003 | 机械键盘 | 3 | 129.99 | 389.97 |
2024-01-17 | P002 | 无线鼠标 | 4 | 49.99 | 199.96 |
在本例中,每个记录都遵循相同的结构和相同的列,使其易于计算每种产品的总销售额、查找按数量计算的最受欢迎的项目、按日期分析销售趋势以及比较产品之间的单价。结构化数据的可预测性使其非常适合需要精确记录保存和快速分析的业务运营。
非结构化数据
非结构化数据是不遵循预定义数据模型或模式的信息。它以各种格式出现,并且无法在传统数据库中轻松组织。与可以整齐地放入行和列的结构化数据不同,非结构化数据以各种格式出现,并且无法在传统数据库中轻松组织。
据估计,当今生成的所有数据中约有 90% 属于此类,使其成为现代数据管理的关键考虑因素。
非结构化数据示例
非结构化数据源是多样化的,并且在我们日益数字化的世界中迅速增长。
人工生成的非结构化数据包括日常通信,如电子邮件、文本文档和社交媒体帖子。这些包含自然语言文本,通常带有非正式的写作风格、表情符号和依赖于上下文的含义。其他形式包括电话录音(捕获语音对话)、带有独特速记的短信以及从正式报告到随意笔记的各种文档。
机器生成的非结构化数据提出了其自身的一系列挑战和机遇。CCTV 系统不断生成视频素材,而卫星则产生大量图像数据,这些数据用于从天气预报到城市规划的各个方面。物联网设备和传感器促成了这种数据洪流,生成连续的读数和测量流。系统日志文件虽然在格式上有些结构化,但其条目中通常包含非结构化文本,使其成为需要专门处理的混合情况。
使用非结构化数据
非结构化数据需要专门的处理才能提取有意义的见解。虽然它自然不适合传统的组织方案,但现代技术可以帮助我们发现模式并从各种内容形式(文本、图像、音频或其他媒体类型)中获取价值。该方法因数据类型而异,每种数据类型都需要其专门的工具和技术。
文本处理是非结构化数据分析中最常见和最发达的领域之一。现代系统从基本全文搜索和索引开始,使内容可被发现,但很快通过自然语言处理 (NLP) 进入更复杂的领域。通过 NLP,组织可以自动理解其文本数据中的上下文和含义,识别从人员和组织等关键实体到客户反馈的情感基调的一切内容。文本嵌入的开发彻底改变了该领域,将书面内容转换为捕获语义含义的数值向量,使计算机能够理解概念之间的关系,而不仅仅是匹配关键字。
生成式 AI 和大型语言模型的出现进一步改变了我们处理非结构化文本数据的方式。这些模型可以理解和分析文本,并生成类似人类的响应、总结长篇文档、在语言之间进行翻译,甚至协助内容创作。这种能力为自动化客户服务、内容审核和文档分析开辟了新的可能性,同时也为通过零样本和小样本学习等技术从非结构化文本中提取结构化信息提供了强大的工具。组织现在可以以前所未有的准确性和规模自动生成元数据、创建分类法和对内容进行分类。
视觉和音频数据提出了其独特的处理挑战和机遇。对于图像,现代 AI 方法(如 OpenAI 的 CLIP 算法)创建嵌入,从而实现图像到图像和文本到图像的搜索,有效地弥合了视觉内容和文本内容之间的差距。图像处理还可以通过光学字符识别 (OCR) 提取文本,将屏幕截图、扫描文档和包含文本的照片转换为可搜索的内容。
音频处理也经历了类似的演变,像 OpenAI 的 Whisper 这样的模型以惊人的准确度将语音转换为文本。这种转录可以通过说话人区分来增强,以识别对话中谁在何时说话。语音分析可以检测情绪状态和情感,这对于呼叫中心分析等应用尤其有价值。一旦转换为文本,此音频内容就可以进行上述所有文本处理技术,从而从最初的非结构化源创建丰富的结构化数据。
半结构化数据
在探索了结构化和非结构化数据之后,半结构化数据代表了这些极端情况之间灵活的中间地带。虽然它保留了一些组织元素,但它比严格的结构化格式提供更大的灵活性,同时比完全非结构化数据提供更多的组织性。
半结构化数据的特点是其可变性。与结构化数据(其中每个记录都必须符合固定的模式)不同,半结构化数据允许记录之间存在差异。某些记录可能包含其他记录缺少的字段,并且数据可以嵌套在多个级别。这种灵活性使其特别适合数据并非总是适合整洁的预定义类别的现实场景。
JSON 和 Avro 已成为半结构化数据的主要格式,尤其是在现代 Web 应用程序和 API 中。这些格式允许复杂的层次结构,同时保持人类可读性。例如,JSON 中的客户记录可能包含某些客户的详细购买历史记录、其他客户的社交媒体偏好以及不同级别的联系信息 - 所有这些都在同一数据集中。
应用程序日志记录表明,数据类型之间的界限并非总是清晰的。某些系统实施结构化日志记录,以 JSON 格式生成半结构化输出,其中包含时间戳和严重程度级别等一致字段,但消息内容和元数据可变。其他日志记录系统可能会生成遵循隐式结构的纯文本,但需要解析才能提取有意义的字段。这突出了相同的数据类型如何根据实现选择以不同的形式存在。
现代数据库系统已经发展为更有效地处理半结构化数据。例如,ClickHouse 引入了原生 JSON 数据类型,该类型允许以与传统结构化数据相当的性能查询半结构化数据。这一发展反映了数据管理系统中更广泛的趋势,这些系统适应更灵活的数据格式,同时保持高性能。
半结构化数据示例
半结构化数据在 Web 应用程序、API 和现代数据平台中很常见。电子商务产品目录提供了一个很好的例子,因为不同的产品类别需要不同的属性。社交媒体数据及其不同的内容类型和嵌套评论代表了另一个常见案例。事件跟踪数据、物联网设备读数和应用程序日志经常使用半结构化格式来处理各种数据需求。
以下是 JSON 格式的半结构化产品数据示例,展示了不同的产品如何具有不同的属性
{"id": "P001", "name": "Gaming Monitor", "category": "Electronics", "price": 299.99, "specifications": {"screen_size": "27 inch", "resolution": "2560x1440", "refresh_rate": "165Hz", "panel_type": "IPS"}, "in_stock": true}
{"id": "P002", "name": "Cotton T-Shirt", "category": "Clothing", "price": 19.99, "available_sizes": ["S", "M", "L", "XL"], "colors": ["black", "white", "navy"], "material": "100% cotton"}
{"id": "P003", "name": "Coffee Beans", "category": "Food", "price": 14.99, "weight": "1kg", "origin": "Ethiopia", "roast_level": "Medium", "organic": true, "tasting_notes": ["chocolate", "berry", "citrus"]}
请注意,每个产品都有一些通用字段(id、名称、类别、价格),但也包含特定于类别的属性。电子产品具有技术规格,服装商品具有尺寸和颜色,食品产品具有原产地和品尝笔记等属性。使用传统的结构化数据格式很难实现这种灵活性。
在 ClickHouse 中使用结构化、非结构化和半结构化数据
ClickHouse 可以处理本指南中描述的每种数据类型,尽管它以在结构化数据方面的出色性能而闻名。
ClickHouse 和结构化数据
典型的工作流程涉及从各种来源(通常是 JSON 或 CSV 文件)提取数据,并在提取期间将相关字段提取到强类型列中。这种方法支持对结构化数据进行高性能分析查询。
让我们看一个结构化数据的示例,以 Reddit 评论数据集 的形式。此数据集包含各种不同的字段,我们可以将其存储在以下 ClickHouse 表中
CREATE TABLE reddit
(
subreddit LowCardinality(String),
subreddit_id LowCardinality(String),
subreddit_type Enum(
'public' = 1, 'restricted' = 2, 'user' = 3,
'archived' = 4, 'gold_restricted' = 5, 'private' = 6
),
author LowCardinality(String),
body String CODEC(ZSTD(6)),
created_date Date DEFAULT toDate(created_utc),
created_utc DateTime,
retrieved_on DateTime,
id String,
parent_id String,
link_id String,
score Int32,
total_awards_received UInt16,
controversiality UInt8,
gilded UInt8,
collapsed_because_crowd_control UInt8,
collapsed_reason Enum(
'' = 0, 'comment score below threshold' = 1, 'may be sensitive content' = 2,
'potentially toxic' = 3, 'potentially toxic content' = 4
),
distinguished Enum('' = 0, 'moderator' = 1, 'admin' = 2, 'special' = 3),
removal_reason Enum('' = 0, 'legal' = 1),
author_created_utc DateTime,
author_fullname LowCardinality(String),
author_patreon_flair UInt8,
author_premium UInt8,
can_gild UInt8,
can_mod_post UInt8,
collapsed UInt8,
is_submitter UInt8,
_edited String,
locked UInt8,
quarantined UInt8,
no_follow UInt8,
send_replies UInt8,
stickied UInt8,
author_flair_text LowCardinality(String)
)
ENGINE = MergeTree
ORDER BY (subreddit, created_date, author);
然后我们可以运行以下查询来提取数据
INSERT INTO reddit
SELECT *
FROM s3(
'https://clickhouse-public-datasets.s3.eu-central-1.amazonaws.com/reddit/original/RC_2017-12.xz',
'JSONEachRow'
);
如果我们想计算 2017 年 12 月有多少个唯一的子版块,以下查询可以完成这项工作
SELECT uniqExact(subreddit)
FROM reddit;
┌─uniqExact(subreddit)─┐
│ 91613 │
└──────────────────────┘
1 row in set. Elapsed: 1.572 sec. Processed 85.97 million rows, 367.43 MB (54.71 million rows/s., 233.80 MB/s.)
ClickHouse 和非结构化数据
虽然 ClickHouse 主要为分析工作负载而设计,但在管理非结构化数据方面也可能至关重要。文本内容可以存储在 String 列中,并且当与现代嵌入技术结合使用时,可以成为有效检索系统的一部分。
例如,我们最近创建了一个 Hacker News/StackOverflow 聊天机器人,并将每个 Hacker News 记录的嵌入与文本内容一起存储
CREATE TABLE hackernews
(
`id` String,
`doc_id` String,
`comment` String,
`text` String,
`vector` Array(Float32),
`node_info` Tuple(start Nullable(UInt64), end Nullable(UInt64)),
`metadata` String,
`type` Enum8(
'story' = 1, 'comment' = 2, 'poll' = 3, 'pollopt' = 4, 'job' = 5
),
`by` LowCardinality(String),
`time` DateTime,
`title` String,
`post_score` Int32,
`dead` UInt8,
`deleted` UInt8,
`length` UInt32,
`parent` UInt32,
`kids` Array(UInt32)
)
ENGINE = MergeTree
ORDER BY (toDate(time), length, post_score);
然后,我们可以将相同的嵌入算法应用于用户问题,然后查询 ClickHouse 以查找最相似的记录。
ClickHouse 和半结构化数据
ClickHouse 的原生 JSON 数据类型为处理半结构化数据提供了优雅的解决方案。这种方法在保持半结构化数据的宝贵灵活性的同时实现了高性能。
我们可以创建以下表来提取 BlueSky 社交网络上的活动数据集
CREATE TABLE bluesky.bluesky
(
`data` JSON(SKIP `commit.record.reply.root.record`, SKIP `commit.record.value.value`),
`kind` LowCardinality(String),
`bluesky_ts` DateTime64(6),
`_rmt_partition_id` LowCardinality(String)
)
ENGINE = MergeTree
PARTITION BY toStartOfInterval(bluesky_ts, toIntervalMonth(1))
ORDER BY (kind, bluesky_ts);
然后我们可以编写以下查询来计算最常见的提交事件类型
SELECT data.commit.collection AS collection, count() AS c, uniq(data.did) AS users
FROM bluesky
WHERE kind = 'commit'
GROUP BY ALL
ORDER BY c DESC
LIMIT 10;
┌─collection───────────────┬─────────c─┬───users─┐
│ app.bsky.feed.like │ 269979403 │ 5270604 │
│ app.bsky.graph.follow │ 150891706 │ 5631987 │
│ app.bsky.feed.post │ 46886207 │ 3083647 │
│ app.bsky.feed.repost │ 33249341 │ 1956986 │
│ app.bsky.graph.block │ 9789707 │ 993578 │
│ app.bsky.graph.listitem │ 3231676 │ 102020 │
│ app.bsky.actor.profile │ 1731669 │ 1280895 │
│ app.bsky.graph.listblock │ 263667 │ 105310 │
│ app.bsky.feed.threadgate │ 215715 │ 49871 │
│ app.bsky.feed.postgate │ 99625 │ 19960 │
└──────────────────────────┴───────────┴─────────┘
10 rows in set. Elapsed: 6.445 sec. Processed 516.53 million rows, 45.50 GB (80.15 million rows/s., 7.06 GB/s.)
Peak memory usage: 986.51 MiB.
总结
数据主要有三种形式:结构化、非结构化和半结构化。结构化数据遵循具有预定义列和数据类型的严格模式,使其非常适合传统的业务运营,如销售交易和库存管理。非结构化数据缺乏预定义的组织,涵盖从文本文档到图像和音频文件的所有内容。相比之下,半结构化数据通过 JSON 等格式提供了中间地带,在提供一定组织性的同时保持了灵活性。
像 ClickHouse 这样的现代数据系统已经发展为有效地处理所有三种类型。它们擅长以列式格式处理结构化数据,可以通过原生 JSON 支持处理半结构化数据,并且可以存储非结构化内容,同时通过向量嵌入支持现代检索技术。这种多功能性反映了在保持高性能的同时有效处理所有数据类型的日益增长的需求。