SQL学习指南:SQL和大数据
由于SQL被数百万的人使用并且被集成到数千的应用程序中,因此利用SQL来处理这些数据是有意义的。在过去的几年里,涌现出了一批新的工具以支持SQL访问结构化、半结构化和非结构化的数据,这些工具包括Presto、Apache Drill和Toad Data Point等。本章探讨了其中之一的Apache Drill,用于演示如何将存储在不同服务器上格式各异的数据汇集起来用于报告和分析。
18.1 Apache Drill简介
目前已经开发出了各种工具和接口以供SQL访问存储在Hadoop、NoSQL、Spark以及基于云端的分布式文件系统中的数据。比如Hive和Spark SQL,前者是允许用户查询存储在Hadoop中数据的初期尝试之一,而后者是一个库,用于查询以各种格式存储在Spark中的数据。Apache Drill是一款相对较新的开源工具,初现于2015年,具有下列引人瞩目的特性:
- 促进跨多种数据格式的查询,包括分隔数据、JSON、Parquet和日志文件;
- 连接到关系型数据库、Hadoop、NoSQL、HBase、Kafka,以及PCAP、BlockChain等专用数据格式;
- 允许创建自定义插件,以连接到大多数其他数据存储;
- 不需要前期的模式定义;
- 支持SQL:2003标准;
- 可与Tableau、Apache Superset等流行的商业智能(business intelligence,BI)工具配合使用。
通过使用Drill,可以连接到任意数量的数据源进行查询,无须先设置元数据仓库。
18.2 使用Drill查询文件
我们从使用Drill来查询文件中的数据开始。Drill知道如何读取多种不同的文件格式,包括抓包(PCAP)文件,这是一种包含网络中包信息的二进制格式文件。如果想查询PCAP文件,所要做的就是配置Drill的分布式文件系统插件dfs,以纳入包含PCAP文件目录的路径,接下来就可以编写查询了。
要做的第一件事是找出待查询文件中包含哪些列。Drill对information_schema(参见第15章)提供了部分支持,可以在工作区中通过其查看数据文件相关的高层级信息:
apache drill> SELECT file_name, is_directory, is_file, permission
. . . . . . > FROM information_schema.`files`
. . . . . . > WHERE schema_name = 'dfs.data';
+-------------------+--------------+---------+------------+
| file_name | is_directory | is_file | permission |
+-------------------+--------------+---------+------------+
| attack-trace.pcap | false | true | rwxrwx--- |
+-------------------+--------------+---------+------------+
1 row selected (0.238 seconds)
结果显示,在数据工作区中有一个名为attack-trace.pcap的文件,该信息很有用,但无法查询 information_schema.columns列,以找出文件中有哪些列。但是,对该文件执行不返回任何结果的查询会显示出可用列[1]:
apache drill> SELECT * FROM dfs.data.`attack-trace.pcap`
. . . . . . > WHERE 1=2;
+------+---------+-----------+-----------------+--------+--------+
| type | network | timestamp | timestamp_micro | src_ip | dst_ip |
+------+---------+-----------+-----------------+--------+--------+----------+----------+-----------------+-----------------+-------------+src_port | dst_port | src_mac_address | dst_mac_address | tcp_session |----------+----------+-----------------+-----------------+-------------+---------+-----------+--------------+---------------+----------------+tcp_ack | tcp_flags | tcp_flags_ns | tcp_flags_cwr | tcp_flags_ece |---------+-----------+--------------+---------------+----------------+---------------------------+--------------------------------------+tcp_flags_ece_ecn_capable | tcp_flags_ece_congestion_experienced |---------------------------+--------------------------------------+---------------+---------------+---------------+---------------+tcp_flags_urg | tcp_flags_ack | tcp_flags_psh | tcp_flags_rst |---------------+---------------+---------------+---------------+---------------+---------------+------------------+---------------+tcp_flags_syn | tcp_flags_fin | tcp_parsed_flags | packet_length |---------------+---------------+------------------+---------------+------------+------+is_corrupt | data |------------+------+No rows selected (0.285 seconds)
现在已经知道了PCAP文件中的列名,可以编写查询了。下列查询统计从每个IP地址向各个目标端口发送的包数量:
apache drill> SELECT src_ip, dst_port,
. . . . . . > count(*) AS packet_count
. . . . . . > FROM dfs.data.`attack-trace.pcap`
. . . . . . > GROUP BY src_ip, dst_port;
+----------------+----------+--------------+
| src_ip | dst_port | packet_count |
+----------------+----------+--------------+
| 98.114.205.102 | 445 | 18 |
| 192.150.11.111 | 1821 | 3 |
| 192.150.11.111 | 1828 | 17 |
| 98.114.205.102 | 1957 | 6 |
| 192.150.11.111 | 1924 | 6 |
| 192.150.11.111 | 8884 | 15 |
| 98.114.205.102 | 36296 | 12 |
| 98.114.205.102 | 1080 | 159 |
| 192.150.11.111 | 2152 | 112 |
+----------------+----------+--------------+
9 rows selected (0.254 seconds)
下列查询聚合了每秒包信息:
apache drill> SELECT trunc(extract(second from `timestamp`)) as packet_time,
. . . . . . > count(*) AS num_packets,
. . . . . . > sum(packet_length) AS tot_volume
. . . . . . > FROM dfs.data.`attack-trace.pcap`
. . . . . . > GROUP BY trunc(extract(second from `timestamp`));
+-------------+-------------+------------+
| packet_time | num_packets | tot_volume |
+-------------+-------------+------------+
| 28.0 | 15 | 1260 |
| 29.0 | 12 | 1809 |
| 30.0 | 13 | 4292 |
| 31.0 | 3 | 286 |
| 32.0 | 2 | 118 |
| 33.0 | 15 | 1054 |
| 34.0 | 35 | 14446 |
| 35.0 | 29 | 16926 |
| 36.0 | 25 | 16710 |
| 37.0 | 25 | 16710 |
| 38.0 | 26 | 17788 |
| 39.0 | 23 | 15578 |
| 40.0 | 25 | 16710 |
| 41.0 | 23 | 15578 |
| 42.0 | 30 | 20052 |
| 43.0 | 25 | 16710 |
| 44.0 | 22 | 7484 |
+-------------+-------------+------------+
17 rows selected (0.422 seconds)
在该查询中,需要在保留字timestamp两边加上反引号(`)。
无论文件存储在本地、网络、分布式文件系统或云端,都可以进行查询。Drill内建了多种文件类型支持,还可以自行编写插件,以允许Drill查询其他类型的文件。接下来将探讨查询存储在数据库中的数据。
18.3 使用Drill查询MySQL
Drill可以通过JDBC驱动程序连接到任何关系型数据库,因此下一步就是展示Drill如何查询用于本书示例的Sakila样本数据库。你要做的就是为MySQL加载JDBC驱动程序并配置Drill连接到 MySQL数据库。
这里你可能会好奇,“我为什么要用Drill来查询MySQL呢?” 一个原因是(正如你将在本章结尾看到的)可以使用Drill编写查询,组合不同来源的数据,例如,你可能会编写一个查询,将来自MySQL、Hadoop和以逗号分隔的文件的数据连接起来。
第一步是选择一个数据库:
apache drill (information_schema)> use mysql.sakila;
+------+------------------------------------------+
| ok | summary |
+------+------------------------------------------+
| true | Default schema changed to [mysql.sakila] |
+------+------------------------------------------+
1 row selected (0.062 seconds)
选择好数据库之后,可以使用show tables命令查看在所选模式中所有可用的数据表:
apache drill (mysql.sakila)> show tables;
+--------------+----------------------------+
| TABLE_SCHEMA | TABLE_NAME |
+--------------+----------------------------+
| mysql.sakila | actor |
| mysql.sakila | address |
| mysql.sakila | category |
| mysql.sakila | city |
| mysql.sakila | country |
| mysql.sakila | customer |
| mysql.sakila | film |
| mysql.sakila | film_actor |
| mysql.sakila | film_category |
| mysql.sakila | film_text |
| mysql.sakila | inventory |
| mysql.sakila | language |
| mysql.sakila | payment |
| mysql.sakila | rental |
| mysql.sakila | sales |
| mysql.sakila | staff |
| mysql.sakila | store |
| mysql.sakila | actor_info |
| mysql.sakila | customer_list |
| mysql.sakila | film_list |
| mysql.sakila | nicer_but_slower_film_list |
| mysql.sakila | sales_by_film_category |
| mysql.sakila | sales_by_store |
| mysql.sakila | staff_list |
+--------------+----------------------------+
24 rows selected (0.147 seconds)
先来执行几个之前章节演示过的查询。下面这个简单的双表连接来自第5章:
apache drill (mysql.sakila)> SELECT a.address_id, a.address, ct.city
. . . . . . . . . . . . . )> FROM address a
. . . . . . . . . . . . . )> INNER JOIN city ct
. . . . . . . . . . . . . )> ON a.city_id = ct.city_id
. . . . . . . . . . . . . )> WHERE a.district = 'California';
+------------+------------------------+----------------+
| address_id | address | city |
+------------+------------------------+----------------+
| 6 | 1121 Loja Avenue | San Bernardino |
| 18 | 770 Bydgoszcz Avenue | Citrus Heights |
| 55 | 1135 Izumisano Parkway | Fontana |
| 116 | 793 Cam Ranh Avenue | Lancaster |
| 186 | 533 al-Ayn Boulevard | Compton |
| 218 | 226 Brest Manor | Sunnyvale |
| 274 | 920 Kumbakonam Loop | Salinas |
| 425 | 1866 al-Qatif Avenue | El Monte |
| 599 | 1895 Zhezqazghan Drive | Garden Grove |
+------------+------------------------+----------------+
9 rows selected (3.523 seconds)
下一个查询来自第8章,包含group by子句和having子句:
apache drill (mysql.sakila)> SELECT fa.actor_id, f.rating,
. . . . . . . . . . . . . )> count(*) num_films
. . . . . . . . . . . . . )> FROM film_actor fa
. . . . . . . . . . . . . )> INNER JOIN film f
. . . . . . . . . . . . . )> ON fa.film_id = f.film_id
. . . . . . . . . . . . . )> WHERE f.rating IN ('G','PG')
. . . . . . . . . . . . . )> GROUP BY fa.actor_id, f.rating
. . . . . . . . . . . . . )> HAVING count(*) > 9;
+----------+--------+-----------+
| actor_id | rating | num_films |
+----------+--------+-----------+
| 137 | PG | 10 |
| 37 | PG | 12 |
| 180 | PG | 12 |
| 7 | G | 10 |
| 83 | G | 14 |
| 129 | G | 12 |
| 111 | PG | 15 |
| 44 | PG | 12 |
| 26 | PG | 11 |
| 92 | PG | 12 |
| 17 | G | 12 |
| 158 | PG | 10 |
| 147 | PG | 10 |
| 14 | G | 10 |
| 102 | PG | 11 |
| 133 | PG | 10 |
+----------+--------+-----------+
16 rows selected (0.277 seconds)
最后这个查询来自第16章,包含3个不同的排名函数:
apache drill (mysql.sakila)> SELECT customer_id, count(*) num_rentals,
. . . . . . . . . . . . . )> row_number()
. . . . . . . . . . . . . )> over (order by count(*) desc)
. . . . . . . . . . . . . )> row_number_rnk,
. . . . . . . . . . . . . )> rank()
. . . . . . . . . . . . . )> over (order by count(*) desc) rank_rnk,
. . . . . . . . . . . . . )> dense_rank()
. . . . . . . . . . . . . )> over (order by count(*) desc)
. . . . . . . . . . . . . )> dense_rank_rnk
. . . . . . . . . . . . . )> FROM rental
. . . . . . . . . . . . . )> GROUP BY customer_id
. . . . . . . . . . . . . )> ORDER BY 2 desc;
+-------------+-------------+----------------+----------+----------------+
| customer_id | num_rentals | row_number_rnk | rank_rnk | dense_rank_rnk |
+-------------+-------------+----------------+----------+----------------+
| 148 | 46 | 1 | 1 | 1 |
| 526 | 45 | 2 | 2 | 2 |
| 144 | 42 | 3 | 3 | 3 |
| 236 | 42 | 4 | 3 | 3 |
| 75 | 41 | 5 | 5 | 4 |
| 197 | 40 | 6 | 6 | 5 |
...
| 248 | 15 | 595 | 594 | 30 |
| 61 | 14 | 596 | 596 | 31 |
| 110 | 14 | 597 | 596 | 31 |
| 281 | 14 | 598 | 596 | 31 |
| 318 | 12 | 599 | 599 | 32 |
+-------------+-------------+----------------+----------+----------------+
599 rows selected (1.827 seconds)
这些示例演示了Drill执行复杂的MySQL查询的能力,但别忘了,Drill还能处理MySQL之外的很多关系型数据库,所以有些语言特性可能会有所差异(比如数据转换函数)。
18.4 使用Drill查询MongoDB
在使用Drill查询MySQL中的Sakila样本数据之后,下一步是将Sakila数据转换成另一种常用的格式并存储在非关系型数据库中,然后使用Drill查询数据。我决定将数据转换为JSON格式并存储在MongoDB中,MongoDB是用于文档存储的一种比较流行的NoSQL平台。Drill包含MongoDB插件,也知道如何读取JSON文档,所以将JSON文件加载到Mongo并编写查询还是比较容易的。
在编写查询之前,先来看看JSON文件的结构,毕竟JSON文件不是规范化形式。films.json是两个JSON文件中的第一个:
{"_id":1,"Actors":[{"First name":"PENELOPE","Last name":"GUINESS","actorId":1},{"First name":"CHRISTIAN","Last name":"GABLE","actorId":10},{"First name":"LUCILLE","Last name":"TRACY","actorId":20},{"First name":"SANDRA","Last name":"PECK","actorId":30},{"First name":"JOHNNY","Last name":"CAGE","actorId":40},{"First name":"MENA","Last name":"TEMPLE","actorId":53},{"First name":"WARREN","Last name":"NOLTE","actorId":108},{"First name":"OPRAH","Last name":"KILMER","actorId":162},{"First name":"ROCK","Last name":"DUKAKIS","actorId":188},{"First name":"MARY","Last name":"KEITEL","actorId":198}],"Category":"Documentary","Description":"A Epic Drama of a Feminist And a Mad Scientistwho must Battle a Teacher in The Canadian Rockies","Length":"86","Rating":"PG","Rental Duration":"6","Replacement Cost":"20.99","Special Features":"Deleted Scenes,Behind the Scenes","Title":"ACADEMY DINOSAUR"},
{"_id":2,"Actors":[{"First name":"BOB","Last name":"FAWCETT","actorId":19},{"First name":"MINNIE","Last name":"ZELLWEGER","actorId":85},{"First name":"SEAN","Last name":"GUINESS","actorId":90},{"First name":"CHRIS","Last name":"DEPP","actorId":160}],"Category":"Horror","Description":"A Astounding Epistle of a Database AdministratorAnd a Explorer who must Find a Car in Ancient China","Length":"48","Rating":"G","Rental Duration":"3","Replacement Cost":"12.99","Special Features":"Trailers,Deleted Scenes","Title":"ACE GOLDFINGER"},
...
{"_id":999,"Actors":[{"First name":"CARMEN","Last name":"HUNT","actorId":52},{"First name":"MARY","Last name":"TANDY","actorId":66},{"First name":"PENELOPE","Last name":"CRONYN","actorId":104},{"First name":"WHOOPI","Last name":"HURT","actorId":140},{"First name":"JADA","Last name":"RYDER","actorId":142}],"Category":"Children","Description":"A Fateful Reflection of a Waitress And a Boatwho must Discover a Sumo Wrestler in Ancient China","Length":"101","Rating":"R","Rental Duration":"5","Replacement Cost":"28.99","Special Features":"Trailers,Deleted Scenes","Title":"ZOOLANDER FICTION"}
{"_id":1000,"Actors":[{"First name":"IAN","Last name":"TANDY","actorId":155},{"First name":"NICK","Last name":"DEGENERES","actorId":166},{"First name":"LISA","Last name":"MONROE","actorId":178}],"Category":"Comedy","Description":"A Intrepid Panorama of a Mad Scientist And a Boywho must Redeem a Boy in A Monastery","Length":"50","Rating":"NC-17","Rental Duration":"3","Replacement Cost":"18.99","Special Features":"Trailers,Commentaries,Behind the Scenes","Title":"ZORRO ARK"}
该集合中有1,000个文档,每个文档中包含若干标量属性(Title、Rating、_id),另外还有一个名为Actors的列表,其中包含1到N个元素,由电影中所有参演演员的演员ID、名字和姓氏组成。因此,该文件包含了MySQL Sakila数据库的actor、film和film_actor数据表中的所有数据。
第二个文件是customer.json,其中的数据来自MySQL Sakila数据库的数据表customer、address、city、country和payment:
{"_id":1,"Address":"1913 Hanoi Way","City":"Sasebo","Country":"Japan","District":"Nagasaki","First Name":"MARY","Last Name":"SMITH","Phone":"28303384290","Rentals":[{"rentalId":1185,"filmId":611,"staffId":2,"Film Title":"MUSKETEERS WAIT","Payments":[{"Payment Id":3,"Amount":5.99,"Payment Date":"2005-06-15 00:54:12"}],"Rental Date":"2005-06-15 00:54:12.0","Return Date":"2005-06-23 02:42:12.0"},{"rentalId":1476,"filmId":308,"staffId":1,"Film Title":"FERRIS MOTHER","Payments":[{"Payment Id":5,"Amount":9.99,"Payment Date":"2005-06-15 21:08:46"}],"Rental Date":"2005-06-15 21:08:46.0","Return Date":"2005-06-25 02:26:46.0"},
...{"rentalId":14825,"filmId":317,"staffId":2,"Film Title":"FIREBALL PHILADELPHIA","Payments":[{"Payment Id":30,"Amount":1.99,"Payment Date":"2005-08-22 01:27:57"}],"Rental Date":"2005-08-22 01:27:57.0","Return Date":"2005-08-27 07:01:57.0"}]
}
该文件包含599个条目(这里只显示了其中的一个),它们作为customers集合中的599个文档被加载到MongoDB。每个文档包含单个客户的信息,以及该客户所有的租借和相关付款信息。此外,文档中还有嵌套列表,因为Rentals列表的租借信息中也包含一个Payments列表。
JSON文件被加载之后,MongoDB包含两个集合(films和customers),这些集合中的数据来自MySQL Sakila数据库的9个不同的数据表。这是一个相当典型的场景,因为应用程序的程序员通常与集合打交道,一般不喜欢将他们的数据解构以存储到规范化的关系数据表中。从SQL的角度来看,挑战在于决定如何将这些数据扁平化,使其表现得就像存储在多个数据表中一样。
作为演示,我们来编写针对films集合的查询:找出参演过10部或以上的G级或PG级电影的所有演员。原始数据如下所示:
apache drill (mongo.sakila)> SELECT Rating, Actors
. . . . . . . . . . . . . )> FROM films
. . . . . . . . . . . . . )> WHERE Rating IN ('G','PG');
+--------+----------------------------------------------------------------+
| Rating | Actors |
+--------+----------------------------------------------------------------+
| PG |[{"First name":"PENELOPE","Last name":"GUINESS","actorId":"1"},{"First name":"FRANCES","Last name":"DAY-LEWIS","actorId":"48"},{"First name":"ANNE","Last name":"CRONYN","actorId":"49"},{"First name":"RAY","Last name":"JOHANSSON","actorId":"64"},{"First name":"PENELOPE","Last name":"CRONYN","actorId":"104"},{"First name":"HARRISON","Last name":"BALE","actorId":"115"},{"First name":"JEFF","Last name":"SILVERSTONE","actorId":"180"},{"First name":"ROCK","Last name":"DUKAKIS","actorId":"188"}] |
| PG |[{"First name":"UMA","Last name":"WOOD","actorId":"13"},{"First name":"HELEN","Last name":"VOIGHT","actorId":"17"},{"First name":"CAMERON","Last name":"STREEP","actorId":"24"},{"First name":"CARMEN","Last name":"HUNT","actorId":"52"},{"First name":"JANE","Last name":"JACKMAN","actorId":"131"},{"First name":"BELA","Last name":"WALKEN","actorId":"196"}] |
...
| G |[{"First name":"ED","Last name":"CHASE","actorId":"3"},{"First name":"JULIA","Last name":"MCQUEEN","actorId":"27"},{"First name":"JAMES","Last name":"PITT","actorId":"84"},{"First name":"CHRISTOPHER","Last name":"WEST","actorId":"163"},{"First name":"MENA","Last name":"HOPPER","actorId":"170"}] |
+--------+----------------------------------------------------------------+
372 rows selected (0.432 seconds)
Actors字段是一个列表,包含一个或多个演员文档。为了像与数据表交互一样与这些数据进行交互,可以使用flatten命令将列表转换为包含3个字段的嵌套数据表:
apache drill (mongo.sakila)> SELECT f.Rating, flatten(Actors) actor_list
. . . . . . . . . . . . . )> FROM films f
. . . . . . . . . . . . . )> WHERE f.Rating IN ('G','PG');
+--------+----------------------------------------------------------------+
| Rating | actor_list |
+--------+----------------------------------------------------------------+
| PG | {"First name":"PENELOPE","Last name":"GUINESS","actorId":"1"} |
| PG | {"First name":"FRANCES","Last name":"DAY-LEWIS","actorId":"48"}|
| PG | {"First name":"ANNE","Last name":"CRONYN","actorId":"49"} |
| PG | {"First name":"RAY","Last name":"JOHANSSON","actorId":"64"} |
| PG | {"First name":"PENELOPE","Last name":"CRONYN","actorId":"104"} |
| PG | {"First name":"HARRISON","Last name":"BALE","actorId":"115"} |
| PG | {"First name":"JEFF","Last name":"SILVERSTONE","actorId":"180"}|
| PG | {"First name":"ROCK","Last name":"DUKAKIS","actorId":"188"} |
| PG | {"First name":"UMA","Last name":"WOOD","actorId":"13"} |
| PG | {"First name":"HELEN","Last name":"VOIGHT","actorId":"17"} |
| PG | {"First name":"CAMERON","Last name":"STREEP","actorId":"24"} |
| PG | {"First name":"CARMEN","Last name":"HUNT","actorId":"52"} |
| PG | {"First name":"JANE","Last name":"JACKMAN","actorId":"131"} |
| PG | {"First name":"BELA","Last name":"WALKEN","actorId":"196"} |
...
| G | {"First name":"ED","Last name":"CHASE","actorId":"3"} |
| G | {"First name":"JULIA","Last name":"MCQUEEN","actorId":"27"} |
| G | {"First name":"JAMES","Last name":"PITT","actorId":"84"} |
| G | {"First name":"CHRISTOPHER","Last name":"WEST","actorId":"163"}|
| G | {"First name":"MENA","Last name":"HOPPER","actorId":"170"} |
+--------+----------------------------------------------------------------+
2,119 rows selected (0.718 seconds) |
该查询返回2,119行,而不是上个查询返回的372行,这表明每部G级或PG级电影平均有5.7位演员参演。可以将这个查询放入子查询中,用于按照分级或演员对数据进行分组:
apache drill (mongo.sakila)> SELECT g_pg_films.Rating,
. . . . . . . . . . . . . )> g_pg_films.actor_list.`First name` first_name
. . . . . . . . . . . . . )> g_pg_films.actor_list.`Last name` last_name,
. . . . . . . . . . . . . )> count(*) num_films
. . . . . . . . . . . . . )> FROM
. . . . . . . . . . . . . )> (SELECT f.Rating, flatten(Actors) actor_list
. . . . . . . . . . . . . )> FROM films f
. . . . . . . . . . . . . )> WHERE f.Rating IN ('G','PG')
. . . . . . . . . . . . . )> ) g_pg_films
. . . . . . . . . . . . . )> GROUP BY g_pg_films.Rating,
. . . . . . . . . . . . . )> g_pg_films.actor_list.`First name`,
. . . . . . . . . . . . . )> g_pg_films.actor_list.`Last name`
. . . . . . . . . . . . . )> HAVING count(*) > 9;
+--------+------------+-------------+-----------+
| Rating | first_name | last_name | num_films |
+--------+------------+-------------+-----------+
| PG | JEFF | SILVERSTONE | 12 |
| G | GRACE | MOSTEL | 10 |
| PG | WALTER | TORN | 11 |
| PG | SUSAN | DAVIS | 10 |
| PG | CAMERON | ZELLWEGER | 15 |
| PG | RIP | CRAWFORD | 11 |
| PG | RICHARD | PENN | 10 |
| G | SUSAN | DAVIS | 13 |
| PG | VAL | BOLGER | 12 |
| PG | KIRSTEN | AKROYD | 12 |
| G | VIVIEN | BERGEN | 10 |
| G | BEN | WILLIS | 14 |
| G | HELEN | VOIGHT | 12 |
| PG | VIVIEN | BASINGER | 10 |
| PG | NICK | STALLONE | 12 |
| G | DARYL | CRAWFORD | 12 |
| PG | MORGAN | WILLIAMS | 10 |
| PG | FAY | WINSLET | 10 |
+--------+------------+-------------+-----------+
18 rows selected (0.466 seconds)
内查询使用flatten命令为每位参演过G级或PG级电影的演员创建一行,外查询则简单地对该数据集进行分组。
接下来,针对MongoDB中的customers集合编写查询。这有点难度,因为每个文档都含有一个电影租借列表,每个租借列表都含有一个付款列表。为了再增加点乐趣,还可以连接films集合,看看Drill如何处理连接。该查询应该返回租借过G级或PG级电影且租借费超过80美元的所有客户,如下所示:
apache drill (mongo.sakila)> SELECT first_name, last_name,
. . . . . . . . . . . . . )> sum(cast(cust_payments.payment_data.Amount
. . . . . . . . . . . . . )> as decimal(4,2))) tot_payments
. . . . . . . . . . . . . )> FROM
. . . . . . . . . . . . . )> (SELECT cust_data.first_name,
. . . . . . . . . . . . . )> cust_data.last_name,
. . . . . . . . . . . . . )> f.Rating,
. . . . . . . . . . . . . )> flatten(cust_data.rental_data.Payments)
. . . . . . . . . . . . . )> payment_data
. . . . . . . . . . . . . )> FROM films f
. . . . . . . . . . . . . )> INNER JOIN
. . . . . . . . . . . . . )> (SELECT c.`First Name` first_name,
. . . . . . . . . . . . . )> c.`Last Name` last_name,
. . . . . . . . . . . . . )> flatten(c.Rentals) rental_data
. . . . . . . . . . . . . )> FROM customers c
. . . . . . . . . . . . . )> ) cust_data
. . . . . . . . . . . . . )> ON f._id = cust_data.rental_data.filmID
. . . . . . . . . . . . . )> WHERE f.Rating IN ('G','PG')
. . . . . . . . . . . . . )> ) cust_payments
. . . . . . . . . . . . . )> GROUP BY first_name, last_name
. . . . . . . . . . . . . )> HAVING
. . . . . . . . . . . . . )> sum(cast(cust_payments.payment_data.Amount
. . . . . . . . . . . . . )> as decimal(4,2))) > 80;
+------------+-----------+--------------+
| first_name | last_name | tot_payments |
+------------+-----------+--------------+
| ELEANOR | HUNT | 85.80 |
| GORDON | ALLARD | 85.86 |
| CLARA | SHAW | 86.83 |
| JACQUELINE | LONG | 86.82 |
| KARL | SEAL | 89.83 |
| PRISCILLA | LOWE | 95.80 |
| MONICA | HICKS | 85.82 |
| LOUIS | LEONE | 95.82 |
| JUNE | CARROLL | 88.83 |
| ALICE | STEWART | 81.82 |
+------------+-----------+--------------+
10 rows selected (1.658 seconds)
最内层的查询,我将其命名为cust_data,将Rentals列表扁平化,使得cust_payments查询能够连接到films集合,同时扁平化Payments列表。最外层的查询按照客户名对数据进行分组,并应用having子句筛选出在G级或PG级电影上花费80美元或更少的客户。
18.5 使用Drill处理多个数据源
到目前为止,使用Drill连接了存储在同一数据库中的多个数据表,但如果数据存储在不同的数据库中呢?例如,假设客户/租借/付款数据存储在MongoDB中,但电影/演员的分类数据存储在MySQL中。只要配置好Drill连接到这两个数据库,剩下的只需要描述到哪里去查找数据。下面是18.4节中的查询,但不是连接到存储在MongoDB中的films集合,而是指定了存储在MySQL中的film数据表:
apache drill (mongo.sakila)> SELECT first_name, last_name,
. . . . . . . . . . . . . )> sum(cast(cust_payments.payment_data.Amount
. . . . . . . . . . . . . )> as decimal(4,2))) tot_payments
. . . . . . . . . . . . . )> FROM
. . . . . . . . . . . . . )> (SELECT cust_data.first_name,
. . . . . . . . . . . . . )> cust_data.last_name,
. . . . . . . . . . . . . )> f.Rating,
. . . . . . . . . . . . . )> flatten(cust_data.rental_data.Payments)
. . . . . . . . . . . . . )> payment_data
. . . . . . . . . . . . . )> FROM mysql.sakila.film f
. . . . . . . . . . . . . )> INNER JOIN
. . . . . . . . . . . . . )> (SELECT c.`First Name` first_name,
. . . . . . . . . . . . . )> c.`Last Name` last_name,
. . . . . . . . . . . . . )> flatten(c.Rentals) rental_data
. . . . . . . . . . . . . )> FROM mongo.sakila.customers c
. . . . . . . . . . . . . )> ) cust_data
. . . . . . . . . . . . . )> ON f.film_id =
. . . . . . . . . . . . . )> cast(cust_data.rental_data.filmID as integer)
. . . . . . . . . . . . . )> WHERE f.rating IN ('G','PG')
. . . . . . . . . . . . . )> ) cust_payments
. . . . . . . . . . . . . )> GROUP BY first_name, last_name
. . . . . . . . . . . . . )> HAVING
. . . . . . . . . . . . . )> sum(cast(cust_payments.payment_data.Amount
. . . . . . . . . . . . . )> as decimal(4,2))) > 80;
+------------+-----------+--------------+
| first_name | last_name | tot_payments |
+------------+-----------+--------------+
| LOUIS | LEONE | 95.82 |
| JACQUELINE | LONG | 86.82 |
| CLARA | SHAW | 86.83 |
| ELEANOR | HUNT | 85.80 |
| JUNE | CARROLL | 88.83 |
| PRISCILLA | LOWE | 95.80 |
| ALICE | STEWART | 81.82 |
| MONICA | HICKS | 85.82 |
| GORDON | ALLARD | 85.86 |
| KARL | SEAL | 89.83 |
+------------+-----------+--------------+
10 rows selected (1.874 seconds)
因为在同一个查询中使用了多个数据库,所以指定了每个数据表/集合的完整路径,以便清楚数据的来源。这正是Drill真正的优势所在,因为我可以在同一个查询中组合多个来源的数据,而无须将一个来源的数据转换并加载到另一个来源。
18.6 SQL的未来
关系型数据库的未来有些不明朗。过去十年的大数据技术可能会继续发展成熟并获得市场份额。也有可能出现一批超越Hadoop和NoSQL的新技术,并从关系型数据库中夺取更多的市场份额。但是,大多数公司仍在使用关系型数据库运行其核心业务功能,这种现状应该需要很长时间才会改变。
不过,SQL的未来似乎更清晰一些。虽然SQL语言最初是作为一种与关系型数据库中的数据进行交互的机制,而像Apache Drill这种工具更像是一个抽象层,促进了跨各种数据库平台的数据分析。从我的观点来看,这种趋势还将继续,SQL在未来多年内仍然是数据分析和报表生成的关键工具。
本文摘自《SQL学习指南(第3版)》
本书介绍了SQL语言的基础知识以及高级特性,包括SQL基本查询、过滤、多数据表查询、集合、数据操作、分组和聚合、子查询、连接、条件逻辑、事务、索引和约束、视图等内容。同时,为了适应近年来数据库领域的发展变化,本书针对大数据、SQL跨平台数据库服务和数据分析等领域的需求,增加了处理大型数据库的实现策略和扩展技术,以及报表和分析工具等内容。
本书内容循序渐进,每章的主题相对独立,并提供了丰富、可扩展的示例,同时还配备精选练习,有利于读者有效学习和快速掌握SQL语言。本书适合作为数据库应用开发者和数据库管理员的必备入门书,也可供SQL相关从业者查阅和参考。
SQL学习指南:SQL和大数据相关推荐
- sql学习指南_SQL格式功能的全面指南
sql学习指南 This article explains the usage of SQL Format function and performance comparison with SQL C ...
- SQL卷土重来 成就炙手可热的大数据
文章讲的是SQL卷土重来 成就炙手可热的大数据,看似NoSQL数据库在过去几年中迎来了发展的春天,而传统数据库却缺乏施展空间.其实情况正好相反,"结构化查询语言(SQL)"实际已经 ...
- SQL on Hadoop在快手大数据平台的实践与优化 | 分享实录
快手大数据架构工程师钟靓 本文是根据快手大数据架构工程师钟靓于 5月18-19日在A2M人工智能与机器学习创新峰会<SQL on Hadoop在快手大数据平台的实践与优化>演讲中的分享内容 ...
- 基于简单sql语句的sql解析原理及在大数据中的应用
基于简单sql语句的sql解析原理及在大数据中的应用 李万鸿 老百姓呼吁打土豪分田地.共同富裕,总有一天会实现. 全面了解你所不知道的外星人和宇宙真想:http://pan.baidu.com/s/1 ...
- 大数据学习笔记一:大数据的发展历程--MapReduce,Hive,Yarn,Hadoop,Spark,Flink
大数据学习系列文章:大数据-博客专栏 今天在学习极客时间专栏:<从0开始学大数据> 从预习 01 | 大数据技术发展史:大数据的前世今生到预习 03 | 大数据应用领域:数据驱动一切,系统 ...
- 财务分析比赛学习(1)大数据部分
财务分析比赛学习(1)大数据部分 本博文记录的主要为之后的财务分析比赛所需要的一些基础知识的大纲用于简单查阅.也是一个快速厘清财务知识的系列 大数据 大数据特征 麦肯锡全球研究所 海量的数据规模 快速 ...
- 大数据早报:百度开源移动端深度学习框架 中国联通成立大数据公司(9.26)
数据早知道,上36dsj看早报! 来源36大数据,作者:奥兰多 『深度学习』百度开源移动端深度学习框架mobile-deep-learning 2017 年 9 月 25 日,百度在 GitHub 开 ...
- 基于深度学习技术的电表大数据检测系统
基于深度学习技术的电表大数据检测系统 人工智能技术与咨询 来源:< 人工智能与机器人研究> ,作者方向 关键词: 智能电表:数据分析:深度学习时序模型: 摘要: 随着我国电厂不断发展,我国 ...
- 大数据Hadoop教程-学习笔记01【大数据导论与Linux基础】
视频教程:哔哩哔哩网站:黑马大数据Hadoop入门视频教程,总时长:14:22:04 教程资源:https://pan.baidu.com/s/1WYgyI3KgbzKzFD639lA-_g,提取码: ...
最新文章
- 如何将github上的 lib fork之后通过podfile 改变更新源到自己fork的地址
- 扒皮下京东首页楼层图标的动画效果实现方式
- c++ 多线程 类成员函数_多线程(C++/Python)
- 【unity基础系列】1、unity Texture Type设置为Advanced时纹理的格式列表
- css3中的 @Keyframes
- nssl1141,jzoj3470-最短路【SPFA,暴力】
- 中国电子协会考评中心_中国电子学会考评中心和CPA青少年编程能力等级测评有什么不同?...
- Velocity 模板引擎介绍
- devops开发运维一体化_进阶 | 中国电信系统集成公司:100% 应用 DevOps 理念,做好企业级行业应用...
- 【对讲机的那点事】如何给4G全网通对讲机写号?
- 高等数学下册——引力
- 教你用VC6写热血江湖小外挂
- python-函数参数和文档
- MacBook Pro维修过程
- 倾斜摄影数据转cesium 3dtile(详细步骤)
- (三篇长文让你玩6Pandas)数据分析入门_PART2常用工具包_CH02数据分析工具:Pandas__Part03(统计分析基础)
- excel统计每个单元格内的单词及空格的个数
- C++文件操作之读写二进制文件
- linux基础unit13-软件安装
- UG技巧,适合初学者
热门文章
- 施乐服务器显示22,惠普m127fn打印机显示代码22什么意思
- 【报告分享】2021年国庆节旅游数据报告-马蜂窝旅游(附下载)
- eclipse编码格式该如何设置
- 10-6 查询选修了所有3学分课程的学生学号
- 十点半前上床睡眠最好,健康有保障! - 生活至上,美容至尚!
- 中文分词之Java实现使用IK Analyzer实现
- emqtt服务器开启日志
- maya腿的蒙皮旋转枢轴_MAYA动画:用驱动关键帧实现旋转门效果
- 双因素认证的解决方案
- The Surprising Effectiveness of PPO in Cooperative Multi-Agent Games 阅读笔记