MYSQL UDF函数返回XML

时间:2015-06-10 12:10:19

标签: mysql xml mysql-udf

情况:

我想创建一个名为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的不同函数完成,该函数将所有数据简单地包装到<![CDATA[]]>中,并且会将数据中]]>的任何先前出现转义为]]]]><![CDATA[>

我还没有能够使用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.的评论所述,有一个repository on Github,可能包含了我所需要的一切。然而,我已经花了几天时间试图让这个在我的系统上运行,但没有成功。任何有关如何在Windows上运行的MySQL 5.5 64位服务器上安装此操作的答案也是可以接受的。

请注意我对制作,制作文件等没有经验。所以请仔细解释。

1 个答案:

答案 0 :(得分: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>

我希望它会有所帮助 问候