it-swarm.com.de

auswählen eines Bereichs von Elementen in einem Array-Funkenfeld

Ich verwende spark-Shell, um die folgenden Operationen auszuführen.

Kürzlich wurde eine Tabelle mit einer Array-Spalte in spark-sql geladen.

Hier ist die DDL für das gleiche:

create table test_emp_arr{
    dept_id string,
    dept_nm string,
    emp_details Array<string>
}

die Daten sehen ungefähr so ​​aus

+-------+-------+-------------------------------+
|dept_id|dept_nm|                     emp_details|
+-------+-------+-------------------------------+
|     10|Finance|[Jon, Snow, Castle, Black, Ned]|
|     20|     IT|            [Ned, is, no, more]|
+-------+-------+-------------------------------+

Ich kann die emp_details-Spalte in etwa wie folgt abfragen:

sqlContext.sql("select emp_details[0] from emp_details").show

Problem

Ich möchte eine Reihe von Elementen in der Auflistung abfragen:

Erwartete Abfrage funktioniert

sqlContext.sql("select emp_details[0-2] from emp_details").show

oder 

sqlContext.sql("select emp_details[0:2] from emp_details").show

Erwartete Ausgabe

+-------------------+
|        emp_details|
+-------------------+
|[Jon, Snow, Castle]|
|      [Ned, is, no]|
+-------------------+

In reiner Scala, wenn ich ein Array habe, etwas wie:

val emp_details = Array("Jon","Snow","Castle","Black")

Ich kann die Elemente von 0 bis 2 mit verwenden 

emp_details.slice(0,3)

bringt mich zurück 

Array(Jon, Snow,Castle)

Ich kann die obige Operation des Arrays nicht in spark-sql anwenden.

Vielen Dank

6
thinkinbee

Hier ist eine Lösung mit einer User Defined Function , die den Vorteil hat, für jede gewünschte Slice-Größe zu arbeiten. Es erstellt einfach eine UDF-Funktion um die slice-Methode von scala:

import sqlContext.implicits._
import org.Apache.spark.sql.functions._

val slice = udf((array : Seq[String], from : Int, to : Int) => array.slice(from,to))

Beispiel mit einem Beispiel Ihrer Daten:

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

Erzeugt die erwartete Ausgabe

+--------------------+-------------------+
|         emp_details|              slice|
+--------------------+-------------------+
|[Jon, Snow, Castl...|[Jon, Snow, Castle]|
+--------------------+-------------------+

Sie können die UDF auch in Ihrer sqlContext registrieren und so verwenden 

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)")

Sie benötigen mit dieser Lösung keine lit mehr

7
cheseaux

Edit2: Für diejenigen, die udf auf Kosten der Lesbarkeit vermeiden wollen ;-)

Wenn Sie es wirklich in einem Schritt tun möchten, müssen Sie mit Scala eine Lambda-Funktion erstellen, die eine Folge von Column zurückgibt, und sie in ein Array einschließen. Dies ist etwas kompliziert, aber es ist ein Schritt:

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

Der _:* wirkt etwas magisch, um eine Liste an eine sogenannte Variadic-Funktion zu übergeben (in diesem Fall array, die das SQL-Array aufbaut). Ich würde jedoch davon abraten, diese Lösung so zu verwenden, wie sie ist. Setzen Sie die Lambda-Funktion in eine benannte Funktion

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

für die Lesbarkeit des Codes. Beachten Sie, dass das Festhalten an Column-Ausdrücken (ohne Verwendung von udf) im Allgemeinen bessere Ergebnisse liefert.

Bearbeiten: Um dies in einer SQL-Anweisung zu tun (wie Sie es in Ihrer Frage stellen ...), würden Sie nach der gleichen Logik die SQL-Abfrage mit der Scala-Logik generieren (nicht sagen, dass dies die lesbarste ist).

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

beachten Sie, dass Sie until durch to ersetzen können, um das letzte Element anzugeben, und nicht das Element, an dem die Iteration beendet wird.

2
Wilmerton

Seit Spark 2.4 können Sie die slice-Funktion verwenden. In Python ):

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

Collection-Funktion: Gibt ein Array zurück, das alle Elemente in x vom Start des Index (oder vom Start aus, wenn der Start negativ ist) mit der angegebenen Länge enthält.

...

Neu in Version 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]|
+-------------------+

In Scala

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

Gibt ein Array zurück, das alle Elemente in x vom Indexbeginn (oder von Anfang an, wenn Start negativ ist) mit der angegebenen Länge enthält.

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

Das gleiche kann natürlich gemacht werden inSQL

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

Wichtig :

Beachten Sie, dass die Werte im Gegensatz zu Seq.slice von Null aus indiziert werden und das zweite Argument die Länge und nicht die Endposition ist.

1
user6910411

Sie können die Funktion array verwenden, um aus den drei Werten ein neues Array zu erstellen:

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

verwenden Sie selecrExpr () und split () - Funktion in Apache spark.

zum Beispiel :

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

Für diejenigen unter Ihnen, die nicht mit Spark <2.4) arbeiten und die Funktion slice nicht haben, ist hier eine Lösung in pySpark (Scala wäre sehr ähnlich), die kein udfs verwendet Stattdessen werden die spark sql Funktionen concat_ws, substring_index und split.

Dies funktioniert nur mit String-Arrays. Damit es mit Arrays anderer Typen funktioniert, müssen Sie sie zuerst in Strings umwandeln und dann auf den ursprünglichen Typ zurückwandeln, nachdem Sie das Array in Scheiben geschnitten haben.

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

Hier ist mein generisches Slice-UDF, das Array mit einem beliebigen Typ unterstützt. Ein bisschen hässlich, weil Sie den Elementtyp im Voraus kennen müssen.

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

Verschachtelten Split verwenden:

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