设为首页收藏本站

 找回密码
 注册

QQ登录

只需一步,快速开始

查看: 1075|回复: 5

MDX for Everyone (1)

[复制链接]
发表于 2011-5-3 08:37:32 | 显示全部楼层 |阅读模式
本帖最后由 demo 于 2011-5-4 02:04 编辑

Written by Mosha Pasumansky, April 1998

Introduction
“So, what does exactly the word table mean?”
“Ah, that’s just acronym for two-dimensional cube!”
(From conversation of two DBAs in 2000)

This book assumes, that the reader is familiar with basicOLAP terms such as cubes, dimensions, measures, levels,hierarchies, dimension members etc. The understanding of theconcepts slicing, dicing, drill-down, drill-up, aggregationwill be helpful. This book also assumes basic knowledge of SQL.

MDX is a language that allows to describe multidimensionalqueries. As a matter of fact, the name MDX stands for Multi-DimensionaleXtensions. As this name suggests, MDX is an extension of SQL, so youwill find its structure to be similar to this of SQL, and the same keywordsserving similar functions. There will be some differences however. MDXbuilds multidimensional view of the data when SQL builds relational view.

The following table helps to draw analogies betweenrelational and multidimensional terms and it is suggested, that you’ll refer tothis table when MDX and SQL queries are compared

Multidimensional term Relational analog
Cube Table
LevelColumn (string or discrete number)
DimensionSeveral related columns or dimension table
MeasureColumn (discrete or continuous numeric)
Dimension memberThe value in the specific row and column of dimension table

Sample cube

All examples in the following sections will be built around sample cube. This cube is sales analysis cube for hypothetical company which sells computer accessories world-wide. This cube contains typical dimensionssuch as Time, Geographic, Products, Customers etc. The actual cube is attachedto this book and can be viewed using Microsoft OLAP software (such as MDXSample).All queries in the book will work immediately against this cube, except thecases where it specially noted.

Our first MDX query.Let’s start to build our first MDX query. The basic MDXquery has the following structure:

SELECT axis1 ON COLUMNS, axis2 ON ROWS FROM cube

Let’s compare that to the similar SQL statement:

SELECT column1, column2, …, columnn FROM table

You may find some of MDX keywords familiar. Indeed, SELECT and FROM serve exactly the same purpose they do in SQL. The SELECT keyword tells us that we want to execute a query and get data results back. The FROM keyword specifies the source of the data. In the SQL case we receive the data from the table, and in MDX case we receive data from the cube. However, there is something different between above two queries. SQL basically gives us relational view of the data, which is always two-dimensional. The result of the SQL query is a table which has two dimensions: rows and columns. And these dimensions are not symmetrical. Rows are all of the same structure, and this structure is defined by the columns. Columns, of course may be of different types and have different meanings.

In the MDX world, we can specify any number of dimensions toform result of our query. We will call them axes to prevent theconfusion with cube’s dimensions. We can have zero, one, two, three or anyother number of axes. And all these axes are perfectly symmetric.

There is no special meaning to rows and to columns in MDXother than for display purposes. You should note however, that most OLAP tools(such as Microsoft’s MDX Sample) are not capable in visualizing of MDX queryresults containing more than two axes. The list of columns in SQL defines thestructure of the result data. In MDX, you have to define the structure of everyaxis. Therefore in our example axis1 will be definition of the axisdisplayed horizontally, and axis2 will be definition of the axisdisplayed vertically. Axis is a collection of dimension members, or moregenerally tuples. We will discuss tuples later, so for now we’llrestrict ourselves to the axes containing members only.

There are many versatile ways to define an axis in MDX. Wewill start with simple one. Suppose we want to present on the axis all membersof certain dimension. The syntax for such an axis definition would be

<dimension name>.MEMBERS

And similarly, if we want to see all dimension members belonging to the certain level of this dimension, the syntax would be

<dimension name>.<level name>.MEMBERS

Now let’s construct our first MDX query using all information we learned so far. We’ll use as an example cube ‘Sales’. (The definition of this cube can be found in appendix). Our first MDX query will show sales in different continents over years. Therefore the query will look like

SELECT
  Years.MEMBERS ON COLUMNS,
  Regions.Continent.MEMBERS ON ROWS
FROM Sales

The result of this query visualized through the table will look like the following:

  
  

1994


1995


1996


1997

  
N. America
  

120,000


200,000


400,000


600,000

  
S. America
  

-


10,000


30,000


70,000

  
Europe
  

55,000


95,000


160,000


310,000

  
Asia
  

30,000


80,000


220,000


200,000


Let’s try to analyze the results of this query. We have a table with two axes. Horizontal axis (i.e. columns) contains members of dimension ‘Years’, and vertical axis (i.e. rows) contains members of level‘Continent’ of dimension ‘Regions’. All these are explicitly specified in the query. However, one can ask: “And what are numbers in this table mean?”.Indeed, if you look into the query, you will find no reference to any quantitative data. So what happened ? We know, that in the cube we have to always specify measures, which are our quantitative data. Since, we didn’t explicitly tell in our MDX query which measure we want to use, the default measure was used. In our example, he default measure was ‘Sales’ (not to be confused with cube name ‘Sales’!). So,for instance, the interpretation of leftmost top number in the table is “In theyear 1994, the sales in N.America were 120,000”. Now for comparison, let’sconstruct SQL query which produces the same data.

SELECT Continent, Year, Sales FROM Sales_Agg

The result is in the following table:

  
Continent
  

Year


Sales

  
N.America
  

1994


120,000

  
N.America
  

1995


200,000

  
N.America
  

1996


400,000

  
N.America
  

1997


600,000

  
S.America
  

1995


10,000

  
S.America
  

1996


30,000

  
S.America
  

1997


70,000

  
  




  
Asia
  

1997


200,000


(We assume, that the table ‘Sales_Agg’ is an aggregationtable, so we don’t need to do GROUP BY’s and we receive already aggregateddata).

Axis as enumeration

OK, so far we know how to build a basic MDX query and specify axes containing all members of the dimension or level. However,sometimes, we want to be more restrictive in our axes. Dimensions or levels often contain thousands of members and we don’t wanna see all of them. Some members might be irrelevant for our current analysis. For example, if we look into our query, we find out that it produces information about all years. And as we know, Wall Street always looks into last two years for comparison. So, we only want to see sales by continent for 1996 and 1997. Fortunately, MDX designers (see their names in appendix) thought about such scenarios, and we can specify a list of members in curly braces as an axis definition. Forexample:

{ dim.member1, dim.member2, … , dim.membern}

Let’s rewrite our MDX query to take advantage of this syntaxand represent only years 1996 and 1997:

SELECT
{ Years.[1996], Years.[1997] } ON COLUMNS,
  Regions.Continent.MEMBERS      ON ROWS
FROM Sales

You may notice, that names of the members 1996 and 1997 are enclosed in square brackets. This quoting is done to prevent MDX parser confusion over the nature of 1996 and 1997. Without quotes, MDX parser might decide that they are numbers rather than member names. In general, square brackets quoting allows you to have member names containing any symbol such as spaces, dots etc. It is always good idea to enclose your member name in square brackets. It will help you to eliminate some syntax errors, that might take a lot of time to troubleshoot.

MDX syntax allows you to define empty axis by writing { }.This is precisely legal, however it is hard to think about useful example with explicit empty set.

Slicing

Slicing is one of the basic operations in data analysis.We’ll explain the concept and it’s implementation in MDX through examples. Let’s take our previous query. So far only two dimensions, Regions and Years, were involved. So what happens if we add otherdimensions? Perspicacious reader might guess the answer.

We already discussed what happens with measures if nomeasure was explicitly specified in the query. But measures are treated in MDXexactly the same as any other dimension. So for all dimension that were omittedfrom the MDX query, the default members will be assumed. When a dimension has level ‘All’ (i.e. has top level consisting of only one memberwhich aggregates the whole dimension) this member will be chosen as default. Otherwise, the first member of the firstlevel will be chosen as default (we’ll discuss ordering of members in the levellater). So going back to our query we realize, that it gave us sales figures for all products, all customersetc. Suppose, now we are very interested only in sales of computers. Of course,we can build new axis containing only one element:

{ Products.[Product Group].Computers }

And then we can add this axis to the query (we said a query can contain any number of axes). However,this is not preferable solution. The problem is that then the query willcontain three axes, and not all OLAP tools will be able to display it. Luckily,designers of MDX had some experience in OLAP, and they recognized theimportance of slicing in OLAP applications. Therefore, MDX supports slicesthrough the syntax of WHERE keyword. To rewrite our query using slice oncomputers, we’ll use

SELECT
{ Years.[1996], Years.[1997] } ON COLUMNS,
  Regions.Continent.MEMBERS ON ROWS
FROM Sales
WHERE ( Products.[Product Group].[Computers] )

Attention SQL gurus ! WHERE clause in SQL means filtering of rows by specified criteria. The WHEREclause in MDX means slicing the multidimensional query. While theseconcepts are somehow similar, they by no means are equivalent.

OK, now let’s present the formal syntax of WHERE clause

WHERE ( member-of-dim1, member-of-dim2, …, member-of-dimn )

As you can see, you can have more than one member in slice.However, you should remember, that these members should come from different dimensions. This is an obvious restriction. You can not slice by two different products,say, computers and printers. You can only slice by one of them.  Let’s build aquery slieces by specific product(computers) and  specific customer (AT&T).

SELECT
{ Years.[1996], Years.[1997] }  ON COLUMNS,
Regions.Continent.MEMBERS       ON ROWS
FROM Sales
WHERE ( Products.[Product Group].[Computers],Customers.[AT&T] )

To summarize again: This query shows the sales of computers to AT&T in years 1996 and 1997 by all continents.
What if we don’t want to see sales of computers, but rathernumber of units shipped ? This requires us to change the default measure ‘Sales’ toanother measure, ‘Units’. And how we are gonnado that ? Yes, again using slice. We just need to add to the slice measureunits.

SELECT
     { Years.[1996], Years.[1997] } ON COLUMNS,
     Regions.Continent.MEMBERS ON ROWS
FROM Sales
WHERE
(
   Products.[Product Group].[Computers],
  Customers.[AT&T],
  Measures.[Units]
)
 楼主| 发表于 2011-5-3 08:38:13 | 显示全部楼层
本帖最后由 demo 于 2011-5-4 01:44 编辑

First exercises.

Well, we have learnedthe basic MDX constructions. We know how to construct axes in different ways,we know how to slice. Even though it was relatively simple, it already givesyou ability to answer many interesting questions. So, take a challenge, and tryto answer the following questions:
1. Buildthe MDX query which shows sales of products over the years.
2. Buildthe MDX query which shows sales of products to customers in 1996.
3. Buildthe MDX query which compares the numberof printers shipped in Asia against Europe over the years.

Tuples
We mentioned earlier that axes can contain members or tuples,and promised to explain about tuples later. Well, it's time to fulfill that promise.Consider the following example: You want to see sales of computers and printers in Europe and Asia over years 1996and 1997. Well, we can do that by writing an MDXquery with three axes:

SELECT
  { Continent.[Europe], Continent.[Asia] }      ON AXIS(0),
  { Product.[Computers], Product.[Printers] }   ON AXIS(1),
  { Years.[1996], Years.[1997] }                ON AXIS(2)
FROM Sales

This will work, but the result of this query is not a two-dimensional table, but three-dimensional cube. This might be fine, if this query was used internally by data mining application. But what if our boss asked you to report these numbers. Howyou are going to print this three-dimensional thing? The solution would be to rewrite this query in such a way, thateven though it will encapsulate three dimensions, it will still contain twoaxes. We want the query which willproduce the following result:
  
  

1996


1997

  
Europe
  

Computers




Printers



  
Asia
  

Computers




Printers




One special thing about this table is that its rows axiscontains two dimensions. The dimensionProducts is nested into dimensionRegions, such that we produced all fourcombinations between {Europe, Asia} and {Computers, Printers}. Every such a combination is called tuple in MDX. So the tuple is a combination ofdimension members, coming from different dimensions. Syntax for specifying a tuple in MDX is

( member-of-dim1, member-of-dim2, …,member-of-dimk )

Now, don’t you have some kind of déjà vu here? Doesn’t it remind you something you’ve already seen in this book ? Yeah, sure. We havehad exactly the same construction in WHERE clause. As a matter of fact,official syntax of MDX specification defines tuple to follow WHERE clause. We are already familiar with tuples. We used them in the WHERE clause. We just didn’tcall them tuples. A tuple can contain only one member. Unlike axis, tuples cannot be empty. However, you have to clearlydistinguish between member and tuple containing only one member.

member is not equivalent to ( member )

We’ll talk more about member and their properties later.Now, let’s write our MDX query to use tuples.

SELECT
  { Year.[1996], Year.[1997] } ON COLUMNS,
  {
    ( Continent.Europe, Product.Computers ),
    ( Continent.Europe, Product.Printers  ),
    ( Continent.Asia,   Product.Computers ),
    ( Continent.Asia,   Product.Printers  )
  } ON ROWS
FROM Sales

It is important to remember, that just like the case when we put the same dimension into the axis members, when we construct an axis from tuples, all these tuples have to be of the same structure. It means, all tuples have to contain the same number of members, and members on the same positions have to be from the same position. For example the following tuples pairs don’t have same structure:

( Europe, Computers ) and ( Europe, Computers, Sales )
( Europe, Computers ) and ( Europe, Sales )
( Europe, Computers ) and ( Computers, Europe )

Well, thoughtful reader may say now: “Everything is fine, but in this example we build the axis which contains only four tuples, and it was relatively easy to enumerate them. What if you want to see all products against all continents against all years. Now what ? MDX has an answer for this problem. MDX has a lot of built-in functionswhich allow to manipulate with axes and produce new axes. We’ll discuss thesefunction in one of the following chapters.

Calculated members

So far we saw the various ways to query the existing information inside the cube. However almost any OLAP application needs the ability to perform some calculations over the existing data. Let’s take classic example. We have predefined measures Cost and Sales whichdescribe how much did the product cost to us and for how much did we sell it.The next natural thing that one wants to ask is, well, what was our profit outof it. If the cube creator included measure Profit inside the cube – weare safe. However, what we are gonna do if such a measure doesn’t exist ?Luckily, MDX allows us to extend the cube by defining new members todimensions, which values are calculated out of existing ones. The syntax forcalculated members is to put the following construction in front of SELECTstatement:

WITH MEMBER parent.name AS 'expression'

Here, “parent” refers to the parent of the new calculated member “name”. Since dimension is organized as a tree, when we add new member,we should specify its position inside this tree. Fast-thinking reader will immediately point out to the fact, that not all dimensions are organized as classic trees, but rather as multiroot trees. Indeed, even in our example, we wanted to add new member to the measures dimension, and measures don’t havecommon root. Well, in such a situation, the dimension itself serves as virtual root. I.e. dimension name will be specified as parent. So, let’s go ahead and create calculated member Profit in the dimension Measures.

WITH MEMBER Measures.Profit AS 'Measures.Sales – Measures.Cost'

As we just discussed, the parent of the new member is measures dimension itself, the name is Profit, and the expression is prettystraight-forward: Subtract your costs from your sales, and you’ll get theprofit. Let’s put the whole query now, which allows to see the profits ofproducts along years:

WITH
  MEMBER Measures.Profit AS 'Measures.Sales – Measures.Cost'
SELECT  Products.MEMEBERS ON COLUMNS,  Year.MEMEBERS ON ROWS
FROM Sales
WHERE ( Measures.Profit )

As you can see, the calculated members is treated in exactly same manner as normal members. They can be used anywhere, where the normal member can be used, i.e. in axis definition, in WHERE condition, you can even define calculated members using the other calculated members. As example, let’s add to the calculated member Profit another calculated member – ProfitPercent,which will exhibit the profit as percent of the costs. To calculate it, we’lltake the profit and divide it by cost.

WITH
MEMBER Measures.Profit AS 'Measures.Sales – Measures.Cost'
MEMBER Measures.ProfitPercent AS 'Measures.Profit / Measures.Cost',  FORMAT_STRING = '#.#%'
SELECT
  { Measures.Profit, Measures.ProfitPercent } ON COLUMNS
FROM Sales

We see several interesting things in this example. First is the fact, that to define calculated member ProfitPercent we used anothercalculated member. Second is the FORMAT_STRING statement right after thedefinition of ProfitPercent expression. As a matter of fact, MDX allowsyou to specify several properties of calculated member. The expression is one,probably most important one (and the only mandatory one). However there areother. One of the most useful is FORMAT_STRING. It allows you to specify how doyou want to get the result cell value formatted for the purposes of nicedisplay. While this property has nothing to do with semantics of MDX, we feelit is still quite important for OLAP applications, so we included it into thisbook. MDX standard doesn’t define how this format strings should look like.Developers of Microsoft MDX implementation have chosen to use familiar VBformat standard. Since ProfitPercent is a fraction which meaning ispercentage, we used here #.#% string which automatically converts fraction topercent (i.e. multiplies by 100).

Well, after these examples, you may get wrong feeling, thatcalculated members are used only in measures dimension. Of course, this is nottrue. Suppose we want to compare the performance of the company between year1997 and 1998 and we want to see the changes. One way to do it is to build aquery which has an { [1997], [1998] } axis, and always look into the pair ofnumbers for every measure. Another way, though, is to define calculated memberin the level Year, parallel to 1997 and 1998, which will hold thedifference between them. I.e.

WITH MEMBER Time.[97 to 98] AS 'Time.[1998] – Time.[1997]'

Now if we follow that by statement

SELECT
   { Time.[97 to 98] } ON COLUMNS,
  Measures.MEMBERS ON ROWS
FROM Sales

Note, that the row of Sales will show the differencein sales (likely positive), and the row of Cost will show difference incost (hopefully negative). Member [97 to 98] belongs to the time dimension andbehaves exactly as any other member in the time dimension. If we want to seedifference between months December and October of 1997, we’ll create calculatedmember under member [1997], i.e.

WITH MEMBER Time.[1997].[Dec To Oct] AS 'Time.[1997].[12] – Time.[1997].[10]'

We’ll see many other interesting examples in followingchapters as we advance in studying powerful MDX functions and not just simplemath. Now, we would like to go a little bit back and recall ourfirst example with Profit. Suppose we want to see how profit has beenchanged from 97 to 98. Well, the natural thing to write will be

WITH
   MEMBER Measures.Profit AS 'Measures.Sales – Measures.Cost'
   MEMBER Time.[97 to 98] AS 'Time.[1998] – Time.[1997]'
SELECT
  { Measures.Sales, Measures.Cost, Measures.Profit } ON COLUMNS,
  { Time.[1997], Time.[1998], Time.[97 to 98] }      ON ROWS
FROM Sales

We’ll get 3x3 matrix:

  
  

Sales


Cost


Profit

  
1997
  

300


220


80

  
1998
  

350


210


140

  
97 to 98
  

50


-10


60


It looks OK for the first glance, but if we look deeperwe’ll see a certain problem here. There is no doubt about what we get in everycell of this grid, except for the low right corner. The cell in the low rightcorner is in the intersection of two calculated members: Profit and [97 to 98].So what is the meaning of this cell: Is it grow of the profit between 97 and98, or is it difference between relative sales and relative cost. In thissimple example it doesn’t really matters, you can look into it one way oranother, and the result will be still the same. But let’s imagine, that [97 to98] was defined as

WITH MEMBER Time.[97 to 98] AS 'Time.[1998] / Time.[1997]'

Now it is clear, that the decision of what formula isapplied to this cell really matters. Indeed to we want to calculate

([97 to 98],[Sales]) – ([97 to 98],[Cost])
or
([1998], [Profit]) / ([1997], [Profit])

Well, the MDX parser cannot read your mind. Sometimes youwill want to do former, sometimes later. That’s why MDX allows to specifyexplicitly what do you want. For every calculated member you define, you canspecify optional property SOLVE_ORDER This property allows you to control which calculated member formula will beapplied if you have conflict on the cell, just like in the example above. Thehigher value you assign to SOLVE_ORDER property, the higher priority of this member. I.e. when several formulaspretend to be calculated for single cell, the one with highest priority ischosen. (As a matter of fact, the author of this book had very strong argumentswith other spec writers to name this property SOLVE_PRIORITY rather than SOLVE_ORDER, becauseit is much easier to understand it that way). Well, let’s get back to ourexample. Since we want to see difference between profits, we’ll give to the [97to 98] calculated member higher priority. The standard says, that if you omit SOLVE_ORDER property,the default value will be zero. So, it’ll be enough to say

WITH
MEMBER Measures.Profit AS 'Measures.Sales – Measures.Cost'
MEMBER Time.[97 to 98] AS 'Time.[1998] / Time.[1997]', FORMAT_STRING = '#.00%', SOLVE_ORDER = 1

The interesting question is what will happen if bothcalculated members have the same solve order property. Well, MDX standardspecifies, that than the outcome is implementation dependent, and generallyunpredictable. To summarize: It has been long, but very important chapter.Calculated members are extremely useful in any OLAP application, and we’ll seethem a lot in following chapters.

Members and related functions

As you know by now, in MDX we operate in the multidimensional hierarchized space. I.e. we have many dimensions, and everydimension has hierarchical structure (MDX allows to same dimension to have morethan one hierarchical structure, but we’ll talk about it later). Obviously weneed set of functions to navigate in this hierarchical multidimensional space.By defining the query’s axes, you specify lower-dimensionality subspace of yourquery (optionally sliced by WHEREcondition). You also want to have functions to navigate in this subspace,knowing where you are, moving to the right direction etc. This chapter isdedicated to this type of functions.If you have dimension member, you may apply several methods on it: Parent, NextMember, PrevMember, FirstSibling, LastSibling, FirstChild, LastChild etc. Theoutcome will be another dimension member with obvious relationship to originalone. For example the writing [WA].Parent is equivalent to write [USA]. Of course nobodywill use [WA].Parent instead of [USA], and we’ll see muchmore useful application of Parent in a moment. Often, you want to know thecoordinates of the cell by some specific dimensions inside the query’ssubspace. This can be achieved by function

<dimension name>.CurrentMember
<level name>.CurrentMember

Now, we can use CurrentMember and Parent to solve thefollowing problem: Suppose we want to calculate the percentage of the sales inevery city relative to its state. To calculate this percentage, we’ll dividethe sales in the city by sales in its state. And how will we deduct the stateby city ? Of course by using Parent !

WITH
MEMBER Measures.PercentageSales AS '(Regions.CurrentMember,   Sales) /  (Regions.CurrentMember.Parent, Sales)'  ,FORMAT_STRING = '#.00%'
SELECT
   { Sales, PercentageSales } ON COLUMNS,
   Regions.Cities.MEMBERS ON ROWS
FROM Sales

This query effectively allows to see the required percentage.

While the functions Parent, FirstChild, LastChild operate inhierarchy in vertical manner, i.e. applying such a function changes level, functions PrevMember, NextMember, FirstSibling, LastSibling move you horizontally, inside the same level. PrevMember returnsthe member preceding to the given one in the level.MEMBERS collection, possibly crossinghierarchy lines. Example:

[Aug].PrevMember returns [Jul]. Both members belong to [Qtr3]
[Oct].PrevMember returns [Sep]. We crossed the hierarchy changing parent from [Qtr 4] to [Qtr 3].

Let’s see interesting example in using PrevMember. We wantto define new calculated member – Sales Growth, which will show the growth ordecline in the sales compared with previous month, quarter or year. Naturally,the formula definition will look like:

WITH MEMBER Measures.[Sales Growth] AS '(Sales) – (Sales, Time.PrevMember)'

Now, we can use this definition in the query like that:

SELECT
     { [Sales], [Sales Growth] } ON COLUMNS,
     Month.MEMBERS ON ROWS
FROM Sales

Another example will be to see breakdown of sales for different products and how did these sales grew for every product in the last month.

SELECT
     { [Sales], [Sales Growth] } ON COLUMNS,
   Product.MEMBERS ON ROWS
FROM Sales

When we’ll talk about set functions, we’ll see many interesting things to do with this query, like filtering only those products, for which sales declined, or for which we had unusual growth. In the calculated members properties chapter we’ll see techniques of implementing what is known as exceptions coloring or ‘traffic-lights’. I.e. we will be able to change presentation color of negative sales growth to red, and those that beat expectations to green. And all this only using MDX.

http://www.mosha.com/msolap/articles/MDXForEveryone.htm
发表于 2011-5-15 04:43:34 | 显示全部楼层
此贴要火!!留下广告位  要出租的联系我!



怎么丰胸,如何丰胸最有效,断奶后如何丰胸,怎样可以丰胸
发表于 2011-5-20 12:56:29 | 显示全部楼层
年后一定要多顶顶,这样才会手气更旺,哈哈

Millasha   
Millasha  
Millasha  
Millasha
Millasha  
Millasha  
Millasha  
Millasha   
Millasha  
Millasha  
Millasha  
Millasha  
发表于 2011-5-25 22:55:54 | 显示全部楼层
留爪......

在此顶帖,欢迎下面继续



产后丰胸方法,丰胸好方法,丰胸的最好方法,丰胸的方法
发表于 2011-6-5 21:18:16 | 显示全部楼层
记得从前刚学会骑自行车时,因为还不熟练,所以并不会转弯、刹车之类的技术!一天,在家闲着无聊,便骑着车出去溜达溜达!和父亲一起,当我们到达一个大坡时(下坡)我不会刹车,而父亲又把我护在里侧,只见这时,我前方出现了一个男人面对一棵大树正撅着屁股给自行车打气。我的自行车正对他的屁股~~~~于是,在我紧急的思考中,自行车已经快速的到达了他的屁股前,因为不会刹车,所以~~~男人被我的车篮狠狠的往前一撞,差点没撞到树上去



如何丰胸最有效如何有效丰胸怎么样才能丰胸怎么样丰胸
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|BC Morning Website ( Best Deal Inc. 001 )  

GMT-8, 2025-7-8 10:27 , Processed in 0.016064 second(s), 17 queries .

Supported by Best Deal Online X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表