集算器序表和SQL数据表的异同

本文对比了集算器序表与SQL数据表在有序性、显式集合及泛型集合等方面的特性与应用场景,探讨了它们在解决复杂计算问题上的优劣。

  集算器序表和SQL数据表都是有结构的二维数据对象,都有记录、索引、主键的概念,都可以应用于结构化数据的计算。虽然都可以应用于结构化数据的计算,但两者的应用场景却有明显的区别,序表适合解决较复杂但数据量不是很大的计算问题,而数据表适合进行常规但可能数据量巨大的计算。

  两者的不同是由底层机制决定的。

  序表具有有序的特点,每条记录、每列数据都有确定的序号;序表支持显式集合,序表之间可以直接进行集合运算;序表也是泛型集合,其基本元素既可以是数值也可以是引用或者另一个集合。

  SQL数据表缺乏上述特点,但它对内外存透明,可以用一致的语法来访问内存、外存或混合数据。

  下面,我们将深入讨论两者的共同点和区别。

相同的基本功能

  集算器序表和SQL数据表都是有结构的二维数据对象,即以记录为基础,多条记录形成行式的二维表,二维表配合列名形成完整的数据结构。因为结构上大体相似,所以两者的基本用法区别不大。

  例1:查询对象中的数据。找到Freight大于100,并且是2013年以前的订单。

  SQL:SELECT * FROM Orders WHERE Freight > 100 AND OrderDate < ’1998-01-01′

  序表:= Orders.select(Freight > 100 && OrderDate < date(“1998-01-01″))

  注:本例使用的数据对象名为订单(Orders),后面还会用到客户(Customers)。

  例2:排序。将订单按EmployeeID正序排序,再按Freight逆序排序。

  SQL:SELECT * FROM Orders ORDER BY EmployeeID ,Freight DESC

  序表:=Orders.sort(EmployeeID,Freight:-1)

  例3:分组汇总。按员工汇总,对运货费求和,对订单计数。

  SQL:SELECT EmployeeID, COUNT(OrderID), SUM(Freight) FROM Orders GROUP BY EmployeeID

  序表:= Orders.groups(EmployeeID;sum(Freight),count(OrderID))

  例4:连接。将订单和客户这两个数据对象连接成新的数据对象,使用左连接,连接字段为CustomerID。

  SQL:Select * from Orders left join Customers on Orders.CustomerID =Customers.CustomerID

  序表:=join@1(Orders:, CustomerID;  Customers:, CustomerID)

  除了上述几种基本用法,集算器序表和SQL数据表在唯一值、计数、求和、平均、最大、最小值等算法上也非常相似,这里不再一一举例。

有序性的区别

  序表的记录集合是有序的,因此可以轻松解决和顺序有关的计算。SQL数据表缺乏序号及序号相关的访问方法,这使它在序运算上不够方便。

  例1:针对sales数据对象,计算每个月的销售额比上个月增长了百分之几。

  SQL:

    select salesAmount, salesMonth,

         (case when

    prev_price !=0 then ((salesAmount)/prev_price)-1

    else 0

    end) compValue

    from (select salesMonth, salesAmount,

    lag(salesAmount,1,0) over(order by salesMonth) prev_price

    from sales) t

  序表:

    sales.derive(salesAmount / salesAmount [-1]-1: compValue)

  比较:

  每个月的销售额和序无关,在序表和数据表中的表示方法一样,都是salesAmount。和序有关的是:上个月的销售额,即相对于当前记录的上一条记录中的salesAmount。序表有序,可以用salesAmount[-1]来直接表示上个月的销售额。SQL数据表无序,在SQL2003标准后添加了窗口函数补充了一些序运算功能,但仍然比较繁琐,需要用lag(salesAmount,1,0) over(order by salesMonth)这种复杂的方法来计算出上个月的销售额。

  序表还可以轻松表达相对区间,比如相对于当前月份的前两个月及后两个月共五个月的销售额,序表可以这样表达:salesAmount{-2,2}。在SQL中使用窗口函数也可以表达区间的汇总运算,但要麻烦得多。

  例2:针对sales数据表,找出每种产品里销售额最高的前10条的记录。

  SQL:

    select salesMan, product ,amount

    from ( select salesMan, product ,amount, rank() over (partition by product order by amount desc ) ranking from sales)

    where ranking <=10

  序表:

    = sales.group(product).(~.top(-amount;10))

  比较:

  实现本例最直观的思路是将数据按产品分组,然后再进行分组内的序运算,简单的方法是直接取组内amount最小的前十条记录,直观的方法是按照amount对组内数据逆序排序,再取组内序号是1到10的记录。

  序表对序运算支持良好,可以用top函数实现第一种算法(如例子中),也可以用sort函数和记录序号实现第二种算法,即:=sales.group(product).(~.sort(Amount:-1)).(~([to(10)]))。

  SQL数据表记录没有次序,必须先计算出一个序号或者可代替序号功能的字段,比如排名。上述例子的算法就是先计算出组内数据的排名,再取排名前十的记录。显而易见,SQL算法有点曲折,语法也难懂,要用到复杂的窗口函数over (partition by…… order by……),以及难以跟踪调试的子查询。

  相比之下,序表更加直接简便,也容易跟踪调试。比如程序员可以先写出=sales.group(product)这句代码进行测试,这句代码表示将数据分组,它可以独立运行并独立显示结果。如果分组结果符合预期,程序员可以继续加入第二段代码:对组内数据逆序排序,即.(~.sort(Amount:-1)),这里的“.”表示把前面的计算结果作为一个整体继续加工,“~”表示当前的组内数据,“-1”表示逆序。现在的代码是=sales.group(product).(~.sort(Amount:-1)),它仍然可独立运行并独立显示结果。观察或调试后,程序员可以加入第三段代码:取组内序号是1到10的记录,即.(~([to(10)]))。

  可以看到,序表的计算过程可以步步递进,在解决复杂的计算问题时简化计算防止出错。事实上,上述连续的三段代码可以写成逐步引用的三行代码,这可以更加清晰地分解计算目标:

  值得注意的是,SQL无法对数据先分组再执行有序计算,即使用临时表也无法实现,它必须将这两步合二为一,产生这一现象的原因是数据表不支持显式集合和泛型集合,语法表达能力较弱。

  另外,SQL使用的窗口函数虽然是ansi标准,但数据库厂商并未完全按标准实现,不同的数据库里写法也会不同,有些数据库根本不提供窗口函数。而序表的函数语法独立于数据源,不论哪种数据源(包括数据库、Txt文件、Excel文件、二进制文件等),使用序表计算时都无需修改代码。

显式集合的区别

  SQL有集合的概念,但不提供显式的集合,不能作为独立的变量存在,只能借助临时表来实现集合运算。而序表是真正的显式集合,可以实现集合运算。

  例子:针对Contract数据对象进行计算,假设业务上将订购数量大于40的合同称为大合同,单价大于2000的合同称为重要合同,请找出既是大合同又是重要合同的当年合同,以及除此之外的其他合同。

  SQL

    select SellDate,Quantity,Amount,Client from Contract where to_char(SellDate,’yyyy’)=’2014′ and quantity>=40 and AMOUNT>=2000

    select SellDate,Quantity,Amount,Client from Contract where not(to_char(SellDate,’yyyy’)=’2014′ and quantity>=40 and AMOUNT>=2000)

  序表

    =thisYear= Contract.select(year(SellDate)=2014)

    =big= Contract.select(Quantity>40)

    =importance = Contract.select(AMOUNT>2000)

    =answer=thieYear^big^ importance

    =others= Contract\answer

  比较分析

  既是大合同又是重要的当年合同,这是比较典型的自然思考方式,使用交集运算最为直观。如果将大合同定义为big,重要合同定义为importance,当年的合同定义为thisYear,那我们可以很容易写出伪代码:big∩importance∩thiYear。序表是显式集合,可以很直观地表达等价的算法,即:thieYear^big^ importance。SQL不能用集合变量来表示,因此只能寻求其他方式,比如转换为布尔条件来表示,即例子中的: to_char(SellDate,’yyyy’)=’2014′ and quantity>=40 and AMOUNT>=2000。

  第一问比较简单,因此两者的开发难度并没有太大的区别。随着问题的深入,两者的区别才会明显起来。

  第二问:“除此之外的其他合同”,这也是典型自然思考方式,使用差集可以一步算出。序表的算法是:Contract\answer,非常直观。使用布尔条件,SQL也可以计算出答案,但表达式的写法和业务描述相差甚远,即:not(to_char(SellDate,’yyyy’)=’2014′ and quantity>=40 and AMOUNT>=2000)。

  SQL也可以用集合运算来解答,算法也很直观,但是因为数据表不能用集合变量来表示,代码会显得非常冗长:

    (select SellDate,Quantity,Amount,Client from Contract)

    minus

    (Select select SellDate,Quantity,Amount,Client from Contract from(

    (select SellDate,Quantity,Amount,Client from Contract where to_char(SellDate,’yyyy’)=’2012′)

    Intersect

    (select SellDate,Quantity,Amount,Client from Contract where quantity>=40)

Intersect

(select SellDate,Quantity,Amount,Client from Contract where AMOUNT>=2000))

  因为代码太冗长,所以很多人宁可使用布尔条件来间接实现集合运算。

  当然,很多情况下数据表使用集合运算会比布尔条件更加方便,比如多个物理表之间的集合运算,或多层子查询之间的集合运算。这种情况下,将集合运算转化为布尔条件的代价就太高了,程序员必须接受冗长的集合运算。

泛型集合的区别

  序表是泛型集合,除了存储实体数据,还可以存储指向关联数据的引用,这就使序表可以通过直观的对象引用实现关联计算。数据表只能存储实体数据,需要用复杂的关联语句才能完成等价的计算。

  例子

  请计算获得总裁奖的部门经理们,其下属的年度优秀员工们都有谁。这里涉及两个数据对象:department和employee,其中,department的deptName字段和employee的empDept是一对一的关系,department中的manager字段和employee中的empName也是一对一的关系。另外,总裁奖的代码是PA;年度优秀员工的代码是EOY。

  SQL

    SELECT A.*

    FROM employee A,department B,employee C

    WHERE A.empDept=B.deptName AND B.manager=C.empName AND A.empHonor=‘EOY’ AND C.empHornor=‘PA’

  序表:

    employee.select(empHonor: “EOY”,empDept.manager.empHornor:”PA”)

  比较分析

  SQL的解法无疑是正确的,但其中的关联语句比较复杂,普通程序员理解费力。序表的解法比较直观,empHonor:”EOY”是条件之一:“年度优秀员工们都有谁”,而empDept.manager.empHornor表示员工的“所属部门.该部门的经理.该经理的获奖情况”,显然,这个值为PA就符合题目中的条件二:“获得总裁奖的部门经理们”。这就是对象引用。

  对象引用允许程序员使用“.”操作符来引用相关数据,这可以将业务中的关联关系直观地翻译为计算机语言,可以方便地表达多层关系,可以直观地进行关联计算。

对内外存透明的区别

  SQL

  SQL数据表因为不支持泛型和集合数据,内存中的数据写到外存时不会丢失信息,因而具有对内外存运算的透明性。首次访问数据表时,数据通常来自外存;之后再访问同样的数据表时,数据就可以来自内存缓存;对于数据量较大的数据表,其一部分数据来自外存,而另一部分会来自于内存。无论数据是来自于内存还是外存,无论数据量是大还是小,数据表的访问语法并无区别,程序员无需为此书写不同的SQL语句。

  序表

  序表支持了泛型(特别是引用)和集合数据,内存中的数据写到外存时会丢失信息,不能再读入,这导致其运算无法对内外存透明。序表是纯内存数据对象,所能计算的数据量受到内存限制,不能太大;如果数据量较大,那就应当使用游标(集算器的另一种数据对象)来进行外存计算,而游标和序表的语法是不同的;如果想提高性能或进行业务逻辑复杂的计算,程序员还必须将数据在游标和序表之间进行转换。

  比较

  序表对内外存不透明,程序员需要书写不同的代码来适应内存、外存或混合计算,还需要为将来数据量的增长而修改代码,因此前期设计和后期维护的工作量较大。而SQL数据表对内外存透明,程序员只需书写一套代码就能适应不同规模的数据,设计和维护的工作量较小。

  通过上述比较分析我们可以看出:数据有序、显示集合、泛型集合,序表的这些特点使它可以轻松解决和顺序有关的复杂问题,可以简化集合运算的复杂度,可以用直观的对象引用来处理复杂的多表关联。而SQL数据表对内外存透明,代码通用性更好。 

《大数据应用与实践》练习题 选择题 1.下列关于Spark特性的说法,正确的是( )。 A. Spark 仅支持 Java 语言开发 B. Scala可以自动推测类型 C. Spark 支持延迟计算 D. Spark 不支持内存计算 2.下面关于 Scala 特性说法正确的是( )。 A. Scala 不支持模式匹配 B. Scala 是纯面向对象语言 C. Scala 支持隐式转换 D. Scala 不能与 Java 互操作 3.下列 Scala 方法中,哪个方法可以正确获取列的前 n 个元素( )。 A. take (n) B. head (n) C. first (n) D. front (n) 4.下列选项中,哪个端口不是 Hadoop 自带的服务端口( )。 A. 50070 B. 8088 C. 9000 D. 4040 5.以下哪个是 HBase 的数据模型特性( )? A. 行式存储 B. 强事务一致性 C. 列族 D. 二级索引 3.下列哪个组件属于 Flink 的流处理核心( )? A. JobManager B. Namenode C. ResourceManager D. HMaster 6.Hive 中用于存储元数据的数据库是( )。 A. MySQL B. HBase C. MongoDB D. Cassandra 7.下面哪个是 DataFrame 的特点( )。 A. 不可分区 B. 无结构信息 C. 支持 SQL 查询 D. 不可列化 8.关于 Spark SQL,下面哪个是错误的( ) A. 支持多种数据源 B. 只能处理结构化数据 C. 提供 DataFrame Dataset API D. 可与 Hive 集成 9.下列选项中,哪个属于行动算子操作( )。 A. map () B. filter () C. reduce () D. flatMap () 10.以下哪个操作会触发 Spark 的宽依赖(Wide Dependency)( )? A. map() B. filter() C. groupByKey() D. union() 11.YARN 中负责单个节点资源管理的组件是( )。 A. ApplicationMaster B. NodeManager C. ResourceManager D. JobTracker 12.Flink 的时间类型中,基于数据生成时间的是( )。 A. 处理时间(Processing Time) B. 事件时间(Event Time) C. 摄入时间(Ingestion Time) D. 系统时间(System Time) 13.Spark 应用程的入口点是( )。 A. SparkContext B. SQLContext C. HiveContext D. SparkSession 14.下面哪个不是 Spark SQL 支持的数据源( )。 A. Parquet B. JSON C. CSV D. NoSQL 数据库 15.Spark 的内存管理模型中,Storage 内存主要用于( )。 A. 存储 RDD 缓存数据 B. 执行 Shuffle 操作 C. 存储任务执行时的临时数据 D. 存储广播变量 16.下列关于 HDFS 副本机制的说法,错误的是( )。 A. 默认副本数为 3 B. 副本分布在不同机架(Rack) C. 副本由 DataNode 自动管理 D. 副本数不可自定义 17. Spark SQL 中用于处理结构化数据的核心抽象是( )。 A. RDD B. DataFrame C. DStream D. Dataset[Row] 18.以下哪个工具用于 Hadoop 集群的配置管理( )? A. ZooKeeper B. Oozie C. Ambari D. Hive 19.下面关于Scala特性说法错误的是( )。 A. JavaScala不可以混编 B. Scala可以自动推测类型 C. Scala有模式匹配 D. Scala是以面向函数编程 20.下列Scala方法中,哪个方法可以正确计算数组arr的长度( )。 A. Count B. take C. tail D. length 21.下列选项中,哪个不是Spark生态系统中的组件( )。 A. Spark Streaming B. Mllib C. Graphx D. Spark R 22.下列选项中,哪个端口不是Spark自带的服务端口( )。 A. 8080 B. 4040 C. 8090 D. 18080 23.Spark是Hadoop生态下哪个组件的替代方案( ) A. Hadoop B.Yarn C.Hdfs D.MapReduce 24.下列选项中,哪个不属于转换算子操作( )。 A. filter() B. map() C. collect() D. reduceByKey() 25.下列选项中,能使RDD产生宽依赖的是( )。 A. groupByKey() B. filter() C. flatMap() D. map() 26.Rdd持久化机制中,默认的存储级别是( )。 A. MEMORY_ONLY B. MEMORY_ONLY_SER C. MEMORY_AND_DISK D. MEMORY_AND_DISK_SER 27.下面哪个不是Spark的部署方式( )。 A. Spark on YARN B. Spark on Mesos C. Spark on Hadoop D. Standalone 28. Spark的阶段划分的依据是产生( )。 A.宽依赖 B.窄依赖 C.Task D.Application 填空题 1.Spark 的两种共享变量是广播变量______________。 2.RDD 的转换操作具有______________特性,即它们不会立即执行。 3.Spark 生态系统包含 Spark Core、Spark SQL、________、MLlib、GraphX 以及独立调度组件。 4.Spark 提交作业时,使用本地模式并分配 4 个 CPU 核心的master参数值 是________。 5.Spark 提交作业时,连接到 YARN 集群的master参数值是________。 6.Hadoop 集群中负责资源调度的组件是________。 7.Spark 的调度模式分为 FIFO ______________。 8.Spark Streaming 的______________机制用于保证数据处理的精确一次语义。 9.Spark RDD 的缓存级别默认是________。 10.Spark SQL 中,______________是 Spark 2.0 引入的统一入口点。 11.Spark Streaming中对DStream的转换操作会转变成对 的 转换操作。 判断题 1.Spark 的 DStream 是基于 RDD 的流式计算抽象。( ) 2.Spark 的累加器(Accumulator)支持跨任务修改。( ) 3.HDFS 不适合存储大量小文件。( ) 4.Hadoop的MapReduce作业中,通过磁盘进行数据Shuffle。( ) 5.Spark 的 RDD 是不可变的分布式数据集( ) 6.Spark Streaming 是批处理框架,不支持实时处理( ) 7.DataFrame 是带有 schema 信息的 RDD( ) 8.Spark 的 shuffle 操作一定会导致数据的磁盘落盘( ) 9.HBase 的行键(Row Key)可不按字典。( ) 10.Hive 的分区可以提高查询效率。( ) 11.YARN 的 ResourceManager 负责全局资源调度。( ) 12.Spark 的广播变量可以在每个 Executor 上缓存一份( ) 13.Spark SQL 的 DataFrame API 只支持 SQL 查询,不支持函数式操作( ) 14.Spark 的 checkpoint 操作会将 RDD 保存到 HDFS 等可靠存储中( ) 15.Spark SQL 不支持直接操作 Hive 。( ) 16.HBase 的列族在创建后不可修改。( ) 17.Spark 任务的 Executor 可以运行多个 Task。( ) 18.Spark 的 Executor 是运行在 Worker 节点上的进程( ) 19.Spark 的 DStream 转换操作是懒执行的( ) 20.Spark 的内存管理模型中,Execution 内存 Storage 内存可以动态调整( ) 简述题 1.简述Spark中RDD的容错机制(Lineage)的工作原理。 2.对比Spark中reduceByKeygroupByKey的性能差异,并说明原因。 3.简述 Spark 的宽窄依赖及其对调度的影响。 4.比较 Spark 的 DataFrame Dataset 的异同。 5.简述Kafka生产者(Producer)消费者(Consumer)的主要功能及关键特性? 6.简述 Spark 的 Shuffle 过程及其优化策略。 7.SparkSQL中DataFrameDataset的主要区别是什么?各适用于什么场景? 8.简述HDFS中NameNodeDataNode的分工协作机制。 9.列举Spark的三种主要部署模式,并说明每种模式的适用场景。 10.如何理解 Spark 的弹性分布式数据集(RDD)? 11.简述 Spark 的资源调度流程。 12.解释 Spark 的容错机制。 13.SparkHadoop有什么区别? 程题 编写函数输出1~100之间所有素数。 使用 Scala 语言按以下要求,编写出程(1) 创建一个名字为 Person 的类,包含私有属性 name(String) age(Int) (2) 提供构造方法初始化这两个属性 (3) 定义一个方法 sayHello,输出 "Hello, my name is [name], I'm [age] years old." (4) 创建一个 Object 包含 main 方法,实例化 Person 类并调用 sayHello 方法 给定RDD Array(1, 2, 3, 4, 5),使用mapreduce算子计算所有元素的立方。 创建一个包含元素 "spark", "hadoop", "flume", "date" 的 RDD,过滤出长度大于 5 的元素。 统计文本date.txt中每个单词出现的频率,并输出频率最高的前 3 个单词。 创建一个 RDD 数据为 Array (3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5),计算所有元素的平方。 现有一份用户访问日志数据 datas/access.log,格式为 "user_id|timestamp|url",要求读取文件创建 RDD,统计每个用户的访问次数。(5 分) 示例数据: u001|2023-01-01 10:00:00|/home u002|2023-01-01 10:01:00|/profile u001|2023-01-01 10:02:00|/cart 答案: val logRDD = sc.textFile("datas/access.log")【1分】 val userCounts = logRDD .map(line => line.split("\\|")(0)) // 提取 user_id【1分】 .map(userId => (userId, 1)) // 映射为 (user_id, 1)【1分】 .reduceByKey(_ + _) // 聚合计数【1分】 userCounts.collect().foreach(println)【1分】 现有sales.txt 文件,记录了某商店的销售数据,请使用 Spark 编写程计算每个类别的总销售额。数据格式为 "product_id,category,price,quantity",(10 分) 示例数据: P001,electronics,5000,2 P002,clothing,200,5 P003,electronics,3000,1 8.1 //创建 SparkSession 8.2 //读取文件创建 DataFrame 8.3 //计算每个类别的总销售额 8.4 //显示结果 8.5 //关闭 SparkSession /import org.apache.spark.sql.SparkSession // 5.1 创建 SparkSession val spark = SparkSession.builder() .appName("CategorySales") .master("local[*]") .getOrCreate() // 5.2 读取文件创建 DataFrame val df = spark.read .option("header", "false") .option("delimiter", ",") .csv("sales.txt") .toDF("product_id", "category", "price", "quantity") // 5.3 计算每个类别的总销售额 import spark.implicits._ val resultDF = df .select($"category", ($"price" * $"quantity").as("total")) .groupBy("category") .sum("total") .withColumnRenamed("sum(total)", "total_sales") // 5.4 显示结果 resultDF.show() // 5.5 关闭 SparkSession spark.stop() 现在有一份服务器日志数据datas/apache.log,要求读取文件创建RDD获取时间是2022年5月17日的请求数据(8分) 部分数据如下: 83.149.9.216--16/05/2022:10:05:03+0000GET /presentations/logstash-monitorama-2013/images/kibana-search.png 83.149.9.216--17/05/2022:10:05:43+0000ET /presentations/logstash-monitorama-2013/images/kibana-dashboard3.png 83.149.9.216--18/05/2022:10:05:47+0000GET /presentations/logstash-monitorama-2013/plugin/highlight/highlight.js // 创建 SparkContext val conf = new SparkConf().setMaster("local[*]").setAppName("test") val sc = new SparkContext(conf) // 读取日志文件 val logRDD = sc.textFile("datas/apache.log") // 筛选日期为 17/05/2022 的记录 val filteredRDD = logRDD.filter { line => val datePattern = """\s17/05/2022:""".r // 匹配日期模式 datePattern.findFirstIn(line).isDefined } // 输出筛选结果 filteredRDD.foreach(println) // 关闭 SparkContext sc.stop() 现有Kafka 的主题test-topic,持续发送英文句子(每行一个句子)。使用 Spark Streaming 实时统计每个单词的出现次数,每5秒输出一次结果。请写出核心代码。(8分) // 创建 SparkConf StreamingContext val conf = new SparkConf().setAppName("WordCount").setMaster("local[2]") val ssc = new StreamingContext(conf, Seconds(5)) // 批处理间隔5秒 // 创建 DStream val stream = KafkaUtils.createDirectStream[String, String]( ssc, LocationStrategies.PreferConsistent, ConsumerStrategies.Subscribe[String, String](Set("test-topic"), kafkaParams) ) // 核心处理逻辑 val lines = stream.map(_.value()) val words = lines.flatMap(_.split(" ")) val wordCounts = words.map(w => (w, 1)).reduceByKey(_ + _) // 输出结果 wordCounts.print() // 启动流计算 ssc.start() ssc.awaitTermination() 有50W个京东店铺,每个顾客访客访问任何一个店铺的任何一个商品时都会产生一条访问日志,访问日志存储的名为Visit,访客的用户id为user_id,被访问的店铺名称shop,数据如下:(10分) user_id shop u1 a u2 b u1 b u1 a u3 c u4 b 请使用spark sql语句统计: 6.1每个店铺的UV(访客数)(4分) 6.2每个店铺访问次数top3的访客信息。输出店铺名称、访客id、访问次数(6分) 6.1// 创建DataFrame val visitDF = data.toDF("user_id", "shop") // 注册为临时 visitDF.createOrReplaceTempView("Visit") val uvResult = spark.sql( """SELECT shop, COUNT(DISTINCT user_id) AS uv FROM Visit GROUP BY shop ORDER BY uv DESC""" ) 6.2val top3Result = spark.sql( """WITH ranked_visits AS ( SELECT shop, user_id, COUNT(*) AS visit_count, ROW_NUMBER() OVER ( PARTITION BY shop ORDER BY COUNT(*) DESC ) AS rank FROM Visit GROUP BY shop, user_id ) SELECT shop, user_id, visit_count FROM ranked_visits WHERE rank <= 3 ORDER BY shop, rank""" ) 现有某电商平台的用户评分数据 ratings.csv,格式为 "user_id,movie_id,rating,timestamp",请使用 Spark SQL 查询出评分最高的前 10 部电影(需考虑评分人数不少于 10 人)。(15 分) 示例数据: 1,101,5,1612345678 2,101,4,1612345679 3,102,3,1612345680 ... 请给出关键 SQL 查询语句。 import org.apache.spark.sql.SparkSession object MovieRatingAnalysis { def main(args: Array[String]): Unit = { // 创建 SparkSession val spark = SparkSession.builder() .appName("TopMoviesByRating") .master("local[*]") .getOrCreate() // 读取数据 val ratingsDF = spark.read .option("header", "false") .option("inferSchema", "true") .csv("ratings.csv") .toDF("user_id", "movie_id", "rating", "timestamp") // 注册临时视图 ratingsDF.createOrReplaceTempView("ratings") // 执行 SQL 查询 val topMoviesDF = spark.sql( """ SELECT movie_id, AVG(rating) AS avg_rating, COUNT(*) AS num_ratings FROM ratings 回答问题
06-20
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值