<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:blogger='http://schemas.google.com/blogger/2008' xmlns:georss='http://www.georss.org/georss' xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-2381314077784322587</id><updated>2026-04-16T23:24:51.347-07:00</updated><category term="business intelligence"/><category term="javascript"/><category term="sails"/><category term="SQL Server 2008"/><category term="mongodb"/><category term="node"/><category term="node.js"/><category term="sailsjs"/><category term=".net"/><category term="Pentaho Mondrian"/><category term="SQL Server Analysis Services 2008"/><category term="bi"/><category term="bi comparison"/><category term="excel 2003"/><category term="grunt"/><category term="js"/><category term="mocha"/><category term="mongo"/><category term="sails.js"/><category term="waterline"/><category term="2010 honda insight"/><category term="65536 limitation"/><category term="65536 row limitation"/><category term="D-Link wireless router"/><category term="SQL Server 2005"/><category term="Windows 7"/><category term="android"/><category term="angularjs"/><category term="athens"/><category term="automated build"/><category term="basic"/><category term="bism"/><category term="chrome extensions"/><category term="cliff lee"/><category term="code coverage"/><category term="coding"/><category term="column headings"/><category term="comparison"/><category term="continuous integration"/><category term="crashing"/><category term="cruise control .net"/><category term="csv"/><category term="csv parsing"/><category term="database"/><category term="database results"/><category term="dependency injection"/><category term="donate"/><category term="excel"/><category term="excel interop"/><category term="existing mysql databases"/><category term="express"/><category term="fast csv reader"/><category term="first programming language"/><category term="fuel efficiency"/><category term="google android"/><category term="google chrome"/><category term="greece"/><category term="greek culture"/><category term="honda"/><category term="hybrid"/><category term="ibm thinkpad"/><category term="inversion of control"/><category term="istanbul"/><category term="java"/><category term="json"/><category term="keyboard and mouse"/><category term="laptop keyboards"/><category term="lenovo ideapad"/><category term="mean stack"/><category term="memory efficient"/><category term="multiple processes"/><category term="multiple projects"/><category term="mysql"/><category term="nodejs"/><category term="npm"/><category term="orlando code camp 2013"/><category term="page breaks"/><category term="pentaho"/><category term="philadelphia phillies"/><category term="programming tasks"/><category term="query"/><category term="rebooting"/><category term="rosetta code"/><category term="s2000"/><category term="shrinking transaction log"/><category term="split tunnel"/><category term="sql"/><category term="sql server 2012"/><category term="sql server reporting services 2008"/><category term="sqliteopenhelper"/><category term="ssas"/><category term="ssrs"/><category term="support"/><category term="synergy"/><category term="tabular"/><category term="tests"/><category term="transaction log"/><category term="unit test"/><category term="unity"/><category term="vb .net"/><category term="visual sourcesafe 6.0"/><category term="vpn"/><category term="vss"/><category term="wcf"/><category term="web services"/><title type='text'>geekstentialism</title><subtitle type='html'>_.map([&#39;.Net&#39;, &#39;Java&#39;, &#39;JavaScript&#39;, &#39;SQL&#39;, &#39;NoSQL&#39;, &#39;BI&#39;], &#xa;function(tech) &#xa;  { &#xa;    return ericNograles(tech);&#xa;  }&#xa;);</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://geekstentialism.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2381314077784322587/posts/default?redirect=false'/><link rel='alternate' type='text/html' href='http://geekstentialism.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><link rel='next' type='application/atom+xml' href='http://www.blogger.com/feeds/2381314077784322587/posts/default?start-index=26&amp;max-results=25&amp;redirect=false'/><author><name>Eric Nograles</name><uri>http://www.blogger.com/profile/02198416401185614213</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJ6RXtY4XZkDuwsIOoO4bKx5gDQMLkcRQIQvtomOIeGbXKFJ1pTzAe7PS1HyPIdW4Rb2Pc5ZljcijJCLlORJpe1c99VfDOUTpn5A06e4WU0yTboyJiAC0eJ8PT0Itv1xc/s1600/*'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>26</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-2381314077784322587.post-4710801003445113816</id><published>2014-07-04T10:53:00.003-07:00</published><updated>2014-07-04T10:54:16.537-07:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="code coverage"/><category scheme="http://www.blogger.com/atom/ns#" term="grunt"/><category scheme="http://www.blogger.com/atom/ns#" term="istanbul"/><category scheme="http://www.blogger.com/atom/ns#" term="mocha"/><category scheme="http://www.blogger.com/atom/ns#" term="node"/><category scheme="http://www.blogger.com/atom/ns#" term="nodejs"/><category scheme="http://www.blogger.com/atom/ns#" term="sails"/><category scheme="http://www.blogger.com/atom/ns#" term="sailsjs"/><title type='text'>New Feature and Refactoring Freedom: Determining Code Coverage in Sails.js</title><content type='html'>&lt;div dir=&quot;ltr&quot; style=&quot;text-align: left;&quot; trbidi=&quot;on&quot;&gt;
Happy Independence Day! &amp;nbsp;To celebrate the USA&#39;s birthday, I offer a quick post on exercising your freedom as a developer to implement new features or refactor code without flying blind or worrying what you may have missed or broken. &amp;nbsp;I&#39;m of course talking about some code coverage in Sails.js.&lt;br /&gt;
&lt;br /&gt;
In my prior post on&amp;nbsp;&lt;a href=&quot;http://geekstentialism.blogspot.com/2014/06/growing-up-and-growing-large-modern_20.html&quot;&gt;Web API development in Sails.js&lt;/a&gt;, we utilized Grunt and Mocha as the primary tools for our unit tests. &amp;nbsp;However, what we did not review was how to target what you missed in your unit tests. &amp;nbsp;After all, what good are running unit tests if you don&#39;t know where your gaps lie?&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;a href=&quot;http://gotwarlost.github.io/istanbul/&quot;&gt;Istanbul&lt;/a&gt; (&lt;a href=&quot;http://www.youtube.com/watch?v=vsQrKZcYtqg&quot;&gt;not Constantinople&lt;/a&gt;) to the rescue! &amp;nbsp;Istanbul is a fantastic JavaScript library which produces all manners of code coverage report formats. &amp;nbsp;For the purpose of this exercise, we will simply have it generate a /coverage folder in our root with nicely formatted HTML showing us where our code coverage gaps are.&lt;br /&gt;
&lt;br /&gt;
&lt;a href=&quot;https://github.com/ericnograles/agilemoviedb/tree/master/Server&quot;&gt;Referring back to our sample Sails.js project&lt;/a&gt;, the AgileMovieDB API, we&#39;ll need to first go into the command line and type in these commands to install Istanbul and the grunt-mocha-istanbul Grunt task library.&lt;br /&gt;
&lt;br /&gt;
&lt;ul style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;&lt;b&gt;sudo npm install -g istanbul&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;sudo npm install grunt-mocha-istanbul --save-dev&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
The second bit will save a dev dependency of grunt-mocha-istanbul to your package.json. &amp;nbsp;Fantastic.&lt;br /&gt;
&lt;br /&gt;
Now, we need to do a little bit of Gruntfile.js hacking.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Gruntfile.js: mocha_istanbul task setup in initConfig&lt;/b&gt;&lt;br /&gt;
&lt;pre class=&quot;brush: jscript&quot;&gt;mocha_istanbul: {
            coverage: {
                src: &#39;tests&#39;, // the folder, not the files,
                options: {
                    mask: &#39;**/*.spec.js&#39;
                }
            },
            coveralls: {
                src: &#39;tests&#39;, // the folder, not the files
                options: {
                    coverage:true,
                    check: {
                        lines: 75,
                        statements: 75
                    },
                    root: &#39;./api&#39;, // define where the cover task should consider the root of libraries that are covered by tests
                    reportFormats: [&#39;html&#39;]
                }
            }
        },
&lt;/pre&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Gruntfile.js: Register a Coverage Task&lt;/b&gt;&lt;br /&gt;
&lt;pre class=&quot;brush: jscript&quot;&gt;// Code Coverage with Mocha and Istanbul (not Constantinople)
    grunt.registerTask(&#39;coverage&#39;, [&#39;mocha_istanbul:coverage&#39;]);
&lt;/pre&gt;
&lt;br /&gt;
&lt;br /&gt;
Congratulations, folks, we&#39;ve got code coverage in our Sails.js application. &amp;nbsp;How do we run it? &amp;nbsp;Fire up the command line and type in this command: &lt;b&gt;grunt coverage&lt;/b&gt;.&lt;br /&gt;
&lt;br /&gt;
You&#39;ll notice a new /coverage folder in the root of the /Server folder. &amp;nbsp;Expand it, then expand the lcov-report folder. &amp;nbsp;Open up index.html in your favorite browser, and you should see the following output:&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi6IJcFOoXg8vNoCHdAnz1SAUtBj4ARX76-DfAuYkrEt0A_avVfaL4PzeLqEpkFLIquW4BtNkVLAUNc0H7m1yBQGtomHtUYd0qDTtagaRYZZOc7sBHPWUzPOXdVDVRkRI_IIAJLEkksbgl_/s1600/istanbul-codecoverage.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi6IJcFOoXg8vNoCHdAnz1SAUtBj4ARX76-DfAuYkrEt0A_avVfaL4PzeLqEpkFLIquW4BtNkVLAUNc0H7m1yBQGtomHtUYd0qDTtagaRYZZOc7sBHPWUzPOXdVDVRkRI_IIAJLEkksbgl_/s1600/istanbul-codecoverage.png&quot; height=&quot;236&quot; width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
The links for each of the folders are hot, so let&#39;s examine one of our managers/ that&#39;s giving us some code coverage issues.&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-Xh9QDs9j5QSoVepsIBeptFWXZnrq3ZkZ8uFBuraVm587O1KuBTfPFh_2EIMHTQiPKYgIVbCkcLFPPyQFoyCwefNZi8S2I5evI6kZnuNCxd0Co_dBEq3l_DjzzTfoknjuAYM0H79ezcka/s1600/SearchManager+Code+Coverage.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-Xh9QDs9j5QSoVepsIBeptFWXZnrq3ZkZ8uFBuraVm587O1KuBTfPFh_2EIMHTQiPKYgIVbCkcLFPPyQFoyCwefNZi8S2I5evI6kZnuNCxd0Co_dBEq3l_DjzzTfoknjuAYM0H79ezcka/s1600/SearchManager+Code+Coverage.png&quot; height=&quot;237&quot; width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
As you can see, Istanbul gives us some fantastic qualitative and quantitative measures. &amp;nbsp;What&#39;s highlighted in red is what was not tested, and the number to the right of the line number indicates the # of times that line of code was hit. &amp;nbsp;Brilliant.&lt;br /&gt;
&lt;br /&gt;
Using Istanbul code coverage with Mocha unit tests will enable us the freedom to implement features and refactor our Sails.js code without the worry of &quot;flying blind&quot; as our software evolves. &amp;nbsp;I hope this post proves useful in your continuing quest for quality MEAN stack development using Sails.js for the Web API!&lt;br /&gt;
&lt;br /&gt;&lt;/div&gt;
</content><link rel='replies' type='application/atom+xml' href='http://geekstentialism.blogspot.com/feeds/4710801003445113816/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/2381314077784322587/4710801003445113816' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2381314077784322587/posts/default/4710801003445113816'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2381314077784322587/posts/default/4710801003445113816'/><link rel='alternate' type='text/html' href='http://geekstentialism.blogspot.com/2014/07/new-feature-and-refactoring-freedom.html' title='New Feature and Refactoring Freedom: Determining Code Coverage in Sails.js'/><author><name>Eric Nograles</name><uri>http://www.blogger.com/profile/02198416401185614213</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJ6RXtY4XZkDuwsIOoO4bKx5gDQMLkcRQIQvtomOIeGbXKFJ1pTzAe7PS1HyPIdW4Rb2Pc5ZljcijJCLlORJpe1c99VfDOUTpn5A06e4WU0yTboyJiAC0eJ8PT0Itv1xc/s1600/*'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi6IJcFOoXg8vNoCHdAnz1SAUtBj4ARX76-DfAuYkrEt0A_avVfaL4PzeLqEpkFLIquW4BtNkVLAUNc0H7m1yBQGtomHtUYd0qDTtagaRYZZOc7sBHPWUzPOXdVDVRkRI_IIAJLEkksbgl_/s72-c/istanbul-codecoverage.png" height="72" width="72"/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2381314077784322587.post-1058584286503219189</id><published>2014-06-30T17:55:00.002-07:00</published><updated>2014-06-30T17:56:25.347-07:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="javascript"/><category scheme="http://www.blogger.com/atom/ns#" term="js"/><category scheme="http://www.blogger.com/atom/ns#" term="mocha"/><category scheme="http://www.blogger.com/atom/ns#" term="node"/><category scheme="http://www.blogger.com/atom/ns#" term="node.js"/><category scheme="http://www.blogger.com/atom/ns#" term="sails"/><category scheme="http://www.blogger.com/atom/ns#" term="sails.js"/><category scheme="http://www.blogger.com/atom/ns#" term="tests"/><category scheme="http://www.blogger.com/atom/ns#" term="unit test"/><category scheme="http://www.blogger.com/atom/ns#" term="waterline"/><title type='text'>How to Unit Test a Sails.js Model Without Lifting Sails</title><content type='html'>&lt;div dir=&quot;ltr&quot; style=&quot;text-align: left;&quot; trbidi=&quot;on&quot;&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;
The Problem&lt;/h3&gt;
&lt;div&gt;
As a quick follow-up to &lt;a href=&quot;http://geekstentialism.blogspot.com/2014/06/growing-up-and-growing-large-modern_20.html&quot;&gt;the previous post about creating a traditional MVC-style Web API using Sails.js&lt;/a&gt;, I did get some questions around how to test models in Sails, especially those that have instance methods and override their Waterline lifecycle callbacks, such as afterCreate and afterUpdate. &amp;nbsp;This post is for you guys!&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;
The Approach&lt;/h3&gt;
&lt;div&gt;
First, we need to ensure that Waterline is installed as a dev dependency. &amp;nbsp;This is accomplished by going to the command line of your Sails application&#39;s root and typing in &lt;b&gt;sudo npm install waterline --save-dev &lt;/b&gt;(or just &lt;b&gt;npm install waterline --save-dev &lt;/b&gt;for Windows folks). &lt;br /&gt;
&lt;br /&gt;
Note that this approach is confirmed to work in Sails 0.9.16, and the Waterline approach used here may not jive with 0.10.x, as some readers have reported.&lt;br /&gt;
&lt;br /&gt;
Using the same exact unit testing framework (Mocha) as the last article, let&#39;s assume we fortified our Movie model with an &quot;unbiasedCriticism&quot; instance method and some lifecycle callbacks using Socket.io, defined as follows:&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;
&lt;b&gt;/api/constants/SocketIOConstants.js&lt;/b&gt;&lt;br /&gt;
&lt;pre class=&quot;brush: jscript&quot;&gt;/**
 * Events for Socket.io pub/sub
 * @type {string}
 */
exports.EVENT_MOVIE_CREATED = &#39;movie:created&#39;;
exports.EVENT_MOVIE_UPDATED = &#39;movie:updated&#39;;
&lt;/pre&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;/api/models/Movie.js&lt;/b&gt;&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;brush: jscript&quot;&gt;var SocketIOConstants = require(&#39;../constants/SocketIOConstants&#39;);

/**
 * The Movie model
 */
var Movie = {
    tableName: &#39;Movie&#39;,
    attributes: {
        movieId: {
            type: &#39;INTEGER&#39;,
            required: true,
            unique: true
        },
        name: {
            type: &#39;STRING&#39;,
            required: true
        },
        releaseDate: {
            type: &#39;DATE&#39;,
            required: true
        },
        rating: {
            type: &#39;STRING&#39;,
            required: true
        },
        grossEarnings: &#39;FLOAT&#39;,

        // Instance methods
        unbiasedCriticism: function() {
            if(this.name.toLowerCase().indexOf(&#39;godfather&#39;) &amp;gt; -1) {
                return &#39;PHENOMENAL!&#39;;
            }
            else {
                return &#39;It stinks!&#39;;
            }
        }
    },

    // Lifecycle callbacks
    afterCreate: function(newRecord, next) {
        sails.io.sockets.emit(SocketIOConstants.EVENT_MOVIE_CREATED, newRecord);
        next();
    },

    afterUpdate: function(updatedRecord, next) {
        sails.io.sockets.emit(SocketIOConstants.EVENT_MOVIE_UPDATED, updatedRecord);
        next();
    }
};

module.exports = Movie;
&lt;/pre&gt;
&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;
Again, as a review from the last post, a unit test is one that can stand alone and tests specific functionality only on the module/component you are referencing.  Any external dependencies should be mocked in some form.  Now, for our unit test!
&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;/tests/models/Movie.spec.js&lt;/b&gt;&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;brush: jscript&quot;&gt;var MovieModel = require(&#39;../../api/models/Movie&#39;),
    assert = require(&#39;assert&#39;),
    _ = require(&#39;underscore&#39;),
    Waterline = require(&#39;waterline&#39;),
    SocketIOConstants = require(&#39;../../api/constants/SocketIOConstants&#39;),
    diskAdapter = require(&#39;sails-disk&#39;); // FYI, sails-memory doesn&#39;t seem to play well in a mock situation

var Movie, movie, movieCollection, sails;
var socketEvents = [];


/**
 * Unit tests for the Movie Model.  Intended only to test attribute methods, not Sails or Waterline functionality.
 */
describe(&#39;The Movie model&#39;, function () {

    /**
     * Create a &quot;sham&quot; model from sails-disk
     */
    before(function(done) {
        // Create a sails stub for socket.io
        sails = {
            io: {
                sockets: {
                    emit: function(event, data) {
                        socketEvents.push(event);
                        return;
                    }
                }
            }
        }
        global.sails = sails;

        // Create a model using sails-disk
        MovieModel.adapter = &#39;disk&#39;;
        Movie = Waterline.Collection.extend(MovieModel);
        new Movie({ adapters: { disk: diskAdapter }}, function(err, collection) {
            if (err) {
                done(err);
            }
            else {
                movieCollection = collection;
                collection.create({ movieId: &#39;1&#39;, name: &#39;The Godfather&#39;, releaseDate: new Date(), rating: &#39;R&#39;})
                    .done(function(err, mockMovie) {
                        if (err) {
                            done(err);
                        }
                        else {
                            movie = mockMovie;
                            done();
                        }
                    });
            }
        });
    });

    describe(&#39;attribute methods&#39;, function () {
        it(&#39;should produce unbiased criticism of the Godfather&#39;, function () {
            var criticism = movie.unbiasedCriticism();
            assert(criticism === &#39;PHENOMENAL!&#39;);
        });

        it(&#39;should have emitted a movie created event on the socket&#39;, function() {
            assert(_.contains(socketEvents, SocketIOConstants.EVENT_MOVIE_CREATED));
        });
    });

    /**
     * Delete everything from sails-disk.  A clean HD is a happy HD.
     */
    after(function(done) {
        movieCollection.destroy().done(function(err) {
            if (err) {
                done(err);
            }
            else {
                done();
            }
        });
    });
});
&lt;/pre&gt;
&lt;/div&gt;
&lt;br /&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;
What The Heck Did We Do?&lt;/h3&gt;
&lt;div&gt;
Our Movie model has two lifecycle callbacks (afterCreate and afterUpdate) and one instance method (unbiasedCriticism). &amp;nbsp;In order to ensure that the model&#39;s methods behave as we expect, we need to spin up a &quot;sham&quot; Waterline. &amp;nbsp;In this case, we are spinning up Waterline using sails-disk. &amp;nbsp;We then &quot;create&quot; a sample object on which we can make assertions. &amp;nbsp;Finally, we need to create a stub sails literal that we can throw in global that has io.sockets.emit defined. &amp;nbsp;This all happens in the asynchronous &quot;before all&quot; hook of Movie.spec.js.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Once that&#39;s all in place, assertions should be elementary. &amp;nbsp;The unbiasedCriticism method indeed shows that the function brings back &#39;PHENOMENAL!&#39; and that our stubbed sails.io.sockets fired off the Movie Created event we established in SocketIOConstants.js. &amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
The &quot;after all&quot; hook simply nukes the object we created on disk so we don&#39;t waste any of our disk space. &amp;nbsp;Easy peasy lemon squeezy.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;
Other Alternatives&lt;/h3&gt;
&lt;div&gt;
&lt;a href=&quot;https://github.com/fdvj/wolfpack&quot;&gt;Fernando De Vega created a library called Wolfpack&lt;/a&gt; that does the stubbing work for you, like we did above with Waterline. &amp;nbsp;I haven&#39;t messed around with it personally, but it certainly looks promising! &amp;nbsp;I simply wanted something that I could fully control and understand, hence the approach above.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
I hope this helps your unit testing efforts as you continue to Sail away!&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;/div&gt;
</content><link rel='replies' type='application/atom+xml' href='http://geekstentialism.blogspot.com/feeds/1058584286503219189/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/2381314077784322587/1058584286503219189' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2381314077784322587/posts/default/1058584286503219189'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2381314077784322587/posts/default/1058584286503219189'/><link rel='alternate' type='text/html' href='http://geekstentialism.blogspot.com/2014/06/how-to-unit-test-sailsjs-model-without.html' title='How to Unit Test a Sails.js Model Without Lifting Sails'/><author><name>Eric Nograles</name><uri>http://www.blogger.com/profile/02198416401185614213</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJ6RXtY4XZkDuwsIOoO4bKx5gDQMLkcRQIQvtomOIeGbXKFJ1pTzAe7PS1HyPIdW4Rb2Pc5ZljcijJCLlORJpe1c99VfDOUTpn5A06e4WU0yTboyJiAC0eJ8PT0Itv1xc/s1600/*'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2381314077784322587.post-3649901628662030752</id><published>2014-06-20T05:57:00.000-07:00</published><updated>2014-06-22T09:57:31.898-07:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="grunt"/><category scheme="http://www.blogger.com/atom/ns#" term="javascript"/><category scheme="http://www.blogger.com/atom/ns#" term="js"/><category scheme="http://www.blogger.com/atom/ns#" term="mongo"/><category scheme="http://www.blogger.com/atom/ns#" term="mongodb"/><category scheme="http://www.blogger.com/atom/ns#" term="npm"/><category scheme="http://www.blogger.com/atom/ns#" term="sails"/><category scheme="http://www.blogger.com/atom/ns#" term="sails.js"/><title type='text'>Growing Up and Growing Large: Modern JavaScript Web Development Using Sails.js and AngularJS (Part 2 of 3)</title><content type='html'>&lt;div dir=&quot;ltr&quot; style=&quot;text-align: left;&quot; trbidi=&quot;on&quot;&gt;
&lt;h3&gt;
Sails.js: A Server-side Solution&lt;/h3&gt;
&lt;div&gt;
One of the early criticisms (and also strengths) of JavaScript, and open source architectures in general, was the conundrum of &quot;too many options&quot;. &amp;nbsp;After all, it was scientifically proven that people (yes, even software developers) prefer&amp;nbsp;&lt;a href=&quot;http://www.forbes.com/sites/oracle/2013/11/05/scientific-evidence-proves-larry-ellison-is-right-about-engineered-systems/&quot; target=&quot;_blank&quot;&gt;solutions over individual components.&lt;/a&gt;&amp;nbsp; Part of growing up is learning from past mistakes, and the JavaScript community has responded in kind, particularly the folks at&amp;nbsp;&lt;a href=&quot;http://sailsjs.org/#!&quot; target=&quot;_blank&quot;&gt;Giant Squid with Sails.js.&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Sails.js attempts to reel in the noise in choosing components for a server-side JavaScript framework. &amp;nbsp;It is a proper MVC framework, inspired by Ruby on Rails, that gives a developer everything you could possibly need: an MVC pattern, ORM, multiple adapters for data stores, Socket.io support, intelligent routing, and much more, all in a carefully curated solution so that a developer can focus on making great software products versus worrying about getting components to play nicely with each other.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;
Great, So What Are We Building?&lt;/h3&gt;
&lt;div&gt;
For this exercise, we&#39;re going to build a simple movie database called the AgileMovieDB. &amp;nbsp;Let&#39;s assume a directory structure of /AgileMovieDB/Server which will house the Sails.js source code and /AgileMovieDB/Web which will house the AngularJS source code.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
This will be a simple application where a user can type in the name (or parts of the name) of a movie or actor on the Angular UI, and our Sails.js Server API spits back search results.&lt;br /&gt;
&lt;br /&gt;
For the &quot;get on with it&quot; crowd, direct your Git to&amp;nbsp;&lt;a href=&quot;https://github.com/ericnograles/agilemoviedb&quot;&gt;the GitHub Repo&lt;/a&gt; &amp;nbsp;:)&lt;/div&gt;
&lt;br /&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;
Prerequisites&lt;/h3&gt;
&lt;/div&gt;
&lt;div&gt;
There&#39;s a few pieces of software you ought to install first before we begin this exercise:&lt;br /&gt;
&lt;br /&gt;
&lt;ol style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;&lt;a href=&quot;http://nodejs.org/&quot;&gt;Node.js&lt;/a&gt;: Server-side JavaScript! &amp;nbsp;For *nix users, make sure to also install npm, Node.js&#39; equivalent of NuGet or Maven.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.mongodb.org/downloads&quot;&gt;MongoDB&lt;/a&gt;: A NoSQL document store which will act as our persistence layer&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.jetbrains.com/webstorm/download/&quot;&gt;WebStorm&lt;/a&gt;: Quite simply, the best IDE for JavaScript development, in my humble opinon&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://plugins.jetbrains.com/plugin/7141?pr=&quot;&gt;Mongo Plugin for WebStorm&lt;/a&gt;: Allows you to browse MongoDB servers right in WebStorm&lt;/li&gt;
&lt;li&gt;(Windows Only) &lt;a href=&quot;http://www.mongovue.com/downloads/&quot;&gt;MongoVue&lt;/a&gt;: A GUI for Windows to browse through Mongo databases&lt;/li&gt;
&lt;/ol&gt;
&lt;br /&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;h3&gt;
Time to Set Sail&lt;/h3&gt;
&lt;div&gt;
So, let&#39;s get started. &amp;nbsp;While Sails.js could stand alone as a &quot;pure&quot; server-side MVC application, complete with support for template engines such as Jade, we are simply using it as a Server API that backs an AngularJS front-end. &amp;nbsp;So, in other words, this application will serve as the RESTful services that act as the gatekeeper to your domain objects, as well as house the domain objects themselves.&lt;br /&gt;
&lt;br /&gt;
Getting a skeleton Sails application called Server is as easy as this:&lt;br /&gt;
&lt;br /&gt;
&lt;ol style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;In a terminal or cmd window in the aforementioned AgileMovieDB folder, type&lt;b&gt; sudo npm -g install sails&lt;/b&gt; (or simply&lt;b&gt; npm -g install sails&lt;/b&gt; for Windows)&lt;/li&gt;
&lt;li&gt;Type in &lt;b&gt;sails new Server&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;Type in &lt;b&gt;cd Server&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;Type in &lt;b&gt;sails lift&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;Open a web browser and go to http://localhost:1337&lt;/li&gt;
&lt;li&gt;Victory dance&lt;/li&gt;
&lt;/ol&gt;
Congratulations, you have your very first Sails.js application!&lt;br /&gt;
&lt;br /&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;
Understanding the Overall Sails.js Architecture&lt;/h3&gt;
&lt;/div&gt;
&lt;div&gt;
When you open the /Server directory in WebStorm, you&#39;re going to see the basic Sails.js application structure. &amp;nbsp;It helps to understand what exactly is under the hood before we start hacking away.&lt;/div&gt;
&lt;div&gt;
&lt;ul style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;&lt;b&gt;/api&lt;/b&gt;: 99% of your coding will be done here. &amp;nbsp;This folder contains separations of the MVC pattern, shared services, adapters, and models.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;/assets&lt;/b&gt;: If we were building a classic MVC web application, this is where your UI/UX artifacts would live -- CSS/LESS files, image files, and the like&lt;/li&gt;
&lt;li&gt;&lt;b&gt;/config&lt;/b&gt;: Under-the-hood configurable items for Sails.js, such as what database to connect to, CORS policies, authentication policies, HTTP error handlers, etc&lt;/li&gt;
&lt;li&gt;&lt;b&gt;/node_modules&lt;/b&gt;: Being a Node.js application with library dependencies managed by npm, this is where said library dependencies will live.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;/views&lt;/b&gt;: Again, if we were building a classic MVC web application, this is where your HTML templates would live. &amp;nbsp;Out of the box, Sails.js supports Jade (as denoted by the *.ejs files)&lt;/li&gt;
&lt;/ul&gt;
You&#39;ll also notice a few files created by Sails on the root.&lt;/div&gt;
&lt;div&gt;
&lt;ul style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;&lt;b&gt;app.js&lt;/b&gt;: Instead of &quot;sails lift&quot;, you may optionally run this using Node.js. &amp;nbsp;When developing in WebStorm, this would be your entry point for debugging.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;gruntfile.js: &lt;/b&gt;The main file for your &lt;a href=&quot;http://gruntjs.com/&quot;&gt;Grunt&lt;/a&gt; task runner. &amp;nbsp;In JavaScript development, these task runners are your best friends, helping immensely with build automation, running unit tests, and the like&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;
&lt;/h3&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;
Got It. &amp;nbsp;So, Let&#39;s Talk About this API Folder&lt;/h3&gt;
&lt;/div&gt;
&lt;div&gt;
Naturally, there&#39;s a separation of concerns when developing a Sails application. &amp;nbsp;It should come as no surprise that this follows the tried and true Model-View-Controller (MVC) pattern. &amp;nbsp;Let&#39;s take a look at each subfolder in /api and understand what goes where.&lt;/div&gt;
&lt;div&gt;
&lt;ul style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;&lt;b&gt;/adapters&lt;/b&gt;: If you must create a custom adapter that Sails&#39; built-in ORM, Waterline, needs to use, your custom adapter goes in here and gets registered in global. &amp;nbsp;9 times out of 10, you won&#39;t need to, but &lt;a href=&quot;http://sailsjs.org/#!documentation/custom-adapters&quot;&gt;there is documentation&lt;/a&gt; for any edge cases.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;/controllers&lt;/b&gt;: Self-explanatory -- your controllers go here. &amp;nbsp;Up-front, they are code-generated using the sails command, which we will see shortly.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;/models&lt;/b&gt;: Self-explanatory -- your domain models go here, annotated in a fashion understood by Waterline, which we will see shortly. &amp;nbsp;Any Model object gets thrown into global.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;/policies&lt;/b&gt;: This folder houses any custom access control code. &amp;nbsp;In short, they enforce who can and can&#39;t access controllers. &amp;nbsp;We won&#39;t get into this level of complexity for this series, but &lt;a href=&quot;http://sailsjs.org/#!documentation/policies&quot;&gt;the documentation&lt;/a&gt; has an excellent overview.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;/services&lt;/b&gt;: This folder houses any application-wide &quot;services&quot; in Sails parlance. &amp;nbsp;These services could be, say, Repositories that interact with Waterline and MongoDB, or services in charge of pulling down and transforming JSON&#39;s from external API&#39;s. &amp;nbsp;Any file created here gets thrown into global.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;
&lt;/h3&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;
10-4, Ghost Rider. &amp;nbsp;Let&#39;s Sort Out this Domain.&lt;/h3&gt;
&lt;/div&gt;
&lt;div&gt;
Now that we have a lay of the land in Sails.js, we can begin development. &amp;nbsp;Let&#39;s attack this with a bottom-up approach, being that we have a well-defined (and simple) UI interaction and a well-understood domain. &amp;nbsp;We&#39;re going to track Movies and people involved with Movies. &amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Being that we&#39;re using a NoSQL data store, we &lt;i&gt;could&lt;/i&gt;&amp;nbsp;just model one Movie table and domain object, but let&#39;s make it a little relational to ease us normalization junkies into this brave, new, schema-less world. &amp;nbsp;So, that would involve a Movie entity, a Person entity, and a many-to-many relationship between both via an intermediary entity called MoviePerson. &amp;nbsp;(a Person can be multiple characters in many movies and also could be multiple roles in the crew, i.e. Executive Producer and Director...and movies contain many people playing multiple roles in the cast or crew).&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
So, with that in mind, we have three models to build in /api/models: Movie, Person, and MoviePerson.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Let&#39;s annotate these objects in the format understood by Waterline.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
&lt;b&gt;Movie.js&lt;/b&gt;&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;brush: jscript&quot;&gt;
/**
 * The Movie model
 */
var Movie = {
    tableName: &#39;Movie&#39;,
    attributes: {
        movieId: {
            type: &#39;INTEGER&#39;,
            required: true,
            unique: true
        },
        name: {
            type: &#39;STRING&#39;,
            required: true
        },
        releaseDate: {
            type: &#39;DATE&#39;,
            required: true
        },
        rating: {
            type: &#39;STRING&#39;,
            required: true
        },
        grossEarnings: &#39;FLOAT&#39;
    }
};

module.exports = Movie;
&lt;/pre&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
&lt;b&gt;Person.js&lt;/b&gt;&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;brush: jscript&quot;&gt;
/**
 * The Person model
 */
var Person = {
    tableName: &#39;Person&#39;,
    attributes: {
        personId: {
            type: &#39;INTEGER&#39;,
            required: true,
            unique: true
        },
        firstName: {
            type: &#39;STRING&#39;,
            required: true
        },
        lastName: {
            type: &#39;STRING&#39;,
            required: true
        },
        dateOfBirth: {
            type: &#39;DATE&#39;,
            required: true
        },
        nationality: &#39;STRING&#39;,
        grossEarnings: &#39;FLOAT&#39;
    }
};

module.exports = Person;
&lt;/pre&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
&lt;b&gt;MoviePerson.js&lt;/b&gt;&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;brush: jscript&quot;&gt;
/**
 * The MoviePerson model
 */
var MoviePerson = {
    tableName: &#39;MoviePerson&#39;,
    attributes: {
        movieId: {
            type: &#39;INTEGER&#39;,
            required: true
        },
        personId: {
            type: &#39;INTEGER&#39;,
            required: true
        },
        role: {
            type: &#39;STRING&#39;,
            required: true
        },
        type: {
            type: &#39;STRING&#39;,
            required: true
        }
    }
};

module.exports = MoviePerson;
&lt;/pre&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Simple enough. &amp;nbsp;But, wait, we forgot something didn&#39;t we? &amp;nbsp;We need to make sure our persistence layer is defined as MongoDB and pointed properly to our installed local MongoDB instance. &amp;nbsp;For that, let&#39;s first obtain the sails-mongo adapter. &amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;ol style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;Fire up a command line (or Alt + F12 from WebStorm) and make sure you&#39;re on the /Server folder&lt;/li&gt;
&lt;li&gt;Type in &lt;b&gt;sudo npm install sails-mongo --save &lt;/b&gt;(or simply &lt;b&gt;npm install sails-mongo --save&lt;/b&gt;)&lt;/li&gt;
&lt;/ol&gt;
Now, let&#39;s shuffle over to /config/adapters.js. &amp;nbsp;It should look like this.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
&lt;b&gt;/config/adapters.js&lt;/b&gt;&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;brush: jscript&quot;&gt;
/**
 * Global adapter config
 * 
 * The `adapters` configuration object lets you create different global &quot;saved settings&quot;
 * that you can mix and match in your models.  The `default` option indicates which 
 * &quot;saved setting&quot; should be used if a model doesn&#39;t have an adapter specified.
 *
 * Keep in mind that options you define directly in your model definitions
 * will override these settings.
 *
 * For more information on adapter configuration, check out:
 * http://sailsjs.org/#documentation
 */

module.exports.adapters = {

  // If you leave the adapter config unspecified 
  // in a model definition, &#39;default&#39; will be used.
  &#39;default&#39;: &#39;mongo&#39;,

    // MongoDB
    mongo : {
        module: &#39;sails-mongo&#39;,
        host: &#39;127.0.0.1&#39;,
        port: 27017,
        database: &#39;AMDB&#39;,
        user: &#39;&#39;,
        password: &#39;&#39;
    },

  // Persistent adapter for DEVELOPMENT ONLY
  // (data is preserved when the server shuts down)
  disk: {
    module: &#39;sails-disk&#39;
  },

  // MySQL is the world&#39;s most popular relational database.
  // Learn more: http://en.wikipedia.org/wiki/MySQL
  myLocalMySQLDatabase: {

    module: &#39;sails-mysql&#39;,
    host: &#39;YOUR_MYSQL_SERVER_HOSTNAME_OR_IP_ADDRESS&#39;,
    user: &#39;YOUR_MYSQL_USER&#39;,
    // Psst.. You can put your password in config/local.js instead
    // so you don&#39;t inadvertently push it up if you&#39;re using version control
    password: &#39;YOUR_MYSQL_PASSWORD&#39;, 
    database: &#39;YOUR_MYSQL_DB&#39;
  }
};
&lt;/pre&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
So, now, our house is order from a domain and persistence perspective. &amp;nbsp;We have Waterline domain objects that will translate to separate Mongo collections, and our application is now configured to use our local MongoDB instance.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;
Surfacing Data to the Controller&lt;/h3&gt;
&lt;div&gt;
In classical MVC frameworks like ASP .Net MVC and Spring MVC, it&#39;s often an acceptable pattern to have some sort of intermediary between Model/ORM interaction and the Controllers themselves. &amp;nbsp;In short, the separation of concerns would be Model/ORM/DB --&amp;gt; Business Logic --&amp;gt; Controllers...that is, controllers ought to only be concerned about transport of data, usually in the form of data transfer objects, or DTO&#39;s, and there will be a layer between it and the Model/ORM that handles any sort of business logic manipulation. &amp;nbsp;We&#39;re going to follow this pattern for this thought exercise.&lt;br /&gt;
&lt;br /&gt;
Sails.js, out of the box, offers this functionality in the way of the /api/services folder. &amp;nbsp;Any exports code created here can be accessible globally. &amp;nbsp;However, for the sake of argument, let&#39;s say we want to keep our Sails.js app as skinny as possible and not clutter up global and only &quot;use what we need&quot; in a Controller. &amp;nbsp;Following this pattern, let&#39;s establish another folder in /api called /managers. &amp;nbsp;Again, a Manager&#39;s concerns would be any sort of Model and Business Logic interaction, and it would return resulting DTO&#39;s back to the Controller.&lt;br /&gt;
&lt;br /&gt;
We&#39;ll create the SearchManager as a standard prototypical JavaScript object with two methods: searchByMovie and searchByPersonText. &amp;nbsp;Again, our application will have the ability to search for parts of a word or words in movie titles as well as people involved with movies. &lt;br /&gt;
&lt;br /&gt;
We&#39;ll need a few DTO&#39;s, per our pattern, to reflect this -- one for each of the Model objects (Movie and Person), a specialized one called CastCrewDTO which is meant to represent a Person belonging to a movie playing any number of roles, and a SearchResultDTO to consolidate all of this for the controller to return to consuming applications. &amp;nbsp;We&#39;re going to create a /api/dto folder and throw these JavaScript objects in there.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;/api/managers/SearchManager.js&lt;/b&gt;&lt;br /&gt;
&lt;pre class=&quot;brush: jscript&quot;&gt;
var _ = require(&#39;underscore&#39;);
var Promise = require(&#39;promise&#39;);
var MovieDTO = require(&#39;../dto/MovieDTO&#39;);
var PersonDTO = require(&#39;../dto/PersonDTO&#39;);
var CastCrewDTO = require(&#39;../dto/CastCrewDTO&#39;);

/**
 * The Manager layer acts as an intermediary between controllers and the domain
 * For this pattern, the Manager layer simply spits back DTO&#39;s for the Controller
 */
function SearchManager() {

};

/**
 * Searches movies by a particular text
 * @param movieText
 * @returns {Promise}
 */
SearchManager.prototype.searchByMovie = function (movieText) {
    var movieDtos = [];
    var self = this;
    return new Promise(
        function (fulfill, reject) {
            Movie.find({ or: [ { like: { name: &#39;%&#39; + movieText + &#39;%&#39; }} ] })
                .done(function (err, movies) {
                    var movieIds = [];
                    if (err || _.isUndefined(movies)) {
                        reject(err || movies);
                    }
                    else {
                        _.each(movies, function (movie) {
                            var movieDto = new MovieDTO();
                            // Add the DTO
                            movieDto.fromEntity(movie);
                            movieDtos.push(movieDto);
                            movieIds.push(movie.movieId);
                        });

                        // Get cast and crew
                        MoviePerson.find({ movieId: movieIds })
                            .done(function(err, moviePersons) {
                                var personIds = [];
                                _.each(moviePersons, function(moviePerson) {
                                    personIds.push(moviePerson.personId);
                                    var castCrewDto = new CastCrewDTO();
                                    castCrewDto.personId = moviePerson.personId;
                                    castCrewDto.role = moviePerson.role;
                                    var movieDto = _.find(movieDtos, function(movieDto) { return movieDto.movieId === moviePerson.movieId });
                                    if (moviePerson.type === &#39;Cast&#39;) {
                                        movieDto.cast.push(castCrewDto);
                                    }
                                    else if (moviePerson.type === &#39;Crew&#39;) {
                                        movieDto.crew.push(castCrewDto);
                                    }
                                });

                                Person.find({ personId: personIds })
                                    .done(function(err, persons) {
                                        _.each(persons, function(person) {
                                            _.each(movieDtos, function(movieDto) {
                                                // TODO: Do a filter instead of a find.  A Person can play multiple roles
                                                var matchedCast = _.find(movieDto.cast, function(castDto) { return castDto.personId === person.personId });
                                                var matchedCrew = _.find(movieDto.crew, function(crewDto) { return crewDto.personId === person.personId });
                                                if (!_.isUndefined(matchedCast)){
                                                    matchedCast.firstName = person.firstName;
                                                    matchedCast.lastName = person.lastName;
                                                    matchedCast.displayName = person.lastName + &#39;, &#39; + person.firstName;
                                                }
                                                if (!_.isUndefined(matchedCrew)){
                                                    matchedCrew.firstName = person.firstName;
                                                    matchedCrew.lastName = person.lastName;
                                                    matchedCrew.displayName = person.lastName + &#39;, &#39; + person.firstName;
                                                }
                                            });
                                        });
                                        fulfill(movieDtos);
                                    });
                            }
                        );
                    }
                }
            );
        }
    );
};

/**
 * Searches Persons by a particular text
 * @param personText
 * @returns {Promise}
 */
SearchManager.prototype.searchByPersonText = function (personText) {
    var personDtos = [];
    return new Promise(
        function (fulfill, reject) {
            Person.find({
                or: [
                    { like: { firstName: &#39;%&#39; + personText + &#39;%&#39;} },
                    { like: { lastName: &#39;%&#39; + personText + &#39;%&#39;} }
                ]
            })
                .done(function (err, persons) {
                    if (err || _.isUndefined(persons)) {
                        reject(err || persons);
                    }
                    else {
                        _.each(persons, function (person) {
                            var personDto = new PersonDTO();
                            personDto.fromEntity(person);
                            personDtos.push(personDto);
                        });
                        fulfill(personDtos);
                    }
                });
        }
    );
};

module.exports = SearchManager;
&lt;/pre&gt;&lt;br /&gt;
&lt;b&gt;&lt;br /&gt;&lt;/b&gt;
&lt;b&gt;/api/dto/CastCrewDTO.js&lt;/b&gt;&lt;br /&gt;
&lt;pre class=&quot;brush: jscript&quot;&gt;
function CastCrewDTO() {
    this.firstName = &#39;&#39;;
    this.lastName = &#39;&#39;;
    this.displayName = &#39;&#39;;
    this.role = &#39;&#39;;
    this.personId = &#39;&#39;;
};

module.exports = CastCrewDTO;
&lt;/pre&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;/api/dto/MovieDTO.js&lt;/b&gt;&lt;br /&gt;
&lt;pre class=&quot;brush: jscript&quot;&gt;
function MovieDTO() {
    this.id = &#39;&#39;;
    this.movieId = &#39;&#39;;
    this.name = &#39;&#39;;
    this.releaseDate = new Date();
    this.rating = &#39;&#39;;
    this.grossEarnings = 0.00;
    this.cast = [];
    this.crew = [];
}

MovieDTO.prototype.fromEntity = function(movie) {
    this.id = movie.id
    this.movieId = movie.movieId;
    this.name = movie.name;
    this.releaseDate = movie.releaseDate;
    this.rating = movie.rating;
    this.grossEarnings = movie.grossEarnings;
};


module.exports = MovieDTO;
&lt;/pre&gt;&lt;br /&gt;
&lt;b&gt;&lt;br /&gt;&lt;/b&gt;
&lt;b&gt;/api/dto/PersonDTO.js&lt;/b&gt;&lt;br /&gt;
&lt;pre class=&quot;brush: jscript&quot;&gt;
function PersonDTO() {
    this.id = &#39;&#39;;
    this.firstName = &#39;&#39;;
    this.lastName = &#39;&#39;;
    this.displayName = &#39;&#39;;
    this.dateOfBirth = new Date();
    this.nationality = &#39;&#39;;
    this.grossEarnings = 0.00;
}

PersonDTO.prototype.fromEntity = function(person) {
    this.id = person.id;
    this.firstName = person.firstName;
    this.lastName = person.lastName;
    this.displayName = person.lastName + &#39;, &#39; + person.firstName;
    this.dateOfBirth = person.dateOfBirth;
    this.nationality = person.nationality;
    this.grossEarnings = person.grossEarnings;
};


module.exports = PersonDTO;
&lt;/pre&gt;&lt;br /&gt;
&lt;b&gt;&lt;br /&gt;&lt;/b&gt;
&lt;b&gt;/api/dto/SearchResultDTO.js&lt;/b&gt;&lt;br /&gt;
&lt;pre class=&quot;brush: jscript&quot;&gt;
/**
 * A DTO to encapsulate search results for movies or persons
 * @param User
 * @constructor
 */
function SearchResultDTO() {
    this.searchText = &#39;&#39;;
    this.movieDtos = [];
    this.personDtos = [];
}

SearchResultDTO.prototype.fromMovieDtos = function(movieDtos) {
    this.movieDtos = movieDtos;
};

SearchResultDTO.prototype.fromPersonDtos = function(personDtos) {
    this.personDtos = personDtos;
};


module.exports = SearchResultDTO;
&lt;/pre&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
You&#39;ll notice the SearchManager directly invokes the Models we created above. &amp;nbsp;WebStorm might bark at you for this, but if you recall, anything created in /api/models gets added to global, so when this runs, this code will resolve just fine!&lt;br /&gt;
&lt;br /&gt;
The DTO&#39;s themselves are nothing special -- plain JS objects. &amp;nbsp;You&#39;ll notice the PersonDTO and MovieDTO have fromEntity methods that convert the models to the DTO&#39;s. &amp;nbsp;Since CastCrewDTO and SearchResultDTO are specialized logical objects, no such &quot;fromEntity&quot; methods exist for these and we&#39;ll set the properties for each as we go.&lt;br /&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;
Controller Methods for Consuming Applications&lt;/h3&gt;
&lt;div&gt;
As was mentioned earlier, controllers can be automatically code-generated. &amp;nbsp;Let&#39;s say we want to create a SearchController, whose methods sort of match what we made above in the SearchManager, with one method called byActorOrMovie. &amp;nbsp;It is simple as going to a terminal or command line, going to the root of your Sails project (same level as node_modules) and typing in &lt;b&gt;sails generate controller search byActorOrMovie. &amp;nbsp;&lt;/b&gt;Hit enter and you get the skeleton below.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;/api/controllers/SearchController.js&lt;/b&gt;&lt;br /&gt;
&lt;pre class=&quot;brush: jscript&quot;&gt;
/**
 * SearchController
 *
 * @module      :: Controller
 * @description :: A set of functions called `actions`.
 *
 *                 Actions contain code telling Sails how to respond to a certain type of request.
 *                 (i.e. do stuff, then send some JSON, show an HTML page, or redirect to another URL)
 *
 *                 You can configure the blueprint URLs which trigger these actions (`config/controllers.js`)
 *                 and/or override them with custom routes (`config/routes.js`)
 *
 *                 NOTE: The code you write here supports both HTTP and Socket.io automatically.
 *
 * @docs        :: http://sailsjs.org/#!documentation/controllers
 */

module.exports = {
    
  
  /**
   * Action blueprints:
   *    `/search/byActorOrMovie`
   */
   byActorOrMovie: function (req, res) {
    
    // Send a JSON response
    return res.json({
      hello: &#39;world&#39;
    });
  },




  /**
   * Overrides for the settings in `config/controllers.js`
   * (specific to SearchController)
   */
  _config: {}

  
};

&lt;/pre&gt;&lt;br /&gt;
&lt;b&gt;&lt;br /&gt;&lt;/b&gt;
For those of you who have worked on Ruby on Rails, this functionality should seem familiar! &amp;nbsp;Codegenerating controllers is nice and all, but we obviously need to wire it up to use the SearchManager and the SearchResultDTO we created, so how do we do that? &amp;nbsp;Well, by the magic of require, of course!&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;/api/controllers/SearchController.js&lt;/b&gt;&lt;br /&gt;
&lt;pre class=&quot;brush: jscript&quot;&gt;
var SearchManager = require(&#39;../managers/SearchManager&#39;);
var SearchResultDTO = require(&#39;../dto/SearchResultDTO&#39;);

/**
 * SearchController
 *
 * @module      :: Controller
 * @description :: A set of functions called `actions`.
 *
 *                 Actions contain code telling Sails how to respond to a certain type of request.
 *                 (i.e. do stuff, then send some JSON, show an HTML page, or redirect to another URL)
 *
 *                 You can configure the blueprint URLs which trigger these actions (`config/controllers.js`)
 *                 and/or override them with custom routes (`config/routes.js`)
 *
 *                 NOTE: The code you write here supports both HTTP and Socket.io automatically.
 *
 * @docs        :: http://sailsjs.org/#!documentation/controllers
 */

module.exports = {
    
  
  /**
   * Action blueprints:
   *    `/search/byActorOrMovie/{someSearchText}`
   */
   byActorOrMovie: function (req, res) {
      var searchManager = new SearchManager();
      var searchResultDto = new SearchResultDTO();

      // Search by movies, then by persons
      searchManager.searchByMovie(req.params.id).then(
          function(movieDtos) {
              searchResultDto.fromMovieDtos(movieDtos);
              searchManager.searchByPersonText(req.params.id).then(
                  function(personDtos) {
                      searchResultDto.fromPersonDtos(personDtos);
                      searchResultDto.searchText = req.params.id;
                      return res.json(searchResultDto);
                  },
                  function(error){
                      res.serverError(error);
                  }
              )
          },
          function(error) {
              res.serverError(error);
          }
      )
  },

  /**
   * Overrides for the settings in `config/controllers.js`
   * (specific to SearchController)
   */
  _config: {}

  
};

&lt;/pre&gt;&lt;br /&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;
Unit Test Setup&lt;/h3&gt;
&lt;div&gt;
As we wrote the DTO&#39;s, SearchManager, and SearchController files above, one thought that must&#39;ve come to mind is how to unit test all of this code? &amp;nbsp;For this exercise, we choose the following supporting libraries to help us (which are installed using npm install)&lt;br /&gt;
&lt;br /&gt;
&lt;ul style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;&lt;a href=&quot;http://visionmedia.github.io/mocha/&quot;&gt;Mocha&lt;/a&gt;: An easy to use, flexible unit testing framework for Node.js&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.npmjs.org/package/assert&quot;&gt;Assert&lt;/a&gt;: An assertion library ported from CommonJS&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://lodash.com/&quot;&gt;Lo-Dash&lt;/a&gt;: Many useful helper functions for JavaScript, specifically checking for defined/undefined/null values and collection helpers&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/thlorenz/proxyquire&quot;&gt;Proxyquire&lt;/a&gt;: An excellent tool to inject stubs in place of anything injected using require&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/pghalliday/grunt-mocha-test&quot;&gt;grunt-mocha-test&lt;/a&gt;: A grunt task for running Mocha tests&lt;/li&gt;
&lt;/ul&gt;
These libraries used together provide us with all the unit testing capability we need. &amp;nbsp;Before we write up some unit tests for the SearchController and SearchManager, we first need to modify the gruntfile.js on the root, as Grunt will be primarily charged with running the Mocha Tests.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;/gruntfile.js&lt;/b&gt;&lt;br /&gt;
&lt;pre class=&quot;brush: jscript&quot;&gt;
    grunt.loadTasks(depsPath + &#39;/grunt-contrib-less/tasks&#39;);
    grunt.loadTasks(depsPath + &#39;/grunt-contrib-coffee/tasks&#39;);
    grunt.loadNpmTasks(&#39;grunt-mocha-test&#39;);
&lt;/pre&gt;&lt;br /&gt;
&lt;br /&gt;
And also, we need to modify the grunt.initConfig to actually have a task to use the grunt-mocha-test task runner.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;/gruntfile.js&lt;/b&gt;&lt;br /&gt;
&lt;pre class=&quot;brush: jscript&quot;&gt;
        mochaTest: {
            test: {
                options: {
                    reporter: &#39;spec&#39;
                },
                src: [&#39;tests/**/*.spec.js&#39;]
            }
        },
&lt;/pre&gt;&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
You&#39;ll see it&#39;s configured to run any *.spec.js in the /tests folder. &amp;nbsp;We&#39;ll save this gruntfile.js and establish said /tests folder, making a subdirectory structure that closely matches the /api folder to correspond with our components.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Typically, in JavaScript, you would create a test for a .js file with an equivalent .spec.js file, so we will do the same here -- /tests/controllers/SearchController.spec.js for the SearchController and /tests/managers/SearchManager.spec.js for the SearchManager.&lt;br /&gt;
&lt;br /&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;
Unit Testing Approach&lt;/h3&gt;
&lt;/div&gt;
&lt;div&gt;
I don&#39;t see enough posts about unit testing, which is a key facet of Agile development, so let&#39;s quickly review what we intend to accomplish here.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
A &quot;true&quot; unit test is one that can stand alone. &amp;nbsp;That is, I should be able to run this test without lifting Sails, having a web server up, having MongoDB running, etc. (hence a UNIT test). &amp;nbsp;To accomplish this in classical languages such as Java and C#, we&#39;d develop to interfaces, utilize dependency injection libraries, and create mocks or stubs of any dependent components, such as the ORM, HTTP calls, database calls, etc.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
JavaScript, being dynamic in nature, allows us to be even more flexible. &amp;nbsp;We can mock these dependencies right in-line. &amp;nbsp;Better yet, as opposed to Mocking libraries which may produce mocks for methods, properties, and fields we don&#39;t even use, by writing these stubs ourselves, we actually narrow down what methods, properties, and fields we need to use from dependent modules.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
You&#39;re going to see the above approach with our unit tests in this particular project.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;
Unit Testing the Manager&lt;/h3&gt;
&lt;div&gt;
The SearchManager&#39;s primary responsibilities are interacting with the Waterline ORM based on some input from the Controller (in this case, a string for which to search in a Movie name or a Person&#39;s first or last name), get the object(s) from the ORM, and send back DTO(s) to the controller.&lt;br /&gt;
&lt;br /&gt;
The curveball here is that Sails.js expects the Models to live in global, so our mocked Models will also live there. &amp;nbsp;We also mocked up some data from the ORM using straight .json files, instead of literally coding them in.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;/tests/managers/SearchManager.spec.js&lt;/b&gt;&lt;br /&gt;
&lt;pre class=&quot;brush: jscript&quot;&gt;
// Injected objects
var _ = require(&#39;lodash&#39;),
    assert = require(&#39;assert&#39;),
    SearchManager = require(&#39;../../api/managers/SearchManager.js&#39;),
    MovieData = require(&#39;../mockData/SearchManager/Movie.json&#39;),
    MoviePersonData = require(&#39;../mockData/SearchManager/MoviePerson.json&#39;),
    PersonData = require(&#39;../mockData/SearchManager/Person.json&#39;);

// Mock domain objects
var mockMovie,
    mockMoviePerson,
    mockPerson,
    searchManager;

describe(&#39;SearchManager&#39;, function(){

    /**
     * Setup mock models
     */
    before(function(){
        // Mocked Movie model
        mockMovie =  {
            find: function(args) {
                {
                    var done = function(cb) {
                        return cb(null, MovieData);
                    };

                    return {
                        done: done
                    };
                }
            }
        };

        // Mocked MoviePersonModel
        mockMoviePerson =  {
            find: function(args) {
                {
                    var done = function(cb) {
                        return cb(null, MoviePersonData);
                    };

                    return {
                        done: done
                    };
                }
            }
        };

        // Mocked Person Data
        mockPerson =  {
            find: function(args) {
                {
                    var done = function(cb) {
                        return cb(null, PersonData);
                    };

                    return {
                        done: done
                    };
                }
            }
        };

        // Set expected globals
        global.Movie = mockMovie;
        global.MoviePerson = mockMoviePerson;
        global.Person = mockPerson;

        // Setup the SearchManager
        searchManager = new SearchManager();

    });

    describe(&#39;searchByMovie&#39;, function() {
        it(&#39;Should get movies with cast and crew&#39;, function(done){
            searchManager.searchByMovie(&#39;Godfather&#39;)
                .then(
                    function(movieDtos) {

                        // Assert that an array of movieDto&#39;s was created
                        assert(movieDtos.length &gt; 0);
                        assert(movieDtos[0].name === MovieData[0].name, &#39;Invalid name&#39;);
                        assert(movieDtos[0].movieId === MovieData[0].movieId, &#39;Invalid movieId&#39;);
                        assert(movieDtos[0].releaseDate === MovieData[0].releaseDate, &#39;Invalid releaseDate&#39;);
                        assert(movieDtos[0].rating === MovieData[0].rating, &#39;Invalid rating&#39;);
                        assert(movieDtos[0].grossEarnings === MovieData[0].grossEarnings, &#39;Invalid grossEarnings&#39;);
                        assert(movieDtos[0].cast.length === 2, &#39;Wrong number of cast members&#39;);
                        done();
                    },
                    function(error) {
                        done(&#39;General error&#39;);
                    }
                )
                .catch(done); // Promise has its own error handler so we need to pass the done function here

        });
    });
});
&lt;/pre&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;/tests/mockData/SearchManager/Movie.json&lt;/b&gt;&lt;br /&gt;
&lt;pre class=&quot;brush: jscript&quot;&gt;
[
    {
        &quot;movieId&quot;: 1,
        &quot;name&quot;: &quot;The Godfather&quot;,
        &quot;releaseDate&quot;: &quot;1975-04-21T18:25:43-05:00&quot;,
        &quot;rating&quot;: &quot;R&quot;,
        &quot;grossEarnings&quot;: 1200000000.00
    }
]
&lt;/pre&gt;&lt;br /&gt;
&lt;b&gt;&lt;br /&gt;&lt;/b&gt;
&lt;b&gt;/tests/mockData/SearchManager/MoviePerson.json&lt;/b&gt;&lt;br /&gt;
&lt;pre class=&quot;brush: jscript&quot;&gt;
[
    {
        &quot;movieId&quot;: 1,
        &quot;personId&quot;: 1,
        &quot;role&quot;: &quot;Michael Corleone&quot;,
        &quot;type&quot;: &quot;Cast&quot;
    },
    {
        &quot;movieId&quot;: 1,
        &quot;personId&quot;: 2,
        &quot;role&quot;: &quot;Vito Corleone&quot;,
        &quot;type&quot;: &quot;Cast&quot;
    }
]
&lt;/pre&gt;&lt;br /&gt;
&lt;b&gt;&lt;br /&gt;&lt;/b&gt;
&lt;b&gt;/tests/mockData/SearchManager/Person.json&lt;/b&gt;&lt;br /&gt;
&lt;pre class=&quot;brush: jscript&quot;&gt;
[
    {
        &quot;personId&quot;: 1,
        &quot;firstName&quot;: &quot;Al&quot;,
        &quot;lastName&quot;: &quot;Pacino&quot;,
        &quot;dateOfBirth&quot;: &quot;1940-04-25T18:25:43-05:00&quot;,
        &quot;nationality&quot;: &quot;Italian&quot;,
        &quot;grossEarnings&quot;: 250000000.00
    },
    {
        &quot;personId&quot;: 2,
        &quot;firstName&quot;: &quot;Marlon&quot;,
        &quot;lastName&quot;: &quot;Brando&quot;,
        &quot;dateOfBirth&quot;: &quot;1924-04-03T18:25:43-05:00&quot;,
        &quot;nationality&quot;: &quot;American&quot;,
        &quot;grossEarnings&quot;: 280000000.00
    }
]
&lt;/pre&gt;&lt;br /&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;
Unit Testing the Controller&lt;/h3&gt;
&lt;div&gt;
The SearchController&#39;s primary responsibilities are taking DTO&#39;s from their respective Manager(s) and shuttling them back to any consuming application. &amp;nbsp;Furthermore, if any errors were thrown downstream, the Controller should also throw an appropriate server error. &amp;nbsp;In short, the Controller acts as the &quot;plumbing&quot; in between the domain and any consuming applications.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;/tests/controllers/SearchController.spec.js&lt;/b&gt;&lt;br /&gt;
&lt;pre class=&quot;brush: jscript&quot;&gt;
// Dependent objects
var _ = require(&#39;underscore&#39;),
    assert = require(&#39;assert&#39;),
    MovieDTO = require(&#39;../../api/dto/MovieDTO&#39;),
    PersonDTO = require(&#39;../../api/dto/PersonDTO&#39;),
    CastCrewDTO = require(&#39;../../api/dto/CastCrewDTO&#39;),
    proxyquire = require(&#39;proxyquire&#39;),
    Promise = require(&#39;promise&#39;);

// Mock objects
var req, res,
    SearchController;

describe(&#39;SearchController&#39;, function(){

    /**
     * Shared objects
     */
    before(function() {
        // Mock req
        req = {
            params: {
                id: &#39;wedding&#39;
            }
        };

        // Mock res
        // Create fields to hold the res responses and server errors
        res = {
            rawObject: null,
            jsonObject: null,
            errorObject: null,

            json: function(object) {
                this.rawObject = object;
                this.jsonObject = JSON.stringify(object);
                return object;
            },
            serverError: function(error) {
                this.errorObject = error;
                return error;
            }
        };
    });

    describe(&#39;on failure of getting data from SearchManager&#39;, function() {
        /**
         * Setup Mock Managers for failed calls
         */
        before(function(){

            // Mock SearchManager
            function SearchManager() {
                var searchByMovie = function(movieText) {
                    var then = function(cb, error) {
                        return error(&#39;Oh noes!&#39;)
                    };

                    return {
                        then: then
                    }
                };

                var searchByPersonText = function(personText) {
                    var then = function(cb, error) {
                        return error(&#39;Oh noes!&#39;);
                    };

                    return {
                        then: then
                    }
                };

                return {
                    searchByMovie: searchByMovie,
                    searchByPersonText: searchByPersonText
                };
            };

            // Setup the Controller
            SearchController = proxyquire(&#39;../../api/controllers/SearchController&#39;, { &#39;../managers/SearchManager&#39;: SearchManager });
            res.rawObject = null;
            res.jsonObject = null;
            res.errorObject = null;
        });

        it(&#39;byActorOrMovie should report a server error&#39;, function() {
            SearchController.byActorOrMovie(req, res);
            assert(_.isNull(res.jsonObject));
            assert(!_.isNull(res.errorObject));
            assert(res.errorObject === &#39;Oh noes!&#39;);

        });

    });

    describe(&#39;on successfully getting data from SearchManager&#39;, function() {
        /**
         * Setup Mock Managers for successful calls
         */
        before(function(){

            // Mock SearchManager
            function SearchManager() {
                var searchByMovie = function(movieText) {
                    var then = function(cb) {
                        var movieDtos = [];
                        var movieDto = new MovieDTO();
                        movieDto.id = 1;
                        movieDto.name = &#39;Wedding Crashers&#39;;
                        movieDto.rating = &#39;R&#39;;
                        movieDto.grossEarnings = 1337000.00

                        var castCrewDto = new CastCrewDTO();
                        castCrewDto.firstName = &#39;Owen&#39;;
                        castCrewDto.lastName = &#39;Wilson&#39;;
                        castCrewDto.displayName = &#39;Wilson, Owen&#39;;
                        castCrewDto.role = &#39;John Beckwith&#39;;
                        castCrewDto.personId = 1;

                        movieDto.cast.push(castCrewDto);
                        movieDtos.push(movieDto);
                        return cb(movieDtos)
                    };

                    return {
                        then: then
                    }
                };

                var searchByPersonText = function(personText) {
                    var then = function(cb) {
                        var personDtos = [];
                        var personDto = new PersonDTO();
                        personDto.id = 1;
                        personDto.firstName = &#39;Owen&#39;;
                        personDto.lastName = &#39;Wilson&#39;;
                        personDto.displayName = &#39;Wilson, Owen&#39;;
                        personDto.dateOfBirth = new Date(&#39;1968-11-18&#39;);
                        personDto.nationality = &#39;American&#39;;
                        personDto.grossEarnings = 133700.00
                        personDtos.push(personDto);
                        return cb(personDtos);
                    };

                    return {
                        then: then
                    }
                };

                return {
                    searchByMovie: searchByMovie,
                    searchByPersonText: searchByPersonText
                };
            };

            // Setup the Controller
            SearchController = proxyquire(&#39;../../api/controllers/SearchController&#39;, { &#39;../managers/SearchManager&#39;: SearchManager });
            res.rawObject = null;
            res.jsonObject = null;
            res.errorObject = null;
        });

        it(&#39;byActorOrMovie should return results&#39;, function(){
            SearchController.byActorOrMovie(req, res);
            assert(!_.isNull(res.rawObject));
            assert(!_.isNull(res.jsonObject));
            assert(_.isNull(res.errorObject));
            assert(res.rawObject.searchText === &#39;wedding&#39;);
            assert(res.rawObject.movieDtos.length === 1);
            assert(res.rawObject.personDtos.length === 1);
            assert(res.rawObject.personDtos[0].firstName === &#39;Owen&#39;, &#39;PersonDTO.firstName&#39;);
            assert(res.rawObject.personDtos[0].lastName === &#39;Wilson&#39;, &#39;PersonDTO.lastName&#39;);
            assert(res.rawObject.personDtos[0].displayName === &#39;Wilson, Owen&#39;, &#39;PersonDTO.displayName&#39;);
            assert(res.rawObject.personDtos[0].nationality === &#39;American&#39;, &#39;PersonDTO.nationality&#39;);
            assert(res.rawObject.personDtos[0].grossEarnings === 133700.00, &#39;PersonDTO.grossEarnings&#39;);
            assert(res.rawObject.movieDtos[0].name === &#39;Wedding Crashers&#39;, &#39;MovieDTO.name&#39;);
            assert(res.rawObject.movieDtos[0].rating === &#39;R&#39;, &#39;MovieDTO.rating&#39;)
            assert(res.rawObject.movieDtos[0].grossEarnings === 1337000.00, &#39;MovieDTO.grossEarnings&#39;);
            assert(res.rawObject.movieDtos[0].cast[0].lastName === &#39;Wilson&#39;, &#39;MovieDTO.cast.lastName&#39;);
            assert(res.rawObject.movieDtos[0].cast[0].firstName === &#39;Owen&#39;, &#39;MovieDTO.cast.firstName&#39;);
            assert(res.rawObject.movieDtos[0].cast[0].role === &#39;John Beckwith&#39;, &#39;MovieDTO.cast.role&#39;);
            assert(res.rawObject.movieDtos[0].cast[0].displayName === &#39;Wilson, Owen&#39;, &#39;MovieDTO.cast.lastName&#39;);
        });
    });
});

&lt;/pre&gt;&lt;br /&gt;
&lt;b&gt;&lt;br /&gt;&lt;/b&gt;
The curveball with this unit test is how we inject the dependency to SearchManager. &amp;nbsp;We use a library called proxyquire to inject our mock SearchManager to the Controller. &amp;nbsp;Think of it as a Mockito or Moq equivalent for JavaScript frameworks that use require.&lt;br /&gt;
&lt;h3&gt;
Unit Testing the Models&lt;/h3&gt;
&lt;div&gt;
In this particular example, being simplistic in nature, we didn&#39;t code in any behavior in the Models. &amp;nbsp;Sails.js allows you to write in methods as part of the attributes of a Model, so if you had said methods implemented, you&#39;d simply require whatever Model it was you&#39;re testing and run against those methods.&lt;/div&gt;
&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/div&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;
Running the Unit Tests&lt;/h3&gt;
&lt;div&gt;
Now that we&#39;ve got our unit tests in place, fire up a terminal or command line, and type in &quot;grunt mochaTest&quot;. &amp;nbsp;You should see the following output&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;brush: shell&quot;&gt;
eric@eric-CM6870:~/Projects/AgileMovieDB/agilemoviedb/Server$ grunt mochaTest
Running &quot;mochaTest:test&quot; (mochaTest) task


  SearchController
    on failure of getting data from SearchManager
      ✓ byActorOrMovie should report a server error 
    on successfully getting data from SearchManager
      ✓ byActorOrMovie should return results 

  SearchManager
    searchByMovie
      ✓ Should get movies with cast and crew 


  3 passing (12ms)


Done, without errors.
&lt;/pre&gt;&lt;/div&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;
&lt;br /&gt;&lt;/h3&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;
Seed Some Data!&lt;/h3&gt;
&lt;div&gt;
What good is this Movie database without some sample data? &amp;nbsp;I&#39;ve included a simple Node.js script in /seeds called seed.js. &amp;nbsp;Simply run this to populate your local MongoDB instance with sample movie data from the .json&#39;s in that folder. &amp;nbsp;Note, this seed will not currently work for Sails.js v0.10.x.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;
Check Out the API!&lt;/h3&gt;
&lt;div&gt;
So, now we can say with pretty good confidence that we&#39;ve got a working API. &amp;nbsp;Fire up a terminal, go to the /Server folder, and type in &quot;sails lift&quot;. &amp;nbsp;Alternatively, in WebStorm, find the app.js on the root of /Server and run it.&lt;br /&gt;
&lt;br /&gt;
Let&#39;s test our primary endpoint, /search/byActorOrMovie.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;/search/byActorOrMovie/b&lt;/b&gt;&lt;br /&gt;
&lt;pre class=&quot;brush: jscript&quot;&gt;
{
searchText: &quot;b&quot;,
movieDtos: [ ],
personDtos: [
{
id: &quot;53a429a41d7e9b4417a67e11&quot;,
firstName: &quot;Marlon&quot;,
lastName: &quot;Brando&quot;,
displayName: &quot;Brando, Marlon&quot;,
dateOfBirth: &quot;1924-04-03T18:25:43-05:00&quot;,
nationality: &quot;American&quot;,
grossEarnings: 280000000
},
{
id: &quot;53a429a41d7e9b4417a67e16&quot;,
firstName: &quot;Steven&quot;,
lastName: &quot;Spielberg&quot;,
displayName: &quot;Spielberg, Steven&quot;,
dateOfBirth: &quot;1946-12-18T18:25:43-05:00&quot;,
nationality: &quot;American&quot;,
grossEarnings: 400000000
}
]
}
&lt;/pre&gt;&lt;br /&gt;
&lt;b&gt;&lt;br /&gt;&lt;/b&gt;
&lt;b&gt;/search/byActorOrMovie/ford&lt;/b&gt;&lt;br /&gt;
&lt;pre class=&quot;brush: jscript&quot;&gt;
{
searchText: &quot;ford&quot;,
movieDtos: [ ],
personDtos: [
{
id: &quot;53a429a41d7e9b4417a67e14&quot;,
firstName: &quot;Harrison&quot;,
lastName: &quot;Ford&quot;,
displayName: &quot;Ford, Harrison&quot;,
dateOfBirth: &quot;1942-07-14T18:25:43-05:00&quot;,
nationality: &quot;American&quot;,
grossEarnings: 250000000
},
{
id: &quot;53a429a41d7e9b4417a67e15&quot;,
firstName: &quot;Francis&quot;,
lastName: &quot;Ford Coppola&quot;,
displayName: &quot;Ford Coppola, Francis&quot;,
dateOfBirth: &quot;1939-04-07T18:25:43-05:00&quot;,
nationality: &quot;American&quot;,
grossEarnings: 300000000
}
]
}
&lt;/pre&gt;&lt;br /&gt;
&lt;b&gt;&lt;br /&gt;&lt;/b&gt;
&lt;b&gt;/search/byActorOrMovie/star&lt;/b&gt;&lt;br /&gt;
&lt;pre class=&quot;brush: jscript&quot;&gt;
{
searchText: &quot;star&quot;,
movieDtos: [
{
id: &quot;53a429a41d7e9b4417a67e0c&quot;,
movieId: 2,
name: &quot;Star Wars&quot;,
releaseDate: &quot;1977-01-21T18:25:43-05:00&quot;,
rating: &quot;PG-13&quot;,
grossEarnings: 1400000000,
cast: [
{
firstName: &quot;Mark&quot;,
lastName: &quot;Hamill&quot;,
displayName: &quot;Hamill, Mark&quot;,
role: &quot;Luke Skywalker&quot;,
personId: 4
},
{
firstName: &quot;Harrison&quot;,
lastName: &quot;Ford&quot;,
displayName: &quot;Ford, Harrison&quot;,
role: &quot;Han Solo&quot;,
personId: 5
}
],
crew: [ ]
}
],
personDtos: [ ]
}
&lt;/pre&gt;&lt;br /&gt;
&lt;b&gt;&lt;br /&gt;&lt;/b&gt;
Pretty neat!&lt;br /&gt;
&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/div&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;
Wrap-up&lt;/h3&gt;
&lt;div&gt;
For the past few years, many have wondered if Node.js as a server-side web API is &quot;enterprise-ready&quot; from a development perspective. &amp;nbsp;While this project is very simplistic, we went over:&lt;/div&gt;
&lt;div&gt;
&lt;ul style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;Separation of Concerns&lt;/li&gt;
&lt;li&gt;Dependency Injection&lt;/li&gt;
&lt;li&gt;Configurability&lt;/li&gt;
&lt;li&gt;Testability&lt;/li&gt;
&lt;/ul&gt;
What we did not cover in this project that Sails.js supports, which I&#39;m sure enterprises are concerned about, to name a few:&lt;/div&gt;
&lt;div&gt;
&lt;ul style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;CORS Configuration&lt;/li&gt;
&lt;li&gt;Authentication using Passport&lt;/li&gt;
&lt;li&gt;Access Control Policies&lt;/li&gt;
&lt;li&gt;Session States&lt;/li&gt;
&lt;li&gt;Socket.io (i.e. real-time push notifications, or pub/sub) Support&lt;/li&gt;
&lt;/ul&gt;
In short, is Sails.js &quot;enterprise-ready&quot;? &amp;nbsp;I&#39;d say a resounding heck yes. &amp;nbsp;For smaller companies equipped with skilled JavaScript developers looking to launch scalable, durable products with quick time to market, Sails.js is certainly a compelling option worth an evaluation.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
Stay tuned for Part 3 of this 3-part series where we wire up an AngularJS SPA to this Sails.js Web API!&lt;/div&gt;
&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geekstentialism.blogspot.com/feeds/3649901628662030752/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/2381314077784322587/3649901628662030752' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2381314077784322587/posts/default/3649901628662030752'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2381314077784322587/posts/default/3649901628662030752'/><link rel='alternate' type='text/html' href='http://geekstentialism.blogspot.com/2014/06/growing-up-and-growing-large-modern_20.html' title='Growing Up and Growing Large: Modern JavaScript Web Development Using Sails.js and AngularJS (Part 2 of 3)'/><author><name>Eric Nograles</name><uri>http://www.blogger.com/profile/02198416401185614213</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJ6RXtY4XZkDuwsIOoO4bKx5gDQMLkcRQIQvtomOIeGbXKFJ1pTzAe7PS1HyPIdW4Rb2Pc5ZljcijJCLlORJpe1c99VfDOUTpn5A06e4WU0yTboyJiAC0eJ8PT0Itv1xc/s1600/*'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2381314077784322587.post-5810182604247673725</id><published>2014-06-15T12:58:00.000-07:00</published><updated>2014-06-19T10:43:39.494-07:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="javascript"/><category scheme="http://www.blogger.com/atom/ns#" term="json"/><category scheme="http://www.blogger.com/atom/ns#" term="mongo"/><category scheme="http://www.blogger.com/atom/ns#" term="mongodb"/><category scheme="http://www.blogger.com/atom/ns#" term="node.js"/><category scheme="http://www.blogger.com/atom/ns#" term="sails"/><category scheme="http://www.blogger.com/atom/ns#" term="sailsjs"/><category scheme="http://www.blogger.com/atom/ns#" term="waterline"/><title type='text'>Seeding a Sails.js Application&#39;s MongoDB Store Standalone Using Node.js and Waterline</title><content type='html'>&lt;div dir=&quot;ltr&quot; style=&quot;text-align: left;&quot; trbidi=&quot;on&quot;&gt;
As I was constructing the Sails.js application for the forthcoming Part 2 of my 3-part MEAN-stack series, I realized that I needed an easy way to seed the MongoDB instance on which my Sails app would run. &amp;nbsp;In this particular example, I wanted to seed three collections (Movie, Person, and MoviePerson) in MongoDB with data defined in .json files that matched the Models for these collections I&#39;d created in Sails.&lt;br /&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
So, how do we go about doing this in a standalone fashion using Node.js and Sails&#39; excellent Waterline ORM?&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
&lt;b&gt;[NOTE: &lt;/b&gt;The approach below is confirmed to work in Sails v0.9.16, but is not functional for the forthcoming v0.10. &amp;nbsp;I will update with a post on that as soon as I&#39;ve got a viable solution]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;
Reusing My Sails Configuration&lt;/h3&gt;
&lt;div&gt;
One thing I wanted to do was to reuse the Models I had already created in Sails, as well as leverage the /config/adapters I&#39;d already set. &amp;nbsp;Furthermore, I didn&#39;t want to have to rely on the full Sails stack to accomplish this: I wanted a simple Node.js script, using Waterline standalone, to perform this task. &amp;nbsp;Finally, I wanted this done in a &quot;promise-y&quot; way -- sequentially executed seeds.&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
First things first.  Let&#39;s assume we have three JSON&#39;s: Movie.json, MoviePerson.json, and Person.json, all corresponding to collections/Sails models of the same name.  Let&#39;s say they live on the root of your Sails app, on a /seeds directory.  Let&#39;s create a seed.js file, intended to be a standalone .js file we can run using Node.js to seed our Mongo instance using Waterline.  
&lt;br /&gt;
&lt;br /&gt;
We need to bring in Waterline, /config/adapters, the sails-mongo adapter, the Promise library, and lodash for some helper functions.  Note that these libraries were pulled down to my Sails project using npm (i.e. &lt;b&gt;sudo npm install lodash waterline sails-mongo promise --save&lt;/b&gt; )
&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;pre class=&quot;brush: jscript&quot;&gt;var _ = require(&#39;lodash&#39;),
    Waterline = require(&#39;waterline&#39;),
    adaptersConfig = require(&#39;../config/adapters&#39;),
    mongoAdapter = require(&#39;sails-mongo&#39;),
    Promise = require(&#39;promise&#39;);
&lt;/pre&gt;
&lt;br /&gt;
&lt;div&gt;
Now that we&#39;ve got the prerequisite libraries, let&#39;s configure the Mongo adapter to reuse /config/adapters.js from my Sails project.  This requires us to define all the properties needed by the sails-mongo library. &lt;br /&gt;
&lt;pre class=&quot;brush: jscript&quot;&gt;// Match our config
var mongoUrl = &#39;mongodb://&#39; +
    adaptersConfig.adapters.mongo.user + &#39;:&#39; + adaptersConfig.adapters.mongo.password + &#39;@&#39; +
    adaptersConfig.adapters.mongo.host + &#39;:&#39; + adaptersConfig.adapters.mongo.port + &#39;/&#39; +
    adaptersConfig.adapters.mongo.database;

mongoAdapter.database = adaptersConfig.adapters.mongo.database;
mongoAdapter.host = adaptersConfig.adapters.mongo.host;
mongoAdapter.port = adaptersConfig.adapters.mongo.port;
mongoAdapter.user = adaptersConfig.adapters.mongo.user;
mongoAdapter.password = adaptersConfig.adapters.mongo.password;
mongoAdapter.config = { url: mongoUrl };
&lt;/pre&gt;
&lt;br /&gt;
The mongoAdapter.config bit was an interesting find.  Originally, I did not define it, and sails-mongo completely vomited.  In tracing down the exception, it appears that sails-mongo (as of v0.9.8) requires that this be defined.
&lt;br /&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;
Reusing My Sails Models and Literally Defined JSON&#39;s&lt;/h3&gt;
&lt;div&gt;
Now that we have our /config/adapters.js doing the work for us, what of the Models in Sails?  Let&#39;s first define how we&#39;re going to seed the Movie collection.  We will encapsulate this in a function, since the ultimate goal is to make this process &quot;promise-y&quot;.
&lt;br /&gt;
&lt;pre class=&quot;brush: jscript&quot;&gt;/**
 * Seeds movies in a promise-y way
 */
var seedMovie = function() {
    var MovieModel = require(&#39;../api/models/Movie&#39;),
        MovieData = require(&#39;./Movie.json&#39;);

    return new Promise(
        function(fulfill, reject) {
            // Seed Movie Data from our JSON
            MovieModel.adapter = &#39;mongo&#39;;
            var Movie = Waterline.Collection.extend(MovieModel);

            // Invoke Waterline
            new Movie({ adapters: { mongo: mongoAdapter }}, function(err, collection) {
                if (err){
                    console.log(&#39;There was a problem initializing the Movie collection.&#39;);
                }
                else {
                    // First nuke all existing movies
                    collection.destroy().done(function(err, deletedMovies) {
                        console.log(&#39;Successfully deleted Movies!&#39;);
                        collection.createEach(MovieData, function(err, models) {
                            if (err || _.isUndefined(models)) {
                                console.log(&#39;Could not create Movie collection!&#39;);
                                fulfill();
                            }
                            else {
                                console.log(&#39;Successfully inserted Movies&#39;);
                                collection.find().done(function(err, existingMovies) {
                                    fulfill(existingMovies);
                                });
                            }
                        });
                    });
                }
            });
        }
    );
};
&lt;/pre&gt;
&lt;br /&gt;
As you can see, by using the magic of Require, we are able to reuse our Movie model and the literal JSON of Movie documents we want in the Mongo collection.  The only curveball is that we have to literally define the adapter for the Movie model before using Waterline to extend and use it.  
&lt;br /&gt;
&lt;br /&gt;
From that point forward, it&#39;s out of the box Waterline.  As you can see, we first destroy the collection to remove any documents in the collection, then invoke a createEach using our Movie.json, which is a literally defined array of Movie objects.  The entire operation is encapsulated in a Promise that is the return type of this function.
&lt;/div&gt;
&lt;br /&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;
Fulfill Our Promises!&lt;/h3&gt;
&lt;div&gt;
Last and certainly not the least, how do we do this sequentially using the magic of Promises?  Assuming we&#39;d already created three functions in seed.js: seedMovie, seedPerson, and seedMoviePerson, all returning promises, we&#39;d just chain them together like so:
&lt;br /&gt;
&lt;pre class=&quot;brush: jscript&quot;&gt;// Node.js: fulfilling more promises than all politicians combined in history
seedMovie()
    .then(function(movies) {
        seedPerson().then(function(persons) {
            seedMoviePerson().then(function(moviePersons){
                console.log(&#39;Seeding process complete!&#39;);
                process.exit();
            });
        });
    });
&lt;/pre&gt;
&lt;/div&gt;
&lt;br /&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;
Wrap-up&lt;/h3&gt;
&lt;div&gt;
Once we are at this point, it&#39;s elementary -- simply run &lt;b&gt;node seed.js&lt;/b&gt; and watch the magic happen.  &lt;a href=&quot;https://github.com/ericnograles/agilemoviedb/tree/master/Server/seeds&quot; target=&quot;_new&quot;&gt;Check out the solution on GitHub&lt;/a&gt; if the step-by-step above didn&#39;t quite work out for you.
&lt;br /&gt;
&lt;br /&gt;
Hopefully, this post is useful for you in your Sails.js endeavors!
&lt;/div&gt;
&lt;/div&gt;
</content><link rel='replies' type='application/atom+xml' href='http://geekstentialism.blogspot.com/feeds/5810182604247673725/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/2381314077784322587/5810182604247673725' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2381314077784322587/posts/default/5810182604247673725'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2381314077784322587/posts/default/5810182604247673725'/><link rel='alternate' type='text/html' href='http://geekstentialism.blogspot.com/2014/06/seeding-sailsjs-applications-mongodb.html' title='Seeding a Sails.js Application&#39;s MongoDB Store Standalone Using Node.js and Waterline'/><author><name>Eric Nograles</name><uri>http://www.blogger.com/profile/02198416401185614213</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJ6RXtY4XZkDuwsIOoO4bKx5gDQMLkcRQIQvtomOIeGbXKFJ1pTzAe7PS1HyPIdW4Rb2Pc5ZljcijJCLlORJpe1c99VfDOUTpn5A06e4WU0yTboyJiAC0eJ8PT0Itv1xc/s1600/*'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2381314077784322587.post-213777114895162035</id><published>2014-06-12T05:30:00.000-07:00</published><updated>2014-06-12T12:59:12.687-07:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="angularjs"/><category scheme="http://www.blogger.com/atom/ns#" term="express"/><category scheme="http://www.blogger.com/atom/ns#" term="javascript"/><category scheme="http://www.blogger.com/atom/ns#" term="mean stack"/><category scheme="http://www.blogger.com/atom/ns#" term="mongodb"/><category scheme="http://www.blogger.com/atom/ns#" term="node"/><category scheme="http://www.blogger.com/atom/ns#" term="node.js"/><category scheme="http://www.blogger.com/atom/ns#" term="sailsjs"/><title type='text'>Growing Up and Growing Large: Modern JavaScript Web Development Using Sails.js and AngularJS (Part 1 of 3)</title><content type='html'>&lt;div dir=&quot;ltr&quot; style=&quot;text-align: left;&quot; trbidi=&quot;on&quot;&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;
The JavaScript Revolution&lt;/h3&gt;
I will be the first to admit: had you told me that JavaScript would become the de-facto standard for web development a few years ago, I would&#39;ve laughed in your face and requested that you take some breathalyzer measurements. &amp;nbsp;How could a language reserved for some ugly client-side DOM manipulation and the general clutter of the view-side of MVC architectures become anything close to enterprise-grade? &amp;nbsp;Heck, JavaScript was even an afterthought, &lt;a href=&quot;https://www.w3.org/community/webed/wiki/A_Short_History_of_JavaScript&quot; target=&quot;_blank&quot;&gt;built in 10 days by Brendan Eich&lt;/a&gt; to give browsers some whizbang on the client-side. &lt;br /&gt;
&lt;br /&gt;
Well, the numbers don&#39;t lie:&amp;nbsp;&lt;a href=&quot;http://www.infoworld.com/t/java-programming/employers-want-java-skills-more-anything-else-242925&quot; target=&quot;_blank&quot;&gt;JavaScript development has grown up and grown large.&lt;/a&gt;..but this ain&#39;t your mid-to-late 2000&#39;s JavaScript, folks. &amp;nbsp;We are talking highly scalable, testable, and standards-compliant multi-platform development. &amp;nbsp;We are talking a language that is very well suited for using NoSQL data stores. &amp;nbsp;We are talking a language that empowers developers to react and implement quickly. &amp;nbsp;Modern JavaScript is to past JavaScript as a jetliner is to the Wright Brothers plane. &amp;nbsp;It is time to take it seriously and for practitioners of classical languages to bone up on this quirky but highly effective interpreted language and its frameworks.&lt;br /&gt;
&lt;br /&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;
Lean and MEAN&lt;/h3&gt;
&lt;div&gt;
PHP had the LAMP stack (Linux/Apache/MySQL/PHP). &amp;nbsp;JavaScript has the MEAN stack (MongoDB/Express/AngularJS/Node.js). &amp;nbsp;Like with the LAMP stack, each letter of the acronym represents important components.&lt;br /&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;h4 style=&quot;text-align: left;&quot;&gt;
&quot;M&quot;: MongoDB&lt;/h4&gt;
&lt;div&gt;
&lt;a href=&quot;http://www.mongodb.org/&quot; target=&quot;_blank&quot;&gt;MongoDB&lt;/a&gt; is the data store of choice for JavaScript-based frameworks. &amp;nbsp;It is, indeed, a NoSQL database: a schemaless document store that, out of the gate, supports REST and JSON, as well as scale-out capabilities (sharding) that you can expect from a NoSQL platform.&lt;br /&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;h4 style=&quot;text-align: left;&quot;&gt;
&quot;E&quot;: Express&lt;/h4&gt;
&lt;div&gt;
&lt;a href=&quot;http://expressjs.com/&quot; target=&quot;_blank&quot;&gt;Express&lt;/a&gt; is a Node.js-based framework (Node.js is, incidentally enough, also part of the overall stack) that represents server-side JavaScript. &amp;nbsp;Yes, server-side like ASP .Net MVC and Spring MVC. &amp;nbsp;This is the Server API portion of a MEAN stack application, the keeper of domain objects via RESTful endpoints.&lt;br /&gt;
&lt;br /&gt;
&lt;a href=&quot;http://www.sailsjs.org/&quot; target=&quot;_blank&quot;&gt;Sails.js&lt;/a&gt; is an excellent MVC framework built on top of Express, and is the framework we will use for this thought exercise. &amp;nbsp;For .Net folks, think of Express as IIS and Sails.js as ASP .Net MVC. &amp;nbsp;For Java folks, think of Express as JBoss and Sails.js as Spring MVC. &amp;nbsp;You could technically build a &quot;traditional&quot; server-side web application on Express without the use of Sails, if you so desired.&lt;br /&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;h4 style=&quot;text-align: left;&quot;&gt;
&quot;A&quot;: AngularJS&lt;/h4&gt;
&lt;div&gt;
&lt;a href=&quot;https://angularjs.org/&quot; target=&quot;_blank&quot;&gt;AngularJS&lt;/a&gt; is a client-side single page application (SPA) framework heavily sponsored by Google that aims to &quot;extend&quot; HTML to represent the dynamic nature of web applications and provide a slick, seamless user experience.&lt;br /&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;h4 style=&quot;text-align: left;&quot;&gt;
&quot;N&quot;: Node.js&lt;/h4&gt;
&lt;div&gt;
While technically a subset of the &quot;E&quot; part of MEAN, &lt;a href=&quot;http://nodejs.org/&quot;&gt;Node.js&lt;/a&gt; does so much more than just the server-side operations. &amp;nbsp;Node.js helps manage JavaScript library dependencies for the Server API as well as AngularJS (using &lt;a href=&quot;https://www.npmjs.org/&quot; target=&quot;_blank&quot;&gt;NPM&lt;/a&gt;...think Maven for Java or NuGet for .Net). &amp;nbsp;It can also help streamline the &quot;build&quot; workflow of a MEAN stack application by running unit tests (in conjunction with a task runner, like &lt;a href=&quot;http://gruntjs.com/&quot; target=&quot;_blank&quot;&gt;Grunt&lt;/a&gt; or &lt;a href=&quot;http://gulpjs.com/&quot; target=&quot;_blank&quot;&gt;Gulp&lt;/a&gt;). &amp;nbsp;Node.js is essentially the glue that binds all the elements together.&lt;br /&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;
A Typical MEAN Stack Web Application&lt;/h3&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhzhbAeXwLJ7RnTOr-fi5Ghrxz7_5hg3PwthOPUt2XPAdNxyvP59BVOZj3vw4Hx3tlzGbnELKihjr5wr8DirSYJm4XZCOmttij3oS9FpPG3Yb-pPUgoy6UCq_juZJ_GvuBiK8QBH99b3ObH/s1600/mean_stack_workflow.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhzhbAeXwLJ7RnTOr-fi5Ghrxz7_5hg3PwthOPUt2XPAdNxyvP59BVOZj3vw4Hx3tlzGbnELKihjr5wr8DirSYJm4XZCOmttij3oS9FpPG3Yb-pPUgoy6UCq_juZJ_GvuBiK8QBH99b3ObH/s1600/mean_stack_workflow.png&quot; height=&quot;353&quot; width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;i&gt;A &quot;typical&quot; use case of a web application&lt;/i&gt;&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: left;&quot;&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: left;&quot;&gt;
As the diagram above indicates, MEAN stack applications give your typically nice, segregated separation of concerns. &amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: left;&quot;&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: left;&quot;&gt;
Sails.js on the server-side exposes RESTful endpoints for external applications to interact with the domain of the application. &amp;nbsp;The domain is persisted on MongoDB and you&#39;ll have &quot;POJSO&#39;s&quot; (Plain-old JavaScript Objects) for Sails.js to interact with the persistence layer. &amp;nbsp;In case you&#39;re wondering, yes, you could easily swap out MongoDB with MySQL or PostgreSQL if you&#39;re still unsure about NoSQL platforms...but I&#39;d recommend against torturing yourself in that fashion. &amp;nbsp;;)&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: left;&quot;&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: left;&quot;&gt;
AngularJS on the client-side focuses on a rich user experience, shuttling any of its data needs to the Sails.js Server API. &amp;nbsp;Likewise, if you were to build, say, a native Android or iOS application, they would interact directly with the Sails.js Server API to get at your application&#39;s domain object model. &amp;nbsp;True multi-platform development!&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: left;&quot;&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: left;&quot;&gt;
While the representation above isn&#39;t anything new in software, doing a full MEAN stack offers many benefits:&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: left;&quot;&gt;
&lt;/div&gt;
&lt;ol style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;JavaScript end-to-end. &amp;nbsp;No context-switching for developers when they cross the boundary from client to server-side. &amp;nbsp;(Read: faster development, real collaboration between client and server)&lt;/li&gt;
&lt;li&gt;Built to scale out. &amp;nbsp;AngularJS is a SPA loaded on the client. &amp;nbsp;Sails.js runs on Express and Node.js which was designed to scale out. &amp;nbsp;Likewise for MongoDB.&lt;/li&gt;
&lt;li&gt;JavaScript has grown up. &amp;nbsp;This ain&#39;t your daddy&#39;s JavaScript. &amp;nbsp;Want complex collection or object manipulation? &amp;nbsp;npm install underscore (or lodash). &amp;nbsp;Need an ORM, Socket.io support, native RESTful/JSON support? &amp;nbsp;Comes out of the box with Sails.js and AngularJS. &amp;nbsp;Dependency Injection? &amp;nbsp;Built into AngularJS, and use require on Sails.js. &amp;nbsp;Unit testing and mocking: do you prefer jasmine or mocha?&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 style=&quot;text-align: left;&quot;&gt;
Okay, So Let&#39;s Build Something!&lt;/h3&gt;
&lt;div&gt;
In Part 2 of this 3 part series, we&#39;re going to get started with a bottom-up approach. &amp;nbsp;Using Sails.js and MongoDB, we will create a domain model and persistence layer for a movie application called the AgileMovieDB.&lt;/div&gt;
&lt;/div&gt;
</content><link rel='replies' type='application/atom+xml' href='http://geekstentialism.blogspot.com/feeds/213777114895162035/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/2381314077784322587/213777114895162035' title='9 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2381314077784322587/posts/default/213777114895162035'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2381314077784322587/posts/default/213777114895162035'/><link rel='alternate' type='text/html' href='http://geekstentialism.blogspot.com/2014/06/growing-up-and-growing-large-modern.html' title='Growing Up and Growing Large: Modern JavaScript Web Development Using Sails.js and AngularJS (Part 1 of 3)'/><author><name>Eric Nograles</name><uri>http://www.blogger.com/profile/02198416401185614213</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJ6RXtY4XZkDuwsIOoO4bKx5gDQMLkcRQIQvtomOIeGbXKFJ1pTzAe7PS1HyPIdW4Rb2Pc5ZljcijJCLlORJpe1c99VfDOUTpn5A06e4WU0yTboyJiAC0eJ8PT0Itv1xc/s1600/*'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhzhbAeXwLJ7RnTOr-fi5Ghrxz7_5hg3PwthOPUt2XPAdNxyvP59BVOZj3vw4Hx3tlzGbnELKihjr5wr8DirSYJm4XZCOmttij3oS9FpPG3Yb-pPUgoy6UCq_juZJ_GvuBiK8QBH99b3ObH/s72-c/mean_stack_workflow.png" height="72" width="72"/><thr:total>9</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2381314077784322587.post-7133519236273707220</id><published>2013-03-17T08:15:00.001-07:00</published><updated>2013-03-17T08:18:21.055-07:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="bi"/><category scheme="http://www.blogger.com/atom/ns#" term="bism"/><category scheme="http://www.blogger.com/atom/ns#" term="business intelligence"/><category scheme="http://www.blogger.com/atom/ns#" term="orlando code camp 2013"/><category scheme="http://www.blogger.com/atom/ns#" term="sql server 2012"/><category scheme="http://www.blogger.com/atom/ns#" term="ssas"/><category scheme="http://www.blogger.com/atom/ns#" term="tabular"/><title type='text'>Orlando Code Camp 2013: SQL 2012 BI</title><content type='html'>&lt;div dir=&quot;ltr&quot; style=&quot;text-align: left;&quot; trbidi=&quot;on&quot;&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Many thanks to everyone who attended my Orlando Code Camp 2013 session on SQL Server 2012 BI. &amp;nbsp;There is great potential for the Tabular Modeling of SSAS, and I hope you&#39;re excited about using it for your BI needs!&lt;/span&gt;&lt;br /&gt;
&lt;div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Here are a few links that I promised that will help you get started quickly in using the entire SQL Server 2012/SharePoint Server 2010/PowerPivot and PowerView stack:&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
&lt;ul style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;a href=&quot;http://msdn.microsoft.com/en-us/library/hh231687.aspx&quot; target=&quot;_blank&quot;&gt;Install Process of SharePoint 2010 With PowerPivot and PowerView Support&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;a href=&quot;http://bit.ly/UMoSeF&quot; target=&quot;_blank&quot;&gt;A Detailed Guide to Tabular Modeling for Beginners&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;a href=&quot;http://bit.ly/ObJbNR&quot; target=&quot;_blank&quot;&gt;Quickstart Guide for PowerView&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;a href=&quot;http://www.telerik.com/products/reporting.aspx&quot; target=&quot;_blank&quot;&gt;Telerik&#39;s Reporting Controls: SSAS Tabular Visualization &quot;A La Carte&quot; (for those who prefer not to use SharePoint/PowerView)&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;For those with Subversion who want to get at the artifacts from the session yesterday, perform a checkout on&amp;nbsp;&lt;a href=&quot;https://edg.sourcerepo.com/edg/OrlandoCodeCamp2013&quot;&gt;https://edg.sourcerepo.com/edg/OrlandoCodeCamp2013&lt;/a&gt;&amp;nbsp;to get the Visual Studio solution and the backup of the NFLDW database we used as our source for SSAS Tabular.&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Finally, feel free to contact me on Twitter (&lt;a href=&quot;https://twitter.com/grales&quot; target=&quot;_blank&quot;&gt;@grales&lt;/a&gt;) or e-mail (eric.v.nograles@gmail.com) if you have any questions/issues or wanted to bounce some ideas around SSAS tabular and its applications in corporate BI.&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Thanks again to the Orlando .Net User Group for the opportunity to speak at this fun event! &amp;nbsp;Hopefully, I&#39;ll be seeing you all again next year!&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;
&lt;br /&gt;
&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right: auto; text-align: center;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiINdz3Puc7zQJuxxjy_dnHxJ-bLgG6EUdOK4mNm_uwcFH0wj83jMMoZ2anbVBkqABu9Ra9iXD9ozAkLqe7px-lVl2HXTA5FqxgNxj3combdl1SXBSK2P8nCLvO1a6wc2oEqKmo6Lr-K_Ho/s1600/AT-CodeCamp-2013.jpg&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;300&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiINdz3Puc7zQJuxxjy_dnHxJ-bLgG6EUdOK4mNm_uwcFH0wj83jMMoZ2anbVBkqABu9Ra9iXD9ozAkLqe7px-lVl2HXTA5FqxgNxj3combdl1SXBSK2P8nCLvO1a6wc2oEqKmo6Lr-K_Ho/s400/AT-CodeCamp-2013.jpg&quot; width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;The AgileThought family thanks everyone for attending our sessions at Orlando Code Camp 2013!&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
</content><link rel='replies' type='application/atom+xml' href='http://geekstentialism.blogspot.com/feeds/7133519236273707220/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/2381314077784322587/7133519236273707220' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2381314077784322587/posts/default/7133519236273707220'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2381314077784322587/posts/default/7133519236273707220'/><link rel='alternate' type='text/html' href='http://geekstentialism.blogspot.com/2013/03/orlando-code-camp-2013-sql-2012-bi.html' title='Orlando Code Camp 2013: SQL 2012 BI'/><author><name>Eric Nograles</name><uri>http://www.blogger.com/profile/02198416401185614213</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJ6RXtY4XZkDuwsIOoO4bKx5gDQMLkcRQIQvtomOIeGbXKFJ1pTzAe7PS1HyPIdW4Rb2Pc5ZljcijJCLlORJpe1c99VfDOUTpn5A06e4WU0yTboyJiAC0eJ8PT0Itv1xc/s1600/*'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiINdz3Puc7zQJuxxjy_dnHxJ-bLgG6EUdOK4mNm_uwcFH0wj83jMMoZ2anbVBkqABu9Ra9iXD9ozAkLqe7px-lVl2HXTA5FqxgNxj3combdl1SXBSK2P8nCLvO1a6wc2oEqKmo6Lr-K_Ho/s72-c/AT-CodeCamp-2013.jpg" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2381314077784322587.post-554358808110063706</id><published>2013-01-29T07:20:00.002-08:00</published><updated>2013-01-29T07:23:47.850-08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="android"/><category scheme="http://www.blogger.com/atom/ns#" term="database"/><category scheme="http://www.blogger.com/atom/ns#" term="existing mysql databases"/><category scheme="http://www.blogger.com/atom/ns#" term="google android"/><category scheme="http://www.blogger.com/atom/ns#" term="mysql"/><category scheme="http://www.blogger.com/atom/ns#" term="sqliteopenhelper"/><title type='text'>Packaging Existing SQLite Databases With Your Google Android Application</title><content type='html'>&lt;div dir=&quot;ltr&quot; style=&quot;text-align: left;&quot; trbidi=&quot;on&quot;&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Many examples of Android applications on the Internet assume that the apps you develop will, by default, create a new, blank MySQL database at its first run-time. &amp;nbsp;However, there aren&#39;t many examples of a situation where one would create a separate MySQL database which would then be used by the Android application. &amp;nbsp;A potential solution, borrowed from databases on other platforms that use Continuous Integration, would be to embed seeding SQL statements in the application to run on creates or upgrades, but this may be prohibitive for some developers in terms of practicality and time, in addition to the fact that Android does some backend wizardry with SQLite databases to have them work properly with its SQLiteOpener class. &amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;So, how would one embed an existing SQLite database to the application&#39;s assets folder and use it at runtime? &amp;nbsp;Stay tuned for the solution after the jump!&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;/span&gt;&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;A fellow blogger, Juan-Manuel Fluxa, &lt;a href=&quot;http://www.reigndesign.com/blog/using-your-own-sqlite-database-in-android-applications/&quot; target=&quot;_blank&quot;&gt;was able to distill a simple solution to this conundrum&lt;/a&gt;. &amp;nbsp;It involves a few manual steps on your existing MySQL database. &amp;nbsp;To perform these steps, either have the SQLite command line handy or download a GUI-based tool such as &lt;a href=&quot;http://sourceforge.net/projects/sqlitebrowser/&quot; target=&quot;_blank&quot;&gt;SQLite Database Browser&lt;/a&gt;. &amp;nbsp;Mimicking the original blog post, we are using SQLite Database Browser on our existing database, which on the filesystem exists as SampleDatabase.db.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Subversion Checkout Source for the Example Below:&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;a href=&quot;https://edg.sourcerepo.com/edg/Geekstentialism/Geekstentialism.AndroidWithDB/&quot; target=&quot;_blank&quot;&gt;https://edg.sourcerepo.com/edg/Geekstentialism/Geekstentialism.AndroidWithDB/&lt;/a&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;b&gt;&lt;u&gt;Step 1: SQLite Manual Steps&lt;/u&gt;&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;ol style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Execute this SQL Script: CREATE TABLE &quot;android_metadata&quot;(&quot;locale&quot; TEXT DEFAULT &#39;en-US&#39;)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;On ALL of the tables in your database, add a column called _id which will be an INTEGER PRIMARY KEY column.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Compact and save your database&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;b&gt;&lt;u&gt;Step 1 Recap: What the Heck Did We Just Do?&lt;/u&gt;&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;The first SQL statement creates an embedded table expected by Android. &amp;nbsp;The android_metadata table is used by Android&#39;s SQLite helper objects to facilitate connectivity to an app&#39;s database.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;The second task has you create the expected primary key field by Android&#39;s SQLite helper objects. &amp;nbsp;Now, you obviously must be asking the question, &quot;what if I already have a Primary Key field defined&quot;? &amp;nbsp;Unfortunately, due to the behavior of the Android SQLite helper objects, whatever your existing primary key may be will need to take a backseat to the &quot;_id&quot; field. &amp;nbsp;To ensure that your data can still be retrieved quickly using your database&#39;s originally intended keys, I would highly recommend creating indices on those columns.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;u style=&quot;font-weight: bold;&quot;&gt;Step 2: Android Magic&lt;/u&gt;&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;ol style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Add the database you created above to your application&#39;s assets folder.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Within your application&#39;s data namespace (or however you&#39;ve arranged your tiered architecture), create an object representation of your database and have it implement SQLiteOpenHelper. &amp;nbsp;For brevity&#39;s sake, &lt;a href=&quot;https://edg.sourcerepo.com/edg/Geekstentialism/Geekstentialism.AndroidWithDB/trunk/src/com/geekstentialism/androidwithdb/data/SampleDatabase.java&quot; target=&quot;_blank&quot;&gt;click here to see the source of the example project above&lt;/a&gt;. &amp;nbsp;This code example was taken from Juan&#39;s original blog post above.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;b&gt;&lt;u&gt;Step 2 Recap: What the Heck Did We Just Do?&lt;/u&gt;&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Adding your MySQL database to the assets folder ensures that it becomes part of your application on deployment.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;The SampleDatabase.java class above implements SQLiteOpenHelper to modify the default behavior of this class. &amp;nbsp;As you can see in the code, it first checks for the existence of the database on the Android device&#39;s filesystem. &amp;nbsp;If the database does not exist, it calls the getReadableDatabase function of the SQLiteOpenHelper to create a default, blank database for your application.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;After the blank database is created, the copyDatabase method streams in the database you dropped in the assets folder of your application, overwriting the blank skeleton. &amp;nbsp;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;To use the SampleDatabase.java class above in your UI (or, preferably, your Data Provider class), after instantiating it, call the createDatabase method to have it perform the magic above. &amp;nbsp;Voila, your MySQL database is now deployed as part of your Android application!&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;The example above does not follow the generally recommended pattern of an Android Data Provider -- we&#39;ll get to that in a later blog post. &amp;nbsp;If you&#39;re familiar with the pattern, the implementation above should be very simple to integrate with your Data Provider solution.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;b&gt;&lt;u&gt;Gotchas&lt;/u&gt;&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;ul style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;The path to your application&#39;s database on the Android device&#39;s filesystem will always be /data/data/com.yourandroidapp/databases, where com.yourandroidapp is obviously the package name you have chosen for your app.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Remember that Google Play has a 50 MB file size limitation for your .apk. &amp;nbsp;Because you are dropping your MySQL database to the assets folder, it becomes part of the .apk.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Indices are your friends, but remember they also take up disk space. &amp;nbsp;If your application has a fairly large database that retrieves its records using your originally intended primary keys (versus the embedded &quot;_id&quot; fields we created above), create indices on those columns, but be cognizant of the disk space growth.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;
</content><link rel='replies' type='application/atom+xml' href='http://geekstentialism.blogspot.com/feeds/554358808110063706/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/2381314077784322587/554358808110063706' title='17 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2381314077784322587/posts/default/554358808110063706'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2381314077784322587/posts/default/554358808110063706'/><link rel='alternate' type='text/html' href='http://geekstentialism.blogspot.com/2013/01/packaging-existing-sqlite-databases.html' title='Packaging Existing SQLite Databases With Your Google Android Application'/><author><name>Eric Nograles</name><uri>http://www.blogger.com/profile/02198416401185614213</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJ6RXtY4XZkDuwsIOoO4bKx5gDQMLkcRQIQvtomOIeGbXKFJ1pTzAe7PS1HyPIdW4Rb2Pc5ZljcijJCLlORJpe1c99VfDOUTpn5A06e4WU0yTboyJiAC0eJ8PT0Itv1xc/s1600/*'/></author><thr:total>17</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2381314077784322587.post-2865700670078187835</id><published>2012-09-15T20:15:00.001-07:00</published><updated>2012-09-18T08:10:21.795-07:00</updated><category scheme="http://www.blogger.com/atom/ns#" term=".net"/><category scheme="http://www.blogger.com/atom/ns#" term="dependency injection"/><category scheme="http://www.blogger.com/atom/ns#" term="inversion of control"/><category scheme="http://www.blogger.com/atom/ns#" term="unity"/><category scheme="http://www.blogger.com/atom/ns#" term="wcf"/><category scheme="http://www.blogger.com/atom/ns#" term="web services"/><title type='text'>Using Unity for Dependency Injection With WCF Services</title><content type='html'>&lt;div dir=&quot;ltr&quot; style=&quot;text-align: left;&quot; trbidi=&quot;on&quot;&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;The Dependency Injection (DI) pattern of software development offers many benefits in the area of separation of concerns. &amp;nbsp;The loose-coupling nature of this pattern allows for truly atomic unit tests and (theoretically) more effective development. &amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;While the DI pattern is well documented in web UI technologies that espouse separation of concerns (such as MVC), the use of this pattern in the less glamorous area of application integration using web services is a little leaner on the volume of documentation. &amp;nbsp;Being that application integration apps such as WCF Services have a tendency to perform some elaborate transportation and transformation logic, having the benefits of the DI pattern greatly improves the effectiveness of the development of these applications.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;So, in terms of WCF Services, how exactly do we achieve the DI pattern? &amp;nbsp;Thanks to the lightweight Unity library, we can offer the following DI benefits for a WCF Service:&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;ul style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;File-less activation of services (no more pesky .svc files to maintain)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Loosely coupled development&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Rapid and agile development thanks to unit testing&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Coding commences after the jump!
&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
For this example, we will create a WCF Service project (hosted on IIS) which will contain the these two services, with their corresponding operations:&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;u&gt;DateOperations.svc&lt;/u&gt;&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;ul style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;DayOfYear [DateTime Input, Integer Output]: Returns the day of the year for a given date&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;CalendarQuarter [DateTime Input, String Output]: Returns a fiscal quarter string (ex. &quot;3Q12&quot;) based on calendar quarters&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;u&gt;NumericOperations.svc&lt;/u&gt;&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;ul style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;CalculateSalesTax [Decimal and String Input, Decimal Output]: Returns the sales tax for a total value for a given state.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Again, we will want to achieve file-less services and loosely coupled development with this WCF service. &amp;nbsp;So, let&#39;s code away!&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;[As an aside, the completed solution can be found in its entirety &lt;a href=&quot;https://edg.sourcerepo.com/edg/Geekstentialism/Geekstentialism.SampleWCF/trunk/&quot;&gt;here for download&lt;/a&gt;! &amp;nbsp;If you have a Subversion client, grab a Checkout from&amp;nbsp;&lt;/span&gt;
&lt;a href=&quot;https://edg.sourcerepo.com/edg/Geekstentialism/Geekstentialism.SampleWCF/trunk/&quot;&gt;https://edg.sourcerepo.com/edg/Geekstentialism/Geekstentialism.SampleWCF/trunk/&lt;/a&gt;&amp;nbsp;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;]&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;u&gt;Defining the Application Architecture&lt;/u&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Firing up Visual Studio 2010, we create a solution with the following projects:&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;ul style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;b&gt;Geekstentialism.SampleWCF.Web&lt;/b&gt;: Our WCF Service Application web project which will be hosted on IIS. &amp;nbsp;Go ahead and remove the default .svc and interface files the template creates, we will have no use for them in this example.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;b&gt;Geekstentialism.SampleWCF.Services&lt;/b&gt;: Class library which contains the concrete implementations of our services&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;b&gt;Geekstentialism.SampleWCF.Common&lt;/b&gt;: Class library which contains the interfaces implemented by our services&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;b&gt;Geekstentialism.SampleWCF.Components&lt;/b&gt;: Class library which contains operations specific to components. &amp;nbsp;For example, a NumericOperationsComponent which handles the calculations for the NumericOperations service.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;b&gt;Geekstentialism.SampleWCF.Tests&lt;/b&gt;: The Unit Test project&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;There are a couple of references we need to add to our solution. &amp;nbsp;We&#39;ll need to add some Library Package References from NuGet to&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Geekstentialism.SampleWCF.Web, specifically for Unity and Unity Interception. &amp;nbsp;Also, we&#39;ll need to add a Library Package Reference in the Unit Test project to Moq, which is a nice, lightweight library which will enable us to mock up the dependent interfaces for each of our layers above.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Finally, following separation of concerns, the projects themselves need to reference each other in the following pattern:&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;ul style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;b&gt;Geekstentialism.SampleWCF.Web&lt;/b&gt;: References&amp;nbsp;&lt;/span&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Geekstentialism.SampleWCF.Common and&amp;nbsp;&lt;/span&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Geekstentialism.SampleWCF.Services&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;b&gt;Geekstentialism.SampleWCF.Services&lt;/b&gt;: References&amp;nbsp;&lt;/span&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Geekstentialism.SampleWCF.Common and&amp;nbsp;&lt;/span&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Geekstentialism.SampleWCF.Components&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;b&gt;Geekstentialism.SampleWCF.Components&lt;/b&gt;: References&amp;nbsp;&lt;/span&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Geekstentialism.SampleWCF.Common&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;b&gt;Geekstentialism.SampleWCF.Tests&lt;/b&gt;: References all projects&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhl5sUXJkYi2MShcHI9uQ24fEvkwXVZvfo_sY5b6yuxYjcYyfoJLhbTsOqzNU5w0l_9Rh-PBOKnhvWljwrqUbtS8e43fX8BnTvGYt6FcTHQBgeuOenDu50kWp2CzigJjfkXtWMIHP0jQT2e/s1600/Solution+Overview.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;259&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhl5sUXJkYi2MShcHI9uQ24fEvkwXVZvfo_sY5b6yuxYjcYyfoJLhbTsOqzNU5w0l_9Rh-PBOKnhvWljwrqUbtS8e43fX8BnTvGYt6FcTHQBgeuOenDu50kWp2CzigJjfkXtWMIHP0jQT2e/s320/Solution+Overview.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;So, now we&#39;ve got our solution all setup with all the proper references to external libraries (Unity, Unity.Interception, and Moq) and each other. &amp;nbsp;We can begin the fun part...coding!&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;u&gt;The Foundation: Interfaces&lt;/u&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;I personally like starting from the ground up, so let&#39;s attack the&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Geekstentialism.SampleWCF.Common project first. &amp;nbsp;First, we create an Interfaces folder. &amp;nbsp;We&#39;re going to need to create the following interfaces in this folder (note, links follow you to the each file in source control):&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;ul style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://edg.sourcerepo.com/edg/Geekstentialism/Geekstentialism.SampleWCF/trunk/Geekstentialism.SampleWCF.Common/Interfaces/IDateOperationsComponent.cs&quot;&gt;IDateOperationsComponent&lt;/a&gt;&lt;/b&gt;: The interface which is the basis of DateOperationsComponent, the heavy lifting arm of the DateOperationsService.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://edg.sourcerepo.com/edg/Geekstentialism/Geekstentialism.SampleWCF/trunk/Geekstentialism.SampleWCF.Common/Interfaces/INumericOperationsComponent.cs&quot;&gt;INumericOperationsComponent&lt;/a&gt;&lt;/b&gt;: The interface which is the basis for NumericOperationsComponent, the heavy lifting arm of the NumericOperationsService.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://edg.sourcerepo.com/edg/Geekstentialism/Geekstentialism.SampleWCF/trunk/Geekstentialism.SampleWCF.Common/Interfaces/IDateOperationsService.cs&quot;&gt;IDateOperationsService&lt;/a&gt;&lt;/b&gt;: The contract which the DateOperationsService will follow.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://edg.sourcerepo.com/edg/Geekstentialism/Geekstentialism.SampleWCF/trunk/Geekstentialism.SampleWCF.Common/Interfaces/INumericOperationsService.cs&quot;&gt;INumericOperationsService&lt;/a&gt;&lt;/b&gt;: The contract which the NumericOperationsService will follow.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;For the sake of brevity, both the Component and Services interfaces above will define the corresponding operations we defined above: DayOfYear and CalendarQuarter for the Date Operations and CalculateSalesTax for the Numeric Operations.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Also, the service interfaces (aka the ones our actual WCF services will implement) will define the ServiceContract and OperationContract text decorations in preparation for WCF Service use.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;


&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;u&gt;The Heavy Lifting: Components&lt;/u&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;u&gt;&lt;br /&gt;&lt;/u&gt;&lt;/span&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Following a familiar n-tiered architecture (and good separation of concerns), the actual implementation of the operations above will not live on the Service code itself. &amp;nbsp;We have a Component layer which will facilitate this logic. &amp;nbsp;Some circles call this the Business Object layer, but it serves the same purpose: do the heavy lifting behind the scenes! &amp;nbsp;These objects will be created in the&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Geekstentialism.SampleWCF.Components class library project.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;We will implement the following components:&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;ul style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://edg.sourcerepo.com/edg/Geekstentialism/Geekstentialism.SampleWCF/trunk/Geekstentialism.SampleWCF.Components/DateOperationsComponent.cs&quot;&gt;DateOperationsComponent&lt;/a&gt;&lt;/b&gt;: Implements the IDateOperationsComponent above. &amp;nbsp;The actual implementation for DayOfYear and CalendarQuarter will be implemented in this object.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://edg.sourcerepo.com/edg/Geekstentialism/Geekstentialism.SampleWCF/trunk/Geekstentialism.SampleWCF.Components/NumericOperationsComponent.cs&quot;&gt;NumericOperationsComponent&lt;/a&gt;&lt;/b&gt;: Implements the INumericOperationsComponent above. &amp;nbsp;Again, we will place the concrete implementation for CalculateSalesTax here.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;So, now that we have concrete implementations of the intended outputs for the service operations themselves, let&#39;s actually build the &quot;public facing&quot; part of our project -- the Services themselves!&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;u&gt;The Integrators: Services&lt;/u&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;u&gt;&lt;br /&gt;&lt;/u&gt;&lt;/span&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;This is the layer where we get into Dependency Injection, the&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Geekstentialism.SampleWCF.Services project&lt;/span&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;. &amp;nbsp;If you think about our structure above, we will create a &lt;a href=&quot;https://edg.sourcerepo.com/edg/Geekstentialism/Geekstentialism.SampleWCF/trunk/Geekstentialism.SampleWCF.Services/DateOperationsService.cs&quot;&gt;DateOperationsService&lt;/a&gt; that is dependent on the DateOperationsComponent. &amp;nbsp;However, since one of the key tenets to DI is to program against interfaces, we are actually going to create a constructor for this class with one parameter, the IDateOperationsComponent interface. &amp;nbsp;We will then create a &lt;a href=&quot;https://edg.sourcerepo.com/edg/Geekstentialism/Geekstentialism.SampleWCF/trunk/Geekstentialism.SampleWCF.Services/NumericOperationsService.cs&quot;&gt;NumericOperationsService&lt;/a&gt; with the same structure, except its constructor&#39;s one parameter is the INumericOperationsComponent interface.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Both services will obviously also implement their respective interfaces (IDateOperationsService and INumericOperationsService, respectively). &amp;nbsp;So, how do we hook up the corresponding components&#39; methods to these interfaces&#39; methods? &amp;nbsp;Quite simply, we define a private object that will hold the interfaces mentioned above and call the methods from those interfaces directly. &amp;nbsp;For example, for DateOperationsService, we will create a private object called dateOperationsComponent, which is an IDateOperationsComponent type. &amp;nbsp;In the constructor which takes this interface, we assign this private object&#39;s value to the interface coming in on the constructor. &amp;nbsp;We will follow the same pattern for the NumericOperationsService.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;For those who are new to Dependency Injection, take heed that we will put everything together when we get to the topmost layer, the web project. &amp;nbsp;However, if you care for some light reading, &lt;a href=&quot;http://www.captaindebug.com/2011/10/benefits-of-dependency-injection.html&quot;&gt;here is quite possibly the easiest explanation for the DI concept and why we apply this pattern in software development&lt;/a&gt;.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;u&gt;The Sanity Checks: Unit Tests&lt;/u&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;u&gt;&lt;br /&gt;&lt;/u&gt;&lt;/span&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Now, we get to perhaps the second most important thing to get right apart from the Web Project -- the test project. &amp;nbsp;While I can imagine some groans coming from my fellow developers, allow me to offer my experiences with unit tests on a properly decoupled application such as the one we have just built. &amp;nbsp;When properly constructed, your unit tests will save you an unbelievable amount of time, especially when you make changes to your software. &amp;nbsp;Get into the habit of creating proper unit tests and configuring your projects to run them first. &amp;nbsp;It can be an odious task, but it is worthwhile.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Now that I&#39;m off my soap box, let&#39;s write up some unit tests for our decoupled components. &amp;nbsp;We&#39;ll create the following folders: Services and Components. &amp;nbsp;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;In the &lt;a href=&quot;https://edg.sourcerepo.com/edg/Geekstentialism/Geekstentialism.SampleWCF/trunk/Geekstentialism.SampleWCF.Tests/Services/&quot;&gt;Services folder&lt;/a&gt;, we will create the DateOperationsServiceTest and NumericOperationsServiceTest unit test classes.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;In the &lt;a href=&quot;https://edg.sourcerepo.com/edg/Geekstentialism/Geekstentialism.SampleWCF/trunk/Geekstentialism.SampleWCF.Tests/Components/&quot;&gt;Components folder&lt;/a&gt;, we will create the DateOperationsComponentTest and NumericOperationsComponentTest unit test classes.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Remember the reference to Moq we had created earlier in this&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;a href=&quot;https://edg.sourcerepo.com/edg/Geekstentialism/Geekstentialism.SampleWCF/trunk/Geekstentialism.SampleWCF.Tests/&quot;&gt;Geekstentialism.SampleWCF.Tests&lt;/a&gt; project? &amp;nbsp;We&#39;re going to put them to use, specifically in the Services tests. &amp;nbsp;This is the beauty of DI and programming to interfaces -- when we create our unit tests for our service operations, we do not actually instantiate concrete DateOperationsComponent and NumericOperationsComponent classes. &amp;nbsp;We instead mock up these components (using Moq) since these unit tests only care for the logic happening on the Service layer. &amp;nbsp;So, we appropriately mock up our Components classes in the Services tests and write a few assertions. &amp;nbsp;Since these services are so simple, we simply assert that the methods on the Component Layer were called once. &amp;nbsp;Loosely coupled software ftw!&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Finally, since the Components themselves aren&#39;t dependent on anything, writing Unit tests for these are fairly straightforward, testing the functionality requested above.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;u&gt;&lt;br /&gt;&lt;/u&gt;&lt;/span&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;u&gt;The Front-end: Web Project&lt;/u&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;u&gt;&lt;br /&gt;&lt;/u&gt;&lt;/span&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;So we have a very nice structure above, separating the concerns of the service and business logic. &amp;nbsp;What you&#39;re most likely asking right now is, how the heck do these services get exposed? &amp;nbsp;They are, after all, just simple class libraries, and we didn&#39;t create concrete .svc files on the web project (nor do we want to!). &amp;nbsp;This is where Unity works its magic. &amp;nbsp;The key to dependency injection is to let the DI container know what the default constructor for an interface is, and all this magic happens in the web.config in the&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Geekstentialism.SampleWCF.Web project.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Before we muck around the web.config, let&#39;s drop some prerequisites to get the web project. &amp;nbsp;Let&#39;s create a new folder called Unity. &amp;nbsp;We&#39;re going to create four supporting objects: UnityServiceHost, UnityServiceHostFactory, UnityServiceBehavior, and UnityInstanceProvider. &amp;nbsp;Thanks to an example from our friends at Avington Solutions, &lt;a href=&quot;https://edg.sourcerepo.com/edg/Geekstentialism/Geekstentialism.SampleWCF/trunk/Geekstentialism.SampleWCF.Web/Unity/&quot;&gt;we have this code readily available in the full example which you are free to use&lt;/a&gt;. &amp;nbsp;In short, these classes will do the magic of using the Unity containers and activating the services we configure in web.config.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;With these supporting objects in place, let&#39;s do some web.config hacking! &amp;nbsp;The first thing we need to do is to define a config section for Unity, where we&#39;ll define the default constructs for our interfaces.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Next, let&#39;s modify our system.ServiceModel node. &amp;nbsp;We want to use the aforementioned supporting Unity classes for service activation, so we won&#39;t have to maintain those pesky .svc files. &amp;nbsp;This will be defined in the serviceHostingEnvironment node. &amp;nbsp;We&#39;re going to create a serviceActivations node and then configure two services : ./DateOperations.svc, which will be activated using &lt;/span&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Geekstentialism.SampleWCF.Services.DateOperationsService, and ./NumericOperations.svc which will be activated using&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Geekstentialism.SampleWCF.Services.NumericOperationsService. &amp;nbsp;You&#39;ll notice that we are also using our newly created UnityServiceHostFactory as the factory for both services.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Finally, we&#39;ll define the unity config section with the default concrete objects for IDateOperationsComponent and INumericOperationsComponent respectively. &amp;nbsp;&lt;a href=&quot;https://edg.sourcerepo.com/edg/Geekstentialism/Geekstentialism.SampleWCF/trunk/Geekstentialism.SampleWCF.Web/Web.config&quot;&gt;This is our web.config in its finished state.&lt;/a&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Voila! &amp;nbsp;With this architecture in place, we now have a file-less WCF Service Application with two services and proper Dependency Injection. &amp;nbsp;We&#39;ve also got some nifty unit classes in place that will quickly identify any logical issues for us should we change any layer of our WCF application.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;When we invoke our operations using their WSDL&#39;s and soapUI as our primary testing tool, we can see they work beautifully.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgD1_DTm3Rp0aveK8Z5x7ZXtymshNR4l1vzJfV5SXSMNAofPrLeeTWKuixmesp6rc_AlqiBbX86gaL4W5CM09oqNu2ZPFFDBihe9Pno3FlE4xzoPR4wyCcm1UTnFuvPYzFfl3CdDhGFSFhS/s1600/soapUI+Calls.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;220&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgD1_DTm3Rp0aveK8Z5x7ZXtymshNR4l1vzJfV5SXSMNAofPrLeeTWKuixmesp6rc_AlqiBbX86gaL4W5CM09oqNu2ZPFFDBihe9Pno3FlE4xzoPR4wyCcm1UTnFuvPYzFfl3CdDhGFSFhS/s400/soapUI+Calls.png&quot; width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;u&gt;The Wrap-up&lt;/u&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;u&gt;&lt;br /&gt;&lt;/u&gt;&lt;/span&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Using Dependency Injection with WCF Services offers us the same benefits as it does for more glamorous architectures such as MVC. &amp;nbsp;Using the DI techniques above, you can craft even more complex WCF Services -- perhaps end-to-end transport services -- and ensure that your software will be agile, well-tested, and elegant!&lt;/span&gt;&lt;/div&gt;
</content><link rel='replies' type='application/atom+xml' href='http://geekstentialism.blogspot.com/feeds/2865700670078187835/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/2381314077784322587/2865700670078187835' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2381314077784322587/posts/default/2865700670078187835'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2381314077784322587/posts/default/2865700670078187835'/><link rel='alternate' type='text/html' href='http://geekstentialism.blogspot.com/2012/09/using-unity-for-dependency-injection.html' title='Using Unity for Dependency Injection With WCF Services'/><author><name>Eric Nograles</name><uri>http://www.blogger.com/profile/02198416401185614213</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJ6RXtY4XZkDuwsIOoO4bKx5gDQMLkcRQIQvtomOIeGbXKFJ1pTzAe7PS1HyPIdW4Rb2Pc5ZljcijJCLlORJpe1c99VfDOUTpn5A06e4WU0yTboyJiAC0eJ8PT0Itv1xc/s1600/*'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhl5sUXJkYi2MShcHI9uQ24fEvkwXVZvfo_sY5b6yuxYjcYyfoJLhbTsOqzNU5w0l_9Rh-PBOKnhvWljwrqUbtS8e43fX8BnTvGYt6FcTHQBgeuOenDu50kWp2CzigJjfkXtWMIHP0jQT2e/s72-c/Solution+Overview.png" height="72" width="72"/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2381314077784322587.post-4966917286476189156</id><published>2012-06-17T08:49:00.004-07:00</published><updated>2012-06-17T08:51:52.463-07:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="bi comparison"/><category scheme="http://www.blogger.com/atom/ns#" term="business intelligence"/><category scheme="http://www.blogger.com/atom/ns#" term="Pentaho Mondrian"/><category scheme="http://www.blogger.com/atom/ns#" term="SQL Server Analysis Services 2008"/><title type='text'>Business Intelligence Shootout: Microsoft SQL Server 2008 BI vs. Pentaho BI Enterprise Edition (Part 2: SQL Server Analysis Services)</title><content type='html'>&lt;div dir=&quot;ltr&quot; style=&quot;text-align: left;&quot; trbidi=&quot;on&quot;&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;b&gt;The BI Platform That They Already Own&lt;/b&gt;&lt;br /&gt;So, now that &lt;a href=&quot;http://geekstentialism.blogspot.com/2012/02/business-intelligence-shootout_20.html&quot;&gt;we&#39;ve seen some of the basic capabilities of Pentaho Analysis Services (aka Mondrian)&lt;/a&gt;, let&#39;s check the other side of the ring where SQL Server Analysis Services (SSAS) sits. &amp;nbsp;The entire SQL Server BI stack is a very interesting case. &amp;nbsp;Starting with SQL Server 2005, Microsoft began packaging their entire BI suite with a Standard Edition license, and I mean the whole shebang. &amp;nbsp;SSIS, SSAS, SSRS, SSMS, and BIDS...the whole gang to satisfy all data analytic needs. &amp;nbsp;Apparently, this was not emphasized enough in the literature for Microsoft SQL Server, because more often than not, this can be news to IT folks in enterprises. &amp;nbsp;Usually, it&#39;s good news, as the company may have already made a sizeable investment in SQL Server, and the icing on the cake is a world-class BI platform.&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Now, for those companies who haven&#39;t already made an investment in SQL Server, the barrier to entry for SQL Server BI may be the price of a license. &amp;nbsp;After all, the bulk of the license fee pays for the RDBMS, one would argue. &amp;nbsp;However, this does not diminish the value of the SQL Server BI stack. &amp;nbsp;This platform has come a long way since Analysis Services was first revealed in SQL Server 2000, and we will take a deep dive into the capabilities of the latest features within SQL Server 2008 R2 Business Intelligence.&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;/span&gt;&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;b&gt;SQL Server Analysis Services (SSAS)&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;u&gt;Setup&lt;/u&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
&lt;/div&gt;
&lt;ul style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Windows 7 Home Premium (64-bit)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;8 GB RAM&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Microsoft SQL Server 2008 R2 (SSAS, SSIS, SSRS, SSMS, BIDS)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Source Control Link: Click Here&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;u&gt;Design Tools&lt;/u&gt;&lt;/span&gt;&lt;br /&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
&lt;/div&gt;
&lt;ul style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;SQL Server Management Studio (aka SSMS, desktop application)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Business Intelligence Development Studio (aka BIDS, desktop IDE)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;u&gt;Overview&lt;/u&gt;&lt;/span&gt;&lt;br /&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;This setup of SQL Server 2008 BI collocates both the relational database system with SSAS, as with our Mondrian deep dive. &amp;nbsp;Now, you may be saying to yourself, the resources available for SQL Server 2008 BI is considerably more than what we offered on a meager VM for PAS. &amp;nbsp;However, this shoot-out isn&#39;t designed to empirically prove which platform is faster -- the intent here is to see which product ,in terms of end-to-end development and consumption of BI, has the edge over the other. &amp;nbsp;So, we&#39;re talking ease of development, usability, and product cohesion versus &quot;how many milliseconds did it take to bring back those 200,000 tuples&quot;.&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;With that in mind, setup of a collocated SQL Server BI platform is also a cinch. &amp;nbsp;Just simply follow the steps on the SQL Server install (we chose default options, apart from our instance names, which we specified as SQLDEV01), let the install run, grab a cup of tea, then come back to see SQL Server 2008 R2 running on your machine. &amp;nbsp;(Of course, when deploying to a production environment, the install gets a little more involved, with ActiveDirectory identities, RDBMS collation settings, default file locations, etc.)&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right: auto; text-align: center;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgAtYFXNuvuOuhdcekHq8yWdNy8nGN9eot6ZR9Gf5g1OYVOK8r5lGTbmrfdiX31ucs6nLVcU3TDPWjE1UJegPnAQWQTVDI7dQjpboNJfxo9k-jPTwRajNMtavrOCXoVNheCYKEQPg9M62FG/s1600/Configuration+Manager.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;202&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgAtYFXNuvuOuhdcekHq8yWdNy8nGN9eot6ZR9Gf5g1OYVOK8r5lGTbmrfdiX31ucs6nLVcU3TDPWjE1UJegPnAQWQTVDI7dQjpboNJfxo9k-jPTwRajNMtavrOCXoVNheCYKEQPg9M62FG/s400/Configuration+Manager.png&quot; width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;SQL Server Business Intelligence on one server&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;As far as a readily deployed UI for ad-hoc analytics (such as Pentaho User Console), SQL Server does not have something out of the box. &amp;nbsp;It would be neat if Microsoft, in a future release, would have a Pentaho User Console equivalent -- perhaps a Sharepoint site with PowerView and PowerPivot.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Like PAS, SSAS runs as a service, persisting and caching calculations in memory to enable fast analysis. &amp;nbsp;Each SSAS database (equivalent to a PAS schema) has a plethora of options (as we will see later) to enable efficient partitioning, scaling, and caching. &amp;nbsp;In terms of connectivity to SSAS databases, you&#39;ve got the usual suspects: default TCP ports on which SSAS listens (2383 by default) and XMLA or SOAP calls via IIS.&amp;nbsp;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;As with the PAS review, I have produced all the artifacts that we cover here on my source control system. &amp;nbsp;If you have a Subversion client, point to&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: white; font-family: Arial, Helvetica, sans-serif; font-size: 16px; line-height: 24px;&quot;&gt;&lt;a href=&quot;https://edg.sourcerepo.com/edg/SQLServerAdventureWorks&quot;&gt;https://edg.sourcerepo.com/edg/SQLServerAdventureWorks&lt;/a&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Now that we&#39;ve got the technical rundown covered, let&#39;s dive right into some development!&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;u&gt;Connection and Schema Creation&lt;/u&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;In PAS, we mentioned that a power user can develop their own cubes directly on Pentaho User Console or a developer can deploy a proper schema for consumption by many stakeholders. &amp;nbsp;The same could be said about the SQL Server BI stack. &amp;nbsp;However, it&#39;s not quite as cohesive as Pentaho. &amp;nbsp;For a power-user who wants to create cubes for quick and dirty analysis, Microsoft offers &lt;a href=&quot;http://www.microsoft.com/en-us/bi/powerpivot.aspx&quot;&gt;PowerPivot for Excel&lt;/a&gt;. &amp;nbsp;While technically not a part of the SQL Server BI stack (it&#39;s obviously an Excel product), it offers the same functionality as the Pentaho User Console in allowing users to define quick one-off schemas for data analysis.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;We&#39;re more concerned about wrapping BI in a development lifecycle in this comparo, so with that in mind, we go straight to the heart of SQL Server BI development, Business Intelligence Development Studio (BIDS). &amp;nbsp;BIDS is simply the Visual Studio 2008 IDE with templates designed specifically for SQL Server BI. &amp;nbsp;You&#39;ve got projects for SSAS, SSIS, and SSRS all in a neat, tidy package.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right: auto; text-align: center;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi4Q0IYd3wUoHIDdmG_xYKNywCoXuaC-Vu0N4L6SyUe2as56wZUE8vuw-ACQv5CyZ-8Mi_4T1SSVvMM61iZyDHns_8ppa8nI_OnlSYph9iRhOwnmQwv0GljfE-qlC03MfrFRmBfLuUFLmmq/s1600/BIDS+Main.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;180&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi4Q0IYd3wUoHIDdmG_xYKNywCoXuaC-Vu0N4L6SyUe2as56wZUE8vuw-ACQv5CyZ-8Mi_4T1SSVvMM61iZyDHns_8ppa8nI_OnlSYph9iRhOwnmQwv0GljfE-qlC03MfrFRmBfLuUFLmmq/s400/BIDS+Main.png&quot; width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;Ready-made, world-class IDE for Business Intelligence development&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;For those who develop Microsoft products, this is tremendous, as you are working within a familiar IDE. &amp;nbsp;For developers from other platforms, I can assure you that you will start to love Visual Studio when working with SQL Server BI (take it from a guy who also loves Eclipse).&amp;nbsp;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;The first thing we must do is to create a new SSAS project. &amp;nbsp;So, upon launching BIDS, we select Analysis Services Project and for a Name, let&#39;s call it the obvious (AdventureWorks). &amp;nbsp;This creates a proper project for you to begin SSAS development. &amp;nbsp;On the Solution Explorer, you will see subfolders for various artifacts that SSAS expects.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right: auto; text-align: center;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEic-mzfQErYDSiLFIWUPY8sI2Tdww6D5fHQLxq9Xjv2cNQFYTOL1e_dHTSR6NAux75w9WsUXS5cGpwxAkK7SzTJN9zWTSk6zx7Ev7g-cBj-cNVudt_T_X00QEe7KvtvHI-5dLjWZeCLmUl7/s1600/BIDS+SSAS+New+Project.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;225&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEic-mzfQErYDSiLFIWUPY8sI2Tdww6D5fHQLxq9Xjv2cNQFYTOL1e_dHTSR6NAux75w9WsUXS5cGpwxAkK7SzTJN9zWTSk6zx7Ev7g-cBj-cNVudt_T_X00QEe7KvtvHI-5dLjWZeCLmUl7/s400/BIDS+SSAS+New+Project.png&quot; width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;A blank SSAS 2008 Project&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;The subfolders are actually arranged vertically by the logical steps you would take to create SSAS cubes. &amp;nbsp;The first thing we must establish is a connection to our RDBMS data mart. &amp;nbsp;This will be accomplished by right clicking on Data Sources, then clicking New Data Source. &amp;nbsp;The Data Source Wizard comes up and guides you through the connection setup process. &amp;nbsp;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhqaLGVJA02_63NPDGL6I3sMUE6KxzSdgWtsF8sEppe2qN-cGpaegqi4L__i2OUA4q8stkV7pZ8MEaSZrRw61sIPMNAGIwqElcVNl8GLF4msQhLYnWv8euHKALvRAsq-4U5Nyu-dt3Coa-5/s1600/BIDS+Data+Source+Wizard.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;289&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhqaLGVJA02_63NPDGL6I3sMUE6KxzSdgWtsF8sEppe2qN-cGpaegqi4L__i2OUA4q8stkV7pZ8MEaSZrRw61sIPMNAGIwqElcVNl8GLF4msQhLYnWv8euHKALvRAsq-4U5Nyu-dt3Coa-5/s320/BIDS+Data+Source+Wizard.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;On the &quot;Select how you define the connection&quot; step, we would click on the &quot;New...&quot; button which will launch a Connection Manager window which should be familiar to Microsoft application developers. &amp;nbsp;The server name for this case is localhost\SQLDEV01 using Windows Authentication. &amp;nbsp;We then select the AdventureWorksDW2008 database which already resides on the instance. &amp;nbsp;Click Test Connection to ensure connectivity, then click OK.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhmutTRUKopOvAXtVeUBCUXM7VrgkyxA24CmOwKhtoXahyphenhyphen7LKRpkdHSKw9C_NIOmoFVvwifEwyuTBuq38B6lzVdlWfXHhchfPtaBIOe2A46L4LHmqAxtG0Q-s1GK4uT4_KgbnxlskVmNUez/s1600/BIDS+Connection+Manager.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;320&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhmutTRUKopOvAXtVeUBCUXM7VrgkyxA24CmOwKhtoXahyphenhyphen7LKRpkdHSKw9C_NIOmoFVvwifEwyuTBuq38B6lzVdlWfXHhchfPtaBIOe2A46L4LHmqAxtG0Q-s1GK4uT4_KgbnxlskVmNUez/s320/BIDS+Connection+Manager.png&quot; width=&quot;312&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Hit Next and then we come to the step which asks for impersonation information for this SSAS database. &amp;nbsp;This is very important, as this is the user context on which the SSAS database will attempt connectivity to the relational data mart. &amp;nbsp;For the purposes of this exercise (and for simplicity&#39;s sake), since the service is running as Local System, I simply select &quot;Use the service account&quot; then click Next. &amp;nbsp;In a Production setting, you would most likely use some kind of domain service account for the user context.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiSss79aHV1C7FquO_8yae8CZvc4z1p3PRJrd7RlEqrCVtw06DCDu9OIBO3zNEd1EFnYVO_f0IpMuey2Lt04qVef95f6G-LIc4aWppoE2mTvvyGIusQR-21ENXm_db3_lgio2ik-9UXpQ3b/s1600/BIDS+SSAS+Impersonation+Information.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;320&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiSss79aHV1C7FquO_8yae8CZvc4z1p3PRJrd7RlEqrCVtw06DCDu9OIBO3zNEd1EFnYVO_f0IpMuey2Lt04qVef95f6G-LIc4aWppoE2mTvvyGIusQR-21ENXm_db3_lgio2ik-9UXpQ3b/s320/BIDS+SSAS+Impersonation+Information.png&quot; width=&quot;298&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;/div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;The last step is to define the name for this Data Source, so let&#39;s call it Adventure Works Data Warehouse, then click Finish. &amp;nbsp;Voila, the foundation for our cube and dimension development has been laid down.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right: auto; text-align: center;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQd11htBh-GjPkDx8dMOPTutxDqFvESW1MuRk3UeIN95e4e_UDd4vAG0OZxN8bc-xaXytd8M2-IlEOYR5tKvexUy9ylTTQwCqaY66ioApLnhbJRCSD7PHCHy0kfjJOw6pED4PS6cEtXKIF/s1600/BIDS+SSAS+Data+Source+Established.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;180&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQd11htBh-GjPkDx8dMOPTutxDqFvESW1MuRk3UeIN95e4e_UDd4vAG0OZxN8bc-xaXytd8M2-IlEOYR5tKvexUy9ylTTQwCqaY66ioApLnhbJRCSD7PHCHy0kfjJOw6pED4PS6cEtXKIF/s320/BIDS+SSAS+Data+Source+Established.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;Ready to rock!&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;u&gt;Cube Creation&lt;/u&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;So, now, let&#39;s define our analytic structure. &amp;nbsp;In SSAS, this is first accomplished by creating Data Source View against a Data Source. &amp;nbsp;This Data Source View essentially is the star schema by which cubes would adhere. &amp;nbsp;You can define your entire star schema on one Data Source View or segment it at your discretion. &amp;nbsp;The Data Source View also acts as an abstraction/alias layer, where you may rename the names of the relational tables and/or table columns to terms that are relevant to your business stakeholders. &amp;nbsp;This is the concept of a &quot;FriendlyName&quot; within SSAS.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;For simplicity&#39;s sake, let&#39;s define the entire star schema of Adventure Works in one Data Source View. &amp;nbsp;We right click on Data Source Views then click on New Data Source View to begin. &amp;nbsp;Another friendly wizard pops up to guide us on our way.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;It first asks for the Data Source for the view -- we will select the Adventure Works Data Warehouse source that we had created in the prior step.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgf96dzls3bUx5YnEbt3VD3IDHAVe-2OeU8Y2kzcr8rc3QeJUu2lFyztDIWCpLa23EjHbgsHW41ya8LZ6W9XJwWDmxtVw6ip3L568h5Hr5NKFd1Unsc5_x_s5ldIDPFCYFRhvryz9tIQbup/s1600/BIDS+SSAS+New+Data+Source+View.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;289&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgf96dzls3bUx5YnEbt3VD3IDHAVe-2OeU8Y2kzcr8rc3QeJUu2lFyztDIWCpLa23EjHbgsHW41ya8LZ6W9XJwWDmxtVw6ip3L568h5Hr5NKFd1Unsc5_x_s5ldIDPFCYFRhvryz9tIQbup/s320/BIDS+SSAS+New+Data+Source+View.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;In the second step, the wizard asks for the tables/views to be included in the view. &amp;nbsp;To answer a possible question, yes, it is entirely possible to create an SSAS cube against an existing relational data source that has Views built on top of it to simulate a star schema. &amp;nbsp;However, this is generally not recommended, as a core tenet of a DW/BI solution is to minimize the hits to your source relational database. &amp;nbsp;For more reading on the subject, I would recommend the usual suspects: &lt;a href=&quot;http://www.amazon.com/The-Data-Warehouse-Toolkit-Dimensional/dp/0471200247/ref=sr_1_1?ie=UTF8&amp;amp;qid=1339856160&amp;amp;sr=8-1&amp;amp;keywords=Data+Warehousing+Toolkit&quot;&gt;The Data Warehousing Toolkit&lt;/a&gt; and the &lt;a href=&quot;http://www.amazon.com/The-Microsoft-Data-Warehouse-Toolkit/dp/0470640383/ref=sr_1_4?ie=UTF8&amp;amp;qid=1339856160&amp;amp;sr=8-4&amp;amp;keywords=Data+Warehousing+Toolkit&quot;&gt;Microsoft Data Warehousing Toolkit&lt;/a&gt;.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;For the purposes of this exercise, we will select the Dimension and Fact tables as they appear on the relational source and bring them over. &amp;nbsp;Click Next.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjA2bbAoZ5Rqht_SBgrgMTTZvlEthJbcNj9GbWkDVkyQg3k04rEmfj35bMrYHFBKoaeyOTSchAr4edRdjgy0cRT0Z5w3MvX5uBEjaMUcS6pUYu45Kqrs1N7BmdKnZX3GR_svdj321yQ68_d/s1600/BIDS+SSAS+Select+Tables.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;289&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjA2bbAoZ5Rqht_SBgrgMTTZvlEthJbcNj9GbWkDVkyQg3k04rEmfj35bMrYHFBKoaeyOTSchAr4edRdjgy0cRT0Z5w3MvX5uBEjaMUcS6pUYu45Kqrs1N7BmdKnZX3GR_svdj321yQ68_d/s320/BIDS+SSAS+Select+Tables.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Finally, we assign a name to the Data Source View. &amp;nbsp;To be descriptive, let&#39;s call this Adventure Works Full Dimensional Model then click Finish. &amp;nbsp;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right: auto; text-align: center;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjIiWu4pJfhw8VzPL2Ov7AmATY2Dxs4K6k9-4-NyBKFBnHqKMJ6O33e-F0-2AQyd72OCzxdAu_6KRjye2-3RE-e9W3thU0vpjahYIKxVwVH_prc7ovBu541kZxLyC-BJZ9BJXgRo4crB8gx/s1600/BIDS+SSAS+Adventure+Works+Full+Dimensional+Model.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;225&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjIiWu4pJfhw8VzPL2Ov7AmATY2Dxs4K6k9-4-NyBKFBnHqKMJ6O33e-F0-2AQyd72OCzxdAu_6KRjye2-3RE-e9W3thU0vpjahYIKxVwVH_prc7ovBu541kZxLyC-BJZ9BJXgRo4crB8gx/s400/BIDS+SSAS+Adventure+Works+Full+Dimensional+Model.png&quot; width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;Star schema relationships defined in the view? &amp;nbsp;Why thank you!&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;The neat part about a Data Source View in SSAS is that it automatically determines the relationships between your Dimension and Fact tables based on the relationships established on the relational database. &amp;nbsp;So, as an example, on the AdventureWorksDW2008 database, there is a foreign key constraint on the FactInternetSales table on the CurrencyKey column that points its parent table to the DimCurrency table&#39;s CurrencyKey column. &amp;nbsp;You see the same relationship established on the DSV. &amp;nbsp;In short, SSAS tries not to have you repeat your work that you did on the data mart.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;You can go crazy with the FriendlyNames here on the DSV. &amp;nbsp;In fact, most businesses would generally request friendlier names for the Dimension and Fact Tables (for example, &quot;Reseller Sales&quot; instead of &quot;FactResellerSales&quot;, &quot;Employee&quot; instead of &quot;DimEmployee&quot;, etc.), but for the purposes of this exercise, we&#39;re going to leave it as-is. &amp;nbsp;Now, let&#39;s create some cubes against this Data Source View. &amp;nbsp;This is accomplished by right clicking on Cubes, then clicking on New Cube. &amp;nbsp;The Cube Wizard pops up and on the second step for the Select Creation Method, we select the Use existing tables option then click Next.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjivvBzlmQEhs-WFA0yY96Hqfvn4ChXydRzJCRXl3Iz6vM_gocCF28YSRVtfmdlVlFezJSB5dXb4ke1gI57skniqi7mR5jsFW8Jk9XuETYxtnUsajphyphenhyphenHM6s1h4oKvRXyTxNZak6FIejfgJ/s1600/BIDS+SSAS+Cube+Wizard+Select+Creation+Method.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;289&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjivvBzlmQEhs-WFA0yY96Hqfvn4ChXydRzJCRXl3Iz6vM_gocCF28YSRVtfmdlVlFezJSB5dXb4ke1gI57skniqi7mR5jsFW8Jk9XuETYxtnUsajphyphenhyphenHM6s1h4oKvRXyTxNZak6FIejfgJ/s320/BIDS+SSAS+Cube+Wizard+Select+Creation+Method.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;On the next step, the Select Measure Group Tables, we will select a Fact table...for this example, we&#39;ll start with FactResellerSales as we did with PAS. &amp;nbsp;Click Next.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjqy9hp_X3tJb6ronfmEi9l2lV8Nh2hGJuPcp2JmBSLMjJ_3cEHLQwoKbTdAnbnC0pJfg2P16Ds3a0O3FvRsALbkPG-hxoX6o5lPa5Wp2-cY2d3PFpecOjL16oO6miETnp3jyRFUcl5GUuR/s1600/SSAS+Cube+Wizard+Select+Measure+Group+Tables.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;289&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjqy9hp_X3tJb6ronfmEi9l2lV8Nh2hGJuPcp2JmBSLMjJ_3cEHLQwoKbTdAnbnC0pJfg2P16Ds3a0O3FvRsALbkPG-hxoX6o5lPa5Wp2-cY2d3PFpecOjL16oO6miETnp3jyRFUcl5GUuR/s320/SSAS+Cube+Wizard+Select+Measure+Group+Tables.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;/div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;In the next step, the wizard breaks down the fact table by the measure groups detected (basically anything numeric). &amp;nbsp;You can de-select any fields that may actually be degenerate dimension fields -- in this fact table&#39;s case, Revision Number would be such a field, so we deselect that field for the measure groups. &amp;nbsp;Click Next.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgu-d9BXgka78IWSjntPaFIQuxMRWLE2gV7Cz-tNahdEe6pODkUxRx1CJTQh1Zlt9BetHf03Q6rx-aR7YcLiEve9zMwuF94yCr4Cbq7A2xMnTD4O8pR5UaS5RkHbqvEZ5xPgWIJucyvE9JG/s1600/BIDS+SSAS+Select+Measures.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;289&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgu-d9BXgka78IWSjntPaFIQuxMRWLE2gV7Cz-tNahdEe6pODkUxRx1CJTQh1Zlt9BetHf03Q6rx-aR7YcLiEve9zMwuF94yCr4Cbq7A2xMnTD4O8pR5UaS5RkHbqvEZ5xPgWIJucyvE9JG/s320/BIDS+SSAS+Select+Measures.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;In the following step, the wizard tries to detect the dimensions that must be created based on the star schema of the sourcs DSV. &amp;nbsp;We will leave this as-is, as it detects all the dimensions and subdimensions properly, thanks to a well-constructed data mart. &amp;nbsp;Click Next.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhbrCEjGxXftsqUToBG3f0_Edh9tJtuvumWg_cVb8xyowaWGrY5iqaZ5zlbjNgVFKMsVHvYcKIG0EBmv35egObXGNDRtztdrbz_EB7VIzYAde4Q-9hARMcWkRUhE6LGspueegEbJdCE_99A/s1600/BIDS+SSAS+Select+New+Dimensions.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;289&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhbrCEjGxXftsqUToBG3f0_Edh9tJtuvumWg_cVb8xyowaWGrY5iqaZ5zlbjNgVFKMsVHvYcKIG0EBmv35egObXGNDRtztdrbz_EB7VIzYAde4Q-9hARMcWkRUhE6LGspueegEbJdCE_99A/s320/BIDS+SSAS+Select+New+Dimensions.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Finally, we are to give the cube a name. &amp;nbsp;Let&#39;s call this one Reseller Sales. &amp;nbsp;Click Finish.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhIFpheyGSdOhltcSEZfecqMkaHA8LZxXxcwNY2KVLQJPa21LV8yXUcExAKuRHicQ-N_z6IXD5BwCl5X1qzKRZR8hw2lgicgOTMpIZCG0r1yQS4soztO4bdOlSU3AttgAsD__W9cwyyepfS/s1600/BIDS+SSAS+Cube+and+Dimensions+Created.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;180&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhIFpheyGSdOhltcSEZfecqMkaHA8LZxXxcwNY2KVLQJPa21LV8yXUcExAKuRHicQ-N_z6IXD5BwCl5X1qzKRZR8hw2lgicgOTMpIZCG0r1yQS4soztO4bdOlSU3AttgAsD__W9cwyyepfS/s320/BIDS+SSAS+Cube+and+Dimensions+Created.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;SSAS creates the cube along with skeleton dimensions (just the dimension keys as attributes). &amp;nbsp;We&#39;ll get to the dimensional attributes later. &amp;nbsp;For now, let&#39;s ensure our cube is setup properly. &amp;nbsp;First, let&#39;s make sure that the appropriate measure groups round off to the nearest hundredth decimal place. &amp;nbsp;This is accomplished by expanding the Fact Reseller Sales node on the Measures pane (upper left hand corner in the Cube Structure window), selecting a measure, then setting its FormatString property to&amp;nbsp;#,##0.00;-#,##0.00. &amp;nbsp;We&#39;ll do this for all the measure groups in Fact Reseller Sales except for Fact Reseller Sales Count.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Just being nitpicky here, but I&#39;d like a future release to have the ability to select many measure groups and being able to set common properties to the same value. &amp;nbsp;It&#39;s certainly not a dealbreaker, but it can get fairly annoying when you have a fact table of many measures.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;For now, let&#39;s Build and Deploy the cube and dimensions as they stand so that BIDS&#39; metadata gets refreshed and we can work with our model in a more dynamic fashion. &amp;nbsp;First, let&#39;s make sure we are deploying to the local SSAS instance by right clicking on the AdventureWorks solution then clicking Properties. &amp;nbsp;In the Deployment subwindow, the Target Server should be localhost\SQLDEV01 and the Database should be AdventureWorks.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhBvP-JaeC48XZAUuPHJbvE84KQtUlpQ7sGswFrHvDu7P8ZaPx9qiKTf7IeqI_5kujr95xMQ6Qnnkvkg7XhMyDfvGTRFhAaDFaOoe2Bo25cJUWWnMK_q42CPUbd1caxAXzc2CxlUNGCSkmd/s1600/BIDS+SSAS+Deployment+Configuration.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;192&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhBvP-JaeC48XZAUuPHJbvE84KQtUlpQ7sGswFrHvDu7P8ZaPx9qiKTf7IeqI_5kujr95xMQ6Qnnkvkg7XhMyDfvGTRFhAaDFaOoe2Bo25cJUWWnMK_q42CPUbd1caxAXzc2CxlUNGCSkmd/s320/BIDS+SSAS+Deployment+Configuration.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Click on Build --&amp;gt; Build AdventureWorks. &amp;nbsp;We shouldn&#39;t receive any errors. &amp;nbsp;Finally, click on Build --&amp;gt; Deploy AdventureWorks. &amp;nbsp;After a quick deployment, we&#39;re all set for further development.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgK1W7cf4AVS-guuG8v77w8nOkGVh7ZsKzv4i5GsgXcKVGQJYjuOmyoVkk8hxm4p3MX1eJSq0N793pxGygMBPdufzpl968xWMs-LLAeBjiyPV2TjD8GQorM_2080P1gbAl7d4XB1RMmlBqF/s1600/BIDS+SSAS+First+Deployment.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;320&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgK1W7cf4AVS-guuG8v77w8nOkGVh7ZsKzv4i5GsgXcKVGQJYjuOmyoVkk8hxm4p3MX1eJSq0N793pxGygMBPdufzpl968xWMs-LLAeBjiyPV2TjD8GQorM_2080P1gbAl7d4XB1RMmlBqF/s320/BIDS+SSAS+First+Deployment.png&quot; width=&quot;223&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Click on Browser to refresh the metadata within the IDE for dynamic development.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;u&gt;Creating Calculations&lt;/u&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;As with PAS, creating calculations in SSAS is a straightforward process. &amp;nbsp;Click on the Calculations subwindow. &amp;nbsp;Then right click in the Script Organizer pane and select &quot;New Calculated Member&quot;. &amp;nbsp;The Name of the Calculated Member will be [Net Profit Or Loss].&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;As we defined in the PAS exercise, this calculated member simply subtracts Total Product Cost from Extended Amount. &amp;nbsp;Since we deployed and refreshed the metadata of our cube, BIDS offers a nice drag and drop feature from the measure groups for this calculated member. &amp;nbsp;We simply expand the Measures node and first drag over Extended Amount to the Expression text box (which results in [Measures].[Extended Amount] to be written). &amp;nbsp;Type in a dash for the minus sign then drag over Total Product Cost. &amp;nbsp;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;In the Format String, use the same &quot;&lt;/span&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;#,##0.00;-#,##0.00&quot; formatting (do not forget to include the double quotes in this case) we used on the measure groups. &amp;nbsp;For Non-empty behavior (in other words, what measure group&#39;s non-empty behavior it would mimic), select Extended Amount and Total Product Cost. Click File --&amp;gt; Save All, and the calculated member is set!&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi9cgmPOZa0FYRNE8uCr5Ipa5XKBjvoTGXXTAO4pd4ahEYKAoPIXlaY39R_vG2bb9GqpewhyphenhyphenM36e_ms1qEyooA-VO5OO6-i2phgL3vdJ8RGMwWPTCzV4IoGRZs1bfnQv28PlZ52EwY8QMu2/s1600/BIDS+SSAS+Calculated+Member.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;180&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi9cgmPOZa0FYRNE8uCr5Ipa5XKBjvoTGXXTAO4pd4ahEYKAoPIXlaY39R_vG2bb9GqpewhyphenhyphenM36e_ms1qEyooA-VO5OO6-i2phgL3vdJ8RGMwWPTCzV4IoGRZs1bfnQv28PlZ52EwY8QMu2/s320/BIDS+SSAS+Calculated+Member.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;u&gt;Dimension Creation&lt;/u&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Now that we have the basic setup of the cube complete, we can move onto hashing out the skeleton dimensions that were created for us. &amp;nbsp;To say the creation and editing of dimensions in BIDS is easier compared to the Pentaho Schema Workbench would be a severe understatement.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;For starters, the Dimensions are already created for us in SSAS upon creation of the cube. &amp;nbsp;As mentioned previously, these skeleton dimensions simply have their primary keys as the attributes, but BIDS allows the ability to intuitively add attributes from the source to the dimension.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;We&#39;ll use the Dim Sales Territory dimension as an example. &amp;nbsp;Let&#39;s double click on Dim Sales Territory under Dimensions. &amp;nbsp;There are four subwindows on this view -- Dimension Structure, Attribute Relationships, Translations, and Browser. &amp;nbsp;Dimension Structure is the default view and is the place where we will define what attributes are to be included in the Dim Sales Territory dimension.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgdyCdKw6Yw4UqcQrbVZU3ZNMh_yZ_Gr9kTAQs4R01DD74d88J7u4IF1rFfcu0hDhq1jf2ke700dl4y678__csWvA3wTcINfkck17KJNNqaZx-J5zbTkuHiDSedpaePsR_0F3cXWgUxSeyi/s1600/BIDS+SSAS+Dimension+Structure.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;130&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgdyCdKw6Yw4UqcQrbVZU3ZNMh_yZ_Gr9kTAQs4R01DD74d88J7u4IF1rFfcu0hDhq1jf2ke700dl4y678__csWvA3wTcINfkck17KJNNqaZx-J5zbTkuHiDSedpaePsR_0F3cXWgUxSeyi/s320/BIDS+SSAS+Dimension+Structure.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;In this Dimension Structure view are three self-explanatory areas: Attributes, Hierarchies, and Data Source View. &amp;nbsp;To add an attribute to the dimension from the source, one must simply select what attributes to be added from the Data Source View and drag/drop these fields to the Attributes area. &amp;nbsp;For Dim Sales Territory, we will move the rest of the fields from the Data Source View over to the Attributes area. &amp;nbsp;Repeat this process for all the dimensions that have been created.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;u&gt;Creating Hierarchies&lt;/u&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Once attributes have been defined, to define hierarchies, it will be the same drag/drop principle, except this time, you will be dragging individual Attributes to the Hierarchies area, in order from parent to child. &amp;nbsp;For this particular hierarchy, as the Sales Territory Group is the parent of Sales Territory Country and Sales Territory Country is the parent of Sales Territory Region. &amp;nbsp;We define this hierarchy as such.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgniiqTcXA9lIseDgdv4tOmI3nrIagG7_DHJlvmaJBgOIehojwQ-WGI3N6em3-2hwU8LAtkLqUGxsAjL90CucB_rlb-lye8yTK5tzfgOo4K-zJCa9l37su7xTU3kuWSxhuggmPQoN1_rog4/s1600/BIDS+SSAS+Dimension+Hierarchies.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;180&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgniiqTcXA9lIseDgdv4tOmI3nrIagG7_DHJlvmaJBgOIehojwQ-WGI3N6em3-2hwU8LAtkLqUGxsAjL90CucB_rlb-lye8yTK5tzfgOo4K-zJCa9l37su7xTU3kuWSxhuggmPQoN1_rog4/s320/BIDS+SSAS+Dimension+Hierarchies.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;You can rename the Hierarchy once it has been established by right clicking on it and going to Properties. &amp;nbsp;We&#39;ll call this hierarchy Sales Territories.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;One last step in defining this hierarchy is to define the Attribute Relationship for the hierarchy. &amp;nbsp;This enables SSAS to effectively and efficiently calculate this hierarchy during processing, and thus, it will make aggregations against this hierarchy be considerably faster.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;To establish the desired Attribute Relationship, we click on the Attribute Relationships pane. &amp;nbsp;At first, we see that there is only attribute relationships defined between the Sales Territory Key and the dimensional attributes we dragged over earlier.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg9uRwue3KL2zzYoOHNWMbJMdPyseidMKwGuyXF0CQLfMAc3XepBzdzu__O4tcclQp1xf57MPU1geGI6udy75iU3RBKdRtjxXYo8EUXPwRd0iAQG8htqYiJgvEo2i5BuPRMy_OJaRVoitXx/s1600/BIDS+SSAS+Dimension+Attribute+Relationships.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;180&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg9uRwue3KL2zzYoOHNWMbJMdPyseidMKwGuyXF0CQLfMAc3XepBzdzu__O4tcclQp1xf57MPU1geGI6udy75iU3RBKdRtjxXYo8EUXPwRd0iAQG8htqYiJgvEo2i5BuPRMy_OJaRVoitXx/s320/BIDS+SSAS+Dimension+Attribute+Relationships.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Attribute Relationships are read to be many to one from left to right. &amp;nbsp;So, for the example above, the Attribute Relationship should be read as &quot;Many Sales Territory Keys are associated to one Sales Territory Group&quot;, etc. &amp;nbsp;We want to further hash this Attribute Relationship out to include the hierarchy we created.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Conceptually speaking, we want to design the relationship such that you can read it as: &quot;Many Sales Territory Regions are associated to one Sales Territory Country, and many Sales Territory Countries are associated to one Sales Territory Group&quot;.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;To implement this, we first start at the lowest level, Sales Territory Region. &amp;nbsp;Right click on this node then click on New Attribute Relationship. &amp;nbsp;On the Related Attribute dropdown, select Sales Territory Country. &amp;nbsp;Leave the Relationship type as-is (Flexible). &amp;nbsp;Click OK. &amp;nbsp;Repeat the same step with the Sales Territory Country node, except the Related Attribute should now be Sales Territory Group.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhMEwKHuRj72ECsH745LzqpkVEmpmCoHMOGDfZBIoy-ngZlLcybLb41ajPw-UbuAjIk_pNxpzX-U_mLwH9tJOhwm1tFWOOWUTUt3cKkeiIY4HBcpn1keE0FDxmUFFkEoSk9gyNx6eC7Qw7b/s1600/BIDS+SSAS+Dimension+Attribute+Relationships+Defined.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;180&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhMEwKHuRj72ECsH745LzqpkVEmpmCoHMOGDfZBIoy-ngZlLcybLb41ajPw-UbuAjIk_pNxpzX-U_mLwH9tJOhwm1tFWOOWUTUt3cKkeiIY4HBcpn1keE0FDxmUFFkEoSk9gyNx6eC7Qw7b/s320/BIDS+SSAS+Dimension+Attribute+Relationships+Defined.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Save the dimension, build the solution, and deploy it. &amp;nbsp;Now, if we go back to the Reseller Sales cube and go to Browser, you will see the Sales Territories Hierarchy defined in the Dim Sales Territory dimension.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right: auto; text-align: center;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEieza4omk2k-387BoJu2baG1LVjUw682QWtQkgIxIrj2RoN2ZRbyI_UKr3Zgw93OBeqOBJ3w40gqjkihjzM8OobPHAOEcIi1upUxp0mUB-NfXUoR6r7OCIbOUBfYt0qtQvkPYcJYUFvbfb6/s1600/BIDS+SSAS+Dimension+Hierarchies+Deployed.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;180&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEieza4omk2k-387BoJu2baG1LVjUw682QWtQkgIxIrj2RoN2ZRbyI_UKr3Zgw93OBeqOBJ3w40gqjkihjzM8OobPHAOEcIi1upUxp0mUB-NfXUoR6r7OCIbOUBfYt0qtQvkPYcJYUFvbfb6/s320/BIDS+SSAS+Dimension+Hierarchies+Deployed.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;A very nice hierarchy!&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;u&gt;Applying Security&lt;/u&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;SSAS has an incredibly robust security system. &amp;nbsp;The database is secured by a Roles-based system, where one can define what users can access what cubes. &amp;nbsp;We can even get to the granular level and define what dimensions or what tuples specific users can see (though, Microsoft generally recommends making your security definitions broad). &amp;nbsp;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Since this topic covers all manner of sins, &lt;a href=&quot;http://technet.microsoft.com/en-us/library/cc304417.aspx&quot;&gt;I will refer you to the TechNet lesson which covers all the basics&lt;/a&gt;. &amp;nbsp;Suffice to say, this security model should fulfill the vast majority of restricted access needs of an organization.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;u&gt;Consumption of Data&lt;/u&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;So, now we have a nice, fully hashed out Dimensional Model with a Reseller Sales cube properly defined. &amp;nbsp;We do one last build and deploy, and our SSAS database is now ready for some slicing and dicing!&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;The primary user interface for SSAS really is Microsoft Excel (2010 version in this example). &amp;nbsp;It is the platform of choice for most data analysts in enterprises, and quite frankly, it is one of the best ad-hoc data analytic tools, period. &amp;nbsp;This is the one area where SQL Server 2008 BI kind of falls short comparatively to Pentaho. As previously mentioned, Pentaho&#39;s main paradigm is everything through the Pentaho User Console first. &amp;nbsp;SQL Server 2008 BI requires a little work to get a common interface up and running, specifically with tools like Sharepoint Server and PowerPivot for Sharepoint.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;With regards to Excel 2010, establishing a connection to our SSAS cube is straightforward. &amp;nbsp;First, go to the Data ribbon and select &quot;From Other Sources&quot;. &amp;nbsp;In the submenu, select Analysis Services. &amp;nbsp;The server name for this example is localhost\SQLDEV01 and we will use Windows Authentication.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjuX2POzdkVjbVYFb_9kvHOoHvhENFY5t6pKhRmnHgpOtv7NIFJ20qKmSPCgKtadExT6BTt1kkrHzqmifbo7OJxGxebvOSIWfPudUs5ttS9cNrNJSBc5p7YjC0AgDmJFgDDZmTCdH9yStf3/s1600/Excel+2010+SSAS+Connection+Wizard.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;222&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjuX2POzdkVjbVYFb_9kvHOoHvhENFY5t6pKhRmnHgpOtv7NIFJ20qKmSPCgKtadExT6BTt1kkrHzqmifbo7OJxGxebvOSIWfPudUs5ttS9cNrNJSBc5p7YjC0AgDmJFgDDZmTCdH9yStf3/s320/Excel+2010+SSAS+Connection+Wizard.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Upon hitting Next, we see our deployed AdventureWorks database and the Reseller Sales cube available. &amp;nbsp;Select the Reseller Sales cube then hit Finish.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEixwdiYiukAVODPoXFHqfBXGv_TB9s5Ni3eXSDo4XSP_qWND7DhtS_epKSc6qkmLF-w0e7z9392XpxkQr1d0tqN42f9IGpJEzoYFTeMYZOPk1lWhCjoceD5j_YfylarTsJ5Rg0RSWseJB74/s1600/Excel+2010+SSAS+Select+Cube.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;225&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEixwdiYiukAVODPoXFHqfBXGv_TB9s5Ni3eXSDo4XSP_qWND7DhtS_epKSc6qkmLF-w0e7z9392XpxkQr1d0tqN42f9IGpJEzoYFTeMYZOPk1lWhCjoceD5j_YfylarTsJ5Rg0RSWseJB74/s320/Excel+2010+SSAS+Select+Cube.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Excel asks what kind of report we want, and we&#39;ll use a plain ol&#39; PivotTable Report for this example. &amp;nbsp;We&#39;ll drop the table on $A$1 and hit OK. &amp;nbsp;What appears next is very familiar to Excel users, the Pivot Table Field List. &amp;nbsp;It is broken down by the measures table first followed by the associated dimensions.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgSQRI__KCxhnqiUbuPP4B5GlwoSxsDS_oQhYDhCOx0tWO6-UTvo5OWk643jlgwaUJdKsVSwLRZhcLLLu9tXzM6DOaDK5R18I1mBzp5B-fpSVl1g2w2HftC1haUI9Rr9rmJaX2ASB0Kf7h5/s1600/Excel+2010+PivotTable.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;126&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgSQRI__KCxhnqiUbuPP4B5GlwoSxsDS_oQhYDhCOx0tWO6-UTvo5OWk643jlgwaUJdKsVSwLRZhcLLLu9tXzM6DOaDK5R18I1mBzp5B-fpSVl1g2w2HftC1haUI9Rr9rmJaX2ASB0Kf7h5/s320/Excel+2010+PivotTable.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;From here, slicing and dicing is as simple as dragging and dropping measures and dimensional attributes to the appropriate axes or to the values section. &amp;nbsp;First, let&#39;s see Net Profit or Loss by Department Name (from the Dim Employee dimension) and broken down by Fiscal Years across the columns. &amp;nbsp;We&#39;ll select Net Profit or Loss from the measures and then drag Department Name to the Row Labels and Fiscal Year to the Column Labels.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgB5l4jIwvzPEZAN14FG978uztpRHGmTq7r7aUBNubqinlFYN2yCPPMwY2goY8o2W_YcB16mt-_VbFCNHWsSG13YOhE9uJ-LEeOwj5yW-j0dkqjHC0K0WgXlvuGE4GuAGJABOrTPnnZ0LVp/s1600/Excel+2010+Slice+and+Dice.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;162&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgB5l4jIwvzPEZAN14FG978uztpRHGmTq7r7aUBNubqinlFYN2yCPPMwY2goY8o2W_YcB16mt-_VbFCNHWsSG13YOhE9uJ-LEeOwj5yW-j0dkqjHC0K0WgXlvuGE4GuAGJABOrTPnnZ0LVp/s320/Excel+2010+Slice+and+Dice.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Easy peasy. &amp;nbsp;The Excel 2010 PivotTable is the most powerful and primary way most &quot;power data analysts&quot; will access the cubes. &amp;nbsp;It allows the ad-hoc functionality to answer most data analytic questions in whatever format the data analyst desires. &amp;nbsp;Because it is native to Excel 2010, all functionality of Excel 2010 is also at the disposal of the data analyst, which some may argue is the best AND worst part about opening up Excel 2010 as the primary front-end for SSAS.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Since this overview only covers SSAS, we will not go into detail about SSRS, but one should understand that SSRS is the primary front-end for parameterized static reports against the SSAS cubes. &amp;nbsp;That is, the report format itself will not change, but users would need the functionality to see different cuts of the data on the report format. &amp;nbsp;One could argue the same functionality could be replicated in Excel -- however, SSRS offers niceties such as application level security and scheduled distribution of reports. &amp;nbsp;In most SQL Server BI implementation I&#39;d been involved with, a combination of Excel and SSRS usually does the trick for a majority of users.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;u&gt;Consumption of Data Summary&lt;/u&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;While PAS comes tightly coupled with Pentaho User Console, SSAS and SQL Server BI does not have a &quot;one stop shop&quot; for users to immediately access BI artifacts. &amp;nbsp;The primary bare-bones method for accessing cube data is via Excel 2010, and SSRS reports could be built on top of the SSAS cubes to allow users the ability to have parameterized static reports.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;This setup isn&#39;t necessarily a bad thing, as not all BI implementations are created equal. &amp;nbsp;However, a common front-end like Pentaho User Console would be most helpful to make BI implementations on SQL Server a little more agile with regards to rapid prototyping. &amp;nbsp;On the flip side of the coin, most of the audience for a BI tool would be ecstatic with the capabilities granted on their &quot;home turf&quot; platform (Excel), so such a user interface would be more of a nice-to-have.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;In terms of consuming the data itself, it is incredibly intuitive within Excel 2010. &amp;nbsp;Instead obtaining knowledge of, say, ANSI SQL querying, data analysts need only understand how the dimensional model is structured and how a PivotTable Report works, and many analytic questions can be answered. &amp;nbsp;In short, if you&#39;re walking into a Microsoft shop that needs BI, there are very few reasons why you SHOULDN&#39;T use SQL Server BI.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;b&gt;SQL Server Analysis Services: The Good, The Bad, and The Weird&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;u&gt;The Good&lt;/u&gt;&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;ul style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Development process is a cinch comparatively to PAS. &amp;nbsp;BIDS is uses the world-class Visual Studio environment, and the dimensional modeling components for SSAS are highly intuitive.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Tight integration with Excel 2010. &amp;nbsp;Considering Excel is the dominant data analytic tool in the market, this is a boon for developers. &amp;nbsp;Data can be easily brought in and any number of creative solutions off of Excel can be formulated for business use.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;SSAS scales very nicely. &amp;nbsp;It is very fast out of the gate, but if your implementation calls for massive data marts and cubes, SSAS offers the ability to scale out across servers.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;The security model is vastly superior to PAS&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;u&gt;The Bad&lt;/u&gt;&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;ul style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Licensing. &amp;nbsp;Want SSAS by itself? &amp;nbsp;You&#39;ll need to buy a full SQL Server license. &amp;nbsp;Smaller enterprises may be swayed away due to the cost of entry, and will essentially pay for components they may not use (such as the database engine, SSIS, SSRS, etc)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;No common user interface for SQL Server BI out of the gate. &amp;nbsp;I think if Microsoft came up with a Pentaho User Console concept for SQL Server BI, it will add tremendous value.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;u&gt;The Weird&lt;/u&gt;&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;ul style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Why can&#39;t we multi-select measures on a cube in BIDS to edit common properties?&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;b&gt;SQL Server Analysis Services: The Summary&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;I honestly think, apart from minor quabbles, that Microsoft has hit their BI offering out of the park. &amp;nbsp;While platforms such as TM-1 or EssBase may have the leg up in terms of flexibility under the hood of the analytic engine, SSAS has succeeded in wrapping the complicated concept of BI development and consumption into an easy-to-use package. &amp;nbsp;While arguments can be made for the merits or shortcomings of requiring a full SQL Server license for the BI stack, I actually think that the tight coupling is needed. &amp;nbsp;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;A BI solution needs to be coherent not only on the end-product but also for development. &amp;nbsp;In my opinion, tightly coupling the components with the SQL Server database engine ensures the best functionality of SSAS (and the other BI components) out of the gate. &amp;nbsp;Gone are the days when developers need to troubleshoot why the analytic engine doesn&#39;t play nicely with the database engine, or why the reporting front-end has some quirks obtaining data from the analytic engine. &amp;nbsp;SQL Server BI&#39;s strongest trait is its coherence.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;If you have a Microsoft shop with an investment already made in SQL Server, choosing SSAS as the primary analytic engine (as well as the rest of the SQL Server BI stack for other functionality) should be a no-brainer. &amp;nbsp;If your organization has not made an investment in SQL Server, but is otherwise a heavy Microsoft shop, SSAS is still definitely worth a look. &amp;nbsp;While the cost is a factor, comparable offerings from vendors such as Oracle or SAP would probably run you the same amount and would not have the luxury of tight integration with other Microsoft offerings. &amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Make no mistake, SSAS is a world-class analytic platform that I would not hesitate recommending.&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geekstentialism.blogspot.com/feeds/4966917286476189156/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/2381314077784322587/4966917286476189156' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2381314077784322587/posts/default/4966917286476189156'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2381314077784322587/posts/default/4966917286476189156'/><link rel='alternate' type='text/html' href='http://geekstentialism.blogspot.com/2012/06/business-intelligence-shootout.html' title='Business Intelligence Shootout: Microsoft SQL Server 2008 BI vs. Pentaho BI Enterprise Edition (Part 2: SQL Server Analysis Services)'/><author><name>Eric Nograles</name><uri>http://www.blogger.com/profile/02198416401185614213</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJ6RXtY4XZkDuwsIOoO4bKx5gDQMLkcRQIQvtomOIeGbXKFJ1pTzAe7PS1HyPIdW4Rb2Pc5ZljcijJCLlORJpe1c99VfDOUTpn5A06e4WU0yTboyJiAC0eJ8PT0Itv1xc/s1600/*'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgAtYFXNuvuOuhdcekHq8yWdNy8nGN9eot6ZR9Gf5g1OYVOK8r5lGTbmrfdiX31ucs6nLVcU3TDPWjE1UJegPnAQWQTVDI7dQjpboNJfxo9k-jPTwRajNMtavrOCXoVNheCYKEQPg9M62FG/s72-c/Configuration+Manager.png" height="72" width="72"/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2381314077784322587.post-150555434763746422</id><published>2012-02-20T18:16:00.000-08:00</published><updated>2012-02-21T06:30:40.424-08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="bi"/><category scheme="http://www.blogger.com/atom/ns#" term="business intelligence"/><category scheme="http://www.blogger.com/atom/ns#" term="comparison"/><category scheme="http://www.blogger.com/atom/ns#" term="Pentaho Mondrian"/><category scheme="http://www.blogger.com/atom/ns#" term="SQL Server Analysis Services 2008"/><title type='text'>Business Intelligence Shootout: Microsoft SQL Server 2008 BI vs. Pentaho BI Enterprise Edition (Part 1: Pentaho Analysis Services)</title><content type='html'>&lt;div dir=&quot;ltr&quot; style=&quot;text-align: left;&quot; trbidi=&quot;on&quot;&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;b&gt;A Tale of Two Analytic Engines&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
&lt;div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;In this first head-to-head comparison, we pit Microsoft SQL Server Analysis Services (SSAS) 2008 against Pentaho BI&#39;s Analysis Services. &amp;nbsp;As mentioned in the introductory post, in-memory analytic engines aren&#39;t exactly new hat. &amp;nbsp;In fact, the inspiration for these engines came from very simple spreadsheet applications, hence the origins of Essbase&#39;s name -- &quot;Extended Spread Sheet Database.&quot; &amp;nbsp;Despite their age, the benefits of analytic engines remain the same: data retrieval and ad-hoc analysis at lightning speeds. &amp;nbsp;Because most of the data is persisted in RAM, the speed at which you retrieve the data is only limited by your network speed and your user interface&#39;s rendering. &amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;With that in mind, we have two analytic engines here that drew inspiration from similar roots, but go about their implementations differently. &amp;nbsp;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;a href=&quot;http://www.microsoft.com/sqlserver/en/us/solutions-technologies/business-intelligence/analysis-services.aspx&quot;&gt;Microsoft SQL Server Analysis Services&lt;/a&gt; has actually been around since SQL Server 7, thanks to Big Redmond&#39;s acquisition of Panorama Software. &amp;nbsp;It has only been a recent development, starting with SQL Server 2005, that Microsoft has made a serious push into the BI space, literally offering its entire BI stack for free with a SQL Server 2005 (and then later, 2008) Standard Edition license. &amp;nbsp;Microsoft innovated the now ubiquitous Multi-Dimension Expression (MDX) query, and has made strides in usability, deeply integrating the SQL Server BI platform to all of its core enterprise offerings, Microsoft Office and Microsoft Sharepoint, as well as its well renowned integrated development environment, Visual Studio.&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;a href=&quot;http://mondrian.pentaho.com/&quot;&gt;Pentaho Analysis Services&lt;/a&gt;, aka Mondrian in the open source world, is a relative newcomer to the BI marketplace. &amp;nbsp;Started by industry veterans from the defunct Arbor Software (where Essbase was incubated and released) at the turn of the 21st century, this Java-based analytic platform began its roots as an open source platform, along with the other components of Pentaho BI. &amp;nbsp;The goal of its founders was to create a powerful, flexible, cohesive, scalable, and cost-effective platform, meeting or exceeding the capabilities of its commercial conglomerate counterparts. &amp;nbsp;Who better to architect and develop such a solution but some of the very pioneers of the BI movement? &amp;nbsp;PAS is the core of the Pentaho BI stack, offering seemingly the same capabilities as other analytic engines in the market.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;With the history of both analytic engines in mind, let&#39;s take a deep dive at both, using AdventureWorks as the star schema base. &amp;nbsp;For the impatient, I have published all the artifacts produced in this blog post on my source control system, which grants everyone read access. &amp;nbsp;If you have a Subversion client, point to&amp;nbsp;&lt;a href=&quot;https://edg.sourcerepo.com/edg/PentahoAdventureWorks&quot;&gt;https://edg.sourcerepo.com/edg/PentahoAdventureWorks&lt;/a&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;/span&gt;&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;b&gt;The Requirements&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;We will test both analytic engines&#39; and corresponding design tools&#39; capabilities in addressing the following requirements for the AdventureWorks data warehouse:&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;ol style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;The analytic engine shall be able to form the ResellerSales cube and shared dimensions within a common domain&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;The analytic engine shall be able to define a simple calculated member against the cube created against ResellerSales called &quot;Net Profit or Loss&quot;. &amp;nbsp; Calculation: (Extended Amount - Total Product Cost)&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;The analytic engine shall be able to define a simply hierarchy in DimSalesTerritory that illustrates the following: Region --&amp;gt; Country --&amp;gt; Group&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;The analytic engine shall implement the following read security rules on FactResellerSales: only Sales Managers and Executives can read FactInternetSales and FactReseller sales data&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;With the stage set, let&#39;s begin our comparison!&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;b&gt;Pentaho Analysis Services (PAS)&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;u&gt;Setup&lt;/u&gt;&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;ul style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Ubuntu 11.10 (aka Oneiric Ocelot) Server&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;2 GB RAM&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;20 GB HDD&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Apache TomCat Server&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;MySQL Server (collocated with PAS)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Source Control Link: &lt;a href=&quot;https://edg.sourcerepo.com/edg/PentahoAdventureWorks&quot;&gt;Click Here&lt;/a&gt;&lt;/span&gt;&lt;span style=&quot;background-color: white; font-family: Arial, Helvetica, sans-serif; font-size: 16px; line-height: 24px;&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;u&gt;Design Tools&lt;/u&gt;&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;ul style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Pentaho User Console (web-based)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Pentaho Schema Workbench (desktop application)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;u&gt;Overview&lt;/u&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;The standard setup of Pentaho Business Analytics is collocating all components on the same server, which is also the default for SQL Server 2008 BI. &amp;nbsp;We won&#39;t get into how to optimally arrange the platforms in a distributed format for the purposes of this head-to-head, but know that Pentaho does have the ability to scale out in a distributed format, if needed.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;The setup for Pentaho Business Analytics on Ubuntu is a cinch. &amp;nbsp;Just have a standard Ubuntu Server ready with no components (save for SSH) installed, and then run the binary, follow the instructions, change the defaults if you wish, and voila. &amp;nbsp;PBA can be up and running on a Linux Server in about 10 minutes. &amp;nbsp;The primary front-end for Pentaho Business Analytics is a component called Pentaho User Console, a web-based application.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjc5u321rTWiih56my88N_cH_7OZtm9gvH-_24jxG3LikV1W1b8a9TkRM1jsmJDxt-zA7x6czEzaGjsfPjhuT_QEW6gKGwxuWtetOdfCmDQ0f5mExydwHwMh_01pfg7Xhecg5t1bq3qAd__/s1600/User+Console+Login.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;182&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjc5u321rTWiih56my88N_cH_7OZtm9gvH-_24jxG3LikV1W1b8a9TkRM1jsmJDxt-zA7x6czEzaGjsfPjhuT_QEW6gKGwxuWtetOdfCmDQ0f5mExydwHwMh_01pfg7Xhecg5t1bq3qAd__/s320/User+Console+Login.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right: auto; text-align: center;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgIhzWW92Rg6XUQKjfelb7sf9NxP6o654TnUEUFD76uZaOsJ3UoLtTTC3ZbfgAYkCC-9bVybt0yOgPlsXkxJBBEmtwIb_5udtG8o8J-Emam_FI14jZKGRs1fnQCUVfMKeMVXSwaoB-oQHtk/s1600/User+Console+Main+Page.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;214&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgIhzWW92Rg6XUQKjfelb7sf9NxP6o654TnUEUFD76uZaOsJ3UoLtTTC3ZbfgAYkCC-9bVybt0yOgPlsXkxJBBEmtwIb_5udtG8o8J-Emam_FI14jZKGRs1fnQCUVfMKeMVXSwaoB-oQHtk/s320/User+Console+Main+Page.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;The Pentaho User Console Login and Main Page, respectively&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;In terms of PAS, like the SSAS engine, it is hosted in-memory. &amp;nbsp;Pentaho describes the PAS as an &quot;ROLAP engine&quot;, which may confuse people accustomed to SSAS. &amp;nbsp;In PAS world, &quot;ROLAP&quot; stands for &quot;relational OLAP&quot;, whereas in SSAS, it stands for a type of expensive in-memory storage for its cubes called &quot;real-time OLAP&quot;. &amp;nbsp;It&#39;s important to note the distinction, as they have completely different uses.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;u&gt;Connection and Schema Creation&lt;/u&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;A user or developer can go about deploying cubes on their Pentaho one of two ways: define a new Data Source on the Pentaho User Console (more for the power-user) or create a proper schema using the desktop application Pentaho Schema Workbench. &amp;nbsp;As a developer, there are many limitations of the first option -- you may only create one cube at a time and cannot fully define the whole domain of your data warehouse, no advanced security settings, inability to create complex calculated members, etc. &amp;nbsp;So, for the purposes of this shootout, we will cover the proper &quot;developer&quot; way of creating and deploying cubes -- using Pentaho Schema Workbench. &amp;nbsp;I have installed it on my local Windows 7 machine and will be developing on Windows whilst deploying to the Linux server.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;When you first launch Pentaho Schema Workbench, the first thing you must define is a connection to your data warehouse, which is accomplished by clicking Options --&amp;gt; Connection...&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjUg2-h-oScpCceVNoRWNMtzBXw1_B-l3fhk_qpCWAcl3xUzjCHcaz42YtiNk83P-Q63HI8i_ZsbHYko1WyTA7y81tS33D7zJjf6BfadBH3Dh3f2v2QYYIMs0ujH4168bvtmRpa-uhNzjrh/s1600/Schema+Workbench+Connection+Info.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;296&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjUg2-h-oScpCceVNoRWNMtzBXw1_B-l3fhk_qpCWAcl3xUzjCHcaz42YtiNk83P-Q63HI8i_ZsbHYko1WyTA7y81tS33D7zJjf6BfadBH3Dh3f2v2QYYIMs0ujH4168bvtmRpa-uhNzjrh/s320/Schema+Workbench+Connection+Info.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Note that Pentaho supports most all database platforms -- however, keep in mind that, if you want a certain database connection to work in development time, &lt;a href=&quot;http://developers.sun.com/product/jdbc/drivers&quot;&gt;you must download its respective JDBC driver&lt;/a&gt; and drop it into the \schema-workbench\drivers subfolder on your development machine. &amp;nbsp;Likewise, on the server, be sure to drop the .JAR file for that JDBC driver into the /tomcat/lib subfolder to ensure functionality. &amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;We are using out of the box connectivity to MySQL, so we don&#39;t have any exotic setup for this review.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Once a connection is established, you may begin creating your cube and dimension structure by clicking on File --&amp;gt; New --&amp;gt; Schema.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhmqRn8whDy2Y6IzfLhbPGkljMeWoTFm3YIIo6hEsxuf7D18C2PwaXmrrA3kbftR8zKeNVWTpc89fecd65Jxx3J28RFU5I_BDbHQRChQtfV8lR4133TaB23lq9_MzajLlD5tdiUzbtBEu3e/s1600/Schema+Workbench+Blank+Schema.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;240&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhmqRn8whDy2Y6IzfLhbPGkljMeWoTFm3YIIo6hEsxuf7D18C2PwaXmrrA3kbftR8zKeNVWTpc89fecd65Jxx3J28RFU5I_BDbHQRChQtfV8lR4133TaB23lq9_MzajLlD5tdiUzbtBEu3e/s320/Schema+Workbench+Blank+Schema.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Nothing too interesting yet, but we have our foundation laid down. &amp;nbsp;A MySQL connection to AdventureWorksDW2008 defined in an as-of-yet unsaved Pentaho schema file. &amp;nbsp;To those familiar with the cohesiveness of the Visual Studio IDE, the first impression of the Pentaho Schema Workbench is that it looks a little...bare bones. &amp;nbsp;The analogy I can provide is that Visual Studio is like making a painting, but having a bunch of tracing templates ready to use, whereas the Schema Workbench seems like literally a blank canvas. &amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;We can click on the topmost &quot;Schema&quot; node and define some attributes, so let&#39;s call our schema &quot;AdventureWorksDW&quot;.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;u&gt;Cube Creation&lt;/u&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Now let&#39;s go and define our analytic structure, starting with the cubes. &amp;nbsp;We&#39;ll begin with the cube that will be created against FactResellerSales.&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;ol style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Right click at the topmost &quot;Schema&quot; node then click on &quot;Add Cube&quot;
&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;In the &quot;name&quot; field, type in ResellerSales&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Right click on the cube level (now named ResellerSales) then click &quot;Add Table&quot;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;In the new &quot;Table for ResellerSales Cube&quot; child, expand the &quot;name&quot; dropdown and select FactResellerSales.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Right click on the cube level then click &quot;Add Measure&quot;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;In this new &quot;Measure for ResellerSales Cube&quot; child member, type in &quot;Unit Price&quot; for the name&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Expand the &quot;aggregator&quot; dropdown and select &quot;sum&quot;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Expand the &quot;column&quot; dropdown, then select &quot;Fact Reseller Sales -&amp;gt; Unit Price - DECIMAL(10,0)&quot;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;In the formatString property, type in #,###&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Expand the dataType dropdown and select Numeric&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;/span&gt;&lt;br /&gt;
&lt;div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Repeat steps 4-9 above for all the measures you want to pull down from FactResellerSales. &amp;nbsp;For all the other cubes you want included in this schema, obviously repeat the entire process.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;u&gt;Creating Calculations&lt;/u&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Creating calculations on the cube is a relatively straightforward process in Schema Workbench.&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;ol style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Right click on the ResellerSales cube, then click &quot;Add Calculated Member&quot;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;In the newly created Calculated Member node, change the name to &quot;Net Profit or Loss&quot;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;The dimension should be &quot;Measures&quot;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;The formula should be [Measures].[Extended Amount] - [Measures].[Total Product Cost]&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;The formatString should be #,##0.00&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;
&lt;/span&gt;&lt;br /&gt;
&lt;div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;The calculated member should look similar to the example below.&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi30mR9T-RtkYzjcfQiMkNYu_PwL5WP5_EMHPMC_SytqIfD0xLvKInDDryTJPGrLKzny1_KNwkIehmFjfNaenw4toaSOtUh2KQosX8S9OLwhqcRBXuQJqxUzUFFvppdINdf_pwDmkEBxywC/s1600/Schema+Workbench+Calculated+Member.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;211&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi30mR9T-RtkYzjcfQiMkNYu_PwL5WP5_EMHPMC_SytqIfD0xLvKInDDryTJPGrLKzny1_KNwkIehmFjfNaenw4toaSOtUh2KQosX8S9OLwhqcRBXuQJqxUzUFFvppdINdf_pwDmkEBxywC/s320/Schema+Workbench+Calculated+Member.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;u&gt;Dimension Creation&lt;/u&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Dimensions can be created on two levels in Pentaho. &amp;nbsp;The first level is local to the cube -- that is, dimensions that are only accessible by that cube only. &amp;nbsp;While this can be useful for dimensions that are only in use by one cube, in the AdventureWorks example, most all of the dimensions are shared across fact tables. &amp;nbsp;This brings us to the second level where we can define dimensions -- schema-wide. &amp;nbsp;We will create all dimensions at this level for this exercise, so to attain maximum flexibility.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;First, right click at the &quot;Schema&quot; node then click on &quot;Add Dimension&quot;. &amp;nbsp;You should get the &quot;New Dimension 0&quot; as a child of Schema now.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Just to show an example of how the dimension creation process works, let&#39;s walk through the creation of adding the &quot;DimEmployee&quot; (we&#39;ll call it Employee in our schema) table. &amp;nbsp;&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;ol style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;In the newly created dimension above, change the name to &quot;DimEmployee&quot; (we&#39;ll call it the appropriate &quot;Employee&quot; dimension name when we use this dimension in cubes later). &amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;In the description, we&#39;ll specify some information. &amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;In foreign key, expand the dropdown and select &quot;FactResellerSales --&amp;gt; EmployeeKey - INTEGER(10)&quot;.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;The type should be &quot;StandardDimension&quot;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;In the Caption, type in AdventureWorks Employee&quot;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Expand this new dimension. &amp;nbsp;A default &quot;New Hierarchy 0&quot; should&#39;ve been created.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;This is the topmost hierarchy which will be the parent for &quot;All Employees&quot;. &amp;nbsp;Blank out the &quot;name&quot; field for New Hierarchy 0 then press enter. &amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;In the allMemberName, type in &quot;All Employees&quot;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;For the primaryKey field, select EmployeeKey&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Right click on this new hierarchy (should now be labelled &quot;default&quot;) then click &quot;Add Table&quot;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;In the new &quot;Table for Hierarchy&quot; child, select DimEmployee in the name dropdown.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Left click back to the &quot;default&quot; node under Employee. &amp;nbsp;Expand the primaryKey field then select DimEmployee -&amp;gt; EmployeeKey - INTEGER(10).&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Right click back on the &quot;default&quot; property. &amp;nbsp;Now click &quot;Add Level&quot;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;In the name field for this new &quot;Level for Hierarchy&quot; child, type in &quot;First Name&quot;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;In the &quot;table&quot; dropdown, select DimEmployee.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;In the &quot;column&quot; dropdown, select &quot;DimEmployee --&amp;gt; FirstName --&amp;gt; VARCHAR(50)&quot;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;In the &quot;type&quot; dropdown, select String.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;For levelType, select Regular&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;for hideMemberIf, select Never&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Repeat steps 12-19 for the rest of the dimensional attributes for DimEmployee. &amp;nbsp;(The caveat being, make sure to select the proper &quot;type&quot; for the data you&#39;re pulling up. &amp;nbsp;Ex. varchar = string, datetime = date, etc). &amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Also, repeat the process above for each dimension linked up to ResellerSales.&lt;/span&gt;
&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;The schema should now look something similar to below.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjLI5tO4jQRX_eltqO2C6_TJSrlVgR6tq15IaHXNZbqi-yoB0atVqI-4u4d1eB0cL3TB_8fXay1g7CUBmsxkFfeCLCu4DDV2sRMJIEzDEgFv7vHqb60Qm2ilX4FsAjTBWQgdprDAL6wTob1/s1600/Schema+Workbench+Employee+Dimension.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;234&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjLI5tO4jQRX_eltqO2C6_TJSrlVgR6tq15IaHXNZbqi-yoB0atVqI-4u4d1eB0cL3TB_8fXay1g7CUBmsxkFfeCLCu4DDV2sRMJIEzDEgFv7vHqb60Qm2ilX4FsAjTBWQgdprDAL6wTob1/s320/Schema+Workbench+Employee+Dimension.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Now, how do we get the cube to use the dimension? &amp;nbsp;In Pentaho Analysis Services, there is a concept of Dimension Usage in the cube. &amp;nbsp;This allows for shared dimensions. &amp;nbsp;To create a dimension usage on the ResellerSales cube, perform the following steps:&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;ol style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Right click on the &quot;ResellerSales&quot; node then click on &quot;Add Dimension Usage&quot;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;For the name of the Dimension Usage, type in &quot;Employee&quot;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;For the foreignKey of the dimension usage, select EmployeeKey&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;For the source of the dimension usage, select DimEmployee (the shared dimension we created previously)&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;The steps above create some configuration for our cube to use the &quot;DimEmployee&quot; shared dimension, and call it the &quot;Employee&quot; dimension within the context of the ResellerSales cube.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;u&gt;Creating Hierarchies&lt;/u&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;When we get to the creation of the Sales Territory dimension, one will notice that it has a hierarchical structure. &amp;nbsp;Specifically, our data is organized such that SalesTerritoryGroup is the parent of SalesTerritoryCountry. &amp;nbsp;In turn, SalesTerritoryCountry is the parent of SalesTerritoryRegion. &amp;nbsp;In this example, I&#39;ve simplified the naming conventions to be Group, Region, and Country.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;To define this hierarchical structure, assuming these fields are already created under the same default hierarchy as explained above, creating this tiered relationship is as follows in Schema Workbench:&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;ol style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Click on the &quot;Country&quot; level under the DimSalesTerritory shared dimension.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Click on the parentColumn attribute, then select SalesTerritoryGroup&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Click on the &quot;Region&quot; level&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Click on the parentColumn attribute, then select SalesTerritoryCountry&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Unfortunately, defining the hierarchy in the Sales Territory dimension above gives us some drawbacks in the dimension. &amp;nbsp;First, the way data will come up will only be if you first drop the Group attribute to an axis first, then the Country, then the Region. &amp;nbsp;If you drop the Country attribute into an axis directly, your report will yield no data. &amp;nbsp;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;This is a little curious, as in the SSAS world, this does not happen. &amp;nbsp;In fact, defining the hierachy in SSAS such as the above makes the parent-child relationships easier to read and has no effect on dropping any of the child attributes to an axis to slice and dice.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Again, my new-ness to Pentaho may have something to do with this, and I will explore a resolution of this hierarchy model as I understood it from SSAS. &amp;nbsp;Until then, I will have to take points away from Pentaho for an unclear definition of how the hierarchies should be defined and consumed. &amp;nbsp;For my model, I have just removed the hierarchy in the Sales Territory dimension altogether so that the slice and dice capability is at least usable for this dimension.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;u&gt;Applying Security&lt;/u&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Security is also implemented with Pentaho Analysis Services. &amp;nbsp;This obviously will be defined at the Schema level, as security spans across all cubes and dimensions. &amp;nbsp;To setup our security such that only Resellers and Executives can actually see the ResellerSales cube, the following steps should be taken:&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;ol style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Right click on the &quot;Schema&quot; node then click &quot;Add Role&quot;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;In the name of the new role, type in &quot;Resellers&quot;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Right click on the Resellers role then click &quot;Add Schema Grant&quot;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;On the access attribute, select &quot;none&quot;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Right click on the Schema Grant node, then click &quot;Add Cube Grant&quot;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;On the access attribute of the Cube Grant, select &quot;all&quot;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;On the cube attribute of the Cube Grant, select ResellerSales&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;One note about the security model -- it is not easy to implement out of the box. &amp;nbsp; &amp;nbsp;&lt;a href=&quot;http://mondrian.pentaho.com/documentation/schema.php#Access_control&quot;&gt;Per the Mondrian documentation, you can either embed the Role in the connection string or programmatically set it&lt;/a&gt;. &amp;nbsp;All I could think of is how ugly this is. &amp;nbsp;The security implementation of an analytic engine should be a basic implemented feature, but Mondrian appears to have a separate security model from the Pentaho User Console. &amp;nbsp;This definitely may be a dealbreaker for many folks.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;u&gt;Publishing the PAS Schema&lt;/u&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;So, assuming you&#39;ve completed the steps above for the rest of the dimensions within AdventureWorksDW2008 and linked it them up to the ResellerSales cube, we essentially have a ready to publish schema. &amp;nbsp;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Publishing from Schema Workbench is relatively straightforward. &amp;nbsp;Simply click File --&amp;gt; Publish. &amp;nbsp;A popup window appears which asks you to specify the URL to your Pentaho User Console, the Publish Password, and a Username/Password combination with sufficient rights to publish.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right: auto; text-align: center;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh8_apE7quDGq8fdIodsgcE0ck2RTXNpU4kKVEeP2gGqZU32NcAkFK8lMonLg6bLqVIQokw9cpVli58fdQdtIPZZJ27sYOnz-NaWQY26vbqVE4c6Ek5DqQbjcLnTzTAY_d3f0fg21TnjTWn/s1600/Schema+Workbench+Publish+Schema.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;309&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh8_apE7quDGq8fdIodsgcE0ck2RTXNpU4kKVEeP2gGqZU32NcAkFK8lMonLg6bLqVIQokw9cpVli58fdQdtIPZZJ27sYOnz-NaWQY26vbqVE4c6Ek5DqQbjcLnTzTAY_d3f0fg21TnjTWn/s320/Schema+Workbench+Publish+Schema.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;tr-caption&quot; style=&quot;font-size: 13px;&quot;&gt;Publishing options in Pentaho Schema Workbench&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Click OK, and Schema Workbench then makes a connection to your Pentaho repository. &amp;nbsp;Finally, select a destination for this new schema, specify the named connection on your Pentaho server which the schema will use to aggregate its data, and optionally, publish this schema as part of PAS&#39; XMLA service. &amp;nbsp;Click on Publish, and then your schema will now be accessible as part of your Pentaho install.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right: auto; text-align: center;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjvMR3UKp3jTJnmenhW3C9yIuTEFOn9Hv6yna5ocl5eiqbxRs8789Lx_RrHCTXeBlGWlfMG01HGcCIf6qmiq_4US_K0nFNmXTNCBmuRD8p2fPXDm3MEx3b7KmFchTBOdVE0VPqlXfbbebX6/s1600/Schema+Workbench+Publish+Schema+Final+Step.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;320&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjvMR3UKp3jTJnmenhW3C9yIuTEFOn9Hv6yna5ocl5eiqbxRs8789Lx_RrHCTXeBlGWlfMG01HGcCIf6qmiq_4US_K0nFNmXTNCBmuRD8p2fPXDm3MEx3b7KmFchTBOdVE0VPqlXfbbebX6/s320/Schema+Workbench+Publish+Schema+Final+Step.png&quot; width=&quot;306&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;tr-caption&quot; style=&quot;font-size: 13px;&quot;&gt;The final step in publishing the Pentaho Schema&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;u&gt;Schema Creation/Publishing Summary&lt;/u&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Admittedly, coming from the land of SQL Server 2008 BI, the development process feels incredibly cumbersome with Pentaho. &amp;nbsp;The Schema Workbench should try to adopt something as smooth as an SSAS project on BIDS, whereby the developer simply adds all the dimension and fact tables visually on a view, drag and drop PK/FK relationships among the dimension and fact tables, then creates cubes based on this view, and then finally the tool should be smart enough to create the skeleton for all the dimensions with the fact measures linked up. &amp;nbsp;The definition of the dimensional attributes, properties, and hierarchies should come after this quick vetting out process. &amp;nbsp;It&#39;s a small thing, but the fact that Schema Workbench can&#39;t create a skeleton structure for me given a relational star schema is a bit annoying.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Anyway, creating your analytic schema will take a bit of time in Pentaho, even if it&#39;s a relatively simple star schema with nothing crazy related to hierarchies or calculated members. &amp;nbsp;Calculated members are straightforward. &amp;nbsp;However, the hierarchies, while straightforward to construct, don&#39;t seem to behave in the way that I would expect them to behave. &amp;nbsp;Furthermore, the security model really is, for lack of a better term, primitive. &amp;nbsp;I don&#39;t understand how PAS got through three version releases without implementing security integration with the rest of the Pentaho suite, it&#39;s just mind-boggling.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;In terms of publishing the schema, the process is relatively straightforward from Schema Workbench. &amp;nbsp;There doesn&#39;t appear to be an automated way of deploying cubes (as part of a Continuous Integration Server, for example), but the structure is relatively simple. &amp;nbsp;The schema file itself is an XML file that will get dropped somewhere in the repository and making it available for consumption is a matter of updating the /pentaho-solutions/system/olap/datasources.xml file.&amp;nbsp;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;To sum up, the creation and publishing of the schemas in Pentaho, while functional, seems a bit rough around the edges. &amp;nbsp;This might be slightly off-putting for anyone who came from the SQL Server BI platform first.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;u&gt;Consumption of Data&lt;/u&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;So, now we get to the fun part -- using the data schema we just published. &amp;nbsp;We&#39;ve got a nice Reseller Sales cube using a bunch of Shared Dimensions defined above and published to our Pentaho server. &amp;nbsp;Let&#39;s do a little slicing and dicing!&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;The primary user interface for the Pentaho BI stack is the User Console. &amp;nbsp;On a default install, it sits on port 8080 of your Apache Tomcat server. &amp;nbsp;So, let&#39;s pop into the User Console and see where we can get at this data.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhdfB9s2gxH02xPKH1T4TgcfgRFvb_jUwLV-TyoIYNMUBzc854PPSDHXx7VjUxH6AUApFXHiTb6pn4_WL9f85KWxoT9P12O7zSlvNDKrVvz1URICeHpsQ7TqskX9JSyeXbT9PJYKvic3VWQ/s1600/Pentaho+User+Console+Main+Page.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;171&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhdfB9s2gxH02xPKH1T4TgcfgRFvb_jUwLV-TyoIYNMUBzc854PPSDHXx7VjUxH6AUApFXHiTb6pn4_WL9f85KWxoT9P12O7zSlvNDKrVvz1URICeHpsQ7TqskX9JSyeXbT9PJYKvic3VWQ/s320/Pentaho+User+Console+Main+Page.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right: auto; text-align: center;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEikLU2nUirJpYRlXnE-xl8CFaBeNFCxxgLi27jFsDga0kI7m-00ucgqdTHcFxRyNX5pHOP-vy8VNpcxPtyI7IIaDzEJE-g2A65FCNl51Xs0o2Ko6yjRwywdYSBA0DCFd8K2rJEJnZqiiWnm/s1600/Pentaho+User+Console+Main.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;207&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEikLU2nUirJpYRlXnE-xl8CFaBeNFCxxgLi27jFsDga0kI7m-00ucgqdTHcFxRyNX5pHOP-vy8VNpcxPtyI7IIaDzEJE-g2A65FCNl51Xs0o2Ko6yjRwywdYSBA0DCFd8K2rJEJnZqiiWnm/s320/Pentaho+User+Console+Main.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;The Pentaho User Console looks very slick&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;I must admit, this is one of the components of Pentaho that I&#39;ve absolutely grown to love. &amp;nbsp;In terms of user interface presentation and coherence, I believe Pentaho really has beaten Microsoft here. &amp;nbsp;Whereas a SQL Server 2008 BI solution is disjointed from the start (until you develop some Sharepoint site to sit on top with a little PowerPivot and some SSRS reports), Pentaho feels...&lt;i&gt;unified &lt;/i&gt;right out of the gate. &amp;nbsp;Everything through the web-based User Console first.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;We&#39;ll go into a deep dive of the Pentaho User Console in a later article which we review the overall &quot;out of the box&quot; user presentation of each platform, but from a preliminary perspective, I really prefer the Pentaho web-based UI.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;The way to do some ad-hoc slice and dice analysis on a published cube on the User Console is simple. &amp;nbsp;Click on New Analysis, select the cube, and you&#39;re off!&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjxvdnMfUzCD5LLIySnM8XXapQqtldPmAYmbN_hsgo7xjqvdIFjr9VWTNRqGD5u5Z0Lgs7ue6GQdqM5GZGzr9gBW1-OR5x24Gc1ziLdS2QMCW6Gw-FOMu3AdWyu-amwJwxKVsvycM4b78U6/s1600/Pentaho+User+Console+New+Analysis+Select+Cube.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;210&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjxvdnMfUzCD5LLIySnM8XXapQqtldPmAYmbN_hsgo7xjqvdIFjr9VWTNRqGD5u5Z0Lgs7ue6GQdqM5GZGzr9gBW1-OR5x24Gc1ziLdS2QMCW6Gw-FOMu3AdWyu-amwJwxKVsvycM4b78U6/s320/Pentaho+User+Console+New+Analysis+Select+Cube.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right: auto; text-align: center;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center;&quot;&gt;&lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;&lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right: auto; text-align: center;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg5Gu6Zs_KUKRYLnCEEUkqgLPVaCssW2S8oTU4Z65UxPOqPpqswSVHFw1aglId1is0afW-p2MiXzMYqRasMQAi2WmbYoAnhUHgjGOewI1aV4Cm0cp2gKNXzA1oZxpcoHpO4HYv0oJCXiw5t/s1600/Pentaho+User+Console+New+Analysis+Unsaved+Report.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;195&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg5Gu6Zs_KUKRYLnCEEUkqgLPVaCssW2S8oTU4Z65UxPOqPpqswSVHFw1aglId1is0afW-p2MiXzMYqRasMQAi2WmbYoAnhUHgjGOewI1aV4Cm0cp2gKNXzA1oZxpcoHpO4HYv0oJCXiw5t/s320/Pentaho+User+Console+New+Analysis+Unsaved+Report.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;Voila! &amp;nbsp;A blank analysis report&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;The report obviously starts off blank, but if you&#39;re familiar with any analytic tool, the next part should be straightforward. &amp;nbsp;Simply drop what measures you want to aggregate onto the report canvas. &amp;nbsp;Afterwards, drop in whatever dimensional attribute you want along the row and column axes. &amp;nbsp;For this example, let&#39;s measure Net Profit or Loss. &amp;nbsp;Then, let&#39;s see the data by Department Name (off the Employee Dimension) across the rows and then by Fiscal Year across the columns.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right: auto; text-align: center;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgt7aR7AdYKDgxb-IqS7usueZnX9AK0KJi9zgl0NQP4lS8BE0I-FA5EaliDBhgj2GIYHIoQULPi32IQiY_q-Fm9nBYPiJwPq374yB4oaIQrQKwbtTYFfS-ItW9U5GansAVLTpmftdwcxj6y/s1600/Pentaho+Analyzer+Report+Net+Profit+or+Loss+By+Department+and+Year.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;196&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgt7aR7AdYKDgxb-IqS7usueZnX9AK0KJi9zgl0NQP4lS8BE0I-FA5EaliDBhgj2GIYHIoQULPi32IQiY_q-Fm9nBYPiJwPq374yB4oaIQrQKwbtTYFfS-ItW9U5GansAVLTpmftdwcxj6y/s320/Pentaho+Analyzer+Report+Net+Profit+or+Loss+By+Department+and+Year.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;Hmmm..repeating department names.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;The data is presented, as we expect, in lightning speed. &amp;nbsp;However, one quirk I see with the Analyzer Report is that it does not aggregate by dimensional attribute...it only seems to aggregate by the dimension itself. &amp;nbsp;In other words, in SSAS, the above slice would&#39;ve come up with one row only, with Net Profit or Loss aggregated by the Department Name. &amp;nbsp;However, it appears that the Pentaho web-based Analyzer Report does not have this built-in functionality. &amp;nbsp;The &quot;Sales&quot; department name above repeats by the number of employees we have in the Employee dimension. &amp;nbsp;How curious.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Of course, this could be the way I designed the dimension, &lt;a href=&quot;http://forums.pentaho.com/showthread.php?90222-How-to-Group-By-Dimensional-Attribute&quot;&gt;so I&#39;ve floated up this question to the Pentaho Forums&lt;/a&gt;, hoping to gain insight, if it is indeed a system limitation or simply incorrect design on my part for the dimension. &amp;nbsp;I will update this article as soon as I get a response back. &amp;nbsp;If it is a system limitation, that would be a little disheartening, as this web-based analytic tool is otherwise brilliant.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;To demonstrate its flexibility, let&#39;s slice out some data that won&#39;t produce repeat data. &amp;nbsp;Let&#39;s measure Net Profit or Loss by Region (off of the Sales Territory dimension) on the column axis and then dragging the Fiscal Year dimensional attribute over to the row axis. &amp;nbsp;Changing your sliced data is as simple as clicking and dragging the Department Name dimension from the row axis and into a garbage can that appears in the lower right hand corner. &amp;nbsp;Then, drag the Fiscal Year dimensional attribute from the column axis to the row axis. &amp;nbsp;Finally, drag over the Region dimensional attribute from the Sales Territory dimension and drop it on the column axis.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right: auto; text-align: center;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgT5U1bR-Za07Fk4l3gqlYu5r9jOUP0HaH7WcBtMYzorP3KtoE_8HDhouT0XyUZrN0HCFzbA7w6V_fVPvUFDC7Ii2RjavbAccfyS3rwNVcXHVcWF3SpuNwMLK1VHC2dqlqaSDQBZoUEImSn/s1600/Pentaho+Analyzer+Report+Net+Profit+or+Loss+by+Region.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;196&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgT5U1bR-Za07Fk4l3gqlYu5r9jOUP0HaH7WcBtMYzorP3KtoE_8HDhouT0XyUZrN0HCFzbA7w6V_fVPvUFDC7Ii2RjavbAccfyS3rwNVcXHVcWF3SpuNwMLK1VHC2dqlqaSDQBZoUEImSn/s320/Pentaho+Analyzer+Report+Net+Profit+or+Loss+by+Region.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;The ol&#39; switcheroo&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;/div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Now, let&#39;s get a little fancy. &amp;nbsp;Let&#39;s see this data plotted across a line graph. &amp;nbsp;To do this, there is a Pie-Chart shaped icon on the upper right hand corner of the report (to the right of &quot;View As&quot;). &amp;nbsp;To the right of this pie chart is a downward arrow. &amp;nbsp;Click on the downward arrow then select &quot;Line&quot;.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;/div&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgvfBDVPL-K5PpreooNAT9eD3ljweOp55-eq1hdB5THg6hGq1V-AgSvMAXe6Hd3oJnHWBZpvMhebCqxs6cyA3R49qAGmvMFP9LGDDgDawMz786CCA3XUiQviBbZUMd00xBwBm9ckGoHTl_5/s1600/Pentaho+Analyzer+Report+Net+Profit+or+Loss+by+Region+View+As+Line.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;196&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgvfBDVPL-K5PpreooNAT9eD3ljweOp55-eq1hdB5THg6hGq1V-AgSvMAXe6Hd3oJnHWBZpvMhebCqxs6cyA3R49qAGmvMFP9LGDDgDawMz786CCA3XUiQviBbZUMd00xBwBm9ckGoHTl_5/s320/Pentaho+Analyzer+Report+Net+Profit+or+Loss+by+Region+View+As+Line.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right: auto; text-align: center;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjO98QXEd-agTmFlfLOrFtI21UL4ZzcwvHChQ7y1Q96bm5iWMivv6wYV7hysZUdluBz1C8wJ1zwuYDtIHccNH9MhHsbLeq9xHWHg4uV6Kg3SZD_GEQpen7BzRLT6L_Cf1DmVa7H0TxlfrFG/s1600/Pentaho+Analyzer+Report+Net+Profit+or+Loss+by+Region+Line+Graph.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;195&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjO98QXEd-agTmFlfLOrFtI21UL4ZzcwvHChQ7y1Q96bm5iWMivv6wYV7hysZUdluBz1C8wJ1zwuYDtIHccNH9MhHsbLeq9xHWHg4uV6Kg3SZD_GEQpen7BzRLT6L_Cf1DmVa7H0TxlfrFG/s320/Pentaho+Analyzer+Report+Net+Profit+or+Loss+by+Region+Line+Graph.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;Very slick!&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;As you might have seen from the other options, there are multiple visualizations of data you can make right off of the interface. &amp;nbsp;Very, very slick. &amp;nbsp;Let&#39;s save this report off before we continue. &amp;nbsp;If you haven&#39;t already, in your Pentaho repository, create an AdventureWorksDW2008 folder, and then create an Analysis subfolder underneath that. &amp;nbsp;Click File --&amp;gt; Save As. &amp;nbsp;Browse to this subdirectory and let&#39;s call this report &quot;Net Profit or Loss by Region&quot;. &amp;nbsp;Finally, click Save, and then when you browse to this subdirectory in the repository, you&#39;ll see our fancy new report.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjWF0aCtesxZ-mWH-_u8xq8DA8pYGTH7_dzKyRGkQX1N5_q1Zl8coiRRK1nVCDX_3Dv4mTvUOpChnG98g7sl8udiwJpVoRljmmGtpsZjSpRyY7Azsh6OxXFVnNOtUyE0rfCeH91tOA5SOym/s1600/Pentaho+Analyzer+Report+Net+Profit+or+Loss+by+Region+Saved.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;195&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjWF0aCtesxZ-mWH-_u8xq8DA8pYGTH7_dzKyRGkQX1N5_q1Zl8coiRRK1nVCDX_3Dv4mTvUOpChnG98g7sl8udiwJpVoRljmmGtpsZjSpRyY7Azsh6OxXFVnNOtUyE0rfCeH91tOA5SOym/s320/Pentaho+Analyzer+Report+Net+Profit+or+Loss+by+Region+Saved.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Let&#39;s examine some export options. &amp;nbsp;Web-based is nice and all, but at times, you will need to save off to a file format that can be read by stakeholders locally. &amp;nbsp;The Analyzer Report offers the ability to export to PDF, Excel, or CSV. &amp;nbsp;Simply click on the More option above the report, select Export Report, then select your desired format. &amp;nbsp;See examples below of the export formats we can achieve.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEieIUNcbKlKpz6-M74UpTrwV6-pZ073SywVWZIQKJ_KsdN0d_EFReJUg6gScRaeMMl5lEVUDVHb7Mn_nhQN6n3wAUsOp5AFnsF9jk5cGoQpLIX3hP72RVVai8BhHUw2tuRM_cJ38Q3ElG7H/s1600/Pentaho+Analyzer+Report+Net+Profit+or+Loss+By+Region+Export+Options.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;196&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEieIUNcbKlKpz6-M74UpTrwV6-pZ073SywVWZIQKJ_KsdN0d_EFReJUg6gScRaeMMl5lEVUDVHb7Mn_nhQN6n3wAUsOp5AFnsF9jk5cGoQpLIX3hP72RVVai8BhHUw2tuRM_cJ38Q3ElG7H/s320/Pentaho+Analyzer+Report+Net+Profit+or+Loss+By+Region+Export+Options.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Without going into too much detail, the PDF reports in particular look very slick, complete with a cover page, any graphs (like the one above) we create, and report parameters in the last page.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;On Excel, the data is separated by sheets: tabular data on a &quot;Report&quot; sheet, charts on a &quot;Chart&quot; sheet, and logistic information on a &quot;Report_Information&quot; sheet. &amp;nbsp;This is also a very nice layout.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;CSV is obviously very bare bones, intended to be a raw source of data for any external apps that need to consume your intelligence data.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;On a final note, for those who wish to access the PAS data using Excel natively, PAS actually publishes out an XMLA service that can be consumed within Excel. There is, however, no out-of-the-box connector to Excel that one could use. &amp;nbsp;A software purveyor, &lt;a href=&quot;http://www.simba.com/odbo-to-xmla.htm&quot;&gt;Simba Technologies&lt;/a&gt;, has created a third party Excel connector (SimbaO2X) to XMLA sources that integrates right with Excel&#39;s PivotTable functionality. &amp;nbsp;However, I did some tests with the latest version, and it does not play well with Excel 2010 -- I&#39;m told a fix will be applied that will allow it to play well with Excel 2010. &amp;nbsp;However, as of this writing, this connector is NOT usable with Excel 2010.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;u&gt;Consumption of Data Summary&lt;/u&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Apart from the quirk we saw above with repeating dimensional attributes, I am a very big fan of the Pentaho Analyzer Report module on the Pentaho User Console. &amp;nbsp;It&#39;s incredibly easy to navigate, functions are very intuitive, and the slice and dice capability is at the very least on-par with SSAS. &amp;nbsp;The niceties, such as graphing, exporting, and saving off to the Pentaho repository for use in Dashboards is icing on the cake. &amp;nbsp;This is a world-class data analytic tool.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;b&gt;Pentaho Analysis Services: The Good, The Bad, and The Weird&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;u&gt;The Good&lt;/u&gt;&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;ul style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Pentaho Analyzer is a killer application. &amp;nbsp;Web-based (read: cross-platform), fast, responsive, and with a plethora of options, this module should be the envy of its competitors.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;The PAS engine seems to be very responsive, simple aggregations and calculations are on-par with leading BI tools&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;XMLA service means that PAS is truly cross-platform out of the gate. &amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;u&gt;The Bad&lt;/u&gt;&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;ul style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;A cumbersome development process, even with the Schema Workbench development environment. &amp;nbsp;Pentaho should strive to create a development experience as smooth as BIDS.&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;The operational options of a PAS cube seems limited. &amp;nbsp;Whereas in SSAS, you can specify items like cube data partitioning, method of retrieving data (ROLAP, HOLAP, MOLAP), you&#39;re pretty much stuck with Pentaho&#39;s base functionality unless you muck up the source.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Uneven documentation on the Pentaho website and Knowledge Base. &amp;nbsp;Perhaps the formal courses offered by Pentaho are the way to go, but the literature available for Mondrian seems lacking, specifically for new users.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;No drillthrough capability in Pentaho Analyzer&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Almost laughably limited security model&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;u&gt;The Weird&lt;/u&gt;&lt;/span&gt;&lt;br /&gt;
&lt;ul style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Repeating dimensional attributes?&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Issues displaying hierarchies on Analyzer?&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;b&gt;Pentaho Analysis Services: The Summary&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;The real question we&#39;re trying to determine is if Pentaho Analysis Services is truly an enterprise-level analytic engine, especially from the eyes of someone who worked with SSAS for so long. &amp;nbsp;While the engine does meet most all of the requirements set forth for the comparison (we&#39;re excluding the hierarchies issue and repeating dimensional attribute issue here, as I am new to Pentaho), the quality of documentation of the product is very uneven. &amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;While I understand that Pentaho does not have the backing of the billions that Microsoft possesses, at the very least, a proper starter&#39;s guide should have been written. &amp;nbsp;As it stands, what you can find on the Knowledge Base, I feel, is very lacking, and I had to do a lot of trial and error and Googling examples from forums to get my schema &quot;right enough&quot;.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Also, the development tools seem very lacking in comparison to Microsoft Business Intelligence Development Studio. &amp;nbsp;As I mentioned in the start of the overview, whereas BIDS is a user-friendly, almost guided way to develop BI, Pentaho Schema Workbench is a rough, blank slate, leaving you to hang yourself in multiple, creative ways.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Finally, the big dealbreaker for me would be the security model. &amp;nbsp;Simply put, Pentaho&#39;s solution for PAS security is unsatisfactory. &amp;nbsp;It makes PAS seem like a completely separate module from the rest of the suite (not using the Pentaho user/role model in the User Console). &amp;nbsp;While having a fast, efficient analytic engine is the primary objective, objective 1b should have been to properly integrate with the overall security model, as not EVERYONE should be able to see all analytic data. &amp;nbsp;I don&#39;t know what Pentaho was thinking with this.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;That being said, I would probably feel uncomfortable recommending PAS as an enterprise-level analytic engine for a large company. &amp;nbsp;It is close to being very good, but I feel even in its current iteration (v4.1), it is still undercooked. &amp;nbsp;This is a little frustrating, as I honestly want to like this platform. &amp;nbsp;Analyzer is one of the best web-based analytic tools I&#39;ve encountered, and the concept of the Pentaho User Console on top of it is very slick. &amp;nbsp;However, the lack of a proper guided development process, quirks with creation of simple analytic artifacts such as hierarchies, the primitive security model, and the uneven documentation would be enough to scare most risk-averse enterprises away.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;However, this does not mean I wouldn&#39;t recommend PAS outright. &amp;nbsp;It still does many things very well, and as such, if a company already had a big investment in Java, Linux, and open source platforms or simply cannot afford the costs associated with running and maintaining SSAS, PAS would definitely deserve a fair shake, knowing that getting PAS up-to-par with industry standard analytic engines may require some custom development (specifically with the security).&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;b&gt;Stay tuned for the next article -- SQL Server Analysis Services 2008 and a side-by-side comparison with Pentaho Analysis Services&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geekstentialism.blogspot.com/feeds/150555434763746422/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/2381314077784322587/150555434763746422' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2381314077784322587/posts/default/150555434763746422'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2381314077784322587/posts/default/150555434763746422'/><link rel='alternate' type='text/html' href='http://geekstentialism.blogspot.com/2012/02/business-intelligence-shootout_20.html' title='Business Intelligence Shootout: Microsoft SQL Server 2008 BI vs. Pentaho BI Enterprise Edition (Part 1: Pentaho Analysis Services)'/><author><name>Eric Nograles</name><uri>http://www.blogger.com/profile/02198416401185614213</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJ6RXtY4XZkDuwsIOoO4bKx5gDQMLkcRQIQvtomOIeGbXKFJ1pTzAe7PS1HyPIdW4Rb2Pc5ZljcijJCLlORJpe1c99VfDOUTpn5A06e4WU0yTboyJiAC0eJ8PT0Itv1xc/s1600/*'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjc5u321rTWiih56my88N_cH_7OZtm9gvH-_24jxG3LikV1W1b8a9TkRM1jsmJDxt-zA7x6czEzaGjsfPjhuT_QEW6gKGwxuWtetOdfCmDQ0f5mExydwHwMh_01pfg7Xhecg5t1bq3qAd__/s72-c/User+Console+Login.png" height="72" width="72"/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2381314077784322587.post-8051710644821041955</id><published>2012-02-03T20:49:00.000-08:00</published><updated>2012-02-19T16:18:36.015-08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="bi comparison"/><category scheme="http://www.blogger.com/atom/ns#" term="business intelligence"/><category scheme="http://www.blogger.com/atom/ns#" term="pentaho"/><category scheme="http://www.blogger.com/atom/ns#" term="SQL Server 2008"/><title type='text'>Business Intelligence Shootout: Microsoft SQL Server 2008 BI vs. Pentaho BI Enterprise Edition (Introduction)</title><content type='html'>&lt;div dir=&quot;ltr&quot; style=&quot;text-align: left;&quot; trbidi=&quot;on&quot;&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;b&gt;Business Intelligence: A History in Review&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Business Intelligence (BI) has become a hot topic in IT and business in recent years. &amp;nbsp;In the past, BI seemed more like a gimmick, with disconnected pieces of software made by various purveyors that seemingly required a significant upfront investment from both IT and business resources, from both a fiscal and training standpoint.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;You might have a tremendous analytic engine from someone the likes of Arbor Software with their Essbase platform, but to effectively syphon the data out into a presentable, maintainable format, you&#39;d also have to invest in some sort of enterprise reporting tool like Arcplan. &amp;nbsp;Furthermore, to grant users the ability to perform ad-hoc slice and dice analysis, you&#39;d need an analytic software package pushed by a company like Hyperion. &amp;nbsp;The overall experience of BI, much like its roots, seemed disjointed, with no common vision, and as a result, was expensive to learn and maintain and did not impact the business world the way it had hoped.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;The days of disjointed BI have thankfully past, and we are in an era of computing where these tools have become nearly as slick as their web and desktop enterprise application counterparts. &amp;nbsp;Speed of deployment, ease of use, and cost efficiency are the key elements in modern BI. &amp;nbsp;Users want their data faster, more flexible, and robust enough to truly gain that holy grail of turning business intelligence into business INSIGHT.&amp;nbsp;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;u&gt;&lt;br /&gt;&lt;/u&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;b&gt;The Geekstantialism BI Shootout!&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;In this eight-part series, we will bring two up-and-coming BI platforms front and center, and objectively review the strengths and weaknesses of each, as well as make some recommendations on their optimal effectiveness.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;In one corner stands a slick platform backed by the billions of the Microsoft Corporation -- &lt;/span&gt;&lt;b style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;a href=&quot;http://www.microsoft.com/sqlserver/en/us/solutions-technologies/business-intelligence.aspx&quot;&gt;SQL Server 2008 BI&lt;/a&gt;&lt;/b&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;In the other corner, a young upstart Java-based open source BI platform architected by some of the brightest minds in BI since its inception -- &lt;b&gt;&lt;a href=&quot;http://www.pentaho.com/explore/pentaho-business-analytics/&quot;&gt;Pentaho Business Analytics&lt;/a&gt;&lt;/b&gt;.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;u&gt;&lt;br /&gt;&lt;/u&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;b&gt;The Parameters&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;In order to gain the best insight of the strengths and weaknesses of both platforms when stacked up against each other, I&#39;ve put together the following parameters/requirements that both must follow in this thought exercise...&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;u&gt;Pentaho System Setup&lt;/u&gt;&lt;/span&gt;&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Ubuntu Linux Server 11.10&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Apache Tomcat Server&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;MySQL database backend&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;u&gt;SQL Server 2008 Setup&lt;/u&gt;&lt;/span&gt;&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Windows Server 2008 Enterprise Edition&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;IIS&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;SQL Server 2008 backend&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;b&gt;Data Source&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;The base data warehouse for both will be the &lt;a href=&quot;http://msftdbprodsamples.codeplex.com/releases/view/55926&quot;&gt;AdventureWorksDW2008 database offered by Microsoft&lt;/a&gt;. &amp;nbsp;Obviously, as Pentaho BI runs off of MySQL out of the box, I will be converting said SQL Server database to MySQL, keeping it structurally the same.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;b&gt;Analytic Engine&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Using the design tools for both platforms, cubes will be built against all fact tables in the AdventureWorksDW2008 database. &amp;nbsp;All dimensions will be created. &amp;nbsp;Dimensional hierarchies will be created where applicable. &amp;nbsp;Simple calculated members will be created. &amp;nbsp;Data mining options will be briefly evaluated.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;b&gt;Ad-hoc Analysis&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;The following ad-hoc slice, dice, and drillthrough will be performed against both platforms using the respective front-ends:&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;ol style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Investigation of Internet Sales by Product, Customer, and Sales Territory&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Investigation of Reseller Sales by Order Date, Employee, and Promotion&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;b&gt;Reporting&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Both platforms will be evaluated in producing the following reports using provided design tools and persisting them to their respective report servers for distribution:&lt;/span&gt;&lt;br /&gt;
&lt;ol style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Call Center Report by Date Shift&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Category of Items Purchased by Customer and Date.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;b&gt;Integration Engine&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Both integration engines will be evaluated by using the design tools provided and will perform the following:&lt;/span&gt;&lt;br /&gt;
&lt;ol style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Refresh DimProduct with new data (CSV source)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Refresh DimCustomer with new data (CSV source)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Rebuild the respective cubes and dimensions on the respective platform&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;b&gt;The Final Verdict&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;After going through the components of both BI platforms, I will outline the overall strengths and weaknesses of both, and make some educated recommendations on the optimal implementation of either platform. &amp;nbsp;The goal here isn&#39;t necessarily to determine which is truly &quot;better&quot; (as that can be a relative term, depending on the circumstances), but instead to gain a better understanding of what scenarios would call for what tool. &amp;nbsp;After all, that&#39;s the whole fun in technology!&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;b&gt;My Qualifications&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Finally, if you haven&#39;t done so already, you must be asking yourself what makes me qualified to make any sort of judgement call on BI platforms? &amp;nbsp;Professionally, business intelligence has been my specialty area for the past 6 years. &amp;nbsp;Having been involved as the lead developer for multiple BI platforms on projects of varying sizes in finance and investments, I have practical experience on BI implementations and the demands of its users.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;I do have a depth of knowledge with the Microsoft product stack, as that has also been my professional specialty for about 9 years now (specifically Visual Studio [C#, VB, ASP .Net], SQL Server, and the SQL Server BI stack).&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;I also have a depth of knowledge in the open source and Java, always experimenting with various flavors of Linux (from RedHat to Mandrake to Debian and now Ubuntu). &amp;nbsp;For fun, I like to develop Google Android apps. &amp;nbsp;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;This will be my first deep dive into the Pentaho platform. &amp;nbsp;I hope to gain and document insight into this well-regarded platform and see how it stacks up to the SQL Server BI stack that I know well. &amp;nbsp;Furthermore, I&#39;ve always had a soft spot for open source initiatives, but found many times that they&#39;re a little too rough around the edges for risk-averse enterprises. &amp;nbsp;Pentaho looks a bit more promising.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Pentaho claims to be able to lure people away from &quot;big commercial BI&quot; with its platform, and I want to put that to the test. &amp;nbsp;Is it really a BI platform I can confidently recommend as a viable, and even superior, option when scoping out projects? &amp;nbsp;Time will tell, and I&#39;m excited to find out! &amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Stay tuned for Part 1: Pentaho Analysis Services!&lt;/span&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geekstentialism.blogspot.com/feeds/8051710644821041955/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/2381314077784322587/8051710644821041955' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2381314077784322587/posts/default/8051710644821041955'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2381314077784322587/posts/default/8051710644821041955'/><link rel='alternate' type='text/html' href='http://geekstentialism.blogspot.com/2012/02/business-intelligence-shootout.html' title='Business Intelligence Shootout: Microsoft SQL Server 2008 BI vs. Pentaho BI Enterprise Edition (Introduction)'/><author><name>Eric Nograles</name><uri>http://www.blogger.com/profile/02198416401185614213</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJ6RXtY4XZkDuwsIOoO4bKx5gDQMLkcRQIQvtomOIeGbXKFJ1pTzAe7PS1HyPIdW4Rb2Pc5ZljcijJCLlORJpe1c99VfDOUTpn5A06e4WU0yTboyJiAC0eJ8PT0Itv1xc/s1600/*'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2381314077784322587.post-481678058169655453</id><published>2012-01-19T06:58:00.000-08:00</published><updated>2012-01-19T06:59:06.043-08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term=".net"/><category scheme="http://www.blogger.com/atom/ns#" term="csv"/><category scheme="http://www.blogger.com/atom/ns#" term="csv parsing"/><category scheme="http://www.blogger.com/atom/ns#" term="fast csv reader"/><category scheme="http://www.blogger.com/atom/ns#" term="memory efficient"/><title type='text'>Sebastien Lorien&#39;s Fast CSV Reader: Standing the Test of Time</title><content type='html'>&lt;div dir=&quot;ltr&quot; style=&quot;text-align: left;&quot; trbidi=&quot;on&quot;&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;A couple of years ago, when I was working on some data integration projects and didn&#39;t have the luxury of SSIS or Informatica, I had to write some custom .Net components to handle CSV sources flowing into OLEDB destinations. &amp;nbsp;Thinking about what libraries are available in standard .Net (2.0 at the time) -- String parsers, RegEx handlers, StreamReaders, etc. -- one would think it would relatively be a cinch. &amp;nbsp;However, I wanted to have some of the niceties afforded to ETL engines like SSIS and Informatica. &amp;nbsp;Specifically, fully qualified text fields, handling of escaped characters, custom delimeter characters, and missing field actions.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Instead of trying to reinvent the wheel, I scoured CodeProject for some inspiration as a starting point. &amp;nbsp;Never did I realize that an entire CSV parsing library was written so well, that I ended up using the entire project out of the box for my CSV handling needs. &amp;nbsp;This is the case with Sebastien Lorien&#39;s&amp;nbsp;&lt;a href=&quot;http://www.codeproject.com/KB/database/CsvReader.aspx&quot;&gt;Fast CSV Reader&lt;/a&gt;.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Sebastien did a tremendous job in parsing CSV&#39;s the way a good integration utility (such as SSIS or Informatica) would. &amp;nbsp;You name it, this library&#39;s got it: handling missing required fields, handling malformed CSV rows, field headers, the works. &amp;nbsp;I believe the only thing that it didn&#39;t do (at the time I used it) was identifying exactly which field was not in an expected data format. &amp;nbsp;That may have changed over the years, however, so I&#39;ll have to bring it into a test project and see what she can do now, about 3 years later.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Oh, and one other thing. &amp;nbsp;The Fast CSV Reader is incredibly memory efficient. &amp;nbsp;I can corroborate the numbers reported on the Code Project site for this code, she runs lean and mean. &amp;nbsp;I seem to recall running a relatively large CSV file (several hundred megabytes) using the reader, and it ran quickly and I didn&#39;t have any memory issues. &amp;nbsp;For that, if I ever meet Sebastien in real life, I would definitely give him a Geek High Five.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Anyway, it appears that this project is still actively supported by Sebastien, so if you are writing custom .Net utilities to handle CSV&#39;s (or any character delimited file), I&#39;d highly recommend either using Fast CSV Reader or using its code as a starting point for your implementation.&lt;/span&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geekstentialism.blogspot.com/feeds/481678058169655453/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/2381314077784322587/481678058169655453' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2381314077784322587/posts/default/481678058169655453'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2381314077784322587/posts/default/481678058169655453'/><link rel='alternate' type='text/html' href='http://geekstentialism.blogspot.com/2012/01/sebastien-loriens-fast-csv-reader.html' title='Sebastien Lorien&#39;s Fast CSV Reader: Standing the Test of Time'/><author><name>Eric Nograles</name><uri>http://www.blogger.com/profile/02198416401185614213</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJ6RXtY4XZkDuwsIOoO4bKx5gDQMLkcRQIQvtomOIeGbXKFJ1pTzAe7PS1HyPIdW4Rb2Pc5ZljcijJCLlORJpe1c99VfDOUTpn5A06e4WU0yTboyJiAC0eJ8PT0Itv1xc/s1600/*'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2381314077784322587.post-2010429278051004574</id><published>2012-01-14T18:37:00.000-08:00</published><updated>2012-01-14T18:37:12.139-08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="chrome extensions"/><category scheme="http://www.blogger.com/atom/ns#" term="google chrome"/><category scheme="http://www.blogger.com/atom/ns#" term="multiple processes"/><title type='text'>Google Chrome: What&#39;s With All those Processes?</title><content type='html'>&lt;div dir=&quot;ltr&quot; style=&quot;text-align: left;&quot; trbidi=&quot;on&quot;&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Full disclosure: I love Google Chrome. &amp;nbsp;Ever since its release, I have not used another browser, be it my old beloved Firefox, Opera, and Internet Explorer. &amp;nbsp;However, one thing always kind of piqued my curiosity about my fast, relatively lightweight browsing friend, Chrome: why the heck does it fire off x processes (10 on this particular instance) of Chrome.exe at launch?&lt;/span&gt;&lt;br /&gt;
&lt;div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;
&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right: auto; text-align: center;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjoui59Zz-P4a0SEfBGeUOtxNfb9PWavWOneZXZv-9rdDXay6mLMeyXolgYJTMIsCSBi0MWZPJ7HRocFwL5HleD-6hPnyQaOcYGnzUO57-OwCM2yxmlycqg15raSXYzYrqdFwiNMfS40SRS/s1600/Chrome+Processes.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;68&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjoui59Zz-P4a0SEfBGeUOtxNfb9PWavWOneZXZv-9rdDXay6mLMeyXolgYJTMIsCSBi0MWZPJ7HRocFwL5HleD-6hPnyQaOcYGnzUO57-OwCM2yxmlycqg15raSXYzYrqdFwiNMfS40SRS/s320/Chrome+Processes.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;Hmm...Google Chrome phone home while I&#39;m not looking, perhaps?&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Upon some further digging, you can check out what&#39;s going on behind the scenes with Chrome by clicking on Tools --&amp;gt; View background pages&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right: auto; text-align: center;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgrHCcUuvrj8dhifVMtvVxNQQ8BgjNZHKXSOe-nKex3yfDIJZnv_knqW9s2bXrDP9DLj4WluoeOeN5sMKb50rgGyadXFKH4vdwct5_JEFVs3MSbXiYScPnNAUOiFfSLPiKRQ3QFyVQaPdb7/s1600/Chome+View+Background+Pages.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;320&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgrHCcUuvrj8dhifVMtvVxNQQ8BgjNZHKXSOe-nKex3yfDIJZnv_knqW9s2bXrDP9DLj4WluoeOeN5sMKb50rgGyadXFKH4vdwct5_JEFVs3MSbXiYScPnNAUOiFfSLPiKRQ3QFyVQaPdb7/s320/Chome+View+Background+Pages.png&quot; width=&quot;221&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;&lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;And this little window pops up...&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhCfXS_EaQMci_OOHIudmU-ffmpJ7oIWQcm8FKU7XL5bZmJsAVmwyyl7b1RwS0sRxIJYm3Mg_WZu3DbMqIVklweqTSPg1su97QS-eepA57XyaeiXnui83Efj6QuQagaG6deikrDzUKosQ5d/s1600/Chrome+Background+Stuff.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;208&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhCfXS_EaQMci_OOHIudmU-ffmpJ7oIWQcm8FKU7XL5bZmJsAVmwyyl7b1RwS0sRxIJYm3Mg_WZu3DbMqIVklweqTSPg1su97QS-eepA57XyaeiXnui83Efj6QuQagaG6deikrDzUKosQ5d/s320/Chrome+Background+Stuff.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Ah-ha...there is the answer to our question. &amp;nbsp;In this example, I had 10 processes of Chrome.exe fire off...to correspond with my extensions AND the tabs I currently have open in Chrome.&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;So, in case you were ever nervous about those excess processes that Chrome spawns, not to worry, it looks like they&#39;re there only to help enhance your web browsing experience.&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;At least, that&#39;s what Skynet wants us to think. &amp;nbsp;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geekstentialism.blogspot.com/feeds/2010429278051004574/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/2381314077784322587/2010429278051004574' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2381314077784322587/posts/default/2010429278051004574'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2381314077784322587/posts/default/2010429278051004574'/><link rel='alternate' type='text/html' href='http://geekstentialism.blogspot.com/2012/01/google-chrome-whats-with-all-those.html' title='Google Chrome: What&#39;s With All those Processes?'/><author><name>Eric Nograles</name><uri>http://www.blogger.com/profile/02198416401185614213</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJ6RXtY4XZkDuwsIOoO4bKx5gDQMLkcRQIQvtomOIeGbXKFJ1pTzAe7PS1HyPIdW4Rb2Pc5ZljcijJCLlORJpe1c99VfDOUTpn5A06e4WU0yTboyJiAC0eJ8PT0Itv1xc/s1600/*'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjoui59Zz-P4a0SEfBGeUOtxNfb9PWavWOneZXZv-9rdDXay6mLMeyXolgYJTMIsCSBi0MWZPJ7HRocFwL5HleD-6hPnyQaOcYGnzUO57-OwCM2yxmlycqg15raSXYzYrqdFwiNMfS40SRS/s72-c/Chrome+Processes.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2381314077784322587.post-1159441914600317247</id><published>2012-01-08T16:14:00.000-08:00</published><updated>2012-01-08T16:18:53.322-08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="column headings"/><category scheme="http://www.blogger.com/atom/ns#" term="excel"/><category scheme="http://www.blogger.com/atom/ns#" term="page breaks"/><category scheme="http://www.blogger.com/atom/ns#" term="SQL Server 2008"/><category scheme="http://www.blogger.com/atom/ns#" term="sql server reporting services 2008"/><category scheme="http://www.blogger.com/atom/ns#" term="ssrs"/><title type='text'>Excel Page Breaks in SQL Server Reporting Services 2008 by Number of Rows and Preserving Column Headers</title><content type='html'>&lt;div dir=&quot;ltr&quot; style=&quot;text-align: left;&quot; trbidi=&quot;on&quot;&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;In your adventures or misadventures with SQL Server Reporting Services 2008 (SSRS), you may be asked to produce exceptionally large tabular reports to be exported to Excel either interactively or on a subscription. &amp;nbsp;While one would assume this to be a non-issue with SSRS, we then realize that SSRS 2008 unfortunately still exports to an Excel 2003 format, and thus is at the mercy of the dreaded 65536 rows per sheet limitation.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;In other words, if you produce a simple tabular report that outputs, say, 100,000 rows of data, and want to export it to Excel, you will get a pretty nasty message from SSRS resembling an unhandled ASP .Net exception, which informs you that Excel sheets (at least in the 2003 version) have a 65536 row limitation. &amp;nbsp;Drat!&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;While the solution would be to have SSRS just export to Excel 2007 or 2010 formats and dump the results to one sheet like a proper modern application, Microsoft felt the need to still tightly couple the output to the &quot;lowest common denominator&quot; of Excel 2003, which, to this day, is probably still the most widely used version of Excel (and MS Office).&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;So, that kind of leaves us developers out on an island initially. &amp;nbsp;Of course, the next best option is to have SSRS create Page Breaks every 65536 rows, but how are we supposed to do that? &amp;nbsp;Furthermore, how do we have the column headers for the tabular report repeat on each page? &amp;nbsp;Through some digging in various forums and MSDN, the solution to this little conundrum is documented below.&lt;/span&gt;&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: left;&quot;&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;ul style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Database Source: AdventureWorks2008 Database&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Database Table: HumanResources.Employees&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Objectives:&lt;/span&gt;&lt;/li&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Produce an Excel output from SSRS 2008 which produces a new sheet every 100 rows&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Ensure that the column headers repeat on the new pages&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Freeze the column headers when browsing the report directly on the web browser&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/ul&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;So, let&#39;s get started by creating a new report which will point to the HumanResources.Employees table of the AdventureWorks2008 database, and throw all of the data elements to the Details section of the report.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right: auto; text-align: center;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEixhwJ77P4w3I4Z_yxTKjn1NKOR8UW6rhgeyl5Cog05XWeH8BY45gJ1ssY9DJCNvL979a3RS0dFxrN8K9-mo6_sKWYP4Eoq3sGmWyQ9l_RnpDHqwELuO-rKnEXvYxbDrIfvk6nm32T8n0qi/s1600/SSRS+2008+-+Report+Query.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;296&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEixhwJ77P4w3I4Z_yxTKjn1NKOR8UW6rhgeyl5Cog05XWeH8BY45gJ1ssY9DJCNvL979a3RS0dFxrN8K9-mo6_sKWYP4Eoq3sGmWyQ9l_RnpDHqwELuO-rKnEXvYxbDrIfvk6nm32T8n0qi/s320/SSRS+2008+-+Report+Query.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;Step 1: Standard SQL query to obtain data&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: left;&quot;&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right: auto; text-align: center;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh9XqJq5rxvVfZjkZhLI5lczdaMZEYGyOeEgbZhFXprk_dKOqstXwpaa7Dk9CCfJKGu0tAuRnjs1LabchtgK5TpXDpXAfQUT9D4cb-rFz5tSrwxVTRSSEVmfxqV2986mI-jSlAjdJHG0dhN/s1600/SSRS+2008+-+Columns+on+Details.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;295&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh9XqJq5rxvVfZjkZhLI5lczdaMZEYGyOeEgbZhFXprk_dKOqstXwpaa7Dk9CCfJKGu0tAuRnjs1LabchtgK5TpXDpXAfQUT9D4cb-rFz5tSrwxVTRSSEVmfxqV2986mI-jSlAjdJHG0dhN/s320/SSRS+2008+-+Columns+on+Details.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;Step 2: Move columns to the Details&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right: auto; text-align: center;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjhqCCm3RA6L2qiNLUOweewP-TcI2uDizCbm3L2J3Qb5G3AP7ngfiheh74Ocdv_ayXgFQiF80rw3LJspjdqo3jFUPVByFMwQ6Dmt2MSHpwp-kHrRJjRrx0vzr4N-t9PNoRdfEzW3MQivakA/s1600/SSRS+2008+-+Report+Design.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;188&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjhqCCm3RA6L2qiNLUOweewP-TcI2uDizCbm3L2J3Qb5G3AP7ngfiheh74Ocdv_ayXgFQiF80rw3LJspjdqo3jFUPVByFMwQ6Dmt2MSHpwp-kHrRJjRrx0vzr4N-t9PNoRdfEzW3MQivakA/s640/SSRS+2008+-+Report+Design.png&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;Step 3: Easy peasy lemon squeezy&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: left;&quot;&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: left;&quot;&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;u&gt;Page Breaks Every Specified Number of Rows&lt;/u&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: left;&quot;&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: left;&quot;&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;So, now we have our standard tabular report. &amp;nbsp;Let&#39;s fulfill our first objective, having a new sheet produced every 100 rows. &amp;nbsp;&lt;b&gt;This is accomplished by creating a new parent group to the details and specifying a Group Expression for this new parent group&lt;/b&gt;.&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: left;&quot;&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;
&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right: auto; text-align: center;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg0xORIDqV0p8ri0wm9RqjGHGpVsPeHq4-0CiMk7MWUE0Q6l6VE860Vw28LCUABnB7QQb8lpeWmYLLrVkIC9Vh7sC1ax5FMVU3Xt7y4Sa0WoLDswvuP6IQiJI4ceoim9-Nby0o2UwiJQ_O_/s1600/SSRS+2008+-+Add+Parent+Group+for+Pagebreaks.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;204&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg0xORIDqV0p8ri0wm9RqjGHGpVsPeHq4-0CiMk7MWUE0Q6l6VE860Vw28LCUABnB7QQb8lpeWmYLLrVkIC9Vh7sC1ax5FMVU3Xt7y4Sa0WoLDswvuP6IQiJI4ceoim9-Nby0o2UwiJQ_O_/s320/SSRS+2008+-+Add+Parent+Group+for+Pagebreaks.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;Step 1: Meet the Parent&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right: auto; text-align: center;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjzNHhdYS7G8TgSjHvc92Kpi2fsyLVRubuZvYPi7y1PU8ejGiDqL6N9rdx5D_iTX5kxhf9etgoTNLJiiIp7E9xkVUS-VecN7Oa1HRkWet6EkvS12nepPVtYth-lO64nRmLqvUtF_Y3UNlg0/s1600/SSRS+2008+-+Pagebreak+Group+Expression.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;288&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjzNHhdYS7G8TgSjHvc92Kpi2fsyLVRubuZvYPi7y1PU8ejGiDqL6N9rdx5D_iTX5kxhf9etgoTNLJiiIp7E9xkVUS-VecN7Oa1HRkWet6EkvS12nepPVtYth-lO64nRmLqvUtF_Y3UNlg0/s320/SSRS+2008+-+Pagebreak+Group+Expression.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;Step 2: Use the following syntax to group your details by number of rows -- &lt;i style=&quot;font-weight: bold;&quot;&gt;&lt;br /&gt;=Ceiling(RowNumber(Nothing)/[number of rows]). &amp;nbsp;&lt;/i&gt;Example above breaks the groups up by 100 rows.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: left;&quot;&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Hit OK twice and you will notice a new group (called Group1) created as the parent of your details. &amp;nbsp;We need to further modify this group to create our Page Breaks, so go ahead and double click on that newly created parent group to bring up the Group Properties window. &amp;nbsp;&lt;b&gt;We will now instruct this report definition to break at the end of the group, as well as remove unnecessary sorting on the group and the new &quot;Group1&quot; column created on our report design&lt;/b&gt;.&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: left;&quot;&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;
&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right: auto; text-align: center;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiBZig6h27V6ohjXvKeGM8-1WLqm1GKWfIzkMOMiReJl1bKHYj3INV7_YJ-c2UnaTZXiY77iTq5pUrjldhqHRUfvOInMa8hdaoWNFBru114CXzaYn8Ei2r1Q4D_scU88x1G-PEewJ7DDQHO/s1600/SSRS+2008+-+Pagebreak+Group+Page+Break+Options.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;259&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiBZig6h27V6ohjXvKeGM8-1WLqm1GKWfIzkMOMiReJl1bKHYj3INV7_YJ-c2UnaTZXiY77iTq5pUrjldhqHRUfvOInMa8hdaoWNFBru114CXzaYn8Ei2r1Q4D_scU88x1G-PEewJ7DDQHO/s320/SSRS+2008+-+Pagebreak+Group+Page+Break+Options.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;Step 3: Enable page breaks&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right: auto; text-align: center;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEij3pvFqTjIqxfZvzh3bQOC1zlq-IvCvvmIoiEwO9qtAuCCE6Ad9omd7L0444FsQOBBTlZ80ZD0k-5lvqY8e1_ef4to6PnIt_vzhywMW7hkAniiatMr-O22oy186531cvc95CHfWQ_TA09s/s1600/SSRS+2008+-+Pagebreak+Group+Remove+Sorting.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;261&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEij3pvFqTjIqxfZvzh3bQOC1zlq-IvCvvmIoiEwO9qtAuCCE6Ad9omd7L0444FsQOBBTlZ80ZD0k-5lvqY8e1_ef4to6PnIt_vzhywMW7hkAniiatMr-O22oy186531cvc95CHfWQ_TA09s/s320/SSRS+2008+-+Pagebreak+Group+Remove+Sorting.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;Step 4: Click on the &quot;Sort by&quot; and then click the Delete button. &amp;nbsp;Press OK twice to save Group Properties.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjjU1tJmJsSoB_OxXpmxxSaYHmsfxzlFAmZbnYr4GTzLQmPCTwEPdAkhZWgYxe6MKCs2uqGa_QazkUFrye49rtevrxSykZ11HgojWNjQRsO5Xel2i8qlY3vz7_wzH86aek1KKAW2PTzLGIa/s1600/SSRS+2008+-+Pagebreak+Group+Delete+Column.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjjU1tJmJsSoB_OxXpmxxSaYHmsfxzlFAmZbnYr4GTzLQmPCTwEPdAkhZWgYxe6MKCs2uqGa_QazkUFrye49rtevrxSykZ11HgojWNjQRsO5Xel2i8qlY3vz7_wzH86aek1KKAW2PTzLGIa/s1600/SSRS+2008+-+Pagebreak+Group+Delete+Column.png&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right: auto; text-align: center;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhnZfX0EtmDxnxpOIVi9XUcHKNwRMYOoRQZzrI8uAwNMu9nJsJXdypNKzHsUOxSrjq7Ce27-V0iWuQR_7KzTIkMJZClCR3-hjUN1dUvnaSMPoR6iXmJR0Fdcbc8ITlOuqizKGClufEF9Q23/s1600/SSRS+2008+-+Pagebreak+Group+Delete+Column+Options.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;111&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhnZfX0EtmDxnxpOIVi9XUcHKNwRMYOoRQZzrI8uAwNMu9nJsJXdypNKzHsUOxSrjq7Ce27-V0iWuQR_7KzTIkMJZClCR3-hjUN1dUvnaSMPoR6iXmJR0Fdcbc8ITlOuqizKGClufEF9Q23/s320/SSRS+2008+-+Pagebreak+Group+Delete+Column+Options.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;Step 5: Delete the new &quot;Group1&quot; column that&#39;s been thrown onto the design. &amp;nbsp;Delete the columns ONLY.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: left;&quot;&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;So, now we have a report definition that page breaks every 100 rows. &amp;nbsp;Brilliant! &amp;nbsp;We&#39;re done, right? &amp;nbsp;Well, not necessarily. &amp;nbsp;Suppose our client wants to see the column headers on each new page, as well as have the column headers freeze themselves when they browse the report on their browser interactively...&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: left;&quot;&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: left;&quot;&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: left;&quot;&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;u&gt;Repeating Column Headers in Excel and Freezing Interactive Column Headers&lt;/u&gt;&lt;/span&gt;&lt;/div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;The first thing we need to do is to go into Advanced Mode for our Row Groups. &amp;nbsp;This is accomplished by clicking the small black down arrow to the far right of the &quot;Column Groups&quot; label.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right: auto; text-align: center;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi6GfOaH6vLVI2WPgxEkhF1RQfbDqlJEt7maBIMsXQU1bC0kp8HQoDdHmt0TGZFCGeJnw-IlZ04lkejRsSFT0lAkGhh7dk9dYf7ntw4YKi-2saSkEZDEPJ9nbmtFb6ndE20alymWXUHtWxw/s1600/SSRS+2008+-+Advanced+Mode.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;305&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi6GfOaH6vLVI2WPgxEkhF1RQfbDqlJEt7maBIMsXQU1bC0kp8HQoDdHmt0TGZFCGeJnw-IlZ04lkejRsSFT0lAkGhh7dk9dYf7ntw4YKi-2saSkEZDEPJ9nbmtFb6ndE20alymWXUHtWxw/s640/SSRS+2008+-+Advanced+Mode.png&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;Step 1: Click on Advanced Mode&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Now that we&#39;ve got Advanced Mode up, click on the first item on the Row Groups labelled &quot;(Static)&quot; and bring up the Properties window if it&#39;s not already on your toolbox.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right: auto; text-align: center;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgD2C_iyiAep77Bwer_JIY3mkAQBhxVT9Z47tqByvBUBJxznHNevgW5lMr-p_TZLNR3xzCsUrsjSSVjwbKed5T0yxid98Y2ObTbeCj4bmromHo7BsL-3occlAe6NM_4oNBrtpDcMzu2MS9z/s1600/SSRS+2008+-+Static+Row+Header+Setup.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;328&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgD2C_iyiAep77Bwer_JIY3mkAQBhxVT9Z47tqByvBUBJxznHNevgW5lMr-p_TZLNR3xzCsUrsjSSVjwbKed5T0yxid98Y2ObTbeCj4bmromHo7BsL-3occlAe6NM_4oNBrtpDcMzu2MS9z/s640/SSRS+2008+-+Static+Row+Header+Setup.png&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;Step 2: Set FixedData to &lt;b&gt;True&lt;/b&gt; (freezes column headings in interactive mode), then set KeepsTogether and RepeatOnNewPage to &lt;b&gt;True&lt;/b&gt; and KeepWithGroup to &lt;b&gt;After&lt;/b&gt; (publishes column heading on each page when output to Excel).&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Congratulations, you have now created an SSRS tabular report that breaks every 100 rows and reproduces the column header on each new sheet, as well as freezes the column headers when scrolling down interactively!&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgghVSEPjK6i4M8FxHy2HENZnY5fgZoahHJrYr8UapgdY0WsMgxQ9z8GnNAwbLhoZ1WyDJWasb-esFHQijnHYSyq_LF-l30cUjhu58VVoOpVkICjAlkwxl9KUPDwep8ndpgI-UefgmNpo4b/s1600/SSRS+2008+-+Employees+Report+Page+1.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;379&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgghVSEPjK6i4M8FxHy2HENZnY5fgZoahHJrYr8UapgdY0WsMgxQ9z8GnNAwbLhoZ1WyDJWasb-esFHQijnHYSyq_LF-l30cUjhu58VVoOpVkICjAlkwxl9KUPDwep8ndpgI-UefgmNpo4b/s640/SSRS+2008+-+Employees+Report+Page+1.png&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiFlEaAcG3huHNDDIoYN3D_y55j6QR6ite2XJLfJRo3t-nrccKrjaOnAyIk2FClU-mTj9lE_srr98qNi7msHfABtW0ADQCCtFCEoZgb-OW0dbn0AOb0X3i01BJJshnll99GW8IKCeFPIztn/s1600/SSRS+2008+-+Employees+Report+Page+2.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;386&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiFlEaAcG3huHNDDIoYN3D_y55j6QR6ite2XJLfJRo3t-nrccKrjaOnAyIk2FClU-mTj9lE_srr98qNi7msHfABtW0ADQCCtFCEoZgb-OW0dbn0AOb0X3i01BJJshnll99GW8IKCeFPIztn/s640/SSRS+2008+-+Employees+Report+Page+2.png&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;u&gt;In Summary...&lt;/u&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Do I wish that there was more apparent solution to this seemingly trivial issue? &amp;nbsp;I absolutely do. &amp;nbsp;I&#39;m not sure why this design limitation was overlooked in the development process of SSRS 2008. &amp;nbsp;It would seem to me that the proper solution would be that the SSRS Excel output algorithm would be loosely coupled from SSRS itself and be dependent upon the version of Excel one installs on the server on which SSRS is running?&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Anyway, whatever the reason, at least there is a solution out there, albeit a bit of a roundabout one.&lt;/span&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geekstentialism.blogspot.com/feeds/1159441914600317247/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/2381314077784322587/1159441914600317247' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2381314077784322587/posts/default/1159441914600317247'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2381314077784322587/posts/default/1159441914600317247'/><link rel='alternate' type='text/html' href='http://geekstentialism.blogspot.com/2012/01/excel-page-breaks-in-sql-server.html' title='Excel Page Breaks in SQL Server Reporting Services 2008 by Number of Rows and Preserving Column Headers'/><author><name>Eric Nograles</name><uri>http://www.blogger.com/profile/02198416401185614213</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJ6RXtY4XZkDuwsIOoO4bKx5gDQMLkcRQIQvtomOIeGbXKFJ1pTzAe7PS1HyPIdW4Rb2Pc5ZljcijJCLlORJpe1c99VfDOUTpn5A06e4WU0yTboyJiAC0eJ8PT0Itv1xc/s1600/*'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEixhwJ77P4w3I4Z_yxTKjn1NKOR8UW6rhgeyl5Cog05XWeH8BY45gJ1ssY9DJCNvL979a3RS0dFxrN8K9-mo6_sKWYP4Eoq3sGmWyQ9l_RnpDHqwELuO-rKnEXvYxbDrIfvk6nm32T8n0qi/s72-c/SSRS+2008+-+Report+Query.png" height="72" width="72"/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2381314077784322587.post-5164020866153072421</id><published>2011-12-29T08:16:00.000-08:00</published><updated>2011-12-29T08:16:46.037-08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="shrinking transaction log"/><category scheme="http://www.blogger.com/atom/ns#" term="SQL Server 2005"/><category scheme="http://www.blogger.com/atom/ns#" term="SQL Server 2008"/><category scheme="http://www.blogger.com/atom/ns#" term="transaction log"/><title type='text'>Shrinking Transaction Logs in SQL Server 2005/2008 Using T-SQL</title><content type='html'>&lt;div dir=&quot;ltr&quot; style=&quot;text-align: left;&quot; trbidi=&quot;on&quot;&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;A tripping point of database development in SQL Server 2005 and 2008 tends to be the management of the transaction logs. &amp;nbsp;When you get into a fairly complicated transactional database design, developers don&#39;t often worry too much about the transaction logs -- that is, until, the number of unused pages grows exponentially to the point where the transaction log hits its disk space limitation (if it has one), or worse yet, peg most of the physical disk space available, not to mention give your database an enormous performance hindrance. &amp;nbsp;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;These tasks ought to be taken care of by a DBA (routine transaction log backups/truncations, etc), but developers may not have the luxury of DBA assistance in their test environments.&lt;/span&gt;&lt;br /&gt;
&lt;div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;You can, of course, use SQL Server Management Studio, to assist with shrinking the transaction logs to free up disk space, but there may be times when you need to shrink the transaction as part of your T-SQL stored procedure processes. &amp;nbsp;Sample code follows:&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;
&lt;/span&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;pre style=&quot;background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;&quot;&gt;&lt;code&gt;CREATE PROCEDURE [dbo].[usp_ShrinkTransactionLog]
as

Checkpoint
DBCC OPENTRAN(SampleDatabase)

DECLARE @wk_fileid INT
SELECT @wk_fileid = fileid  
FROM sysfiles
WHERE [name] = &#39;SampleDatabase_log&#39;

DBCC SHRINKFILE (@wk_fileid)

GO
&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;
The stored procedure above calls DBCC OPENTRAN to give you information on any open transactions, and is optional. &amp;nbsp;It&#39;s more of a nice-to-have to see what&#39;s still ongoing in the database. &amp;nbsp;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;The real meat of the stored procedure is grabbing the internal FileId of your database&#39;s log and then issuing a DBCC SHRINKFILE against it, essentially performing the same task as the Tasks --&amp;gt; Shrink --&amp;gt; Files on SSMS -- the code above does not specify a target size, so it will try to shrink to your transaction log&#39;s configured default size.&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Note that, if your database is on a Full Recovery model, this sproc may not free up all the disk space that you&#39;d want (with DBCC SHRINKFILE potentially coming back with a message saying it couldn&#39;t free up all the space requested). &amp;nbsp;This is because the Full Recovery model, by design, requires that you have regular backups of your logs. &amp;nbsp;Thus, it will keep all transaction logs in the virtual logs until they are backed up. &amp;nbsp;The act of backing up your transaction log will truncate it and free up even more space. &amp;nbsp;So, we can modify the above stored procedure to take this into account:&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;pre style=&quot;background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;&quot;&gt;&lt;code&gt;CREATE procedure [dbo].[usp_BackupAndShrinkTransactionLog]
as

Checkpoint
DBCC opentran(SampleDatabase)
BACKUP LOG SampleDatabase

DECLARE @wk_fileid INT
SELECT @wk_fileid = fileid  
FROM sysfiles
WHERE [name] = &#39;SampleDatabase_log&#39;

DBCC SHRINKFILE (@wk_fileid)

GO
&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Note that the stored procedure code above assumes that you or your DBA have configured a default Backup Destination for your database. &amp;nbsp;You can modify the BACKUP LOG statement to dump the transaction log to a specific named Backup Device by adding &quot; TO SampleBackupDevice_Log1&quot; (&lt;b&gt;BACKUP LOG(Sample Database) TO SampleBackupDevice_Log1&lt;/b&gt;)&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Using either of these stored procedures as part of your T-SQL routines will help in keeping your transaction log sizes manageable. &amp;nbsp;Of course, do consult with your DBA&#39;s if you end up moving your test database to a production environment, as I&#39;m sure they will wonder why the log files backups happen more frequently than they designed. &amp;nbsp;:-)&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;
&lt;span style=&quot;font-family: Verdana, sans-serif;&quot;&gt;Additional info from MSDN: &lt;a href=&quot;http://msdn.microsoft.com/en-us/library/ms178037.aspx&quot;&gt;Shrinking the Transaction Log&lt;/a&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geekstentialism.blogspot.com/feeds/5164020866153072421/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/2381314077784322587/5164020866153072421' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2381314077784322587/posts/default/5164020866153072421'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2381314077784322587/posts/default/5164020866153072421'/><link rel='alternate' type='text/html' href='http://geekstentialism.blogspot.com/2011/12/shrinking-transaction-logs-in-sql.html' title='Shrinking Transaction Logs in SQL Server 2005/2008 Using T-SQL'/><author><name>Eric Nograles</name><uri>http://www.blogger.com/profile/02198416401185614213</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJ6RXtY4XZkDuwsIOoO4bKx5gDQMLkcRQIQvtomOIeGbXKFJ1pTzAe7PS1HyPIdW4Rb2Pc5ZljcijJCLlORJpe1c99VfDOUTpn5A06e4WU0yTboyJiAC0eJ8PT0Itv1xc/s1600/*'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2381314077784322587.post-6642411893001563159</id><published>2011-09-22T06:36:00.000-07:00</published><updated>2011-09-22T06:55:04.620-07:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="keyboard and mouse"/><category scheme="http://www.blogger.com/atom/ns#" term="split tunnel"/><category scheme="http://www.blogger.com/atom/ns#" term="synergy"/><category scheme="http://www.blogger.com/atom/ns#" term="vpn"/><title type='text'>Using Synergy Over a Non-Split Tunnel VPN</title><content type='html'>As with any good geek, I have multiple computers running at home.  Going by the &quot;DRY&quot; (Don&#39;t Repeat Yourself) principle of software development, I preferred not to use multiple sets of keyboards and mice to control these machines.  Enter &lt;a href=&quot;http://synergy-foss.org/&quot;&gt;Synergy&lt;/a&gt;.  &lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Synergy is a clever piece of open source software.  It uses the basic client-server paradigm to allow you to share one computer&#39;s keyboard and mouse across multiple computers over the network.  The idea here is that the &quot;server&quot; computer has the keyboard and mouse physically connected to it, and the &quot;client&quot; machines simply connect to the &quot;server&quot; to get access to its keyboard and mouse.  Simple, yet elegant.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I wanted to take this concept a step further.  For my work machine, we have a non-split tunnel VPN.  In lay terms, this means that, when I initiate a VPN connection to my work network, I lose all local connectivity on my laptop.  In other words, my work laptop is no longer considered &quot;local&quot; to the other computers on my LAN.  This is a bummer, because now I WOULDN&#39;T be able to share my one set of keyboard/mouse between my personal laptop and my work laptop.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Through some port forwarding trickery, I was able to get Synergy to run on my personal laptop as well as on my work laptop whilst I was on VPN.  How did I achieve this? &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;Establish a port forwarding rule from my router to my local machine for HTTP (which is TCP Port 80).  In other words, if a machine from outside my network browses to the WAN address of my router from a web browser, it will redirect that traffic to my local machine.&lt;/li&gt;&lt;li&gt;Configure the Synergy &quot;server&quot; on my personal machine to run on Port 80.&lt;/li&gt;&lt;li&gt;(Optional) If you have IIS running, set your Default Website to run on another port (say, 81) or just stop it outright.&lt;/li&gt;&lt;li&gt;On the client machine (my work laptop while on VPN), the host name is the WAN address of my router.  Go to Advanced Options and set the port to 80.&lt;/li&gt;&lt;li&gt;Start the Synergy server on my personal laptop.  Start the Synergy client on my work laptop while on VPN.  Presto.&lt;/li&gt;&lt;/ol&gt;So, let me explain my approach above.  By default, Synergy runs on TCP port 24800, which is all fine and good for my local network (I can do whatever I please with regards to my router firewall, port forwarding, etc).  However, that is not kosher for my work&#39;s firewall.  In fact, my work&#39;s firewall blocks all outgoing traffic to &quot;non-common&quot; ports...we&#39;re extra stingy at my work, so the only &quot;common&quot; port defined is HTTP (port 80), since, well, that&#39;s kind of the backbone of the Internet, and they don&#39;t want to block out all Internet traffic.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;TCP Port 80 is the only non-blocked TCP port I could use to connect Synergy from my work network (via non-split tunnel VPN) to my personal network, hence the setup above.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Of course, this little setup only works if it&#39;s not vital for you to actually publish web content on Port 80 for your local network...it personally isn&#39;t for me (that&#39;s what my web hosts are for!).  If your work network&#39;s firewall rules are less stingy than mine, you can of course apply the same approach to any TCP port that isn&#39;t blocked.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Now, my only concern is that they don&#39;t outright block my local network&#39;s IP.  I haven&#39;t WireSharked Synergy so I don&#39;t know how verbose the language is when publishing out the X and Y coordinates of your mouse, as well as action buttons from the mouse or keyboard (I can&#39;t imagine it to be TOO verbose), so hopefully it will not generate an exorbitant amount of traffic to warrant blocking. &lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geekstentialism.blogspot.com/feeds/6642411893001563159/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/2381314077784322587/6642411893001563159' title='28 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2381314077784322587/posts/default/6642411893001563159'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2381314077784322587/posts/default/6642411893001563159'/><link rel='alternate' type='text/html' href='http://geekstentialism.blogspot.com/2011/09/using-synergy-over-non-split-tunnel-vpn.html' title='Using Synergy Over a Non-Split Tunnel VPN'/><author><name>Eric Nograles</name><uri>http://www.blogger.com/profile/02198416401185614213</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJ6RXtY4XZkDuwsIOoO4bKx5gDQMLkcRQIQvtomOIeGbXKFJ1pTzAe7PS1HyPIdW4Rb2Pc5ZljcijJCLlORJpe1c99VfDOUTpn5A06e4WU0yTboyJiAC0eJ8PT0Itv1xc/s1600/*'/></author><thr:total>28</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2381314077784322587.post-8884507727528154634</id><published>2011-03-18T15:10:00.000-07:00</published><updated>2011-03-22T06:32:20.387-07:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="65536 row limitation"/><category scheme="http://www.blogger.com/atom/ns#" term="database results"/><category scheme="http://www.blogger.com/atom/ns#" term="excel 2003"/><category scheme="http://www.blogger.com/atom/ns#" term="excel interop"/><category scheme="http://www.blogger.com/atom/ns#" term="vb .net"/><title type='text'>Breaking Down Database Query Results in Chunks of 65536 Rows for Excel 2003 Using Office Interop and VB .Net (Memory Efficient Edition)</title><content type='html'>So, my prior post had a solution for breaking down database query results from Excel into Worksheets of 65536 rows each.  As previously mentioned in the post, it is a memory hog, and probably will blow through your assembly&#39;s allocation.  I came up with a more memory-friendly solution to this issue.  In essence, in this approach, I swap out memory processing for I/O processing. &lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;This solution takes the following approach:&lt;/div&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;Dump out contents of database query to Excel 2003 XML format using a SqlDataReader, broken by x rows (65536 rows in this case, in the spirit of Excel 2003&#39;s limitations)&lt;/li&gt;&lt;li&gt;Open said Excel 2003 XML file using Excel Interop&lt;/li&gt;&lt;li&gt;Do any post-processing formatting and niceties to your spreadsheet&lt;/li&gt;&lt;li&gt;Save the file as a normal Excel Workbook&lt;/li&gt;&lt;li&gt;Delete the source XML file (which does indeed get gigantic)&lt;/li&gt;&lt;/ol&gt;&lt;div&gt;The result is a process which consumes, at most, 40-50 MB in memory (as opposed to the hundreds of MB in the prior approach) with performance close to the prior approach&#39;s in-memory + Excel Interop approach.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;Code follows.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;&lt;u&gt;Excel XML Header (as referenced in the code below)&lt;/u&gt;&lt;/b&gt;&lt;/div&gt;&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&amp;lt;?xml version=&quot;1.0&quot;?&amp;gt;&lt;br /&gt;&amp;lt;?mso-application progid=&quot;Excel.Sheet&quot;?&amp;gt;&lt;br /&gt;&amp;lt;Workbook xmlns=&quot;urn:schemas-microsoft-com:office:spreadsheet&quot; xmlns:o=&quot;urn:schemas-microsoft-com:office:office&quot; xmlns:x=&quot;urn:schemas-microsoft-com:office:excel&quot; xmlns:ss=&quot;urn:schemas-microsoft-com:office:spreadsheet&quot; xmlns:html=&quot;http://www.w3.org/TR/REC-html40&quot;&amp;gt;&lt;br /&gt;&amp;lt;DocumentProperties xmlns=&quot;urn:schemas-microsoft-com:office:office&quot;&amp;gt;&lt;br /&gt;&amp;lt;Author&amp;gt;enograles&amp;lt;/Author&amp;gt;&lt;br /&gt;&amp;lt;LastAuthor&amp;gt;enograles&amp;lt;/LastAuthor&amp;gt;&lt;br /&gt;&amp;lt;Created&amp;gt;2011-03-18T16:24:37Z&amp;lt;/Created&amp;gt;&lt;br /&gt;&amp;lt;Company&amp;gt;&amp;lt;/Company&amp;gt;&lt;br /&gt;&amp;lt;Version&amp;gt;12.00&amp;lt;/Version&amp;gt;&lt;br /&gt;&amp;lt;/DocumentProperties&amp;gt;&lt;br /&gt;&amp;lt;ExcelWorkbook xmlns=&quot;urn:schemas-microsoft-com:office:excel&quot;&amp;gt;&lt;br /&gt;&amp;lt;WindowHeight&amp;gt;11895&amp;lt;/WindowHeight&amp;gt;&lt;br /&gt;&amp;lt;WindowWidth&amp;gt;19020&amp;lt;/WindowWidth&amp;gt;&lt;br /&gt;&amp;lt;WindowTopX&amp;gt;120&amp;lt;/WindowTopX&amp;gt;&lt;br /&gt;&amp;lt;WindowTopY&amp;gt;105&amp;lt;/WindowTopY&amp;gt;&lt;br /&gt;&amp;lt;ProtectStructure&amp;gt;False&amp;lt;/ProtectStructure&amp;gt;&lt;br /&gt;&amp;lt;ProtectWindows&amp;gt;False&amp;lt;/ProtectWindows&amp;gt;&lt;br /&gt;&amp;lt;/ExcelWorkbook&amp;gt;&lt;br /&gt;&amp;lt;Styles&amp;gt;&lt;br /&gt;&amp;lt;Style ss:ID=&quot;sDate&quot;&amp;gt;&lt;br /&gt;&amp;lt;NumberFormat ss:Format=&quot;Short Date&quot;/&amp;gt;&lt;br /&gt;&amp;lt;/Style&amp;gt;&lt;br /&gt;&amp;lt;/Styles&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;&lt;u&gt;Implementation Code&lt;/u&gt;&lt;/b&gt;&lt;/div&gt;&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;Public Shared Function ExportToExcelXML(ByVal conn As SqlConnection, ByVal sql As String, _&lt;br /&gt;                                     Optional ByVal path As String = Nothing) As String&lt;br /&gt;&lt;br /&gt;    Dim excel As Application&lt;br /&gt;&lt;br /&gt;    Try&lt;br /&gt;        Dim destination As System.IO.DirectoryInfo&lt;br /&gt;        Dim workbookFullPathSource As String&lt;br /&gt;        Dim dtResult As New System.Data.DataTable&lt;br /&gt;        Dim textWriter As System.IO.TextWriter&lt;br /&gt;        Dim pageCount As Integer = 1&lt;br /&gt;        Dim rowCount As Integer = 0&lt;br /&gt;        Dim worksheetWritten As Boolean&lt;br /&gt;&lt;br /&gt;        &#39; First grab the result set&lt;br /&gt;        Dim cmdResult As New SqlCommand(sql, conn)&lt;br /&gt;        Dim rdr As SqlDataReader = cmdResult.ExecuteReader&lt;br /&gt;&lt;br /&gt;        &#39; Generate a new Guid for the Workbook name.&lt;br /&gt;        Dim workbook_name As String = System.Guid.NewGuid().ToString()&lt;br /&gt;&lt;br /&gt;        &#39; First validate the destination&lt;br /&gt;        If String.IsNullOrEmpty(path) = False Then&lt;br /&gt;            If System.IO.Directory.Exists(path) Then&lt;br /&gt;                destination = New System.IO.DirectoryInfo(path)&lt;br /&gt;            Else&lt;br /&gt;                &#39; Attempt to create it&lt;br /&gt;                destination = System.IO.Directory.CreateDirectory(path)&lt;br /&gt;            End If&lt;br /&gt;        Else&lt;br /&gt;            &#39; Just drop it in the executing assembly&#39;s root folder if no path is specified&lt;br /&gt;            destination = New System.IO.DirectoryInfo(System.Reflection.Assembly.GetExecutingAssembly.Location.Substring(0, System.Reflection.Assembly.GetExecutingAssembly.Location.LastIndexOf(&quot;\&quot;)))&lt;br /&gt;&lt;br /&gt;        End If&lt;br /&gt;&lt;br /&gt;        &#39; Construct the full path&lt;br /&gt;        workbookFullPathSource = destination.FullName &amp;amp; &quot;\&quot; &amp;amp; workbook_name &amp;amp; &quot;.xml&quot;&lt;br /&gt;&lt;br /&gt;        &#39; Instantiate the writer&lt;br /&gt;        textWriter = New System.IO.StreamWriter(workbookFullPathSource, False)&lt;br /&gt;&lt;br /&gt;        &#39; Write the header&lt;br /&gt;        WriteExcelXMLHeader(textWriter)&lt;br /&gt;&lt;br /&gt;        &#39; Iterate through the results&lt;br /&gt;        If rdr.HasRows Then&lt;br /&gt;            While rdr.Read&lt;br /&gt;&lt;br /&gt;                &#39; Iterate the sheet if we&#39;ve reached the limit&lt;br /&gt;                If rowCount = 65536 Then&lt;br /&gt;                    worksheetWritten = False&lt;br /&gt;                    pageCount += 1&lt;br /&gt;                    rowCount = 0 &#39; Reset the counter to 0&lt;br /&gt;                End If&lt;br /&gt;&lt;br /&gt;                &#39; Define a sheet and write the headers&lt;br /&gt;                If worksheetWritten = False Then&lt;br /&gt;                    If pageCount &amp;lt;&amp;gt; 1 Then&lt;br /&gt;                        textWriter.WriteLine(&quot;   &amp;lt;/Table&amp;gt;&quot;)&lt;br /&gt;                        textWriter.WriteLine(&quot; &amp;lt;/Worksheet&amp;gt;&quot;)&lt;br /&gt;                    End If&lt;br /&gt;&lt;br /&gt;                    textWriter.WriteLine(&quot; &amp;lt;Worksheet ss:Name=&quot;&quot;Page &quot; &amp;amp; pageCount &amp;amp; &quot;&quot;&quot;&amp;gt;&quot;)&lt;br /&gt;                    textWriter.WriteLine(&quot;   &amp;lt;Table ss:ExpandedColumnCount=&quot;&quot;&quot; &amp;amp; rdr.FieldCount &amp;amp; &quot;&quot;&quot;&amp;gt;&quot;)&lt;br /&gt;                    textWriter.WriteLine(&quot;     &amp;lt;Row ss:AutoFitHeight=&quot;&quot;0&quot;&quot;&amp;gt;&quot;)&lt;br /&gt;&lt;br /&gt;                    &#39; The headers&lt;br /&gt;                    For i As Integer = 0 To rdr.FieldCount - 1&lt;br /&gt;                        textWriter.WriteLine(&quot;      &amp;lt;Cell&amp;gt;&amp;lt;Data ss:Type=&quot;&quot;String&quot;&quot;&amp;gt;&quot; &amp;amp; rdr.GetName(i) &amp;amp; &quot;&amp;lt;/Data&amp;gt;&amp;lt;/Cell&amp;gt;&quot;)&lt;br /&gt;                    Next&lt;br /&gt;&lt;br /&gt;                    textWriter.WriteLine(&quot;     &amp;lt;/Row&amp;gt;&quot;)&lt;br /&gt;&lt;br /&gt;                    &#39; Yes, the header counts as a row&lt;br /&gt;                    worksheetWritten = True&lt;br /&gt;                    rowCount += 1&lt;br /&gt;                End If&lt;br /&gt;&lt;br /&gt;                &#39; Write the actual data&lt;br /&gt;                textWriter.WriteLine(&quot;     &amp;lt;Row ss:AutoFitHeight=&quot;&quot;0&quot;&quot;&amp;gt;&quot;)&lt;br /&gt;                For i As Integer = 0 To rdr.FieldCount - 1&lt;br /&gt;                    Dim dataType As String&lt;br /&gt;                    Dim dataContents As String = rdr.Item(i).ToString()&lt;br /&gt;&lt;br /&gt;                    If TypeOf (rdr.Item(i)) Is String Then&lt;br /&gt;                        dataType = &quot;String&quot;&lt;br /&gt;                    ElseIf TypeOf (rdr.Item(i)) Is DateTime Then&lt;br /&gt;                        dataType = &quot;String&quot;&lt;br /&gt;                        dataContents = CType(rdr.Item(i), DateTime).ToString(&quot;MM/dd/yyyy&quot;)&lt;br /&gt;                    ElseIf IsNumeric(rdr.Item(i)) Then&lt;br /&gt;                        dataType = &quot;Number&quot;&lt;br /&gt;                    Else&lt;br /&gt;                        dataType = &quot;String&quot;&lt;br /&gt;                    End If&lt;br /&gt;&lt;br /&gt;                    &#39; The data with the proper type&lt;br /&gt;                    textWriter.WriteLine(&quot;      &amp;lt;Cell&amp;gt;&amp;lt;Data ss:Type=&quot;&quot;&quot; &amp;amp; dataType &amp;amp; &quot;&quot;&quot;&amp;gt;&quot; &amp;amp; dataContents &amp;amp; &quot;&amp;lt;/Data&amp;gt;&amp;lt;/Cell&amp;gt;&quot;)&lt;br /&gt;                Next&lt;br /&gt;&lt;br /&gt;                &#39; Terminate the row&lt;br /&gt;                textWriter.WriteLine(&quot;     &amp;lt;/Row&amp;gt;&quot;)&lt;br /&gt;&lt;br /&gt;                &#39; Iterate row counter&lt;br /&gt;                rowCount += 1&lt;br /&gt;            End While&lt;br /&gt;        Else&lt;br /&gt;            textWriter.WriteLine(&quot; &amp;lt;Worksheet ss:Name=&quot;&quot;Page &quot; &amp;amp; pageCount &amp;amp; &quot;&quot;&quot;&amp;gt;&quot;)&lt;br /&gt;            textWriter.WriteLine(&quot;   &amp;lt;Table ss:ExpandedColumnCount=&quot;&quot;&quot; &amp;amp; rdr.FieldCount &amp;amp; &quot;&quot;&quot;&amp;gt;&quot;)&lt;br /&gt;            textWriter.WriteLine(&quot;     &amp;lt;Row ss:AutoFitHeight=&quot;&quot;0&quot;&quot;&amp;gt;&quot;)&lt;br /&gt;            textWriter.WriteLine(&quot;      &amp;lt;Cell&amp;gt;&amp;lt;Data ss:Type=&quot;&quot;String&quot;&quot;&amp;gt;No Results Found from Query:&quot; &amp;amp; cmdResult.CommandText &amp;amp; &quot;&amp;lt;/Data&amp;gt;&amp;lt;/Cell&amp;gt;&quot;)&lt;br /&gt;            textWriter.WriteLine(&quot;     &amp;lt;/Row&amp;gt;&quot;)&lt;br /&gt;        End If&lt;br /&gt;&lt;br /&gt;        &#39; Close out the writer&lt;br /&gt;        textWriter.WriteLine(&quot;   &amp;lt;/Table&amp;gt;&quot;)&lt;br /&gt;        textWriter.WriteLine(&quot; &amp;lt;/Worksheet&amp;gt;&quot;)&lt;br /&gt;        textWriter.WriteLine(&quot;&amp;lt;/Workbook&amp;gt;&quot;)&lt;br /&gt;        textWriter.Close()&lt;br /&gt;&lt;br /&gt;        &#39; Process in Excel Interop for formatting and saving to a proper Excel document&lt;br /&gt;        excel = New Application()&lt;br /&gt;        excel.DisplayAlerts = False&lt;br /&gt;&lt;br /&gt;        Dim wbSource As Workbook = excel.Workbooks.Open(workbookFullPathSource)&lt;br /&gt;&lt;br /&gt;        &#39; For all sheets, autofit columns, bold first rows, freeze panes on all worksheets&lt;br /&gt;        For i As Integer = 1 To wbSource.Worksheets.Count&lt;br /&gt;            Dim ws As Worksheet = CType(wbSource.Worksheets(i), Worksheet)&lt;br /&gt;            ws.Select()&lt;br /&gt;            CType(ws.Cells(1, 1), Range).EntireRow.Font.Bold = True&lt;br /&gt;            CType(ws.Cells(2, 1), Range).Select()&lt;br /&gt;            excel.ActiveWindow.FreezePanes = True&lt;br /&gt;            ws.Columns.AutoFit()&lt;br /&gt;        Next&lt;br /&gt;&lt;br /&gt;        &#39; Select the first Worksheet&lt;br /&gt;        CType(wbSource.Worksheets(1), Worksheet).Select()&lt;br /&gt;&lt;br /&gt;        &#39; Save as a workbook, exit out of Excel&lt;br /&gt;        wbSource.SaveAs(destination.FullName &amp;amp; &quot;\&quot; &amp;amp; workbook_name &amp;amp; &quot;.xls&quot;, FileFormat:=Microsoft.Office.Interop.Excel.XlFileFormat.xlWorkbookNormal)&lt;br /&gt;        wbSource.Close()&lt;br /&gt;        excel.Quit()&lt;br /&gt;&lt;br /&gt;        &#39; Delete the XML source&lt;br /&gt;        &#39;System.IO.File.Delete(workbookFullPathSource)&lt;br /&gt;&lt;br /&gt;        Return workbook_name &amp;amp; &quot;.xls&quot; &#39;workbook_full_path&lt;br /&gt;&lt;br /&gt;    Catch ex As Exception&lt;br /&gt;        Throw ex&lt;br /&gt;    Finally&lt;br /&gt;        If excel Is Nothing = False Then&lt;br /&gt;            excel.Quit()&lt;br /&gt;            System.Runtime.InteropServices.Marshal.ReleaseComObject(excel)&lt;br /&gt;            excel = Nothing&lt;br /&gt;            GC.Collect()&lt;br /&gt;        End If&lt;br /&gt;    End Try&lt;br /&gt;End Function&lt;br /&gt;&lt;br /&gt;&#39;&#39;&#39; &amp;lt;summary&amp;gt;&lt;br /&gt;&#39;&#39;&#39; This routine will write an Excel XML header before defining worksheets&lt;br /&gt;&#39;&#39;&#39; &amp;lt;/summary&amp;gt;&lt;br /&gt;&#39;&#39;&#39; &amp;lt;param name=&quot;textWriter&quot;&amp;gt;&amp;lt;/param&amp;gt;&lt;br /&gt;&#39;&#39;&#39; &amp;lt;remarks&amp;gt;&amp;lt;/remarks&amp;gt;&lt;br /&gt;Private Shared Sub WriteExcelXMLHeader(ByVal textWriter As System.IO.TextWriter)&lt;br /&gt;    textWriter.WriteLine(My.Resources.resMain.ExcelXMLHeader)&lt;br /&gt;End Sub&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geekstentialism.blogspot.com/feeds/8884507727528154634/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/2381314077784322587/8884507727528154634' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2381314077784322587/posts/default/8884507727528154634'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2381314077784322587/posts/default/8884507727528154634'/><link rel='alternate' type='text/html' href='http://geekstentialism.blogspot.com/2011/03/breaking-down-database-query-results-in.html' title='Breaking Down Database Query Results in Chunks of 65536 Rows for Excel 2003 Using Office Interop and VB .Net (Memory Efficient Edition)'/><author><name>Eric Nograles</name><uri>http://www.blogger.com/profile/02198416401185614213</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJ6RXtY4XZkDuwsIOoO4bKx5gDQMLkcRQIQvtomOIeGbXKFJ1pTzAe7PS1HyPIdW4Rb2Pc5ZljcijJCLlORJpe1c99VfDOUTpn5A06e4WU0yTboyJiAC0eJ8PT0Itv1xc/s1600/*'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2381314077784322587.post-5553144015698914299</id><published>2011-03-10T09:24:00.000-08:00</published><updated>2011-03-15T15:00:30.906-07:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="65536 limitation"/><category scheme="http://www.blogger.com/atom/ns#" term="excel 2003"/><category scheme="http://www.blogger.com/atom/ns#" term="query"/><category scheme="http://www.blogger.com/atom/ns#" term="sql"/><title type='text'>Breaking Down Database Query Results in Chunks of 65536 Rows for Excel 2003 Using Office Interop and VB .Net</title><content type='html'>March 10, 2011.  Still hard to believe the date.  Even harder to believe is how enterprises hold onto older versions of MS Office -- in our case, MS Office 2003.  Yes, Excel 2007/2010 and the Ribbon UI brings a bit of a learning curve, but having worked extensively with 2003 and 2007/2010, I can honestly say that I am faster and way more productive on the new Ribbon UI.  I have drank the Kool-Aid, I love the newer versions of Office.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The new version of Office give us some niceties not afforded to the 2003 version.  Some neat niceties (the Excel file being a glorified Zip file, with XML standards), and some practical niceties... in the case of the latter, the elimination of that pesky 65536 row limitation.  Alas, with my enterprise still on 2003, it is a limitation we developers need to live with.  We have various utilities in .Net that export results out to Excel spreadsheets, because let&#39;s face it, as an ad-hoc BI tool, Excel is pretty darn good at analyzing volumes of data.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;With that in mind, when you have a requirement for large amounts of data (think in the hundreds of thousands of rows) to be published out to an Excel 2003 Workbook, it poses a little bit of a challenge.  Specifically, how do you break up your results into Pages of Worksheets (65536 rows in each page) that the end-users can manipulate to their hearts&#39; contentment?  A couple of issues you run into: (a) How to break down your results in chunks of 65536 rows and (b) How to insert them blocks at a time without blowing through the Range.Value2&#39;s memory limitation (which, as far as I can tell, is not even published)?&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I came up with a routine that facilitates this functionality.  Just a fair word of warning, it&#39;s a bit of a hack, using standard ADO .Net objects and clever pasting of arrays to the Range.Value2 property of the Excel Interop Model while gratuitously calling the Garbage Collector so we don&#39;t blow through our memory allocation (because it uses up a bunch to begin with in persisting the results to memory as an ADO .Net DataTable).  &lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;div&gt;Not my neatest of routines, but it gets the job done.  An obvious improvement would be to break down the work in multiple subroutines.  Another improvement could be the utilization of memory -- I&#39;ve only tested this routine for the upper bounds of our data requirements (about 900,000 points of data?), so for larger data requirements, you may need to tune it a bit so it will not blow through the assembly&#39;s memory allocation.  Of course, if you come up with a clever way to clean the code up a bit, I&#39;d be happy to hear about it!&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Again, I reiterate my &lt;b&gt;&lt;i&gt;HACK ALERT&lt;/i&gt; &lt;/b&gt;statement:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;pre&gt;  &lt;br /&gt;   Public Shared Function ExportToExcel(ByVal conn As SqlConnection, ByVal sql As String, _&lt;br /&gt;                                         Optional ByVal path As String = Nothing) As String&lt;br /&gt;&lt;br /&gt;        Dim excel As Application&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;        Try&lt;br /&gt;            Dim destination As System.IO.DirectoryInfo&lt;br /&gt;            Dim workbook_full_path As String&lt;br /&gt;            Dim dtResult As New System.Data.DataTable&lt;br /&gt;            Dim lstSheets As New Generic.List(Of String)&lt;br /&gt;&lt;br /&gt;            &#39; First grab the result set&lt;br /&gt;            Dim cmdResult As New SqlCommand(sql, conn)&lt;br /&gt;            Dim rdr As SqlDataReader = cmdResult.ExecuteReader&lt;br /&gt;            dtResult.Load(rdr)&lt;br /&gt;&lt;br /&gt;            &#39; How many sheets will we need?&lt;br /&gt;            Dim sheet_count As Integer = SheetCount(dtResult.Rows.Count)&lt;br /&gt;&lt;br /&gt;            &#39; Generate a new Guid for the Workbook name.&lt;br /&gt;            Dim workbook_name As String = System.Guid.NewGuid().ToString()&lt;br /&gt;&lt;br /&gt;            &#39; First validate the destination&lt;br /&gt;            If String.IsNullOrEmpty(path) = False Then&lt;br /&gt;                If System.IO.Directory.Exists(path) Then&lt;br /&gt;                    destination = New System.IO.DirectoryInfo(path)&lt;br /&gt;                Else&lt;br /&gt;                    &#39; Attempt to create it&lt;br /&gt;                    destination = System.IO.Directory.CreateDirectory(path)&lt;br /&gt;                End If&lt;br /&gt;            Else&lt;br /&gt;                &#39; Just drop it in the executing assembly&#39;s root folder if no path is specified&lt;br /&gt;                destination = New System.IO.DirectoryInfo(System.Reflection.Assembly.GetExecutingAssembly.Location.Substring(0, System.Reflection.Assembly.GetExecutingAssembly.Location.LastIndexOf(&quot;\&quot;)))&lt;br /&gt;&lt;br /&gt;            End If&lt;br /&gt;&lt;br /&gt;            &#39; Construct the full path&lt;br /&gt;            workbook_full_path = destination.FullName &amp; &quot;\&quot; &amp; workbook_name &amp; &quot;.xls&quot;&lt;br /&gt;&lt;br /&gt;            &#39; Create Excel objects&lt;br /&gt;            excel = New Application&lt;br /&gt;            excel.DisplayAlerts = False&lt;br /&gt;            Dim wb As Workbook = excel.Workbooks.Add()&lt;br /&gt;            Dim results_processed As Integer = 0&lt;br /&gt;&lt;br /&gt;            &#39; Tracking dictionary so we can order the sheets&lt;br /&gt;            Dim dicSheets As New Generic.Dictionary(Of Integer, Worksheet)&lt;br /&gt;&lt;br /&gt;            &#39; Only do the Excel processing if we have results&lt;br /&gt;            &#39; Create the right number of sheets&lt;br /&gt;            &#39; And drop data into each sheet&lt;br /&gt;            If sheet_count &gt; 0 Then&lt;br /&gt;                For i As Integer = 1 To sheet_count&lt;br /&gt;&lt;br /&gt;                    &#39; Create a sheet&lt;br /&gt;                    Dim ws As Worksheet&lt;br /&gt;&lt;br /&gt;                    &#39; Put in order&lt;br /&gt;                    If i &gt; 1 Then&lt;br /&gt;                        ws = wb.Worksheets.Add(Type.Missing, dicSheets(i - 1))&lt;br /&gt;                    Else&lt;br /&gt;                        ws = wb.Worksheets.Add&lt;br /&gt;                    End If&lt;br /&gt;&lt;br /&gt;                    dicSheets.Add(i, ws)&lt;br /&gt;&lt;br /&gt;                    Dim results_outstanding As Integer = dtResult.Rows.Count - results_processed&lt;br /&gt;&lt;br /&gt;                    ws.Name = &quot;Page &quot; &amp; i.ToString()&lt;br /&gt;                    lstSheets.Add(ws.Name)&lt;br /&gt;&lt;br /&gt;                    &#39; Start and end positions of the DataTable.  Conditions for if we have&lt;br /&gt;                    &#39; more than 65536 results, we need to know the breakdown for where we&lt;br /&gt;                    &#39; should start and end (ordinally) on the table based on what Sheet we&#39;re on&lt;br /&gt;                    Dim dt_start_row As Integer&lt;br /&gt;                    Dim dt_end_row As Integer&lt;br /&gt;                    Dim sheet_end_row As Integer&lt;br /&gt;&lt;br /&gt;                    &#39; Data Table Bounds&lt;br /&gt;                    &#39; Positionally on the DataTable, where are we extracting data?&lt;br /&gt;                    If i &gt; 1 Then&lt;br /&gt;                        dt_start_row = (i - 1) * 65536 &#39; Minus one because the first sheet is rows 1-65536&lt;br /&gt;                    Else&lt;br /&gt;                        dt_start_row = 1&lt;br /&gt;                    End If&lt;br /&gt;&lt;br /&gt;                    If results_outstanding &lt; 65536 Then&lt;br /&gt;                        dt_end_row = dtResult.Rows.Count&lt;br /&gt;                        sheet_end_row = results_outstanding + 1 &#39; We need it to be inclusive&lt;br /&gt;                    Else&lt;br /&gt;                        dt_end_row = i * 65535&lt;br /&gt;                        sheet_end_row = 65535&lt;br /&gt;                    End If&lt;br /&gt;&lt;br /&gt;                    &#39; Create a two dimensional array&lt;br /&gt;                    &#39; First dimension = rows&lt;br /&gt;                    &#39; Second dimension = columns&lt;br /&gt;                    &#39; Add + 1 to rows because the first row is always the column headers&lt;br /&gt;                    Dim result(sheet_end_row + 1, dtResult.Columns.Count) As Object&lt;br /&gt;&lt;br /&gt;                    &#39; Publish the row header&lt;br /&gt;                    For col As Integer = 0 To dtResult.Columns.Count - 1&lt;br /&gt;                        result(0, col) = dtResult.Columns(col).ColumnName&lt;br /&gt;                    Next&lt;br /&gt;&lt;br /&gt;                    Dim sheet_row_count As Integer = 1&lt;br /&gt;&lt;br /&gt;                    &#39; Propagate the data down in the array&lt;br /&gt;                    &#39; subtract one since the DataTable is zero based&lt;br /&gt;                    For result_data_row As Integer = dt_start_row - 1 To dt_end_row - 1&lt;br /&gt;&lt;br /&gt;                        For result_data_col As Integer = 0 To dtResult.Columns.Count - 1&lt;br /&gt;&lt;br /&gt;                            Dim value As Object = dtResult.Rows(result_data_row)(result_data_col)&lt;br /&gt;&lt;br /&gt;                            &#39; DateTimes come up weird on Excel&lt;br /&gt;                            If TypeOf (value) Is DateTime Then&lt;br /&gt;                                result(sheet_row_count, result_data_col) = value.ToString() &#39;dtResult.Rows(result_data_row)(result_data_col).ToString()&lt;br /&gt;                            Else&lt;br /&gt;                                result(sheet_row_count, result_data_col) = value &#39;dtResult.Rows(result_data_row)(result_data_col)&lt;br /&gt;                            End If&lt;br /&gt;                        Next&lt;br /&gt;&lt;br /&gt;                        &#39; Iterate the sheet&#39;s row counter (not the data table&#39;s row counter)&lt;br /&gt;                        sheet_row_count += 1&lt;br /&gt;                    Next&lt;br /&gt;&lt;br /&gt;                    &#39; Arbitrary number of rows to drop at a time so Excel&#39;s rng.Value2 doesn&#39;t exception out.  Needs further refining, obviously&lt;br /&gt;                    If sheet_row_count &gt;= 65535 AndAlso dtResult.Columns.Count &gt; 30 Then&lt;br /&gt;&lt;br /&gt;                        &#39; Drop in Chunks of 700 rows at a time&lt;br /&gt;                        &#39; We can change this later as we performance tune&lt;br /&gt;                        Dim row_chunks As Integer = 700&lt;br /&gt;                        Dim excel_sheet_max As Integer = 65536&lt;br /&gt;&lt;br /&gt;                        For array_row As Integer = 0 To excel_sheet_max - 1 Step (row_chunks - 1)&lt;br /&gt;&lt;br /&gt;                            Dim number_of_rows As Integer&lt;br /&gt;&lt;br /&gt;                            &#39; If we are at the very end, specify as such&lt;br /&gt;                            &#39; as it might not be a full 700 rows&lt;br /&gt;                            If array_row + (row_chunks - 1) &gt; excel_sheet_max Then&lt;br /&gt;                                number_of_rows = excel_sheet_max - array_row&lt;br /&gt;                            Else&lt;br /&gt;                                number_of_rows = row_chunks&lt;br /&gt;                            End If&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;                            Dim result_segment(number_of_rows, dtResult.Columns.Count) As Object&lt;br /&gt;                            Dim rngBegin As Range = ws.Cells(array_row + 1, 1)&lt;br /&gt;                            Dim rngEnd As Range = ws.Cells(array_row + number_of_rows, dtResult.Columns.Count)&lt;br /&gt;                            Dim rngAll As Range = ws.Range(rngBegin.Address &amp; &quot;:&quot; &amp; rngEnd.Address)&lt;br /&gt;&lt;br /&gt;                            &#39; Copy the 700 rows of elements to the segment&lt;br /&gt;                            Try&lt;br /&gt;                                Array.Copy(result, array_row * (dtResult.Columns.Count + 1), result_segment, 0, number_of_rows * (dtResult.Columns.Count + 1))&lt;br /&gt;                                rngAll.Value2 = result_segment&lt;br /&gt;                            Catch e As Exception&lt;br /&gt;&lt;br /&gt;                            End Try&lt;br /&gt;&lt;br /&gt;                            Array.Clear(result_segment, result_segment.GetLowerBound(0), result_segment.Length)&lt;br /&gt;                            Array.Clear(result_segment, result_segment.GetLowerBound(1), result_segment.Length)&lt;br /&gt;                            result_segment = Nothing&lt;br /&gt;                        Next&lt;br /&gt;&lt;br /&gt;                    Else&lt;br /&gt;                        Dim rngBegin As Range = ws.Cells(1, 1)&lt;br /&gt;                        Dim rngEnd As Range = ws.Cells(sheet_end_row + 1, dtResult.Columns.Count)&lt;br /&gt;                        Dim rngAll As Range = ws.Range(rngBegin.Address &amp; &quot;:&quot; &amp; rngEnd.Address)&lt;br /&gt;&lt;br /&gt;                        rngAll.Value2 = result&lt;br /&gt;                    End If&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;                    &#39; Formatting -- bold and freeze header columns, autofit columns&lt;br /&gt;                    CType(ws.Cells(1, 1), Range).EntireRow.Font.Bold = True&lt;br /&gt;                    CType(ws.Cells(2, 1), Range).Select()&lt;br /&gt;                    excel.ActiveWindow.FreezePanes = True&lt;br /&gt;                    ws.Columns.AutoFit()&lt;br /&gt;&lt;br /&gt;                    &#39; Blank out the array&lt;br /&gt;                    Array.Clear(result, result.GetLowerBound(0), result.Length)&lt;br /&gt;                    Array.Clear(result, result.GetLowerBound(1), result.Length)&lt;br /&gt;                    result = Nothing&lt;br /&gt;                    GC.Collect()&lt;br /&gt;&lt;br /&gt;                    &#39; Keep track of what we&#39;ve processed&lt;br /&gt;                    results_processed += sheet_end_row&lt;br /&gt;                Next&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;                &#39; Cleanse sheets - remove Worksheets that aren&#39;t named &quot;Page x&quot;&lt;br /&gt;                Dim lstRemoveSheets As New Generic.List(Of String)&lt;br /&gt;                For Each objWS As Object In wb.Worksheets&lt;br /&gt;                    Dim ws As Worksheet = CType(objWS, Worksheet)&lt;br /&gt;                    If lstSheets.Contains(ws.Name) = False Then&lt;br /&gt;                        lstRemoveSheets.Add(ws.Name)&lt;br /&gt;                    End If&lt;br /&gt;                Next&lt;br /&gt;&lt;br /&gt;                For Each sheet_to_delete As String In lstRemoveSheets&lt;br /&gt;                    CType(wb.Worksheets(sheet_to_delete), Worksheet).Delete()&lt;br /&gt;                Next&lt;br /&gt;            End If&lt;br /&gt;&lt;br /&gt;            &#39; Select the first cell of the sheet&lt;br /&gt;&lt;br /&gt;            Dim wsFirst As Worksheet &#39;= dicSheets(1)&lt;br /&gt;&lt;br /&gt;            &#39; Indicate we received no results&lt;br /&gt;            If sheet_count = 0 Then&lt;br /&gt;                wsFirst = wb.Worksheets(1)&lt;br /&gt;                CType(wsFirst.Cells(1, 1), Range).Value2 = &quot;No results found.&quot;&lt;br /&gt;            Else&lt;br /&gt;                wsFirst = dicSheets(1)&lt;br /&gt;            End If&lt;br /&gt;&lt;br /&gt;            wsFirst.Select()&lt;br /&gt;            CType(wsFirst.Cells(1, 1), Range).Select()&lt;br /&gt;&lt;br /&gt;            &#39; Finally return the workbook name&lt;br /&gt;            wb.SaveAs(workbook_full_path)&lt;br /&gt;            wb.Close()&lt;br /&gt;&lt;br /&gt;            &#39; Dispose the dtResult&lt;br /&gt;            dtResult.Dispose()&lt;br /&gt;            dtResult = Nothing&lt;br /&gt;&lt;br /&gt;            Return workbook_name &amp; &quot;.xls&quot; &#39;workbook_full_path&lt;br /&gt;&lt;br /&gt;        Catch ex As Exception&lt;br /&gt;            Throw New Exception(&quot;Error encountered when exporting to Excel: &quot; &amp; ex.Message &amp; &quot; &quot; &amp; ex.StackTrace)&lt;br /&gt;            &#39;Return Nothing &#39; A nothing returned implies unsuccessful creation&lt;br /&gt;        Finally&lt;br /&gt;            If excel Is Nothing = False Then&lt;br /&gt;                excel.Quit()&lt;br /&gt;                System.Runtime.InteropServices.Marshal.ReleaseComObject(excel)&lt;br /&gt;                excel = Nothing&lt;br /&gt;                GC.Collect()&lt;br /&gt;            End If&lt;br /&gt;        End Try&lt;br /&gt;    End Function&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;</content><link rel='replies' type='application/atom+xml' href='http://geekstentialism.blogspot.com/feeds/5553144015698914299/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/2381314077784322587/5553144015698914299' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2381314077784322587/posts/default/5553144015698914299'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2381314077784322587/posts/default/5553144015698914299'/><link rel='alternate' type='text/html' href='http://geekstentialism.blogspot.com/2011/03/working-with-database-query-results-in.html' title='Breaking Down Database Query Results in Chunks of 65536 Rows for Excel 2003 Using Office Interop and VB .Net'/><author><name>Eric Nograles</name><uri>http://www.blogger.com/profile/02198416401185614213</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJ6RXtY4XZkDuwsIOoO4bKx5gDQMLkcRQIQvtomOIeGbXKFJ1pTzAe7PS1HyPIdW4Rb2Pc5ZljcijJCLlORJpe1c99VfDOUTpn5A06e4WU0yTboyJiAC0eJ8PT0Itv1xc/s1600/*'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2381314077784322587.post-8153708470976313925</id><published>2011-02-26T08:54:00.000-08:00</published><updated>2011-02-26T09:11:28.490-08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="athens"/><category scheme="http://www.blogger.com/atom/ns#" term="greece"/><category scheme="http://www.blogger.com/atom/ns#" term="greek culture"/><title type='text'>Greekstentialism</title><content type='html'>&lt;a onblur=&quot;try {parent.deselectBloggerImageGracefully();} catch(e) {}&quot; href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiTa5dCSkYJYHKXKcv5MtNCSltPAg8G6ctwTJZphuxswJXN7sPHyDbDuoI311aJB6Q2Q6MK5YElGl1lnSCqPIvEQv-rvTOBktNf1nYlxRe9TSpnoUDGgGwTyDHRqfLuXdtn_zRq1lyTI55L/s1600/IMG_2082.JPG&quot;&gt;&lt;img style=&quot;float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;width: 320px; height: 214px;&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiTa5dCSkYJYHKXKcv5MtNCSltPAg8G6ctwTJZphuxswJXN7sPHyDbDuoI311aJB6Q2Q6MK5YElGl1lnSCqPIvEQv-rvTOBktNf1nYlxRe9TSpnoUDGgGwTyDHRqfLuXdtn_zRq1lyTI55L/s320/IMG_2082.JPG&quot; border=&quot;0&quot; alt=&quot;&quot; id=&quot;BLOGGER_PHOTO_ID_5578043313697173410&quot; /&gt;&lt;/a&gt;&lt;br /&gt;Kalisera from Athens, Greece!  Another day, another data integration project with one of our field offices...and thankfully, this one happens to be in one of the cities I&#39;ve been meaning to visit since I was a child.  Growing up a product of the US public school system, you&#39;d often hear stories about ancient Greek civilizations and their culture.  This country is indeed the birthplace of democracy and great minds such as Socrates, and of course, being a general math geek that I am, Pythagoras.  Heck, ancient Greek reading was even required in high school, as we read Homer&#39;s Iliad and Odyssey in our English classes (the latter being one of my favorite books of all time).  All things considered, despite having fallen on hard times recently, I believe Greece to still be one of the most fascinating countries in the world.  The picture to the left is me seated out front of the Parthenon in the Acropolis, a truly amazing structure that I&#39;d rank up with the Egyptian pyramids as a &quot;must see before time wears it down to nothing.&quot;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Anyway, geeky tourist fascination aside, I&#39;ve learned much about the modern Greek culture since I&#39;ve been here.  Some tips for my fellow Americans who may have never visited Greece before and are planning on coming by in the near future:&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;The Greeks LOVE cheese.  Not just feta either.  Cheese is practically on every dish.  For those who are lactose-intolerant, bring all appropriate OTC treatments.&lt;/li&gt;&lt;li&gt;Eat lunch at 12-1 pm?  Most Greeks will look at you strangely.  Lunch here typically begins around 2:30pm/3:00pm.&lt;/li&gt;&lt;li&gt;Eat dinner at 6-7 pm?  Again, most Greeks will look at you strangely.  Actually, you won&#39;t see them looking at you strangely because none of them will be out.  Dinner starts around 9:00pm/9:30pm.&lt;/li&gt;&lt;li&gt;Common Greek phrases &lt;a href=&quot;http://www.takisnet.org/Greek/&quot;&gt;can be found here&lt;/a&gt; complete with phonetic pronunciation.&lt;/li&gt;&lt;li&gt;Want to eat some amazing seafood while driving by some of the biggest villas in Greece?  Take a trip a little north of Athens to a place called Kiffisias.  &lt;/li&gt;&lt;li&gt;Have ouzo on the rocks when having seafood, then finish off with mastica as a dessert apertif.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;Word is I might be headed back around these parts sooner rather than later, so I will be sure to post any more tips as I discover them.&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geekstentialism.blogspot.com/feeds/8153708470976313925/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/2381314077784322587/8153708470976313925' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2381314077784322587/posts/default/8153708470976313925'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2381314077784322587/posts/default/8153708470976313925'/><link rel='alternate' type='text/html' href='http://geekstentialism.blogspot.com/2011/02/greekstentialism.html' title='Greekstentialism'/><author><name>Eric Nograles</name><uri>http://www.blogger.com/profile/02198416401185614213</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJ6RXtY4XZkDuwsIOoO4bKx5gDQMLkcRQIQvtomOIeGbXKFJ1pTzAe7PS1HyPIdW4Rb2Pc5ZljcijJCLlORJpe1c99VfDOUTpn5A06e4WU0yTboyJiAC0eJ8PT0Itv1xc/s1600/*'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiTa5dCSkYJYHKXKcv5MtNCSltPAg8G6ctwTJZphuxswJXN7sPHyDbDuoI311aJB6Q2Q6MK5YElGl1lnSCqPIvEQv-rvTOBktNf1nYlxRe9TSpnoUDGgGwTyDHRqfLuXdtn_zRq1lyTI55L/s72-c/IMG_2082.JPG" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2381314077784322587.post-4277807182497195765</id><published>2011-02-13T05:06:00.000-08:00</published><updated>2011-02-13T05:36:02.326-08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="2010 honda insight"/><category scheme="http://www.blogger.com/atom/ns#" term="fuel efficiency"/><category scheme="http://www.blogger.com/atom/ns#" term="honda"/><category scheme="http://www.blogger.com/atom/ns#" term="hybrid"/><category scheme="http://www.blogger.com/atom/ns#" term="s2000"/><title type='text'>Environmental Geekstentialism</title><content type='html'>With petrol prices still climbing and a need for a highly efficient car for my fiance, I decided it was time to finally bid adieu to my &lt;b&gt;Honda S2000&lt;/b&gt;.  I had quite a terrific time with that car, but its impracticality wore my patience a little thin over the years.  I would still recommend it for anyone who wants a reasonably priced convertible sports car without sacrificing the fun -- I dare say you won&#39;t find a better 6-speed shifter this side of a Porsche 911.  I&#39;ll still have my 2003 Subaru WRX project car to keep my speed needs at bay.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;That being said, as a result of our vehicular needs, we have joined the ranks of the hybrid owners of the world and got ourselves a &lt;b&gt;2010 Honda Insight&lt;/b&gt;.  I must admit, I was a little leery with hybrid technology at first; the first generation Insight was the S2000&#39;s environmental cousin (i.e. completely impractical with anything else aside from fuel efficiency) and the prior and current gen Priuses never really did it for me with regards to a driving experience...not to mention its steadily climbing price.  &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;With that in mind, I was able to hunt down a certified 2010 Honda Insight that struck our fancy with its price point.  In test driving the car, I found myself to be a bit surprised -- the handling of the car is, dare I say it, actually fun.  Its handling characteristics reminded me of my cousin&#39;s Honda Fit, with which I&#39;m sure the Insight shared some of its components.  The steering is very responsive, definitely much more responsive than the Priuses I&#39;ve test driven, and it feels pretty nimble on its feet in normal and highway traffic.  &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Don&#39;t get me wrong, speed and acceleration-wise, the Insight is the polar opposite of the S2000, but my fiance was more concerned with reliability and fuel efficiency.  In both aspects, I&#39;d say Honda excels.  Reliability?  It&#39;s a Honda.  I know it&#39;s become the company&#39;s unofficial motto, but their cars simply are highly reliable.  I still remember my parents&#39; old 1989 Civic.  My family piled on about 200,000 miles and 15 years on that car.  Engine never blew, only had to replace the usual wear and tear items, and overall was a great little car.  Ditto my S2000 -- not a problem with it mechanically in my 3 years of ownership (and, um, enthusiastic driving).&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Fuel efficiency...now this is where the Insight really impresses the geek in me.  Most of the new hybrids have a similar system, but I believe the Insight&#39;s execution of it is a little better on the user-side.  The car has a built in ECO Mode which maximizes the car&#39;s fuel efficiency to go along with the brilliant ECO Guide user interface on the dash.  The ECO Guide is what I&#39;m impressed with the most.  The speedometer (mounted at the top of the dash, a la the new Civics) has an ambient light that glows green when you&#39;re driving in a fuel efficient manner and dark blue when you&#39;re driving like a maniac.  To help coach you into better fuel efficiency, there is also a UI on the dash called the Eco Drive Bar which shows you efficiency ranges as you accelerate and decelerate.  I think this system, at least for me, works a bit better than Ford&#39;s pretty &quot;leaves&quot; UI.  The ambient lighting on the dash gives enough of a high level overview of your fuel efficiency while the Eco Drive Bar can show a driver specifically how his inputs affect his fuel efficiency.  So, even a lead footed speed addict such as myself can be trained into maximizing his fuel efficiency.  Not bad at all.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;So, what&#39;s the verdict on the fuel efficiency?  The sticker says 40 city/43 highway.  I drove from the hills of Manayunk to Abington, taking all local hilly roads, and I easily beat the city average and got closer to 45 mpg.  I&#39;d imagine that reaching 50+ mpg on the highway is easily attained.  The Prius still beats those numbers by about 10-20% with its slightly more advanced hybrid system, but again, I felt the driving experience on the Insight was slightly more fun than that of its Toyota competitor...not to mention I&#39;m kind of biased to Honda products.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;While the car enthusiast in me may mourn the loss of one of the best production sports cars ever built, the Eco Geek in me will rejoice in (maybe) once a month fill-ups, lower insurance premiums, and a more positive environment impact.  That will certainly help.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;...and I think my WRX is about due for a new turbo and intercooler anyway  ;-)&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geekstentialism.blogspot.com/feeds/4277807182497195765/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/2381314077784322587/4277807182497195765' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2381314077784322587/posts/default/4277807182497195765'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2381314077784322587/posts/default/4277807182497195765'/><link rel='alternate' type='text/html' href='http://geekstentialism.blogspot.com/2011/02/environmental-geekstentialism.html' title='Environmental Geekstentialism'/><author><name>Eric Nograles</name><uri>http://www.blogger.com/profile/02198416401185614213</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJ6RXtY4XZkDuwsIOoO4bKx5gDQMLkcRQIQvtomOIeGbXKFJ1pTzAe7PS1HyPIdW4Rb2Pc5ZljcijJCLlORJpe1c99VfDOUTpn5A06e4WU0yTboyJiAC0eJ8PT0Itv1xc/s1600/*'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2381314077784322587.post-9137052326588198074</id><published>2010-12-29T11:41:00.001-08:00</published><updated>2010-12-29T11:44:28.431-08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="coding"/><category scheme="http://www.blogger.com/atom/ns#" term="donate"/><category scheme="http://www.blogger.com/atom/ns#" term="programming tasks"/><category scheme="http://www.blogger.com/atom/ns#" term="rosetta code"/><category scheme="http://www.blogger.com/atom/ns#" term="support"/><title type='text'>Support Rosetta Code</title><content type='html'>For all you hobbyist or professional developers out there who have not heard of &lt;a href=&quot;http://rosettacode.org/wiki/Welcome_to_Rosetta_Code&quot;&gt;Rosetta Code&lt;/a&gt;, it is a Wikipedia of sorts for programming tasks solved with a plethora of different languages (452 and counting, I believe) -- and they need your support to keep it alive!&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Visit their &lt;a href=&quot;http://rosettacode.org/wiki/Rosetta_Code:Finances&quot;&gt;Finances Page&lt;/a&gt; to see how you may be able to help (yes, they take PayPal donations).  It&#39;d be an absolute shame to see this growing and useful resource go under.&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geekstentialism.blogspot.com/feeds/9137052326588198074/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/2381314077784322587/9137052326588198074' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2381314077784322587/posts/default/9137052326588198074'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2381314077784322587/posts/default/9137052326588198074'/><link rel='alternate' type='text/html' href='http://geekstentialism.blogspot.com/2010/12/support-rosetta-code.html' title='Support Rosetta Code'/><author><name>Eric Nograles</name><uri>http://www.blogger.com/profile/02198416401185614213</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJ6RXtY4XZkDuwsIOoO4bKx5gDQMLkcRQIQvtomOIeGbXKFJ1pTzAe7PS1HyPIdW4Rb2Pc5ZljcijJCLlORJpe1c99VfDOUTpn5A06e4WU0yTboyJiAC0eJ8PT0Itv1xc/s1600/*'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2381314077784322587.post-1324251648193610145</id><published>2010-12-28T13:43:00.000-08:00</published><updated>2010-12-28T16:32:21.631-08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="basic"/><category scheme="http://www.blogger.com/atom/ns#" term="first programming language"/><category scheme="http://www.blogger.com/atom/ns#" term="java"/><title type='text'>The Best Teaching Programming Language?</title><content type='html'>I ran across an &lt;a href=&quot;http://www.kickstarter.com/projects/1319327833/clubcompy-kids-heart-computers/posts&quot;&gt;article&lt;/a&gt; today that attempts to explain the merits of using BASIC as a teaching programming language.  While the author makes some valid points, specifically at the simplicity of BASIC, I believe that BASIC is a bit of an archaic language that should probably not be considered as a teaching language.  I believe learning this language would espouse some bad habits (GOTO, GOSUB nightmares inherent in BASIC) and would promote design concepts that wouldn&#39;t have any application in the real world.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;In terms of context, I was trying to think of what would stir someone to pursue programming as a hobby and potential career path.  Sure, the very fundamental idea of programming is to send instructions for your computer to execute, but why would someone (assuming in younger generations) pursue this seemingly droll craft?  &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Thinking back on my younger days as a script kid/amateur hacker, what really appealed to me initially was actually videogames.  I was astounded at the &quot;black art&quot; of sending instructions understood by a computer that somehow turned into the gaming experiences I loved as a child.  Of course, over time, my interest eventually evolved to my current interest in data driven applications, but what really hooked me first was definitely the flashiness that could be achieved in programming in videogames.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;That being said, I tried to think of what language out there would be the best teaching language for someone of my demographic when I was younger -- a language that would be readily accessible, relatively easy to pick up, and a person could steadily expand his/her skills through a learning process.  Also, the ability to do some flashy things wouldn&#39;t hurt; let&#39;s face it, at the end of the day, a programmer would like to see something with a little pizazz versus some boring command line outputs.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Taking these factors into account, if I were younger today, I&#39;d probably want to pick up &lt;b&gt;Java&lt;/b&gt; as my first language.  The learning curve of object-oriented design may be a turn off for beginners, but you can certainly use Java without having the full knowledge of OOD (though the architecture inherently will drive you to understand it).  It is: &lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;A free, readily available platform&lt;/li&gt;&lt;li&gt;All the basic OOD goodies like polymorphism, inheritance, and recursion for advanced learners&lt;/li&gt;&lt;li&gt;Type safety, i.e., an integer is an integer, a String is a String, etc (some schools of thought consider type safety to be a hindrance -- I am still a believer in it)&lt;/li&gt;&lt;li&gt;A huge community from which to absorb knowledge&lt;/li&gt;&lt;li&gt;Multiple platforms for development -- Java 2 SE, J2EE, Android, etc&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;Some may say that Java may be too formidable a platform on which to start, but I feel that the wealth of resources available for one to learn Java, coupled with the strong community and great tools that aid in learning would make Java the best candidate for a teaching programming language.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;So, what about the aforementioned flashiness that will hook certain people?  Java&#39;s got tools for that too.  Specifically, I still remember an intermediate Java class in college that I thought would&#39;ve helped drive the concepts of OOD home for a lot of students in a high school programming course.  We used a great Java application called &lt;a href=&quot;http://robocode.sourceforge.net/&quot;&gt;RoboCode&lt;/a&gt; as our course-long project.  The final exam?  Having all of our classmates&#39; robots go at it in a battle royale on the last day.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Certainly beats solving abstract problems and was more educational if you ask me.  &lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geekstentialism.blogspot.com/feeds/1324251648193610145/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/2381314077784322587/1324251648193610145' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2381314077784322587/posts/default/1324251648193610145'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2381314077784322587/posts/default/1324251648193610145'/><link rel='alternate' type='text/html' href='http://geekstentialism.blogspot.com/2010/12/best-teaching-programming-language.html' title='The Best Teaching Programming Language?'/><author><name>Eric Nograles</name><uri>http://www.blogger.com/profile/02198416401185614213</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJ6RXtY4XZkDuwsIOoO4bKx5gDQMLkcRQIQvtomOIeGbXKFJ1pTzAe7PS1HyPIdW4Rb2Pc5ZljcijJCLlORJpe1c99VfDOUTpn5A06e4WU0yTboyJiAC0eJ8PT0Itv1xc/s1600/*'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2381314077784322587.post-1691247244436504581</id><published>2010-12-15T11:21:00.001-08:00</published><updated>2010-12-15T11:25:13.065-08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="cliff lee"/><category scheme="http://www.blogger.com/atom/ns#" term="philadelphia phillies"/><title type='text'>Philly Sports: The Four Horsemen Assembled</title><content type='html'>&lt;div&gt;Great day to be a Phillies fan today.  In a little over 45 minutes, &lt;a href=&quot;http://philadelphia.phillies.mlb.com/video/play.jsp?topic_id=15407782&quot;&gt;Cliff Lee comes home to Philly&lt;/a&gt;!  Not sure to whom I should give credit for the Photoshop below, but whoever you are, well done.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;a onblur=&quot;try {parent.deselectBloggerImageGracefully();} catch(e) {}&quot; href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiGjkubUUd0cVriNI_aGY4ACx-my7v1P49Ap72rVPCUQ7uejj7XMjio-WfgBKViD-RR12ETfGn6cCDE9kveo3B0Y7ZHP7MIXfTnhAK74JpgOlywoASsUpSiXbRGZgoNj_7hITCAOoskztLZ/s1600/always-winning.jpg&quot;&gt;&lt;img style=&quot;display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 257px; height: 320px;&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiGjkubUUd0cVriNI_aGY4ACx-my7v1P49Ap72rVPCUQ7uejj7XMjio-WfgBKViD-RR12ETfGn6cCDE9kveo3B0Y7ZHP7MIXfTnhAK74JpgOlywoASsUpSiXbRGZgoNj_7hITCAOoskztLZ/s320/always-winning.jpg&quot; border=&quot;0&quot; alt=&quot;&quot; id=&quot;BLOGGER_PHOTO_ID_5550991692811872674&quot; /&gt;&lt;/a&gt;&lt;div&gt;&lt;a onblur=&quot;try {parent.deselectBloggerImageGracefully();} catch(e) {}&quot; href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiGjkubUUd0cVriNI_aGY4ACx-my7v1P49Ap72rVPCUQ7uejj7XMjio-WfgBKViD-RR12ETfGn6cCDE9kveo3B0Y7ZHP7MIXfTnhAK74JpgOlywoASsUpSiXbRGZgoNj_7hITCAOoskztLZ/s1600/always-winning.jpg&quot;&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geekstentialism.blogspot.com/feeds/1691247244436504581/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/2381314077784322587/1691247244436504581' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2381314077784322587/posts/default/1691247244436504581'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2381314077784322587/posts/default/1691247244436504581'/><link rel='alternate' type='text/html' href='http://geekstentialism.blogspot.com/2010/12/philly-sports-four-horsemen-assembled.html' title='Philly Sports: The Four Horsemen Assembled'/><author><name>Eric Nograles</name><uri>http://www.blogger.com/profile/02198416401185614213</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJ6RXtY4XZkDuwsIOoO4bKx5gDQMLkcRQIQvtomOIeGbXKFJ1pTzAe7PS1HyPIdW4Rb2Pc5ZljcijJCLlORJpe1c99VfDOUTpn5A06e4WU0yTboyJiAC0eJ8PT0Itv1xc/s1600/*'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiGjkubUUd0cVriNI_aGY4ACx-my7v1P49Ap72rVPCUQ7uejj7XMjio-WfgBKViD-RR12ETfGn6cCDE9kveo3B0Y7ZHP7MIXfTnhAK74JpgOlywoASsUpSiXbRGZgoNj_7hITCAOoskztLZ/s72-c/always-winning.jpg" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2381314077784322587.post-8766420477369656839</id><published>2010-12-15T10:08:00.000-08:00</published><updated>2010-12-15T11:20:34.273-08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="automated build"/><category scheme="http://www.blogger.com/atom/ns#" term="continuous integration"/><category scheme="http://www.blogger.com/atom/ns#" term="cruise control .net"/><category scheme="http://www.blogger.com/atom/ns#" term="multiple projects"/><category scheme="http://www.blogger.com/atom/ns#" term="visual sourcesafe 6.0"/><category scheme="http://www.blogger.com/atom/ns#" term="vss"/><title type='text'>Multiple VSS Projects Using Cruise Control .Net</title><content type='html'>I&#39;m a big fan of &lt;a href=&quot;http://confluence.public.thoughtworks.org/display/CCNET/Welcome+to+CruiseControl.NET&quot;&gt;Cruise Control .Net&lt;/a&gt;.  As an open source continuous integration server, it performs wonderfully and is quite scalable.  &lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Unfortunately, great CI servers can often times be matched with horrendous source control systems.  Specifically, the bane of all SCM&#39;s, Visual SourceSafe 6.0.  My group at work unfortunately is still encumbered by VSS 6.0 for a little while longer until we finally migrate to a proper SCM in 2011.  Subversion or Git obviously would get my first vote....but, I digress...&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I ran into a little hiccup with VSS on Cruise Control .Net -- some of my assemblies are located in different project locations.  I needed to update multiple projects on different locations in the SCM prior to executing the build.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;So, how does one properly update multiple projects in VSS for a Cruise Control .Net build?  Digging into the CC .Net documentation further, I found that there is an option for a &quot;multi&quot; sourcecontrol block in your ccnet.config file.  &lt;b&gt;Using the multi type sourcecontrol block, one can call for multiple updates to different projects on VSS&lt;/b&gt;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;&lt;span class=&quot;Apple-style-span&quot;&gt;Example:&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&amp;lt;sourcecontrol type=&quot;multi&quot;&amp;gt;&lt;br /&gt; &amp;lt;sourceControls&amp;gt;&lt;br /&gt;  &amp;lt;vss&amp;gt;&lt;br /&gt;     &amp;lt;executable&amp;gt;C:\Program Files\Microsoft Visual Studio\Common\VSS\win32\ss.exe&amp;lt;/executable&amp;gt;&lt;br /&gt;     &amp;lt;project&amp;gt;$/SampleApplication&amp;lt;/project&amp;gt;&lt;br /&gt;     &amp;lt;username&amp;gt;VSSUser&amp;lt;/username&amp;gt;&lt;br /&gt;     &amp;lt;password&amp;gt;VSSPassword&amp;lt;/password&amp;gt;&lt;br /&gt;     &amp;lt;ssdir&amp;gt;\\SERVERNAME\VSS&amp;lt;/ssdir&amp;gt;&lt;br /&gt;     &amp;lt;workingDirectory&amp;gt;c:\VSS\SampleApplication&amp;lt;/workingDirectory&amp;gt;&lt;br /&gt;     &amp;lt;culture&amp;gt;en-US&amp;lt;/culture&amp;gt;&lt;br /&gt;     &amp;lt;cleanCopy&amp;gt;false&amp;lt;/cleanCopy&amp;gt;&lt;br /&gt;  &amp;lt;/vss&amp;gt;&lt;br /&gt;  &amp;lt;vss&amp;gt;&lt;br /&gt;     &amp;lt;executable&amp;gt;C:\Program Files\Microsoft Visual Studio\Common\VSS\win32\ss.exe&amp;lt;/executable&amp;gt;&lt;br /&gt;     &amp;lt;project&amp;gt;$/SampleApplication2&amp;lt;/project&amp;gt;&lt;br /&gt;     &amp;lt;username&amp;gt;VSSUser&amp;lt;/username&amp;gt;&lt;br /&gt;     &amp;lt;password&amp;gt;VSSPassword&amp;lt;/password&amp;gt;&lt;br /&gt;     &amp;lt;ssdir&amp;gt;\\SERVERNAME\VSS&amp;lt;/ssdir&amp;gt;&lt;br /&gt;     &amp;lt;workingDirectory&amp;gt;c:\VSS\SampleApplication2&amp;lt;/workingDirectory&amp;gt;&lt;br /&gt;     &amp;lt;culture&amp;gt;en-US&amp;lt;/culture&amp;gt;&lt;br /&gt;     &amp;lt;cleanCopy&amp;gt;false&amp;lt;/cleanCopy&amp;gt;&lt;br /&gt;  &amp;lt;/vss&amp;gt;&lt;br /&gt; &amp;lt;/sourceControls&amp;gt;&lt;br /&gt;&amp;lt;/sourcecontrol&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;In essence, you are instructing CC .Net to make multiple calls to the SCM (VSS in this case) to grab all pertinent source code prior to your build.  This example would obviously also work for other SCM&#39;s compatible with CC .Net, such as Subversion.&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geekstentialism.blogspot.com/feeds/8766420477369656839/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/2381314077784322587/8766420477369656839' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2381314077784322587/posts/default/8766420477369656839'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2381314077784322587/posts/default/8766420477369656839'/><link rel='alternate' type='text/html' href='http://geekstentialism.blogspot.com/2010/12/multiple-vss-projects-using-cruise.html' title='Multiple VSS Projects Using Cruise Control .Net'/><author><name>Eric Nograles</name><uri>http://www.blogger.com/profile/02198416401185614213</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJ6RXtY4XZkDuwsIOoO4bKx5gDQMLkcRQIQvtomOIeGbXKFJ1pTzAe7PS1HyPIdW4Rb2Pc5ZljcijJCLlORJpe1c99VfDOUTpn5A06e4WU0yTboyJiAC0eJ8PT0Itv1xc/s1600/*'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2381314077784322587.post-4234988653550181934</id><published>2010-12-12T11:56:00.000-08:00</published><updated>2010-12-12T12:09:05.014-08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="ibm thinkpad"/><category scheme="http://www.blogger.com/atom/ns#" term="laptop keyboards"/><category scheme="http://www.blogger.com/atom/ns#" term="lenovo ideapad"/><title type='text'>Laptop Keyboard Ergonomics</title><content type='html'>The design and feel of laptop keyboards always seemed very similar across the board in my experience.  However, there were/are exceptions to this rule.  To me, the ultimate laptop keyboard was that of my old &lt;b&gt;IBM Thinkpad T22&lt;/b&gt;.  The keys were perfectly spaced and the ancillary functions were so easy to figure out, I could turn down my laptop speaker&#39;s volume, turn on the keyboard LED, and put the machine on hibernate with my eyes closed.  After that laptop, all other keyboards seemed very inadequate to me.&lt;div&gt;&lt;br /&gt;&lt;a onblur=&quot;try {parent.deselectBloggerImageGracefully();} catch(e) {}&quot; href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg2BOpn_48yBOQff9jzPPSJunHoTFKUmhfe5RawHWnZ6y5ymdtbXFsFO1222zpNoJXE7WAAHxg_drIcOcJQTDtUKmVxzHNzTgVTP1zSy3DfHkOoTrVGa9x4sP0fJbxJ3Wa7aP-QSgm8BMrc/s1600/2010-12-12_14-56-15_815.jpg&quot;&gt;&lt;img style=&quot;float:right; margin:0 0 10px 10px;cursor:pointer; cursor:hand;width: 320px; height: 180px;&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg2BOpn_48yBOQff9jzPPSJunHoTFKUmhfe5RawHWnZ6y5ymdtbXFsFO1222zpNoJXE7WAAHxg_drIcOcJQTDtUKmVxzHNzTgVTP1zSy3DfHkOoTrVGa9x4sP0fJbxJ3Wa7aP-QSgm8BMrc/s320/2010-12-12_14-56-15_815.jpg&quot; border=&quot;0&quot; alt=&quot;&quot; id=&quot;BLOGGER_PHOTO_ID_5549889334496003794&quot; /&gt;&lt;/a&gt;&lt;br /&gt;I&#39;m pleased to say that my new laptop, the &lt;b&gt;Lenovo IdeaPad Z565&lt;/b&gt;, has a keyboard that will make my yearning for my old T22 keyboard go away.  As you can see from the image on the right, Lenovo opted to go with the &quot;chiclet layout&quot; that&#39;s become vogue with laptop these days thanks to the MacBooks.  I&#39;m definitely a bigger fan of this assembly versus the interconnected keys that would inevitably pop off when my cat decides to use my laptop as a butt heater.  Beyond the assembly, you can see that the laptop&#39;s size affords it the ability to actually have a relatively full-sized keyboard complete with numeric pad.  It took about a day to get used to NOT crunching my hands together to adhere to a small layout.  Now that I have gotten accustomed to having a full-sized keyboard on a laptop, I don&#39;t think I&#39;ll ever be able to go back to the old standard laptop keyboards.&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://geekstentialism.blogspot.com/feeds/4234988653550181934/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/2381314077784322587/4234988653550181934' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2381314077784322587/posts/default/4234988653550181934'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2381314077784322587/posts/default/4234988653550181934'/><link rel='alternate' type='text/html' href='http://geekstentialism.blogspot.com/2010/12/laptop-keyboard-ergonomics.html' title='Laptop Keyboard Ergonomics'/><author><name>Eric Nograles</name><uri>http://www.blogger.com/profile/02198416401185614213</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJ6RXtY4XZkDuwsIOoO4bKx5gDQMLkcRQIQvtomOIeGbXKFJ1pTzAe7PS1HyPIdW4Rb2Pc5ZljcijJCLlORJpe1c99VfDOUTpn5A06e4WU0yTboyJiAC0eJ8PT0Itv1xc/s1600/*'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg2BOpn_48yBOQff9jzPPSJunHoTFKUmhfe5RawHWnZ6y5ymdtbXFsFO1222zpNoJXE7WAAHxg_drIcOcJQTDtUKmVxzHNzTgVTP1zSy3DfHkOoTrVGa9x4sP0fJbxJ3Wa7aP-QSgm8BMrc/s72-c/2010-12-12_14-56-15_815.jpg" height="72" width="72"/><thr:total>0</thr:total></entry></feed>