我们在哪? 我们去哪?
在本教程系列的第一部分中,您学习了如何从Informix到Spark获取单个表,如何在Spark中获取整个数据库,以及如何为Informix构建特定的方言。 第二部分侧重于基本分析并从数据中获取见解。
第三部分更深入地介绍了联接和更复杂的查询。 您将继续使用Informix示例数据库,但是您不需要任何Informix知识。
更多数据!
您使用以下四个表:
- 顾客
- 命令
- 物料(订单的每一行)
- 库存(这是您销售产品的描述)
在该数据库的结构中要注意的唯一技巧是,库存(在库存表中)由两个键唯一地标识: stock_num
和manu_code
。 manu_code
字段定义了您将不使用的制造商。 尽管被称为库存,但此表并未提供库存/仓库中物品的数量。 这有点令人困惑,但是当您从旧系统继承时,事情可能会令人不安,不是吗?
您可能对此进行了不同的设计,但是我认为这是挑战(和乐趣)的一部分:对80年代后期设计的数据库进行分析。 为了教程的目的,设计定制数据库太容易了!
未售出商品
您的第二个应用程序将挖掘数据并查找您有参考但从未出售过的文章。 您的销售人员可能希望通过销售,退还给制造商或其他方式摆脱这些产品。 但是我们是IT人士,所以我们不要过多干预销售人员和采购人员的创造力。
在此示例UnsoldProductsApp.java
(在net.jgp.labs.informix2spark.l300 package
)和前面的示例TimeToShipApp.java
(在net.jgp.labs.informix2spark.l200
程序net.jgp.labs.informix2spark.l300 package
)之间有很多通用代码。 )。 我喜欢完整的示例,但是在这种情况下,因为我们是在上一个示例的基础上构建的,所以我将重点放在更改上。
保留“数据湖”,其中包含四个表:
List<String> tables = new ArrayList<>();
tables.add("customer");
tables.add("orders");
tables.add("items");
tables.add("stock");
让Spark加载它们:
Map<String, Dataset<Row>> datalake = new HashMap<>();
for (String table : tables) {
System.out.print("Loading table [" + table + "] ... ");
Dataset<Row> df = spark.read()
.format("jdbc")
.option("url", config.getJdbcUrl())
.option("dbtable", table)
.option("user", config.getUser())
.option("password", config.getPassword())
.option("driver", config.getDriver())
.load();
datalake.put(table, df);
System.out.println("done");
}
System.out.println("We have loaded " + datalake.size() + " table(s) in our data lake");
到目前为止,执行此程序将给出以下信息:
Loading table [customer] ... done
Loading table [orders] ... done
Loading table [items] ... done
Loading table [stock] ... done
We have loaded 4 table(s) in our data lake
您可以快速浏览一下数据:
for (String table : tables) {
Dataset<Row> df = datalake.get(table);
System.out.println("Number of rows in " + table + ": " + df.count());
df.show(10);
df.printSchema();
}
并且您应该得到类似以下内容(仅限于第四数据集):
...
Number of rows in stock: 74
+---------+---------+---------------+----------+----+---------------+
|stock_num|manu_code| description|unit_price|unit| unit_descr|
+---------+---------+---------------+----------+----+---------------+
| 1| HRO|baseball gloves| 250.00|case|10 gloves/case |
| 1| HSK|baseball gloves| 800.00|case|10 gloves/case |
| 1| SMT|baseball gloves| 450.00|case|10 gloves/case |
| 2| HRO|baseball | 126.00|case|24/case |
| 3| HSK|baseball bat | 240.00|case|12/case |
| 3| SHM|baseball bat | 280.00|case|12/case |
| 4| HSK|football | 960.00|case|24/case |
| 4| HRO|football | 480.00|case|24/case |
| 5| NRG|tennis racquet | 28.00|each|each |
| 5| SMT|tennis racquet | 25.00|each|each |
+---------+---------+---------------+----------+----+---------------+
only showing top 10 rows
root
|-- stock_num: integer (nullable = true)
|-- manu_code: string (nullable = true)
|-- description: string (nullable = true)
|-- unit_price: decimal(6,2) (nullable = true)
|-- unit: string (nullable = true)
|-- unit_descr: string (nullable = true)
您可以将每个集合分配给一个数据帧( Dataset<Row>
)。 之后更容易重用它们。
Dataset<Row> ordersDf = datalake.get("orders");
Dataset<Row> customerDf = datalake.get("customer");
Dataset<Row> itemsDf = datalake.get("items");
Dataset<Row> stockDf = datalake.get("stock");
加入查看更多数据
如您所知,要获得更多见解,您必须加入表格:
Seq<String> stockColumns = new Set2<>("stock_num", "manu_code").toSeq();
Dataset<Row> allDf = customerDf
.join(
ordersDf,
customerDf.col("customer_num").equalTo(ordersDf.col("customer_num")),
"full_outer")
.join(
itemsDf,
ordersDf.col("order_num").equalTo(itemsDf.col("order_num")),
"full_outer")
.join(stockDf, stockColumns, "full_outer")
.drop(ordersDf.col("customer_num"))
.drop(itemsDf.col("order_num"))
.drop(stockDf.col("stock_num"))
.drop(stockDf.col("manu_code"));
因为它使用方法链接 ,所以这种转换看起来很复杂。 方法链接变得越来越流行。 在这里,使用四列联接四个表,然后删除重复的列。
拆分联接
为了更好地理解联接过程,可以将转换拆分为更多原子操作,例如:
Dataset<Row> allDf = customerDf.join(
ordersDf,
customerDf.col("customer_num").equalTo(ordersDf.col("customer_num")),
"full_outer");
allDf = allDf.join(
itemsDf,
ordersDf.col("order_num").equalTo(itemsDf.col("order_num")),
"full_outer");
Seq<String> stockColumns = new Set2<>("stock_num", "manu_code").toSeq();
allDf = allDf.join(stockDf, stockColumns, "full_outer");
...
您可以逐步执行。 分步代码也位于GitHub存储库中 ; 在net.jgp.labs.informix2spark.l301
包中查找UnsoldProductsSplitJoinApp
类。
现在,您可以将orders表与customer_num
列上的customer_num
表联接,执行外部联接。
Dataset<Row> allDf = customerDf.join(
ordersDf,
customerDf.col("customer_num").equalTo(ordersDf.col("customer_num")),
"full_outer");
allDf.printSchema();
allDf.show();
您的数据框的架构如下所示:
root
|-- customer_num: integer (nullable = true)
|-- fname: string (nullable = true)
|-- lname: string (nullable = true)
|-- company: string (nullable = true)
|-- address1: string (nullable = true)
|-- address2: string (nullable = true)
|-- city: string (nullable = true)
|-- state: string (nullable = true)
|-- zipcode: string (nullable = true)
|-- phone: string (nullable = true)
|-- order_num: integer (nullable = true)
|-- order_date: date (nullable = true)
|-- customer_num: integer (nullable = true)
|-- ship_instruct: string (nullable = true)
|-- backlog: string (nullable = true)
|-- po_num: string (nullable = true)
|-- ship_date: date (nullable = true)
|-- ship_weight: decimal(8,2) (nullable = true)
|-- ship_charge: decimal(6,2) (nullable = true)
|-- paid_date: date (nullable = true)
注意,您有两列名为customer_num
。 一列来自左侧的客户表(更确切地说是customerDf
数据框),另一列来自右侧的订单表(或ordersDf
数据框)。
联接数据框类似于使用Informix和其他RDBMS联接表:您从左到右联接,指定条件(这里有等联接)和联接方式(full_outer,left等)。
我通过删除多余的列简化了数据的输出。 在查看数据时,请注意有2个customer_num
列,但它们的值不同 。 为什么?
+------------+--------------------+---------+----------+------------+----------+----------+
|customer_num| company|order_num|order_date|customer_num| po_num| paid_date|
+------------+--------------------+---------+----------+------------+----------+----------+
| 108|Quinn's Sports | null| null| null| null| null|
| 101|All Sports Supplies | 1002|2008-05-21| 101|9270 |2008-06-03|
| 115|Gold Medal Sports | 1010|2008-06-17| 115|429Q |2008-08-22|
| 126|Neelie's Discount Sp| 1022|2008-07-24| 126|W9925 |2008-09-02|
| 103|Phil's Sports | null| null| null| null| null|
| 128|Phoenix University | null| null| null| null| null|
| 122|The Sporting Life | 1019|2008-07-11| 122|Z55709 |2008-08-06|
| 111|Sports Center | 1009|2008-06-14| 111|4745 |2008-08-21|
| 120|Century Pro Shop | 1017|2008-07-09| 120|DM354331 | null|
| 117|Kids Korner | 1007|2008-05-31| 117|278693 | null|
| 117|Kids Korner | 1012|2008-06-18| 117|278701 | null|
| 112|Runners & Others | 1006|2008-05-30| 112|Q13557 | null|
| 127|Big Blue Bike Shop | 1023|2008-07-24| 127|KF2961 |2008-08-22|
| 107|Athletic Supplies | null| null| null| null| null|
| 114|Sporting Place | null| null| null| null| null|
| 102|Sports Spot | null| null| null| null| null|
| 113|Sportstown | null| null| null| null| null|
| 121|City Sports | 1018|2008-07-10| 121|S22942 |2008-08-06|
| 125|Total Fitness Sports| null| null| null| null| null|
| 109|Sport Stuff | null| null| null| null| null|
| 105|Los Altos Sports | null| null| null| null| null|
| 110|AA Athletics | 1008|2008-06-07| 110|LZ230 |2008-07-21|
| 110|AA Athletics | 1015|2008-06-27| 110|MA003 |2008-08-31|
| 106|Watson & Son | 1004|2008-05-22| 106|8006 | null|
| 106|Watson & Son | 1014|2008-06-25| 106|8052 |2008-07-10|
| 116|Olympic City | 1005|2008-05-24| 116|2865 |2008-06-21|
| 123|Bay Sports | 1020|2008-07-11| 123|W2286 |2008-09-20|
| 119|The Triathletes Club| 1016|2008-06-29| 119|PC6782 | null|
| 131|JGP.net | null| null| null| null| null|
| 118|Blue Ribbon Sports | null| null| null| null| null|
| 124|Putnum's Putters | 1021|2008-07-23| 124|C3288 |2008-08-22|
| 104|Play Ball! | 1001|2008-05-20| 104|B77836 |2008-07-22|
| 104|Play Ball! | 1003|2008-05-22| 104|B77890 |2008-06-14|
| 104|Play Ball! | 1011|2008-06-18| 104|B77897 |2008-08-29|
| 104|Play Ball! | 1013|2008-06-22| 104|B77930 |2008-07-31|
+------------+--------------------+---------+----------+------------+----------+----------+
第一个customer_num
列属于左数据框。 因为您进行了完全外部联接,所以您将获得所有数据,甚至包括未下订单的客户的数据。 这就是为什么第二个customer_num
列中的某些条目为null的原因,例如第4行或第8行。
稍后,如果您要引用此列,Spark可能会与您正在调用的customer_num
列混淆。 因此,删除不需要的列(例如,第二列)很有意义。
要删除不需要的列,请在数据框上调用drop()
方法。 请注意以下参数:使用数据框的col()
方法指定要删除的col()
。
allDf = allDf.drop(ordersDf.col("customer_num"));
然后,您可以连接其他两个表。
以下allDf
只是将数据allDf
( allDf
)与order_num
列中的项目order_num
,然后删除多余的列:
allDf = allDf.join(
itemsDf,
ordersDf.col("order_num").equalTo(itemsDf.col("order_num")),
"full_outer");
allDf = allDf.drop(itemsDf.col("order_num"));
两列链接最后一个表: stock_num
和manu_code
。 您需要按顺序组装它们( Seq
)。 这里使用的序列和集合是Scala对象,但是您可以在Java代码中轻松使用它们。
Seq<String> stockColumns = new Set2<>("stock_num",
"manu_code").toSeq();
现在,加入的语法更加简单:
allDf = allDf.join(stockDf, stockColumns, "full_outer");
您可以删除最后两列:
allDf = allDf.drop(stockDf.col("stock_num"));
allDf = allDf.drop(stockDf.col("manu_code"));
这样就结束了原子联接操作与方法链接的详细演练。 如果您有任何疑问,请随时使用本教程底部的评论部分。
利用完整的数据框
现在,您将所有数据都放在一个数据框中! 您可以开始对其进行钻取以获取价值。
一个很好的反映是缓存数据,因此不必每次都需要重新计算它。
allDf.cache();
您还可以打印架构或查看几行数据。
allDf.printSchema();
allDf.show(5);
在架构中,您可以看到没有任何重复的行。
root
|-- stock_num: integer (nullable = true)
|-- manu_code: string (nullable = true)
|-- description: string (nullable = true)
|-- unit_price: decimal(6,2) (nullable = true)
|-- unit: string (nullable = true)
|-- unit_descr: string (nullable = true)
|-- stock_num: integer (nullable = true)
|-- manu_code: string (nullable = true)
|-- customer_num: integer (nullable = true)
|-- fname: string (nullable = true)
|-- lname: string (nullable = true)
|-- company: string (nullable = true)
|-- address1: string (nullable = true)
|-- address2: string (nullable = true)
|-- city: string (nullable = true)
|-- state: string (nullable = true)
|-- zipcode: string (nullable = true)
|-- phone: string (nullable = true)
|-- order_num: integer (nullable = true)
|-- order_date: date (nullable = true)
|-- customer_num: integer (nullable = true)
|-- ship_instruct: string (nullable = true)
|-- backlog: string (nullable = true)
|-- po_num: string (nullable = true)
|-- ship_date: date (nullable = true)
|-- ship_weight: decimal(8,2) (nullable = true)
|-- ship_charge: decimal(6,2) (nullable = true)
|-- paid_date: date (nullable = true)
|-- item_num: integer (nullable = true)
|-- quantity: integer (nullable = true)
|-- total_price: decimal(8,2) (nullable = true)
|-- description: string (nullable = true)
|-- unit_price: decimal(6,2) (nullable = true)
|-- unit: string (nullable = true)
|-- unit_descr: string (nullable = true)
And the data is indeed wide as there are now all those columns resulting from the merge.
(line numbers useful here)
+---------+---------+------------+---------------+---------------+--------------------+--------------------+--------+---------------+-----+-------+------------------+---------+----------+------------+--------------------+-------+----------+----------+-----------+-----------+----------+--------+--------+-----------+---------------+----------+----+---------------+
|stock_num|manu_code|customer_num| fname| lname| company| address1|address2| city|state|zipcode| phone|order_num|order_date|customer_num| ship_instruct|backlog| po_num| ship_date|ship_weight|ship_charge| paid_date|item_num|quantity|total_price| description|unit_price|unit| unit_descr|
+---------+---------+------------+---------------+---------------+--------------------+--------------------+--------+---------------+-----+-------+------------------+---------+----------+------------+--------------------+-------+----------+----------+-----------+-----------+----------+--------+--------+-----------+---------------+----------+----+---------------+
| 101| SHM| 119|Bob |Shorter |The Triathletes Club|2405 Kings Highway | null|Cherry Hill | NJ| 08002|609-663-6079 | 1016|2008-06-29| 119|delivery entrance...| n|PC6782 |2008-07-12| 35.00| 11.80| null| 1| 2| 136.00|bicycle tires | 68.00|box |4/box |
| 309| SHM| null| null| null| null| null| null| null| null| null| null| null| null| null| null| null| null| null| null| null| null| null| null| null|ear drops | 40.00|case|20/case |
| 109| PRC| 119|Bob |Shorter |The Triathletes Club|2405 Kings Highway | null|Cherry Hill | NJ| 08002|609-663-6079 | 1016|2008-06-29| 119|delivery entrance...| n|PC6782 |2008-07-12| 35.00| 11.80| null| 2| 3| 90.00|pedal binding | 30.00|case|6 pairs/case |
| 6| ANZ| 116|Jean |Parmelee |Olympic City |1104 Spinosa Drive | null|Mountain View | CA| 94040|415-534-8822 | 1005|2008-05-24| 116|call before deliv...| n|2865 |2008-06-09| 80.80| 16.20|2008-06-21| 4| 1| 48.00|tennis ball | 48.00|case|24 cans/case |
| 6| ANZ| 115|Alfred |Grant |Gold Medal Sports |776 Gary Avenue | null|Menlo Park | CA| 94025|415-356-1123 | 1010|2008-06-17| 115|deliver 776 King ...| n|429Q |2008-06-29| 40.60| 12.30|2008-08-22| 2| 1| 48.00|tennis ball | 48.00|case|24 cans/case |
+---------+---------+------------+---------------+---------------+--------------------+--------------------+--------+---------------+-----+-------+------------------+---------+----------+------------+--------------------+-------+----------+----------+-----------+-----------+----------+--------+--------+-----------+---------------+----------+----+---------------+
only showing top 5 rows
清空仓库
您的工作是找到从未销售过的产品。
首先,删除不需要的列。 在上一个示例中,您一次删除了一个列。 您现在是Spark专家,可以提高工作效率,不是吗? 同样,当您掌握方法链接时,现在可以在一个调用中执行此操作,对吗?
首先,创建一个列列表。
List<String> columnsToDrop = new ArrayList<>();
columnsToDrop.add("zipcode");
columnsToDrop.add("phone");
columnsToDrop.add("customer_num");
columnsToDrop.add("fname");
columnsToDrop.add("lname");
columnsToDrop.add("company");
columnsToDrop.add("address1");
columnsToDrop.add("address2");
columnsToDrop.add("city");
columnsToDrop.add("state");
columnsToDrop.add("order_num");
columnsToDrop.add("order_date");
columnsToDrop.add("customer_num");
columnsToDrop.add("ship_instruct");
columnsToDrop.add("backlog");
columnsToDrop.add("po_num");
columnsToDrop.add("ship_date");
columnsToDrop.add("ship_weight");
columnsToDrop.add("ship_charge");
columnsToDrop.add("paid_date");
columnsToDrop.add("time_to_ship");
columnsToDrop.add("item_num");
columnsToDrop.add("quantity");
columnsToDrop.add("total_price");
然后在一个操作中:
- 删除所有列。 注意如何将Java列表转换为Scala序列以与
drop()
。 - 使用Spark SQL语法进行过滤。
Dataset<Row> unsoldProductsDf = allDf
.drop(JavaConversions.asScalaBuffer(columnsToDrop))
.filter("order_num IS NULL")
.filter("description IS NOT NULL");
unsoldProductsDf.cache();
System.out.println("We have " + unsoldProductsDf.count()
+ " unsold references in our warehouse, time to do something!");
unsoldProductsDf.show();
现在,您知道仓库中有多少从未售出的产品参考:
There are 35 unsold references in this warehouse, it's time to do something!
+---------+---------+---------------+----------+----+---------------+
|stock_num|manu_code| description|unit_price|unit| unit_descr|
+---------+---------+---------------+----------+----+---------------+
| 309| SHM|ear drops | 40.00|case|20/case |
| 312| SHM|racer goggles | 96.00|box |12/box |
| 203| NKL|irons/wedge | 670.00|case|2 sets/case |
| 102| PRC|bicycle brakes | 480.00|case|4 sets/case |
| 302| HRO|ice pack | 4.50|each|each |
| 3| SHM|baseball bat | 280.00|case|12/case |
| 205| HRO|3 golf balls | 312.00|case|24/case |
| 313| ANZ|swim cap | 60.00|box |12/box |
| 110| ANZ|helmet | 244.00|case|4/case |
| 301| HRO|running shoes | 42.50|each|each |
| 108| SHM|crankset | 45.00|each|each |
| 110| HRO|helmet | 260.00|case|4/case |
| 205| NKL|3 golf balls | 312.00|case|24/case |
| 311| SHM|water gloves | 48.00|box |4 pairs/box |
| 310| SHM|kick board | 80.00|case|10/case |
| 303| KAR|socks | 36.00|box |24 pairs/box |
| 310| ANZ|kick board | 84.00|case|12/case |
| 101| PRC|bicycle tires | 88.00|box |4/box |
| 109| SHM|pedal binding | 200.00|case|4 pairs/case |
| 312| HRO|racer goggles | 72.00|box |12/box |
+---------+---------+---------------+----------+----+---------------+
only showing top 20 rows
该仓库中有35个未售出的参考书,是时候做点事情了!
现在,您可以使用此信息转到销售和采购部门。
你学到了什么
在本系列的稍长文章中,您学习了如何在Spark中联接数据,删除一些列,缓存数据框以及使用SQL对生成的数据框执行分析。 借助这些信息,您可以为从未销售过的产品的采购部门提供见解,并为您的销售人员提供要淘汰哪些产品的见解!
走得更远
- 要在您的Mac上安装并更好地理解Informix,请阅读“ Mac 10.12上的Informix 12.10,并带有一些Java 8:苹果,咖啡和一个伟大的数据库的故事 ”(自私无耻的插件;我写了这本书)。
- 从我的GitHub存储库下载本教程中的所有代码。 如何使用JDBC将IBM Informix数据传输到Apache Spark 。 别忘了喜欢叉!
- 阅读有关Apache Spark中联接类型的信息。
翻译自: https://www.ibm.com/developerworks/opensource/library/ba-offloading-informix-data-spark-3/index.html