顺子の杂货铺
生命不息,折腾不止,且行且珍惜~

30-UDF自定义函数详解

DMIT VPS

UDF自定义函数详解

本篇介绍如何在各数据库平台创建和使用自定义函数(UDF),涵盖Java UDF、Python UDF及内置函数扩展。


一、概述

UDF(User Defined Function)允许用户根据业务需求扩展SQL函数能力,适用于复杂计算逻辑、内置函数无法满足的场景。

1.1 UDF类型

类型 说明 返回值
标量UDF 一行输入,一行输出(最常用) 单值
表值UDF 返回结果集(可当表使用) 多行多列
聚合UDF 多行输入,单行输出 聚合值

二、MySQL UDF

2.1 C/C++ UDF开发

MySQL原生支持C/C++编写的UDF,需要编译为动态链接库。

基本结构:

// hello.cc
#include 
#include 

extern "C" {
    // 初始化函数
    my_bool hello_init(UDF_INIT *init, UDF_ARGS *args, char *message) {
        // 校验参数
        if (args->arg_count != 1 || args->arg_type[0] != STRING_RESULT) {
            strcpy(message, "Hello requires one string argument");
            return 1;
        }
        return 0;
    }

    // 销毁函数
    void hello_deinit(UDF_INIT *init) {
        // 清理资源
    }

    // 主函数
    char *hello(UDF_INIT *init, UDF_ARGS *args, char *result,
                unsigned long *length, char *is_null, char *error) {
        static char buffer[256];
        snprintf(buffer, sizeof(buffer), "Hello, %s!", args->args[0]);
        *length = strlen(buffer);
        return buffer;
    }
}

编译与部署:

# 编译
g++ -shared -fPIC -I/usr/include/mysql hello.cc -o libhello.so

# 部署到MySQL
sudo cp libhello.so /usr/lib/mysql/plugin/
sudo chmod 755 /usr/lib/mysql/plugin/libhello.so

# 注册函数
CREATE FUNCTION hello RETURNS STRING SONAME 'libhello.so';

# 测试
SELECT hello('World');  -- Hello, World!

# 查看已注册UDF
SELECT * FROM mysql.func;

2.2 MySQL 8.0 Java UDF

MySQL 8.0+支持Java编写的UDF(需要MySQL Shell或企业版)。

// MyJavaUDF.java
public class MyJavaUDF {
    public static String concatPrefix(String input, String prefix) {
        return prefix + input;
    }

    public static double calculateArea(double radius) {
        return Math.PI * radius * radius;
    }
}
-- 创建Java UDF
CREATE FUNCTION concat_prefix(input VARCHAR(255), prefix VARCHAR(255))
RETURNS VARCHAR(255)
SONAME 'MyJavaUDF.jar';

CREATE FUNCTION calculate_area(radius DOUBLE)
RETURNS DOUBLE
SONAME 'MyJavaUDF.jar';

-- 测试
SELECT concat_prefix('World', 'Hello ');  -- Hello World
SELECT calculate_area(5);  -- 78.5398

三、Oracle UDF

3.1 PL/SQL函数

Oracle使用PL/SQL创建自定义函数。

-- 创建函数
CREATE OR REPLACE FUNCTION get_discount_price(
    p_price IN NUMBER,
    p_discount_rate IN NUMBER DEFAULT 0.1
) RETURN NUMBER IS
BEGIN
    RETURN ROUND(p_price * (1 - p_discount_rate), 2);
END get_discount_price;
/

-- 调用
SELECT get_discount_price(100, 0.2) FROM dual;  -- 80
SELECT get_discount_price(100) FROM dual;       -- 90(使用默认值)

-- 在SQL中使用
SELECT product_name, price, get_discount_price(price, 0.15) AS discounted_price
FROM products;

3.2 Oracle Java UDF(Oracle JVM)

// MyOracleUDF.java
public class MyOracleUDF {
    public static String reverseString(String input) {
        if (input == null) return null;
        return new StringBuilder(input).reverse().toString();
    }
}
-- 加载Java类
loadjava -u username/password MyOracleUDF.class

-- 创建函数
CREATE OR REPLACE FUNCTION reverse_string(input VARCHAR2)
RETURN VARCHAR2 AS LANGUAGE JAVA
NAME 'MyOracleUDF.reverseString(java.lang.String) return java.lang.String';
/

-- 调用
SELECT reverse_string('Hello') FROM dual;  -- olleH

四、ClickHouse UDF

4.1 Lambda表达式(简单UDF)

ClickHouse支持在查询中直接定义Lambda函数。

-- 定义临时Lambda
SELECT arrayFilter(x -> x > 0, [-1, 2, -3, 4]) AS result;  -- [2,4]

-- 使用higherOrderFunction
SELECT transform(['a', 'b', 'c'], ['a', 'b'], ['A', 'B'], 'Unknown') AS result;
-- ['A', 'B', 'Unknown']

4.2 ClickHouse Java UDF

ClickHouse通过ExecutableUserDefinedFunction支持外部程序UDF。

JSON配置文件:

// /etc/clickhouse-server/udf/hello_udf.xml


    
        executable
        HELLO
        String
        String
        hello_udf.py
        JSONEachRow
    

Python脚本:

#!/usr/bin/env python3
# hello_udf.py
import sys
import json

for line in sys.stdin:
    data = json.loads(line.strip())
    result = f"Hello, {data.get('input', '')}!"
    print(json.dumps({"result": result}))

使用:

SELECT HELLO('World') AS greeting;
-- Hello, World!

4.3 ClickHouse SQL UDF

-- 创建SQL函数
CREATE FUNCTION get_domain AS (url) ->
    splitByString('/', url)[3];

CREATE FUNCTION is_mobile AS (ua) ->
    ua LIKE '%Mobile%' OR ua LIKE '%Android%';

-- 使用
SELECT get_domain('https://www.example.com/path') AS domain;
-- www.example.com

五、Hive UDF

5.1 Java UDF开发

简单UDF:

import org.apache.hadoop.hive.ql.exec.UDF;
import org.apache.hadoop.io.Text;

public class LowerCaseUDF extends UDF {
    public Text evaluate(Text input) {
        if (input == null) return null;
        return new Text(input.toString().toLowerCase());
    }
}

复杂类型UDF(处理Array/Map):

import org.apache.hadoop.hive.ql.exec.UDF;
import org.apache.hadoop.io.ArrayWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.io.Writable;

public class ArrayLengthUDF extends UDF {
    public Integer evaluate(ArrayWritable input) {
        if (input == null) return null;
        Writable[] values = input.get();
        return values != null ? values.length : 0;
    }
}

5.2 聚合UDF(UDAF)

import org.apache.hadoop.hive.ql.exec.UDAF;
import org.apache.hadoop.hive.ql.exec.UDAFEvaluator;

public class CollectUDAF extends UDAF {
    public static class Evaluator implements UDAFEvaluator {
        private List values = new ArrayList<>();

        public void init() {
            values.clear();
        }

        public boolean iterate(String value) {
            if (value != null) {
                values.add(value);
            }
            return true;
        }

        public List terminatePartial() {
            return values;
        }

        public boolean merge(List other) {
            if (other != null) {
                values.addAll(other);
            }
            return true;
        }

        public List terminate() {
            return values;
        }
    }
}

5.3 部署与注册

# 编译并打包
mvn clean package -DskipTests

# 上传JAR到HDFS
hdfs dfs -put /path/to/udf.jar /user/hive/udf/

# 注册临时函数(当前会话有效)
ADD JAR /path/to/udf.jar;
CREATE TEMPORARY FUNCTION lower_case AS 'com.example.LowerCaseUDF';

# 永久函数(需要元数据存储)
CREATE FUNCTION db.lower_case AS 'com.example.LowerCaseUDF'
USING JAR 'hdfs:///user/hive/udf/udf.jar';

# 使用
SELECT lower_case(name) FROM users;
SELECT collectUDAF(phone_number) FROM users;

六、MaxCompute UDF

6.1 Python UDF

MaxCompute支持直接编写Python UDF。

# odps_udf.py
from odps.udf import annotate

@annotate('string->string')
class LowerCase(object):
    def evaluate(self, input_str):
        if input_str is None:
            return None
        return input_str.lower()

@annotate('double,double->double')
class CalculateDistance(object):
    def evaluate(self, x1, y1, x2, y2):
        import math
        return math.sqrt((x2-x1)**2 + (y2-y1)**2)
-- 注册Python UDF
CREATE FUNCTION lower_case AS 'odps_udf.LowerCase' USING 'odps_udf.py';

-- 使用
SELECT lower_case('HELLO WORLD') FROM table1;

6.2 Java UDF

import com.aliyun.odps.udf.UDF;

public class GetDomain extends UDF {
    public String evaluate(String url) {
        if (url == null) return null;
        String[] parts = url.split("/");
        if (parts.length >= 3) {
            return parts[2];
        }
        return null;
    }
}
-- 注册Java UDF
CREATE FUNCTION get_domain AS 'com.example.GetDomain'
USING 'odps-udf.jar,odps-udf-dist.jar';

-- 使用
SELECT url, get_domain(url) AS domain FROM urls;

6.3 内置函数扩展

MaxCompute还支持通过GET_IDENTITY系列函数进行安全扩展。


七、Hologres UDF

7.1 PostgreSQL兼容函数

Hologres兼容PostgreSQL,支持多种方式创建函数。

-- SQL函数
CREATE OR REPLACE FUNCTION concat_with_prefix(prefix TEXT, input TEXT)
RETURNS TEXT AS $$
BEGIN
    RETURN prefix || input;
END;
$$ LANGUAGE plpgsql;

-- 测试
SELECT concat_with_prefix('Hello, ', 'World!');  -- Hello, World!

7.2 外部语言函数(如果有扩展)

-- 使用foreign data wrapper扩展
CREATE FOREIGN DATA WRAPPER custom_wrapper HANDLER custom_handler;

八、通用最佳实践

8.1 UDF设计原则

原则 说明
单一职责 每个UDF只做一件事
输入校验 处理NULL和非法输入
性能优先 避免循环,优先使用内置函数
可复用性 考虑通用性,避免业务耦合

8.2 性能注意事项

-- ❌ 避免:在大数据集上使用复杂UDF
SELECT complex_udf(column) FROM huge_table;

-- ✅ 推荐:先过滤再应用UDF
SELECT simple_udf(column)
FROM huge_table
WHERE column IS NOT NULL AND filter_condition;

8.3 调试技巧

-- 添加日志(Oracle示例)
CREATE OR REPLACE FUNCTION debug_udf(p_input VARCHAR2)
RETURN VARCHAR2 IS
    v_result VARCHAR2(100);
BEGIN
    v_result := process_logic(p_input);
    DBMS_OUTPUT.PUT_LINE('Input: ' || p_input || ', Result: ' || v_result);
    RETURN v_result;
END;

九、总结

数据库 UDF语言 类型支持
MySQL C/C++, Java(企业版) 标量
Oracle PL/SQL, Java 标量、表值
ClickHouse SQL, Python, C++ 标量、外部
Hive Java 标量、聚合
MaxCompute Java, Python 标量
Hologres SQL, PostgreSQL 标量

选择合适的UDF实现方式,可以显著提升复杂场景的开发效率和计算性能。

赞(0)
未经允许不得转载:順子の杂货铺 » 30-UDF自定义函数详解
搬瓦工VPS

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

分享创造快乐

联系我们联系我们