Funciones definidas por el usuario
Una función definida por el usuario (UDF) te permite crear una función mediante una expresión de SQL o un código JavaScript. Una UDF acepta columnas de entrada, realiza acciones en la entrada y muestra el resultado de esas acciones como un valor.
Puedes definir una UDF como persistente o temporal. Puedes reutilizar las UDF persistentes en varias consultas, mientras que las UDF temporales solo existen en el alcance de una única consulta.
Para crear un conjunto de datos, usa la declaración CREATE FUNCTION
. Para borrar una función persistente definida por el usuario, usa la declaración DROP FUNCTION
. Las UDF temporales vencen apenas finaliza la consulta. La declaración DROP
FUNCTION
solo es compatible con UDF temporales en consultas de varias declaraciones y procedimientos.
Para obtener más información sobre las UDF en SQL heredado, consulta Funciones definidas por el usuario en SQL heredado.
UDF de SQL
En el siguiente ejemplo, se crea una UDF de SQL temporal llamada AddFourAndDivide
y se llama a la UDF desde una sentencia SELECT
:
CREATE TEMP FUNCTION AddFourAndDivide(x INT64, y INT64) RETURNS FLOAT64 AS ( (x + 4) / y ); SELECT val, AddFourAndDivide(val, 2) FROM UNNEST([2,3,5,8]) AS val;
En este ejemplo, se produce el siguiente resultado:
+-----+-----+
| val | f0_ |
+-----+-----+
| 2 | 3.0 |
| 3 | 3.5 |
| 5 | 4.5 |
| 8 | 6.0 |
+-----+-----+
En el siguiente ejemplo, se crea la misma función que una UDF persistente:
CREATE FUNCTION mydataset.AddFourAndDivide(x INT64, y INT64) RETURNS FLOAT64 AS ( (x + 4) / y );
Debido a que esta UDF es persistente, debes especificar un conjunto de datos para la función (mydataset
en este ejemplo). Después de ejecutar la declaración CREATE FUNCTION
, puedes llamar a la función desde una consulta:
SELECT val, mydataset.AddFourAndDivide(val, 2) FROM UNNEST([2,3,5,8,12]) AS val;
Parámetros de UDF de SQL con plantilla
Un parámetro con un tipo igual a ANY TYPE
puede coincidir con más de un tipo de argumento cuando se llama a la función.
- Si hay más de un parámetro con el tipo
ANY TYPE
, BigQuery no impone ninguna relación entre estos argumentos. - La función tipo de datos que se muestra no puede ser
ANY TYPE
. Debe omitirse, lo que significa que se determina de forma automática en función desql_expression
, o debe ser un tipo explícito. - Si pasas los argumentos de funciones de tipos que son incompatibles con la definición de la función, se producirá un error durante la llamada.
En el siguiente ejemplo se muestra una UDF de SQL que usa un parámetro con plantilla.
CREATE TEMP FUNCTION addFourAndDivideAny(x ANY TYPE, y ANY TYPE) AS ( (x + 4) / y ); SELECT addFourAndDivideAny(3, 4) AS integer_input, addFourAndDivideAny(1.59, 3.14) AS floating_point_input;
En este ejemplo, se produce el siguiente resultado:
+----------------+-----------------------+
| integer_input | floating_point_input |
+----------------+-----------------------+
| 1.75 | 1.7802547770700636 |
+----------------+-----------------------+
En el siguiente ejemplo, se usa un parámetro con plantilla para mostrar el último elemento de un array de cualquier tipo:
CREATE TEMP FUNCTION lastArrayElement(arr ANY TYPE) AS ( arr[ORDINAL(ARRAY_LENGTH(arr))] ); SELECT lastArrayElement(x) AS last_element FROM ( SELECT [2,3,5,8,13] AS x );
En este ejemplo, se produce el siguiente resultado:
+--------------+
| last_element |
+--------------+
| 13 |
+--------------+
Subconsultas escalares
Una UDF de SQL puede mostrar el valor de una subconsulta escalar. Una subconsulta escalar debe seleccionar una sola columna.
En el siguiente ejemplo, se muestra una UDF de SQL que usa una subconsulta escalar para contar la cantidad de usuarios con una edad determinada en una tabla de usuario.
CREATE TEMP TABLE users AS ( SELECT 1 AS id, 10 AS age UNION ALL SELECT 2 AS id, 30 AS age UNION ALL SELECT 3 AS id, 10 AS age ); CREATE TEMP FUNCTION countUserByAge(userAge INT64) AS ( (SELECT COUNT(1) FROM users WHERE age = userAge) ); SELECT countUserByAge(10) AS count_user_age_10, countUserByAge(20) AS count_user_age_20, countUserByAge(30) AS count_user_age_30;
En este ejemplo, se produce el siguiente resultado:
+-------------------+-------------------+-------------------+
| count_user_age_10 | count_user_age_20 | count_user_age_30 |
+-------------------+-------------------+-------------------+
| 2 | 0 | 1 |
+-------------------+-------------------+-------------------+
Proyecto predeterminado en expresiones de SQL
En el cuerpo de una UDF de SQL, cualquier referencia a entidades de BigQuery, como tablas o vistas, debe incluir el ID del proyecto, a menos que la entidad resida en el mismo proyecto que contiene la UDF.
Por ejemplo, considera la siguiente declaración:
CREATE FUNCTION project1.mydataset.myfunction() AS ( (SELECT COUNT(*) FROM mydataset.mytable) );
Si ejecutas esta declaración desde project1
y mydataset.mytable
existe en project1
, entonces la instrucción se realiza de forma correcta. Sin embargo, si ejecutas esta declaración desde un proyecto diferente, la instrucción fallará. Para corregir el error, incluye el ID del proyecto en la referencia de la tabla:
CREATE FUNCTION project1.mydataset.myfunction() AS ( (SELECT COUNT(*) FROM project1.mydataset.mytable) );
También puedes hacer referencia a una entidad en un proyecto o conjunto de datos diferente del que creaste la función:
CREATE FUNCTION project1.mydataset.myfunction() AS ( (SELECT COUNT(*) FROM project2.another_dataset.another_table) );
UDF de JavaScript
Una UDF de JavaScript te permite llamar al código escrito en JavaScript desde una consulta de SQL. Las UDF de JavaScript suelen consumir más recursos de ranuras en comparación con las consultas en SQL estándar, lo que disminuye el rendimiento del trabajo. Si la función se puede expresar en SQL, suele ser óptimo ejecutar el código como un trabajo de consulta de SQL estándar.
En el siguiente ejemplo, se muestra una UDF de JavaScript. El código JavaScript se coloca entre comillas dentro de una string sin procesar.
CREATE TEMP FUNCTION multiplyInputs(x FLOAT64, y FLOAT64) RETURNS FLOAT64 LANGUAGE js AS r""" return x*y; """; WITH numbers AS (SELECT 1 AS x, 5 as y UNION ALL SELECT 2 AS x, 10 as y UNION ALL SELECT 3 as x, 15 as y) SELECT x, y, multiplyInputs(x, y) AS product FROM numbers;
En este ejemplo, se produce el siguiente resultado:
+-----+-----+--------------+
| x | y | product |
+-----+-----+--------------+
| 1 | 5 | 5 |
| 2 | 10 | 20 |
| 3 | 15 | 45 |
+-----+-----+--------------+
En el siguiente ejemplo, se suman los valores de todos los campos llamados foo
en la string JSON dada.
CREATE TEMP FUNCTION SumFieldsNamedFoo(json_row STRING) RETURNS FLOAT64 LANGUAGE js AS r""" function SumFoo(obj) { var sum = 0; for (var field in obj) { if (obj.hasOwnProperty(field) && obj[field] != null) { if (typeof obj[field] == "object") { sum += SumFoo(obj[field]); } else if (field == "foo") { sum += obj[field]; } } } return sum; } var row = JSON.parse(json_row); return SumFoo(row); """; WITH Input AS ( SELECT STRUCT(1 AS foo, 2 AS bar, STRUCT('foo' AS x, 3.14 AS foo) AS baz) AS s, 10 AS foo UNION ALL SELECT NULL, 4 AS foo UNION ALL SELECT STRUCT(NULL, 2 AS bar, STRUCT('fizz' AS x, 1.59 AS foo) AS baz) AS s, NULL AS foo ) SELECT TO_JSON_STRING(t) AS json_row, SumFieldsNamedFoo(TO_JSON_STRING(t)) AS foo_sum FROM Input AS t;
En el ejemplo, se produce el siguiente resultado:
+---------------------------------------------------------------------+---------+
| json_row | foo_sum |
+---------------------------------------------------------------------+---------+
| {"s":{"foo":1,"bar":2,"baz":{"x":"foo","foo":3.14}},"foo":10} | 14.14 |
| {"s":null,"foo":4} | 4 |
| {"s":{"foo":null,"bar":2,"baz":{"x":"fizz","foo":1.59}},"foo":null} | 1.59 |
+---------------------------------------------------------------------+---------+
Tipos de datos de UDF de JavaScript admitidos
Algunos tipos de SQL se corresponden de forma directa con tipos de JavaScript, pero otros no. BigQuery representa los tipos de la siguiente manera:
Tipo de datos de BigQuery | Tipo de datos de JavaScript |
---|---|
ARRAY | ARRAY |
BOOL | BOOLEAN |
BYTES | STRING codificada en base64 |
FLOAT64 | NÚMERO |
NUMERIC, BIGNUMERIC | Si un valor NUMERIC o BIGNUMERIC se puede representar de forma exacta como un valor de punto flotante IEEE 754 y no tiene una parte fraccionaria, se codifica como un Número. Estos valores están en el rango [-253, 253]. De lo contrario, el valor se codifica como una cadena. |
STRING | STRING |
STRUCT | OBJECT en el que cada campo de STRUCT tiene nombre |
TIMESTAMP | DATE con un campo de microsegundos que contiene la fracción de microsecond de la marca de tiempo |
DATE | DATE |
JSON |
Los OBJECTS, ARRAY y VALUES de JSON se convierten en OBJETOS, ARRAY y VALORES de JavaScript equivalentes. JavaScript no admite valores INT64. Solo los números JSON en el rango [-253, 253] se convierten con exactitud. De lo contrario, el valor numérico se redondea, lo que podría generar una pérdida de precisión. |
Debido a que JavaScript no admite un tipo de número entero de 64 bits, INT64
no es compatible como tipo de entrada para UDF de JavaScript. En su lugar, usa FLOAT64
a fin de representar valores como un número entero, o STRING
para representarlos como una string.
BigQuery admite INT64
como un tipo de datos que se muestra en las UDF de JavaScript. En este caso, el cuerpo de la función de JavaScript puede mostrar una String o un Número de JavaScript. BigQuery luego convierte cualquiera de estos tipos en INT64
.
Si el valor de retorno de la UDF de JavaScript es una
Promise
, BigQuery espera por la Promise
hasta que se establezca la Promise
. Si Promise
se establece en un estado entregado, BigQuery muestra su resultado. Si Promise
se establece en un estado rechazado, BigQuery muestra un error.
Reglas de entrecomillado
Debes encerrar el código de JavaScript entre comillas. Para fragmentos de código simples de una línea, puedes usar una string entre comillas estándar:
CREATE TEMP FUNCTION plusOne(x FLOAT64) RETURNS FLOAT64 LANGUAGE js AS "return x+1;"; SELECT val, plusOne(val) AS result FROM UNNEST([1, 2, 3, 4, 5]) AS val;
En este ejemplo, se produce el siguiente resultado:
+-----------+-----------+
| val | result |
+-----------+-----------+
| 1 | 2.0 |
| 2 | 3.0 |
| 3 | 4.0 |
| 4 | 5.0 |
| 5 | 6.0 |
+-----------+-----------+
En los casos en que el fragmento contiene comillas o consta de varias líneas, usa bloques con comillas triples:
CREATE TEMP FUNCTION customGreeting(a STRING) RETURNS STRING LANGUAGE js AS r""" var d = new Date(); if (d.getHours() < 12) { return 'Good Morning, ' + a + '!'; } else { return 'Good Evening, ' + a + '!'; } """; SELECT customGreeting(names) AS everyone FROM UNNEST(['Hannah', 'Max', 'Jakob']) AS names;
En este ejemplo, se produce el siguiente resultado:
+-----------------------+ | everyone | +-----------------------+ | Good Morning, Hannah! | | Good Morning, Max! | | Good Morning, Jakob! | +-----------------------+
Incluye bibliotecas de JavaScript
Puedes extender tus UDF de JavaScript con la sección OPTIONS
.
En esta sección, se permite especificar bibliotecas de código externas para la UDF.
CREATE TEMP FUNCTION myFunc(a FLOAT64, b STRING) RETURNS STRING LANGUAGE js OPTIONS ( library=['gs://my-bucket/path/to/lib1.js', 'gs://my-bucket/path/to/lib2.js']) AS r""" // Assumes 'doInterestingStuff' is defined in one of the library files. return doInterestingStuff(a, b); """; SELECT myFunc(3.14, 'foo');
En el ejemplo anterior, el código en lib1.js
y lib2.js
está disponible para cualquier código en la sección [external_code]
de la UDF.
Recomendaciones para las UDF de JavaScript
Realiza un filtro previo a tu entrada
Si tu entrada se puede filtrar antes de pasarla a una UDF de JavaScript, tu consulta podría ser más rápida y económica.
Evita el estado mutable persistente
No almacenes ni accedas a un estado mutable en llamadas a la UDF de JavaScript. Por ejemplo, evita el siguiente patrón:
-- Avoid this pattern CREATE FUNCTION temp.mutable() RETURNS INT64 LANGUAGE js AS r""" var i = 0; // Mutable state function dontDoThis() { return ++i; } return dontDoThis() """;
Usa la memoria de manera eficaz
El entorno de procesamiento de JavaScript tiene un límite de memoria disponible por consulta. Las consultas de UDF de JavaScript que acumulan demasiado estado local pueden fallar debido al agotamiento de la memoria.
Autoriza rutinas
Puedes autorizar UDF como rutinas. Las rutinas autorizadas te permiten compartir resultados de consultas con usuarios o grupos específicos sin darles acceso a las tablas subyacentes que generaron los resultados. Por ejemplo, una rutina autorizada puede calcular una agregación en los datos o buscar un valor de tabla y usar ese valor en un cálculo. Para obtener más información, consulta Rutinas autorizadas.
Agrega descripciones a las UDF
Para agregar una descripción a una UDF, sigue estos pasos:
Console
Ve a la página de BigQuery en la consola de Google Cloud.
En el panel Explorador, expande tu proyecto y conjunto de datos y, luego, selecciona la función.
En el panel Detalles, haz clic en
Editar detalles de rutina para editar el texto de la descripción.En el diálogo, ingresa una descripción en la casilla o edita la descripción existente. Haz clic en Guardar para guardar el nuevo texto de la descripción.
SQL
Para actualizar la descripción de una función, vuelve a crearla con la CREATE FUNCTION
declaración DDL y establece el campo description
en la lista OPTIONS
:
En la consola de Google Cloud, ve a la página de BigQuery.
En el editor de consultas, escribe la siguiente sentencia:
CREATE OR REPLACE FUNCTION mydataset.my_function(...) AS ( ... ) OPTIONS ( description = 'DESCRIPTION' );
Haz clic en
Ejecutar.
Para obtener más información sobre cómo ejecutar consultas, visita Ejecuta una consulta interactiva.
Crea rutinas de enmascaramiento personalizadas
Puedes crear UDF para usar con rutinas de enmascaramiento personalizadas. Las rutinas de enmascaramiento personalizadas deben cumplir con los siguientes requisitos:
- La rutina de enmascaramiento personalizada debe ser una UDF de SQL.
- En la función
OPTIONS
, la opcióndata_governance_type
debe configurarse comoDATA_MASKING
. - Las rutinas de enmascaramiento personalizadas admiten las siguientes funciones:
- Función de cadena
REGEXP_REPLACE
- Función hash
FARM_FINGERPRINT
- Función hash
MD5
- Función hash
SHA1
- Función hash
SHA256
- Función hash
SHA512
- Función de conversión
CAST
- Función de cadena
CONCAT
- Función de cadena
REPLACE
- Función de cadena
REGEX_EXTRACT
- Función de cadena
SUBSTRING
- Función de cadena
TO_BASE32
- Función de cadena
TO_BASE64
- Función de cadena
FROM_BASE32
- Función de cadena
FROM_BASE64
- Función de cadena
TO_HEX
- Función de cadena
FROM_HEX
- Función de utilidad
GENERATE_UUID
- Función de fecha
CURRENT_DATE
- Función fecha y hora
CURRENT_DATETIME
- Función de hora
CURRENT_TIME
- Función de marca de tiempo
CURRENT_TIMESTAMP
- Función de conversión
SAFE_CAST
- Función de cadena
LENGTH
- Función de cadena
STARTS_WITH
- Función de encriptación AEAD
KEYS.KEYSET_CHAIN
- Función de encriptación AEAD
AEAD.ENCRYPT
con keyset_chain (no se admite el uso de claves sin procesar) - Función de encriptación AEAD
AEAD.DECRYPT_BYTES
conKEYS.KEYSET_CHAIN
(no se admite el uso de claves sin procesar) AEAD.DECRYPT_STRING,
Función de encriptación AEAD conKEYS.KEYSET_CHAIN
(no se admite el uso de clave sin procesar)DETERMINISTIC_ENCRYPT
Función de encriptación AEAD conKEYS.KEYSET_CHAIN
(no se admite el uso de clave sin procesar)DETERMINISTIC_DECRYPT_BYTES
Función de encriptación AEAD conKEYS.KEYSET_CHAIN
(no se admite el uso de clave sin procesar)DETERMINISTIC_DECRYPT_STRING
Función de encriptación AEAD conKEYS.KEYSET_CHAIN
(no se admite el uso de clave sin procesar)
- Función de cadena
- Las rutinas de enmascaramiento personalizadas pueden aceptar ninguna entrada o una entrada de tipos de datos de BigQuery, excepto
GEOGRAPHY
ySTRUCT
.GEOGRAPHY
ySTRUCT
no son compatibles con las rutinas de enmascaramiento personalizadas. - No se admiten los parámetros de UDF de SQL con plantilla.
- Cuando se proporciona una entrada, los tipos de datos de entrada y de salida deben ser los mismos.
- Se debe proporcionar un tipo de salida.
- En el cuerpo de la definición, no se puede hacer referencia a otras UDF, subconsultas, tablas o vistas.
- Después de crear una rutina de enmascaramiento, esta no se puede cambiar a una función estándar. Esto significa que si la opción
data_governance_type
se configura comoDATA_MASKING
, no puedes cambiardata_governance_type
mediante sentencias de DDL ni llamadas a la API.
Por ejemplo, una rutina de enmascaramiento que reemplaza el número de seguridad social de un usuario por XXX-XX-XXXX
podría verse de la siguiente manera:
CREATE OR REPLACE FUNCTION SSN_Mask
(ssn STRING) RETURNS STRING
OPTIONS (data_governance_type="DATA_MASKING") AS (
SAFE.REGEXP_REPLACE(ssn, '[0-9]', 'X') # 123-45-6789 -> XXX-XX-XXXX
);
En el siguiente ejemplo, se genera un hash con el sal proporcionado por el usuario, a través de la función
SHA256
:
CREATE OR REPLACE FUNCTION `project.dataset.masking_routine1`( ssn STRING) RETURNS STRING OPTIONS (data_governance_type = 'DATA_MASKING') AS ( CAST(SHA256(CONCAT(ssn, 'salt')) AS STRING format 'HEX') );
En el siguiente ejemplo, se enmascara una columna DATETIME
con un valor constante:
CREATE OR REPLACE FUNCTION `project.dataset.masking_routine2`( column DATETIME) RETURNS DATETIME OPTIONS (data_governance_type = 'DATA_MASKING') AS ( SAFE_CAST('2023-09-07' AS DATETIME) );
Como práctica recomendada, utiliza el prefijo SAFE
cuando sea posible, para evitar exponer datos sin procesar a través de mensajes de error.
Después de crear la rutina de enmascaramiento personalizada, estará disponible como una regla de enmascaramiento en Crea políticas de datos.
Funciones que aporta la comunidad
Las UDF que aporta la comunidad están disponibles en el conjunto de datos públicos bigquery-public-data.persistent_udfs
y en el repositorio de GitHub bigquery-utils
de código abierto.
Para ver todas las UDF de la comunidad en la consola de Google Cloud, destaca el proyecto bigquery-public-data
en el panel Explorador y, luego, expande el conjunto de datos anidado persistent_udfs
dentro de ese proyecto.
Si deseas contribuir a las UDF de este repositorio, consulta Cómo contribuir con UDF para obtener instrucciones.
Limitaciones
Las siguientes limitaciones se aplican a funciones temporales y persistentes definidas por el usuario:
- No se admiten los objetos DOM
Window
,Document
yNode
, ni las funciones que los requieren. - Las funciones de JavaScript que dependen del código nativo pueden fallar, por ejemplo, si realizan llamadas restringidas al sistema.
- Una UDF de JavaScript puede agotar el tiempo de espera y evitar que se complete tu consulta. Los tiempos de espera pueden ser de tan solo 5 minutos, pero pueden variar según distintos factores, incluidos el tiempo de CPU del usuario que consume tu función y el tamaño de las entradas y salidas en la función de JavaScript.
- Las operaciones a nivel de bits en JavaScript manejan solo los 32 bits más significativos
- Las UDF están sujetas a ciertos límites de frecuencia y límites de cuota. Para obtener más información, consulta Límites de UDF.
Las siguientes limitaciones se aplican a las funciones persistentes definidas por el usuario:
- Cada conjunto de datos solo puede contener una UDF persistente con el mismo nombre. Sin embargo, puedes crear una UDF cuyo nombre sea igual al de una tabla en el mismo conjunto de datos.
- Cuando hagas referencia a una UDF persistente desde otra UDF persistente o una vista lógica, debes calificar el nombre con el conjunto de datos. Por ejemplo:
CREATE FUNCTION mydataset.referringFunction() AS (mydataset.referencedFunction());
Las limitaciones siguientes se aplican a las funciones temporales definidas por el usuario.
- Cuando creas una UDF temporal, el
function_name
no puede contener puntos. - Las vistas lógicas y las UDF persistentes no pueden hacer referencia a las UDF temporales.