41

OLAP数仓入门:基础篇

 3 years ago
source link: http://mp.weixin.qq.com/s?__biz=MzU1NTMyOTI4Mw%3D%3D&%3Bmid=2247508303&%3Bidx=2&%3Bsn=b64e91588851c43a78db6c6fddad4400
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

3YjU7jI.png!mobile

文章作者:温正湖 网易易数

内容来源:数据库内核@知乎专栏

导读: 近七年在网易杭研一直从事数据库相关的开发工作,主要是MySQL和MongoDB这两种数据库,去年开始涉及图数据库Neo4J。上述几种,都可认为是OLTP类数据处理,由于工作需要,需要调研学习OLAP技术和相关系统,本文开始逐步进行第一轮总结,很多东西还只是片面理解,权当做个笔记。

对事物的认识总是螺旋式的,先有个大致的认识,再逐渐丰富其血肉。这个过程,会有片面性、也可能否定之前的理解,但只要一直用心用脑,总会不断进步的。当然,多参考优秀的资料,会大大缩短过程中花费的时间。

01

基础问答

1. 什么是OLAP,其与OLTP有什么区别?

如果展开来说,这个问题估计可以写好几篇文章,这里简单谈谈个人的理解。

OLTP是Online transaction processing的英文缩写,指在线/联机事务处理,这么说其实还是比抽象的。OLTP典型的应用领域包括银行、证劵等金融行业,电子商务系统等,在此举最经典的银行例子,我们在招商银行APP上查询账户余额、收支信息和转账记录,在ATM机上存钱,取钱,将招行账号的钱转到工行账号上。这些都是典型的OLTP类操作,这些操作都比较简单,主要是对数据库中的数据进行增删改查。操作主体一般是产品的用户。

OLAP是Online analytical processing的英文缩写,指联机分析处理。从字面上我们能看出是做分析类操作。通过分析数据库中的数据来得出一些结论性的东西。比如给老总们看的报表,用于进行市场开拓的用户行为统计,不同维度的汇总分析结果等等。操作主体一般是运营、销售和市场等团队人员而不是用户。

单次OLTP处理的数据量比较小,所涉及的表非常有限,一般仅一两张表。而OLAP是为了从大量的数据中找出某种规律性的东西,经常用到count()、sum()和avg()等聚合方法,用于了解现状并为将来的计划/决策提供数据支撑,所以对多张表的数据进行连接汇总非常普遍。

为了表示跟OLTP的数据库(database)在数据量和复杂度上的不同,一般称OLAP的操作对象为数据仓库(data warehouse),简称数仓。数据库仓库中的数据,往往来源于多个数据库,以及相应的业务日志。

下表是对OLTP和OLAP的简单总结。

nqiIviu.jpg!mobile

网易杭研OLTP数据库团队为业界培养了多位数据库资深专家,在线数仓团队也同样如此,在Impala、Kudu等技术上有深厚的积累,是网易猛犸、有数等网易大数据产品的核心基础设施。

2. MySQL等OLTP数据库能处理OLAP业务吗?

MySQL是当前最流行的开源数据库,一般作为OLTP数据库使用。在MySQL上也能执行一些OLAP操作,但这不是MySQL擅长的领域。虽然OLTP和OLAP都是通过SQL来执行,但SQL语句只是描述了我想要什么,而并没有说明应该怎么做(不考虑hint等),即确定最优的执行计划。由于OLTP操作比较简单,所涉及的表也少,因此不需要相应的数据库具有强大的执行优化能力,比如说MySQL在查询优化这块就比较弱,但这其实没有给它的大规模普及使用造成多大伤害。

当然,MySQL也在快速进步,尤其是最新的8.0版本,在查询优化模块添加了很多众望所归的功能特性,包括窗口函数,通用表达式和更强大的Join能力等。

而OLAP类操作不一样,没有强大的执行计划产生和优化能力,执行这类操作肯定不会有多高的效率,甚至会寸步难行。当然,如果总数据量较小,SQL也相对简单,那MySQL也是能够应付的。在MySQL高可用实例的从库做些报表类查询也有不少案例。

3. OLAP的查询跟OLTP查询具体有那些不一样?

上文简要提及,OLTP查询一般仅涉及单表,点查为主,返回的是记录本身或该记录的多个列。即使是范围查询,基本上也会通过limit来限制返回的记录数。

而OLAP则不同,表中单条记录本身并不是查询所关心的,比较典型的特点包括有聚合类算子、涉及多表Join,查询所用谓语/条件没有索引,玩玩不是返回记录。由于这些操作都非常耗计算资源,而且数据仓库相比数据库在数据量上大很多,因此,OLAP类查询经常表现为cpu-bound而不是io-bound。

这样说可能还不够直观,下面换种形式。OLTP和OLAP发展到现在已经比较成熟,业界也有些公认的benchmark来进行性能评估。我们可以通过这些benchmark中的对应sql来了解两位服务的典型查询语句。对于OLTP来说,有sysbench和tpcc测试套件,对于OLAP来说,有tpch和tpcds 2种。这里分别例举sysbench oltp和tpcds的sql作为参考。

sysbench oltp查询

可以从sysbench的lua脚本中获取都有哪些查询类型。如下所示:

local stmt_defs = {
   point_selects = {
      "SELECT c FROM sbtest%u WHERE id=?",
      t.INT},
   simple_ranges = {
      "SELECT c FROM sbtest%u WHERE id BETWEEN ? AND ?",
      t.INT, t.INT},
   sum_ranges = {
      "SELECT SUM(k) FROM sbtest%u WHERE id BETWEEN ? AND ?",
       t.INT, t.INT},
   order_ranges = {
      "SELECT c FROM sbtest%u WHERE id BETWEEN ? AND ? ORDER BY c",
       t.INT, t.INT},
   distinct_ranges = {
      "SELECT DISTINCT c FROM sbtest%u WHERE id BETWEEN ? AND ? ORDER BY c",
      t.INT, t.INT},

对应到测试时,就是下面的样子。

SELECT c FROM sbtest10 WHERE id=4352
SELECT c FROM sbtest10 WHERE id BETWEEN 5046 AND 5046+99 ORDER BY c
SELECT c FROM sbtest3 WHERE id BETWEEN 4983 AND 4983+99
SELECT SUM(K) FROM sbtest1 WHERE id BETWEEN 4981 AND 4981+99
SELECT DISTINCT c FROM sbtest3 WHERE id BETWEEN 4989 AND 4989+99 ORDER BY c

感兴趣的同学可以查看github上sysbench代码。上述sql均位于oltp_common.lua中。

https://github.com/akopytov/sysbench/blob/master/src/lua/oltp_common.lua

同样的,我们也可以从github上找到tpcc的查询sql。如下:

https://github.com/Percona-Lab/sysbench-tpcc/blob/master/tpcc_run.lua
例子如下:
--      SELECT c_id
--      FROM customer
--      WHERE c_w_id = :c_w_id
--      AND c_d_id = :c_d_id
--      AND c_last = :c_last
--      ORDER BY c_first;

--      SELECT c_balance, c_first, c_middle, c_last
--              FROM customer
--              WHERE c_w_id = :c_w_id
--          AND c_d_id = :c_d_id
--          AND c_id = :c_id;

--  SELECT c_discount, c_last, c_credit, w_tax
--  FROM customer, warehouse
--  WHERE w_id = :w_id
--  AND c_w_id = w_id
--  AND c_d_id = :d_id
--  AND c_id = :c_id;

相对来说,tpcc的查询比oltp查询更复杂些。包含了2表join操作。

tpcds查询

下面在看看复杂的tpcds查询是怎么样的。tpcds一共99个query,下面举例。

-- query68
SELECT
  "c_last_name"
, "c_first_name"
, "ca_city"
, "bought_city"
, "ss_ticket_number"
, "extended_price"
, "extended_tax"
, "list_price"
FROM
  (
   SELECT
     "ss_ticket_number"
   , "ss_customer_sk"
   , "ca_city" "bought_city"
   , "sum"("ss_ext_sales_price") "extended_price"
   , "sum"("ss_ext_list_price") "list_price"
   , "sum"("ss_ext_tax") "extended_tax"
   FROM
     ${database}.${schema}.store_sales
   , ${database}.${schema}.date_dim
   , ${database}.${schema}.store
   , ${database}.${schema}.household_demographics
   , ${database}.${schema}.customer_address
   WHERE ("store_sales"."ss_sold_date_sk" = "date_dim"."d_date_sk")
      AND ("store_sales"."ss_store_sk" = "store"."s_store_sk")
      AND ("store_sales"."ss_hdemo_sk" = "household_demographics"."hd_demo_sk")
      AND ("store_sales"."ss_addr_sk" = "customer_address"."ca_address_sk")
      AND ("date_dim"."d_dom" BETWEEN 1 AND 2)
      AND (("household_demographics"."hd_dep_count" = 4)
         OR ("household_demographics"."hd_vehicle_count" = 3))
      AND ("date_dim"."d_year" IN (1999   , (1999 + 1)   , (1999 + 2)))
      AND ("store"."s_city" IN ('Midway'   , 'Fairview'))
   GROUP BY "ss_ticket_number", "ss_customer_sk", "ss_addr_sk", "ca_city"
)  dn
, ${database}.${schema}.customer
, ${database}.${schema}.customer_address current_addr
WHERE ("ss_customer_sk" = "c_customer_sk")
   AND ("customer"."c_current_addr_sk" = "current_addr"."ca_address_sk")
   AND ("current_addr"."ca_city" <> "bought_city")
ORDER BY "c_last_name" ASC, "ss_ticket_number" ASC
LIMIT 100
--query53
SELECT *
FROM
  (
   SELECT
     "i_manufact_id"
   , "sum"("ss_sales_price") "sum_sales"
   , "avg"("sum"("ss_sales_price")) OVER (PARTITION BY "i_manufact_id") "avg_quarterly_sales"
   FROM
     ${database}.${schema}.item
   , ${database}.${schema}.store_sales
   , ${database}.${schema}.date_dim
   , ${database}.${schema}.store
   WHERE ("ss_item_sk" = "i_item_sk")
      AND ("ss_sold_date_sk" = "d_date_sk")
      AND ("ss_store_sk" = "s_store_sk")
      AND ("d_month_seq" IN (1200   , (1200 + 1)   , (1200 + 2)   , (1200 + 3)   , (1200 + 4)   , (1200 + 5)   , (1200 + 6)   , (1200 + 7)   , (1200 + 8)   , (1200 + 9)   , (1200 + 10)   , (1200 + 11)))
      AND ((("i_category" IN ('Books                                             '         , 'Children                                          '         , 'Electronics                                       '))
            AND ("i_class" IN ('personal                                          '         , 'portable                                          '         , 'reference                                         '         , 'self-help                                         '))
            AND ("i_brand" IN ('scholaramalgamalg #14                             '         , 'scholaramalgamalg #7                              '         , 'exportiunivamalg #9                               '         , 'scholaramalgamalg #9                              ')))
         OR (("i_category" IN ('Women                                             '         , 'Music                                             '         , 'Men                                               '))
            AND ("i_class" IN ('accessories                                       '         , 'classical                                         '         , 'fragrances                                        '         , 'pants                                             '))
            AND ("i_brand" IN ('amalgimporto #1                                   '         , 'edu packscholar #1                                '         , 'exportiimporto #1                                 '         , 'importoamalg #1                                   '))))
   GROUP BY "i_manufact_id", "d_qoy"
)  tmp1
WHERE ((CASE WHEN ("avg_quarterly_sales" > 0) THEN ("abs"((CAST("sum_sales" AS DECIMAL(38,4)) - "avg_quarterly_sales")) / "avg_quarterly_sales") ELSE null END) > DECIMAL '0.1')
ORDER BY "avg_quarterly_sales" ASC, "sum_sales" ASC, "i_manufact_id" ASC
LIMIT 100
--query59
WITH
  wss AS (
   SELECT
     "d_week_seq"
   , "ss_store_sk"
   , "sum"((CASE WHEN ("d_day_name" = 'Sunday   ') THEN "ss_sales_price" ELSE null END)) "sun_sales"
   , "sum"((CASE WHEN ("d_day_name" = 'Monday   ') THEN "ss_sales_price" ELSE null END)) "mon_sales"
   , "sum"((CASE WHEN ("d_day_name" = 'Tuesday  ') THEN "ss_sales_price" ELSE null END)) "tue_sales"
   , "sum"((CASE WHEN ("d_day_name" = 'Wednesday') THEN "ss_sales_price" ELSE null END)) "wed_sales"
   , "sum"((CASE WHEN ("d_day_name" = 'Thursday ') THEN "ss_sales_price" ELSE null END)) "thu_sales"
   , "sum"((CASE WHEN ("d_day_name" = 'Friday   ') THEN "ss_sales_price" ELSE null END)) "fri_sales"
   , "sum"((CASE WHEN ("d_day_name" = 'Saturday ') THEN "ss_sales_price" ELSE null END)) "sat_sales"
   FROM
     ${database}.${schema}.store_sales
   , ${database}.${schema}.date_dim
   WHERE ("d_date_sk" = "ss_sold_date_sk")
   GROUP BY "d_week_seq", "ss_store_sk"
)
SELECT
  "s_store_name1"
, "s_store_id1"
, "d_week_seq1"
, ("sun_sales1" / "sun_sales2")
, ("mon_sales1" / "mon_sales2")
, ("tue_sales1" / "tue_sales2")
, ("wed_sales1" / "wed_sales2")
, ("thu_sales1" / "thu_sales2")
, ("fri_sales1" / "fri_sales2")
, ("sat_sales1" / "sat_sales2")
FROM
  (
   SELECT
     "s_store_name" "s_store_name1"
   , "wss"."d_week_seq" "d_week_seq1"
   , "s_store_id" "s_store_id1"
   , "sun_sales" "sun_sales1"
   , "mon_sales" "mon_sales1"
   , "tue_sales" "tue_sales1"
   , "wed_sales" "wed_sales1"
   , "thu_sales" "thu_sales1"
   , "fri_sales" "fri_sales1"
   , "sat_sales" "sat_sales1"
   FROM
     wss
   , ${database}.${schema}.store
   , ${database}.${schema}.date_dim d
   WHERE ("d"."d_week_seq" = "wss"."d_week_seq")
      AND ("ss_store_sk" = "s_store_sk")
      AND ("d_month_seq" BETWEEN 1212 AND (1212 + 11))
)  y
, (
   SELECT
     "s_store_name" "s_store_name2"
   , "wss"."d_week_seq" "d_week_seq2"
   , "s_store_id" "s_store_id2"
   , "sun_sales" "sun_sales2"
   , "mon_sales" "mon_sales2"
   , "tue_sales" "tue_sales2"
   , "wed_sales" "wed_sales2"
   , "thu_sales" "thu_sales2"
   , "fri_sales" "fri_sales2"
   , "sat_sales" "sat_sales2"
   FROM
     wss
   , ${database}.${schema}.store
   , ${database}.${schema}.date_dim d
   WHERE ("d"."d_week_seq" = "wss"."d_week_seq")
      AND ("ss_store_sk" = "s_store_sk")
      AND ("d_month_seq" BETWEEN (1212 + 12) AND (1212 + 23))
)  x
WHERE ("s_store_id1" = "s_store_id2")
   AND ("d_week_seq1" = ("d_week_seq2" - 52))
ORDER BY "s_store_name1" ASC, "s_store_id1" ASC, "d_week_seq1" ASC
LIMIT 100

很显然,tpcds的查询复杂度相比oltp和tpcc高非常多。

4. 是否有可能将OLAP和OLTP统一起来?

目前有个趋势是将OLTP和OLAP相融合,在同一个系统中同时提供TP和AP 2种服务,即HTAP产品,国内的数据库创业公司PingCAP的TiDB即是其中的佼佼者。

但由于两者服务类型相差甚大,完全融合是很难的,如何解决AP业务对要求更高实时性和稳定性的TP业务带来影响,如何同时提供2种服务且2种服务与业界其他系统相比具备足够竞争力,这些都是很大的挑战。

在目前的HTAP系统中,一般通过存储层的数据多副本来进行针对AP和TP业务的不同方式的优化,使用多个副本来以行存方式更好满足TP业务,通过增加一个副本来以列存方式为AP业务提供服务。

在存储系统上,配置独立的计算/查询系统,分别满足TP和AP不同的要求。比如TP系统很重要的一个特点就是事务的ACID,而AP系统更加关心分布式并行查询能力。

TP和AP融合不是本系列文章关注焦点,因此下面我们聚焦到OLAP/数仓上来。

5. 数仓有哪些基础知识和概念?

OLAP的查询语句比OLTP更复杂,显然是因为两则操作的数据集和目的都是不一样的。数据库模型是2维的关系-实体模型。而数仓则是多维立方体模型。相对来说,给数仓建模的难度更高。为此,有必要再介绍下输出基础知识和一些重要概念。

先来看看这张图,基于该图,介绍下数仓的数据来源,作用和存在方式。

IFZVFbv.jpg!mobile

02

说说数仓中数据的前世今生?

1. 数仓中的数据从何而来?

OLAP对应的数据载体叫做数据仓库,称之为仓库个人认为挺贴切的。因为它不是数据的生产者,其中的数据都是从其他地方搬运过来的,而搬运和清洗的过程就是ETL流程(Extract-Transform-Load,即数据抽取、转换和加载),在此不展开。

vMJR3er.jpg!mobile

图片来源:维基百科

那么这些数据从何而来,表现形式如何呢?归纳起来大体有3种:

  • 结构化数据:一般来自于数据库,比如MySQL等关系型数据库的表中保存的记录(rows)。即承担OLTP功能的数据载体。这类数据最好处理,因为数据表达方式作为规范,约束性最好;

  • 半结构化数据:该部分数据来源较多,包括用户行为日志(如app的页面访问记录)、平台或管理服务日志(tomcat、mysql等服务日志)等等,也包括存储于MongoDB等NoSQL数据库中的记录(Docs等)。这些数据一般以Json或XML等形式存在,在ETL时难度较大。

  • 非结构化数据:包括图片、音频、视频和网页等,这些数据非常复杂,信息量也很大,一般不会直接抽取出来直接保存到数仓中,而是记录他们的元数据信息(metadata),举图片为例,可能保存该图片的产生时间、格式、大小等等,至于图片本身,一般通过url链接保存在对象或文件存储系统中。

2. 数仓的作用有哪些?

数据仓库大致可以分为以下一些作用:

  • 进行交互式/即席查询(ad-hoc);

  • 用于报表类查询(BI Reporting);

  • 进行数据分析类查询(Data Analytics);

  • 用于数据挖掘类查询(Data Mining);

在数仓仓库之前可以部署至少如上所述4类数据应用。

3. 数据在数仓中是如何组织的?

简单介绍了数仓的数据来源,数仓中数据所能发挥的作用后,接下来聊聊这些通过不同方式进来的数据,如何存在于数仓当中的。相应地引入多维数据模型和数据立方体(data cube)概念。数仓中数据的存在方式跟数仓索要发挥的作用息息相关,即该数仓要承载什么样的业务模型。

基于业务模型设计对应的数据仓库的数据模型,进而针对性实现不同的ETL操作将外部数据经过不同程度的过滤、聚合等处理之后引入到数仓之中。

4. 什么是多维数据模型?

抽象的概念光通过文字描述是无法在大脑中具象化的,这是因为大自然存在的都是具体的事物,抽象的东西是竟然我们加工所得。为了更加清晰的进行说明,需要将抽象概念重新具体化。下面就通过例子来说明与数仓多维数据模型相关的概念,以便大家更好得建立初步的认识。

FNjAF3.jpg!mobile

上图所示即为一个采用简单星型模型组织起来的多维数据模型,用来存储商品销售情况。在这张图中的6个表又可分为2种类型,分别是最中间的事实表,和围绕其展开的维度表。

5. 什么是事实表?

事实表(Fact Table)用来记录具体事件,包含了每个事件的具体要素,以及具体发生的事情。事实表是主干,简明扼要得介绍一个事实。例子中就通过一条事实表记录说明了某个地方(地域ID)的某人(用户ID)在某个时间(时间ID)通过某种方式(支付ID)买了某产品(产品ID)。

6. 什么是维度表?

维度表(Dimension Table )是依赖事实表而存在的,“皮之不存,毛将焉附”,没有事实表数据,维度表也就没有存在的意义。每个维度表都是对事实表中的每个列/字段进行展开描述。

比如事实表中的用户ID,就可以进一步展开成一张维度表,记录该用户ID实体的用户名、联系信息、地址信息、年龄、性别和注册方式等等;

一般来说,对于数仓,事实表的增删改操作相比维度表更为频繁,模型建立后,维度表中的数据保持相对稳定。试想,商品销售行为是一直在发生的,而用户注册和产品更新不总是随时有的。再说到地域和支付方式,那就更少变化了。

通过事实表和维度表组织起来的数仓多维数据模型,相比原本分散在数据库等各处的数据,能够有更有目的更高效的查询效率,比如可以查询汇总地域维度中某个省的商品销售情况,也可以通过时间维度分析每个季度的某类商品销售趋势。将多个维度表跟事实表进行不同程度的连接,可以展开得到各种各样的分析结果,满足商品运营等数据使用者的不同需求。

基于数据模型及操作又可以引入数据立方体概念及对其的常见操作。

7.什么是数据立方体?

中国作为信息技术领域的后起之秀,我们现在介绍的这些概念都源于英文。数据立方体就是从英文“Data Cube”而来。下图就是一个商品销售模型的数据立方体。

ui2Ufey.jpg!mobile

来源:http://webdataanalysis.net/web-data-warehouse/data-cube-and-olap/

其实我们也可以叫它”数据魔方体“,因为立方体是三维的,而多维数据模型并不仅仅三维,虽然受图形化展示限制,一般仅展示其三个维度。而”魔方“一词,则凸现出了其变化性,通过对其进行不同的操作,让数据呈现出千变万化的结果。

上图来源于参考资料,比较好展示了多维模型,从大立方体上可以看到商品类型、季度和地区三个维度。但对于每个维度又是一个小立方体,比如第一季度浙江的书籍销售情况就是左下角的小立方体。在这个小立方体中,根据需要,我们还可以按照书籍类型,从季度拆分为月度,浙江拆出各地级市。

上面的拆分例子正是基于立方体的场景操作之一,下面进一步介绍。

8. 数据立方体有哪些常见操作?

在进行OLAP查询时,基于数据立方体的多维分析操作包括:钻取(Drill-down)、上卷(Roll-up)、切片(Slice)、切块(Dice)以及旋转(Pivot),接下来以上面的数据立方体为例来逐一解释下:

InUJbmU.jpg!mobile

钻取(Drill-down):

该操作我们上面简单举过例子,从钻取这个名字,就可以知道,这是往更细粒度深挖。从上一个层次到下一层,即深入该层内部。比如书籍中可以分计算机、数理化、文史地等,二季度又可分为4、5、6三个月,浙江省又可以分为杭、甬、温等地级市的销售数据。

iQJVR3i.jpg!mobile

上卷(Roll-up):

与钻取往深度挖相反,上卷顾名思义,即从细粒度数据向上层聚合,如将江苏省、上海市和浙江省的销售数据进行汇总来查看江浙沪地区的销售数据,将2010年的四个季度汇总成2010年的总数据;将电子产品、日用品和书籍汇总成实体商品,与服务相对应。

上面的钻取和上卷通过摊薄和加厚来改变维度的粒度。接下来介绍的切片和切块相似,是对维度进行筛选,获取其中一部分相同的样本。

EBv2maM.jpg!mobile

切片(Slice):

如左图所示,切片就是选择维中特定的值进行分析,比如只选择电子产品的销售数据,或2010年第二季度的数据,或浙江一个省粒度进行分析。

切块(Dice):

如右图所示,切块是选择维中特定区间的数据或者某批特定值进行分析,比如选择2010年第一季度到2010年第二季度的销售数据,或者是电子产品和日用品的销售数据。

与切片不同的是,切块的粒度更大,会选择一个维度中某个区间或范围的值,而不仅仅是某个值。

naUbqyE.jpg!mobile

旋转(Pivot):

即维的位置的互换,就像是二维表的行列转换,如图中通过旋转实现产品维和地域维的互换。

与上面几种操作不同,旋转并未减少或增加要分析的样本。而是根据不同的目的,改变了分析的角度,比如本来将产品作为观察角度,将地域和时间作为参照,分析不同产品在销售情况。通过旋转,转而分析江浙沪三个不同地区的产品销售情况。

以上仅简单介绍了数仓领域最最基础的知识和概念。下一篇重点分析现实中数仓的类型及其代表产品,并介绍优秀的数仓产品会用到的核心技术。

注:在调研过程中,看过不少数仓的基础文章,逐渐形成了对数仓的认识,在将其转化为自己的描述过程中,发现了一个博客( http://webdataanalysis.net ),有论述有实践,在实践中不断加深自己的认识。虽然其中的文章都是很多年前的,但个人觉得对学习入门数仓很有帮助,所以文中很多内容和图片都参考了该博客。

引用:

http://webdataanalysis.net/web-data-warehouse/

https://en.wikipedia.org/wiki/Online_analytical_processing

原文链接:

https://zhuanlan.zhihu.com/p/144926830

未完待续,TO BE CONTINUE!

在文末分享、点赞、在看,给个三连击呗~~

会员推荐:

DataFun会员计划重磅发布!多重权益加持,为你筑就数据科学家之路!扫码了解更多:

BrMvQvY.jpg!mobile

文章推荐:

网易数据基础平台建设

关于我们:

DataFunTalk  专注于大数据、人工智能技术应用的分享与交流。发起于2017年,在北京、上海、深圳、杭州等城市举办超过100场线下沙龙、论坛及峰会,已邀请近500位专家和学者参与分享。其公众号 DataFunTalk 累计生产原创文章300+,百万+阅读,7万+精准粉丝。

b6V3Ujz.jpg!mobile

分享、点赞、在看 ,给个 三连击 呗! :point_down: 


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK