我使用spark-shell
执行以下操作。
最近在spark-sql中加载了一个包含数组列的表。
这是相同的DDL:
create table test_emp_arr{
dept_id string,
dept_nm string,
emp_details Array<string>
}
数据看起来像这样
+-------+-------+-------------------------------+
|dept_id|dept_nm| emp_details|
+-------+-------+-------------------------------+
| 10|Finance|[Jon, Snow, Castle, Black, Ned]|
| 20| IT| [Ned, is, no, more]|
+-------+-------+-------------------------------+
我可以查询emp_details列,如下所示:
sqlContext.sql("select emp_details[0] from emp_details").show
问题
我想查询集合中的一系列元素:
预期的查询工作
sqlContext.sql("select emp_details[0-2] from emp_details").show
或
sqlContext.sql("select emp_details[0:2] from emp_details").show
预期输出
+-------------------+
| emp_details|
+-------------------+
|[Jon, Snow, Castle]|
| [Ned, is, no]|
+-------------------+
在纯Scala中,如果我有一个数组:
val emp_details = Array("Jon","Snow","Castle","Black")
我可以使用
获取0到2范围内的元素emp_details.slice(0,3)
给我回报
Array(Jon, Snow,Castle)
我无法在spark-sql中应用上述数组操作。
由于
答案 0 :(得分:8)
这是一个使用User Defined Function的解决方案,它具有适用于您想要的任何切片大小的优势。它只是围绕scala builtin slice
方法构建一个UDF函数:
import sqlContext.implicits._
import org.apache.spark.sql.functions._
val slice = udf((array : Seq[String], from : Int, to : Int) => array.slice(from,to))
您的数据样本示例:
val df = sqlContext.sql("select array('Jon', 'Snow', 'Castle', 'Black', 'Ned') as emp_details")
df.withColumn("slice", slice($"emp_details", lit(0), lit(3))).show
产生预期的输出
+--------------------+-------------------+
| emp_details| slice|
+--------------------+-------------------+
|[Jon, Snow, Castl...|[Jon, Snow, Castle]|
+--------------------+-------------------+
您也可以在sqlContext
中注册UDF并像这样使用
sqlContext.udf.register("slice", (array : Seq[String], from : Int, to : Int) => array.slice(from,to))
sqlContext.sql("select array('Jon','Snow','Castle','Black','Ned'),slice(array('Jon','Snow','Castle','Black','Ned'),0,3)")
此解决方案不再需要lit
答案 1 :(得分:4)
从Spark 2.4开始,您可以使用slice
函数。 In Python):
pyspark.sql.functions.slice(x, start, length)
集合函数:返回一个数组,该数组包含从索引开头(如果start为负,则从结尾开始)中指定长度的x中的所有元素。
...
2.4版的新功能。
from pyspark.sql.functions import slice
df = spark.createDataFrame([
(10, "Finance", ["Jon", "Snow", "Castle", "Black", "Ned"]),
(20, "IT", ["Ned", "is", "no", "more"])
], ("dept_id", "dept_nm", "emp_details"))
df.select(slice("emp_details", 1, 3).alias("empt_details")).show()
+-------------------+
| empt_details|
+-------------------+
|[Jon, Snow, Castle]|
| [Ned, is, no]|
+-------------------+
def slice(x: Column, start: Int, length: Int): Column
从索引开头(如果start为负,则从结尾开始)以指定的长度返回包含x中所有元素的数组。
import org.apache.spark.sql.functions.slice
val df = Seq(
(10, "Finance", Seq("Jon", "Snow", "Castle", "Black", "Ned")),
(20, "IT", Seq("Ned", "is", "no", "more"))
).toDF("dept_id", "dept_nm", "emp_details")
df.select(slice($"emp_details", 1, 3) as "empt_details").show
+-------------------+
| empt_details|
+-------------------+
|[Jon, Snow, Castle]|
| [Ned, is, no]|
+-------------------+
当然可以in SQL
SELECT slice(emp_details, 1, 3) AS emp_details FROM df
重要:
请注意,与Seq.slice
不同的是,值从零开始索引,第二个参数是长度,而不是结束位置。
答案 2 :(得分:3)
Edit2:谁想以牺牲可读性为代价来避免使用udf; - )
如果你真的想一步完成,你将不得不使用Scala创建一个lambda函数,返回一个Column
序列并将其包装在一个数组中。这有点牵扯,但这是一步:
val df = List(List("Jon", "Snow", "Castle", "Black", "Ned")).toDF("emp_details")
df.withColumn("slice", array((0 until 3).map(i => $"emp_details"(i)):_*)).show(false)
+-------------------------------+-------------------+
|emp_details |slice |
+-------------------------------+-------------------+
|[Jon, Snow, Castle, Black, Ned]|[Jon, Snow, Castle]|
+-------------------------------+-------------------+
_:*
将列表传递给所谓的可变参数函数(在这种情况下为array
,构造sql数组),这有点神奇。但我建议不要按原样使用此解决方案。将lambda函数放在命名函数中
def slice(from: Int, to: Int) = array((from until to).map(i => $"emp_details"(i)):_*))
代码可读性。请注意,一般来说,坚持Column
表达式(不使用`udf)会有更好的表现。
编辑:为了在sql语句中执行它(正如您在问题中提到的那样...),遵循相同的逻辑,您将使用scala逻辑生成sql查询(不是说它是最可读的)
def sliceSql(emp_details: String, from: Int, to: Int): String = "Array(" + (from until to).map(i => "emp_details["+i.toString+"]").mkString(",") + ")"
val sqlQuery = "select emp_details,"+ sliceSql("emp_details",0,3) + "as slice from emp_details"
sqlContext.sql(sqlQuery).show
+-------------------------------+-------------------+
|emp_details |slice |
+-------------------------------+-------------------+
|[Jon, Snow, Castle, Black, Ned]|[Jon, Snow, Castle]|
+-------------------------------+-------------------+
请注意,您可以将until
替换为to
,以便提供最后一个元素,而不是迭代停止的元素。
答案 3 :(得分:1)
您可以使用函数array
从三个值中构建一个新数组:
import org.apache.spark.sql.functions._
val input = sqlContext.sql("select emp_details from emp_details")
val arr: Column = col("emp_details")
val result = input.select(array(arr(0), arr(1), arr(2)) as "emp_details")
val result.show()
// +-------------------+
// | emp_details|
// +-------------------+
// |[Jon, Snow, Castle]|
// | [Ned, is, no]|
// +-------------------+
答案 4 :(得分:0)
在apache spark中使用 selecrExpr()和 split()功能。
例如:
fs.selectExpr("((split(emp_details, ','))[0]) as e1,((split(emp_details, ','))[1]) as e2,((split(emp_details, ','))[2]) as e3);
答案 5 :(得分:0)
这是我的通用切片UDF,支持任何类型的数组。有点难看因为你需要提前知道元素类型。
import org.apache.spark.sql.types._
import org.apache.spark.sql.functions._
def arraySlice(arr: Seq[AnyRef], from: Int, until: Int): Seq[AnyRef] =
if (arr == null) null else arr.slice(from, until)
def slice(elemType: DataType): UserDefinedFunction =
udf(arraySlice _, ArrayType(elemType)
fs.select(slice(StringType)($"emp_details", 1, 2))
答案 6 :(得分:0)
对于那些使用Spark <2.4且没有slice
功能的用户,这是pySpark中的一个解决方案(Scala非常相似),它不使用udfs。相反,它使用spark sql函数concat_ws
,substring_index
和split
。
这仅适用于字符串数组。为了使其与其他类型的数组一起使用,您必须首先将它们转换为字符串,然后在对数组进行“切片”之后再转换回原始类型。
from pyspark.sql import SparkSession
from pyspark.sql import functions as F
spark = (SparkSession.builder
.master('yarn')
.appName("array_slice")
.getOrCreate()
)
emp_details = [
["Jon", "Snow", "Castle", "Black", "Ned"],
["Ned", "is", "no", "more"]
]
df1 = spark.createDataFrame(
[tuple([emp]) for emp in emp_details],
["emp_details"]
)
df1.show(truncate=False)
+-------------------------------+
|emp_details |
+-------------------------------+
|[Jon, Snow, Castle, Black, Ned]|
|[Ned, is, no, more] |
+-------------------------------+
last_string = 2
df2 = (
df1
.withColumn('last_string', (F.lit(last_string)))
.withColumn('concat', F.concat_ws(" ", F.col('emp_details')))
.withColumn('slice', F.expr("substring_index(concat, ' ', last_string + 1)" ))
.withColumn('slice', F.split(F.col('slice'), ' '))
.select('emp_details', 'slice')
)
df2.show(truncate=False)
+-------------------------------+-------------------+
|emp_details |slice |
+-------------------------------+-------------------+
|[Jon, Snow, Castle, Black, Ned]|[Jon, Snow, Castle]|
|[Ned, is, no, more] |[Ned, is, no] |
+-------------------------------+-------------------+
答案 7 :(得分:-1)
使用嵌套拆分:
split(split(concat_ws(',',emp_details),concat(',',emp_details[3]))[0],',')
scala> import org.apache.spark.sql.SparkSession
import org.apache.spark.sql.SparkSession
scala> val spark=SparkSession.builder().getOrCreate()
spark: org.apache.spark.sql.SparkSession = org.apache.spark.sql.SparkSession@1d637673
scala> val df = spark.read.json("file:///Users/gengmei/Desktop/test/test.json")
18/12/11 10:09:32 WARN ObjectStore: Failed to get database global_temp, returning NoSuchObjectException
df: org.apache.spark.sql.DataFrame = [dept_id: bigint, dept_nm: string ... 1 more field]
scala> df.createOrReplaceTempView("raw_data")
scala> df.show()
+-------+-------+--------------------+
|dept_id|dept_nm| emp_details|
+-------+-------+--------------------+
| 10|Finance|[Jon, Snow, Castl...|
| 20| IT| [Ned, is, no, more]|
+-------+-------+--------------------+
scala> val df2 = spark.sql(
| s"""
| |select dept_id,dept_nm,split(split(concat_ws(',',emp_details),concat(',',emp_details[3]))[0],',') as emp_details from raw_data
| """)
df2: org.apache.spark.sql.DataFrame = [dept_id: bigint, dept_nm: string ... 1 more field]
scala> df2.show()
+-------+-------+-------------------+
|dept_id|dept_nm| emp_details|
+-------+-------+-------------------+
| 10|Finance|[Jon, Snow, Castle]|
| 20| IT| [Ned, is, no]|
+-------+-------+-------------------+