JasperReports grouping

Рубрика: JasperReports | 20 March 2007, 10:56 | juriy

Возможность группировать данные, и подсчитывать показатели группы (количество элементом, среднее значение некоторого поля и.т.д.) – мощный инструмент формирования гибких отчетов. Именно об этом инструменте и пойдет речь в новой заметке о JasperReports. Вторая часть заметки посвящена возможностям выражений (expressions) в JasperReports.

Все статьи

Группировка данных.

Ниже представлен небольшой пример кода, с которым мы будем работать. Исходный отчет показывает список всех заказчиков: имя, фамилию и среднемесячные затраты.

<?xml version="1.0" encoding="windows-1251"?>
<!DOCTYPE jasperReport
  PUBLIC "-//JasperReports//DTD Report Design//EN"
  "http://jasperreports.sourceforge.net/dtds/jasperreport.dtd">

<jasperReport name="group_demo">

	<style name="Normal" isDefault="true"
		fontSize="12"
		pdfFontName="c:\tahoma.ttf"
		pdfEncoding="Cp1251"
		/>
	<queryString>
		<![CDATA[
			select * from customers c
		]]>
	</queryString>

	<field name="firstname" class="java.lang.String" />
	<field name="lastname" class="java.lang.String" />
	<field name="segment" class="java.lang.String" />
	<field name="city" class="java.lang.String" />
	<field name="spendings" class="java.lang.Integer" />
	<field name="id" class="java.lang.String" />

	<detail>
		<band height="20">
			<textField>
				<reportElement x="51" y="0" width="200" height="20" />
				<textFieldExpression class="java.lang.String">
					<![CDATA[$F{firstname}]]>
				</textFieldExpression>
			</textField>
			<textField>
				<reportElement x="252" y="0" width="200" height="20" />
				<textFieldExpression class="java.lang.String">
					<![CDATA[$F{lastname}]]>
				</textFieldExpression>
			</textField>
			<textField>
				<reportElement x="452" y="0" width="50" height="20" />
				<textFieldExpression class="java.lang.Integer">
					<![CDATA[$F{spendings}]]>
				</textFieldExpression>
			</textField>
		</band>
	</detail>
</jasperReport>

Предположим теперь, что необходимо сгруппировать отчет по городам. Реализовать группировку можно с помощью элемента group:

<?xml version="1.0" encoding="windows-1251"?>
<!DOCTYPE jasperReport
  PUBLIC "-//JasperReports//DTD Report Design//EN"
  "http://jasperreports.sourceforge.net/dtds/jasperreport.dtd">

<jasperReport name="group_demo">

	<style name="Normal" isDefault="true"
		fontSize="12"
		pdfFontName="c:\tahoma.ttf"
		pdfEncoding="Cp1251"
		/>
	<queryString>
		<![CDATA[
			select * from customers c order by c.city
		]]>
	</queryString>

	<field name="firstname" class="java.lang.String" />
	<field name="lastname" class="java.lang.String" />
	<field name="segment" class="java.lang.String" />
	<field name="city" class="java.lang.String" />
	<field name="spendings" class="java.lang.Integer" />
	<field name="id" class="java.lang.String" />

	<group name="city_group">
		<groupExpression>
			<![CDATA[$F{city}]]>
		</groupExpression>
		<groupHeader>
			<band height="40">
				<textField>
					<reportElement x="0" y="10" width="50" height="30"/>
					<textFieldExpression>$F{city}</textFieldExpression>
				</textField>
			</band>
		</groupHeader>
		<groupFooter>
			<band height="10">
			</band>
		</groupFooter>
	</group>

	<detail>
		<band height="20">
			<textField>
				<reportElement x="51" y="0" width="200" height="20" />
				<textFieldExpression class="java.lang.String">
					<![CDATA[$F{firstname}]]>
				</textFieldExpression>
			</textField>
			<textField>
				<reportElement x="252" y="0" width="200" height="20" />
				<textFieldExpression class="java.lang.String">
					<![CDATA[$F{lastname}]]>
				</textFieldExpression>
			</textField>
			<textField>
				<reportElement x="452" y="0" width="50" height="20" />
				<textFieldExpression class="java.lang.Integer">
					<![CDATA[$F{spendings}]]>
				</textFieldExpression>
			</textField>
		</band>
	</detail>
</jasperReport>

Элемент group имеет три подэлемента:

groupExpression – выражение, по которому будет производится группировка
groupHeader – заголовок группы – что будет напечатано перед первым элементом группы
groupFooter – что будет напечатано после последнего элемента группы

Механизм группировки довольно прост – для каждой записи из DataSource вычисляется groupExpression и сравнивается с groupExpression предыдущей записи. Если они не равны – значит нужно закрывать прошлую группу и открывать следующую.
Необходимо помнить, что в SQL запросе необходимо сортировать записи по тем полям, по которым планируется вводить группировку в отчете. Иначе получится белиберда.
Заметьте, что в качестве группировки используется выражение (expression). В этом поле можно написать выражение на языке java (или groovy, но об этом расскажет Вадим ;-)) по которому будет определяться начало новой группы.

Вот пример того, как работают выражения в JasperReports: в следующем отчете мы сгруппируем покупателей по первой букве в имени:

<?xml version="1.0" encoding="windows-1251"?>
<!DOCTYPE jasperReport
  PUBLIC "-//JasperReports//DTD Report Design//EN"
  "http://jasperreports.sourceforge.net/dtds/jasperreport.dtd">

<jasperReport name="group_demo">

	<style name="Normal" isDefault="true"
		fontSize="12"
		pdfFontName="c:\tahoma.ttf"
		pdfEncoding="Cp1251"
		/>
	<queryString>
		<![CDATA[
			select * from customers c order by c.firstname
		]]>
	</queryString>

	<field name="firstname" class="java.lang.String" />
	<field name="lastname" class="java.lang.String" />
	<field name="segment" class="java.lang.String" />
	<field name="city" class="java.lang.String" />
	<field name="spendings" class="java.lang.Integer" />
	<field name="id" class="java.lang.String" />

	<group name="city_group">
		<groupExpression>
			<![CDATA[$F{firstname}.substring(0, 1)]]>
		</groupExpression>
		<groupHeader>
			<band height="40">
				<textField>
					<reportElement x="0" y="10" width="250" height="30"/>
					<textFieldExpression>"Покупатели на букву " + $F{firstname}.substring(0, 1) </textFieldExpression>
				</textField>
			</band>
		</groupHeader>
		<groupFooter>
			<band height="10">
			</band>
		</groupFooter>
	</group>

	<detail>
		<band height="20">
			<textField>
				<reportElement x="51" y="0" width="200" height="20" />
				<textFieldExpression class="java.lang.String">
					<![CDATA[$F{firstname}]]>
				</textFieldExpression>
			</textField>
			<textField>
				<reportElement x="252" y="0" width="200" height="20" />
				<textFieldExpression class="java.lang.String">
					<![CDATA[$F{lastname}]]>
				</textFieldExpression>
			</textField>
			<textField>
				<reportElement x="452" y="0" width="50" height="20" />
				<textFieldExpression class="java.lang.Integer">
					<![CDATA[$F{spendings}]]>
				</textFieldExpression>
			</textField>
		</band>
	</detail>
</jasperReport>

Выражения в JasperReports – это гибкий механизм, который помогает решать множество проблем. В конце заметки я покажу, как можно использовать выражения (expressions) для “подсветки” нужных значений в отчете.

Вернемся к группировкам. Чтобы сгруппировать отчет по нескольким критериям, достаточно описать по одной группе для каждого из них. В примере ниже, покупатели будут сгруппированы сначала по городу, а затем по сегменту:

<?xml version="1.0" encoding="windows-1251"?>
<!DOCTYPE jasperReport
  PUBLIC "-//JasperReports//DTD Report Design//EN"
  "http://jasperreports.sourceforge.net/dtds/jasperreport.dtd">

<jasperReport name="group_demo">

	<style name="Normal" isDefault="true"
		fontSize="12"
		pdfFontName="c:\tahoma.ttf"
		pdfEncoding="Cp1251"
		/>
	<queryString>
		<![CDATA[
			select * from customers c order by c.firstname
		]]>
	</queryString>

	<field name="firstname" class="java.lang.String" />
	<field name="lastname" class="java.lang.String" />
	<field name="segment" class="java.lang.String" />
	<field name="city" class="java.lang.String" />
	<field name="spendings" class="java.lang.Integer" />
	<field name="id" class="java.lang.String" />

	<group name="city_group">
		<groupExpression>
			<![CDATA[$F{city}]]>
		</groupExpression>
		<groupHeader>
			<band height="40">
				<textField>
					<reportElement x="0" y="10" width="250" height="30"/>
					<textFieldExpression>$F{city} </textFieldExpression>
				</textField>
			</band>
		</groupHeader>
		<groupFooter>
			<band height="10">
			</band>
		</groupFooter>
	</group>
	<group name="segment_group">
		<groupExpression>
			<![CDATA[$F{segment}]]>
		</groupExpression>
		<groupHeader>
			<band height="40">
				<textField>
					<reportElement x="30" y="10" width="250" height="30"/>
					<textFieldExpression>$F{segment}</textFieldExpression>
				</textField>
			</band>
		</groupHeader>
		<groupFooter>
			<band height="10">
			</band>
		</groupFooter>
	</group>
	<detail>
		<band height="20">
			<textField>
				<reportElement x="51" y="0" width="200" height="20" />
				<textFieldExpression class="java.lang.String">
					<![CDATA[$F{firstname}]]>
				</textFieldExpression>
			</textField>
			<textField>
				<reportElement x="252" y="0" width="200" height="20" />
				<textFieldExpression class="java.lang.String">
					<![CDATA[$F{lastname}]]>
				</textFieldExpression>
			</textField>
			<textField>
				<reportElement x="452" y="0" width="50" height="20" />
				<textFieldExpression class="java.lang.Integer">
					<![CDATA[$F{spendings}]]>
				</textFieldExpression>
			</textField>
		</band>
	</detail>
</jasperReport>

Добавьте стили по вкусу :-).

Выражения в JasperReports.

Выражения – это механизм JasperReports, который позволяет вставлять в отчет некоторые вычисляемые значения. Выражения записываются на языке Java или Groovy. Выражения можно вставлять в блоки, названия которых оканчиваются на Expression: groupExpression, textFieldExpression, imageExpression и другие. В следующих примерах я покажу несколько примеров выражений на Java.

Не забывайте учитывать, какой class вы указали в качестве параметра выражения. Попытка сложить java.lang.String c java.lang.Integer и получить java.lang.Double ни к чему хорошему не приведет.

Пример первый. Объединение строк.

<textFieldExpression class="java.lang.String">
		<![CDATA[$F{firstname} + " " + $F{lastname}]]>
	</textFieldExpression>

Пример второй. Вычисление значения.

<textFieldExpression class="java.lang.String">
	<![CDATA[
		String.valueOf(
			new Integer($F{firstname}.intValue()*10).intValue())
	]]>
</textFieldExpression>

Пример третий. Подчеркнуть значения больше 400, остальные написать “как есть”.

<textField>
	<reportElement x="452" y="0" width="50" height="20" />
	<textElement isStyledText="true" />
	<textFieldExpression class="java.lang.String">
		<![CDATA[
			$F{spendings}.intValue() > 400 ?
				"<style isUnderline=\"true\">" +
					String.valueOf($F{spendings}.intValue()) +
				"</style>"
			:
			String.valueOf($F{spendings}.intValue())]]>
	</textFieldExpression>
</textField>

Пример четвертый. Выделить другим стилем ячейку, если затраты больше 400.

Этот пример требует некоторых дополнительных пояснений. У элемента reportElement есть необязательный подэлемент: printWhenExpression. В качестве expression необходимо передать java.lang.Boolean. Если вычисление даст результат true, то элемент, который описывается этим reportElement, будет напечатан, иначе не будет.
Чтобы реализовать идею выделения нужных элементов цветом, необходимо сделать вот что:

1. Создать два стиля – green и red – для выделения “хороших” и “плохих” клиентов.
2. Создать два одинаковых блока – один поверх другого. “Нижний” (“красный”) блок будет отображаться всегда. “Трюк” заключается в том, что верхний блок будет отображаться только в том случае, когда значение $F{spendings} больше 400

Вот листинг, который иллюстрирует этот прием:

<?xml version="1.0" encoding="windows-1251"?>
<!DOCTYPE jasperReport
  PUBLIC "-//JasperReports//DTD Report Design//EN"
  "http://jasperreports.sourceforge.net/dtds/jasperreport.dtd">

<jasperReport name="group_demo">

	<style name="Normal" isDefault="true"
		fontSize="12"
		pdfFontName="c:\tahoma.ttf"
		pdfEncoding="Cp1251"
		/>
	<style
		name="green"
		style="Normal"
		forecolor="#00AA00"
	/>

	<style
		name="red"
		style="Normal"
		forecolor="#AA0000"
	/>
	<queryString>
		<![CDATA[
			select * from customers c order by c.firstname
		]]>
	</queryString>

	<field name="firstname" class="java.lang.String" />
	<field name="lastname" class="java.lang.String" />
	<field name="segment" class="java.lang.String" />
	<field name="city" class="java.lang.String" />
	<field name="spendings" class="java.lang.Integer" />
	<field name="id" class="java.lang.String" />

	<detail>
		<band height="20">
			<textField>
				<reportElement x="51" y="0" width="200" height="20" />
				<textFieldExpression class="java.lang.String">
					<![CDATA[$F{firstname}]]>
				</textFieldExpression>
			</textField>
			<textField>
				<reportElement x="252" y="0" width="200" height="20" />
				<textFieldExpression class="java.lang.String">
					<![CDATA[$F{lastname}]]>
				</textFieldExpression>
			</textField>
			<textField>
				<reportElement x="452" y="0" width="50" height="20" style="red"/>
				<textFieldExpression class="java.lang.Integer">
					<![CDATA[$F{spendings}]]>
				</textFieldExpression>
			</textField>
			<textField>
				<reportElement x="452" y="0" width="50" height="20" style="green">
					<printWhenExpression>
						<![CDATA[new Boolean($F{spendings}.intValue() > 400)]]>
					</printWhenExpression>
				</reportElement>
				<textFieldExpression class="java.lang.Integer">
					<![CDATA[$F{spendings}]]>
				</textFieldExpression>
			</textField>
		</band>
	</detail>
</jasperReport>

В следующей заметке речь пойдет о параметрах и переменных в JasperReports.

Предыдущая тема: Оформление отчета

Комментариев: 10

10 Responses to “JasperReports grouping”

Комментарии:

  1. CG

    О! Круто. Спасибо большое! Мне эта статья очень помогла!

  2. volkodav

    Скажите, не понимаю, как сделать, чтоб в конце отчета, скажем выводилась сумма(что-то вроде накладной) по позициям. Скажем, в отчете 3 товара и в конце выводится Итого, где суммируются 3 цены. Или это лучше организовать через SQL в ResultSet?

  3. volkodav

    сам спросил, сам ответил. Надо до объявления полей ввести переменную: в моем случае это сумма по столбцу с ценами

    после ввести это

  4. volkodav

    длинный коммент не вставился, поэтому пишу словами:
    в тэге перед или после тэгов field вставить variable. А потом, в зависимости от того, что считать, выбираем куда поместить textField с variable. В МОЕМ СЛУЧАЕ, чтоб подсчитать сумму по столбцу, надо было в columnFooter.

  5. Cim

    А если необходимо создать отчет, в котором полное имя (Иванов Иван Иванович) представляется в виде инициалов (Иванов И.И.). Полное имя представлено несколькими атрибутами (например, $P{firstname}, $P{secondname}, $P{lastname}). Подскажите пожалуйста, как разобрать эти атрибуты на Фамилию И.О.?

  6. rihard-89

    просто выбираете имя и отчество с помощью полстроки

  7. Cim

    В первом примере “объединение строк” возможен случай, когда одна из переменных пуста и возвращается значение “null”. Как можно убрать “null” из отчета? Вариант с Print when expression работает только в случае, когда нужно написать ограничение на все поле, а как быть с одной переменной в поле?

  8. Dee

    Поддерживаю Сim’a та же проблема, и тоже бы не отказался от ответа, а то приходится на уровне кода формировать строки и передавать уже всё готовое джасперу, хотелось бы это делать на уровне джаспера.

  9. Fox

    Всем доброго времени суток!!
    Я только начинаю работать с IReport у меня по работе возникла следующая задача.

    Вопрос : Как правильно задать групповое выражение для того что бы внутри группы учесть результат подгруппы?

    Мне нужно подвести итог , но с учетом дополнительных условий.
    Есть следующая структура : каскад, филиал
    Например, в филиале и в каскаде есть признак Pr который имеет значение 0 или 1.

    Мне нужно получить следующее

    Общий список каскадов (detail)
    итого по каскаду0 с признаком 0: (group)
    итого по каскаду0 с признаком 1: (group)
    итого по каскаду : (group)

    Общий список каскада 1 (detail)
    итого по каскаду1 с признаком 0:(group)
    итого по каскаду1 с признаком 1:(group)
    итого по каскаду : (group)

    итого по филиалу с признаком 0:(group)
    итого по филиалу с признаком 1:(group)
    итого по филиалу : (group)

    Вопрос : Как правильно задать групповое выражение для того что бы внутри группы учесть результат подгруппы?

Leave a Reply