Lorsque vous écrivez une UDF qui traite des données structurées, vous devez faire correspondre les types de données complexes MaxCompute — ARRAY, MAP et STRUCT — aux types Java ou Python équivalents dans votre code de gestionnaire. Ce tutoriel explique comment implémenter, déployer et appeler une UDF qui convertit des horodatages pour ces trois types de données complexes.
Les UDF Java prennent en charge la surcharge de méthodes : une seule UDF peut donc gérer les trois types. Les UDF Python nécessitent en revanche une fonction distincte pour chaque type.
Prérequis
Avant de commencer, assurez-vous de disposer des éléments suivants :
Un projet MaxCompute
Un environnement de développement Java avec Maven, ou un environnement Python 3
Correspondances de types
Chaque type de données complexe MaxCompute correspond à un type Java ou Python spécifique dans le gestionnaire de votre UDF :
|
Type MaxCompute |
Type Java |
Type Python |
|
ARRAY |
|
|
|
MAP |
|
|
|
STRUCT |
|
|
Le type STRUCT requiert l'annotation@Resolveen Java. La réflexion ne permettant pas de lire les noms et les types de champs depuiscom.aliyun.odps.data.Struct, cette annotation fournit les informations nécessaires au moment de la compilation. L'annotation n'affecte que les méthodes surchargées dont les paramètres d'entrée ou la valeur de retour incluentStruct.
Étape 1 : Rédiger l'UDF
UDF Java
Les trois méthodes evaluate de la classe Java partagent le même nom UDF_COMPLEX_DATA. MaxCompute distribue les appels en fonction du type d'argument, ce qui permet à chaque méthode de traiter un type complexe spécifique.
Les signatures UDF en SQL correspondent directement aux signatures de méthode Java :
array<string> UDF_COMPLEX_DATA(array<bigint> as)
map<string, string> UDF_COMPLEX_DATA(map<string,bigint> ms)
struct<output_name:string,output_time:string> UDF_COMPLEX_DATA(struct<input_name:string,input_timestamp:bigint> st)
Ajoutez la dépendance suivante au fichier pom.xml :
<dependency>
<groupId>com.aliyun.odps</groupId>
<artifactId>odps-sdk-udf</artifactId>
<version>0.29.10-public</version>
</dependency>
package com.aliyun; // Specify a package name.
import com.aliyun.odps.data.Struct;
import com.aliyun.odps.udf.UDF;
import com.aliyun.odps.udf.annotation.Resolve;
import java.text.SimpleDateFormat;
import java.util.*;
@Resolve("struct<input_name:string, input_timestamp:bigint>->map<string,string>")
public class ComplexDataTypeExample extends UDF{
private static final String PATTERN = "yyyy-MM-dd HH:mm:ss";
/**
* Convert a list of timestamps into a list of time strings.
* @param timestamps Enter a list of timestamps.
* @return Obtain a list of time strings.
*/
public List<String> evaluate(List<Long> timestamps) {
if (timestamps == null) {
return null;
}
List<String> result = new ArrayList<>();
SimpleDateFormat formatter = new SimpleDateFormat(PATTERN);
for (Long timestamp : timestamps) {
Date date = new Date(timestamp < 9999999999L ? timestamp * 1000 : timestamp);
String dateString = formatter.format(date);
result.add(dateString);
}
return result;
}
/**
* Convert timestamps of the MAP data type into time strings of the MAP data type.
* @param timestamps Enter data of the MAP data type in which values are timestamps.
* @return Obtain a list of time strings of the MAP data type.
*/
public Map<String, String> evaluate(Map<String, Long> timestamps) {
if (timestamps == null) {
return null;
}
Map<String, String> result = new HashMap<>(timestamps.size());
SimpleDateFormat formatter = new SimpleDateFormat(PATTERN);
for (String key : timestamps.keySet()) {
Long timestamp = timestamps.get(key);
Date date = new Date(timestamp < 9999999999L ? timestamp * 1000 : timestamp);
String dateString = formatter.format(date);
result.put(key, dateString);
}
return result;
}
/**
* Convert a timestamp into a time string.
* @param input Enter a timestamp of the STRUCT data type.
* @return Obtain a time string of the STRUCT data type.
*/
public Map<String, String> evaluate(Struct input) {
if (input == null) {
return null;
}
SimpleDateFormat formatter = new SimpleDateFormat(PATTERN);
String nameValue = (String) input.getFieldValue("input_name");
Long timestampValue = (Long) input.getFieldValue("input_timestamp");
Date date = new Date(timestampValue < 9999999999L ? timestampValue * 1000 : timestampValue);
String dateString = formatter.format(date);
Map<String, String> result = new HashMap<>(8);
result.put("output_name", nameValue);
result.put("output_time", dateString);
return result;
}
}
Chaque méthode evaluate renvoie null lorsque son entrée est null. MaxCompute ne garantissant pas l'ordre d'évaluation des sous-expressions SQL, les vérifications de nullité au sein de l'UDF constituent le moyen fiable de gérer les entrées nulles.
Pour connaître les autres exigences liées au code, consultez la rubrique UDF Java.
UDF Python
Les UDF Python ne prenant pas en charge la surcharge de méthodes, chaque type complexe nécessite une UDF distincte portant son propre nom.
Les projets MaxCompute exécutent Python 2 par défaut. Pour utiliser Python 3, exécutez la commande set odps.sql.python.version=cp37 au niveau de la session avant d'appeler une UDF Python 3.
Pour plus d'informations sur les autres exigences relatives aux UDF Python 3, consultez la rubrique UDF Python 3.
UDF_COMPLEX_DATA_ARRAY — gère les entrées de type ARRAY<BIGINT> :
from odps.udf import annotate
import datetime
@annotate('array<bigint>->array<datetime>')
class ArrayExample:
def evaluate(self, input_list):
output_list = list()
for item in input_list:
t = datetime.datetime.fromtimestamp(item)
output_list.append(t)
return output_list
UDF_COMPLEX_DATA_MAP — gère les entrées de type MAP<STRING, BIGINT> :
from odps.udf import annotate
import datetime
@annotate('map<string,bigint>->map<string,datetime>')
class MapExample:
def evaluate(self, input_dict):
output_dict = dict()
for key in input_dict:
value = input_dict[key]
t = datetime.datetime.fromtimestamp(value)
output_dict[key] = t
return output_dict
UDF_COMPLEX_DATA_STRUCT — gère les entrées de type STRUCT<input_name:STRING, input_timestamp:BIGINT> :
from odps.udf import annotate
import datetime, collections
@annotate('struct<input_name:string,input_timestamp:bigint>->struct<output_name:string,output_time:datetime>')
class StructExample:
def evaluate(self, input_namedtuple):
OutputNamedTuple = collections.namedtuple('output_namedtuple', ['output_name', 'output_time'])
name_val = input_namedtuple.input_name
time_val = datetime.datetime.fromtimestamp(input_namedtuple.input_timestamp)
output_namedtuple = OutputNamedTuple(name_val, time_val)
return output_namedtuple
Étape 2 : Télécharger les ressources et créer l'UDF
Une fois le code de votre UDF rédigé et débogué, téléchargez-le sur MaxCompute et enregistrez l'UDF.
UDF Java : Emballez la classe compilée dans un fichier JAR, téléchargez-le en tant que ressource, puis créez l'UDF nommée
UDF_COMPLEX_DATA. Consultez la page Empaqueter un programme Java, télécharger le package et créer une UDF MaxCompute.UDF Python : Téléchargez chaque fichier
.pyen tant que ressource, puis créez trois UDF :UDF_COMPLEX_DATA_ARRAY,UDF_COMPLEX_DATA_MAPetUDF_COMPLEX_DATA_STRUCT. Consultez la page Télécharger un programme Python et créer une UDF MaxCompute.
Étape 3 : Appeler l'UDF
ARRAY
-- Java UDF
SELECT UDF_COMPLEX_DATA(array(1554047999, 1554047989));
-- Python UDF (enable Python 3 first)
set odps.sql.python.version=cp37;
SELECT UDF_COMPLEX_DATA_ARRAY(array(1554047999, 1554047989));
Résultat attendu :
+---------------------------------------------+
| _c0 |
+---------------------------------------------+
| [2019-03-31 23:59:59, 2019-03-31 23:59:49] |
+---------------------------------------------+
MAP
-- Java UDF
SELECT UDF_COMPLEX_DATA(map('date1', 1554047989, 'date2', 1554047999));
-- Python UDF (enable Python 3 first)
set odps.sql.python.version=cp37;
SELECT UDF_COMPLEX_DATA_MAP(map('date1', 1554047989, 'date2', 1554047999));
Résultat attendu :
+----------------------------------------------------------------+
| _c0 |
+----------------------------------------------------------------+
| {"date1":"2019-03-31 23:59:49","date2":"2019-03-31 23:59:59"} |
+----------------------------------------------------------------+
STRUCT
-- Java UDF
SELECT UDF_COMPLEX_DATA(struct('date', 1554047989));
-- Python UDF (enable Python 3 first)
set odps.sql.python.version=cp37;
SELECT UDF_COMPLEX_DATA_STRUCT(struct('date', 1554047989));
Résultat attendu :
+-------------------------------------------------------------+
| _c0 |
+-------------------------------------------------------------+
| {"output_name":"date","output_time":"2019-03-31 23:59:49"} |
+-------------------------------------------------------------+
Voir aussi
UDF Java — Référence complète des spécifications des UDF Java
UDF Python 3 — Spécifications et limites des UDF Python 3