Spark SQL добавить столбец / обновить накопить значение - PullRequest
0 голосов
/ 09 ноября 2018

У меня есть следующий DataFrame:

name,email,phone,country
------------------------------------------------
[Mike,mike@example.com,+91-9999999999,Italy]
[Alex,alex@example.com,+91-9999999998,France]
[John,john@example.com,+1-1111111111,United States]
[Donald,donald@example.com,+1-2222222222,United States]
[Dan,dan@example.com,+91-9999444999,Poland]
[Scott,scott@example.com,+91-9111999998,Spain]
[Rob,rob@example.com,+91-9114444998,Italy]

отображается как временная таблица tagged_users:

resultDf.createOrReplaceTempView("tagged_users")

Мне нужно добавить дополнительный столбец tag к этому фрейму данных и назначить вычисляемые теги для различных условий SQL, которые описаны на следующей карте (ключ - имя тега, значение - условие для предложения WHERE)

val tags = Map(
  "big" -> "country IN (SELECT * FROM big_countries)",
  "medium" -> "country IN (SELECT * FROM medium_countries)",
  //2000 other different tags and conditions
  "sometag" -> "name = 'Donald' AND email = 'donald@example.com' AND phone = '+1-2222222222'"
  )

У меня есть следующие DataFrames (как словари данных), чтобы иметь возможность использовать их в запросе SQL:

Seq("Italy", "France", "United States", "Spain").toDF("country").createOrReplaceTempView("big_countries")
Seq("Poland", "Hungary", "Spain").toDF("country").createOrReplaceTempView("medium_countries")

Я хочу проверить каждую строку в моей таблице tagged_users и назначить ей соответствующие теги. Я попытался реализовать следующую логику для достижения этой цели:

tags.foreach {
  case (tag, tagCondition) => {
    resultDf = spark.sql(buildTagQuery(tag, tagCondition, "tagged_users"))
       .withColumn("tag", lit(tag).cast(StringType))
  }
}

def buildTagQuery(tag: String, tagCondition: String, table: String): String = {
    f"SELECT * FROM $table WHERE $tagCondition"
}

но сейчас я не знаю, как накапливать теги и не переопределять их. Прямо сейчас в результате у меня есть следующий DataFrame:

name,email,phone,country,tag
Dan,dan@example.com,+91-9999444999,Poland,medium
Scott,scott@example.com,+91-9111999998,Spain,medium

но мне нужно что-то вроде:

name,email,phone,country,tag
Mike,mike@example.com,+91-9999999999,Italy,big
Alex,alex@example.com,+91-9999999998,France,big
John,john@example.com,+1-1111111111,United States,big
Donald,donald@example.com,+1-2222222222,United States,(big|sometag)
Dan,dan@example.com,+91-9999444999,Poland,medium
Scott,scott@example.com,+91-9111999998,Spain,(big|medium)
Rob,rob@example.com,+91-9114444998,Italy,big

Обратите внимание, что Donal должно иметь 2 тега (big|sometag) и Scott должно иметь 2 тега (big|medium).

Пожалуйста, покажите, как это реализовать.

ОБНОВЛЕНО

val spark = SparkSession
  .builder()
  .appName("Java Spark SQL basic example")
  .config("spark.master", "local")
  .getOrCreate();

import spark.implicits._
import spark.sql

Seq("Italy", "France", "United States", "Spain").toDF("country").createOrReplaceTempView("big_countries")
Seq("Poland", "Hungary", "Spain").toDF("country").createOrReplaceTempView("medium_countries")

val df = Seq(
  ("Mike", "mike@example.com", "+91-9999999999", "Italy"),
  ("Alex", "alex@example.com", "+91-9999999998", "France"),
  ("John", "john@example.com", "+1-1111111111", "United States"),
  ("Donald", "donald@example.com", "+1-2222222222", "United States"),
  ("Dan", "dan@example.com", "+91-9999444999", "Poland"),
  ("Scott", "scott@example.com", "+91-9111999998", "Spain"),
  ("Rob", "rob@example.com", "+91-9114444998", "Italy")).toDF("name", "email", "phone", "country")

df.collect.foreach(println)

df.createOrReplaceTempView("tagged_users")

val tags = Map(
  "big" -> "country IN (SELECT * FROM big_countries)",
  "medium" -> "country IN (SELECT * FROM medium_countries)",
  "sometag" -> "name = 'Donald' AND email = 'donald@example.com' AND phone = '+1-2222222222'")

val sep_tag = tags.map((x) => { s"when array_contains(" + x._1 + ", country) then '" + x._1 + "' " }).mkString

val combine_sel_tag1 = tags.map((x) => { s" array_contains(" + x._1 + ",country) " }).mkString(" and ")

val combine_sel_tag2 = tags.map((x) => x._1).mkString(" '(", "|", ")' ")

val combine_sel_all = " case when " + combine_sel_tag1 + " then " + combine_sel_tag2 + sep_tag + " end as tags "

val crosqry = tags.map((x) => { s" cross join ( select collect_list(country) as " + x._1 + " from " + x._1 + "_countries) " + x._1 + "  " }).mkString

val qry = " select name,email,phone,country, " + combine_sel_all + " from tagged_users " + crosqry

spark.sql(qry).show

spark.stop()

не выполняется со следующим исключением:

Caused by: org.apache.spark.sql.catalyst.analysis.NoSuchTableException: Table or view 'sometag_countries' not found in database 'default';
    at org.apache.spark.sql.catalyst.catalog.ExternalCatalog$class.requireTableExists(ExternalCatalog.scala:48)
    at org.apache.spark.sql.catalyst.catalog.InMemoryCatalog.requireTableExists(InMemoryCatalog.scala:45)
    at org.apache.spark.sql.catalyst.catalog.InMemoryCatalog.getTable(InMemoryCatalog.scala:326)
    at org.apache.spark.sql.catalyst.catalog.ExternalCatalogWithListener.getTable(ExternalCatalogWithListener.scala:138)
    at org.apache.spark.sql.catalyst.catalog.SessionCatalog.lookupRelation(SessionCatalog.scala:701)
    at org.apache.spark.sql.catalyst.analysis.Analyzer$ResolveRelations$.org$apache$spark$sql$catalyst$analysis$Analyzer$ResolveRelations$$lookupTableFromCatalog(Analyzer.scala:730)
    ... 74 more

Ответы [ 3 ]

0 голосов
/ 09 ноября 2018

Проверьте это решение DF:

scala> val df = Seq(("Mike","mike@example.com","+91-9999999999","Italy"),
     | ("Alex","alex@example.com","+91-9999999998","France"),
     | ("John","john@example.com","+1-1111111111","United States"),
     | ("Donald","donald@example.com","+1-2222222222","United States"),
     | ("Dan","dan@example.com","+91-9999444999","Poland"),
     | ("Scott","scott@example.com","+91-9111999998","Spain"),
     | ("Rob","rob@example.com","+91-9114444998","Italy")
     | ).toDF("name","email","phone","country")
df: org.apache.spark.sql.DataFrame = [name: string, email: string ... 2 more fields]

scala> val dfbc=Seq("Italy", "France", "United States", "Spain").toDF("country")
dfbc: org.apache.spark.sql.DataFrame = [country: string]

scala> val dfmc=Seq("Poland", "Hungary", "Spain").toDF("country")
dfmc: org.apache.spark.sql.DataFrame = [country: string]

scala> val dfbc2=dfbc.agg(collect_list('country).as("bcountry"))
dfbc2: org.apache.spark.sql.DataFrame = [bcountry: array<string>]

scala> val dfmc2=dfmc.agg(collect_list('country).as("mcountry"))
dfmc2: org.apache.spark.sql.DataFrame = [mcountry: array<string>]

scala> val df2=df.crossJoin(dfbc2).crossJoin(dfmc2)
df2: org.apache.spark.sql.DataFrame = [name: string, email: string ... 4 more fields]

scala> df2.selectExpr("*","case when array_contains(bcountry,country) and array_contains(mcountry,country) then '(big|medium)' when array_contains(bcountry,country) then 'big' when array_contains(mcountry,country) then 'medium' else 'none' end as `tags`").select("name","email","phone","country","tags").show(false)
+------+------------------+--------------+-------------+------------+
|name  |email             |phone         |country      |tags        |
+------+------------------+--------------+-------------+------------+
|Mike  |mike@example.com  |+91-9999999999|Italy        |big         |
|Alex  |alex@example.com  |+91-9999999998|France       |big         |
|John  |john@example.com  |+1-1111111111 |United States|big         |
|Donald|donald@example.com|+1-2222222222 |United States|big         |
|Dan   |dan@example.com   |+91-9999444999|Poland       |medium      |
|Scott |scott@example.com |+91-9111999998|Spain        |(big|medium)|
|Rob   |rob@example.com   |+91-9114444998|Italy        |big         |
+------+------------------+--------------+-------------+------------+


scala>

Подход SQL

scala> Seq(("Mike","mike@example.com","+91-9999999999","Italy"),
     |       ("Alex","alex@example.com","+91-9999999998","France"),
     |       ("John","john@example.com","+1-1111111111","United States"),
     |       ("Donald","donald@example.com","+1-2222222222","United States"),
     |       ("Dan","dan@example.com","+91-9999444999","Poland"),
     |       ("Scott","scott@example.com","+91-9111999998","Spain"),
     |       ("Rob","rob@example.com","+91-9114444998","Italy")
     |       ).toDF("name","email","phone","country").createOrReplaceTempView("tagged_users")

scala> Seq("Italy", "France", "United States", "Spain").toDF("country").createOrReplaceTempView("big_countries")

scala> Seq("Poland", "Hungary", "Spain").toDF("country").createOrReplaceTempView("medium_countries")

scala> spark.sql(""" select name,email,phone,country,case when array_contains(bc,country) and array_contains(mc,country) then '(big|medium)' when array_contains(bc,country) then 'big' when array_contains(mc,country) then 'medium' else 'none' end as tags from tagged_users cross join ( select collect_list(country) as bc from big_countries ) b cross join ( select collect_list(country) as mc from medium_countries ) c """).show(false)
+------+------------------+--------------+-------------+------------+
|name  |email             |phone         |country      |tags        |
+------+------------------+--------------+-------------+------------+
|Mike  |mike@example.com  |+91-9999999999|Italy        |big         |
|Alex  |alex@example.com  |+91-9999999998|France       |big         |
|John  |john@example.com  |+1-1111111111 |United States|big         |
|Donald|donald@example.com|+1-2222222222 |United States|big         |
|Dan   |dan@example.com   |+91-9999444999|Poland       |medium      |
|Scott |scott@example.com |+91-9111999998|Spain        |(big|medium)|
|Rob   |rob@example.com   |+91-9114444998|Italy        |big         |
+------+------------------+--------------+-------------+------------+


scala>

Перебор тегов

scala> val tags = Map(
     |   "big" -> "country IN (SELECT * FROM big_countries)",
     |   "medium" -> "country IN (SELECT * FROM medium_countries)")
tags: scala.collection.immutable.Map[String,String] = Map(big -> country IN (SELECT * FROM big_countries), medium -> country IN (SELECT * FROM medium_countries))

scala> val sep_tag = tags.map( (x) => { s"when array_contains("+x._1+", country) then '" + x._1 + "' " } ).mkString
sep_tag: String = "when array_contains(big, country) then 'big' when array_contains(medium, country) then 'medium' "

scala> val combine_sel_tag1 = tags.map( (x) => { s" array_contains("+x._1+",country) " } ).mkString(" and ")
combine_sel_tag1: String = " array_contains(big,country)  and  array_contains(medium,country) "

scala> val combine_sel_tag2 = tags.map( (x) => x._1 ).mkString(" '(","|", ")' ")
combine_sel_tag2: String = " '(big|medium)' "

scala> val combine_sel_all = " case when " + combine_sel_tag1 + " then " + combine_sel_tag2 +  sep_tag + " end as tags "
combine_sel_all: String = " case when  array_contains(big,country)  and  array_contains(medium,country)  then  '(big|medium)' when array_contains(big, country) then 'big' when array_contains(medium, country) then 'medium'  end as tags "

scala> val crosqry = tags.map( (x) => { s" cross join ( select collect_list(country) as "+x._1+" from "+x._1+"_countries) "+ x._1 + "  " } ).mkString
crosqry: String = " cross join ( select collect_list(country) as big from big_countries) big   cross join ( select collect_list(country) as medium from medium_countries) medium  "

scala> val qry = " select name,email,phone,country, " + combine_sel_all + " from tagged_users " + crosqry
qry: String = " select name,email,phone,country,  case when  array_contains(big,country)  and  array_contains(medium,country)  then  '(big|medium)' when array_contains(big, country) then 'big' when array_contains(medium, country) then 'medium'  end as tags  from tagged_users  cross join ( select collect_list(country) as big from big_countries) big   cross join ( select collect_list(country) as medium from medium_countries) medium  "

scala> spark.sql(qry).show
+------+------------------+--------------+-------------+------------+
|  name|             email|         phone|      country|        tags|
+------+------------------+--------------+-------------+------------+
|  Mike|  mike@example.com|+91-9999999999|        Italy|         big|
|  Alex|  alex@example.com|+91-9999999998|       France|         big|
|  John|  john@example.com| +1-1111111111|United States|         big|
|Donald|donald@example.com| +1-2222222222|United States|         big|
|   Dan|   dan@example.com|+91-9999444999|       Poland|      medium|
| Scott| scott@example.com|+91-9111999998|        Spain|(big|medium)|
|   Rob|   rob@example.com|+91-9114444998|        Italy|         big|
+------+------------------+--------------+-------------+------------+


scala>

UPDATE2:

scala> Seq(("Mike","mike@example.com","+91-9999999999","Italy"),
     | ("Alex","alex@example.com","+91-9999999998","France"),
     | ("John","john@example.com","+1-1111111111","United States"),
     | ("Donald","donald@example.com","+1-2222222222","United States"),
     | ("Dan","dan@example.com","+91-9999444999","Poland"),
     | ("Scott","scott@example.com","+91-9111999998","Spain"),
     | ("Rob","rob@example.com","+91-9114444998","Italy")
     | ).toDF("name","email","phone","country").createOrReplaceTempView("tagged_users")

scala> Seq("Italy", "France", "United States", "Spain").toDF("country").createOrReplaceTempView("big_countries")

scala> Seq("Poland", "Hungary", "Spain").toDF("country").createOrReplaceTempView("medium_countries")

scala> val tags = Map(
     |   "big" -> "country IN (SELECT * FROM big_countries)",
     |   "medium" -> "country IN (SELECT * FROM medium_countries)",
     |   "sometag" -> "name = 'Donald' AND email = 'donald@example.com' AND phone = '+1-2222222222'")
tags: scala.collection.immutable.Map[String,String] = Map(big -> country IN (SELECT * FROM big_countries), medium -> country IN (SELECT * FROM medium_countries), sometag -> name = 'Donald' AND email = 'donald@example.com' AND phone = '+1-2222222222')

scala> val sql_tags = tags.map( x => { val p = x._2.trim.toUpperCase.split(" ");
     | val qry = if(p.contains("IN") && p.contains("FROM"))
     | s" case when array_contains((select collect_list("+p.head +") from " + p.last.replaceAll("[)]","")+ " ), " +p.head + " ) then '" + x._1 + " ' else '' end " + x._1 + " "
     | else
     | " case when " + x._2 + " then '" + x._1 + " ' else '' end " + x._1 + " ";
     | qry } ).mkString(",")
sql_tags: String = " case when array_contains((select collect_list(COUNTRY) from BIG_COUNTRIES ), COUNTRY ) then 'big ' else '' end big , case when array_contains((select collect_list(COUNTRY) from MEDIUM_COUNTRIES ), COUNTRY ) then 'medium ' else '' end medium , case when name = 'Donald' AND email = 'donald@example.com' AND phone = '+1-2222222222' then 'sometag ' else '' end sometag "

scala> val outer_query = tags.map( x=> x._1).mkString(" regexp_replace(trim(concat(", ",", " )),' ','|') tags ")
outer_query: String = " regexp_replace(trim(concat(big,medium,sometag )),' ','|') tags "

scala> spark.sql(" select name,email, country, " + outer_query + " from ( select name,email, country ," + sql_tags + "   from tagged_users ) " ).show
+------+------------------+-------------+-----------+
|  name|             email|      country|       tags|
+------+------------------+-------------+-----------+
|  Mike|  mike@example.com|        Italy|        big|
|  Alex|  alex@example.com|       France|        big|
|  John|  john@example.com|United States|        big|
|Donald|donald@example.com|United States|big|sometag|
|   Dan|   dan@example.com|       Poland|     medium|
| Scott| scott@example.com|        Spain| big|medium|
|   Rob|   rob@example.com|        Italy|        big|
+------+------------------+-------------+-----------+


scala>
0 голосов
/ 09 ноября 2018

Я бы определил несколько таблиц тегов со значением столбца, тег.

Тогда вашим определением тегов будет коллекция, скажем, Seq [(String, String], где первый элемент кортежа - это столбец, по которому вычисляется тег.

Допустим,

Seq(
  "country" -> "bigCountries", // Columns [country, bigCountry]
  "country" -> "mediumCountries", // Columns [country, mediumCountry]
  "email" -> "hotmailLosers" // [country, hotmailLoser]
)

Затем выполните итерацию по этому списку, оставив слева соединить каждую таблицу в соответствующем столбце с соответствующим столбцом.

После объединения каждой таблицы просто выберите столбец тегов, который будет текущим значением + объединенный столбец, если он не равен нулю.

0 голосов
/ 09 ноября 2018

Если вам нужно объединить результаты, а не просто выполнить каждый запрос, возможно, используйте карту вместо foreach, а затем объедините результаты

 val o = tags.map {
  case (tag, tagCondition) => {
    val resultDf = spark.sql(buildTagQuery(tag, tagCondition, "tagged_users"))
      .withColumn("tag", new Column("blah"))
    resultDf
  }
}

o.foldLeft(o.head) {
  case (acc, df) => acc.union(df)
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...