MYSQL UDF 函数返回 XML

Posted

技术标签:

【中文标题】MYSQL UDF 函数返回 XML【英文标题】:MYSQL UDF function to return XML 【发布时间】:2015-08-25 16:24:15 【问题描述】:

情况:

我想创建一个名为 XMLify 的 mysql 函数,它接收一个字符串和一个返回集合的表达式

XMLify(string, expr)

该函数应该将集合中每个返回行的每个返回字段包装到它自己的 XML 标记中。标签的名称应该是字段名称。

小例子:

select XMLify('foo', (SELECT 1 as `a`, 2 as `b` UNION SELECT 3 as `a`, 4 as `b`));

应该返回:

<foo><a>1</a><b>2</b></foo><foo><a>3</a><b>4</b></foo>

我想要这个,因为它使我能够运行具有许多连接和/或依赖子查询的复杂查询,而不必向客户端返回冗余数据。

我已经有了一个没有想要构建的功能的解决方法。但这涉及编写不易维护的困难查询。 请参阅下面的示例。

确保字段名称是合法的 XML 节点名称是为了以后担心。一旦函数成立,我会想到一些算法,将字段名称转换为合法的 XML 节点名称。

转义 XML 数据也是为了以后的事。这将使用名为 CDATAify 的不同函数来完成,该函数将简单地将所有数据包装到 &lt;![CDATA[]]&gt; 中,并将数据中任何先前出现的 ]]&gt; 转义到 ]]]]&gt;&lt;![CDATA[&gt; 中。

我无法使用 MySQL 中的存储函数来完成此操作,因为这些函数不接收结果集。此外,即使您将 SQL 作为字符串传入,然后准备语句并执行它,如果您还不知道字段名称,您也无法访问这些字段。

所以现在我想知道这个技巧是否可以通过用户定义的函数 (UDF) 来完成。这是我还没有使用过的东西,我希望在入船前得到您的建议。

问题:

所以我现在的问题是:

回顾一下,我想要一个 MySQL 函数,我可以传递表达式或结果集,并且我还可以使用结果集的字段名称。 我是否正确地假设这在存储函数中是不可能的? UDF 会将表达式/它们的结果集作为参数吗? UDF 是否允许我访问结果集的字段名称,以便我可以将它们用作 XML 标记名称 它也可以在 Windows 上运行吗?我读到 UDF 有一些限制 我还没有想到更好的方法吗? 我是否可以在自己的开发计算机上创建一个 UDF .dll,然后将 .dll 文件复制到我的服务器并在那里使用它? 如何让这个节目连载?请仔细考虑并考虑到我在 Windows 计算机上安装的是 MySQL 5.5 64 位。

示例:

想象一下有以下 3 个表格:

users:         grades:             toys:
+----+------+  +--------+-------+  +--------+--------------+
| id | name |  | userid | grade |  | userid | toy          |
+----+------+  +--------+-------+  +--------+--------------+
|  1 | Bart |  |      1 |     E |  |      1 | slingshot    |
|  2 | Lisa |  |      1 |     E |  |      1 | Krusty       |
| .. | ...  |  |      2 |     A |  |      2 | Malibu Stacy |  
| .. | ...  |  |      2 |     B |  |      2 | calculator   |
+----+------+  +--------+-------+  +--------+--------------+

我想要的结果是,仅限于 Bart 和 Lisa:

<users>
    <user>
        <id><![CDATA[1]]></id>
        <name><![CDATA[Bart]]></name>
        <grades>
            <grade><![CDATA[E]]></grade>
            <grade><![CDATA[E]]></grade>
        </grades>
        <toys>
            <toy><![CDATA[slingshot]]></toy>
            <toy><![CDATA[Krusty]]></toy>
        </toys>
    </user>
    <user>
        <id><![CDATA[1]]></id>
        <name><![CDATA[Lisa]]></name>
        <grades>
            <grade><![CDATA[A]]></grade>
            <grade><![CDATA[B]]></grade>
        </grades>
        <toys>
            <toy><![CDATA[Malibu Stacey]]></toy>
            <toy><![CDATA[calculator]]></toy>
        </toys>
    </user>
</users>

考虑:

我不希望在 php 或 C# 中必须首先查询用户表,然后为每个用户运行两个额外的成绩和玩具查询。因为对于 1000 个用户,我将运行 2001 个查询。 我也不想运行包含所有连接的查询并检查 PHP 或 C# 中的结果集,因为发送用户名的次数与成绩数乘以玩具数一样多。想象一下,有一个包含巨大 blob 的用户字段! 我不能简单地在连接表上使用 GROUP_CONCAT,因为成绩/玩具仍会显示为两倍。 如果我将 GROUP_CONCAT 与 DISTINCT 一起使用,我将失去相同的成绩,例如 Bart 的两个 E。

所以目前我会使用下面的语句来得到这个结果,涉及两个相关的子查询。这很好用:

SELECT
    CONCAT(
        '<users>',
            IFNULL(
                GROUP_CONCAT(
                    '<user>',
                        '<id><![CDATA[',
                            REPLACE(u.id,']]>',']]]]><![CDATA[>'),
                        ']]></id>',
                        '<name><![CDATA[',
                            REPLACE(u.name,']]>',']]]]><![CDATA[>'),
                        ']]></name>',
                        '<grades>',
                            (
                                SELECT
                                    IFNULL(
                                        GROUP_CONCAT(
                                            '<grade><![CDATA[',
                                                REPLACE(g.grade,']]>',']]]]><![CDATA[>'),
                                            ']]></grade>'
                                            SEPARATOR ''
                                        ),
                                    '')
                                FROM
                                    grades g
                                WHERE
                                    g.userid = u.id
                            ),
                        '</grades>',
                        '<toys>',
                            (
                                SELECT
                                    IFNULL(
                                        GROUP_CONCAT(
                                            '<toys><![CDATA[',
                                                REPLACE(t.toy,']]>',']]]]><![CDATA[>'),
                                            ']]></toys>'
                                            SEPARATOR ''
                                        ),
                                    '')
                                FROM
                                    toys t
                                WHERE
                                    t.userid = u.id
                            ),
                        '</toys>',
                    '</user>'
                    SEPARATOR ''
                ),
                ''
            ),
        '</users>'
    )
FROM
    users u
WHERE
    u.name = 'Bart' or u.name = 'Lisa'
;

现在您可能会注意到,这是一个相当大且丑陋的查询,阅读时会伤眼。 维护这样的查询很困难。 如果我有我的函数 XMLify 和 CDATAify,我可以简单地写这个:

SELECT
    XMLify('users',(
        XMLify('user',(
            SELECT
                CDATAify(u.id) as id,
                CDATAify(u.name) as name,
                XMLify('grade',(
                    SELECT
                        CDATAify(g.grade) as grade
                    FROM
                        grades g
                    where
                        g.userid = u.id
                )) AS grades,
                XMLify('toys',(
                    SELECT
                        CDATAify(t.toy) as toy
                    FROM
                        toys t
                    where
                        t.userid = u.id
                )) AS grades
            FROM
                users u
            WHERE
                u.name = 'Bart' or u.name = 'Lisa'
        ))
    ))
;

编辑:

正如 N.B. 在 cmets 中提到的,有一个 repository on Github,可能包含我需要的所有东西。然而,我现在花了几天时间试图让它在我的系统上运行,但没有成功。任何包含如何在 Windows 上运行的 MySQL 5.5 64 位服务器上安装它的分步操作方法的答案也是可以接受的。

请考虑到我没有使用 make、makefile 等方面的经验。所以请详细解释一下。

【问题讨论】:

好吧,它似乎是this would be exactly what you want。不过,您是否选择了正确的路径值得怀疑,您唯一缺少的就是字段名称。 @N.B.我现在已经花了几天的时间来让它工作,但我一直失败。我尝试将“win”文件夹中提供的 lib_mysqludf_xql.dll 直接复制到我的“MySQL Server 5.5/lib/plugin”并运行 installdb.sql,但我不断收到ERROR 1126 (HY000): Can't open shared library 'lib_mysqludf_xql.dll' (errno: 193)。我也想编译我自己的 DLL,但不管我做什么,我总是失败。我尝试使用 Windows SDK、CMake、Cygwin,但似乎我只是不知道如何创建自己的 .dll。我在 Windows 上有 MySQL 5.5 64 位,就像在我的服务器上一样。这不能改变。) Check this link, section troubleshooting - 我从来没有在 Windows 上使用过 MySQL,所以我在那里帮不上什么忙,但可能是你的 C++ 发行版有误,或者 MySQL 有不同的插件目录。 SHOW VARIABLES LIKE '%plugin%'; 应该显示它尝试从哪里加载插件。除此之外,我不知道我还能提供多少帮助:/ @N.B.经过大量的试验和错误,我设法在 CygWin 中编译了我自己的 DLL。 (我实际上得到了cyg_mysqludf_xql.dlllib_mysqludf_xql.alib_mysqludf_xql.dll.alib_mysqludf_xql.la。我想我只需要使用 .dll 文件。但疯狂的是当我使用这个文件时,我得到了与分发的 .dll ,除了 errorno 现在是 126 而不是 193。(如果文件不存在,errorno 126 也是你得到的。所以这对我来说很奇怪。) 您可能会遇到中间加载问题(而错误 193 可能是平台位数问题 - 32 位与 64 位)。 lib_mysqludf_xql.dll 需要其他库,例如 libxml2。如果后者未正确安装或无法访问,则 UDF 加载将失败即使第一个 DLL 确实存在。要查看是否是这种情况,您可以使用 PROCMON.EXE 或 DEPENDS.EXE 等工具。 【参考方案1】:

x,就在今天我发现了这个问题,我只是希望不要(太)迟回答这个问题,如果太晚了,也许它会帮助别人。

因为 MySql 不允许对函数或触发器实现动态查询,我只是选择实现存储过程。

DELIMITER //
DROP PROCEDURE IF EXISTS XMLify//
CREATE PROCEDURE XMLify(IN wraper VARCHAR(100), IN expr VARCHAR(1000))
LANGUAGE SQL NOT DETERMINISTIC READS SQL DATA SQL SECURITY INVOKER
BEGIN
    DECLARE done INT DEFAULT FALSE;
    DECLARE col_name VARCHAR(255);
    DECLARE cur1 CURSOR FOR
    SELECT
        column_name
    FROM
        information_schema.columns
    WHERE
        table_schema = 'test' AND /*Name of the database (schema)*/
        table_name = 'temp' AND
        column_name <> 'c4l5mn';
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;

    DROP TABLE IF EXISTS temp;
    SET @SQL = CONCAT('CREATE TABLE temp (c4l5mn TINYINT NOT NULL DEFAULT ''1'') AS ', expr);
    PREPARE stmt1 FROM @SQL;
    EXECUTE stmt1;
    DEALLOCATE PREPARE stmt1;

    OPEN cur1;
    SET col_name = '';
    SET @SQL = '';
    read_loop: LOOP
        FETCH cur1 INTO col_name;
        IF done THEN
          LEAVE read_loop;
        END IF;
        SET @SQL = CONCAT(@SQL, '<', col_name, '>'', ', col_name, ', ''</', col_name, '>');
    END LOOP;
    CLOSE cur1;
    SET @SQl = CONCAT('SELECT GROUP_CONCAT(CONCAT(''<', wraper, '>', @SQL, '</', wraper, '>'') SEPARATOR '''') row FROM temp GROUP BY c4l5mn');
    PREPARE stmt1 FROM @SQL;
    EXECUTE stmt1;
    DEALLOCATE PREPARE stmt1;
    DROP TABLE IF EXISTS temp;
END//
DELIMITER ;

就是这样,现在你可以这样称呼它

CALL XMLify('foo', 'SELECT 1 as `a`, 2 as `b` UNION SELECT 3, 4');

它会返回

<foo><a>1</a><b>2</b></foo><foo><a>3</a><b>4</b></foo>

打电话

CALL XMLify('foo', 'SELECT 1 as a, 2 as b, 3 as c UNION SELECT 4, 5, 6');

会回来

<foo><a>1</a><b>2</b><c>3</c></foo><foo><a>4</a><b>5</b><c>6</c></foo>

我只是希望它会有所帮助 问候

【讨论】:

以上是关于MYSQL UDF 函数返回 XML的主要内容,如果未能解决你的问题,请参考以下文章

为啥 MySQL UDF 返回随机数据?

返回 STRING 的 MySQL UDF 与数据重叠

无法序列化 PySpark UDF

在 Spark UDF 中处理 XML 字符串并返回 Struct Field

删除 initid->ptr、MySQL 聚合函数 (UDF) 时失去与 MySQL 服务器的连接

SQL 你如何让 udf 在错误时返回 null?