Spark(六):SparkSQLAndDataFrames对结构化数据集与非结构化数据的处理

如上转载的这篇文章写得不错!!!


一:简单了解SparkSQL。

Spark SQL 是结构化的数据处理一个Spark模块。与基本的Spark RDD API不同,Spark SQL 所提供的接口为Spark 提供有关数据和正在执行的计算的结构的详细信息。Spark SQL内部使用这些额外的信息来执行额外的优化。有几种方法与Spark SQL 包括 SQL、 DataFrames API 和数据集 API 进行交互。计算结果相同的执行引擎在使用时,独立的 API/语言使用的表达计算。这种统一意味着开发人员很容易可以提供最自然的方式来表达一个给定的转换基于各种 Api 之间来回切换。

Spark SQL是Spark中的一个模块,主要用于进行结构化数据的处理。它提供的最核心的编程抽象,就是DataFrame。同时Spark SQL还可以作为分布式的SQL查询引擎。Spark SQL最重要的功能之一,就是从Hive中查询数据。

二:简单了解DataFrame。

DataFrame是一个以命名列方式组织的分布式数据集,等同于关系型数据库中的一个表,也相当于R/Python中的data frames(但是进行了更多的优化)。DataFrame可以通过很多来源进行构建,包括:结构化的数据文件,Hive中的表,外部的关系型数据库,以及RDD。

接下来是对 结构化数据集 与 非结构化数据集 的操作。

三:结构化数据集: 如何把JSON文件转化为DataFrame

3.1.在HDFS上放置了两个JSON文件,即

people.json, 文件内容如下:

{"id": "19","name": "berg","sex": "male","age": 19}
{"id": "20","name": "cccc","sex": "female","age": 20}
{"id": "21","name": "xxxx","sex": "male","age": 21}
{"id": "22","name": "jjjj","sex": "female","age": 21}

student.json,文件内容如下:

{"id": "1","name": "china","sex": "female","age": 100}
{"id": "19","name": "xujun","sex": "male","age": 22}
3.2 通过DataFrame的API来操作数据,熟悉下DataFrame中方法的使用:
public class SparkSqlDemo {

	private static String appName = "Test Spark RDD";
	private static String master = "local";

	public static void main(String[] args) {
		SparkConf conf = new SparkConf();
		conf.set("spark.testing.memory", "269522560000");
		JavaSparkContext sc = new JavaSparkContext(master, appName, conf);

		//创建了 sqlContext的上下文,注意,它是DataFrame的起点。
		SQLContext sqlContext = new SQLContext(sc);

		//本地的JSON文件转化为DataFrame
		DataFrame df = sqlContext.read().json("hdfs://192.168.226.129:9000/txt/sparkshell/people.json");

		//输出表结构
		df.printSchema();

		//显示DataFrame的内容。
		df.show();

		//选择name
		df.select(df.col("name")).show();

		// 选择所有年龄大于21岁的人,只保留name字段
		df.filter(df.col("age").lt(21)).select("name").show();

		// 选择name,并把age字段自增 1
		df.select(df.col("name"), df.col("age").plus(1)).show();

		//按年龄分组计数:
		df.groupBy("age").count().show();  // 应该有一条数据记录为  2 

		//把另个JSON文件转化为DataFrame
		DataFrame df2 = sqlContext.read().json("hdfs://192.168.226.129:9000/txt/sparkshell/student.json");

		df2.show();
		
		//表的关联。
		df.join(df2,df.col("id").equalTo(df2.col("id"))).show();
		
		
		//以编程方式运行SQL:
		//把DataFrame对象转化为一个虚拟的表
		df.registerTempTable("people");
		sqlContext.sql("select age,count(*) from people group by age").show();

		System.out.println(  "-------------" );
		sqlContext.sql("select * from people").show();

	}
}
3.3 以编程方式运行 SQL 查询并返回作为综合结果,通过注册表,操作sql的方式来操作数据:
public class SparkSqlDemo1 {

	private static String appName = "Test Spark RDD";
	private static String master = "local";

	public static void main(String[] args) {
		SparkConf conf = new SparkConf();
		conf.set("spark.testing.memory", "269522560000");
		JavaSparkContext sc = new JavaSparkContext(master, appName, conf);

		//创建了 sqlContext的上下文,注意,它是DataFrame的起点。
		SQLContext sqlContext = new SQLContext(sc);

		//本地的JSON文件转化为DataFrame
		DataFrame df = sqlContext.read().json("hdfs://192.168.226.129:9000/txt/sparkshell/people.json");

		//把另一个JSON文件转化为DataFrame
		DataFrame df2 = sqlContext.read().json("hdfs://192.168.226.129:9000/txt/sparkshell/student.json");		

		//以编程方式运行SQL:
		//把DataFrame对象转化为一个虚拟的表
		df.registerTempTable("people");
		df2.registerTempTable("student");

		// 查询虚拟表 people 中所有数据
		sqlContext.sql("select * from people").show();

		//查看某个字段  
		sqlContext.sql("select name from people ").show();

		//查看多个字段  
		sqlContext.sql("select name,age+1 from people ").show();  

		//过滤某个字段的值  
		sqlContext.sql("select name, age from people where age>=21").show();

		//count group 某个字段的值  
		sqlContext.sql("select age,count(*) countage from people group by age").show();


		//关联: 内联 。 
		sqlContext.sql("select * from people inner join student on people.id = student.id ").show();
		/*
	    +---+---+----+----+---+---+-----+----+
		|age| id|name| sex|age| id| name| sex|
		+---+---+----+----+---+---+-----+----+
		| 19| 19|berg|male| 22| 19|xujun|male|
		+---+---+----+----+---+---+-----+----+ 
		 */
	}
}

四:非结构化数据集:

第一种方法使用反射来推断架构 RDD 包含特定类型的对象。
这种基于反射方法导致更简洁的代码和工程好当您已经知道该Schema编写Spark应用程序时。

创建 DataFrames 的第二个方法是通过允许您构建一个Schema,然后将它应用于现有 RDD 的编程接口。
虽然这种方法更为详细,它允许您构建 DataFrames 时直到运行时才知道的列和它们的类型。
4.1 非结构化的数据集文件,user.txt,内容如下:
1,"Hadoop",20
2,"HBase", 21
3,"Zookeeper",22
4,"Hive",23
5,"Spark",24
6,"Berg",22
7,"Xujun",23
4.2 通过 class反射来注册一张表。
Spark SQL 支持 JavaBeans RDD 自动转换分布式数据集。BeanInfo,使用反射来获取定义表的架构。目前,Spark SQL 不支持包含嵌套的 JavaBeans 或包含复杂的类型,例如列表或数组。您可以通过创建一个类,实现可序列化并有 getter 和 setter 方法的所有其字段创建 JavaBean。
public class SparkSqlDemo2 {

	private static String appName = "Test Spark RDD";
	private static String master = "local";

	public static void main(String[] args) {
		SparkConf conf = new SparkConf();
		conf.set("spark.testing.memory", "269522560000");
		JavaSparkContext sc = new JavaSparkContext(master, appName, conf);

		//创建了 sqlContext的上下文,注意,它是DataFrame的起点。
		SQLContext sqlContext = new SQLContext(sc);

		//把加载的文本文件 并 每行转换 JavaBean
		JavaRDD<String> rdd = sc.textFile("hdfs://192.168.226.129:9000/txt/sparkshell/user.txt");

		JavaRDD<User> userRDD = rdd.map( new Function<String, User>() {

			private static final long serialVersionUID = 1L;

			public User call(String line) throws Exception {
				String[] parts = line.split(",");
				User user = new User();
				user.setId(Integer.parseInt(parts[0].trim()));
				user.setName(parts[1].trim());
				user.setAge(Integer.parseInt(parts[2].trim()));
				return user;
			}
		});

		// collect 属于行动算子Action 提交作业并触发运算。
		List<User> list = userRDD.collect();
		for (User user : list) {
			System.out.println(  user );
		}

		//通过 class 反射注册一张表
		DataFrame df = sqlContext.createDataFrame(userRDD, User.class);
		df.registerTempTable("user");

		DataFrame df1 = sqlContext.sql("SELECT id,name,age FROM user WHERE age >= 21 AND age <= 23");

		// 通过sql 查询的结果是 DataFrame 即df1 它还是支持 RDD的所有正常操作。
		df1.show();
		
		//并且 结果中的行列可以按序号访问。
		List<String> listString = df1.javaRDD().map(new Function<Row, String>() {
			
			private static final long serialVersionUID = 1L;

			public String call(Row row) {
				return "Id: " + row.getInt(0) + ", Name: "+row.getString(1) + ", Age: " + row.getInt(2);
			}
		}).collect();
		
		for (String string : listString) {
			System.out.println(  string );
		}
	}
}
4.3 以编程方式指定 schema, 通过字段反射来映射注册临时表
在某些情况下不能提前定义 JavaBean 类 (例如,记录的结构编码的字符串,或将解析文本数据集和领域预计将以不同的方式为不同的用户),
三个步骤,可以以编程方式创建分布式数据集。

1. 从原始 RDD; 创建行 RDD

2. 创建由 StructType 中 RDD 在步骤 1 中创建的行结构相匹配的schema。

3.适用于行 RDD 通过 createDataFrame 方法由 SQLContext 提供的schema。
public class SparkSqlDemo3 {

	private static String appName = "Test Spark RDD";
	private static String master = "local";

	public static void main(String[] args) {
		SparkConf conf = new SparkConf();
		conf.set("spark.testing.memory", "269522560000");
		JavaSparkContext sc = new JavaSparkContext(master, appName, conf);

		//创建了 sqlContext的上下文,注意,它是DataFrame的起点。
		SQLContext sqlContext = new SQLContext(sc);

		//把加载的文本文件 并 每行转换 JavaBean
		JavaRDD<String> rdd = sc.textFile("hdfs://192.168.226.129:9000/txt/sparkshell/user.txt");

		// schema 以字符串形式编码
		String schemaString = "id name age";

		// 基于 字符串的schema生成 schema。
		List<StructField> fields = new ArrayList<StructField>();
		
		String[] str = schemaString.split(" ");
		fields.add(DataTypes.createStructField(str[0], DataTypes.IntegerType, true));
		fields.add(DataTypes.createStructField(str[1], DataTypes.StringType, true));
		fields.add(DataTypes.createStructField(str[2], DataTypes.IntegerType, true));

		StructType schema = DataTypes.createStructType(fields);  //  id name age

		JavaRDD<Row> rowRDD = rdd.map( new Function<String, Row>() {

			private static final long serialVersionUID = 1L;
			public Row call(String record) throws Exception {
				String[] fields = record.split(",");
				return RowFactory.create(Integer.parseInt(fields[0].trim()), fields[1].trim(),Integer.parseInt(fields[2].trim()));
			}
		});

		List<Row> list = rowRDD.collect();
		for (Row row : list) {
			System.out.println(  row.getInt(0) + "\t"+ row.getString(1) + "\t"+row.getInt(2)  );
		}

		//对RDD应用schema 并注册一张表:
		DataFrame df = sqlContext.createDataFrame(rowRDD, schema);
		System.out.println( "df : " + df);
		df.registerTempTable("user");

		df.show();
		DataFrame df2 = sqlContext.sql("SELECT id,name,age FROM user WHERE age >= 21 AND age <= 23");

		// 通过sql 查询的结果是 DataFrame 即df1 它还是支持 RDD的所有正常操作。
		df2.show();
		// 并且 结果中的行列可以按序号访问。
		List<String> listString = df2.javaRDD().map(new Function<Row, String>() {

			private static final long serialVersionUID = 1L;
			public String call(Row row) {
				System.out.println( row );
				return "Id: " + row.getInt(0) + ", Name: "+row.getString(1) + ", Age: " + row.getInt(2);
			}
		}).collect();

		for (String string : listString) {
			System.out.println(  string );
		}

	}
}

注意如果将上述代码段中的一段,即:

String[] str = schemaString.split(" ");

        fields.add(DataTypes.createStructField(str[0], DataTypes.IntegerType, true));

        fields.add(DataTypes.createStructField(str[1], DataTypes.StringType, true));

        fields.add(DataTypes.createStructField(str[2], DataTypes.IntegerType, true));

改为下面这段代码:

for (String fieldName: schemaString.split(" ")) {


          fields.add(DataTypes.createStructField(fieldName, DataTypes.StringType, true));

      }

将会出现以下错误:

Caused by: scala.MatchError: 1 (of class java.lang.Integer)

那就来认识下 局部套用和部分应用 : http://www.ibm.com/developerworks/cn/java/j-jn9/