web-dev-qa-db-fra.com

filtre de plage de datetime dans PySpark SQL

Quelle est la bonne façon de filtrer la trame de données par champ d'horodatage?

J'ai essayé différents formats de date et formes de filtrage, rien n'y fait: soit pyspark renvoie 0 objet, soit renvoie une erreur qui ne comprend pas le format datetime

Voici ce que j'ai obtenu jusqu'à présent:

from pyspark import SparkContext
from pyspark.sql import SQLContext

from Django.utils import timezone
from Django.conf import settings

from myapp.models import Collection

sc = SparkContext("local", "DjangoApp")
sqlc = SQLContext(sc)
url = "jdbc:postgresql://%(Host)s/%(NAME)s?user=%(USER)s&password=%(PASSWORD)s" % settings.DATABASES['default']
sf = sqlc.load(source="jdbc", url=url, dbtable='myapp_collection')

plage pour le champ d'horodatage:

system_tz = timezone.pytz.timezone(settings.TIME_ZONE)
date_from = datetime.datetime(2014, 4, 16, 18, 30, 0, 0, tzinfo=system_tz)
date_to = datetime.datetime(2015, 6, 15, 18, 11, 59, 999999, tzinfo=system_tz)

tentative 1

date_filter = "my_col >= '%s' AND my_col <= '%s'" % (
    date_from.isoformat(), date_to.isoformat()
)
sf = sf.filter(date_filter)
sf.count()

Out[12]: 0

tentative 2

sf = sf.filter(sf.my_col >= date_from).filter(sf.my_col <= date_to)
sf.count()

---------------------------------------------------------------------------
Py4JJavaError: An error occurred while calling o63.count.
: org.Apache.spark.SparkException: Job aborted due to stage failure:
Task 0 in stage 4.0 failed 1 times, most recent failure: 
Lost task 0.0 in stage 4.0 (TID 3, localhost): org.postgresql.util.PSQLException: 
ERROR: syntax error at or near "18"
# 
# ups.. JDBC doesn't understand 24h time format??

tentative 3

sf = sf.filter("my_col BETWEEN '%s' AND '%s'" % \
     (date_from.isoformat(), date_to.isoformat())
     )
---------------------------------------------------------------------------
Py4JJavaError: An error occurred while calling o97.count.
: org.Apache.spark.SparkException: Job aborted due to stage failure:
Task 0 in stage 17.0 failed 1 times, most recent failure:
Lost task 0.0 in stage 17.0 (TID 13, localhost): org.postgresql.util.PSQLException:
ERROR: syntax error at or near "18"

les données existent cependant dans le tableau:

Django_filters = {
    'my_col__gte': date_from,
    'my_col__lte': date_to
    }
Collection.objects.filter(**Django_filters).count()

Out[17]: 1093436

Ou de cette façon

Django_range_filter = {'my_col__range': (date_from, date_to)}
Collection.objects.filter(**Django_range_filter).count()

Out[19]: 1093436
19
funkifunki

Supposons que votre bloc de données se présente comme suit:

sf = sqlContext.createDataFrame([
    [datetime.datetime(2013, 6, 29, 11, 34, 29)],
    [datetime.datetime(2015, 7, 14, 11, 34, 27)],
    [datetime.datetime(2012, 3, 10, 19, 00, 11)],
    [datetime.datetime(2016, 2, 8, 12, 21)],
    [datetime.datetime(2014, 4, 4, 11, 28, 29)]
], ('my_col', ))

avec schéma:

root
 |-- my_col: timestamp (nullable = true)

et vous souhaitez rechercher des dates dans une plage suivante:

import datetime, time 
dates = ("2013-01-01 00:00:00",  "2015-07-01 00:00:00")

timestamps = (
    time.mktime(datetime.datetime.strptime(s, "%Y-%m-%d %H:%M:%S").timetuple())
    for s in dates)

Il est possible d'interroger en utilisant des horodatages calculés côté pilote:

q1 = "CAST(my_col AS INT) BETWEEN {0} AND {1}".format(*timestamps)
sf.where(q1).show()

ou en utilisant unix_timestamp une fonction:

q2 = """CAST(my_col AS INT)
        BETWEEN unix_timestamp('{0}', 'yyyy-MM-dd HH:mm:ss')
        AND unix_timestamp('{1}', 'yyyy-MM-dd HH:mm:ss')""".format(*dates)

sf.where(q2).show()

Il est également possible d'utiliser udf d'une manière similaire que j'ai décrite dans un ne autre réponse .

Si vous utilisez du SQL brut, il est possible d'extraire différents éléments d'horodatage à l'aide de year, date, etc.

sqlContext.sql("""SELECT * FROM sf
    WHERE YEAR(my_col) BETWEEN 2014 AND 2015").show()

[~ # ~] éditez [~ # ~] :

Depuis Spark 1.5 vous pouvez utiliser les fonctions intégrées:

dates = ("2013-01-01",  "2015-07-01")
date_from, date_to = [to_date(lit(s)).cast(TimestampType()) for s in dates]

sf.where((sf.my_col > date_from) & (sf.my_col < date_to))

Vous pouvez aussi utiliser pyspark.sql.Column.between , qui comprend les limites:

from pyspark.sql.functions import col
sf.where(col('my_col').between(*dates)).show(truncate=False)
#+---------------------+
#|my_col               |
#+---------------------+
#|2013-06-29 11:34:29.0|
#|2014-04-04 11:28:29.0|
#+---------------------+
12
zero323

Que diriez-vous quelque chose comme ça:

import pyspark.sql.functions as func

df = sf.select(func.to_date(sf.my_col).alias("time"))
sf = df.filter(sf.time > date_from).filter(sf.time < date_to)
3
Sean