私が使う 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に適用できません。
ありがとう
これは ユーザー定義関数 を使用したソリューションで、必要なスライスサイズで機能するという利点があります。 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
は必要ありません
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]|
+-------------------+
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番目の引数は終了位置ではなく長さであることに注意してください。
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]|
+-------------------------------+-------------------+
until
をto
に置き換えると、反復が停止した要素ではなく、最後に取得した要素を提供できることに注意してください。
関数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]|
// +-------------------+
apache SparkでselecrExpr()およびsplit()関数を使用します。
例えば :
fs.selectExpr("((split(emp_details, ','))[0]) as e1,((split(emp_details, ','))[1]) as e2,((split(emp_details, ','))[2]) as e3);
Spark <2.4を使用してスタックし、slice
関数がない場合は、udfsを使用しないpySpark(Scalaも非常に似ている)のソリューションを次に示します。 。代わりに、spark sql functions 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] |
+-------------------------------+-------------------+
これが私の一般的なスライス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))
ネストされた分割を使用:
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]|
+-------+-------+-------------------+