Data Formatting, Working with XML Data in MySQLNovember 19, 2009 In the Working with XML Data in MySQL article, we explored some of MySQLs XML capabilities, including the --xml command line option, which provides a means of formatting query output as XML, as well as a couple of new XML-specific functions that were introduced in MySQL 5.1.5. In todays article, well continue to explore XML support in MySQL. Specifically, well look at a nicer way to format our resultset so that the proper headers appear in the column headings, rather than the supplied expressions. Well also expand the last example of the previous installment to retrieve all the records from the XML document, using a stored procedure. Adding Our Own Headers to ExtractValues OutputIf you
recall, our final example of the last article imported the contents of an XML
file into a variable using the
CREATE TEMPORARY TABLE client_citizenship (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
xml_data TEXT NOT NULL
);
SET @xml = LOAD_FILE("c:\\client_citizenships.xml");
INSERT INTO client_citizenship VALUES (NULL, @xml);
SELECT xml_data FROM client_citizenship LIMIT 1 INTO @xml;
SELECT ExtractValue(@xml, '//row[2]/field[1]/@name'),
ExtractValue(@xml, '//row[2]/field[1]');
The last SELECT statement extracted the first <field> of the second <row> node:
...
</row>
<row> (row[2])
<field name="client_id">2</field> (field[1])
<field name="date_of_birth">1944-01-15</field>
...
This produced the following resultset: +----------------------------------------------+----------------------------------------+ |ExtractValue(@xml, "//row[2]/field[1]/@name") |ExtractValue(@xml, '//row[2]/field[1]') | +----------------------------------------------+----------------------------------------+ |client_id |2 | +----------------------------------------------+----------------------------------------+ Notice that MySQL uses the expressions as the column headers. To format
the resultset with the field name in the header, we need to use a
SET @header = SELECT ExtractValue(@xml, "//row[2]/field[1]/@name");
SET @qry = CONCAT("SELECT ExtractValue(@xml, \"//row[2]/field[1]\") AS ", @header, ";");
PREPARE stmt FROM @qry;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
Now our data is formatted how were used to seeing it: +-----------+ | client_id | +-----------+ | 2 | +-----------+ Using the ExtractValue() Function Within a Stored ProcedureOne obvious limitation to the above approach is that the rows are hard-coded so, unless youre after specific values, it wont be of much use for more generic tasks, such as importing an entire XML document into a table. For that, we need to use a stored proc. MySqlDumpThe MySqlDump client is a backup program originally written by Igor Romanenko. It can be used to dump a database or a collection of databases for backup or transfer to another database (MySQL or other). The dump typically contains SQL statements to create the table, populate it, or both. However, MySqlDump can also be used to generate files in CSV, other delimited text, or XML format, which is what well be doing here. Although there is more than one way to run MySqlDump, the syntax for what we want to do is as follows: mysqldump --xml databasename [tables] You can also extract to a file by using the standard UNIX/DOS method of outputting to a file. Here is the command that I used to convert my client table into an XML file: C:\>mysqldump --xml temp_table_article client > c:\\sqldump.xml Looking a the sqldump.xml, we can see that the MySqlDump client includes a lot more information than was produced by the --xml flag alone. Whereas it simply outputed each row, field names and values, MySqlDump separates the output into table_structure and table_data sections:
<?xml version="1.0"?>
<mysqldump xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<database name="temp_table_article">
<table_structure name="client">
<field Field="client_id" Type="int(10) unsigned" Null="NO" Key="PRI" Extra="auto_increment" />
<field Field="date_of_birth" Type="date" Null="YES" Key="" Extra="" />
<field Field="gender" Type="char(1)" Null="YES" Key="" Extra="" />
<field Field="logical_delete_indicator" Type="tinyint(1)" Null="NO" Key="" Default="0" Extra="" />
<key Table="client" Non_unique="0" Key_name="PRIMARY" Seq_in_index="1" Column_name="client_id"
Collation="A" Cardinality="4" Null="" Index_type="BTREE" Comment="" />
<key Table="client" Non_unique="0" Key_name="client_id" Seq_in_index="1" Column_name="client_id"
Collation="A" Cardinality="4" Null="" Index_type="BTREE" Comment="" />
<options Name="client" Engine="InnoDB" Version="10" Row_format="Compact" Rows="4" Avg_row_length="4096"
Data_length="16384" Max_data_length="0" Index_length="16384" Data_free="10485760"
Auto_increment="5"
Create_time="2009-10-05 17:51:34" Collation="latin1_swedish_ci" Create_options=""
Comment="" />
</table_structure>
<table_data name="client">
<row>
<field name="client_id">1</field>
<field name="date_of_birth">1976-02-12</field>
<field name="gender">M</field>
<field name="logical_delete_indicator">1</field>
</row>
<row>
<field name="client_id">2</field>
<field name="date_of_birth">1944-01-15</field>
<field name="gender">F</field>
<field name="logical_delete_indicator">0</field>
</row>
<row>
<field name="client_id">3</field>
<field name="date_of_birth">1956-06-04</field>
<field name="gender">M</field>
<field name="logical_delete_indicator">1</field>
</row>
<row>
<field name="client_id">4</field>
<field name="date_of_birth">1938-11-19</field>
<field name="gender">F</field>
<field name="logical_delete_indicator">0</field>
</row>
</table_data>
</database>
</mysqldump>
The
reason that we used this tool to create the XML data structure is because there
are already stored procedures available to insert MySqlDump XML data into a
table. One such proc was written by MySQL developer Alexander
Barkov. His
DELIMITER |
DROP PROCEDURE IF EXISTS xmldump_load |
CREATE PROCEDURE xmldump_load( file_name VARCHAR(128),
database_name VARCHAR(128),
table_name VARCHAR(128))
BEGIN
DECLARE xml TEXT;
DECLARE nrows INT;
DECLARE rownum INT DEFAULT 1;
DECLARE ncols INT;
DECLARE colnum INT DEFAULT 1;
DECLARE ins_list TEXT DEFAULT '';
DECLARE val_list TEXT DEFAULT '';
DECLARE tmp VARCHAR(255);
# load the XML file's contents into a string
SET xml = LOAD_FILE(file_name);
# get the number of <row>s in this table
SET nrows = ExtractValue(xml,
'count(/mysqldump/database[@name=$database_name]/table_data[@name=$table_name]/row)');
# get the number of <field>s (columns) in this table
SET ncols = ExtractValue(xml,
'count(/mysqldump/database[@name=$database_name]/table_data[@name=$table_name]/row[1]/field)');
# for each <row>
WHILE rownum <= nrows DO
# for each <field> (column)
WHILE colnum <= ncols DO
SET tmp = ExtractValue(xml,
'/mysqldump/database[@name=$database_name]/table_data[@name=$table_name]/row[$rownum]/field[$colnum]/@name');
SET ins_list = CONCAT(ins_list, tmp, IF(colnum<ncols, ',', ''));
SET tmp = ExtractValue(xml,
'/mysqldump/database[@name=$database_name]/table_data[@name=$table_name]/row[$rownum]/field[$colnum]');
SET val_list = CONCAT(val_list, '''', tmp ,'''', IF(colnum<ncols, ',', ''));
SET colnum = colnum + 1;
END WHILE;
SET @ins_text = CONCAT('INSERT INTO t1 (', ins_list, ') VALUES (', val_list, ')');
SET ins_list = '';
SET val_list = '';
PREPARE stmt FROM @ins_text;
EXECUTE stmt;
SET rownum = rownum + 1;
SET colnum = 1;
END WHILE;
END |
DELIMITER ;
Examine the code carefully and youll see that the xmldump_load proc uses the same XML tools as weve used so far, such as the LOAD_FILE() and ExtractValue() functions. What it adds are two nested While loops to iterate through every row and column. Here are the steps that one would take to import the sqldump.xml file that we created earlier: The first thing we need to do is import the xmldump_load proc into MySQL if this is the first time that we use it: C:\> mysql db_name < text_file C:\> mysql temp_table_article < c:\xmldump_load.sql You can also load the proc from within the MySql client using the source or \. commands: mysql> source c:xmldump_load.sql; OR mysql> \. c:xmldump_load.sql; The proc accepts the three following input parameters: the XML input file name the database name to write to the table name to create Now we can supply the sqldump.xml file that we created earlier, along with a database and table name:
mysql> call xmldump_load('c:\sqldump.xml ', 'client_info', 'client');
That will insert the contents of the sqldump.xml file into the client table of the client_info database. Coming Soon...A new SQL statement has been accepted for MySQL 6.0, and will be
available in version 6.0.3. |