web-dev-qa-db-ja.com

配列内の要素の範囲を選択する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に適用できません。

ありがとう

10
thinkinbee

これは ユーザー定義関数 を使用したソリューションで、必要なスライスサイズで機能するという利点があります。 scala builtin slice methodの周りに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は必要ありません

9
cheseaux

Spark 2.4なので、slice関数を使用できます。 InPython ):

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

コレクション関数:指定された長さの、インデックスstartから(またはstartが負の場合はendから)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]|
+-------------------+

InScala

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

指定された長さの、インデックスstartから(またはstartが負の場合はendから)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番目の引数は終了位置ではなく長さであることに注意してください。

8
zero323

Edit2:読みやすさを犠牲にしてudfを避けたい人のために;-)

本当に1つのステップで実行したい場合は、Scalaを使用して、Columnのシーケンスを返すラムダ関数を作成し、それを配列にラップする必要があります。これは少し複雑ですが、それは1つのステップです。

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配列を構成する)にリストを渡します。ただし、このソリューションをそのまま使用することはお勧めしません。ラムダ関数を名前付き関数に入れる

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

コードを読みやすくするため。一般に、([udvar]を使用せずに)Column式を使用すると、パフォーマンスが向上します。

編集:(質問で尋ねるように)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]|
+-------------------------------+-------------------+

untiltoに置き換えると、反復が停止した要素ではなく、最後に取得した要素を提供できることに注意してください。

3
Wilmerton

関数arrayを使用して、3つの値から新しい配列を作成できます。

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

apache SparkでselecrExpr()およびsplit()関数を使用します。

例えば ​​:

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

Spark <2.4を使用してスタックし、slice関数がない場合は、udfsを使用しないpySpark(Scalaも非常に似ている)のソリューションを次に示します。 。代わりに、spark sql functions concat_wssubstring_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]      |
+-------------------------------+-------------------+
0
Clay

これが私の一般的なスライス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 = 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]|
+-------+-------+-------------------+
0
MarcelG