快速开始
Example Hotel Business

酒店业务(半结构化数据支持)

背景

在繁忙的纽约市,餐厅的数量众多,从街头小摊到高档餐厅,种类繁多。对于一个数据分析师来说,纽约是一个充满机会和挑战的数据宝藏。通过对餐厅的数据进行深入挖掘,分析师可以为食客提供更具价值的推荐,例如:哪家餐厅的食品安全评分更高、哪家餐厅在特定时间段内顾客评价更好等。

为了进行这样的深入分析,数据分析师需要一个可以同时存储结构化和非结构化数据的数据库,而且必须具有高效的查询性能。传统的关系型数据库在处理结构化数据方面很出色,但在处理如顾客评价、餐厅介绍等非结构化数据时,可能会遇到一些挑战。同样,虽然文档型数据库在处理非结构化数据方面很灵活,但在执行某些复杂查询时可能不如关系型数据库高效。

这时,ProtonBase 数据库显得尤为出色。它不仅完全兼容 PostgreSQL 的语法生态,而且能够完美地结合关系型和文档型数据库的优势。这为数据分析师提供了一个高效、灵活的工具,以帮助他们从海量的餐厅数据中提炼出有价值的信息。

本教程将指导您如何在 ProtonBase 中创建和查询一个纽约餐厅的数据集,并展示其在数据查询和管理上的强大功能。

数据结构与准备的样本数据

您不需要从零开始处理原始的 JSON 数据。为了使您的体验更为流畅,我们已经将数据处理为与表结构相匹配的 CSV 格式。这种格式可以方便地导入 ProtonBase,充分利用其强大的存储和查询功能。

CSV 样本数据:

1   Morris Park Bake Shop  Bronx  Bakery {"coord": [-73.856077, 40.848447], "street": "Morris Park Ave", "zipcode": "10462", "building": "1007"}    [{"date": {"$date": 1393804800000}, "grade": "A", "score": 2}, {"date": {"$date": 1378857600000}, "grade": "A", "score": 6}, {"date": {"$date": 1358985600000}, "grade": "A", "score": 10}, {"date": {"$date": 1322006400000}, "grade": "A", "score": 9}, {"date": {"$date": 1299715200000}, "grade": "B", "score": 14}]   30075445
2  Wendy'S    Brooklyn   Hamburgers {"coord": [-73.961704, 40.662942], "street": "Flatbush Avenue", "zipcode": "11225", "building": "469"} [{"date": {"$date": 1419897600000}, "grade": "A", "score": 8}, {"date": {"$date": 1404172800000}, "grade": "B", "score": 23}, {"date": {"$date": 1367280000000}, "grade": "A", "score": 12}, {"date": {"$date": 1336435200000}, "grade": "A", "score": 12}]    30112340
3  Dj Reynolds Pub And Restaurant Manhattan  Irish  {"coord": [-73.98513559999999, 40.7676919], "street": "West   57 Street", "zipcode": "10019", "building": "351"}   [{"date": {"$date": 1409961600000}, "grade": "A", "score": 2}, {"date": {"$date": 1374451200000}, "grade": "A", "score": 11}, {"date": {"$date": 1343692800000}, "grade": "A", "score": 12}, {"date": {"$date": 1325116800000}, "grade": "A", "score": 12}]    30191841

在开始导入数据之前,请确保您已下载了这份 CSV 文件,并知道其存放路径(例如 /tmp/restaurants.csv)。

预备知识与环境设置

  • 基础的 SQL 语言知识。
  • 已经安装并配置好 ProtonBase 的环境。

主要步骤

1. 创建数据库与数据表

CREATE DATABASE restaurantdb;
 
\c restaurantdb
 
CREATE TABLE restaurants (
   id SERIAL PRIMARY KEY,
   name text NOT NULL,
   borough text,
   cuisine text,
   address JSONB NOT NULL,
   grades JSONB NOT NULL,
   restaurant_id text
);

2. 导入数据

根据前面的处理,我们已将 JSON 数据转化为与表结构相匹配的格式,现在我们可以使用 COPY 命令将其导入。

\copy restaurants from '/tmp/restaurants.csv';

3. 数据查询

  1. 简单查询
SELECT name, borough, cuisine FROM restaurants WHERE borough = 'Brooklyn';
  1. 查询含有特定评级的餐厅
SELECT name, grades FROM restaurants WHERE grades @> '[{"grade": "A"}]';
  1. 查询特定地点的餐厅
SELECT name FROM restaurants WHERE address->>'zipcode' = '11225';

进阶查询教程

  1. 查询某一类型的餐厅,并按名称排序:
SELECT name, borough FROM restaurants WHERE cuisine = 'Hamburgers' ORDER BY name;
  1. 查询所有存在评级数据的餐厅:
SELECT name FROM restaurants WHERE jsonb_array_length(grades) > 0;
  1. 查询所有评级为“A”的餐厅:
SELECT borough, name FROM restaurants WHERE grades @> '[{"grade": "A"}]'::jsonb order by borough;
  1. 查询某一邮编下的餐厅数量:
SELECT address->>'zipcode' AS zipcode, COUNT(*) FROM restaurants GROUP BY address->>'zipcode';
  1. 查询某一评分日期范围内的餐厅:
SELECT name FROM restaurants WHERE grades @> '[{"date": {"$date": 1419897600000}}]'::jsonb;
  1. 查询具有多个评级的餐厅:
SELECT name, grades FROM restaurants WHERE jsonb_array_length(grades) > 1;
  1. 查询某一街道上评分最高的餐厅:
SELECT name, MAX((grades->0->>'score')::int) AS max_score FROM restaurants WHERE address->>'street' = 'Flatbush Avenue' GROUP BY name;

深度探索 ProtonBase:高级查询与数据分析

在前面的章节中,我们介绍了如何使用 ProtonBase 设置数据结构并导入数据。但数据的真正价值来自于我们如何查询和分析它。ProtonBase 的魅力不仅仅是它可以轻松地存储关系型和文档型数据,还在于它可以高效地执行复杂查询,特别是涉及 JSON 数据的查询。

在本章节中,我们将深入探索一些高级查询技巧。这些技巧将展示如何充分利用 ProtonBase 的能力,对 restaurants 表中的数据进行深入的数据挖掘和分析。这些查询将帮助数据分析师们更好地理解餐厅的分布、菜系的受欢迎程度、评分趋势等多种维度的信息。让我们开始吧!

  1. 聚合查询: 找出每种菜系下的餐厅数量。
SELECT cuisine, COUNT(*) as restaurant_count
FROM restaurants
GROUP BY cuisine
ORDER BY restaurant_count DESC;
  1. JSON 操作: 找出所有位于指定坐标范围内的餐厅。
SELECT name, address->>'street' as street
FROM restaurants
WHERE (address->'coord'->>0)::float8 BETWEEN -73.97 AND -73.95
AND (address->'coord'->>1)::float8 BETWEEN 40.66 AND 40.67;
  1. 深度 JSON 查询:找出所有评分超过 90 的餐厅。
SELECT name
FROM restaurants
WHERE EXISTS (
  SELECT 1
  FROM jsonb_array_elements(grades) grade
  WHERE (grade->>'score')::int > 90
);
  1. 使用 JSON 聚合函数:找出平均评分最高的 5 家餐厅。
SELECT
    name,
    AVG((grade->>'score')::float) as average_score
FROM
    restaurants, jsonb_array_elements(grades) as grade
WHERE
    grade->>'score' IS NOT NULL
GROUP BY
    name
HAVING
    AVG((grade->>'score')::float) IS NOT NULL
ORDER BY
    average_score DESC
LIMIT 5;
  1. 日期与时间操作: 分析某个时间段内,得分最高的 5 家餐厅。
SELECT
    name,
    borough,
    cuisine,
    grades->0->>'score' AS score
FROM
    restaurants
WHERE
    (grades->0->>'score') IS NOT NULL
AND
    (grades->0->'date'->>'$date')::bigint / 1000 BETWEEN EXTRACT(EPOCH FROM '2015-01-01'::timestamp) AND EXTRACT(EPOCH FROM '2015-02-28'::timestamp)
AND
    grades->0->'date'->>'$date' IS NOT NULL
ORDER BY
    (grades->0->>'score')::int DESC
LIMIT 5;

深入探索 ProtonBase:JSONB 索引与优化

随着文档型数据在现代应用中的广泛应用,能够有效地查询和索引 JSON 结构变得越来越重要。特别是在 ProtonBase 这样的高性能数据库中,利用 jsonb 数据类型及其索引能力,可以为我们提供前所未有的查询优势。在本章中,我们将集中探讨如何最大化地利用 jsonb 的索引能力,以及如何针对它进行查询优化,确保您的查询始终处于最佳状态。

在我们的场景中,一个数据分析师正在研究众多餐厅的信息,这些信息储存于 restaurants 表中,其中 addressgrades 两个字段使用了 jsonb 数据类型。为了快速地查询和分析这些文档型数据,我们需要对其进行合理的索引。

JSONB 索引类型

ProtonBase 提供了几种不同的 jsonb 索引类型,其中最常用的是 GIN(Generalized Inverted Index)索引。它特别适用于包含多个键值对、数组和嵌套结构的 jsonb 列。

为了提高查询效率,我们可以为 addressgrades 字段创建 GIN 索引:

CREATE INDEX idx_address_gin ON restaurants USING GIN(address);
CREATE INDEX idx_grades_gin ON restaurants USING GIN(grades);

使用索引进行查询

有了 GIN 索引后,我们可以更高效地执行包含 jsonb 字段的查询。例如,要找到所有在 Brooklyn 地区并且得分为"A"的餐厅,我们可以这样查询:

SELECT name, borough
FROM restaurants
WHERE borough = 'Brooklyn' AND grades @> '[{"grade": "A"}]';

这里的 @>jsonb 的包含操作符,用于检查左侧的 jsonb 值是否包含右侧的 jsonb 值。

查询优化建议

  • 当查询 jsonb 字段时,使用 ProtonBase 提供的提供的 jsonb 操作符和函数,这些都已经为 GIN 索引优化。
  • 避免使用转换函数,例如 ::text,因为这些函数会避免使用索引并导致全表扫描。
  • 定期使用 EXPLAIN 命令检查查询的执行计划,以确保它们正在有效地使用索引

在 ProtonBase 这种高性能数据库中,通过合理地使用 jsonb 的索引,可以确保文档型数据的查询效率与传统关系型数据相媲美,甚至在某些情况下更优。结合 ProtonBase 的特性,数据分析师可以在不牺牲查询性能的情况下,灵活地分析和探索文档型数据。

深入探索 ProtonBase:使用 JSONPath 查询餐厅信息

使用 jsonpath,我们可以更加灵活和直观地查询 jsonb 数据。进行复杂查询时,关键是理解其内部工作原理以及如何与 JSON 结构进行交互。以下是一些复杂查询示例及其详细解释:

1. 查找至少有一次评级为"A"且分数超过 15 的餐厅

SELECT name
FROM restaurants
WHERE grades @? '$[*] ? (@.grade == "A" && @.score > 15)';

此查询使用了 [*] 通配符来遍历 grades 数组中的每一个元素。? 操作符后的条件用于筛选符合条件的元素。@ 符号代表当前遍历到的元素。

2. 查找所有没有评级为 "B" 的餐厅

SELECT name
FROM restaurants
WHERE NOT grades @? '$[*] ? (@.grade == "B")';

这里使用了 NOT 关键字与存在操作符 @? 结合,确保没有任何评级为"B"的元素。

3. 查找街道名称中包含 "Avenue" 但不是 "Flatbush Avenue" 的餐厅

SELECT name
FROM restaurants
WHERE address @? '$ ? (@.street like_regex "Avenue" flag "i" && !(@.street == "Flatbush Avenue"))';

like_regex 操作符用于正则表达式匹配,flag "i" 表示匹配是不区分大小写的。! 符号表示逻辑“非”。

4. 查询指定日期范围内评级为"A"的餐厅

SELECT name
FROM restaurants
WHERE grades @? '$[*] ? (@.grade == "A" && @.date."$date" >= 1419897600000 && @.date."$date" <= 1422748800000)';

此查询旨在筛选评级为"A"且日期在指定范围内的评级。首先,我们使用 [*] 通配符来遍历每一个 grades 数组中的元素。然后,我们使用 ? 操作符后的条件进行筛选,其中 @ 符号代表当前遍历到的元素。我们检查每一个评级的 grade 属性是否为"A",以及其 date.$date 属性是否在我们的目标日期范围内。

5. 查找评分中有超过 90 分的餐厅及其对应分数

SELECT
    name,
    (jsonb_path_query_first(grades, '$[*] ? (@.score > 90)')->>'score')::int AS score_above_90
FROM
    restaurants
WHERE
    grades @? '$[*] ? (@.score > 90)'
ORDER BY
    score_above_90 DESC;

整个 JSONPath 表达式 $[*] ? (@.score > 90) 的作用是:遍历 grades 数组中的每个评分对象,并选取其中 score 属性大于 90 的对象。

jsonpath 为数据分析师提供了一个强大的工具,使其能够灵活地查询嵌套和结构化的 JSON 数据,无需使用复杂的 JSON 函数和操作符。结合 ProtonBase 的高性能特性,jsonpath 可以帮助数据分析师快速地获取他们需要的信息,从而提高他们的生产力。

总结与进一步学习

通过这篇教程,我们深入了解了 ProtonBase 如何完美结合关系型和文档型的数据存储和查询能力。有了这些知识,您可以更加灵活地处理各种复杂数据,为您的数据分析工作提供强大的支持。 对于进一步学习,您可以参考 ProtonBase 的官方文档,了解其更多高级功能和特性。