it-swarm.com.ru

выбор диапазона элементов в массиве spark sql

Я использую 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.

Спасибо

6
thinkinbee

Вот решение, использующее определенную пользователем функцию , которая имеет преимущество работы с любым размером среза, который вы хотите. Он просто строит функцию UDF на основе встроенного в Scala метода slice:

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

Вы также можете зарегистрировать UDF в вашей sqlContext и использовать его следующим образом 

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 с этим решением

7
cheseaux

Edit2: для тех, кто хочет избежать udf за счет читабельности ;-)

Если вы действительно хотите сделать это за один шаг, вам придется использовать Scala для создания лямбда-функции, возвращающей последовательность 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]|
+-------------------------------+-------------------+

_:* работает немного волшебно, передавая список так называемой функции variadic (в данном случае array, которая создает массив sql). Но я бы посоветовал не использовать это решение как есть. положить лямбда-функцию в именованную функцию

def slice(from: Int, to: Int) = array((from until to).map(i => $"emp_details"(i)):_*))

для читабельности кода. Обратите внимание, что в целом соблюдение выражений Column (без использования `udf) дает лучшие результаты.

Правка: чтобы сделать это в SQL-выражении (как вы задаете в своем вопросе ...), следуя той же логике, вы должны сгенерировать SQL-запрос, используя логику scala (не говоря, что это наиболее читаемый)

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, чтобы предоставить последний взятый элемент, а не элемент, на котором итерация останавливается.

2
Wilmerton

Начиная с Spark 2.4 вы можете использовать функцию slice. В Python ):

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

Функция сбора: возвращает массив, содержащий все элементы в 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]|
+-------------------+

В Scala

def slice(x: Column, start: Int, length: Int): Column

Возвращает массив, содержащий все элементы в 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]|
+-------------------+

То же самое, конечно, можно сделать в SQL

SELECT slice(emp_details, 1, 3) AS emp_details FROM df

Важный:

Обратите внимание, что в отличие от Seq.slice , значения индексируются с нуля, а вторым аргументом является длина, а не конечная позиция.

1
user6910411

Вы можете использовать функцию 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]|
// +-------------------+
1
Tzach Zohar

используйте selecrExpr () и split () функцию в искре Apache.

например :

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

Вот мой общий срез 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))
0
Bewang

Использовать вложенный сплит:

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 = [email protected]

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