<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Rittman Mead Consulting &#187; Oracle BI Suite EE</title>
	<atom:link href="http://www.rittmanmead.com/category/oracle-bi-suite-ee/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.rittmanmead.com</link>
	<description>Delivered Intelligence</description>
	<lastBuildDate>Wed, 17 Mar 2010 20:23:43 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.8.4</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>Oracle BI EE 10.1.3.4.1 &#8211; Solutions &#8211; Puzzle 4</title>
		<link>http://www.rittmanmead.com/2010/03/08/oracle-bi-ee-10-1-3-4-1-solutions-puzzle-4/</link>
		<comments>http://www.rittmanmead.com/2010/03/08/oracle-bi-ee-10-1-3-4-1-solutions-puzzle-4/#comments</comments>
		<pubDate>Mon, 08 Mar 2010 06:21:25 +0000</pubDate>
		<dc:creator>Venkatakrishnan J</dc:creator>
				<category><![CDATA[Oracle BI Suite EE]]></category>

		<guid isPermaLink="false">http://www.rittmanmead.com/?p=4495</guid>
		<description><![CDATA[The 4th puzzle in this series was a reasonably simple one which basically demonstrates a capability that allows end users to bypass the security applied in the BI Server layer. There are 3 possible solutions for this
Solution 1: Using Evaluate
EVALUATE was introduced in the 10.1.3.3.1 version of BI EE, that allows end users to call [...]]]></description>
			<content:encoded><![CDATA[<p>The 4th puzzle in this series was a reasonably simple one which basically demonstrates a capability that allows end users to bypass the security applied in the BI Server layer. There are 3 possible solutions for this</p>
<p><strong>Solution 1: Using Evaluate</strong></p>
<p>EVALUATE was introduced in the 10.1.3.3.1 version of BI EE, that allows end users to call database functions directly. This feature has a lot of uses and actually is widely used. One such use case (others might term this as a bug) is its ability to call table columns that are not in the repository, by using native database functions. For example, the report shown below is a very simple one where only dimensional attributes CHANNEL_CLASS and CHANNEL_TOTAL exist in the report</p>
<p align="center"><img style="border-top-width: 0px; display: block; border-left-width: 0px; float: none; border-bottom-width: 0px; margin-left: auto; margin-right: auto; border-right-width: 0px" title="image" src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/03/image.png" border="0" alt="image" width="203" height="80" /></p>
<p>The SQL for this report is give below</p>
<pre>select distinct T4167.CHANNEL_TOTAL as c1,
     T4167.CHANNEL_CLASS as c2
from
     CHANNELS T4167
order by c1, c2</pre>
<p>In our case CHANNEL_DESC column is completely not available for reporting as that is blocked by Security in the presentation layer as shown below</p>
<p align="center"><img style="border-top-width: 0px; display: block; border-left-width: 0px; float: none; border-bottom-width: 0px; margin-left: auto; margin-right: auto; border-right-width: 0px" title="image" src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/03/image1.png" border="0" alt="image" width="444" height="204" /></p>
<p>To bypass this, in one of the columns of the above report, use an EVALUATE function shown below</p>
<p><strong><em>EVALUATE(&#8217;DECODE(%1,&#8221;Dummy&#8221;,NULL,CHANNEL_DESC)&#8217; AS CHAR(10),Dim.CHANNEL_CLASS)</em></strong></p>
<p>This will basically bypass the security of the BI Server and will provide a means of looking at the CHANNEL_DESC column directly. The report and the SQL are given below</p>
<p align="center"><img style="border-top-width: 0px; display: block; border-left-width: 0px; float: none; border-bottom-width: 0px; margin-left: auto; margin-right: auto; border-right-width: 0px" title="image" src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/03/image2.png" border="0" alt="image" width="298" height="79" /></p>
<pre>select distinct T4167.CHANNEL_TOTAL as c1,
     T4167.CHANNEL_CLASS as c2,
     DECODE(T4167.CHANNEL_CLASS,'Dummy',NULL,CHANNEL_DESC) as c3
from
     CHANNELS T4167
order by c1, c2, c3</pre>
<p>So far so good. Now, lets try adding a measure to this report. You will notice that this will start producing an OCI error.</p>
<p align="center"><img style="border-top-width: 0px; display: block; border-left-width: 0px; float: none; border-bottom-width: 0px; margin-left: auto; margin-right: auto; border-right-width: 0px" title="image" src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/03/image3.png" border="0" alt="image" width="504" height="76" /></p>
<p>The reason is since CHANNEL_DESC was not part of the report directly, BI Server did not include that as part of the Select or the Group By clause. The wrong SQL is given below</p>
<pre>select T4167.CHANNEL_TOTAL as c1,
     T4167.CHANNEL_CLASS as c2,
     DECODE(T4167.CHANNEL_CLASS,'Dummy',NULL,CHANNEL_DESC) as c3,
     sum(1) as c4
from
     CHANNELS T4167
group by T4167.CHANNEL_CLASS, T4167.CHANNEL_TOTAL
order by c1, c2, c3</pre>
<p>Now, to make this work even when a fact attribute is included, the only option is to somehow push this column inside an Aggregate function like SUM, MAX etc since we cannot make the BI Server to generate the Group By (for the secured column). This requires a non-secure dimensional attribute at the same grain as the secured column. In our case, both CHANNEL_CLASS and CHANNEL_DESC have one to one relation and hence i will use that itself to demonstrate. The idea is to encapsulate the EVALUATE expression inside a string aggregate function like MAX etc as shown below</p>
<p><strong><em>MAX(EVALUATE(&#8217;DECODE(%1,&#8221;Dummy&#8221;,NULL,CHANNEL_DESC)&#8217; AS CHAR(10),Dim.CHANNEL_CLASS))</em></strong></p>
<p align="center"><img style="border-top-width: 0px; display: block; border-left-width: 0px; float: none; border-bottom-width: 0px; margin-left: auto; margin-right: auto; border-right-width: 0px" title="image" src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/03/image4.png" border="0" alt="image" width="371" height="79" /></p>
<pre>select T4167.CHANNEL_TOTAL as c1,
     T4167.CHANNEL_CLASS as c2,
     max(DECODE(T4167.CHANNEL_CLASS,'Dummy',NULL,CHANNEL_DESC)) as c3,
     sum(1) as c4
from
     CHANNELS T4167
group by T4167.CHANNEL_CLASS, T4167.CHANNEL_TOTAL
order by c1, c2</pre>
<p>How do we disable this ability completely as this might be considered as a security bug? Ideally i would like to have a privilege in Answers, that can basically stop the use of EVALUATE functions thereby providing us with the capability of controlling who has access to this. But since this is not currently available, the easiest approach is to make sure that all your query columns are pushed into a sub-query. There are a multiple ways we can push all the columns to a sub-query. I will list them below</p>
<p>1. Using Logical Columns</p>
<p>2. Using Level Based Measures</p>
<p>3. Using SELECT based tables</p>
<p>There are other techniques as well. But for the sake of keeping this brief, i will show how all columns can be pushed into Sub-Queries using SELECT tables (instead of the normal tables obtained through Import). The idea is to use a simple table based on SELECT as shown below</p>
<p align="center"><img style="border-top-width: 0px; display: block; border-left-width: 0px; float: none; border-bottom-width: 0px; margin-left: auto; margin-right: auto; border-right-width: 0px" title="image" src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/03/image5.png" border="0" alt="image" width="488" height="315" /></p>
<p>instead of the normal imported table. Then we cannot use EVALUATE as the EVALUATE function can be pushed only to the sub-query. For example, the SQL for the same report above, using SELECT table instead of normal table is given below</p>
<pre>select distinct T5419.CHANNEL_TOTAL as c1,
     T5419.CHANNEL_CLASS as c2
from
     (SELECT
CHANNEL_CLASS,
CHANNEL_CLASS_ID,
CHANNEL_ID,
CHANNEL_TOTAL,
CHANNEL_TOTAL_ID
FROM
CHANNELS) T5419
order by c1, c2</pre>
<p>As you see, whatever function we apply in the front-end will always be pushed only to the outer query (inner sub-query will always remain the same which is for the SELECT table). Hence other than the columns in the Select sub-Query, EVALUATE cannot get external columns residing in the actual table (CHANNEL_DESC for example). If we try using the same EVALUATE function we will get an error. But this approach has potential performance issues since for every query, depending on the database, predicates might not get pushed from outer query to inner sub-query(within the optimizer) thereby causing performance issues.</p>
<p>People who answered this correctly: Craig, Anu</p>
<p><strong>Solution 2: Using BYPASS_NQS authentication</strong></p>
<p>This is not something that is normally used. But this is another important example where when the authentication model gets changed, the entire BI EE security can be bypassed. This security model allows any user to login to BI EE. But only database users will be allowed to report out of the databases(login to BI EE with the same username/password as the database). When this is done, any security that is applied at the column level will be bypassed and hence everyone can see the secured column data as well</p>
<p align="center"><img style="border-top-width: 0px; display: block; border-left-width: 0px; float: none; border-bottom-width: 0px; margin-left: auto; margin-right: auto; border-right-width: 0px" title="image" src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/03/image6.png" border="0" alt="image" width="499" height="146" /></p>
<p><strong>Solution 3: Impersonation &amp; Proxy Authentication</strong></p>
<p>For this solution to work, one needs to have the privilege to do proxy authentication. Also, one cannot call this exactly as a security bypass since the user requires the capability to proxy in as another user. But there are cases when logged in as a single user, to look at the actual report values (during report development) we might want to login as Administrator which will essentially bypass all the security that is applied at the column level in BI Administrator. For more details on Proxy Authentication refer my blog post <a href="http://www.google.com/url?sa=t&amp;source=web&amp;ct=res&amp;cd=3&amp;ved=0CBEQFjAC&amp;url=http%3A%2F%2Foraclebizint.wordpress.com%2F2008%2F05%2F16%2Foracle-bi-ee-1013332-proxy-user-functionality%2F&amp;ei=CpOUS-auHYHgswPen_T8Aw&amp;usg=AFQjCNET19Vn8Hk_s8uEOH_RqCyOO3hIaQ&amp;sig2=uic7WVz5OIBXQN-HY7znOA" target="_blank">here</a>.</p>
<p>The 5th Puzzle in this series to follow later this week.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.rittmanmead.com/2010/03/08/oracle-bi-ee-10-1-3-4-1-solutions-puzzle-4/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Inside the Oracle BI Server Part 3 : BI Server In-Memory Joins</title>
		<link>http://www.rittmanmead.com/2010/03/03/inside-the-oracle-bi-server-part-3-bi-server-in-memory-joins/</link>
		<comments>http://www.rittmanmead.com/2010/03/03/inside-the-oracle-bi-server-part-3-bi-server-in-memory-joins/#comments</comments>
		<pubDate>Wed, 03 Mar 2010 09:00:10 +0000</pubDate>
		<dc:creator>Mark Rittman</dc:creator>
				<category><![CDATA[Oracle BI Suite EE]]></category>

		<guid isPermaLink="false">http://www.rittmanmead.com/2010/02/28/inside-the-oracle-bi-server-part-3-bi-server-in-memory-joins/</guid>
		<description><![CDATA[In the previous two postings in this series, I looked at the architecture of the Oracle BI Server, and how it processes incoming queries from Oracle BI Answers. In the latter article I touched on the concept of BI Server in-memory joins, and in this article I want to expand on this topic and look [...]]]></description>
			<content:encoded><![CDATA[<p>In the previous two postings in this series, I looked at the architecture of the Oracle BI Server, and how it processes incoming queries from Oracle BI Answers. In the latter article I touched on the concept of BI Server in-memory joins, and in this article I want to expand on this topic and look at just what goes on when the BI Server is called upon to combine data from multiple sources.</p>
<p>When the BI Server executes a query plan, it handles the data in four separate stages:</p>
<p style="text-align:center;"><img src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/bis322.jpg" height="373" width="200" border="0" hspace="4" vspace="4" alt="Bis32" /></p>
<ul>
<li>Firstly, filters and functions are applied to the data from each data source</li>
<li>Then, the data from these data sources are aggregated as required</li>
<li>Then they are joined together (or &#8220;stitched&#8221; together), and</li>
<li>Then, any calculations and/or aggregations that are applied across data sources are applied</li>
</ul>
<p>In simple OBIEE environments, data used by a request will come from a single database, and therefore any joins that need to be performed by the BI Server will automatically be &#8220;pushed down&#8221; to the underlying database. In the cases though where more than one physical database is being used to provide data for a query, this join will instead need to be performed by the BI Server &#8220;in memory&#8221;. This ability to &#8220;federate&#8221; data sources, and therefore produce reports and analysis that span multiple data sources, but present the data to users as if it was a single database, is one of the key unique features of OBIEE and sets it apart from tools like Discoverer which are really restricted to reporting against single data sources.</p>
<p>So given this capability, how does it work under the covers? When does the BI Server perform a join in-memory, and when does it get done at the underlying database level? Where can we see what is happening, and can we predict what method the BI Server will use when performing a join? Finally, what algorithm does the BI Server use when performing these joins, and how does it use memory and disk when during the process?</p>
<p>To illustrate how the process works, there are a number of join scenarios that we need to consider. Some relate to joining fact and dimension tables together, and others relate to joining fact tables that share conforming dimensions, or hold conforming data sets of differing granularity.</p>
<p><span style="font-size:14pt;"><strong>Joining Fact and Dimension Tables Together</strong></span></p>
<p>The BI Server semantic layer requires you to organize your business model and mapping layer into a star schema. This star schema may have one or more logical dimension tables, that join to one or more logical fact tables. The logical fact tables typically have conforming dimensions, so that you can create requests that span multiple fact tables and multiple dimension tables.</p>
<p>Taking for the moment joins between fact and dimension tables, depending on how the underlying physical or logical table source joins are set up in the semantic model, these may be either inner joins, left outer joins, right outer joins or full outer joins. The simple example to consider is a business model that is mapped to a single physical database, so that all logical table sources point to the same underlying data source, as shown in the screenshot below:</p>
<p style="text-align:center;"><img src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/bis25-2.jpg" width="500" border="0" hspace="4" vspace="4" alt="Bis25-2" /></p>
<p>In this case, if we issued a request against this business model that required data from a dimension table and a fact table, the BI Server would push the join between logical table sources down to the underlying database, a single SQL query would be generated and the execution plan from a level 5 query log entry would look like this:</p>
<pre>-------------------- Execution plan:

RqList &lt;&lt;2105&gt;&gt; [for database 3023:2820:orcl3,44]
PRODUCTS.PROD_SUBCATEGORY_DESC as c1 GB [for database 3023:2820,44],
sum(SALES.QUANTITY_SOLD by [ PRODUCTS.PROD_SUBCATEGORY_DESC] ) as c2 GB [for database 3023:2820,44]
Child Nodes (RqJoinSpec): &lt;&lt;2136&gt;&gt; [for database 3023:2820:orcl3,44]
PRODUCTS T2874
SALES T2911
DetailFilter: PRODUCTS.PROD_ID = SALES.PROD_ID [for database 0:0]
GroupBy: [ PRODUCTS.PROD_SUBCATEGORY_DESC]  [for database 3023:2820,44]
OrderBy: c1 asc [for database 3023:2820,44]
</pre>
<p>The same would apply to a left outer join between table sources in the same database, a right outer join or a full outer join. The BI Server doesn&#8217;t do any work here except to issue a single SQL query, and you can see just the one &#8220;RqList&#8221; (request list) in the execution plan, indicating again that the BI Server thinks it only needs to put together one query to satisfy the request.</p>
<p>If, however, one of the logical dimension tables had its logical table source re-pointed to a separate physical database, as shown in the screenshot below, the BI Server would now have to do the join itself, as it can&#8217;t be pushed down to the underlying database (as there are now two of them).</p>
<p style="text-align:center;"><img src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/bis261.jpg" width="500" border="0" hspace="4" vspace="4" alt="Bis26" /></p>
<p>In this case, two SQL queries would be issued, one against each of the two physical databases, and the BI Server would do the join in-memory (or to disk, I&#8217;ll elaborate on this later on). The corresponding logical execution plan from a level 5 log file would now look like this:</p>
<pre>-------------------- Execution plan:

RqBreakFilter &lt;&lt;2465&gt;&gt;[1] [for database 0:0,0]
RqList &lt;&lt;2466&gt;&gt; [for database 0:0,0]
D1.c2 as c1 [for database 3023:2500,44],
sum(D1.c5 by [ D1.c2]  at_distinct [ D1.c2, D1.c3] ) as c2 [for database 0:0,0]
Child Nodes (RqJoinSpec): &lt;&lt;2478&gt;&gt; [for database 0:0,0]
(
RqList &lt;&lt;2482&gt;&gt; [for database 0:0,0]
D902.c1 as c2 GB [for database 3023:2500,44],
D901.c2 as c3 [for database 3023:132,44],
D901.c3 as c5 [for database 3023:132,44]
Child Nodes (RqJoinSpec): &lt;&lt;2490&gt;&gt; [for database 0:0,0]

(
RqList &lt;&lt;2495&gt;&gt; [for database 3023:132:orcl,44]
SALES.PROD_ID as c2 [for database 3023:132,44],
sum(SALES.QUANTITY_SOLD by [ SALES.PROD_ID] ) as c3 [for database 3023:132,44]
Child Nodes (RqJoinSpec): &lt;&lt;2504&gt;&gt; [for database 3023:132:orcl,44]
SALES T211
GroupBy: [ SALES.PROD_ID]  [for database 3023:132,44]
OrderBy: c2 asc [for database 3023:132,44]
) as D901
InnerJoin &lt;&lt;2492&gt;&gt; On D901.c2 = D902.c2; actual join vectors:  [ 0 ] =  [ 1 ]

(
RqList &lt;&lt;2517&gt;&gt; [for database 3023:2500:orcl2,44]
PRODUCTS.PROD_SUBCATEGORY_DESC as c1 GB [for database 3023:2500,44],
PRODUCTS.PROD_ID as c2 [for database 3023:2500,44]
Child Nodes (RqJoinSpec): &lt;&lt;2523&gt;&gt; [for database 3023:2500:orcl2,44]
PRODUCTS T2502
OrderBy: c2 asc [for database 3023:2500,44]
) as D902
OrderBy: c2, c3 [for database 0:0,0]
) as D1
OrderBy: c1 asc [for database 0:0,0]
</pre>
<p>Notice the <strong>&#8220;InnerJoin &lt;&lt;2492&gt;&gt; On D901.c2 = D902.c2; actual join vectors:  [ 0 ] =  [ 1 ]&#8220;</strong> that is in the middle of the execution plan, between the two main Rqlists &#8211; this tells you that the BI Server is doing the join, as it would only appear here if it couldn&#8217;t be pushed down to the underlying database. You might also find references to <strong>LeftOuterJoin</strong>, <strong>RightOuterJoin</strong> and <strong>FullOuterJoin</strong> here, depending on how the join between the tables is defined in the physical or logical table source joins in your semantic layer.</p>
<p><span style="font-size:14pt;"><strong>Joining Facts with Conforming Dimensions Together</strong></span></p>
<p>Another situation occurs when you are joining fact tables together that share conforming dimensions. A simple example of this is where you create a request that requires data from two or more fact tables that share conforming dimensions, such as those shown in the screenshot below:</p>
<p style="text-align:center;"><img src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/bis25-1.jpg" height="256" width="500" border="0" hspace="4" vspace="4" alt="Bis25-1" /></p>
<p>As requests such as these can potentially lead to &#8220;fan trap&#8221; issues (explained in <a href="http://www.rittmanmead.com/2008/08/26/resolving-fan-traps-and-circular-joins-using-obiee/">this blog post</a>), the BI Server knows that it has to generate two logical queries and join, or &#8220;stitch&#8221; them together to avoid the fan trap. If both fact tables are sourced from the same physical database, and this database supports subquery factoring (the &#8220;WITH&#8221; clause that you see in Oracle 10gR2/11g SQL statements) then it will generate the following execution plan, which has a FullOuterStitchJoin between the two inner RqList blocks:</p>
<pre>RqBreakFilter &lt;&lt;3571&gt;&gt;[3] [for database 0:0,0]
    RqList &lt;&lt;3462&gt;&gt; [for database 3023:2820:orcl3,46]
        D1.c1 as c1 GB [for database 3023:2820,46],
        D2.c1 as c2 GB [for database 3023:2820,46],
        case  when D1.c2 is not null then D1.c2 when D2.c2 is not null then D2.c2 end  as c3 GB [for database 3023:2820,46]
    Child Nodes (RqJoinSpec): &lt;&lt;3567&gt;&gt; [for database 3023:2820:orcl3,46]
        (
            RqList &lt;&lt;3474&gt;&gt; [for database 3023:2820:orcl3,46]
                sum(COSTS.UNIT_COST by [ PRODUCTS.PROD_SUBCATEGORY_DESC] ) as c1 GB [for database 3023:2820,46],
                PRODUCTS.PROD_SUBCATEGORY_DESC as c2 GB [for database 3023:2820,46]
            Child Nodes (RqJoinSpec): &lt;&lt;3507&gt;&gt; [for database 3023:2820:orcl3,46]
                PRODUCTS T2874
                COSTS T2830
            DetailFilter: COSTS.PROD_ID = PRODUCTS.PROD_ID [for database 0:0]
            GroupBy: [ PRODUCTS.PROD_SUBCATEGORY_DESC]  [for database 3023:2820,46]
        ) as D1 FullOuterStitchJoin &lt;&lt;3565&gt;&gt; On D1.c2 = D2.c2
        (
            RqList &lt;&lt;3511&gt;&gt; [for database 3023:2820:orcl3,46]
                sum(SALES.AMOUNT_SOLD by [ PRODUCTS.PROD_SUBCATEGORY_DESC] ) as c1 GB [for database 3023:2820,46],
                PRODUCTS.PROD_SUBCATEGORY_DESC as c2 GB [for database 3023:2820,46]
            Child Nodes (RqJoinSpec): &lt;&lt;3544&gt;&gt; [for database 3023:2820:orcl3,46]
                PRODUCTS T2874
                SALES T2911
            DetailFilter: PRODUCTS.PROD_ID = SALES.PROD_ID [for database 0:0]
            GroupBy: [ PRODUCTS.PROD_SUBCATEGORY_DESC]  [for database 3023:2820,46]
        ) as D2
    OrderBy: c3 asc [for database 3023:2820,46]
</pre>
<p>The BI Server Navigator then generates a single SQL statement off of this execution plan, which queries both fact tables using subquery factoring, and then brings the results together in the main body of the statement:</p>
<pre>-------------------- Sending query to database named orcl3 (id: &lt;&lt;3462&gt;&gt;):
WITH
SAWITH0 AS (select sum(T2830.UNIT_COST) as c1,
     T2874.PROD_SUBCATEGORY_DESC as c2
from
     PRODUCTS T2874,
     COSTS T2830
where  ( T2830.PROD_ID = T2874.PROD_ID )
group by T2874.PROD_SUBCATEGORY_DESC),
SAWITH1 AS (select sum(T2911.AMOUNT_SOLD) as c1,
     T2874.PROD_SUBCATEGORY_DESC as c2
from
     PRODUCTS T2874,
     SALES T2911
where  ( T2874.PROD_ID = T2911.PROD_ID )
group by T2874.PROD_SUBCATEGORY_DESC)
select distinct SAWITH0.c1 as c1,
     SAWITH1.c1 as c2,
     case  when SAWITH0.c2 is not null then SAWITH0.c2 when SAWITH1.c2 is not null then SAWITH1.c2 end  as c3
from
     SAWITH0 full outer join SAWITH1 On SAWITH0.c2 = SAWITH1.c2
order by c3
</pre>
<p>If the physical database doesn&#8217;t support subquery factoring, such as Oracle Database 10gR1 or higher, then the BI Server generates a slightly different execution plan, again with a FullOuterStitchJoin, like this:</p>
<pre>-------------------- Execution plan:

RqBreakFilter &lt;&lt;3115&gt;&gt;[3] [for database 0:0,0]
    RqList &lt;&lt;3006&gt;&gt; [for database 0:0,0]
        D903.c1 as c1 GB [for database 3023:2820,44],
        D903.c2 as c2 GB [for database 3023:2820,44],
        case  when D903.c3 is not null then D903.c3 when D903.c4 is not null then D903.c4 end  as c3 GB [for database 3023:2820,44]
    Child Nodes (RqJoinSpec): &lt;&lt;3117&gt;&gt; [for database 0:0,0]
        (
            RqList &lt;&lt;3160&gt;&gt; [for database 0:0,0]
                D901.c1 as c1 GB [for database 3023:2820,44],
                D902.c1 as c2 GB [for database 3023:2820,44],
                D901.c2 as c3 [for database 3023:2820,44],
                D902.c2 as c4 [for database 3023:2820,44]
            Child Nodes (RqJoinSpec): &lt;&lt;3163&gt;&gt; [for database 0:0,0]

                    (
                        RqList &lt;&lt;3018&gt;&gt; [for database 3023:2820:orcl3,44]
                            sum(COSTS.UNIT_COST by [ PRODUCTS.PROD_SUBCATEGORY_DESC] ) as c1 GB [for database 3023:2820,44],
                            PRODUCTS.PROD_SUBCATEGORY_DESC as c2 GB [for database 3023:2820,44]
                        Child Nodes (RqJoinSpec): &lt;&lt;3051&gt;&gt; [for database 3023:2820:orcl3,44]
                            PRODUCTS T2874
                            COSTS T2830
                        DetailFilter: COSTS.PROD_ID = PRODUCTS.PROD_ID [for database 0:0]
                        GroupBy: [ PRODUCTS.PROD_SUBCATEGORY_DESC]  [for database 3023:2820,44]
                        OrderBy: c2 asc [for database 3023:2820,44]
                    ) as D901 FullOuterStitchJoin &lt;&lt;3109&gt;&gt; On D901.c2 = D902.c2; actual join vectors:  [ 1 ] =  [ 1 ]

                    (
                        RqList &lt;&lt;3055&gt;&gt; [for database 3023:2820:orcl3,44]
                            sum(SALES.AMOUNT_SOLD by [ PRODUCTS.PROD_SUBCATEGORY_DESC] ) as c1 GB [for database 3023:2820,44],
                            PRODUCTS.PROD_SUBCATEGORY_DESC as c2 GB [for database 3023:2820,44]
                        Child Nodes (RqJoinSpec): &lt;&lt;3088&gt;&gt; [for database 3023:2820:orcl3,44]
                            PRODUCTS T2874
                            SALES T2911
                        DetailFilter: PRODUCTS.PROD_ID = SALES.PROD_ID [for database 0:0]
                        GroupBy: [ PRODUCTS.PROD_SUBCATEGORY_DESC]  [for database 3023:2820,44]
                        OrderBy: c2 asc [for database 3023:2820,44]
                    ) as D902
        ) as D903
    OrderBy: c3 asc [for database 0:0,0]
</pre>
<p>This is then resolved for this database into two separate SQL statements, which then joined &#8220;in-memory&#8221; together by the BI Server.</p>
<pre>-------------------- Sending query to database named orcl3 (id: &lt;&lt;3018&gt;&gt;):

select sum(T2830.UNIT_COST) as c1,
     T2874.PROD_SUBCATEGORY_DESC as c2
from
     PRODUCTS T2874,
     COSTS T2830
where  ( T2830.PROD_ID = T2874.PROD_ID )
group by T2874.PROD_SUBCATEGORY_DESC
order by c2

+++Administrator:2a0000:2a0005:----2010/02/28 15:05:31

-------------------- Sending query to database named orcl3 (id: &lt;&lt;3055&gt;&gt;):

select sum(T2911.AMOUNT_SOLD) as c1,
     T2874.PROD_SUBCATEGORY_DESC as c2
from
     PRODUCTS T2874,
     SALES T2911
where  ( T2874.PROD_ID = T2911.PROD_ID )
group by T2874.PROD_SUBCATEGORY_DESC
order by c2
</pre>
<p><span style="font-size:14pt;"><strong><br />
Joining Table Sources within a Logical Fact</strong></span></p>
<p>Another situation is a fact table may have more than one logical table source, because individual measures are sourced from different data sources or perhaps measures may be mapped in at differing levels of granularity (this blog post describes such a scenario). In this case, again the BI Server will initially try and push the join down to the underlying database, something that may be possible if a single physical database is used and we can use a technique like subquery factoring; more likely though it will require the BI Server to issue two or more physical SQL statements and then bring the results back together again using a FullOuterStitchJoin.</p>
<pre>-------------------- Execution plan:

RqList &lt;&lt;7829&gt;&gt; [for database 0:0,0]
    D1.c1 as c1 [for database 0:0,0],
    D1.c2 as c2 [for database 0:0,0],
    D1.c3 as c3 [for database 0:0,0],
    D1.c4 as c4 [for database 3023:4210,44]
Child Nodes (RqJoinSpec): &lt;&lt;7842&gt;&gt; [for database 0:0,0]
    (
        RqList &lt;&lt;7809&gt;&gt; [for database 0:0,0]
            D1.c1 as c1 [for database 0:0,0],
            D1.c2 as c2 [for database 0:0,0],
            D1.c3 as c3 [for database 0:0,0],
            D1.c4 as c4 [for database 3023:4210,44],
            D1.c5 as c5 [for database 0:0,0]
        Child Nodes (RqJoinSpec): &lt;&lt;7824&gt;&gt; [for database 0:0,0]
            (
                RqBreakFilter &lt;&lt;7808&gt;&gt;[1,2,5] [for database 0:0,0]
                    RqList &lt;&lt;7604&gt;&gt; [for database 0:0,0]
                        case  when D903.c1 is not null then D903.c1 when D903.c2 is not null then D903.c2 end  as c1 GB [for database 0:0,0],
                        case  when D903.c3 is not null then D903.c3 when D903.c4 is not null then D903.c4 end  as c2 GB [for database 0:0,0],
                        D903.c5 as c3 GB [for database 0:0,0],
                        D903.c6 as c4 GB [for database 3023:4210,44],
                        case  when D903.c7 is not null then D903.c7 when D903.c8 is not null then D903.c8 end  as c5 GB [for database 0:0,0]
                    Child Nodes (RqJoinSpec): &lt;&lt;7844&gt;&gt; [for database 0:0,0]
                        (
                            RqList &lt;&lt;7915&gt;&gt; [for database 0:0,0]
                                D901.c1 as c1 [for database 0:0,0],
                                D902.c1 as c2 [for database 3023:4210,44],
                                D902.c2 as c3 [for database 3023:4210,44],
                                D901.c2 as c4 [for database 0:0,0],
                                D901.c3 as c5 GB [for database 0:0,0],
                                D902.c3 as c6 GB [for database 3023:4210,44],
                                D901.c4 as c7 [for database 0:0,0],
                                D902.c4 as c8 [for database 3023:4210,44]
                            Child Nodes (RqJoinSpec): &lt;&lt;7918&gt;&gt; [for database 0:0,0]

                                    (
                                        RqList &lt;&lt;7851&gt;&gt; [for database 0:0,0]
                                            D1.c2 as c1 [for database 0:0,0],
                                            D1.c3 as c2 [for database 0:0,0],
                                            D1.c1 as c3 GB [for database 0:0,0],
                                            D1.c4 as c4 [for database 0:0,0]
                                        Child Nodes (RqJoinSpec): &lt;&lt;7854&gt;&gt; [for database 0:0,0]
                                            (
                                                RqBreakFilter &lt;&lt;7687&gt;&gt;[2,3] [for database 0:0,0]
                                                    RqList &lt;&lt;8040&gt;&gt; [for database 0:0,0]
                                                        D1.c1 as c1 [for database 0:0,0],
                                                        D1.c2 as c2 [for database 0:0,0],
                                                        D1.c3 as c3 [for database 0:0,0],
                                                        D1.c4 as c4 [for database 0:0,0]
                                                    Child Nodes (RqJoinSpec): &lt;&lt;8058&gt;&gt; [for database 0:0,0]
                                                        (
                                                            RqList &lt;&lt;7972&gt;&gt; [for database 3023:4483:Quotas,2]
                                                                sum(QUANTITY_QUOTAS.QUOTA by [ CATEGORY.CATEGORY, MONTHS.MONTH_MON_YYYY] ) as c1 [for database 3023:4483,2],
                                                                MONTHS.MONTH_MON_YYYY as c2 [for database 3023:4483,2],
                                                                CATEGORY.CATEGORY as c3 [for database 3023:4483,2],
                                                                MONTHS.MONTH_YYYYMM as c4 [for database 3023:4483,2]
                                                            Child Nodes (RqJoinSpec): &lt;&lt;7682&gt;&gt; [for database 3023:4483:Quotas,2]
                                                                CATEGORY T4486
                                                                MONTHS T4488
                                                                QUANTITY_QUOTAS T4492
                                                            DetailFilter: CATEGORY.CATEGORY = QUANTITY_QUOTAS.CATEGORY and MONTHS.MONTH_YYYYMM = QUANTITY_QUOTAS.MONTH_YYYYMM [for database 0:0]
                                                            GroupBy: [ CATEGORY.CATEGORY, MONTHS.MONTH_YYYYMM, MONTHS.MONTH_MON_YYYY]  [for database 3023:4483,2]
                                                        ) as D1
                                                    OrderBy: c2, c3 [for database 0:0,0]
                                            ) as D1
                                        OrderBy: c1 asc, c2 asc [for database 0:0,0]
                                    ) as D901 FullOuterStitchJoin &lt;&lt;7800&gt;&gt; On D901.c1 =NullsEqual D902.c1 and D901.c2 =NullsEqual D902.c2; actual join vectors:  [ 0 1 ] =  [ 0 1 ]

                                    (
                                        RqList &lt;&lt;7880&gt;&gt; [for database 3023:4210:orcl4,44]
                                            D2.c2 as c1 [for database 3023:4210,44],
                                            D2.c3 as c2 [for database 3023:4210,44],
                                            D2.c1 as c3 GB [for database 3023:4210,44],
                                            D2.c4 as c4 [for database 3023:4210,44]
                                        Child Nodes (RqJoinSpec): &lt;&lt;7883&gt;&gt; [for database 3023:4210:orcl4,44]
                                            (
                                                RqBreakFilter &lt;&lt;7760&gt;&gt;[2,3] [for database 3023:4210:orcl4,44]
                                                    RqList &lt;&lt;7989&gt;&gt; [for database 3023:4210:orcl4,44]
                                                        sum(ITEMS.QUANTITY by [ PRODUCT.CATEGORY, TIMES.MONTH_MON_YYYY] ) as c1 [for database 3023:4210,44],
                                                        TIMES.MONTH_MON_YYYY as c2 [for database 3023:4210,44],
                                                        PRODUCT.CATEGORY as c3 [for database 3023:4210,44],
                                                        TIMES.MONTH_YYYYMM as c4 [for database 3023:4210,44]
                                                    Child Nodes (RqJoinSpec): &lt;&lt;7755&gt;&gt; [for database 3023:4210:orcl4,44]
                                                        PRODUCT T4256
                                                        TIMES T4264
                                                        ITEMS T4239
                                                        ORDERS T4248
                                                    DetailFilter: ITEMS.ORDID = ORDERS.ORDID and ITEMS.PRODID = PRODUCT.PRODID and ORDERS.ORDERDATE = TIMES.DAY_ID [for database 0:0]
                                                    GroupBy: [ PRODUCT.CATEGORY, TIMES.MONTH_MON_YYYY, TIMES.MONTH_YYYYMM]  [for database 3023:4210,44]
                                            ) as D2
                                        OrderBy: c1 asc, c2 asc [for database 3023:4210,44]
                                    ) as D902
                        ) as D903
                    OrderBy: c1, c2, c5 [for database 0:0,0]
            ) as D1
        OrderBy: c5 asc, c2 asc, c4 asc [for database 0:0,0]
    ) as D1
</pre>
<p>Again, notice the FullOuterStitchJoin in the execution plan &#8211; this indicates that facts (as opposed to facts and dimensions) are being joined together.</p>
<p>This in turn leads to two separate SQL statements. The one against the &#8220;orcl&#8221; database is more complex because the results then need to be mapped to the aggregation level that the second source, &#8220;quotas&#8221;, comes in at:</p>
<pre>-------------------- Sending query to database named Quotas (id: &lt;&lt;7972&gt;&gt;):
select sum(T4492."QUOTA") as c1,
     T4488."MONTH_MON_YYYY" as c2,
     T4486."CATEGORY" as c3,
     T4488."MONTH_YYYYMM" as c4
from
     "CATEGORY" T4486,
     "MONTHS" T4488,
     "QUANTITY_QUOTAS" T4492
where  ( T4486."CATEGORY" = T4492."CATEGORY" and T4488."MONTH_YYYYMM" = T4492."MONTH_YYYYMM" )
group by T4486."CATEGORY", T4488."MONTH_YYYYMM", T4488."MONTH_MON_YYYY"

+++Administrator:2b0000:2b000a:----2010/02/24 17:18:51

-------------------- Sending query to database named orcl4 (id: &lt;&lt;7880&gt;&gt;):

select D2.c2 as c1,
     D2.c3 as c2,
     D2.c1 as c3,
     D2.c4 as c4
from
     (select D1.c1 as c1,
               D1.c2 as c2,
               D1.c3 as c3,
               D1.c4 as c4
          from
               (select sum(T4239.QUANTITY) as c1,
                         T4264.MONTH_MON_YYYY as c2,
                         T4256.CATEGORY as c3,
                         T4264.MONTH_YYYYMM as c4,
                         ROW_NUMBER() OVER (PARTITION BY T4256.CATEGORY, T4264.MONTH_MON_YYYY ORDER BY T4256.CATEGORY ASC, T4264.MONTH_MON_YYYY ASC) as c5
                    from
                         PRODUCT T4256,
                         TIMES T4264,
                         ITEMS T4239,
                         ORDERS T4248
                    where  ( T4239.ORDID = T4248.ORDID and T4239.PRODID = T4256.PRODID and T4248.ORDERDATE = T4264.DAY_ID )
                    group by T4256.CATEGORY, T4264.MONTH_MON_YYYY, T4264.MONTH_YYYYMM
               ) D1
          where  ( D1.c5 = 1 )
     ) D2
order by c1, c2
</pre>
<p>So, to summarize things so far:</p>
<ul>
<li>Where possible, the BI Server will try and generate a single SQL statement to resolve a request</li>
<li>And if possible, any joins that are required between tables will be pushed down to the database</li>
<li>If table data sources are located on separate physical databases, the BI Server will request the individual data source data blocks, and then join the results together in-memory using an inner, left outer, right outer or full outer join as appropriate</li>
<li>If facts (or measures within a fact) are being joined together, the BI Server will need to generate one logical query per logical table source, and bring the data together with a full outer stitch join</li>
<li>As mentioned above, if it&#8217;s possible to do this stitch join at the database level (using, for example,  a WITH clause), it&#8217;ll do so</li>
<li>Otherwise the BI Server will generate separate SQL statements and join the data together in-memory</li>
</ul>
<p>When an in-memory BI Server join happens between two tables, it will bring back both sets of data from the two (or more) table sources and then perform a sort-merge join to bring the data together. If possible, it will push the sort back to the underlying database and just do the &#8220;merge&#8221; part of the join, and it&#8217;ll in all likelihood page some of the temporary data to TMP files in $ORACLEBIDATA/tmp depending on the load on the server, available memory and the number of concurrent queries that it is running. The NQSConfig.INI BI Server parameter VIRTUAL_TABLE_PAGE_SIZE determines the point at which temporary data is paged to disk, and on a Unix server you can experiment with increasing it from its default setting if you have lots of unused memory available (the docs suggest that this will probably not have much of a positive effect, though).</p>
<p><span style="font-size:14pt;"><strong>Fragmented Data Sources<br />
</strong></span><br />
Another variation on a join that the BI Server can do is a &#8220;union&#8221; between two queries. This is most common when you have fragmented data sources, such as the example below where part of the data in the sales table comes from one table, and part from another.</p>
<p style="text-align:center;"><img src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/bis34-1.jpg" height="390" width="500" border="0" hspace="4" vspace="4" alt="Bis34-1" /></p>
<p>In this case, the logical execution plan will contain RqUnionAll between the inner RqList request lists, to show that the BI Server knows it needs to union all the two queries.</p>
<pre>-------------------- Execution plan:

RqList &lt;&lt;7569&gt;&gt; [for database 3023:6594:orcl7,44]
    D3.c2 as c1 GB [for database 3023:6594,44],
    sum(D3.c3 by [ D3.c2] ) as c2 GB [for database 3023:6594,44]
Child Nodes (RqJoinSpec): &lt;&lt;7695&gt;&gt; [for database 3023:6594:orcl7,44]
    (
        RqList &lt;&lt;7613&gt;&gt; [for database 3023:6594:orcl7,44]
            PRODUCTS.PROD_SUBCATEGORY_DESC as c2 [for database 3023:6594,44],
            SALES_UPTO_2003.AMOUNT_SOLD as c3 [for database 3023:6594,44]
        Child Nodes (RqJoinSpec): &lt;&lt;7617&gt;&gt; [for database 3023:6594:orcl7,44]
            PRODUCTS T6596
            SALES T6629
        DetailFilter: PRODUCTS.PROD_ID = SALES_UPTO_2003.PROD_ID [for database 0:0]
        RqUnion All &lt;&lt;7690&gt;&gt; [for database 3023:6594:orcl7,44]
        RqList &lt;&lt;7668&gt;&gt; [for database 3023:6594:orcl7,44]
            PRODUCTS.PROD_SUBCATEGORY_DESC as c2 [for database 3023:6594,44],
            SALES_BEYOND_2003.AMOUNT_SOLD as c3 [for database 3023:6594,44]
        Child Nodes (RqJoinSpec): &lt;&lt;7672&gt;&gt; [for database 3023:6594:orcl7,44]
            PRODUCTS T6596
            SALES T6637
        DetailFilter: PRODUCTS.PROD_ID = SALES_BEYOND_2003.PROD_ID [for database 0:0]
    ) as D3
GroupBy: [ D3.c2]  [for database 3023:6594,44]
OrderBy: c1 asc [for database 3023:6594,44]
</pre>
<p>Then, depending on whether the BI Server can resolve this using a single query or multiple queries against separate data source, either a single SQL statement like the one below will be issued, or separate statements will be issued and the BI Server will do the union all in memory.</p>
<pre>select D3.c2 as c1,
     sum(D3.c3) as c2
from
     ((select T6596.PROD_SUBCATEGORY_DESC as c2,
               T6629.AMOUNT_SOLD as c3
          from
               PRODUCTS T6596,
               SALES T6629 /* SALES_UPTO_2003 */
          where  ( T6596.PROD_ID = T6629.PROD_ID )
          union all
          select T6596.PROD_SUBCATEGORY_DESC as c2,
               T6637.AMOUNT_SOLD as c3
          from
               PRODUCTS T6596,
               SALES T6637 /* SALES_BEYOND_2003 */
          where  ( T6596.PROD_ID = T6637.PROD_ID ) )
     ) D3
group by D3.c2
order by c1
</pre>
<p><span style="font-size:14pt;"><strong>Driving Tables (Parameterized Nested Loop Joins)</strong></span></p>
<p>I mentioned in the paragraph above that BI Server joins are typically done using the sort-merge algorithm. One variation on this though is when you set one of the two tables in a business model and mapping logical join to be a driving table, typically because you are federating fact and dimension tables and one table is much smaller than the other, as shown in the screenshot below.</p>
<p style="text-align:center;"><img src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/bis31-1.jpg" height="461" width="500" border="0" hspace="4" vspace="4" alt="Bis31-1" /></p>
<p>The first thing to understand with driving tables is that they are regarded as a &#8220;hint&#8221; by the BI Server, and the BI Server may well choose to ignore the setting if it makes more sense to perform the join as normal (presumably, when both tables are relatively small). If the driving table instruction is followed, though, the BI Server will always do the join in-memory, even if both tables come from logical table sources pointing to the same physical database. In the execution plan shown below, you can see the InnerJoin (left drive) that indicates a parameterized nested loop join (PNLJ) will be required, and as the name suggests the BI Server will perform a nested loop join rather than the sort-merge join that it usually uses to join tables together.</p>
<pre>-------------------- Execution plan:
RqBreakFilter &lt;&lt;8705&gt;&gt;[1] [for database 0:0,0]
    RqList &lt;&lt;8972&gt;&gt; [for database 0:0,0]
        D1.c2 as c1 [for database 3023:2500,44],
        sum(D1.c5 by [ D1.c2]  at_distinct [ D1.c2, D1.c3] ) as c2 [for database 0:0,0]
    Child Nodes (RqJoinSpec): &lt;&lt;8984&gt;&gt; [for database 0:0,0]
        (
            RqList &lt;&lt;8463&gt;&gt; [for database 0:0,0]
                D901.c1 as c2 GB [for database 3023:2500,44],
                D902.c2 as c3 [for database 3023:5035,44],
                D902.c3 as c5 [for database 3023:5035,44]
            Child Nodes (RqJoinSpec): &lt;&lt;8707&gt;&gt; [for database 0:0,0]

                    (
                        RqList &lt;&lt;8757&gt;&gt; [for database 3023:2500:orcl2,44]
                            PRODUCTS.PROD_NAME as c1 GB [for database 3023:2500,44],
                            PRODUCTS.PROD_ID as c2 [for database 3023:2500,44]
                        Child Nodes (RqJoinSpec): &lt;&lt;8760&gt;&gt; [for database 3023:2500:orcl2,44]
                            PRODUCTS T2502
                        DetailFilter: PRODUCTS.PROD_NAME = '128MB Memory Card' or PRODUCTS.PROD_NAME = '3 1/2" Bulk diskettes, Box of 100' or PRODUCTS.PROD_NAME = '5MP Telephoto Digital Camera' or PRODUCTS.PROD_NAME = '64MB Memory Card' or PRODUCTS.PROD_NAME = 'Deluxe Mouse' or PRODUCTS.PROD_NAME = 'Envoy Ambassador' or PRODUCTS.PROD_NAME = 'Envoy External 8X CD-ROM' or PRODUCTS.PROD_NAME = 'Martial Arts Champions' or PRODUCTS.PROD_NAME = 'Model A3827H Black Image Cartridge' or PRODUCTS.PROD_NAME = 'Model C93822D Wireless Phone Battery' or PRODUCTS.PROD_NAME = 'Model CD13272 Tricolor Ink Cartridge' or PRODUCTS.PROD_NAME = 'PCMCIA modem/fax 28800 baud' or PRODUCTS.PROD_NAME = 'SIMM- 16MB PCMCIAII card' or PRODUCTS.PROD_NAME = 'Smash up Boxing' or PRODUCTS.PROD_NAME = 'Unix/Windows 1-user pack' [for database 0:0]
                        OrderBy: c2 asc [for database 3023:2500,44]
                    ) as D901
                InnerJoin  (left drive) &lt;&lt;8806&gt;&gt; On D901.c2 = D902.c2; actual join vectors:  [ 1 ] =  [ 0 ]

                    (
                        RqList &lt;&lt;8790&gt;&gt; [for database 3023:5035:orcl5,44]
                            SALES.PROD_ID as c2 [for database 3023:5035,44],
                            sum(SALES.AMOUNT_SOLD by [ SALES.PROD_ID] ) as c3 [for database 3023:5035,44]
                        Child Nodes (RqJoinSpec): &lt;&lt;8793&gt;&gt; [for database 3023:5035:orcl5,44]
                            SALES T5126
                        DetailFilter: SALES.PROD_ID = ?1 or SALES.PROD_ID = ?2 or SALES.PROD_ID = ?3 or SALES.PROD_ID = ?4 or SALES.PROD_ID = ?5 or SALES.PROD_ID = ?6 or SALES.PROD_ID = ?7 or SALES.PROD_ID = ?8 or SALES.PROD_ID = ?9 or SALES.PROD_ID = ?10 or SALES.PROD_ID = ?11 or SALES.PROD_ID = ?12 or SALES.PROD_ID = ?13 or SALES.PROD_ID = ?14 or SALES.PROD_ID = ?15 or SALES.PROD_ID = ?16 or SALES.PROD_ID = ?17 or SALES.PROD_ID = ?18 or SALES.PROD_ID = ?19 or SALES.PROD_ID = ?20 [for database 0:0]
                        GroupBy: [ SALES.PROD_ID]  [for database 3023:5035,44]
                        OrderBy: c2 asc [for database 3023:5035,44]
                    ) as D902
            OrderBy: c2, c3 [for database 0:0,0]
        ) as D1
    OrderBy: c1 asc [for database 0:0,0]
</pre>
<p>Then then leads to the following parameterized SQL statements being issued, with the first statement representing the &#8220;driving&#8221; query, and the second the &#8220;probing&#8221; one against the larger table.</p>
<pre>-------------------- Sending query to database named orcl2 (id: &lt;&lt;8757&gt;&gt;):

select T2502.PROD_NAME as c1,
     T2502.PROD_ID as c2
from
     PRODUCTS T2502
where  ( T2502.PROD_NAME in ('128MB Memory Card', '3 1/2" Bulk diskettes, Box of 100', '5MP Telephoto Digital Camera', '64MB Memory Card', 'Deluxe Mouse', 'Envoy Ambassador', 'Envoy External 8X CD-ROM', 'Martial Arts Champions', 'Model A3827H Black Image Cartridge', 'Model C93822D Wireless Phone Battery', 'Model CD13272 Tricolor Ink Cartridge', 'PCMCIA modem/fax 28800 baud', 'SIMM- 16MB PCMCIAII card', 'Smash up Boxing', 'Unix/Windows 1-user pack') )
order by c2

+++Administrator:2c0000:2c000a:----2010/02/24 21:06:47

-------------------- Sending query to database named orcl5 (id: &lt;&lt;8790&gt;&gt;):

select T5126.PROD_ID as c2,
     sum(T5126.AMOUNT_SOLD) as c3
from
     SALES T5126
where  ( T5126.PROD_ID in (:PARAM1, :PARAM2, :PARAM3, :PARAM4, :PARAM5, :PARAM6, :PARAM7, :PARAM8, :PARAM9, :PARAM10, :PARAM11, :PARAM12, :PARAM13, :PARAM14, :PARAM15, :PARAM16, :PARAM17, :PARAM18, :PARAM19, :PARAM20) )
group by T5126.PROD_ID
order by c2
</pre>
<p>In reality you rarely see driving table joins being used as there are much better solutions to bringing together small and large tables together &#8211; the main one being to co-locate the tables and then push the join down to the database, rather than bring both datasets together and have the BI Server join them in memory instead (this also applies to a lesser degree to all BI Server joins). But this could be a useful &#8220;quick fix&#8221; until such time as you can co-locate the data, and its useful to remember that these types of joins are always done by the BI Server due to the need to iterate through drive/probe operations.</p>
<p><span style="font-size:14pt;"><strong>Persist Connnection Pools</strong></span></p>
<p>One final variation on BI Server execution plans and join types is when you set up a &#8220;persist connection pool&#8221;. Persist connection pools are typically used in two scenarios; firstly, where Oracle/Siebel Marketing is being used, and secondly, where the underlying physical database doesn&#8217;t handle large numbers of values in an IN-list. In this case, you can set up a second connection pool within a physical database and specify it as the persist connection pool, as shown in the screenshot below:</p>
<p style="text-align:center;"><img src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/bis30-1.jpg" height="397" width="500" border="0" hspace="4" vspace="4" alt="Bis30-1" /></p>
<p>I&#8217;ve never encountered a persist connection pool &#8220;in the wild&#8221;, so to speak, but an example query log output from when one was used is shown below. In this instance, the first query was sent to a MS Analysis Services database, and a persist connection pool was used to materialize the in-list results into a database table which is then joined back to the ORDERS table in the final query, rather than have the BI Server do the join in-memory.</p>
<pre>-------------------- Sending query to database named FoodMart (id: &lt;&lt;10980&gt;&gt;):
With
  member [Measures].[YearAnc] as 'ancestor([Time].Currentmember,[Time].[Year]).name'
  set [Q] as '{{[Time].[Year].members}}'
  select
    {[measures].[YearAnc]} on columns,
    {[Q]} on rows
  from     [Sales]

-------------------- Sending query to database named SQLDB_Northwind (id: CreateTable TransGateway):
CREATE TABLE TTCH5C5DEL554110000020000003 ( column1 VARCHAR2(8) )

-------------------- Sending query to database named SQLDB_Northwind (id: &lt;&lt;11057&gt;&gt;):
select distinct TO_NUMBER(TO_CHAR(T1864.OrderDate, 'yyyy'), '9999') as c1
from
     Orders T1864
where  ( TO_NUMBER(TO_CHAR(T1864.OrderDate, 'yyyy'), '9999') in (select column1 from TTCH5C5DEL554110000020000003) )
</pre>
<p><span style="font-size:14pt;"><strong><br />
Conclusions</strong></span></p>
<p>So, there you have it. The join strategy of the BI Server, as is the case with functions and calculations, is to wherever possible push them down to the underlying database. If this can&#8217;t be done, because either the database version doesn&#8217;t support features like subquery factoring, or if the data for the request is being sourced from more than one physical databas, the BI Server will do the join itself, initially in-memory but usually with temporary data being paged to disk.</p>
<p>There are two main types of BI Server join; regular (inner, left outer, right outer and fullouter) joins for bringing together fact and dimension tables; and full outer stitch joins, for bringing together facts and measures. There are also variations for handling joins from very small tables to very large tables (driving tables, or parameterized nested loop joins), or when the physical database doesn&#8217;t support large in-lists, however these issues are usually better handled by co-locating data or upgrading the database.</p>
<p>Finally, even though the BI Server is pretty clever at doing these types of joins, you&#8217;re usually better trying to invest your time in physically bringing your data together into a data mart or data warehouse than spending too much time fine-tuning these joins, though a knowledge of how they work (and how to read a level 5 execution plan) can be useful if you have to understand, or tune, an existing system in-place. Of course the level 5 execution plan doesn&#8217;t really tell you anything you couldn&#8217;t determine by looking at the design of the RPD &#8211; there&#8217;s nothing that goes on beyond this that might change the execution plan for a certain set of data, unlike the Oracle database which changes the plan from database to database depending on the distribution and nature of the data &#8211; but its interesting to get a peek into the workings of the BI Server Navigator module.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.rittmanmead.com/2010/03/03/inside-the-oracle-bi-server-part-3-bi-server-in-memory-joins/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>OBIEE Content at ODTUG Kaleidoscope 2010</title>
		<link>http://www.rittmanmead.com/2010/03/02/obiee-content-at-odtug-kaleidoscope-2010/</link>
		<comments>http://www.rittmanmead.com/2010/03/02/obiee-content-at-odtug-kaleidoscope-2010/#comments</comments>
		<pubDate>Tue, 02 Mar 2010 14:04:01 +0000</pubDate>
		<dc:creator>Mark Rittman</dc:creator>
				<category><![CDATA[Oracle BI Suite EE]]></category>

		<guid isPermaLink="false">http://www.rittmanmead.com/2010/03/02/obiee-content-at-odtug-kaleidoscope-2010/</guid>
		<description><![CDATA[As well as organizing our own BI Forum in the UK, another event I&#8217;ve had a hand in is ODTUG Kaleidoscope 2010, which is due to run from June 27th &#8211; July 1st in Washington D.C. I&#8217;ve been the content lead for the BI, DW and Hyperion Reporting Tools stream, and if you were thinking [...]]]></description>
			<content:encoded><![CDATA[<p>As well as organizing our own BI Forum in the UK, another event I&#8217;ve had a hand in is <a href="http://www.odtugkaleidoscope.com/">ODTUG Kaleidoscope 2010</a>, which is due to run from June 27th &#8211; July 1st in Washington D.C. I&#8217;ve been the content lead for the <a href="http://www.odtugkaleidoscope.com/oraclebusinessintelligence.html">BI, DW and Hyperion Reporting Tools</a> stream, and if you were thinking of coming to the BI Forum but couldn&#8217;t make it over to the UK, this event has a similar level of OBIEE content and might be of interest to you.</p>
<p style="text-align:center;"><img src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/03/odtug.jpg" height="117" width="500" border="0" hspace="4" vspace="4" alt="Odtug" /></p>
<p>Like the BI Forum, we wanted to focus on the technical aspects of OBIEE, and as the event is running Stateside we were able to get some of the key product managers from Oracle to come and present, who don&#8217;t usually make it to general user group events like this but were keen to contribute to our OBIEE focus. Here&#8217;s a taster for what&#8217;s on the agenda:</p>
<p><strong>OBIEE 11g Integration with Oracle ADF Business Components</strong><br />
<em>Palaniappan Chidambaram, Oracle Corporation</em></p>
<p style="text-indent:20pt;"><em>With Oracle Middleware getting smarter and robust, it’s time to update the data source of OBIEE from relational database to Fusion ADF Business Model Components (light-weight Java View Objects). This is an efficient way to add pervasive BI to the enterprise applications built using Fusion ADF. Learn the new OBIEE 11g and Fusion ADF integration with added metadata on security, UI hints, and much more.</em></p>
<p>I met Palanippan back at last year&#8217;s Open World, and he&#8217;s responsible for some of the OBIEE/ADF integration that&#8217;s coming with the new Fusion Release of Oracle Applications. Palanippan will be talking about something that will be of interest to both OBIEE, and ADF developers, and will be speaking about it from the perspective of someone responsible for its features and usage.</p>
<p><strong>Best Practices for Performance, Scalability, and Reliability with Oracle BI Enterprise Edition</strong><br />
<em>Mike Durran, Oracle Corporation</em>, and<br />
<strong>Oracle OBIEE Metadata Modeling Best Practices and Tips for Concurrent Development</strong><br />
<em>Alan Fuller, Oracle Corporation<br />
</em>
<p style="text-indent:20pt;"><em>The initial setup and configuration of an Oracle BI system can reap benefits in terms of ongoing performance and reliability. Mike&#8217;s session describes the creation of a system that can scale to your enterprise from the initial install, configuration for optimum performance, ongoing monitoring, and troubleshooting tips.</p>
<p style="text-indent:20pt;">In Alan&#8217;s session, a senior member of the OBIEE product management team will cover best practices for metadata modeling in OBIEE including: using the power of the enterprise semantic layer for performance tuning and query optimization, rapid development processes, managing frequently encountered stumbling blocks, and making it easy for end users. Also covered will be tips for concurrent development of metadata across multiple developers.</p>
<p style="text-indent:20pt;"></em></p>
<p>From speaking with past attendees of Kaleidoscope, a regular bit of feedback is that people want to hear &#8220;best practice&#8221; sessions on their technology area of interest. Mike and Alan are therefore going to give Oracle&#8217;s view on best practices for OBIEE performance, and RPD modeling, which will also give the audience the change to discuss their best practices and comment on the ones put forward by Oracle.</p>
<p><strong>Web Services and Application Integration with Oracle Business Intelligence EE Suite</strong><br />
<em>David Granholm, Oracle Corporation</em></p>
<p style="text-indent:20pt;"><em>A variety of techniques can be used to build novel applications which extend the core capabilities of the OBIEE Suite. Approaches include URL-based integration, Web services leveraging SOAP methods, portal and WebCenter integration, and custom applications built in Oracle’s Application Development Framework (ADF) using Oracle JDeveloper &#8211; Oracle&#8217;s main development tool for Java-based SOA applications. Each of these approaches and a few real-world examples will be discussed.</em></p>
<p>David is a really good speaker, and this session will complement Palanippan&#8217;s and extend the discussion to SOAP and web services. Again, something of interest for general application developers as well as OBIEE developers.</p>
<p><strong>Oracle Business Intelligence Applications Essbase Integrator</strong><br />
<em>Alaric Thomas, Oracle Corporation</em></p>
<p style="text-indent:20pt;"><em>Oracle has a number of powerful business intelligence technologies in its portfolio, and we are rapidly integrating these technologies to provide more value and lower TCO for customers. In this session, Alaric Thomas will discuss how the Oracle BI Applications – Essbase Integrator will bring together the capabilities of the Oracle BI Applications and Oracle Essbase to further leverage a common applications information model delivering value to both IT and end users.</em></p>
<p>I blogged on this presentation a while ago, and this will be an excellent opportunity to see how the integration of Essbase and Oracle BI Apps is taking place, using technology available now rather than being based on the Fusion BI Apps.</p>
<p><strong>Oracle® Hyperion Smart View for Office, Fusion Edition</strong><br />
<em>Toufic Wakim, Oracle Corporation</em></p>
<p style="text-indent:20pt;"><em>For those working with a financial application, Oracle Essbase or Oracle Business Intelligence Suite Enterprise Edition Plus, Oracle Hyperion Smart View for Office brings access to data and report templates and the ability to author reports in the Microsoft Office framework. This session gives an overview of this product and demonstrates new features. It also shows how to use it with Oracle Hyperion Planning, Oracle Hyperion Financial Management, Oracle Business Intelligence Suite Enterprise Edition Plus, and Oracle Essbase.</em></p>
<p>SmartView is supposed to replace the BI Office add-in for OBIEE, but in its current incarnation doesn&#8217;t work too well. Apparently a new version that is more suited to OBIEE will be released shortly, and Toufic hopefully will discuss and demonstrate this version at Kaleidoscope. If you&#8217;re interested in the future of MS Office integration with OBIEE, this will be a must-see session.</p>
<p>Apart from these sessions, there&#8217;s a couple from myself on OBIEE 11g new features (assuming I can get sign-off to present, as 11g is doubtful for release by the time of Kaleidoscope), and a whole bunch of OWB, DW and ODI sessions as well as content aimed at Hyperion users upgrading to OBIEE. Outside of the sessions we&#8217;ll be organizing a social event specifically for the BI community, and there&#8217;ll of course be other great Essbase, Hyperion, Oracle and development sessions running concurrently.</p>
<p><a href="http://www.odtugkaleidoscope.com/registration.html">Registration is open now</a>, and if you&#8217;re serious about OBIEE and based Stateside (or even outside the USA, and fancy a trip to DC), this is definitely the event to attend in 2010.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.rittmanmead.com/2010/03/02/obiee-content-at-odtug-kaleidoscope-2010/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Inside the Oracle BI Server Part 2 : How Is A Query Processed?</title>
		<link>http://www.rittmanmead.com/2010/03/01/inside-the-oracle-bi-server-part-2-how-is-a-query-processed/</link>
		<comments>http://www.rittmanmead.com/2010/03/01/inside-the-oracle-bi-server-part-2-how-is-a-query-processed/#comments</comments>
		<pubDate>Mon, 01 Mar 2010 07:15:37 +0000</pubDate>
		<dc:creator>Mark Rittman</dc:creator>
				<category><![CDATA[Oracle BI Suite EE]]></category>

		<guid isPermaLink="false">http://www.rittmanmead.com/2010/02/28/inside-the-oracle-bi-server-part-2-how-is-a-query-processed/</guid>
		<description><![CDATA[In the first article on this series about the Oracle BI Server, I looked at the architecture and functions within this core part of the OBIEE product set. in this article, I want to look closer at what happens when a query (or &#8220;request&#8221;) comes into the BI Server, and how it translates it into [...]]]></description>
			<content:encoded><![CDATA[<p>In the <a href="http://www.rittmanmead.com/2010/02/25/inside-the-oracle-bi-server-part-1-the-bi-server-architecture/">first article on this series about the Oracle BI Server</a>, I looked at the architecture and functions within this core part of the OBIEE product set. in this article, I want to look closer at what happens when a query (or &#8220;request&#8221;) comes into the BI Server, and how it translates it into the SQL, MDX, file and XML requests that then get passed to the underlying data sources.</p>
<p>In the previous article, I laid out a conceptual diagram of the BI Server and talked about how the Navigator turned incoming queries into one or more physical database queries. As a recap, here&#8217;s the architecture diagram again:</p>
<p style="text-align:center;"><img src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/bis2-1.jpg" height="542" width="500" border="0" hspace="4" vspace="4" alt="Bis2-1" /></p>
<p>Now as we all know, the BI Server uses a three-layer metadata model that exposes one or more databases (or &#8220;subject areas&#8221;) for ODBC-compliant query tools to run queries against. Here&#8217;s a typical metadata model that takes a number of physical data sources, joins them together into a smaller number of business model and mapping models, and then presents them out to the query tool (usually, Oracle BI Answers) as a set of databases made up of relational tables, columns and joins.</p>
<p style="text-align:center;"><img src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/bis7.jpg" height="320" width="500" border="0" hspace="4" vspace="4" alt="Bis7" /></p>
<p>Usually you access this metadata model using Oracle BI Answers, which presents you with an initial choice of subject areas (databases in ODBC terminology) and then displays the contents of one of them as a list of tables and columns (in 11g, you&#8217;ll be able to to include tables from multiple subject areas in queries as long as there are tables in common between them).</p>
<p style="text-align:center;"><img src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/bis9.jpg" height="481" width="500" border="0" hspace="4" vspace="4" alt="Bis9" /></p>
<p>Other ODBC-compliant query tools, such as Microsoft Excel, Cognos or Business Objects, can access these subject areas and run queries against them just as if it was a regular database. Here&#8217;s Microsoft Excel 2007 building a query against the same subject area:</p>
<p style="text-align:center;"><img src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/bis8.jpg" height="357" width="500" border="0" hspace="4" vspace="4" alt="Bis8" /></p>
<p><span style="font-size:14pt;"><strong>What Happens When the BI Server Handles a Query?<br />
</strong></span><br />
So just what happens then, when a query (or &#8220;request&#8217;) comes in from one of these sources, and needs to be processed in order to return results to the user? As you&#8217;re probably aware, the BI Server doesn&#8217;t itself hold data (except cached results from other queries, when this feature is enabled); instead, it translates the incoming &#8220;logical&#8221; query into one or more outgoing &#8220;physical&#8221; queries against the relevant data sources. As such, a logical model presented to users might be mapped to data in an Oracle data warehouse, an E-Business Suite application, some data in a Teradata data warehouse, some measures in an Essbase cube and even some data in an Excel spreadsheet. The BI server resolves this complexity by creating a simplified, star schema business model over these data sources so that the user can query it as if it&#8217;s a single source of data.</p>
<p>If you&#8217;re used to the Oracle database, you&#8217;ll probably know that it has various components that are used to resolve queries &#8211; the library cache, query rewrite, table and system statistics, etc &#8211; and both rule-based and cost-based optimizers that are used to generate a query plan. For most modern Oracle systems, a statistics-based cost-based optimizer (most famously documented by Jonathan Lewis in <a href="http://www.jlcomp.demon.co.uk/cbo_book/ind_book.html">this book</a>) is used to generate a number of potential execution plans (which can be displayed in a 10035 trace), with the lowest cost being chosen to run the query. Now whilst the equivalent process isn&#8217;t really documented for the BI Server, what it appears to do is largely follow a rule-based approach with a small amount of statistics being used (or not used, as I&#8217;ll mention in a moment). In essence, the following sources of metadata information are consulted when creating the query plan for the BI Server;</p>
<ul>
<li>The presentation (subject area) layer to business model layer mapping rules;</li>
<li>The logical table sources for each of the business columns used in the request;</li>
<li>The dimension level mappings for each of the logical table sources;</li>
<li>The &#8220;Number of Elements at this Level&#8221; count for each dimension level (potentially the statistics bit, though anecdotally I&#8217;ve heard that these figures aren&#8217;t actually used by the BI Server);</li>
<li>Whether caching is enabled, and if so, whether the query can be found in the cache;</li>
<li>What physical features are enabled for the particular source database for each column (and whether they are relational, multi-dimensional, file, XML or whatever)</li>
<li>Specific rules for generating time-series queries, binning etc, and</li>
<li>Security settings and filters</li>
</ul>
<p>As far as I can tell, there are no indexes, no statistics (apart from the dimension level statistics mentioned above) and no hints; there is however query rewrite and aggregates, as the BI Server allows aggregate tables to be defined which are then mapped in to specific levels in a dimension hierarchy. Cleverly, the back-end data source doesn&#8217;t even have to be an SQL database, and can in fact be a multi-dimensional database such as Essbase, Oracle OLAP or Microsoft Analysis Services, with the multi-dimensional dataset that they return converted into a row-based dataset that can be joined to other data coming in from a more traditional relational database.</p>
<p><span style="font-size:14pt;"><strong>&#8220;A Day in the Life of a Query&#8221;<br />
</strong></span><br />
A good way of looking at what Oracle has termed &#8220;A day in the life of a query&#8221;, is to take a look at some slides from a presentation that Oracle used regularly around the time of the introduction of Oracle BI EE. I&#8217;ll go through it slide by slide and add some interpretation from myself.</p>
<p>1. A query comes in from Answers or any other ODBC query tool, asking for one or more columns from a subject area. Overall, the function within the BI Server that deals with this is called <strong>Intelligent Request Generation, </strong>marked in yellow in the diagram below.</p>
<p style="text-align:center;"><img src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/bis10.jpg" height="260" width="500" border="0" hspace="4" vspace="4" alt="Bis10" /></p>
<p>2. The query is then passed to the <strong>Logical Request Generation</strong> engine, marked in yellow in the diagram below. The request itself requires the Brand, Closed Revenue (ultimately held in the GL system), Service Requests (held in the CRM system) and Share of Revenue (a calculated, or derived, measure). As such it&#8217;s going to require multiple physical SQL queries and multi-pass calculations, all of which will be worked out by another part of the BI Server architecture, the Navigator.</p>
<p style="text-align:center;"><img src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/bis11.jpg" height="264" width="500" border="0" hspace="4" vspace="4" alt="Bis11" /></p>
<p>3. Once the logical request has been generated but before its passed off to the Navigator, a check is made (if this feature is enabled) as to whether the logical request can be found in the cache. <strong>Cache Services</strong> will either do a fast, or more comprehensive match of the incoming request against those stored in the query cache, and if found, return the results from there rather than have the BI Server run physical SQL against the business model&#8217;s data sources.</p>
<p style="text-align:center;"><img src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/bis12.jpg" height="264" width="500" border="0" hspace="4" vspace="4" alt="Bis12" /></p>
<p>For a more detailed look at what Cache Services does, the old Siebel Analytics Administration Tool documentation has a good flowchart that explains what goes on:</p>
<p style="text-align:center;"><img src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/bis13.jpg" height="452" width="450" border="0" hspace="4" vspace="4" alt="Bis13" /></p>
<p>The key bit is the Cache Hit step. In general, a cache hit will occur if the following conditions are met:</p>
<ul>
<li>Caching is enabled (CACHE=Y in the NQSConfig.INI file);</li>
<li>The WHERE clause in the logical SQL is semantically the same, or a logical subset of a cached statement;</li>
<li>All of the columns in the SELECT list have to exist in the cached query, or they must be able to be calculated from them;</li>
<li>It has equivalent join conditions, so that the resultant joined table of any incoming query has to be the same as (or a subset of) the cached results</li>
<li>If DISTINCT is used, the cached copy has to use this attribute as well</li>
<li>Aggregation levels have to be compatible, being either the same or more aggregated than the cached query</li>
<li>No further aggregation (for example, RANK) can be used in the incoming query</li>
<li>Any ORDER BY clause has to use columns that are also in the cached SELECT list</li>
</ul>
<p>In addition, there are two NQSConfig.INI parameters that I think were added in the last few releases (as I can&#8217;t find them mentioned in the Siebel Analytics documentation) are USE_ADVANCED_HIT_DETECTION and MAX_SUBEXPR_SEARCH_DEPTH. The latter determines how many levels into an expression (for example, SUM(MAX(SIN(COS(TAN(ABS(TRUNC(PROFIT)))))))) that the cache hit detector will go in trying to get a match, whilst the former turns on some additional cache hit searches that you might want to enable if caching is important but not otherwise happening. Unfortunately the docs don&#8217;t really expand on what these additional searches are or the performance impact that they can introduce, so if anyone has any more information on this, I&#8217;d be glad to hear.</p>
<p>4. If the cache can&#8217;t provide the answer to the request, the request then gets passed to the <strong>Navigator</strong>. The Navigator handles the logical request &#8220;decision tree&#8221; and determines how complex the request is, what data sources (logical table sources) need to be used, whether there are any aggregates that can be used, and overall what is the best way to satisfy the request, based on how you&#8217;ve set up the presentation, business model and mapping, and physical layers in your RPD.</p>
<p style="text-align:center;"><img src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/bis14.jpg" height="264" width="500" border="0" hspace="4" vspace="4" alt="Bis14" /></p>
<p>5. Within the Navigator, the <strong>Multi-Pass / Sub-Request Logic</strong> function analyzes the incoming request and works out the complexity of the query. It works out whether it requires multiple passes (for example, calculates the average of two aggregated measures), or whether the request is based on the results of another request (in other words, uses a sub-request). The BI Server then uses this information to work out the optimal way to gather the required data and do the calculations; in the example used in the slides, the revenue share calculation is based on the other two measures and is therefore considered &#8220;multi-pass&#8221;.</p>
<p style="text-align:center;"><img src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/bis15.jpg" height="264" width="500" border="0" hspace="4" vspace="4" alt="Bis15" /></p>
<p>6. A measure used within the business model and mapping layer may be &#8220;fragmented&#8221;, which means that it is logically partitioned so that historic information, for examples, comes from a data warehouse whilst current information comes from an OLTP application. The <strong>Fragment Optimization Engine</strong> within the Navigator sits between the incoming request and the Execution Engine and where appropriate, transforms the base-level SQL into &#8220;fragmented&#8221; SQL against each of the data sources mapped into the fragmented measure. For more background information on fragmentation, check out <a href="http://www.rittmanmead.com/2007/06/19/obiee-data-modeling-tips-2-fragmentation/">this old blog post on the subject</a>.</p>
<p style="text-align:center;"><img src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/bis17.jpg" height="264" width="500" border="0" hspace="4" vspace="4" alt="Bis17" /></p>
<p>7. The final function within the Navigator is the <strong>Aggregate Navigator</strong>, which uses the logical table source mappings together (in theory) with the dimension level statistics to determine the most efficient table to fetch the data from (i.e. the table with the least number of records to successfully fulfil a request).</p>
<p style="text-align:center;"><img src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/bis16.jpg" height="264" width="500" border="0" hspace="4" vspace="4" alt="Bis16" /></p>
<p>8. The <strong>Optimized Query Rewrites</strong> function within the BI Server then takes the query plan generated by the Navigator and rewrites it to use the features of the underlying database engines, adding RANK(OVER()) calculations if Oracle is being used, for example (referred to as &#8220;function shipping&#8221;) or just getting the raw data and having the BI Server do the calculations afterwards, if working with a database that doesn&#8217;t support analytic SQL functions. This part of the BI Server is also responsible for generating XML queries, or MDX queries for OLAP sources,<br />
which are then sent to the underlying physical databases, in parallel, so that they can retrieve their relevant data sets.</p>
<p style="text-align:center;"><img src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/bis18.jpg" height="264" width="500" border="0" hspace="4" vspace="4" alt="Bis18" /></p>
<p>9. Once the data is retrieved, the results combined together and any further calculations applied, the results are returned to the calling application via the ODBC interface, and also copied to the cache along with the logical SQL query if caching is enabled.</p>
<p style="text-align:center;"><img src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/bis21.jpg" height="264" width="500" border="0" hspace="4" vspace="4" alt="Bis21" /></p>
<p>The BI Server&#8217;s knowledge of what each source database can support, in terms of SQL functions, is determined by the contents of the DBFeatures.INI configuration file which can in turn be over-ridden by the &#8220;Features&#8221; tab in the Database settings in the physical database model.</p>
<p style="text-align:center;"><img src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/bis19.jpg" height="523" width="500" border="0" hspace="4" vspace="4" alt="Bis19" /></p>
<p>I think I&#8217;ve also noticed that, from release to release of OBIEE, the way that time-series queries, for example, get resolved into physical SQL queries changes over time, as Oracle get better at generating efficient SQL queries to resolve complex calculations. It&#8217;s also the case that currently, for Essbase data sources, very few of the functions used by the BI Server get function-shipped to their equivalent MDX functions, though this is meant to be improving in the forthcoming 11g release (and in the meantime, you can use EVALUATE and EVALUATE_AGGR to call MDX functions directly).</p>
<p><span style="font-size:14pt;"><strong>Level 5 Logging, and Logical Execution Plans<br />
</strong></span><br />
You can see what goes on when a complex, multi-pass request that requires multiple data sources is sent through from Answers and gets logged in the NQQuery.log file with level 5 logging enabled. The query requests &#8220;quantity&#8221; information that is held in an Oracle database, &#8220;quotas&#8221; that comes from an Excel spreadsheet, and &#8220;variance&#8221; which is derived from quantity minus quotas. Both columns need to be aggregated before the variance calculation can take place, and you can see from the logs the Navigator being used to resolve the query.</p>
<p>Starting off, this is the logical request coming through.</p>
<pre>
-------------------- Logical Request (before navigation):

RqList
    Times.Month Name as c1 GB,
    Quantity:[DAggr(Items.Quantity by [ Times.Month Name, Times.Month ID] )] as c2 GB,
    Quota:[DAggr(Items.Quota by [ Times.Month Name, Times.Month ID] )] as c3 GB,
    Quantity:[DAggr(Items.Quantity by [ Times.Month Name, Times.Month ID] )] - Quota:[DAggr(Items.Quota by [ Times.Month Name, Times.Month ID] )] as c4 GB,
    Times.Month ID as c5 GB
OrderBy: c5 asc
</pre>
<p>Then the navigator breaks the query down, works out what sources, multi-pass calculations and aggregates can be used, and generates the logical query plan.</p>
<pre>
-------------------- Execution plan:

RqList &lt;&lt;993147&gt;&gt; [for database 0:0,0]
    D1.c1 as c1 [for database 0:0,0],
    D1.c2 as c2 [for database 3023:491167,44],
    D1.c3 as c3 [for database 0:0,0],
    D1.c4 as c4 [for database 0:0,0]
Child Nodes (RqJoinSpec): &lt;&lt;993160&gt;&gt; [for database 0:0,0]
    (
        RqList &lt;&lt;993129&gt;&gt; [for database 0:0,0]
            D1.c1 as c1 [for database 0:0,0],
            D1.c2 as c2 [for database 3023:491167,44],
            D1.c3 as c3 [for database 0:0,0],
            D1.c4 as c4 [for database 0:0,0],
            D1.c5 as c5 [for database 0:0,0]
        Child Nodes (RqJoinSpec): &lt;&lt;993144&gt;&gt; [for database 0:0,0]
            (
                RqBreakFilter &lt;&lt;993128&gt;&gt;[1,5] [for database 0:0,0]
                    RqList &lt;&lt;992997&gt;&gt; [for database 0:0,0]
                        case  when D903.c1 is not null then D903.c1 when D903.c2 is not null then D903.c2 end  as c1 GB [for database 0:0,0],
                        D903.c3 as c2 GB [for database 3023:491167,44],
                        D903.c4 as c3 GB [for database 0:0,0],
                        D903.c3 - D903.c4 as c4 GB [for database 0:0,0],
                        case  when D903.c5 is not null then D903.c5 when D903.c6 is not null then D903.c6 end  as c5 GB [for database 0:0,0]
                    Child Nodes (RqJoinSpec): &lt;&lt;993162&gt;&gt; [for database 0:0,0]
                        (
                            RqList &lt;&lt;993219&gt;&gt; [for database 0:0,0]
                                D902.c1 as c1 [for database 0:0,0],
                                D901.c1 as c2 [for database 3023:491167,44],
                                D901.c2 as c3 GB [for database 3023:491167,44],
                                D902.c2 as c4 GB [for database 0:0,0],
                                D902.c3 as c5 [for database 0:0,0],
                                D901.c3 as c6 [for database 3023:491167,44]
                            Child Nodes (RqJoinSpec): &lt;&lt;993222&gt;&gt; [for database 0:0,0]

                                    (
                                        RqList &lt;&lt;993168&gt;&gt; [for database 3023:491167:ORCL,44]
                                            D1.c2 as c1 [for database 3023:491167,44],
                                            D1.c1 as c2 GB [for database 3023:491167,44],
                                            D1.c3 as c3 [for database 3023:491167,44]
                                        Child Nodes (RqJoinSpec): &lt;&lt;993171&gt;&gt; [for database 3023:491167:ORCL,44]
                                            (
                                                RqBreakFilter &lt;&lt;993051&gt;&gt;[2] [for database 3023:491167:ORCL,44]
                                                    RqList &lt;&lt;993263&gt;&gt; [for database 3023:491167:ORCL,44]
                                                        sum(ITEMS.QUANTITY by [ TIMES.MONTH_MON_YYYY] ) as c1 [for database 3023:491167,44],
                                                        TIMES.MONTH_MON_YYYY as c2 [for database 3023:491167,44],
                                                        TIMES.MONTH_YYYYMM as c3 [for database 3023:491167,44]
                                                    Child Nodes (RqJoinSpec): &lt;&lt;993047&gt;&gt; [for database 3023:491167:ORCL,44]
                                                        TIMES T492004
                                                        ITEMS T491980
                                                        ORDERS T491989
                                                    DetailFilter: ITEMS.ORDID = ORDERS.ORDID and ORDERS.ORDERDATE = TIMES.DAY_ID [for database 0:0]
                                                    GroupBy: [ TIMES.MONTH_MON_YYYY, TIMES.MONTH_YYYYMM]  [for database 3023:491167,44]
                                            ) as D1
                                        OrderBy: c1 asc [for database 3023:491167,44]
                                    ) as D901 FullOuterStitchJoin &lt;&lt;993122&gt;&gt; On D901.c1 =NullsEqual D902.c1; actual join vectors:  [ 0 ] =  [ 0 ]

                                    (
                                        RqList &lt;&lt;993192&gt;&gt; [for database 0:0,0]
                                            D2.c2 as c1 [for database 0:0,0],
                                            D2.c1 as c2 GB [for database 0:0,0],
                                            D2.c3 as c3 [for database 0:0,0]
                                        Child Nodes (RqJoinSpec): &lt;&lt;993195&gt;&gt; [for database 0:0,0]
                                            (
                                                RqBreakFilter &lt;&lt;993093&gt;&gt;[2] [for database 0:0,0]
                                                    RqList &lt;&lt;993319&gt;&gt; [for database 0:0,0]
                                                        D1.c1 as c1 [for database 0:0,0],
                                                        D1.c2 as c2 [for database 0:0,0],
                                                        D1.c3 as c3 [for database 0:0,0]
                                                    Child Nodes (RqJoinSpec): &lt;&lt;993334&gt;&gt; [for database 0:0,0]
                                                        (
                                                            RqList &lt;&lt;993278&gt;&gt; [for database 3023:496360:Quotas,2]
                                                                sum(QUANTITY_QUOTAS.QUOTA by [ MONTHS.MONTH_MON_YYYY] ) as c1 [for database 3023:496360,2],
                                                                MONTHS.MONTH_MON_YYYY as c2 [for database 3023:496360,2],
                                                                MONTHS.MONTH_YYYYMM as c3 [for database 3023:496360,2]
                                                            Child Nodes (RqJoinSpec): &lt;&lt;993089&gt;&gt; [for database 3023:496360:Quotas,2]
                                                                MONTHS T496365
                                                                QUANTITY_QUOTAS T496369
                                                            DetailFilter: MONTHS.MONTH_YYYYMM = QUANTITY_QUOTAS.MONTH_YYYYMM [for database 0:0]
                                                            GroupBy: [ MONTHS.MONTH_YYYYMM, MONTHS.MONTH_MON_YYYY]  [for database 3023:496360,2]
                                                        ) as D1
                                                    OrderBy: c2 [for database 0:0,0]
                                            ) as D2
                                        OrderBy: c1 asc [for database 0:0,0]
                                    ) as D902
                        ) as D903
                    OrderBy: c1, c5 [for database 0:0,0]
            ) as D1
        OrderBy: c5 asc [for database 0:0,0]
    ) as D1
</pre>
<p>Notice the &#8220;FullOuterStitchJoin&#8221; in the middle of the plan? We&#8217;ll look into this more in the next posting in this series. For now though, this logical query plan is then passed to the Optimized Query Rewrites and Execution Engine, which then generates in this case two physical SQL statements that are then passed back, and &#8220;stitch joined&#8221;, by the BI Server, before performing the post-aggregation calculation required for the variance measure.</p>
<pre>-------------------- Sending query to database named ORCL (id: &lt;&lt;993168&gt;&gt;):

select D1.c2 as c1,
     D1.c1 as c2,
     D1.c3 as c3
from
     (select D1.c1 as c1,
               D1.c2 as c2,
               D1.c3 as c3
          from
               (select sum(T491980.QUANTITY) as c1,
                         T492004.MONTH_MON_YYYY as c2,
                         T492004.MONTH_YYYYMM as c3,
                         ROW_NUMBER() OVER (PARTITION BY T492004.MONTH_MON_YYYY ORDER BY T492004.MONTH_MON_YYYY ASC) as c4
                    from
                         CUST_ORDER_HISTORY.TIMES T492004,
                         CUST_ORDER_HISTORY.ITEMS T491980,
                         CUST_ORDER_HISTORY.ORDERS T491989
                    where  ( T491980.ORDID = T491989.ORDID and T491989.ORDERDATE = T492004.DAY_ID )
                    group by T492004.MONTH_MON_YYYY, T492004.MONTH_YYYYMM
               ) D1
          where  ( D1.c4 = 1 )
     ) D1
order by c1

+++Administrator:2b0000:2b000e:----2010/02/23 16:04:42

-------------------- Sending query to database named Quotas (id: &lt;&lt;993278&gt;&gt;):
select sum(T496369."QUOTA") as c1,
     T496365."MONTH_MON_YYYY" as c2,
     T496365."MONTH_YYYYMM" as c3
from
     "MONTHS" T496365,
     "QUANTITY_QUOTAS" T496369
where  ( T496365."MONTH_YYYYMM" = T496369."MONTH_YYYYMM" )
group by T496365."MONTH_YYYYMM", T496365."MONTH_MON_YYYY"
</pre>
<p><span style="font-size:14pt;"><strong><br />
Memory Usage and Paging Files</strong></span></p>
<p>If you follow the BI Server at the process level during these steps, you&#8217;ll find that memory usage is largely determined at startup time by the size and complexity of the RPD thats attached online, and then goes up by around 50MB when the first query is executed. After that, memory usage tends to go up the more concurrent sessions that are run, and also when cross-database joins are performed. You&#8217;ll also find TMP files being created in $ORACLEBIDATA/tmp directory, which are used by the BI Server to hold temporary data as it pages out from memory, again typically when cross-database joins are used but also when it needs to perform additional aggregations that can&#8217;t be put into the physical SQL query.</p>
<p style="text-align:center;"><img src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/bis20.jpg" height="478" width="500" border="0" hspace="4" vspace="4" alt="Bis20" /></p>
<p>These files can get fairly big (up to 2GB in some cases) and can be created even when a single data source is used, typically for grouping data or as we&#8217;ll see in the next posting, when joining data across fact tables. They are usually cleared down when the BI Server and Presentation Server are restarted, but bear in mind when creating complex calculations that they can get pretty I/O intensive on the BI Server hardware.</p>
<p>So that&#8217;s the basics in terms of how basic queries are processed by the BI Server, and how the various BI Server components and engines process the query as it goes through the various stages. Again, if anyone knows any more, please add it as a comment, but for now that&#8217;s it and I&#8217;ll be back in a few days with part 3, on BI Server In-Memory Joins.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.rittmanmead.com/2010/03/01/inside-the-oracle-bi-server-part-2-how-is-a-query-processed/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Oracle BI EE 10.1.3.4.1 &#8211; Puzzlers &#8211; Puzzle 4 &#8211; Bypassing Security</title>
		<link>http://www.rittmanmead.com/2010/02/28/oracle-bi-ee-10-1-3-4-1-puzzlers-puzzle-4-bypassing-security/</link>
		<comments>http://www.rittmanmead.com/2010/02/28/oracle-bi-ee-10-1-3-4-1-puzzlers-puzzle-4-bypassing-security/#comments</comments>
		<pubDate>Sun, 28 Feb 2010 19:36:17 +0000</pubDate>
		<dc:creator>Venkatakrishnan J</dc:creator>
				<category><![CDATA[Oracle BI Suite EE]]></category>

		<guid isPermaLink="false">http://www.rittmanmead.com/?p=4462</guid>
		<description><![CDATA[On to the 4th puzzle in this Puzzle series. This is a very interesting Puzzle at least from the perspective of BI EE Security. We all know that BI EE provides comprehensive security within the repository. For example, the screenshot below shows that the column CHANNEL_DESC can be accessed only by the exec user.

Now when [...]]]></description>
			<content:encoded><![CDATA[<p>On to the 4th puzzle in this Puzzle series. This is a very interesting Puzzle at least from the perspective of BI EE Security. We all know that BI EE provides comprehensive security within the repository. For example, the screenshot below shows that the column CHANNEL_DESC can be accessed only by the exec user.</p>
<p align="center"><img style="border-top-width: 0px; display: block; border-left-width: 0px; float: none; border-bottom-width: 0px; margin-left: auto; margin-right: auto; border-right-width: 0px" title="image" src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/image50.png" border="0" alt="image" width="432" height="315" /></p>
<p>Now when we log in as any user(users who do not belong to Administrators group) and open the report containing this secured column, we will either be getting an error or will be shown as NULL in reports depending on the PROJECT_INACCESSIBLE_COLUMN_AS_NULL property in the NQSConfig.ini. The question is how do we bypass the security and still show the CHANNEL_DESC column(with their values of course) in reports. I know this sounds a bit malicious and can even be read as a security hack, but this is currently possible in BI EE(in the solution i will let you know how to use a workaround to disable this though). The question or the Puzzle today is how do we achieve this. There are some potential use cases of this</p>
<p>1. If you do not have access to the repository but still want to look at certain security protected columns to validate certain reports</p>
<p>2. If you do not have a column in the repository at all (but exists in the database) and you still want to access it.</p>
<p>Remember this is not about enabling Direct Database Requests as in most cases that will always be disabled. For example, if you look at the report below, CHANNEL_DESC has become null for the user i have logged in as this user does not have access to this column(PROJECT_INACCESSIBLE_COLUMN_AS_NULL  is set to YES in my case).</p>
<p align="center"><img style="border-top-width: 0px; display: block; border-left-width: 0px; float: none; border-bottom-width: 0px; margin-left: auto; margin-right: auto; border-right-width: 0px" title="image" src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/image51.png" border="0" alt="image" width="272" height="117" /></p>
<p>If you look at the SQL, you will notice that the CHANNEL_DESC column is not even pushed back to the database.</p>
<pre>WITH
SAWITH0 AS (select sum(1) as c1,
     T4167.CHANNEL_ID as c2
from
     CHANNELS T4167
group by T4167.CHANNEL_ID)
select distinct SAWITH0.c2 as c1,
     cast(NULL as  VARCHAR ( 1 ) ) as c2,
     SAWITH0.c1 as c3
from
     SAWITH0
order by c1, c2</pre>
<p>which is good and as expected. Now, the puzzle is to somehow bypass this security and display the CHANNEL_DESC column as shown below.</p>
<p align="center"><img style="border-top-width: 0px; display: block; border-left-width: 0px; float: none; border-bottom-width: 0px; margin-left: auto; margin-right: auto; border-right-width: 0px" title="image" src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/image52.png" border="0" alt="image" width="269" height="119" /></p>
<p>Remember, there are 2 pre-conditions to this</p>
<p>1. No Direct Database Requests</p>
<p>2. No changing the repository to add a new column in the presentation layer</p>
]]></content:encoded>
			<wfw:commentRss>http://www.rittmanmead.com/2010/02/28/oracle-bi-ee-10-1-3-4-1-puzzlers-puzzle-4-bypassing-security/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>Oracle BI EE 10.1.3.4.1 &#8211; Solutions &#8211; Puzzle 3</title>
		<link>http://www.rittmanmead.com/2010/02/27/oracle-bi-ee-10-1-3-4-1-solutions-puzzle-3/</link>
		<comments>http://www.rittmanmead.com/2010/02/27/oracle-bi-ee-10-1-3-4-1-solutions-puzzle-3/#comments</comments>
		<pubDate>Sat, 27 Feb 2010 20:09:33 +0000</pubDate>
		<dc:creator>Venkatakrishnan J</dc:creator>
				<category><![CDATA[Oracle BI Suite EE]]></category>

		<guid isPermaLink="false">http://www.rittmanmead.com/?p=4429</guid>
		<description><![CDATA[After almost a week of me giving out the Puzzle 3, there was hardly any interest for this one (just couple of odd replies requesting clarification of the Puzzle). Probably this is because this one has no direct practical usage and also there are lots of possibilities. But to me this is one very important [...]]]></description>
			<content:encoded><![CDATA[<p>After almost a week of me giving out the Puzzle 3, there was hardly any interest for this one (just couple of odd replies requesting clarification of the Puzzle). Probably this is because this one has no direct practical usage and also there are lots of possibilities. But to me this is one very important Puzzle as in many cases when we are called in for repository tuning, the first question that we normally get in such situations is, why does BI EE generate such a big query when the same report can be solved by a very simple query. In such cases, we need to know where to look at and also understand what can cause BI EE to generate long SQLs. Remember, there is no theoretical limit to the length of the SQL generated (we can make it to generate as big a SQL as we want). The puzzle was meant primarily to know the possibilities of what can make BI EE to generate long SQLs. I always try to visualize a repository by looking at the SQL. That generally helps in doing further analysis on a pre-built repository.</p>
<p><strong>Solution 1 – Conforming Dimensions:</strong></p>
<p>This is probably the easiest and the most common reason why BI EE generates a lot of sub-queries. Always conforming dimensions should be used only when absolutely necessary as that will start generating sub-queries for every fact. For example, if you look at the repository below, it shows a very simple Business Model and Mapping layer containing one dimension with one Logical table source pointing to the physical CHANNELS table.It also contains 3 Facts each having a count metric (Mapped to 1 in the BMM layer for all the 3 columns)</p>
<p align="center"><img style="border-top-width: 0px; display: block; border-left-width: 0px; float: none; border-bottom-width: 0px; margin-left: auto; margin-right: auto; border-right-width: 0px" title="image" src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/image36.png" border="0" alt="image" width="152" height="315" /></p>
<p align="center"><img style="border-top-width: 0px; display: block; border-left-width: 0px; float: none; border-bottom-width: 0px; margin-left: auto; margin-right: auto; border-right-width: 0px" title="image" src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/image37.png" border="0" alt="image" width="504" height="178" /></p>
<p>There is also one more Logical Column that basically adds all the 3 columns together using a logical calculation</p>
<p align="center"><img style="border-top-width: 0px; display: block; border-left-width: 0px; float: none; border-bottom-width: 0px; margin-left: auto; margin-right: auto; border-right-width: 0px" title="image" src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/image38.png" border="0" alt="image" width="412" height="271" /></p>
<p>Now, when you generate a report using the CHANNEL_TOTAL and the logical calculated column, you will notice that BI EE will generate 3 sub-queries and then will bring them together as shown below</p>
<p align="center"><img style="border-top-width: 0px; display: block; border-left-width: 0px; float: none; border-bottom-width: 0px; margin-left: auto; margin-right: auto; border-right-width: 0px" title="image" src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/image39.png" border="0" alt="image" width="504" height="257" /></p>
<p>So the solution is you can create n number of conforming dimensions like this to make BI EE to produce n sub-queries thereby making the SQL very long. In this case there is no need for actually using conforming dimensions. The same SQL can actually be converted into a single SQL with all the counts (without the sub-queries). This basically demonstrates a bad use of Conforming Dimensions.</p>
<p><strong>Solution 2 – Fragmentation:</strong></p>
<p>This is another way of generating big SQLs. Same source can be made to appear as part of UNION ALL queries using Fragmentation. For example, if you look at the repository below</p>
<p align="center"><img style="border-top-width: 0px; display: block; border-left-width: 0px; float: none; border-bottom-width: 0px; margin-left: auto; margin-right: auto; border-right-width: 0px" title="image" src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/image40.png" border="0" alt="image" width="195" height="260" /></p>
<p>there are basically 3 logical table sources that contribute to the Fact Count. Each logical table source is modeled in a way such that all of them contribute to the Count and follow Parallel Fragmentation as shown below</p>
<p align="center"><img style="border-top-width: 0px; display: block; border-left-width: 0px; float: none; border-bottom-width: 0px; margin-left: auto; margin-right: auto; border-right-width: 0px" title="image" src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/image41.png" border="0" alt="image" width="267" height="315" /> <img style="border-top-width: 0px; display: block; border-left-width: 0px; float: none; border-bottom-width: 0px; margin-left: auto; margin-right: auto; border-right-width: 0px" title="image" src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/image42.png" border="0" alt="image" width="260" height="315" /></p>
<p align="center"><img style="border-top-width: 0px; display: block; border-left-width: 0px; float: none; border-bottom-width: 0px; margin-left: auto; margin-right: auto; border-right-width: 0px" title="image" src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/image43.png" border="0" alt="image" width="261" height="315" /></p>
<p>And if you look at the SQL, you will notice that BI EE will fire 3 UNION ALLs to generate the count. You can make this query as big as you want by adding more and more logical table sources.</p>
<p align="center"><img style="border-top-width: 0px; display: block; border-left-width: 0px; float: none; border-bottom-width: 0px; margin-left: auto; margin-right: auto; border-right-width: 0px" title="image" src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/image44.png" border="0" alt="image" width="504" height="299" /></p>
<p><strong>Solution 3 – Level Based Measures:</strong></p>
<p>This is another possible solution where incorrect use of Level-Based aggregation can start generating pretty complex queries. In your queries, if you start noticing Partition By using ROW_NUMBER or SUM() OVER functions then that means level based measures are being used somewhere (not in all cases but in most of them). For example, lets look at the repository below</p>
<p align="center"><img style="border-top-width: 0px; display: block; border-left-width: 0px; float: none; border-bottom-width: 0px; margin-left: auto; margin-right: auto; border-right-width: 0px" title="image" src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/image45.png" border="0" alt="image" width="178" height="315" /></p>
<p>As you notice we basically have have 3 count columns each dependent on the other. Count1 is a normal measure assigned to a constant 1 and to the lowermost level in the Channel Dimension. Count 1 is a measure which is equal to measure Count(logically calculated) but assigned to the Channel Class level. Count3 is equal to measure Count2(logically calculated) but assigned to the Total level.</p>
<p align="center"><img style="border-top-width: 0px; display: block; border-left-width: 0px; float: none; border-bottom-width: 0px; margin-left: auto; margin-right: auto; border-right-width: 0px" title="image" src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/image46.png" border="0" alt="image" width="409" height="128" /></p>
<p align="center"><img style="border-top-width: 0px; display: block; border-left-width: 0px; float: none; border-bottom-width: 0px; margin-left: auto; margin-right: auto; border-right-width: 0px" title="image" src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/image47.png" border="0" alt="image" width="405" height="246" /></p>
<p align="center"><img style="border-top-width: 0px; display: block; border-left-width: 0px; float: none; border-bottom-width: 0px; margin-left: auto; margin-right: auto; border-right-width: 0px" title="image" src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/image48.png" border="0" alt="image" width="412" height="138" /></p>
<p>As you see, this basically demonstrates why logically calculated measures can have different level assignments than their base members. If you look at the SQL generated,</p>
<p align="center"><img style="border-top-width: 0px; display: block; border-left-width: 0px; float: none; border-bottom-width: 0px; margin-left: auto; margin-right: auto; border-right-width: 0px" title="image" src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/image49.png" border="0" alt="image" width="460" height="315" /></p>
<p>you will notice a number of sub-queries which will equal to the number of level assignments for each dependent measure.</p>
<p>In all the cases above, the queries were generated using a single Physical table and from just using 2 report attributes. Much more complex queries are possible using other methods but most of them will be a variation of the 3 listed above. Puzzle 4 to follow tomorrow.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.rittmanmead.com/2010/02/27/oracle-bi-ee-10-1-3-4-1-solutions-puzzle-3/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Inside the Oracle BI Server Part 1 : The BI Server Architecture</title>
		<link>http://www.rittmanmead.com/2010/02/25/inside-the-oracle-bi-server-part-1-the-bi-server-architecture/</link>
		<comments>http://www.rittmanmead.com/2010/02/25/inside-the-oracle-bi-server-part-1-the-bi-server-architecture/#comments</comments>
		<pubDate>Thu, 25 Feb 2010 22:11:30 +0000</pubDate>
		<dc:creator>Mark Rittman</dc:creator>
				<category><![CDATA[Oracle BI Suite EE]]></category>

		<guid isPermaLink="false">http://www.rittmanmead.com/2010/02/25/inside-the-oracle-bi-server-part-1-the-bi-server-architecture/</guid>
		<description><![CDATA[The session that I&#8217;m giving at the BI Forum in Brighton in May is entitled &#8220;Inside the Oracle BI Server&#8221;, and I&#8217;m aiming to take a closer look at the architecture and functionality of this key OBIEE component. We&#8217;re all fairly aware of what the BI Server does at a high level, but I thought [...]]]></description>
			<content:encoded><![CDATA[<p>The session that I&#8217;m giving at the <a href="http://www.rittmanmead.com/biforum2010">BI Forum in Brighton in May</a> is entitled &#8220;Inside the Oracle BI Server&#8221;, and I&#8217;m aiming to take a closer look at the architecture and functionality of this key OBIEE component. We&#8217;re all fairly aware of what the BI Server does at a high level, but I thought it&#8217;d be interesting to take a closer look at what the BI Server does, particularly when it parses queries and joins datasets together.</p>
<p>At a very high level, the main function of the BI Server is to process inbound SQL requests against against a virtual database model, build and execute one or more physical database queries, process the data and then return it to users. The BI Server is one part of the Oracle BI Enterprise Edition Plus product family, and presents itself to query tools as one or more databases in a simple relational (star schema) model, that can then point to a much more complex set of relational, multidimensional, file and XML data sources (and in 11g, ADF objects).</p>
<p>Taking the standard OBIEE architecture diagram, the BI Server sits in the middle of the OBIEE set of servers and provides the query capability, security, interfaces to data sources and calculation logic for OBIEE (all of this is based on the current, 10g set of products).</p>
<p style="text-align:center;"><img src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/bis1-1.jpg" height="321" width="500" border="0" hspace="4" vspace="4" alt="Bis1-1" /></p>
<p>The BI Server communicates with the BI Presentation Server via ODBC, and then connects out to the various supported data sources through ODBC, OCI, XML/A, the Essbase Client API and other native protocols. A key function of the BI Server is to create a three-layer metadata model, stored in a file-based repository along with security settings, database passwords, BI Server settings, startup macros and variable definitions.</p>
<p><span style="font-size:14pt;"><strong>The BI Server Logical Components<br />
</strong></span><br />
Taking a look specifically at the BI Server, it has a number of logical components.</p>
<p style="text-align:center;"><img src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/bis2.jpg" height="542" width="500" border="0" hspace="4" vspace="4" alt="Bis2" /></p>
<ul>
<li>The <strong>ODBC interface</strong>, that is used by Oracle BI Answers and other third-party tools to pass requests to the BI Server, and to receive the output from queries;</li>
<li>The <strong>Logical Business Model</strong>, the three-layer metadata model that describes the data available for queries;</li>
<li>The <strong>Intelligent Request Generator</strong>, a module responsible for taking the incoming queries and turning them into physical queries against the connected data source, which is made up of several sub-components including:</li>
<li>The <strong>Navigator</strong>, probably the most important part of the BI Server, and the part that takes the incoming query, compares it against cached answers, navigates the logical model and generates the physical queries that will best return the data required for the query</li>
<li>Within the Navigator, there are modules for determining whether <strong>multiple physical queries</strong> are needed, whether stored <strong>aggregates</strong> can be used, and whether <strong>fragmented</strong> data sources can be used for partitioned measures;</li>
<li>An <strong>Optimized Query Rewrite</strong> engine for handling aggregate navigation and fragments, and for translating to the correct physical SQL dialect, and</li>
<li>An <strong>Execution Engine</strong> for firing off the queries to the relational, multi-dimensional, file and XML sources required to satisfy the query.</li>
<li><strong>Cache Services</strong> stores the results of previously run queries, matches incoming SQL against that used before and returns data from the cache rather than making the BI Server query the underlying databases again</li>
</ul>
<p>In addition, various supporting technologies, modules and services provide the infrastructure for the BI Server, including:</p>
<ul>
<li><strong>Data Source Adapters</strong> for Oracle, ODBC, SQL Server, DB/2, Teradata, file, XML and other sources;</li>
<li><strong>System and Performance Monitoring</strong> through JMX counters and other technologies;</li>
<li><strong>Security Services</strong> for setting up users and groups in the RPD, filters, subject area security, links to outside LDAP servers and custom authenticators;</li>
<li><strong>Query Governance</strong>, for placing limits on numbers of rows returned and length of query execution for users and groups;</li>
<li><strong>Load Balancing</strong>, and <strong>Session Management</strong></li>
</ul>
<p><span style="font-size:14pt;"><strong>Taking a Look at the BI Server Process<br />
</strong></span><br />
Now whilst the BI Server has many characteristics of a database, compared to running Oracle on Unix which exposes many of its components (SMON, PMON, MMON, LGWR etc) as separate processes, the BI Server is just a single executable that runs under the name NQSServer.exe (or just nqsserver under Unix). The screenshot below is a view of this service (along with sawserver.exe, the BI Presentation Server) as shown in the Windows Task Manager utility.</p>
<p style="text-align:center;"><img src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/bis3.jpg" height="465" width="426" border="0" hspace="4" vspace="4" alt="Bis3" /></p>
<p>We&#8217;ll get on to memory usage in a future posting in this series, but in general the amount of memory taken up by the BI Server is initially determined by the size and complexity of the repository (RPD) that is running online, with further chunks taken up by concurrent sessions and then intermittent spikes of memory when in-memory (stitch) joins take place between data sources. The BI Server creates TMP (temporary) files in the $ORACLEBIDATA/tmp directory as data is further totalled and calculated, and as cross-database joins are paged to file.</p>
<p>If you take a closer look at the NQSServer.exe process using a tools such as Microsoft&#8217;s Process Explorer utility, you can see that it&#8217;s a multi-threaded server application:</p>
<p style="text-align:center;"><img src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/bis4.jpg" height="522" width="500" border="0" hspace="4" vspace="4" alt="Bis4" /></p>
<p>You can see that the BI Server is a C++ application that uses the Microsoft Visual C++ runtime, whilst taking a look at one of the running threads shows the various DLLs that are being used:</p>
<p style="text-align:center;"><img src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/bis5.jpg" height="402" width="461" border="0" hspace="4" vspace="4" alt="Bis5" /></p>
<p><span style="font-size:14pt;"><strong>Another Conceptual View of the BI Server<br />
</strong></span><br />
Another conceptual view of the BI Server architecture can be found in the old Siebel Analytics Administration Tool documentation, which shows the BI Server (or the Siebel Analytics Server as it was called then) having several layered components:</p>
<p style="text-align:center;"><img src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/bis6.jpg" height="489" width="500" border="0" hspace="4" vspace="4" alt="Bis6" /></p>
<ul>
<li>The <strong>Security Model</strong>, presumably the users and groups in the RPD, plus the filters and subject area security in the repository;</li>
<li>The <strong>Business Model</strong>, the three-layer metadata model;</li>
<li><strong>Aggregate Navigation</strong>, for rewriting queries to use mapped in aggregate tables;</li>
<li><strong>SQL Generation Engine</strong> and <strong>Multi-database Query Processing</strong>, presumably the bit that takes the database capabilities matrix and generates the correct physical SQL for the various data sources;</li>
<li>The <strong>Computation Engine</strong>, for performing in-memory stitch joins, post-aggregation filters and functions, and sorting,</li>
<li>Query cachiing</li>
<li>The <strong>Metadata Repositories</strong> that can be connected to the BI Server (with one marked as &#8220;default&#8221;, and</li>
<li>The various <strong>data sources</strong>, such as Oracle, DB/2, Informix and SQL Server</li>
</ul>
<p><span style="font-size:14pt;"><strong>Conclusions<br />
</strong></span><br />
So the BI server has some of the characteristics of a BI tool (metadata model, connectivity to data sources, security etc) and some of a regular relational database (query processing, optimization, rewrite, aggregate navigation etc) but without OLTP database features such as transactions. Its primary job is to process incoming requests against this metadata model and translate them into the physical queries required to get the data from the underlying data sources, acting more as a query broker with no data being stored locally except that held in the cache. If you&#8217;re interested in a bit more history of the BI Server, including its origins as a search engine called the nQuire Query Server, take a look at <a href="http://www.rittmanmead.com/2007/09/18/a-potted-history-of-oracle-bi-suite-enterprise-edition/">this old blog post on the origins of Siebel Analytics and OBIEE</a> where I&#8217;ve written up some of the original origins of the OBIEE product set.</p>
<p>The BI Server has one main configuration file, held at $ORACLEBI/server/config/NQSConfig.INI, which contains parameter settings in plain text. The full set of possible parameters are held in the Server Administrators&#8217; Guide within the Oracle docs, and this method of holding parameter settings looks like it&#8217;ll be carried across to 11g, although the settings themselves will be maintained through Enterprise Manager rather than the Administration tool as is the case with 10g and earlier.</p>
<p>For now though, that&#8217;s it for architecture and components and in the next posting, I&#8217;ll be looking at how the BI Server, and in particular the Navigator, handles incoming requests.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.rittmanmead.com/2010/02/25/inside-the-oracle-bi-server-part-1-the-bi-server-architecture/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Oracle BI EE 10.1.3.4.1 &#8211; Puzzlers &#8211; Puzzle 3 &#8211; Negative Modeling</title>
		<link>http://www.rittmanmead.com/2010/02/21/oracle-bi-ee-10-1-3-4-1-puzzlers-puzzle-3-negative-modeling/</link>
		<comments>http://www.rittmanmead.com/2010/02/21/oracle-bi-ee-10-1-3-4-1-puzzlers-puzzle-3-negative-modeling/#comments</comments>
		<pubDate>Sun, 21 Feb 2010 19:49:10 +0000</pubDate>
		<dc:creator>Venkatakrishnan J</dc:creator>
				<category><![CDATA[Oracle BI Suite EE]]></category>

		<guid isPermaLink="false">http://www.rittmanmead.com/?p=4391</guid>
		<description><![CDATA[Now that i have blogged about the solution for Puzzles 1 and 2, its time for the next puzzle. This one requires you to actually build a very simple repository and show me how you can do negative modeling i.e a model that will produce the longest single SQL(basically a pretty big SQL), all on [...]]]></description>
			<content:encoded><![CDATA[<p>Now that i have blogged about the solution for Puzzles 1 and 2, its time for the next puzzle. This one requires you to actually build a very simple repository and show me how you can do negative modeling i.e a model that will produce the longest single SQL(basically a pretty big SQL), all on a single table. To expand further, lets assume that you have a single table in the physical database called CHANNELS(with 3 attributes). Now, you need to build a repository that will basically expose 2 columns in the presentation layer. When we create a report using these 2 columns, the SQL produced should be extremely long. I know this is not something that you would encounter anywhere but this will basically help you understand what can produce large SQLs in the database. There are 3 pre-conditions to the puzzle</p>
<p>1. No physical aliases can be used. Only one physical table(No select SQL) should exist in the physical layer. Else this will become very easy to solve.<br />
2. Your entire SQL should be driven out of your Business Model in the repository.<br />
3. Only 2 columns can be exposed in the presentation layer (i can be a bit flexible here. If you need to expose more columns, feel free to expose them if you think that will make the SQL to be long enough but ideally i would like only 2 to be exposed)</p>
<p>There is no business case/requirement that the RPD has to solve. All it has to do is to produce the longest SQL that you think is possible. Of course again, there are multiple possible solutions but all i need is a generic idea of how you would be implementing this.</p>
<p>A sample SQL (which is not that long but longer than the normal simple select) is given below of a simple report containing 2 columns</p>
<pre>WITH
SAWITH0 AS (select D1.c1 as c1,
     D1.c2 as c2,
     D1.c3 as c3
from
     (select T1776.CHANNEL_CLASS as c1,
               T1776.CHANNEL_CLASS_ID as c2,
               T1776.CHANNEL_TOTAL_ID as c3,
               ROW_NUMBER() OVER (PARTITION BY T1776.CHANNEL_CLASS_ID ORDER BY T1776.CHANNEL_CLASS_ID ASC) as c4
          from
               CHANNELS T1776
     ) D1
where  ( D1.c4 = 1 ) ),
SAWITH1 AS (select sum(1) as c1,
     T1776.CHANNEL_TOTAL_ID as c2
from
     CHANNELS T1776
group by T1776.CHANNEL_TOTAL_ID),
SAWITH2 AS (select D1.c1 as c1,
     D1.c2 as c2,
     D1.c3 as c3,
     D1.c4 as c4
from
     (select SAWITH0.c1 as c1,
               SAWITH1.c1 as c2,
               case  when SAWITH1.c2 is not null then SAWITH1.c2 when SAWITH0.c3 is not null then SAWITH0.c3 end  as c3,
               SAWITH0.c2 as c4,
               ROW_NUMBER() OVER (PARTITION BY SAWITH0.c1, SAWITH0.c2,
               case  when SAWITH1.c2 is not null then SAWITH1.c2
               when SAWITH0.c3 is not null then SAWITH0.c3 end
               ORDER BY SAWITH0.c1 ASC, SAWITH0.c2 ASC,
               case  when SAWITH1.c2 is not null
               then SAWITH1.c2 when SAWITH0.c3 is not null
               then SAWITH0.c3 end  ASC) as c5
          from
               SAWITH0 full outer join SAWITH1 On SAWITH0.c3 = SAWITH1.c2
     ) D1
where  ( D1.c5 = 1 ) )
select SAWITH2.c1 as c1,
     SAWITH2.c2 as c2,
     SAWITH2.c3 as c3,
     SAWITH2.c4 as c4
from
     SAWITH2
order by c1</pre>
<p align="center"><img style="border-top-width: 0px; display: block; border-left-width: 0px; float: none; border-bottom-width: 0px; margin-left: auto; margin-right: auto; border-right-width: 0px" title="image" src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/image35.png" border="0" alt="image" width="498" height="315" /></p>
<p style="text-align: left;">
<p style="text-align: left;">Puzzle 4 to follow next which again will be in the lines of negative modeling.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.rittmanmead.com/2010/02/21/oracle-bi-ee-10-1-3-4-1-puzzlers-puzzle-3-negative-modeling/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Oracle BI EE 10.1.3.4.1 &#8211; Solutions &#8211; Puzzles 1 &amp; 2</title>
		<link>http://www.rittmanmead.com/2010/02/20/oracle-bi-ee-10-1-3-4-1-solutions-puzzles-1-2/</link>
		<comments>http://www.rittmanmead.com/2010/02/20/oracle-bi-ee-10-1-3-4-1-solutions-puzzles-1-2/#comments</comments>
		<pubDate>Sat, 20 Feb 2010 22:18:08 +0000</pubDate>
		<dc:creator>Venkatakrishnan J</dc:creator>
				<category><![CDATA[Oracle BI Suite EE]]></category>

		<guid isPermaLink="false">http://www.rittmanmead.com/?p=4388</guid>
		<description><![CDATA[Well its been almost a week since i blogged the first 2 puzzles in this puzzle series. I guess it is time for me to blog about the solutions. I have been amazed by the response so far. Its been really positive, so more puzzles to come during this week and the next.
Puzzle 1 Solution1:
Well, [...]]]></description>
			<content:encoded><![CDATA[<p>Well its been almost a week since i blogged the first 2 puzzles in this puzzle series. I guess it is time for me to blog about the solutions. I have been amazed by the response so far. Its been really positive, so more puzzles to come during this week and the next.</p>
<p><strong>Puzzle 1 Solution1:</strong></p>
<p>Well, this was a very easy puzzle and lot of people got it right. There are a couple of possible solutions. The most obvious and the easiest solution for this is to create a universal security group in the repository and then in the filters area assign the column level filters (vary the filter depending on the column) as shown below</p>
<p align="center"><img style="border-top-width: 0px; display: block; border-left-width: 0px; float: none; border-bottom-width: 0px; margin-left: auto; margin-right: auto; border-right-width: 0px" title="image" src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/image22.png" border="0" alt="image" width="376" height="315" /></p>
<p>I had to put this as a puzzle since this is one feature that i feel is the most under-utilized in BI EE. The drawback with this is the fact that this solution cannot be applied for Administrator user directly. But that is one user that is rarely exposed directly to end users.</p>
<p>People who got it right: Nicolas Gerard, Fiston and Harish Gopalan</p>
<p><strong>Puzzle 1 Solution 2:</strong></p>
<p>The other solution is to use a combination of multiple logical table sources in the dimension logical table. This is again another solution that directly affects the modeling we do in the Business Model and Mapping layer. Though it requires some work compared with solution 1, this can prove to be quite useful when the requirement is rephrased a bit like this. The report columns and the filters for the rephrased question is given below</p>
<p>“CHANNEL_CLASS &amp; AMOUNT_SOLD” – Filter required is CHANNEL_CLASS = &#8216;Puzzle1 Test&#8217;</p>
<p>“CHANNEL_DESC &amp; AMOUNT_SOLD” – Filter required is CHANNEL_DESC = &#8216;Puzzle Test2&#8242;</p>
<p>“CHANNEL_CLASS, CHANNEL_DESC &amp; AMOUNT_SOLD” – Filter required is CHANNEL_DESC = &#8216;Puzzle Test3&#8242; and CHANNEL_CLASS = &#8216;Puzzle Test4&#8242;</p>
<p>In such a case, solution 1 cannot be used. Only solution 2 can be used. Screenshots of the BMM is given below for this</p>
<p align="center"><img style="border-top-width: 0px; display: block; border-left-width: 0px; float: none; border-bottom-width: 0px; margin-left: auto; margin-right: auto; border-right-width: 0px" title="image" src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/image23.png" border="0" alt="image" width="238" height="246" /></p>
<p align="center"><img style="border-top-width: 0px; display: block; border-left-width: 0px; float: none; border-bottom-width: 0px; margin-left: auto; margin-right: auto; border-right-width: 0px" title="image" src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/image24.png" border="0" alt="image" width="492" height="241" /></p>
<p align="center"><img style="border-top-width: 0px; display: block; border-left-width: 0px; float: none; border-bottom-width: 0px; margin-left: auto; margin-right: auto; border-right-width: 0px" title="image" src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/image25.png" border="0" alt="image" width="504" height="220" /></p>
<p align="center"><img style="border-top-width: 0px; display: block; border-left-width: 0px; float: none; border-bottom-width: 0px; margin-left: auto; margin-right: auto; border-right-width: 0px" title="image" src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/image26.png" border="0" alt="image" width="504" height="216" /></p>
<p>Each LTS will have a separate filter. Ordering of the LTS is important to push the predicate filters properly.</p>
<p>People who got it right: Christian Berg, Nicholas Gerard and Ilmari Alto</p>
<p><strong>Puzzle 2:</strong></p>
<p>Puzzle 2 was a bit trickier as there are quite a few possible solutions. But lot of people answered it correctly.</p>
<p><strong>Puzzle 2 Solution 1:</strong></p>
<p>Everyone who answered this correctly, have used the solution 1. This basically alters 2 Database Features. They are given below. The idea is to basically enable both the options below</p>
<p>1. PERF_PREFER_MINIMAL_WITH_USAGE<br />
2. PERF_PREFER_INTERNAL_STITCH_JOIN</p>
<p align="center"><img style="border-top-width: 0px; display: block; border-left-width: 0px; float: none; border-bottom-width: 0px; margin-left: auto; margin-right: auto; border-right-width: 0px" title="image" src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/image27.png" border="0" alt="image" width="504" height="159" /></p>
<p>Or the other option is to just switch between 10gR1 and 11g/10gR2 as the database in the database type (this basically does the same thing as above – enables both the options above)</p>
<p align="center"><img style="border-top-width: 0px; display: block; border-left-width: 0px; float: none; border-bottom-width: 0px; margin-left: auto; margin-right: auto; border-right-width: 0px" title="Picture 2" src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/Picture2.png" border="0" alt="Picture 2" width="480" height="191" /></p>
<p>The drawback with this solution is, altering the database features can start affecting other queries. WITH clause provides such an advantage and sometimes even in a normal model(without conforming dimensions), the queries might get pushed to the BI Server. I generally do not recommend altering the database features unless its told by Oracle support. For example there have been instances in older versions of BI EE where PERF_PREFER_INTERNAL_STITCH_JOIN was asked to be enabled/disabled by Oracle support (due to bypass some 10gR1/10gR2 database bug). I generally prefer a solution where everything is always pushed to the database but if due to some reason we want to enable the BI Server join then we should be in a position to do that. For example, there have been instances(couple of bugs in BI EE which even exists in 10.1.3.4.1) where BI EE generates wrong sub-totals when pushed to the database. In many cases, this happens when conforming dimension joins happen at the database and we start getting nulls in the results. To check whether sub-total query is wrong we might want to push everything (even the conforming dimension join to the BI Server). The following 2 solutions basically demonstrate that. One other potential issue is, if you have more than one fact table from the same database then, even those conforming dimensions will be pushed to the BI Server layer. The other drawback is, i can still make BI EE to generate the database joins. Want to know how? Well, lets enable the above 2 settings in the physical database features. And lets create a report. In the Advanced SQL of the report, enter the below logical SQL</p>
<pre>SELECT A.CHANNEL_CLASS saw_0,
 A.QUANTITY_SOLD saw_1,
 B.UNIT_COST saw_2
 FROM
 (
 SELECT Channel.CHANNEL_CLASS,
 Sales.QUANTITY_SOLD
 FROM "SH - Conforming Dimension)
 A FULL OUTER JOIN
 (
 SELECT Channel.CHANNEL_CLASS,
 Costs.UNIT_COST
 FROM "SH - Conforming Dimension") B
 ON A.CHANNEL_CLASS = B.CHANNEL_CLASS
 ORDER BY saw_0</pre>
<p>If you look at the report now, you will notice that this join has actually been pushed into the database (even after disabling the WITH clause and STITCH join features)</p>
<p align="center"><img style="border-top-width: 0px; display: block; border-left-width: 0px; float: none; border-bottom-width: 0px; margin-left: auto; margin-right: auto; border-right-width: 0px" title="image" src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/image28.png" border="0" alt="image" width="481" height="315" /></p>
<p>But from a Puzzle standpoint, i was expecting any one of these solutions. So thanks for everyone who answered this correctly.</p>
<p>People who got it right: Vinod Jaganathan, Harish Gopalan, Somasekhar</p>
<p><strong>Puzzle 2 Solution 2:</strong></p>
<p>This involves adding more metadata to the repository and altering the BMM layer. But this provides you complete control on how the joins will be generated and you can be sure about the structure of the SQL Query. Basically the idea is to replicate the dimension and the fact table in 2 separate databases. For example, CHANNELS and SALES in one database &amp; CHANNELS and COSTS in the other database as shown below</p>
<p align="center"><img style="border-top-width: 0px; display: block; border-left-width: 0px; float: none; border-bottom-width: 0px; margin-left: auto; margin-right: auto; border-right-width: 0px" title="image" src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/image29.png" border="0" alt="image" width="183" height="166" /></p>
<p>And your BMM dimension will have 2 sources (same CHANNELS source coming from 2 different Physical Databases as shown below</p>
<p align="center"><img style="border-top-width: 0px; display: block; border-left-width: 0px; float: none; border-bottom-width: 0px; margin-left: auto; margin-right: auto; border-right-width: 0px" title="image" src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/image30.png" border="0" alt="image" width="215" height="313" /></p>
<p align="center"><img style="border-top-width: 0px; display: block; border-left-width: 0px; float: none; border-bottom-width: 0px; margin-left: auto; margin-right: auto; border-right-width: 0px" title="image" src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/image31.png" border="0" alt="image" width="504" height="290" /></p>
<p>If you think logically, when we model the same set of tables in different databases, from a BI EE standpoint, these 2 are completely different. So, if you think from a SQL standpoint, the only way we can enforce the conforming dimension join in the database is by using Database links. Since we do not have them here, only way BI EE can join them is by stitching them in its memory. Remember, if you create aliases within the same physical database, even then BI EE will force the SQL to the database as, from a BI EE perspective, all the tables still reside in the same database. The issue with this approach is, it will become very difficult to manage the BMM layer especially when you have a physical source like BI Applications. But this is something to keep in mind while doing any Repository Modeling.</p>
<p>People Who got it right: Scott Powell (was close in mentioning the alias solution but i give the benefit of the doubt as this was the only comment suggested that was in line with this solution)</p>
<p><strong>Puzzle 2 Solution 3:</strong></p>
<p>This is something that is used rarely. Now that 11g is around the corner where everything will be based on View Objects etc, it becomes even more important to consider this. Understanding Logical SQL is very important as in many cases we would be using BI Server and some other front end instead of presentation services. The solution is to use a logical SQL that basically combines both the facts in separate sub-queries. This is not a straight forward solution. This requires introducing just a single CHANNELS table in another physical database (instead of all the tables,including the facts as described in the above solution) and creating a dummy Presentation Subject area containing the table.</p>
<p align="center"><img style="border-top-width: 0px; display: block; border-left-width: 0px; float: none; border-bottom-width: 0px; margin-left: auto; margin-right: auto; border-right-width: 0px" title="image" src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/image32.png" border="0" alt="image" width="212" height="101" /></p>
<p>Then use logical SQL method to push the join to the BI Server layer (introduce the new presentation layer object Channels)</p>
<p>A simple logical SQL that i used is given below</p>
<pre>SELECT
 A.CHANNEL_CLASS saw_0, A.QUANTITY_SOLD saw_1, B.UNIT_COST saw_2
 FROM (
 SELECT
 Channel.CHANNEL_CLASS,
 Sales.QUANTITY_SOLD
 FROM "SH - Conforming Dimension"
 ) A FULL OUTER JOIN (
 SELECT
 Channel.CHANNEL_CLASS
 FROM
 "SH - Conforming Dimension#1"
 ) B ON A.CHANNEL_CLASS = B.CHANNEL_CLASS FULL OUTER JOIN (
 SELECT
 Channel.CHANNEL_CLASS,
 Costs.UNIT_COST
 FROM
 "SH - Conforming Dimension"
) C ON A.CHANNEL_CLASS = C.CHANNEL_CLASS
 ORDER BY saw_0</pre>
<p>As you see i basically fired 3 separate sub-queries and then joined them together in the logical SQL. When you enter this in Answers, you will notice that the join is pushed to the BI Server irrespective of how you have modeled the repository. A sub-query join in BI Server means a join at the BI Server layer.</p>
<p align="center"><img style="border-top-width: 0px; display: block; border-left-width: 0px; float: none; border-bottom-width: 0px; margin-left: auto; margin-right: auto; border-right-width: 0px" title="image" src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/image33.png" border="0" alt="image" width="504" height="195" /></p>
<p>If you look at the SQL, you will notice that there are 3 SQLs that get fired simultaneously and all of them are joined in the BI Server memory. Not exactly the solution i was looking for but this pushes the join to the BI Server layer. This technique has a lot of drawbacks as well like you lose the drills in the front-end etc. But i generally always like the logical SQL approach not for implementation but for visualizing how your BMM layer has to be structured.</p>
<p align="center"><img style="border-top-width: 0px; display: block; border-left-width: 0px; float: none; border-bottom-width: 0px; margin-left: auto; margin-right: auto; border-right-width: 0px" title="image" src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/image34.png" border="0" alt="image" width="422" height="315" /></p>
<p>There is one more solution as which also uses an extra new physical database table. But i will leave that for now as it more or less follows what i have discussed in the above 2.</p>
<p>There can be other possible solutions as well. If you can think of any feel free to add them in the comments. As i have said this before BI Server is one of the best tools out there that can effectively model most reporting scenarios. There can be more than one possible way of arriving at a solution. Of course, there will be advantages and disadvantages associated with each approach. The idea of the Puzzle series is to basically highlight as many solutions along with their drawbacks etc.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.rittmanmead.com/2010/02/20/oracle-bi-ee-10-1-3-4-1-solutions-puzzles-1-2/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Oracle BI EE 10.1.3.4.1 &#8211; Puzzlers &#8211; Puzzle 2</title>
		<link>http://www.rittmanmead.com/2010/02/14/oracle-bi-ee-10-1-3-4-1-puzzlers-puzzle-2/</link>
		<comments>http://www.rittmanmead.com/2010/02/14/oracle-bi-ee-10-1-3-4-1-puzzlers-puzzle-2/#comments</comments>
		<pubDate>Sun, 14 Feb 2010 17:26:35 +0000</pubDate>
		<dc:creator>Venkatakrishnan J</dc:creator>
				<category><![CDATA[Oracle BI Suite EE]]></category>

		<guid isPermaLink="false">http://www.rittmanmead.com/2010/02/14/oracle-bi-ee-10-1-3-4-1-puzzlers-puzzle-2/</guid>
		<description><![CDATA[I received some interesting replies through email as well as in the blog comments for the last Puzzle. I will blog about the solution for that and the solution for the 2nd puzzle together later this week. So far 3 people have answered and all are right which i will explain in my solution blog. [...]]]></description>
			<content:encoded><![CDATA[<p>I received some interesting replies through email as well as in the blog comments for the last Puzzle. I will blog about the solution for that and the solution for the 2nd puzzle together later this week. So far 3 people have answered and all are right which i will explain in my solution blog. Thanks to everyone who participated so far (Gerard Nico, Christian &amp; Harish Gopalan). It is still open and you can still post your solution, if you haven&#8217;t already, until i blog the solutions later this week. On to the second Puzzle of the week today.</p>
<p>This is a very common but an interesting scenario which sometimes can act as the key in determining the performance of BI EE especially when you have a big repository with a lot of users accessing it simultaneously. As you all know BI EE avoids fan traps through the concept of conforming dimensions. Since conforming dimensions are used quite frequently, it is imperative that BI EE generates the right performing SQL or rather we model the repository in such a way that BI EE generates the correct SQL queries. Today’s puzzle is </p>
<p><strong>“How do we push the conforming dimension join to the database and how do we push the conforming dimension join to the BI Server(how do we control this behavior)?”</strong></p>
<p>For example, if you consider the repository below, it is a very simple model with Channels Dimension joining to 2 fact tables COSTS and SALES. </p>
<p align="center"><img title="image" style="border-top-width: 0px; display: block; border-left-width: 0px; float: none; border-bottom-width: 0px; margin-left: auto; margin-right: auto; border-right-width: 0px" height="294" alt="image" src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/image17.png" width="200" border="0" /> </p>
<p>Now when someone creates a report containing both the measures and a dimension attribute, we want the conforming dimension join to be pushed into the database SQL itself as shown below</p>
<p align="center"><img title="image" style="border-top-width: 0px; display: block; border-left-width: 0px; float: none; border-bottom-width: 0px; margin-left: auto; margin-right: auto; border-right-width: 0px" height="138" alt="image" src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/image18.png" width="277" border="0" /> </p>
<p align="center"><img title="image" style="border-top-width: 0px; display: block; border-left-width: 0px; float: none; border-bottom-width: 0px; margin-left: auto; margin-right: auto; border-right-width: 0px" height="189" alt="image" src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/image19.png" width="504" border="0" /></p>
<pre>WITH
SAWITH0 AS (select sum(T1929.QUANTITY_SOLD) as c1,
     T1776.CHANNEL_DESC as c2
from
     CHANNELS T1776,
     SALES T1929
where  ( T1776.CHANNEL_ID = T1929.CHANNEL_ID )
group by T1776.CHANNEL_DESC),
SAWITH1 AS (select sum(T1784.UNIT_COST) as c1,
     T1776.CHANNEL_DESC as c2
from
     CHANNELS T1776,
     COSTS T1784
where  ( T1776.CHANNEL_ID = T1784.CHANNEL_ID )
group by T1776.CHANNEL_DESC)
select distinct case  when SAWITH1.c2 is not null then SAWITH1.c2 when SAWITH0.c2 is not null then SAWITH0.c2 end  as c1,
     SAWITH0.c1 as c2,
     SAWITH1.c1 as c3
from
     SAWITH0 full outer join SAWITH1 On SAWITH0.c2 = SAWITH1.c2
order by c1</pre>
<p>As you see in the above case, the query was completely fired back to the database. You can confirm that this is a conforming dimension join by looking at the full outer join clause and the entire SQL. Remember, it is not necessary in your case to generate the exact same SQL listed above. It is enough if you can demonstrate how the entire query can be pushed as a single query to the database i.e a modified form of the above SQL is acceptable as well. </p>
<p>The second part of the puzzle is how do we model the same repository above to achieve the conforming dimension join to happen in the BI Server memory as shown below</p>
<p align="center"><img title="image" style="border-top-width: 0px; display: block; border-left-width: 0px; float: none; border-bottom-width: 0px; margin-left: auto; margin-right: auto; border-right-width: 0px" height="315" alt="image" src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/image20.png" width="484" border="0" /> </p>
<p align="center"><img title="image" style="border-top-width: 0px; display: block; border-left-width: 0px; float: none; border-bottom-width: 0px; margin-left: auto; margin-right: auto; border-right-width: 0px" height="195" alt="image" src="http://www.rittmanmead.com/wp2/wp-content/uploads/2010/02/image21.png" width="504" border="0" /> </p>
<p>You can see that the conforming dimension join in this case happens in the BI Server layer. BI Server basically fires 2 queries separately and stitches them together in its own memory. In what ways can we control this behavior? There are more than one solution for this but again this is a little bit trickier than the Puzzle 1 i gave yesterday. Another important point to note is, in both the above cases, the output of the report should not change i.e both database based join and BI Server based join should produce the same results.</p>
<p>Some more interesting puzzles lined up for next week. Stay Tuned!!!</p>
]]></content:encoded>
			<wfw:commentRss>http://www.rittmanmead.com/2010/02/14/oracle-bi-ee-10-1-3-4-1-puzzlers-puzzle-2/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
	</channel>
</rss>
