Как эффективно сравнить 2 файла большого объема XML - PullRequest
0 голосов
/ 02 августа 2020

- РЕДАКТИРОВАТЬ -, уточняющие документы и желаемый результат. (также почему разница между 1-м ответом)

Я пытаюсь сравнить 2 больших XML набора данных с помощью XSLT 2.0 (я также могу использовать 3.0), и у меня возникают проблемы с производительностью.

У меня есть ~ 300 тыс. Записей в файле 1, которые мне нужно сравнить с другими ~ 300 тыс. Записей в файле 2, чтобы увидеть, существуют ли записи из файла 1 в файле 2. Если это так, мне нужно вставить узел в результат. Мне также нужно исключить определенные типы записей из файла 1.

Файл 1

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <row>
        <col1>100035</col1>
        <col2>3000009091</col2>
        <col3>SSL</col3>
        <col4>8.000000</col4>
        <col5>06-Jul-2020</col5>
        <col6>A</col6>
    </row>
    <row>
        <col1>100002</col1>
        <col2>3000009091</col2>
        <col3>UUT</col3>
        <col4>8.000000</col4>
        <col5>07-Jul-2020</col5>
        <col6>P</col6>
    </row>
    <row>
        <col1>100028</col1>
        <col2>3000009091</col2>
        <col3>UUT</col3>
        <col4>8.000000</col4>
        <col5>08-Jul-2020</col5>
        <col6>P</col6>
    </row>
    <row>
        <col1>100200</col1>
        <col2>3000009091</col2>
        <col3>UUT</col3>
        <col4>8.000000</col4>
        <col5>09-Jul-2020</col5>
        <col6>A</col6>
    </row>
    <row>
        <col1>100689</col1>
        <col2>3000009091</col2>
        <col3>UUT</col3>
        <col4>8.000000</col4>
        <col5>10-Jul-2020</col5>
        <col6>A</col6>
    </row>
    <row>
        <col1>100035</col1>
        <col2>3000013528</col2>
        <col3>UFH</col3>
        <col4>8.000000</col4>
        <col5>16-Jul-2020</col5>
        <col6>A</col6>
    </row>
</root>

Файл 2

<?xml version="1.0" encoding="UTF-8"?>
<nm:Data xmlns:nm="namespace">
    <nm:Entry>
        <nm:Record>
            <nm:ID>10084722-Jun-2020UUT</nm:ID>
        </nm:Record>
        <nm:Record>
            <nm:ID>48548310-Jul-2020SSL</nm:ID>
        </nm:Record>
        <nm:Record>
            <nm:ID>10000201-Jul-2020UUT</nm:ID>
        </nm:Record>
        <nm:Record>
            <nm:ID>57307407-Jul-2020SSL</nm:ID>
        </nm:Record>
        <nm:Record>
            <nm:ID>10003516-Jul-2020UFH</nm:ID>
        </nm:Record>
        <nm:Record>
            <nm:ID>10020009-Jul-2020UUT</nm:ID>
        </nm:Record>
        <nm:Record>
            <nm:ID>00155501-Jun-2020UUT</nm:ID>
        </nm:Record>
        <nm:Record>
            <nm:ID>10533728-May-2020UUT</nm:ID>
        </nm:Record>
    </nm:Entry>
    <nm:Entry>
        <nm:Record>
            <nm:ID>99954801-Jul-2020UUT</nm:ID>
        </nm:Record>
    </nm:Entry>
    <nm:Entry>
        <nm:Record>
            <nm:ID>30254801-Jun-2020UFH</nm:ID>
        </nm:Record>
    </nm:Entry>
</nm:Data>

Желаемый результат (скопируйте записи A и добавить узел "тип"). «Adj», если есть соответствующий идентификатор из файла 2, в противном случае тип «New»:

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <row>
        <type>New</type>
        <col1>100035</col1>
        <col2>3000009091</col2>
        <col3>SSL</col3>
        <col4>8.000000</col4>
        <col5>06-Jul-2020</col5>
        <col6>A</col6>
    </row> 
    <row>
        <type>Adj</type>
        <col1>100200</col1>
        <col2>3000009091</col2>
        <col3>UUT</col3>
        <col4>8.000000</col4>
        <col5>09-Jul-2020</col5>
        <col6>A</col6>
    </row>
    <row>
        <type>New</type>
        <col1>100689</col1>
        <col2>3000009091</col2>
        <col3>UUT</col3>
        <col4>8.000000</col4>
        <col5>10-Jul-2020</col5>
        <col6>A</col6>
    </row>
    <row>
        <type>Adj</type>
        <col1>100035</col1>
        <col2>3000013528</col2>
        <col3>UFH</col3>
        <col4>8.000000</col4>
        <col5>16-Jul-2020</col5>
        <col6>A</col6>
    </row>
</root>

Первоначально я не мог получить точный результат, поэтому я пошел на компромисс с помощью следующего xslt; однако производительность низкая, и мне нужно гораздо более эффективное решение.

Попытка XSLT 1 (требуется заменить функции exists () и copy-of ()):

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:nm="namespace"
    exclude-result-prefixes="xs" version="3.0">
    
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
    <xsl:strip-space elements="*"/>
    
    <xsl:variable name="report" select="document('File2.xml')"/>
    
    <xsl:template match="root">
        <root>
            <xsl:for-each select="row[col6 = 'A']">
                <record>
                    <!-- Create value to match against -->
                    <xsl:variable name="inputID" select="concat(col1,col5,col3)"/>
                    
                    <!-- Add Node based on existing match or not -->
                    <xsl:choose>
                        <xsl:when test="exists($report/nm:Data/nm:Entry/nm:Record/nm:ID[. = $inputID])">
                            <type>Adj</type>
                        </xsl:when>
                        <xsl:otherwise>
                            <type>New</type>
                        </xsl:otherwise>
                    </xsl:choose>
                    <!-- Copy all other nodes -->
                    <xsl:copy-of select="."/>
                </record>
            </xsl:for-each>
        </root>
    </xsl:template>
</xsl:stylesheet>

Фактический результат 1 (не идеальный результат, но приемлемый):

<?xml version="1.0" encoding="UTF-8"?>
<root xmlns:nm="namespace">
   <record>
      <type>New</type>
      <row>
         <col1>100035</col1>
         <col2>3000009091</col2>
         <col3>SSL</col3>
         <col4>8.000000</col4>
         <col5>06-Jul-2020</col5>
         <col6>A</col6>
      </row>
   </record>
   <record>
      <type>Adj</type>
      <row>
         <col1>100200</col1>
         <col2>3000009091</col2>
         <col3>UUT</col3>
         <col4>8.000000</col4>
         <col5>09-Jul-2020</col5>
         <col6>A</col6>
      </row>
   </record>
   <record>
      <type>New</type>
      <row>
         <col1>100689</col1>
         <col2>3000009091</col2>
         <col3>UUT</col3>
         <col4>8.000000</col4>
         <col5>10-Jul-2020</col5>
         <col6>A</col6>
      </row>
   </record>
   <record>
      <type>Adj</type>
      <row>
         <col1>100035</col1>
         <col2>3000013528</col2>
         <col3>UFH</col3>
         <col4>8.000000</col4>
         <col5>16-Jul-2020</col5>
         <col6>A</col6>
      </row>
   </record>
</root>

Затем я воспользовался приведенными ниже предложениями и попытался применить как потоковую передачу, так и функцию key () в XSLT 3.0, но мне не удалось заставить что-либо работать. Самым близким был этот xslt здесь, но вывод неверен.

Попытка XSLT 3.0:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:nm="namespace"
    exclude-result-prefixes="#all" version="3.0">

    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
    <xsl:strip-space elements="*"/>

    <xsl:variable name="report" select="document('File2.xml')"/>

    <xsl:key name="ref" match="nm:Data/nm:Entry/nm:Record/nm:ID" use="."/>
    
    <xsl:key name="type-ref" match="row" use="col6"/>
    
    <xsl:mode on-no-match="shallow-copy"/>
    
    <xsl:template match="key('type-ref', 'A')[key('ref', col1 || col3 || col5, $report)]">
        <xsl:copy>
            <type>Adj</type>
            <xsl:apply-templates/>
        </xsl:copy>
    </xsl:template>
    
    <xsl:template match="key('type-ref', 'A')[not(key('ref', col1 || col3 || col5, $report))]">
        <xsl:copy>
            <type>New</type>
            <xsl:apply-templates/>
        </xsl:copy>
    </xsl:template>
    
    <xsl:template match="key('type-ref', 'P')"/>

</xsl:stylesheet>

Вывод 3.0 (обратите внимание, что тип «Adj» не применяется правильно, но P записывает сбрасываются):

<?xml version="1.0" encoding="UTF-8"?>
<root>
   <row>
      <type>New</type>
      <col1>100035</col1>
      <col2>3000009091</col2>
      <col3>SSL</col3>
      <col4>8.000000</col4>
      <col5>06-Jul-2020</col5>
      <col6>A</col6>
   </row>
   <row>
      <type>New</type>
      <col1>100200</col1>
      <col2>3000009091</col2>
      <col3>UUT</col3>
      <col4>8.000000</col4>
      <col5>09-Jul-2020</col5>
      <col6>A</col6>
   </row>
   <row>
      <type>New</type>
      <col1>100689</col1>
      <col2>3000009091</col2>
      <col3>UUT</col3>
      <col4>8.000000</col4>
      <col5>10-Jul-2020</col5>
      <col6>A</col6>
   </row>
   <row>
      <type>New</type>
      <col1>100035</col1>
      <col2>3000013528</col2>
      <col3>UFH</col3>
      <col4>8.000000</col4>
      <col5>16-Jul-2020</col5>
      <col6>A</col6>
   </row>
</root>

У меня недостаточно глубокого понимания функции key (), чтобы настроить ее дальше или как правильно применять операторы copy () при попытке использовать режим потока.

Еще раз спасибо за ввод, и я буду продолжать попытки.

1 Ответ

1 голос
/ 02 августа 2020

Я бы использовал ключ (https://www.w3.org/TR/xslt-30/#key) для индексации второго документа и (возможно, дополнительно) ключ для выбора только определенных row s для всей обработки:

  <xsl:key name="ref" match="data/id" use="."/>
  
  <xsl:key name="type-ref" match="row" use="type"/>

  <xsl:mode on-no-match="shallow-copy"/>
  
  <xsl:template match="root">
      <xsl:copy>
          <xsl:apply-templates select="key('type-ref', 'A')"/>
      </xsl:copy>
  </xsl:template>

  <xsl:template match="row[key('ref', id || code || date, $report)]">
      <xsl:copy>
         <type>Adj</type>
         <xsl:apply-templates/>
      </xsl:copy>
  </xsl:template>
  
  <xsl:template match="row[not(key('ref', id || code || date, $report))]">
      <xsl:copy>
         <type>New</type>
         <xsl:apply-templates/>
      </xsl:copy>
  </xsl:template>

https://xsltfiddle.liberty-development.net/a9HjZH/2

Аргументы функции key объяснены в https://www.w3.org/TR/xslt-30/#func -клавиша :

fn:key( $key-name    as xs:string,
        $key-value   as xs:anyAtomicType*,
        $top     as node()) as node()*

Третий аргумент используется для идентификации выбранного поддерева. Если аргумент присутствует, выбранное поддерево представляет собой набор узлов, которые имеют $top в качестве узла-предка или собственного узла. Если аргумент опущен, выбранное поддерево является документом, содержащим контекстный узел. Это означает, что третий аргумент эффективно по умолчанию равен /.

Применяется к вашим измененным входным выборкам (единственная трудность заключалась в объединении элементов colX в том порядке, в котором их значения появляются во втором документе) это даст

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:nm="namespace"
    exclude-result-prefixes="#all"
    version="3.0">

  <xsl:param name="report">
<nm:Data xmlns:nm="namespace">
    <nm:Entry>
        <nm:Record>
            <nm:ID>10084722-Jun-2020UUT</nm:ID>
        </nm:Record>
        <nm:Record>
            <nm:ID>48548310-Jul-2020SSL</nm:ID>
        </nm:Record>
        <nm:Record>
            <nm:ID>10000201-Jul-2020UUT</nm:ID>
        </nm:Record>
        <nm:Record>
            <nm:ID>57307407-Jul-2020SSL</nm:ID>
        </nm:Record>
        <nm:Record>
            <nm:ID>10003516-Jul-2020UFH</nm:ID>
        </nm:Record>
        <nm:Record>
            <nm:ID>10020009-Jul-2020UUT</nm:ID>
        </nm:Record>
        <nm:Record>
            <nm:ID>00155501-Jun-2020UUT</nm:ID>
        </nm:Record>
        <nm:Record>
            <nm:ID>10533728-May-2020UUT</nm:ID>
        </nm:Record>
    </nm:Entry>
    <nm:Entry>
        <nm:Record>
            <nm:ID>99954801-Jul-2020UUT</nm:ID>
        </nm:Record>
    </nm:Entry>
    <nm:Entry>
        <nm:Record>
            <nm:ID>30254801-Jun-2020UFH</nm:ID>
        </nm:Record>
    </nm:Entry>
</nm:Data>
  </xsl:param>
  
  <xsl:output indent="yes"/>
  <xsl:strip-space elements="*"/>
    
  <xsl:key name="ref" match="nm:Data/nm:Entry/nm:Record/nm:ID" use="."/>
  
  <xsl:key name="type-ref" match="row" use="col6"/>

  <xsl:mode on-no-match="shallow-copy"/>
  
  <xsl:template match="root">
      <xsl:copy>
          <xsl:apply-templates select="key('type-ref', 'A')"/>
      </xsl:copy>
  </xsl:template>

  <xsl:template match="row[key('ref', col1 || col5 || col3, $report)]">
      <xsl:copy>
         <type>Adj</type>
         <xsl:apply-templates/>
      </xsl:copy>
  </xsl:template>
  
  <xsl:template match="row[not(key('ref', col1 || col5 || col3, $report))]">
      <xsl:copy>
         <type>New</type>
         <xsl:apply-templates/>
      </xsl:copy>
  </xsl:template>
  
</xsl:stylesheet>

https://xsltfiddle.liberty-development.net/a9HjZH/3

Наконец, с XSLT 3 и потоковой передачей (например, с Saxon 9 или 10 EE) вы можете использовать другой подход, который считывает второй документ с потоковой передачей в карту, а затем выполняет потоковую передачу через первый входной документ и выполняет сопоставление шаблонов для каждого row, материализованного в памяти:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:map="http://www.w3.org/2005/xpath-functions/map"
    exclude-result-prefixes="#all"
    version="3.0">
    
    <xsl:param name="doc2-uri" as="xs:string">input-sample2.xml</xsl:param>
    
    <xsl:strip-space elements="*"/>
    <xsl:output indent="yes"/>
        
    <xsl:param name="key-map" as="map(xs:string, xs:boolean)">
        <xsl:map>
            <xsl:source-document href="{$doc2-uri}" streamable="yes">
                <xsl:iterate select="data/id">
                    <xsl:map-entry key="string()" select="true()"/>
                </xsl:iterate>
            </xsl:source-document>
        </xsl:map>
    </xsl:param>
    
    <xsl:mode on-no-match="shallow-copy" streamable="yes"/>
    
    <xsl:template match="root">
        <xsl:copy>
            <xsl:apply-templates select="row!copy-of()" mode="grounded"/>
        </xsl:copy>
    </xsl:template>
    
    <xsl:mode name="grounded" on-no-match="shallow-copy"/>
    
    <xsl:template match="row[map:contains($key-map, id || code || date)]" mode="grounded">
        <xsl:copy>
            <type>Adj</type>
            <xsl:apply-templates mode="#current"/>
        </xsl:copy>
    </xsl:template>
    
    <xsl:template match="row[not(map:contains($key-map, id || code || date))]" mode="grounded">
        <xsl:copy>
            <type>New</type>
            <xsl:apply-templates mode="#current"/>
        </xsl:copy>
    </xsl:template>
    
</xsl:stylesheet>

или, для адаптированных входных выборок и уточненных Требование, чтобы обрабатывались только определенные типы row s:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:map="http://www.w3.org/2005/xpath-functions/map"
    xmlns:nm="namespace"
    exclude-result-prefixes="#all"
    version="3.0">
    
    <xsl:param name="doc2-uri" as="xs:string">input2-sample2.xml</xsl:param>
    
    <xsl:strip-space elements="*"/>
    <xsl:output indent="yes"/>
    
    <xsl:param name="key-map" as="map(xs:string, xs:boolean)">
        <xsl:map>
            <xsl:source-document href="{$doc2-uri}" streamable="yes">
                <xsl:iterate select="nm:Data/nm:Entry/nm:Record/nm:ID">
                    <xsl:map-entry key="string()" select="true()"/>
                </xsl:iterate>
            </xsl:source-document>
        </xsl:map>
    </xsl:param>
    
    <xsl:mode on-no-match="shallow-copy" streamable="yes"/>
    
    <xsl:template match="root">
        <xsl:copy>
            <xsl:apply-templates select="row!copy-of()[col6 = 'A']" mode="grounded"/>
        </xsl:copy>
    </xsl:template>
    
    <xsl:mode name="grounded" on-no-match="shallow-copy"/>
    
    <xsl:template match="row[map:contains($key-map, col1 || col5 || col3)]" mode="grounded">
        <xsl:copy>
            <type>Adj</type>
            <xsl:apply-templates mode="#current"/>
        </xsl:copy>
    </xsl:template>
    
    <xsl:template match="row[not(map:contains($key-map, col1 || col5 || col3))]" mode="grounded">
        <xsl:copy>
            <type>New</type>
            <xsl:apply-templates mode="#current"/>
        </xsl:copy>
    </xsl:template>
    
</xsl:stylesheet>

Это должно снизить потребление памяти для первого документа, даже если у вас миллионы строк. Для второго документа он проходит и строит облегченную карту для хранения ключей вместо хранения полного XML дерева и его ключевой функции в памяти.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...