选择数组中的元素范围



我使用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)

返回me

Array(Jon, Snow,Castle)

我无法在spark-sql中应用上述数组操作。

谢谢

从Spark 2.4开始可以使用slice函数。Python在):

pyspark.sql.functions.slice(x, start, length)

集合函数:返回一个数组,其中包含x中从索引start开始(如果start为负数,则从末尾开始)指定长度的所有元素。

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]|
+-------------------+
Scala在<<p> strong>
def slice(x: Column, start: Int, length: Int): Column

返回一个数组,其中包含x中从索引start开始(如果start为负数,则从末尾开始)指定长度的所有元素。

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]|
+-------------------+
同样的事情当然也可以在SQL 中完成
SELECT slice(emp_details, 1, 3) AS emp_details FROM df
重要

:

请注意,与Seq.slice不同,值从0开始索引,第二个参数是长度,而不是结束位置。

这是一个使用用户定义函数的解决方案,它具有适用于任何您想要的切片大小的优点。它只是围绕scala内置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

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,以便提供获取的最后一个元素,而不是迭代停止的元素。

您可以使用array函数从三个值中构建一个新的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]|
// +-------------------+

在apache spark中使用selecrExpr()split()函数

例如:

fs.selectExpr("((split(emp_details, ','))[0]) as e1,((split(emp_details, ','))[1]) as e2,((split(emp_details, ','))[2]) as e3);

这是我的通用切片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))

对于那些一直在使用Spark的人;2.4并且没有slice函数,这里是pySpark (Scala将非常相似)中不使用udfs的解决方案。相反,它使用spark sql函数concat_ws, substring_indexsplit

这只适用于字符串数组。要使它与其他类型的数组一起工作,您必须首先将它们强制转换为字符串,然后在对数组进行"切片"后将其强制转换回原始类型。

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]      |
+-------------------------------+-------------------+

使用嵌套分割:

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]|
+-------+-------+-------------------+

相关内容

  • 没有找到相关文章

最新更新