sql >> Base de Datos >  >> RDS >> PostgreSQL

¿Cómo puedo crear un nuevo archivo XML a partir de una base de datos existente en la base de datos PostgreSQL usando java?

Tengo una implementación funcional donde hago todo dentro de PostgreSQL sin bibliotecas adicionales.

Función de análisis auxiliar

CREATE OR REPLACE FUNCTION f_xml_extract_val(text, xml)
  RETURNS text AS
$func$
SELECT CASE
        WHEN $1 ~ '@[[:alnum:]_]+$' THEN
           (xpath($1, $2))[1]
        WHEN $1 ~* '/text()$' THEN
           (xpath($1, $2))[1]
        WHEN $1 LIKE '%/' THEN
           (xpath($1 || 'text()', $2))[1]
        ELSE
           (xpath($1 || '/text()', $2))[1]
       END;
$func$  LANGUAGE sql IMMUTABLE;

Manejar múltiples valores

La implementación anterior no maneja múltiples atributos en un xpath. Aquí hay una versión sobrecargada de f_xml_extract_val() para eso. Con el tercer parámetro puedes elegir one (el primero), all o dist valores (distintos). Varios valores se agregan a una cadena separada por comas.

CREATE OR REPLACE FUNCTION f_xml_extract_val(_path text, _node xml, _mode text)
  RETURNS text AS
$func$
DECLARE
   _xpath text := CASE
                   WHEN $1 ~~ '%/'              THEN $1 || 'text()'
                   WHEN lower($1) ~~ '%/text()' THEN $1
                   WHEN $1 ~ '@\w+$'            THEN $1
                   ELSE                              $1 || '/text()'
                  END;
BEGIN
   -- fetch one, all or distinct values
   CASE $3
       WHEN 'one'  THEN RETURN (xpath(_xpath, $2))[1]::text;
       WHEN 'all'  THEN RETURN array_to_string(xpath(_xpath, $2), ', ');
       WHEN 'dist' THEN RETURN array_to_string(ARRAY(
            SELECT DISTINCT unnest(xpath(_xpath, $2))::text ORDER BY 1), ', ');
       ELSE RAISE EXCEPTION
          'Invalid $3: >>%<<', $3;
   END CASE;
END
$func$  LANGUAGE plpgsql;

COMMENT ON FUNCTION f_xml_extract_val(text, xml, text) IS '
Extract element of an xpath from XML document
Overloaded function to f_xml_extract_val(..)
$3 .. mode is one of: one | all | dist'

Llamar:

SELECT f_xml_extract_val('//city', x, 'dist');

Parte principal

Nombre de la tabla de destino:tbl; remilgado. clave:id :

CREATE OR REPLACE FUNCTION f_sync_from_xml()
  RETURNS boolean AS
$func$
DECLARE
   datafile text := 'path/to/my_file.xml';  -- only relative path in db dir
   myxml    xml  := pg_read_file(datafile, 0, 100000000); -- arbitrary 100 MB
BEGIN
   -- demonstrating 4 variants of how to fetch values for educational purposes
   CREATE TEMP TABLE tmp ON COMMIT DROP AS
   SELECT (xpath('//some_id/text()', x))[1]::text AS id   -- id is unique  
        , f_xml_extract_val('//col1', x)          AS col1 -- one value
        , f_xml_extract_val('//col2/', x, 'all')  AS col2 -- all values incl. dupes
        , f_xml_extract_val('//col3/', x, 'dist') AS col3 -- distinct values
   FROM   unnest(xpath('/xml/path/to/datum', myxml)) x;

   -- 1.) DELETE?

   -- 2.) UPDATE
   UPDATE tbl t
   SET   (  col_1,   col2,   col3) =
         (i.col_1, i.col2, i.col3)
   FROM   tmp i
   WHERE  t.id = i.id
   AND   (t.col_1, t.col2, t.col3) IS DISTINCT FROM
         (i.col_1, i.col2, i.col3);

   -- 3.) INSERT NEW
   INSERT INTO tbl
   SELECT i.*
   FROM   tmp i
   WHERE  NOT EXISTS (SELECT 1 FROM tbl WHERE id = i.id);
END
$func$  LANGUAGE plpgsql;

Notas importantes

  • Esta implementación verifica en una clave principal si la fila insertada ya existe y actualiza en este caso. Solo se insertan filas nuevas.

  • Utilizo una mesa de preparación temporal para acelerar el procedimiento.

  • Probado con Postgres 8.4 , 9.0 y 9.1 .

  • XML tiene que estar bien formado.

  • pg_read_file() tiene restricciones para ello. El manual:

    El uso de estas funciones está restringido a superusuarios.

    Y:

    Solo archivos dentro del directorio del clúster de la base de datos y el log_directory se puede acceder.

Por lo tanto, debe colocar su archivo fuente allí, o crear un enlace simbólico a su archivo/directorio real.

O puede proporcionar el archivo a través de Java en su caso (lo hice todo dentro de Postgres).

O puede importar los datos en 1 columna de 1 fila de una tabla temporal y tomarlos desde allí.

O puedes usar lo_import como se demuestra en esta respuesta relacionada en dba.SE.

  • SQL para leer XML desde un archivo a la base de datos PostgreSQL

Esta publicación de blog de Scott Bailey me ayudó.