O comando SELECT TRANSFORM encaminha linhas de uma tabela do MaxCompute para um script externo ou comando shell e mapeia a saída padrão do script de volta para colunas SQL. Esse recurso permite executar lógicas em AWK, Python, Perl, Shell ou Java diretamente em uma consulta SQL, sem criar uma função de valor de tabela definida pelo usuário (UDTF).
Quando usar o SELECT TRANSFORM
Tanto o SELECT TRANSFORM quanto as UDTFs processam linhas com código personalizado, mas cada um é mais adequado a cenários específicos:
|
Dimensão |
SELECT TRANSFORM |
UDTF |
|
Esforço de desenvolvimento |
Basta escrever um comando shell ou script inline diretamente no SQL, sem empacotamento |
Exige criar e implantar uma classe Java ou Python |
|
Análise ad hoc |
Iteração rápida; funciona com AWK, Python, Perl e Shell sem upload de arquivos |
Mais indicado para pipelines de produção |
|
Tipos de dados |
Toda entrada e saída é tratada como |
Suporte completo a tipos de dados na entrada e na saída |
|
Transmissão de dados |
Baseada em pipeline do SO; buffer de pipeline de 4 KB (fixo); menor sobrecarga que código baseado em JVM |
Sem limite de buffer de pipeline |
|
Parâmetros constantes |
Sempre transmitidos ao processo filho |
Opcionais |
|
Modelo de processo |
Gera um processo filho; aproveita servidores multicore quando a computação é alta e o throughput é baixo |
Executa em um único processo |
|
Desempenho com pequenos volumes de dados |
Superior — ferramentas nativas como AWK evitam a sobrecarga da JVM |
Inferior |
|
Desempenho com grandes volumes de dados |
Inferior |
Superior |
Em resumo: Utilize o SELECT TRANSFORM para análises ad hoc e scripts leves. Para cargas de trabalho de produção com alto throughput, prefira as UDTFs.
Limitações
Os clusters de computação do MaxCompute não incluem PHP nem Ruby. Portanto, não é possível chamar scripts nessas linguagens no MaxCompute.
Sintaxe
SELECT TRANSFORM(<arg1>, <arg2>, ...)
[ROW FORMAT DELIMITED
[FIELDS TERMINATED BY '<field_delimiter>' [ESCAPED BY '<character_escape>']]
[NULL DEFINED AS '<null_value>']]
USING '<unix_command_line>'
[RESOURCES '<res_name>' [, '<res_name>'...]]
[AS (<col1> [<type>], <col2> [<type>], ...)]
[ROW FORMAT DELIMITED
[FIELDS TERMINATED BY '<field_delimiter>' [ESCAPED BY '<character_escape>']]
[NULL DEFINED AS '<null_value>']]
Você pode substituir oSELECT TRANSFORMpela palavra-chavemapoureduce— ambas são aliases com semântica idêntica. Use oSELECT TRANSFORMpara maior clareza.
Parâmetros
|
Parâmetro |
Obrigatório |
Descrição |
|
|
Sim |
Colunas ou expressões de entrada. Os valores são convertidos implicitamente para |
|
Primeiro |
Não |
Formato dos dados enviados para o processo filho. Padrões: |
|
|
Sim |
Comando usado para iniciar o processo filho. Cria um subprocesso, não uma sessão shell. Recursos do shell, como pipes, loops e redirecionamento de E/S, não estão disponíveis na própria string de comando. Para usar esses recursos, passe um script shell como comando (por exemplo, |
|
|
Não |
Arquivos de recurso acessíveis ao processo filho. Alternativa: defina |
|
|
Não |
Nomes das colunas de saída e tipos opcionais. Se omitido, a saída assume o padrão |
|
Segundo |
Não |
Formato dos dados lidos da saída padrão do processo filho. Mesmos padrões do primeiro |
Restrições do ROW FORMAT
Os parâmetros
field_delimiterecharacter_escapeaceitam apenas caracteres únicos. Se você especificar uma string com vários caracteres, somente o primeiro será utilizado.O uso de
ROW FORMATcominputRecordReader,outputRecordReaderouSerDeexige o modo compatível com Hive. Adicioneset odps.sql.hive.compatible=true;antes da instrução SQL. Esses formatos específicos do Hive podem reduzir a velocidade de execução.
Para consultar a sintaxe completa do ROW FORMAT do Apache Hive, acesse Hive LanguageManual Transform.
Comportamento do formato de dados
Compreender como os dados trafegam entre o MaxCompute e o processo filho evita erros silenciosos.
Entrada para o processo filho:
Todos os valores das colunas são convertidos para
STRING.As colunas são unidas com
\te gravadas na entrada padrão, com uma linha por registro.
Saída do processo filho:
Cada linha da saída padrão se torna uma linha de saída.
Os campos são divididos por
\t(ou pelo delimitador definido no segundoROW FORMAT).Se a cláusula
ASfor omitida, a saída será mapeada comokey STRING, value STRING— o campo anterior ao primeiro\ttorna-sekeye as partes seguintes tornam-sevalue. Isso equivale aAS(key, value).Se as colunas de saída não forem do tipo
STRING, o MaxCompute chamaCASTimplicitamente para converter os valores. Uma exceção de tempo de execução poderá ocorrer se a conversão falhar.
Exemplos de scripts inline
Para transformações simples, escreva o script diretamente na instrução SQL. Não é necessário fazer upload de recursos.
Shell
Gere 50 linhas numeradas de 1 a 50:
SELECT TRANSFORM('for i in `seq 1 50`; do echo $i; done') USING 'sh' AS (data);
Saída:
+------+
| data |
+------+
| 1 |
| 2 |
| ... |
| 50 |
+------+
Python
Gere 50 linhas numeradas de 1 a 50:
SELECT TRANSFORM('for i in xrange(1, 51): print(i);') USING 'python' AS (data);
AWK
Extraia a segunda coluna de uma tabela:
-- Create and populate a test table
CREATE TABLE testdata (c1 BIGINT, c2 BIGINT);
INSERT INTO TABLE testdata VALUES (1, 4), (2, 5), (3, 6);
-- Extract c2 using AWK
SELECT TRANSFORM(*) USING "awk '//{print $2}'" AS (data) FROM testdata;
Saída:
+------+
| data |
+------+
| 4 |
| 5 |
| 6 |
+------+
Perl
Retorne todas as colunas (demonstrando a saída padrão key/value quando AS é omitido):
CREATE TABLE testdata (c1 BIGINT, c2 BIGINT);
INSERT INTO TABLE testdata VALUES (1, 4), (2, 5), (3, 6);
SELECT TRANSFORM(testdata.c1, testdata.c2)
USING "perl -e 'while($input = <STDIN>){print $input;}'"
FROM testdata;
Saída (sem cláusula AS — assume o padrão key, value):
+-----+-------+
| key | value |
+-----+-------+
| 1 | 4 |
| 2 | 5 |
| 3 | 6 |
+-----+-------+
Anexar arquivos de script
Para lógicas complexas, faça upload de um arquivo de script como recurso do MaxCompute e referencie-o na cláusula USING.
Usando a cláusula RESOURCES
Especifique o recurso inline na instrução:
SELECT TRANSFORM(...) USING 'python myplus.py' RESOURCES 'myplus.py' AS (...) FROM ...;
Usando uma configuração no nível da sessão
Defina os recursos uma única vez para todas as instruções SELECT TRANSFORM da sessão:
set odps.sql.session.resources=myplus.py,bar.txt;
Separe vários arquivos de recurso por vírgulas. Após essa configuração entrar em vigor, todas as instruções SELECT TRANSFORM subsequentes na sessão poderão acessar os recursos listados sem precisar especificar RESOURCES em cada instrução.
Exemplo de script Python
-
Crie o arquivo
myplus.py:#!/usr/bin/env python import sys line = sys.stdin.readline() while line: token = line.split('\t') if (token[0] == '\\N') or (token[1] == '\\N'): print('\\N') else: print(str(token[0]) + '\t' + str(token[1])) line = sys.stdin.readline() -
Faça upload do arquivo como um recurso do MaxCompute:
Também é possível fazer upload do arquivo pelo console do DataWorks. Para mais detalhes, consulte Criar e usar recursos do MaxCompute .
add py ./myplus.py -f; -
Execute a transformação:
CREATE TABLE testdata (c1 BIGINT, c2 BIGINT); INSERT INTO TABLE testdata VALUES (1, 4), (2, 5), (3, 6); SELECT TRANSFORM(testdata.c1, testdata.c2) USING 'python myplus.py' RESOURCES 'myplus.py' AS (result1, result2) FROM testdata;Saída:
+---------+---------+ | result1 | result2 | +---------+---------+ | 1 | 4 | | | NULL | | 2 | 5 | | | NULL | | 3 | 6 | | | NULL | +---------+---------+
Exemplo de script Java
-
Escreva e compile uma classe Java e exporte-a como
Sum.jar:package com.aliyun.odps.test; import java.util.Scanner; public class Sum { public static void main(String[] args) { Scanner sc = new Scanner(System.in); while (sc.hasNext()) { String s = sc.nextLine(); String[] tokens = s.split("\t"); if (tokens.length < 2) { throw new RuntimeException("illegal input"); } if (tokens[0].equals("\\N") || tokens[1].equals("\\N")) { System.out.println("\\N"); } System.out.println(Long.parseLong(tokens[0]) + Long.parseLong(tokens[1])); } } } -
Faça upload do arquivo JAR como um recurso do MaxCompute:
add jar ./Sum.jar -f; -
Execute a transformação:
CREATE TABLE testdata (c1 BIGINT, c2 BIGINT); INSERT INTO TABLE testdata VALUES (1, 4), (2, 5), (3, 6); SELECT TRANSFORM(testdata.c1, testdata.c2) USING 'java -cp Sum.jar com.aliyun.odps.test.Sum' RESOURCES 'Sum.jar' AS cnt FROM testdata;Saída:
+-----+ | cnt | +-----+ | 5 | | 7 | | 9 | +-----+
Tanto Java quanto Python possuem frameworks de UDTF, mas oSELECT TRANSFORMcostuma ser mais rápido de escrever, pois não exige dependências nem estrutura de classes. Scripts Java offline são resolvidos a partir deJAVA_HOME; scripts Python offline, a partir dePYTHON_HOME.
Encadear múltiplas transformações
Passe a saída de uma transformação como entrada da seguinte aninhando instruções SELECT TRANSFORM. Use DISTRIBUTE BY e SORT BY para controlar como as linhas são particionadas e ordenadas antes de cada transformação.
SELECT TRANSFORM(key, value) USING '<cmd2>' FROM
(
SELECT TRANSFORM(*) USING '<cmd1>' FROM
(
SELECT * FROM testdata DISTRIBUTE BY c2 SORT BY c1
) t DISTRIBUTE BY key SORT BY value
) t2;
Os aliases map e reduce tornam o padrão map-reduce explícito:
@a := SELECT * FROM data DISTRIBUTE BY col2 SORT BY col1;
@b := map * USING 'cmd1' DISTRIBUTE BY col1 SORT BY col2 FROM @a;
reduce * USING 'cmd2' FROM @b;
Os comandos cmd1 e cmd2 iniciam cada processo filho.