Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

This

...

technical

...

article

...

was

...

generously

...

contributed

...

by

...

Chris

...

Webb,

...

an

...

OLAP

...

guru

...

and

...

independent

...

consultant

...

(check

...

him

...

out

...

at

...

Crossjoin

...

Consulting

...

,

...

or

...

on

...

his

...

blog

...

).

...

Chris

...

also

...

has

...

written

...

a

...

book

...

on

...

MDX

...

called

...

'MDX

...

Solutions',

...

that

...

you

...

can

...

get

...

here

...

once

...

you

...

are

...

looking

...

to

...

go

...

deep

...

with

...

Mondrian

...

and

...

MDX

...

!

...

In

...

the

...

last

...

two

...

articles

...

in

...

this

...

series

...

we've

...

focused

...

on

...

creating

...

some

...

common

...

types

...

of

...

calculation

...

in

...

MDX.

...

However,

...

creating

...

calculations

...

isn't

...

the

...

only

...

problem

...

you'll

...

face

...

when

...

creating

...

business

...

intelligence

...

reports:

...

you

...

need

...

to

...

be

...

able

...

to

...

define

...

your

...

queries

...

appropriately

...

too

...

so

...

you

...

see

...

the

...

information

...

you

...

want.

...

In

...

this

...

article

...

we'll

...

look

...

at

...

the

...

ways

...

you

...

can

...

manipulate

...

sets

...

in

...

MDX

...

in

...

order

...

to

...

do

...

this.

...

Resources

...

Before

...

You

...

Get

...

Started

...

To

...

run

...

the

...

queries

...

that

...

Chris

...

presents

...

in

...

this

...

technical

...

article,

...

you

...

will

...

need

...

JPivot,

...

hooked

...

up

...

to

...

the

...

SteelWheelsSales

...

sample

...

cube.

...

You

...

can

...

get

...

both

...

of

...

these

...

pre-configured

...

and

...

ready

...

to

...

use

...

out

...

of

...

the

...

box

...

in

...

the

...

Pentaho

...

Pre-configured

...

Installation

...

(PCI).

...

Just

...

navigate

...

to

...

the

...

Steel

...

Wheels

...

Analysis

...

Samples,

...

and

...

select

...

the

...

MDX

...

Query

...

button

...

from

...

the

...

JPivot

...

toolbar.

...

...

...

...

...

  • running

...

  • locally,

...

  • and

...

  • the

...

  • hypersonic

...

  • database

...

  • running

...

  • locally

...

  • with

...

  • the

...

  • SampleData

...

  • database,

...

  • version

...

  • 1.2

...

  • or

...

  • later.

...

Overview

First of all,

...

though,

...

what

...

is

...

a

...

set?

...

A

...

set

...

is

...

an

...

ordered

...

list

...

of

...

either

...

members

...

or

...

tuples

...

(if

...

you're

...

not

...

sure

...

what

...

a

...

member

...

or

...

a

...

tuple

...

is

...

it

...

might

...

be

...

helpful

...

to

...

reread

...

the

...

first

...

article

...

in

...

this

...

series

...

on

...

'Previous

...

Period

...

Growths')

...

and

...

is

...

written

...

in

...

MDX

...

as

...

a

...

comma-delimited

...

list

...

of

...

uniquenames

...

or

...

tuples

...

enclosed

...

in

...

curly

...

brackets.

...

Here's

...

an

...

example

...

of

...

a

...

set

...

of

...

members:

...

}
Code Block
{[Time].[All Years].[2003], [Time].[All Years].[2004], [Time].[All Years].[2005]}
{code}

And

...

here's

...

an

...

example

...

of

...

a

...

set

...

of

...

tuples:

...

}
Code Block
{([Measures].[Sales],[Markets].[All Markets].[EMEA]),
([Measures].[Sales],[Markets].[All Markets].[APAC])}
{code}

h3. MDX Queries By Example

When 

MDX Queries By Example

When you're

...

writing

...

a

...

SELECT

...

statement

...

in

...

MDX

...

you

...

use

...

sets

...

to

...

specify

...

which

...

data

...

appears

...

on

...

which

...

axis

...

in

...

your

...

query.

...

So,

...

for

...

example,

...

we

...

can

...

take

...

the

...

first

...

set

...

above

...

and

...

put

...

it

...

on

...

the

...

columns

...

axis

...

in

...

a

...

query

...

and

...

take

...

the

...

second

...

set

...

and

...

put

...

it

...

on

...

rows,

...

and

...

the

...

resulting

...

SELECT

...

statement

...

would

...

look

...

like

...

this:

...

}
Code Block
select
{[Time].[All Years].[2003], [Time].[All Years].[2004], [Time].[All Years].[2005]}
on columns,
{([Measures].[Sales],[Markets].[All Markets].[EMEA]), ([Measures].[Sales],[Markets].[All Markets].[APAC])}
on rows
from [SteelWheelsSales]
{code}

Run

...

this

...

query

...

and

...

you'd

...

see

...

the

...

following:


Image Added

As noted above, a set is an ordered list and the members or tuples appear on the axis in the order they are specified in the set.

There are several MDX functions available which mean you don't always have to write out every member's unique name in your sets by hand. Probably the most common is the Members function which will return all the members on either a dimension or on a level in a dimension. Here's an example query which returns all the Months on the Time dimension on rows:

Code Block
select
{[Measures].[Sales]}
on columns,
{[Time].[Months].Members}
on rows
from [SteelWheelsSales]

To

...

use

...

the

...

Members

...

function

...

you

...

simply

...

give

...

the

...

unique

...

name

...

of

...

the

...

dimension

...

or

...

level

...

(in

...

the

...

example

...

above,

...

the

...

unique

...

name

...

of

...

the

...

Months

...

level

...

on

...

Time

...

is

...

[Time

...

].

...

[Months

...

])

...

and

...

add

...

.Members

...

onto

...

the

...

end

...

of

...

it.

...

Other

...

common

...

functions

...

include

...

the

...

Children

...

and

...

Descendants()

...

functions

...

which

...

we

...

came

...

across

...

earlier

...

in

...

this

...

series,

...

both

...

of

...

which

...

return

...

the

...

set

...

of

...

members

...

'underneath'

...

a

...

given

...

member

...

in

...

a

...

dimension.

...

Here's

...

an

...

example

...

of

...

the

...

.Children

...

function

...

which

...

shows

...

all

...

of

...

the

...

Quarters

...

in

...

the

...

Year

...

2004:

Code Block
select {[Measures].[Sales]}
ON COLUMNS,
{[Time].[All Years].[2004].Children}
ON ROWS
from \[SteelWheelsSales

All

...

of

...

these

...

functions

...

return

...

sets

...

of

...

members,

...

but

...

what

...

about

...

creating

...

sets

...

of

...

tuples?

...

This

...

is

...

where

...

another

...

very

...

common

...

function,

...

Crossjoin(),

...

comes

...

in:

...

it

...

takes

...

two

...

sets

...

of

...

members

...

from

...

different

...

dimensions,

...

performs

...

a

...

cartesian

...

product

...

and

...

returns

...

a

...

set

...

of

...

tuples

...

containing

...

every

...

single

...

possible

...

combination

...

of

...

members

...

from

...

each

...

set.

...

So,

...

logically,

Code Block
Crossjoin (
{a1, a2}
,
{x1, x2}) )

will

...

return

...

the

...

set

Code Block
{(a1, x1), (a1, x2), (a2, x1), (a2, x2)}

.

...

Here's

...

an

...

example

...

which

...

returns

...

all

...

possible

...

combinations

...

of

...

Years

...

and

...

Markets

...

on

...

rows:

Code Block

select
{[Measures].[Sales]} ON COLUMNS,
crossjoin(
[Time].[Years].Members,
[Markets].[All Markets].Children)
ON ROWS
from [SteelWheelsSales]

Now

...

we've

...

got

...

our

...

sets,

...

let's

...

do

...

something

...

useful

...

with

...

them.

...

For

...

instance

...

we

...

might

...

want

...

to

...

sort

...

the

...

items

...

in

...

the

...

set

...

in

...

a

...

particular

...

order,

...

and

...

to

...

do

...

that

...

we

...

need

...

to

...

use

...

the

...

Order()

...

function.

...

Order()

...

takes

...

three

...

parameters:

...

the

...

set

...

you

...

wish

...

to

...

sort,

...

the

...

MDX

...

expression

...

you

...

wish

...

to

...

use

...

to

...

sort

...

by,

...

and

...

whether

...

you

...

want

...

to

...

sort

...

in

...

ascending

...

or

...

descending

...

order.

...

Here's

...

an

...

example

...

that

...

sorts

...

all

...

of

...

our

...

Years

...

by

...

Sales:

Code Block

select {[Measures].[Sales]}
ON COLUMNS,order(
[Time].[Years].Members,[Measures].[Sales],bdesc)
ON ROWS
from [SteelWheelsSales]

The

...

second

...

parameter,

...

the

...

MDX

...

expression

...

you

...

wish

...

to

...

order

...

by,

...

can

...

either

...

return

...

a

...

numeric

...

value

...

or

...

a

...

string

...

value.

...

In

...

the

...

example

...

above

...

we

...

used

...

the

...

Sales

...

measure

...

but

...

we

...

could

...

equally

...

have

...

used

...

a

...

tuple,

...

and

...

it's

...

important

...

to

...

remember

...

that

...

this

...

expression

...

is

...

independent

...

of

...

anything

...

we

...

are

...

displaying

...

on

...

columns.

...

So,

...

for

...

instance

...

the

...

following

...

query

...

shows

...

Years

...

ordered

...

in

...

the

...

same

...

way

...

as

...

in

...

the

...

previous

...

query

...

even

...

though

...

the

...

ordering

...

does

...

not

...

reflect

...

the

...

values

...

actually

...

displayed

...

in

...

the

...

grid:

Code Block

select
{([Measures].[Sales], [Markets].[All Markets].[Japan])}
ON COLUMNS,
Order([Time].[Years].Members, [Measures].[Sales], BDESC)
ON ROWS
from [SteelWheelsSales]

This

...

can

...

be

...

pretty

...

confusing

...

for

...

people

...

new

...

to

...

MDX

...

'

...

we

...

are

...

sorting

...

by

...

Sales

...

at

...

the

...

[All

...

Markets

...

]

...

level

...

but

...

displaying

...

Sales

...

for

...

Japan.

...

If

...

we

...

wanted

...

to

...

sort

...

by

...

the

...

values

...

we're

...

seeing

...

in

...

this

...

query,

...

we'd

...

need

...

to

...

explicitly

...

state

...

that

...

we

...

wanted

...

to

...

sort

...

by

...

Sales

...

for

...

Japan

...

by

...

using

...

a

...

tuple

...

in

...

the

...

second

...

parameter:

Code Block

select {([Measures].[Sales], [Markets].[All Markets].[Japan])}
ON COLUMNS,
Order([Time].[Years].Members, ([Measures].[Sales],
[Markets].[All Markets].[Japan]), BDESC)
ON ROWS
from [SteelWheelsSales]

There's

...

one

...

last

...

thing

...

to

...

notice

...

about

...

the

...

Order()

...

function,

...

and

...

that's

...

the

...

'b'

...

in

...

front

...

of

...

the

...

'desc'

...

in

...

the

...

last

...

parameter.

...

This

...

'b'

...

stands

...

for

...

'break

...

the

...

hierarchy':

...

without

...

it

...

Order

...

will

...

first

...

sort

...

the

...

member

...

in

...

the

...

set

...

you

...

pass

...

in

...

into

...

hierarchical

...

order

...

and

...

then

...

sort

...

by

...

the

...

criteria

...

you

...

specify.

...

You

...

can

...

see

...

what

...

this

...

does

...

by

...

running

...

the

...

two

...

queries

...

which

...

show

...

all

...

Months

...

sorted

...

by

...

name

...

'

...

the

...

first

...

breaks

...

the

...

hierarchy,

...

so

...

that

...

you

...

see

...

all

...

the

...

Septembers,

...

then

...

all

...

the

...

Octobers,

...

then

...

all

...

the

...

Novembers

...

and

...

so

...

on,

...

while

...

the

...

second

...

retains

...

the

...

hierarchy

...

so

...

you

...

see

...

all

...

the

...

Months

...

for

...

2003

...

sorted

...

by

...

name,

...

then

...

all

...

the

...

Months

...

for

...

2004

...

sorted

...

by

...

name

...

and

...

all

...

the

...

Months

...

for

...

2005

...

sorted

...

by

...

name.

Code Block

select
{([Measures].[Sales], [Markets].[All Markets].[Japan])}
ON COLUMNS,
Order([Time].[Months].Members, [Time].CurrentMember.Name, BDESC)
ON ROWS
from [SteelWheelsSales]select {([Measures].[Sales], [Markets].[All Markets].[Japan])}
ON COLUMNS,
Order([Time].[Months].Members, [Time].CurrentMember.Name, DESC)
ON ROWS
from [SteelWheelsSales]

Another

...

thing

...

we

...

might

...

want

...

to

...

do

...

with

...

our

...

set

...

is

...

filter

...

it

...

somehow.

...

Looking

...

the

...

query

...

we've

...

just

...

run

...

we

...

can

...

see

...

that

...

there

...

are

...

several

...

rows

...

with

...

no

...

values

...

and

...

we

...

might

...

want

...

to

...

remove

...

those

...

rows

...

from

...

the

...

resultset.

...

The

...

easiest

...

way

...

to

...

do

...

is

...

to

...

add

...

NON

...

EMPTY

...

before

...

the

...

beginning

...

of

...

our

...

row

...

axis

...

definition,

...

as

...

follows:

Code Block

select
{([Measures].[Sales], [Markets].[All Markets].[ Japan ])}
ON COLUMNS,
NON EMPTY Order([Time].[Months].Members, [Time].CurrentMember.Name, DESC)
ON ROWS
from [SteelWheelsSales]

NON

...

EMPTY

...

has

...

removed

...

all

...

rows

...

where

...

every

...

value

...

on

...

that

...

row

...

is

...

a

...

null,

...

and

...

if

...

we

...

added

...

it

...

to

...

the

...

columns

...

axis

...

it

...

would

...

do

...

the

...

same

...

there.

...

To

...

implement

...

more

...

demanding

...

filter

...

criteria,

...

though,

...

we

...

need

...

to

...

use

...

the

...

Filter()

...

function.

...

It

...

takes

...

two

...

parameters:

...

the

...

set

...

you

...

wish

...

to

...

filter

...

and

...

an

...

MDX

...

expression

...

which

...

returns

...

a

...

Boolean

...

value

...

which

...

will

...

be

...

evaluated

...

for

...

every

...

item

...

in

...

the

...

set.

...

Here's

...

an

...

example

...

which

...

only

...

returns

...

Months

...

where

...

Sales

...

is

...

greater

...

than

...

500000:

Code Block

select
{[Measures].[Sales]}
ON COLUMNS,
Filter( Order([Time].[Months].Members, [Time].CurrentMember.Name, DESC) ,
([Measures].[Sales] > 500000.0))
ON ROWS
from [SteelWheelsSales]

This

...

also

...

illustrates

...

the

...

fact

...

that

...

you

...

can

...

nest

...

functions

...

like

...

Filter()

...

and

...

Order()

...

which

...

take

...

a

...

set

...

as

...

one

...

of

...

their

...

parameters

...

and

...

return

...

a

...

set.

...

Another

...

common

...

type

...

of

...

filter

...

is

...

the

...

'top

...

n',

...

for

...

example

...

where

...

you

...

want

...

to

...

return

...

your

...

top

...

ten

...

best-selling

...

products.

...

MDX

...

provides

...

the

...

Topcount()

...

function

...

to

...

allow

...

you

...

to

...

do

...

this

...

(there

...

is

...

also

...

a

...

Bottomcount()

...

function

...

which

...

finds

...

the

...

'bottom

...

n',

...

a

...

Toppercent()

...

function

...

which

...

finds

...

the

...

'top

...

n%'

...

and

...

a

...

Bottompercent()

...

function)

...

and

...

it

...

takes

...

three

...

parameters:

...

the

...

set

...

you

...

wish

...

to

...

filter,

...

the

...

number

...

of

...

items

...

you

...

want

...

to

...

return,

...

and

...

the

...

MDX

...

expression

...

to

...

filter

...

with.

...

Here's

...

an

...

example:

Code Block
select {[Measures].[Sales]}
ON COLUMNS,
TopCount([Product].[Product].Members, 10.0, [Measures].[Sales])
ON ROWS
from [SteelWheelsSales]

For

...

people

...

coming

...

to

...

MDX

...

who

...

have

...

more

...

than

...

a

...

passing

...

knowledge

...

of

...

SQL,

...

it's

...

easy

...

to

...

get

...

confused

...

by

...

the

...

fact

...

that

...

you

...

need

...

to

...

filter

...

using

...

functions

...

in

...

this

...

way

...

and

...

not

...

using

...

the

...

WHERE

...

clause.

...

MDX

...

also

...

has

...

a

...

WHERE

...

clause

...

but

...

it

...

never

...

directly

...

filters

...

what

...

you

...

can

...

see

...

on

...

the

...

rows

...

and

...

columns

...

of

...

your

...

query

...

'

...

it

...

acts

...

more

...

as

...

a

...

third

...

axis,

...

one

...

member

...

thick,

...

which

...

slices

...

the

...

resultset.

...

Consider

...

the

...

following

...

query:

Code Block

select
{[Measures].[Sales]}
ON COLUMNS,
[Product].[Product].Members
ON ROWS
from [SteelWheelsSales]

It

...

has

...

no

...

WHERE

...

clause

...

and

...

it

...

doesn't

...

mention

...

the

...

Time

...

dimension

...

at

...

all,

...

but

...

that

...

doesn't

...

mean

...

we're

...

not

...

slicing

...

by

...

something

...

on

...

Time

...

'

...

we

...

are,

...

we're

...

seeing

...

values

...

for

...

the

...

All

...

member

...

on

...

Time.

...

When

...

we

...

introduce

...

a

...

WHERE

...

clause

...

we

...

can

...

explicitly

...

state

...

which

...

member

...

on

...

the

...

Time

...

dimension

...

we

...

want

...

to

...

see

...

values

...

for,

...

for

...

example

...

the

...

Year

...

2004:

Code Block

select {[Measures].[Sales]}
ON COLUMNS,
[Product].[Product].Members
ON ROWS
from [SteelWheelsSales]
where([Time].[All Years].[2004])

Notice

...

that

...

while

...

the

...

Products

...

that

...

appear

...

on

...

the

...

rows

...

haven't

...

changed,

...

the

...

values

...

for

...

Sales

...

have

...

'

...

we're

...

now

...

seeing

...

the

...

Sales

...

for

...

the

...

Year

...

2004

...

alone.

...

So

...

a

...

WHERE

...

clause

...

can't

...

directly

...

affect

...

which

...

members

...

appear

...

on

...

an

...

axis,

...

but

...

it

...

can

...

do

...

so

...

indirectly

...

if

...

you're

...

applying

...

some

...

kind

...

of

...

filter

...

to

...

that

...

axis.

...

If

...

we

...

go

...

back

...

to

...

our

...

Topcount()

...

example

...

and

...

add

...

a

...

WHERE

...

clause

...

specifying

...

that

...

we

...

see

...

values

...

for

...

2004

...

alone,

...

we'll

...

only

...

see

...

the

...

top

...

10

...

products

...

for

...

2004:

Code Block

select
{[Measures].[Sales]}
ON COLUMNS,
TopCount([Product].[Product].Members, 10.0, [Measures].[Sales])
ON ROWS
from [SteelWheelsSales]
where([Time].[All Years].[2004])

We

...

can

...

now

...

see

...

that

...

the

...

second-highest

...

ranking

...

product

...

is

...

the

...

2001

...

Ferrari

...

Enzo

...

and

...

not

...

the

...

1952

...

Alpine

...

Renault

...

'

...

the

...

Topcount

...

function

...

has

...

taken

...

into

...

account

...

the

...

WHERE

...

clause

...

in

...

its

...

filter.

...

In

...

fact

...

all

...

the

...

filter

...

functions

...

we've

...

looked

...

at

...

in

...

this

...

article

...

so

...

far

...

will

...

do

...

this

...

'

...

but

...

why

...

do

...

they

...

take

...

the

...

WHERE

...

clause

...

into

...

account

...

when

...

they

...

don't

...

take

...

what's

...

on

...

the

...

opposing

...

axis

...

into

...

account,

...

as

...

we

...

saw

...

earlier

...

with

...

the

...

Order

...

function

...

sorting

...

by

...

something

...

other

...

than

...

what

...

was

...

on

...

columns?

...

Simply

...

because

...

they

...

can:

...

the

...

WHERE

...

clause

...

is

...

an

...

axis

...

one

...

member

...

thick

...

so

...

logically

...

there's

...

no

...

problem

...

about

...

which

...

value

...

to

...

use

...

for

...

the

...

filtering

...

'

...

this

...

is

...

only

...

one

...

value

...

to

...

use,

...

the

...

value

...

that's

...

displayed.

...

On

...

the

...

other

...

axes

...

there

...

is

...

potentially

...

more

...

than

...

value

...

to

...

take

...

into

...

account

...

(for

...

example

...

on

...

the

...

rows

...

axis

...

there

...

could

...

be

...

more

...

than

...

one

...

column

...

to

...

choose

...

from)

...

so

...

MDX

...

instead

...

ignores

...

these

...

axes

...

and

...

makes

...

you

...

explicitly

...

state

...

what

...

you

...

want

...

to

...

filter

...

by.

...

As

...

with

...

SQL

...

there

...

is

...

the

...

concept

...

of

...

finding

...

the

...

union

...

of

...

two

...

sets

...

and

...

it's

...

also

...

possible

...

to

...

subtract

...

one

...

set

...

from

...

another

...

and

...

find

...

the

...

intersection

...

between

...

two

...

sets.

...

The

...

following

...

query

...

uses

...

the

...

Union()

...

function

...

to

...

return

...

all

...

Months

...

which

...

have

...

Sales

...

greater

...

than

...

500000

...

plus

...

those

...

which

...

have

...

Sales

...

less

...

than

...

200000:

Code Block

select {[Measures].[Sales]}
ON COLUMNS,
union( filter( [Time].[Months].Members, ([Measures].[Sales],
[Time].Currentmember)<200000 ) ,
filter( [Time].[Months].Members, ([Measures].[Sales],
[Time].Currentmember)>500000 ) )
ON ROWS
from [SteelWheelsSales]

The

...

Except()

...

function

...

takes

...

the

...

removes

...

all

...

members

...

found

...

in

...

one

...

set

...

from

...

another.

...

So,

...

for

...

example,

...

the

...

following

...

query

...

shows

...

all

...

Months

...

with

...

Sales

...

greater

...

than

...

200000

...

except

...

those

...

which

...

have

...

Sales

...

greater

...

than

...

500000:

Code Block

select
{[Measures].[Sales]}
ON COLUMNS,
except(
filter( [Time].[Months].Members, ([Measures].[Sales],
[Time].Currentmember)>200000 ) ,
filter( [Time].[Months].Members, ([Measures].[Sales],
[Time].Currentmember)>500000 ) )
ON ROWS
from [SteelWheelsSales]

The

...

Intersect()

...

function

...

returns

...

only

...

those

...

members

...

present

...

in

...

both

...

sets

...

passed

...

into

...

it,

...

so

...

for

...

example

...

the

...

following

...

query

...

returns

...

the

...

Months

...

which

...

have

...

Sales

...

greater

...

than

...

200000

...

and

...

Sales

...

less

...

than

...

500000:

Code Block

select {[Measures].[Sales]}
ON COLUMNS,
intersect(
filter( [Time].[Months].Members, ([Measures].[Sales],
[Time].Currentmember)>200000 ) ,
filter( [Time].[Months].Members, ([Measures].[Sales],
[Time].Currentmember)<500000 ) )
ON ROWS
from [SteelWheelsSales]

Lastly,

...

it's

...

possible

...

to

...

iterate

...

over

...

every

...

member

...

of

...

a

...

set

...

and

...

perform

...

a

...

set

...

operation

...

at

...

each

...

iteration.

...

Why

...

would

...

you

...

need

...

to

...

do

...

this?

...

Let's

...

say

...

that

...

you've

...

found

...

our

...

top

...

3

...

selling

...

Products

...

and

...

you

...

want

...

to

...

find

...

the

...

top

...

5

...

Months

...

for

...

each

...

of

...

these

...

Products.

...

You

...

might

...

think

...

that

...

the

...

following

...

query

...

would

...

do

...

the

...

trick:

Code Block

select
{[Measures].[Sales]}
ON COLUMNS,
crossjoin( TopCount([Product].[Product].Members, 3, [Measures].[Sales]) ,
TopCount([Time].[Months].Members, 5, [Measures].[Sales]) )
ON ROWS
from [SteelWheelsSales]

However,

...

the

...

fact

...

that

...

the

...

top

...

5

...

Months

...

for

...

each

...

Product

...

are

...

the

...

same

...

might

...

make

...

you

...

suspicious

...

and

...

in

...

fact

...

further

...

investigation

...

will

...

show

...

that

...

this

...

query

...

does

...

not

...

return

...

what

...

we

...

wanted.

...

It

...

actually

...

returns

...

the

...

Crossjoin

...

of

...

the

...

top

...

3

...

Products

...

for

...

all

...

Months

...

and

...

the

...

top

...

5

...

Months

...

for

...

all

...

Products,

...

although

...

the

...

Sales

...

values

...

for

...

each

...

Product/Month

...

tuple

...

are

...

correct.

...

What

...

we

...

need

...

to

...

do

...

is

...

use

...

the

...

Generate()

...

function

...

to

...

iterate

...

through

...

the

...

set

...

of

...

Products

...

and

...

for

...

each

...

Product

...

return

...

the

...

top

...

5

...

Months

...

for

...

that

...

Product

...

as

...

follows:

Code Block

select {[Measures].[Sales]}
ON COLUMNS,
Generate( TopCount([Product].[Product].Members, 3, [Measures].[Sales]) ,
Crossjoin(
{[Product].Currentmember}
,
TopCount([Time].[Months].Members, 5, [Measures].[Sales]) ) )
ON ROWS
from [SteelWheelsSales]

In

...

this

...

case

...

Generate

...

is

...

being

...

passed

...

the

...

set

...

of

...

the

...

top

...

3

...

Products

...

as

...

its

...

first

...

parameter,

...

and

...

then

...

for

...

each

...

of

...

these

...

three

...

Products

...

it

...

Crossjoins

...

the

...

Currentmember

...

on

...

Product

...

(the

...

current

...

Product

...

in

...

the

...

iteration)

...

with

...

the

...

top

...

5

...

Months

...

which

...

produces

...

the

...

result

...

we're

...

looking

...

for.

...

Thanks

...

to

...

Chris

...

Webb

...

for

...

submitting

...

this

...

article

...

!