Xslt distinct select / Group by

2019-01-20 17:09发布

<statisticItems>
    <statisticItem id="1" frontendGroupId="2336" caseId="50264"  />
    <statisticItem id="2" frontendGroupId="2336" caseId="50264"  />
    <statisticItem id="3" frontendGroupId="2337" caseId="50265"  />
    <statisticItem id="4" frontendGroupId="2337" caseId="50266"  />
    <statisticItem id="5" frontendGroupId="2337" caseId="50266"  />
</statisticItems>

<?xml version="1.0" encoding="utf-8"?>
  <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" omit-xml-declaration="yes"/>

  <xsl:key name="statistic-by-frontendGroupId" match="statisticItem" use="@frontendGroupId" />

  <xsl:for-each select="statisticItems/statisticItem[count(.|key('statistic-by-frontendGroupId', @frontendGroupId)[1]) = 1]">
       <xsl:value-of select="@frontendGroupId"/>
  </xsl:for-each>

What i have done so fare is to go through all distict frontendGroupIds. What i would like to do now is to do a count of all the distict caseIds for each distict frontendGroupId but i cant seem to make that work. Can someone help me here plz?

标签: xslt
2条回答
女痞
2楼-- · 2019-01-20 17:24

You are attempting to sort via the dreaded 'MUENCHIAN' method - something i've found so confusing that is not worth trying - so i worked out my own method.

It uses two transformations instead of one.

The first sorts the data into the right order based on your grouping requirements -- your sample data is already in the right order so i'll leave it out of this explanation( ask if you want help here)

The second transformation does the grouping simply by comparing one node to the next:

    <?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
    <xsl:output method="xml" indent="yes"/>


    <xsl:template match="statisticItems">
        <groupedItem>
            <xsl:apply-templates select="statisticItem"></xsl:apply-templates>
        </groupedItem>
    </xsl:template>

    <xsl:template match="statisticItem">
        <xsl:choose>

            <xsl:when test="position()=1">
                <xsl:apply-templates select="@frontendGroupId" />
            </xsl:when>

            <xsl:when test="@frontendGroupId!=preceding-sibling::statisticItem[1]/@frontendGroupId">
                <xsl:apply-templates select="@frontendGroupId" />
            </xsl:when>

        </xsl:choose>

        <xsl:apply-templates select="@caseId" />


    </xsl:template>

<xsl:template match="@frontendGroupId">
    <group>
        <xsl:variable name="id" select="." ></xsl:variable>
        <xsl:attribute name="count">
            <xsl:value-of select="count(//statisticItem/@frontendGroupId[.=$id])"/>
        </xsl:attribute>        
        <xsl:value-of select="." />
    </group>
</xsl:template>

    <xsl:template match="@caseId">
        <value>
            <xsl:value-of select="." />
        </value>
    </xsl:template>

</xsl:stylesheet>

With this method you can go several groups deep and still have maintainable code.

查看更多
孤傲高冷的网名
3楼-- · 2019-01-20 17:32

You were close:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <xsl:output method="text" />

  <xsl:key 
    name="statistic-by-frontendGroupId" 
    match="statisticItem" 
    use="@frontendGroupId" 
  />

  <xsl:template match="statisticItems">
    <xsl:for-each select="
      statisticItem[
        count(
          . | key('statistic-by-frontendGroupId', @frontendGroupId)[1]
        ) = 1
      ]
    ">
      <xsl:value-of select="@frontendGroupId"/>
      <xsl:value-of select="' - '"/>
      <!-- simple: the item count is the node count of the key -->
      <xsl:value-of select="
        count(
          key('statistic-by-frontendGroupId', @frontendGroupId)
        )
      "/>
      <xsl:value-of select="'&#10;'"/>
    </xsl:for-each>
  </xsl:template>

</xsl:stylesheet>

This results in:

2336 - 2
2337 - 3

EDIT - Oh, I see you want the distinct count within the group. This would be:

<!-- the other key from the above solution is still defined -->

<xsl:key 
  name="kStatisticItemByGroupAndCase" 
  match="statisticItem" 
  use="concat(@frontendGroupId, ',', @caseId)"
/>

<xsl:template match="statisticItems">
  <xsl:for-each select="
    statisticItem[
      count(
        . | key('kStatisticItemByGroup', @frontendGroupId)[1]
      ) = 1
    ]
  ">
    <xsl:value-of select="@frontendGroupId"/>
    <xsl:value-of select="' - '"/>
    <xsl:value-of select="
      count(
        key('kStatisticItemByGroup', @frontendGroupId)[
          count(
            . | key('kStatisticItemByGroupAndCase', concat(@frontendGroupId, ',', @caseId))[1]
          ) = 1
        ]
      )
    "/>
    <xsl:value-of select="'&#10;'"/>
  </xsl:for-each>
</xsl:template>

Which looks (admittedly) a bit frightening. It outputs:

2336 - 1
2337 - 2

The core expression:

count(
  key('kStatisticItemByGroup', @frontendGroupId)[
    count(
      . | key('kStatisticItemByGroupAndCase', concat(@frontendGroupId, ',', @caseId))[1]
    ) = 1
  ]
)

boils down to:

Count the nodes from "key('kStatisticItemByGroup', @frontendGroupId)" that fulfill the following condition: They are the first in their respective "kStatisticItemByGroupAndCase" group.

Looking closely, you will find that this is no more complicated than what you already do. :-)


EDIT: One last hint. Personally, I find this a lot more expressive than the above expressions, because it emphasizes node equality a lot more than the "count(.|something) = 1" approach:

count(
  key('kStatisticItemByGroup', @frontendGroupId)[
    generate-id()
    =
    generate-id(
      key('kStatisticItemByGroupAndCase', concat(@frontendGroupId, ',', @caseId))[1]
    )
  ]
)

The result is the same.

查看更多
登录 后发表回答