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

PostgreSQL a XML con 3 tablas

Tienes tres niveles de tablas anidadas.

Datos de muestra:

CREATE TABLE a(
  a_id integer primary key,
  name text
);

CREATE TABLE b(
  b_id integer primary key,
  a_id integer references a(a_id),
  val text
);

CREATE TABLE c(
  c_id serial primary key,
  b_id integer references b(b_id),
  blah text
);

INSERT INTO a(a_id, name) VALUES (1, 'fred'),(2, 'bert');

INSERT INTO b(b_id, a_id, val) VALUES 
(11, 1, 'x'), (12, 1, 'y'), (21, 2, 'a'), (22, 2, 'b');

INSERT INTO c(b_id, blah) VALUES
(11, 'whatever'), (11, 'gah'), (12, 'borkbork'), (22, 'fuzz');

Método 1:haga una combinación izquierda, maneje XML en el cliente

La forma más sencilla de manejar esto es hacer una combinación izquierda en las tres tablas, ordenadas desde la más externa a la más interna. Luego itera hacia abajo el conjunto de resultados, cerrando un elemento y abriendo otro cada vez que cambia el tema en ese nivel.

select *
from a left join b on (a.a_id = b.a_id)
       left join c on (b.b_id = c.b_id)
order by a.a_id, b.b_id, c.c_id;

luego recorra las filas devueltas, y para cada fila, pseudocódigo :

cur_row = get_new_row()

if (cur_row[b_id] != prev_row[b_id]) {
   emit_close_tableb();
}
if (cur_row[a_id] != prev_row[a_id]) {
   emit_close_tablea();
   emit_open_tablea(cur_row);
}
if (cur_row[b_id] != prev_row[b_id]) {
   emit_open_tableb(cur_row);
}
emit_tablec(cur_row);

prev_row = cur_row;

Para escribir el XML, usaría algo como XMLWriter . Para leer los datos de la consulta, puede usar algo como PDO o cualquier controlador que prefiera. Si el conjunto de datos es grande, considere usar un cursor para leer los datos.

Esto funciona bien, pero transfiere mucho mucho de exceso de datos, ya que transfieres n copias de los datos de la tabla exterior para cada n filas de la tabla interna asociada a él.

Para reducir el exceso de datos intercambiados, puede seleccionar solo los ID para las tablas externas

select a.a_id, b.b_id, c.*
from a left join b on (a.a_id = b.a_id)
       left join c on (b.b_id = c.b_id)
order by a.a_id, b.b_id, c.c_id;

... luego, cuando cambie a una nueva tablea / tableb, SELECT el resto de sus filas entonces. Probablemente usará una segunda conexión para hacer esto para no interrumpir el conjunto de resultados y el estado del cursor en la conexión principal de la que está leyendo las filas.

Método 2:Hazlo todo en PostgreSQL

Para conjuntos de datos más pequeños, o para los niveles internos de conjuntos de datos más grandes, puede usar el soporte XML de PostgreSQL para construir los documentos XML, por ejemplo:

WITH xmlinput AS (
  SELECT a, b, c
  FROM a
  LEFT JOIN b ON (a.a_id = b.a_id)
  LEFT JOIN c on (b.b_id = c.b_id)
  ORDER BY a.a_id, b.b_id, c.c_id
)
SELECT
  XMLELEMENT(name items,
    xmlagg(
      XMLELEMENT(name a,
        XMLFOREST((a).a_id AS a_id, (a)."name" AS name),
        b_xml
      )
    ORDER BY (a).a_id)
  ) AS output
FROM
(
  SELECT
    a,
    xmlagg(
      XMLELEMENT(name b,
        XMLFOREST((b).b_id AS b_id, (b).val AS val),
        c_xml
      )
    ORDER BY (b).b_id)
    AS b_xml
  FROM
  (
    SELECT
      a, b,
      xmlagg(
        XMLELEMENT(name c,
          XMLFOREST((c).c_id AS c_id, (c).blah AS blah)
        )
      ORDER BY (c).c_id)
      AS c_xml
    FROM xmlinput
    GROUP BY a, b
  ) c_as_xml
  GROUP BY a
) b_as_xml;

... pero realmente, tienes que ser una especie de masoquista para escribir código como ese. Aunque podría resultar bastante rápido.

Para comprender la consulta deberá leer los documentos XML de PostgreSQL . La sintaxis extravagante fue ideada por el comité SQL/XML, no nos culpe.

También tenga en cuenta que row-variables se utilizan mucho en el código anterior para mantenerlo organizado. a , b y c se pasan como filas enteras a las capas exteriores de la consulta. Esto evita la necesidad de meterse con los alias cuando los nombres chocan. La sintaxis (a).a_id , etc, significa "el a_id campo de la variable de fila a ". Consulte el manual de PostgreSQL para obtener más detalles.

Lo anterior utiliza una mejor estructura XML (ver comentarios a continuación). Si desea emitir atributos, no elementos, puede cambiar el XMLFOREST llamadas a XMLATTRIBUTES llamadas.

Salida:

<items><a><a_id>1</a_id><name>fred</name><b><b_id>11</b_id><val>x</val><c><c_id>1</c_id><blah>whatever</blah></c><c><c_id>2</c_id><blah>gah</blah></c></b><b><b_id>12</b_id><val>y</val><c><c_id>3</c_id><blah>borkbork</blah></c></b></a><a><a_id>2</a_id><name>bert</name><b><b_id>21</b_id><val>a</val><c/></b><b><b_id>22</b_id><val>b</val><c><c_id>4</c_id><blah>fuzz</blah></c></b></a></items>

o, bastante impreso:

<?xml version="1.0" encoding="utf-16"?>
<items>
    <a>
        <a_id>1</a_id>
        <name>fred</name>
        <b>
            <b_id>11</b_id>
            <val>x</val>
            <c>
                <c_id>1</c_id>
                <blah>whatever</blah>
            </c>
            <c>
                <c_id>2</c_id>
                <blah>gah</blah>
            </c>
        </b>
        <b>
            <b_id>12</b_id>
            <val>y</val>
            <c>
                <c_id>3</c_id>
                <blah>borkbork</blah>
            </c>
        </b>
    </a>
    <a>
        <a_id>2</a_id>
        <name>bert</name>
        <b>
            <b_id>21</b_id>
            <val>a</val>
            <c />
        </b>
        <b>
            <b_id>22</b_id>
            <val>b</val>
            <c>
                <c_id>4</c_id>
                <blah>fuzz</blah>
            </c>
        </b>
    </a>
</items>

Por favor emita un mejor XML

En una nota al margen, usar atributos como ese en XML parece tentador, pero rápidamente se vuelve difícil y feo trabajar con ellos. Utilice elementos XML normales:

  <Table 1>
    <Nr>1</Nr>
    <Name>blah</Name>
     <Table 2>
       <Nr>1</Nr>
       <Table 3>
          <Col1>42</Col1>
          <Col2>...</Col2>
          <Col3>...</Col3>
          <Col4>...</Col4>
          ...
       </Table 3>
     </Table 2>
   </Table 1>