<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="http://weblogs.foxite.com/rss.xsl" media="screen"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:wfw="http://wellformedweb.org/CommentAPI/"><channel><title>Andy Kramek</title><link>http://weblogs.foxite.com/andykramek/default.aspx</link><description>Andy's Weblog</description><dc:language>en-US</dc:language><generator>CommunityServer 2.0 (Build: 60217.2664)</generator><item><title>Our DevTeach Experience Follow-Up</title><link>http://weblogs.foxite.com/andykramek/archive/2009/06/21/8544.aspx</link><pubDate>Sun, 21 Jun 2009 11:34:00 GMT</pubDate><guid isPermaLink="false">8827bd1c-7596-4a8f-b0de-f59ce9ede522:8544</guid><dc:creator>andykr</dc:creator><slash:comments>2</slash:comments><comments>http://weblogs.foxite.com/andykramek/comments/8544.aspx</comments><wfw:commentRss>http://weblogs.foxite.com/andykramek/commentrss.aspx?PostID=8544</wfw:commentRss><description>&lt;P&gt;&lt;FONT face=Tahoma size=2&gt;I have one more point to add in respect of our Vancouver DevTeach experience in June of this year. If you read my blog last week you will know that I had a complaint about the lack of conference materials in general and specifically that:&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Tahoma size=2&gt;&lt;EM&gt;There was also no conference CD (though I did get an Email telling me that session materials would be downloadable from 6/18)&lt;/EM&gt; &lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Tahoma size=2&gt;Well, I just downloaded the session materials for the sessions I wanted and I am&amp;nbsp;seriously angry - for several reasons:&amp;nbsp;&lt;BR&gt;&lt;/FONT&gt;&lt;FONT size=2&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;&lt;FONT face=Tahoma size=2&gt;There is no&amp;nbsp;composite download. You have to go to the web site and download each sessions's materials individually. &amp;nbsp;There are not even files by track - only by session. What a waste of time!&lt;/FONT&gt; 
&lt;LI&gt;&lt;FONT face=Tahoma size=2&gt;Even the session file(s) are not&amp;nbsp;consistent - some have 1 file, some have a dozen. Why couldn't the presenters have zipped their material into 1 file per session?&lt;/FONT&gt; 
&lt;LI&gt;&lt;FONT face=Tahoma size=2&gt;On some sessions, when you try to download the file you get a "Page Not Found" error, other sessions simply have a "No materials available" message&lt;/FONT&gt; 
&lt;LI&gt;&lt;FONT face=Tahoma size=2&gt;When you do get something it is, by and large, useless. The vast majority of "Session Materials" turn out to be just the PowerPoint Slides! The few that have anything more are simply unannotated demo files&lt;/FONT&gt; 
&lt;LI&gt;&lt;FONT face=Tahoma size=2&gt;I spent more than an hour painstakingly downloading over 50 files, one a time, in&amp;nbsp;the&amp;nbsp;hope of finally getting some value - but all I have gotten are a bunch of PowerPoint slides and the odd bit of sample code&lt;/FONT&gt;&lt;/LI&gt;&lt;/UL&gt;
&lt;P&gt;&lt;FONT face=Tahoma&gt;&lt;FONT size=2&gt;There are NO white papers! &lt;/FONT&gt;&lt;/FONT&gt;&lt;FONT face=Tahoma&gt;&lt;FONT size=2&gt;Not one! Without a white paper a session is pretty much a waste of time. &lt;/FONT&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Tahoma&gt;&lt;FONT size=2&gt;What I mean is that at the conference you attend 15+ sessions in three days - how can&amp;nbsp;anyone&amp;nbsp;possibly remember all the details? You NEED the white papers so that later, when you come back to review the material, or look&amp;nbsp;up the details of some vaguely remembered speaker's comment, you have some chance of finding it! &lt;/FONT&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Tahoma&gt;&lt;FONT size=2&gt;In all my years as a conference speaker I have never given a session that did not include a background paper that could be used by attendees to review the session contents and give meaning and context in retrospect. &lt;/FONT&gt;&lt;/FONT&gt;&lt;FONT face=Tahoma&gt;&lt;FONT size=2&gt;In fact I have never heard of a conference where the submission of a white paper was not part of the requirements&amp;nbsp;for speakers (and I even have known cases where failure to submit a paper meant you didn't speak at all and that you were dropped from the list!)&lt;/FONT&gt;&lt;/FONT&gt;&lt;FONT face=Tahoma&gt;&lt;FONT size=2&gt;&lt;BR&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;FONT face=Tahoma&gt;&lt;FONT size=2&gt;&lt;BR&gt;How can a conference organization be so appallingly dismissive of their attendees? &lt;/FONT&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Tahoma&gt;&lt;FONT size=2&gt;I really feel we were ripped off from A-Z by this conference and &lt;/FONT&gt;&lt;FONT size=2&gt;I have already said that we would probably not attend another DevTeach - now I am sure we won't. &lt;BR&gt;&lt;BR&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;FONT face=Tahoma size=2&gt;In fact, nothing on earth would persuade me to attend another DevTeach and unless you especially like to waste money, I strongly urge you to try another conference where, perhaps, your money and time are appreciated sufficiently by the organizers that they will make a token effort to ensure that you get reasonable value. &lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Tahoma size=2&gt;&lt;STRONG&gt;The one thing this has experience has taught me is that&amp;nbsp;DevTeach does NOT deliver value by any reasonable measure.&lt;/STRONG&gt;&lt;/FONT&gt;&lt;/P&gt;&lt;img src="http://weblogs.foxite.com/aggbug.aspx?PostID=8544" width="1" height="1"&gt;</description><category domain="http://weblogs.foxite.com/andykramek/archive/category/1063.aspx">Comment/Opinion</category><category domain="http://weblogs.foxite.com/andykramek/archive/category/1080.aspx">Non-Technical</category></item><item><title>Creating Data Driven Pop-Up menus in VFP</title><link>http://weblogs.foxite.com/andykramek/archive/2009/06/20/8532.aspx</link><pubDate>Sat, 20 Jun 2009 14:38:00 GMT</pubDate><guid isPermaLink="false">8827bd1c-7596-4a8f-b0de-f59ce9ede522:8532</guid><dc:creator>andykr</dc:creator><slash:comments>0</slash:comments><comments>http://weblogs.foxite.com/andykramek/comments/8532.aspx</comments><wfw:commentRss>http://weblogs.foxite.com/andykramek/commentrss.aspx?PostID=8532</wfw:commentRss><description>&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;One of the little tools that Marcia and I use all the time when working in VFP is a pop-up menu generator that allows us to select a development environment easily and quickly. Yes, we know all about the Task Pane but personally I have always found that a real pain (pardon the pun) to use. All we wanted was a quick and easy way to be able to switch between my various environments. &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;It seemed to us that the easiest way to do that is to have a pop-up menu that could be invoked from a hot key. Then we realized that there are all sorts of situations in which it would be useful to have a pop-up menu (like right-click options on forms and controls) and so we came up with a simple menu generator to build a pop-up menu on the fly from simple tables. In order to make this generic we set up a relational structure using three tables as shown in Table 1.&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;&lt;A HREF="/photos/andykramek/images/8535/original.aspx" target=_blank&gt;&lt;IMG src="/photos/andykramek/images/8535/original.aspx" border=0&gt;&lt;/A&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;o:p&gt;&lt;FONT face=Tahoma size=2&gt;&lt;A HREF="/photos/andykramek/images/8533/original.aspx" target=_blank&gt;&lt;/A&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;These tables are all contained in their own DBC named (imaginateively) "PopMenu". So how do we use the tables?&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;Well, first we create the records in the names table, currently I have two menus defined, one named "Projects" and another named "EditOptions". The first is the one that I run when I want to select a project environment, and the second is a generic editing options popup. Of course there is no real limit to the number of menus you can define but for the purposes of this article let's stick to these two.&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;Now we need to define our menu bars for these options and this is really simple. The first thing we need is a horizontal menu divider (grouped options always look better) so that gets defined as the first bar #1 as follows:&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;cBarText&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;= "\-"&lt;BR&gt;cBarDesc&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;= "Divider Line"&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;Nothing else is needed for this entry. Now we need a bar for each option that we want to include, but for these bars we also need to include the action (and Skip For commands if needed). For my default development environment I use the following:&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;cBarText&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;= "\-"&lt;BR&gt;cBarDesc&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;= "Divider Line"&lt;BR&gt;mBarAction&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;= " CLOSE ALL&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;CLEAR&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;SET PATH TO (HOME() + ";D:\VFP90\RUN\;LIBS;FORMS;DATA;PROGS;UTILS;BMPS" )&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;SET DEFAULT TO D:\VFP90\RUN\"&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;And for the "Paste" option on the EditOptions menu we need to include a SKIP FOR condition too:&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;cBarText&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;= "Paste"&lt;BR&gt;cBarDesc&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;= "Paste From Clipboard"&lt;BR&gt;mBarAction&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;= "SYS(1500, '_MED_PASTE', '_MEDIT')"&lt;BR&gt;mSkipFor&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;= "EMPTY( _ClipText )"&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;o:p&gt;&lt;FONT face=Tahoma size=2&gt;&amp;nbsp;&lt;/FONT&gt;&lt;/o:p&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;The code to generate the tables is in the attached zip file as GenBars.prg. You can use this file as the template to set up your own version of this little utility, and Figure 1 shows how the data looks for the result:&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=figure&gt;&lt;o:p&gt;&lt;FONT face=Tahoma size=2&gt;&lt;A HREF="/photos/andykramek/images/8534/original.aspx" target=_blank&gt;&lt;IMG src="/photos/andykramek/images/8534/original.aspx" border=0&gt;&lt;/A&gt;&amp;nbsp;&lt;/FONT&gt;&lt;/o:p&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;Now all we need is the code to use this. We created this as a class that does all the work in its INIT() event so that it runs itself when called. By returning .F. on completion of the code we prevent the object from actually being instantiated so that there is no lasting impact on the system at run time. The class is based on the session base class and this means that it creates a transient data session of its own – again, to avoid any environmental impact. &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;However, this also means that the class cannot be defined visually and so, if you really wanted to have this object defined as a visual class you could have to use either a Toolbar, Form or FormSet – these being the three visual classes that can create a Private Datasession. (Note: Of the three, the formset is actually the one with the smallest memory footprint – so this may be the only time you might actually use a formset in VFP. Personally I don't care if the definition is visual or not and so I just use the Session base class).&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;The Init() method is very simple indeed, as follows:&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=codeoneline&gt;&lt;STRONG&gt;&lt;FONT face="Courier New" size=2&gt;PROCEDURE INIT( tcMenuName )&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;LOCAL lcScript&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;*** Have we got this menu definition&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;IF This.GetMenuDef( tcMenuName )&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&lt;/SPAN&gt;lcScript = This.BuildMenu( tcMenuName )&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&lt;/SPAN&gt;EXECSCRIPT( lcScript )&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;ENDIF&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;RETURN .F.&lt;BR&gt;ENDPROC&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;If the passed in menu name is found by GetMenuDef(), the BuildMenu() method is called. This creates a temporary MPR file using the data from the metadata tables and the file is then executed to display the menu. O)n completion of the menu action, the method returns false preventing the object from actually instantiating.&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;The GetMenuDef() method simply executes a SQL query, creating a cursor that contains the relevant data to generate the required menu:&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=codeoneline&gt;&lt;STRONG&gt;&lt;FONT face="Courier New" size=2&gt;LOCAL lcMenuName&lt;BR&gt;lcMenuName = UPPER( ALLTRIM( tcMenuName ))&lt;BR&gt;*** Populate the cursor&lt;BR&gt;SELECT PB.cbartext, PB.mbaraction, PB.mbarskip, PL.ilnkseq ;&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;FROM popnames PN, popbars PB, poplink PL ;&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;WHERE PB.ibarpk = PL.ilnkbarfk ;&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;AND PL.ilnknamfk = PN.imenupk ;&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;AND UPPER( PN.cmenuname ) = lcMenuName ;&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;AND NOT DELETED( 'poplink' ) ;&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;INTO CURSOR curMenu ;&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;ORDER BY PL.ilnkseq&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;*** Did we get anything?&lt;BR&gt;RETURN (_TALLY &amp;gt; 0)&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;This cursor is then used by the subordinate methods (GetBars() and GetActions()) that are called by the BuildMenu() method. Here is the script generated for the EditOptions pop-up and, as you can see it is a perfectly standard MPR file that uses ExecScript in the ON SELECTION clause to execute whatever action has been defined:&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=codeoneline&gt;&lt;STRONG&gt;&lt;FONT face="Courier New" size=2&gt;DEFINE POPUP editoptions SHORTCUT RELATIVE FROM MROW(),MCOL()&lt;BR&gt;DEFINE BAR 1 OF editoptions PROMPT [Copy]&lt;BR&gt;DEFINE BAR 2 OF editoptions PROMPT [\-]&lt;BR&gt;DEFINE BAR 3 OF editoptions PROMPT [Paste]SKIP FOR EMPTY( _ClipText )&lt;BR&gt;DEFINE BAR 4 OF editoptions PROMPT [\-]&lt;BR&gt;DEFINE BAR 5 OF editoptions PROMPT [Cut]&lt;BR&gt;ON SELECTION BAR 1 OF editoptions EXECSCRIPT( [SYS(1500, '_MED_COPY', '_MEDIT')])&lt;BR&gt;ON SELECTION BAR 3 OF editoptions EXECSCRIPT( [SYS(1500, '_MED_PASTE', '_MEDIT')])&lt;BR&gt;ON SELECTION BAR 5 OF editoptions EXECSCRIPT( [SYS(1500, '_MED_CUT', '_MEDIT')])&lt;BR&gt;ACTIVATE POPUP editoptions&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;So how do we use this class? The PopMenu.prg is in my VFP root directory and the class name is "xMenuPop". My VFP startup program, called from config.fpw, includes the following two lines:&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=codeoneline&gt;&lt;STRONG&gt;&lt;FONT face="Courier New" size=2&gt;*** Set Projects menu Hotkey and Run the Projects menu&lt;BR&gt;ON KEY LABEL CTRL+F12 NEWOBJECT( 'xMenuPop', 'D:\vfp90\popmenu.prg', NULL, 'Projects' )&lt;BR&gt;NEWOBJECT( 'xMenuPop', 'D:\vfp90\popmenu.prg', NULL, 'startup' )&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;The first assigns the projects menu to my CTRL+F12 key, and the second, which is the last line in startup program, &lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;runs it immediately. Similarly to use the EditOptions menu we simply add one line of code to the right-click of any control that we want to invoke the menu from:&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=codeoneline&gt;&lt;STRONG&gt;&lt;FONT face="Courier New" size=2&gt;NEWOBJECT( 'xMenuPop', 'D:\vfp90\popmenu.prg', NULL, 'EditOptions' )&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/P&gt;&lt;SPAN&gt;The class definition, the DBC, tables and the sample data generation program are all included in the downloadable zip file attached to this blog, I hope you find it as useful as we do.&lt;/SPAN&gt;&lt;/FONT&gt;&lt;/o:p&gt;&lt;A HREF="/photos/andykramek/images/8480/original.aspx" target=_blank&gt;&lt;/A&gt;&lt;img src="http://weblogs.foxite.com/aggbug.aspx?PostID=8532" width="1" height="1"&gt;</description><enclosure url="http://weblogs.foxite.com/andykramek/attachment/8532.ashx" length="8645" type="application/x-zip-compressed" /><category domain="http://weblogs.foxite.com/andykramek/archive/category/1062.aspx">VFP How-To</category></item><item><title>Our Vancouver Devteach Experience</title><link>http://weblogs.foxite.com/andykramek/archive/2009/06/13/8507.aspx</link><pubDate>Sat, 13 Jun 2009 11:39:00 GMT</pubDate><guid isPermaLink="false">8827bd1c-7596-4a8f-b0de-f59ce9ede522:8507</guid><dc:creator>andykr</dc:creator><slash:comments>6</slash:comments><comments>http://weblogs.foxite.com/andykramek/comments/8507.aspx</comments><wfw:commentRss>http://weblogs.foxite.com/andykramek/commentrss.aspx?PostID=8507</wfw:commentRss><description>&lt;P class=MsoNormal&gt;&lt;FONT face=Tahoma size=2&gt;You may have noticed the blog has been quiet for a couple of weekends. This is because Marcia and I have been out of town to attend the DevTeach conference in Vancouver. As regular readers of this blog will know, I have for many years extolled the virtues of conference attendance and encouraged people to ‘put their money where their mouths are’ and attend conferences. &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;FONT face=Tahoma size=2&gt;On occasion it has been suggested that this was an easy position for me to take because I have been, for more than ten years, a speaker at conferences and haven’t actually had to put my hand in my own pocket to attend. The fallacy with that argument is, of course, that preparing a conference session takes many hours of effort – it’s not just a case of throwing a few slides together and dummying up a few code samples, there's a paper to be written to support it all, and many hours of rehearsal review and rework. My own estimate is that a typical 75 minute session requires 80 hours of work, and a 4 hour “pre-con” can take several weeks! So while there&amp;nbsp;may be no direct cash payment, the cost in terms of lost time (and income) can be considerable.&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;What is the point of this? Well, as you may know Marcia and I decided last year that we were not going to speak at conferences any more and that SW Fox in October 2008 would be the last time we would submit sessions. As it happens, Marcia was not selected for that conference anyway – but either way it was our last as speakers. So this year we decided to attend DevTeach in Vancouver. We might have chosen the Montreal venue last December (it’s a lot closer for us) but we were in England visiting family and so couldn’t make it. Besides, neither of us had ever been to British Columbia and so we decided to add a couple of days up front of the conference and have a little vacation time in Vancouver too.&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;Why DevTeach? Well, while Marcia does have some existing clients still in VFP, all her new work is in .NET and I have been working almost exclusively in SQL Server for several years now anyway. Since admission to DevTeach (primarily .Net focused) also included admission to SQLTeach (SQL server focused) it was an obvious choice. Not cheap mind you, but obvious. It was also our first non-FoxPro conference for several years. The cash investment alone (fees, flights and hotel), for the two of us, ran close to $5,000.00 – not an inconsiderable sum in these tough economic times.&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;So, how did we fare? Well, I have to be honest and say that I did not really enjoy the experience. In the sessions that I attended, there were some good speakers who gave solid presentations and imparted a lot of good information. A definite plus there! However there were also some truly appalling speakers who, in my opinion should not have been allowed in front of a paying audience. I am not saying that they were not knowledgeable, or that their topics were uninteresting, but their presentation technique was so bad that it was painful. &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;No names, no pack drill, but I actually walked out of one session (from the front row where I had been sitting). The speaker began 15 minutes late (he kept waiting [hopefully] for ‘stragglers’), then spent the next 10 minutes on his personal life history and eventually began talking without agenda, structure or plan. After another five minutes I realized that I had no clue what he was talking about and, much worse, felt that he did not know either. What was worse he actually asked me later how I thought he had done! &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;OK, so not everyone is a natural public speaker in the Jim Booth, Ted Roche or (putting false modesty aside) even Andy Kramek, mode. However, there are simple rules that, when followed, will allow anyone to give a competent and respectable presentation and what I am talking about is people who failed to&amp;nbsp;follow these basic rules. This, to my mind shows a cavalier disregard for their audience who have, after all, paid good money, and also invested their time to come and hear them.&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;So there were a couple of sessions that I thought were bad. Why am I whining, have I never seen a bad session before? Of course I have (and to be honest, I’ve probably given some in my time). That isn’t why I didn’t enjoy the experience. Taken overall, the technical content (from my SQL-oriented perspective) was at least ‘Good’ and verging on ‘Above Average’. But, as I have so often said, the technical content of the sessions is actually the least important part of any conference. &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;What is more important is the ability to mingle with like-minded people who share common problems and issues and exchange knowledge and views – i.e. Networking. This is the aspect in which I felt that DevTeach failed miserably. The question is, why? There could be several reasons, and I am not really competent to assess them (after all I wasn’t involved in planning the conference). &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;The first is that it was unquestionably a very “local” conference. Most of the attendees seemed to be either from the Vancouver area, or to have relations in the Vancouver area. Consequently at the end of sessions they went home. Result – no-one was left in the hotel and there was no real opportunity to network – people were either in sessions, or simply not there.&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;Second, the hotel layout made it a poor conference venue. The session rooms were split between two floors. Registration, the .Net and related Tracks, the Trade Show and breakfast/coffee between sessions were all on the third floor, while the Keynote, SQL tracks and lunch were on the second. Doesn’t make for a good mixing environment! The only bar was “L” shaped (and quite small) and&amp;nbsp;even in the lobby there was really nowhere to sit and chat (a couple of sofas was about it). Taken all in all it is hard to imagine a less suitable venue for encouraging people to get together in groups.&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;Third, there was no real conference material. Upon registration we were offered a tote bag and a two page conference schedule and that was the sum total of everything. There was no binder&amp;nbsp;describing the hotel, environs (where should we go for dinner outside the hotel?) and conference facilities (general timings, location of meals, where to look for notices etc) and, initially, not even paper and pens (though they did appear on Day 2 I believe). There was also no conference CD (though I did get an Email telling me that session materials would be downloadable from 6/18 – a week AFTER the conference ended but that the speakers slide decks were available for download immediately). Not really the kind of supporting material I expected or am used to.&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;Fourth communications in general were weak, in fact, on reflection, they were&amp;nbsp;nonexistent! For example, someone actually complained, in conversation with Marcia on the afternoon of Day 2, that there was ‘not even lunch’&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;provided. They didn’t realize that lunch was being served on the floor below – but then how would they? There was no information provided at registration, no signs, and no announcements – in sessions or anywhere else - as to what facilities were provided, or where they were located. Worse, there was no indication outside the rooms telling you what sessions were going being held. A simple chart for each room with Topic, Speaker and Time would have been very helpful. I ended up in the wrong room twice and had to do a hurried exit and re-entry when I realized that the topic being presented wasn't what I thought it was going to be. Little things, but…&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;Fifth, the Keynote. Ah how we love those keynote presentations. In this case the keynote was a demo of Visual Studio Dot_Next. Of course this was of great interest (NOT!) to us SQL Server people. If you are going to combine two conferences, why not have two keynotes? After all the speaker was&amp;nbsp;just one of the regular conference speakers – not an imported celebrity (as at Russ Swall’s “Essential…” conferences), or even an Industry (i.e. Microsoft) expert as at the German Devcon or SW Fox. Couldn't a SQL Server speaker have given a SQL Keynote?&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;Sixth, out of session activities. This was the last nail in the coffin for me – there were none! Oh yes, the Vancouver IT community hosted a ‘free beer’ party on the Monday night. Unfortunately it was not in the Conference Hotel, nor even close, and unless you were a local (oh, hang on, most attendees were!) it was not an easy task to figure it all out. The only information was in an Email – no announcements, no organized travel (how about sign-up sheets for organizing groups to share Taxi costs….?), no information or directions on how to get there at the conference desk (which was unmanned most of the time anyway). But then this was nothing to do with the conference, and was, obviously, aimed at the local residents. &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;Apart from this there was nothing. No “mixers”, no “Show and Tell” evening sessions, no “mitt bier” evening sessions, just an empty hotel bar and lobby.&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;For the first time in more than 10 years attending conferences I was back in my room by 8:00pm every night.&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;Seventh, there was no passion. I attended 15 sessions over three days and I didn’t see one where I felt that the speaker was really passionate about their topic. For those of you who have ever seen Cathy Poutney, or Doug Hennig, or Jim Booth, Marcia or I (to name but a few) give a session, you will know what I mean when I talk about speakers with passion. The impression I had was that, for the DevTeach speakers, it was ‘&lt;I&gt;just another day at the office&lt;/I&gt;’. Most of them made it clear that the sessions were not new and&amp;nbsp;the feeling I got was that it was all a bit of a chore for them.&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;Finally, one of the things that I have always hated was there, in spades! This conference, more than any other I can remember, suffered from the “Inaccessible Speaker” syndrome. I do not recall, in three days, seeing any speaker outside of a session room – unless they were traveling in packs as when waiting to go out to their speaker dinner. &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;Trying to go and speak to a speaker is, for most attendees, pretty intimidating. When you have to interrupt a group of speakers who are interacting with each other it is positively frightening. At lunch, what attendee is going to go and sit down at a table of 6 speakers earnestly engaged in high level discussions of (presumably) great weight and import? The problem was compounded at DevTeach because most of the speakers didn’t wear their name tags outside of sessions (so unless you already know them you don’t even know that they are speakers) and so the only the time the speakers were really visible was in sessions. &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT size=2&gt;&lt;FONT face=Tahoma&gt;I know that Marcia and I (and the vast majority of the VFP Speakers, encouraged strongly, and in some cases even required, by VFP Conference Organizers) always made a conscious effort to be approachable, and accessible to attendees. We deliberately tried &lt;I&gt;not&lt;/I&gt; to sit at “speaker tables” at lunch, and to ensure that we were always in the bar/lounge after sessions (an easy task for us &lt;img src="/emoticons/emotion-1.gif" alt="Smile [:)]" /&gt;&lt;/FONT&gt;&lt;FONT face=Tahoma&gt;). Alas that culture does not seem to have carried over to DevTeach despite its original roots in the VFP community.&lt;/FONT&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;My conclusion? Well, as I said, technically the conference was adequate. Most presentations were competent, some were good and only a few were really poor. I did learn stuff; some of it will be directly useful to me, some is potentially useful and some just improved my general background knowledge and understanding. I have no complaints in this respect.&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;However, when you factor in the ancillary costs (travel time, lost work time, flight, accommodation and meal costs) the return on investment was very poor indeed. So while for a local resident it was probably cheap training, for us it was a very expensive, unstructured, training course with only moderate value. I left knowing no-one whom I did not know before I arrived (though it was certainly nice to see a few familiar faces among the speakers) and with the total number of business cards in my case the same as on the first day.&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;That was most the disappointing thing. The reality is that the whole conference felt like a commercial training course (where you sit in the classroom with a bunch of people you don’t know and with whom you have no real opportunity for communication and, at the end of the day, you all go your separate ways). From that perspective the training course is actually better value – at least there is a consistent and clear learning program.&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;Of course, this is just my opinion and others who were there may disagree with me but, as my&amp;nbsp;readers will already know, I always try to tell it like I see it and this is how I saw it. I still believe that conferences can be great value, I just don’t think that DevTeach Vancouver was one of them.&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;So will we go to another DevTeach? Probably not. To another conference? Probably (right now I am not sure when or where, but I expect that we will) because despite this poor experience I still believe that GOOD conferences amply repay the investment you make in them.&lt;/FONT&gt;&lt;o:p&gt;&lt;FONT face=Tahoma size=2&gt;&amp;nbsp;&lt;/FONT&gt;&lt;/o:p&gt;&lt;/P&gt;&lt;img src="http://weblogs.foxite.com/aggbug.aspx?PostID=8507" width="1" height="1"&gt;</description><category domain="http://weblogs.foxite.com/andykramek/archive/category/1063.aspx">Comment/Opinion</category><category domain="http://weblogs.foxite.com/andykramek/archive/category/1080.aspx">Non-Technical</category></item><item><title>The Cost/Time/Content Triangle</title><link>http://weblogs.foxite.com/andykramek/archive/2009/05/30/8482.aspx</link><pubDate>Sat, 30 May 2009 14:36:00 GMT</pubDate><guid isPermaLink="false">8827bd1c-7596-4a8f-b0de-f59ce9ede522:8482</guid><dc:creator>andykr</dc:creator><slash:comments>3</slash:comments><comments>http://weblogs.foxite.com/andykramek/comments/8482.aspx</comments><wfw:commentRss>http://weblogs.foxite.com/andykramek/commentrss.aspx?PostID=8482</wfw:commentRss><description>&lt;P class=bodytext&gt;&lt;FONT size=2&gt;&lt;FONT face=Tahoma&gt;The Cost/Time/Content Triangle is a simple way of representing the rather complex (not to say confusing) interaction between the three key components of any IT project - Cost, Time and Scope.&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;It provides a mechanism for visualising the effect of changes in any parameter and provides managers with a tool for quickly assessing the impact (and hence the risk) of changes to any one component on the others.&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;The Cost/Time/Content Triangle is a graphical triangle whose three sides each represent a measure for an element of the project. By setting an appropriate scale for each side we can define the limits for the three key elements of any project. This is probably easier followed with an example, so let's consider a simple project that, based on the defined scope (the 'Content' element) has been estimated as comprising 4 key parts totaling 20 weeks of work as follows:&lt;/FONT&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;
&lt;DIV class=bodytext&gt;&lt;FONT face="Courier New" size=2&gt;Main Data Screens&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;12 Weeks&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&lt;/SPAN&gt;(55% of the Content)&lt;/FONT&gt;&lt;/DIV&gt;
&lt;LI&gt;
&lt;DIV class=bodytext&gt;&lt;FONT face="Courier New" size=2&gt;System Admin Functions&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;3 Weeks&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&lt;/SPAN&gt;(15% of the Content)&lt;/FONT&gt;&lt;/DIV&gt;
&lt;LI&gt;
&lt;DIV class=bodytext&gt;&lt;FONT face="Courier New" size=2&gt;Standard Reports&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;4 Weeks&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&lt;/SPAN&gt;(25% of the Content)&lt;/FONT&gt;&lt;/DIV&gt;
&lt;LI&gt;
&lt;DIV class=bodytext&gt;&lt;FONT face="Courier New" size=2&gt;User Definable Configuration&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;1&lt;/SPAN&gt; Weeks&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&lt;/SPAN&gt;(&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;5% of the Content)&lt;/FONT&gt;&lt;/DIV&gt;&lt;/LI&gt;&lt;/UL&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;The percentages refer to the contribution each part makes to the whole project. Having got the Content, and Time elements we can calculate the Cost using our standard formula which, in this example is very simplistic and based on a standard Development Cost of $25 per man-hour + 25%, giving us a total cost of $25k:&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;&lt;STRONG&gt;&lt;EM&gt;20 * 40 hrs = 800hrs @ $25 = $20,000 + 25% = $25,000&lt;/EM&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT size=2&gt;&lt;FONT face=Tahoma&gt;(Note: this is the estimated internal cost of undertaking the project, not the price we intend to charge to the customer; that is a wholly different algorithm &lt;img src="/emoticons/emotion-1.gif" alt="Smile [:)]" /&gt;&lt;/FONT&gt;&lt;FONT face=Tahoma&gt;)&lt;/FONT&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;&lt;SPAN&gt;We can now construct our initial Cost/Time/Content Triangle -&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;using these values to generate the scales.&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;The objective is to get a balance so we want to get our initial estimate centered in the triangle. As you will recall from your Geometry classes you find the center of a triangle by drawing a line from each vertex to the mid-point of the opposing side. Where the three lines intersect is the center.&lt;SPAN&gt;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;Where each baseline intersects the opposite side of the triangle we set the appropriate estimated value; i.e. Cost = £25k, Time = 20 Weeks and Scope = 100% as shown at Figure 1.&lt;/SPAN&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;&lt;SPAN&gt;&lt;A href="/photos/andykramek/images/8478/original.aspx" target=_blank&gt;&lt;IMG src="/photos/andykramek/images/8478/original.aspx" border=0&gt;&lt;/A&gt;&lt;/SPAN&gt;&lt;/FONT&gt;&lt;/P&gt;&lt;FONT face=Tahoma size=2&gt;&lt;SPAN&gt;
&lt;P class=bodytext&gt;Scope is, of course, the main driving force behind any project and in overall, will largely dictate both the project time scale, and the cost.&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;After all, the more you have to do, the longer it will take, and the more it will cost!&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;However, not all elements of a project scope will have equal weight, and invariably the actual scope consists of two parts, the 'Must Be Done list' and the 'WIBNIF (Wouldn't It Be Nice If…) list".&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;The key to assessing scope is to determine what percentage of the project each element will account for. &lt;/P&gt;
&lt;P class=bodytext&gt;Note that, thus far, our Content and Time scales are directly related (10% of the content = 10% of the time) which is what we would expect since our time estimate is based directly on the content. Cost is also directly related to content at this point because it too is derived solely from the content estimate and all we have considered to date is the "must-do" components. In fact, there were (as there always are) a couple WIBNIFs that we estimated as follows:&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;
&lt;DIV class=bodytext&gt;&lt;FONT face="Courier New"&gt;Ad-Hoc Query Screen&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/SPAN&gt;2 Weeks&lt;/FONT&gt;&lt;/DIV&gt;
&lt;LI&gt;
&lt;DIV class=bodytext&gt;&lt;FONT face="Courier New"&gt;Ad-Hoc Report Generator&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;3 Weeks&lt;/FONT&gt;&lt;/DIV&gt;&lt;/LI&gt;&lt;/UL&gt;
&lt;P class=bodytext&gt;Moreover our internal assessment is that both the "User Definable Configuration" and the "Standard Reports" are really WIBNIFs too (the basic project could be done as "Phase 1" with just Screens and Admin functions) . So our project scope could drop as low as 70% of the base estimate, or go as high as 120%. These are, therefore the limits for our "Content" scale, which we define as a linear scale. Similarly our time, based on this content, could be as short as 14 weeks, and go out to as much as 25 weeks and we should add an appropriate time scale that covers this range, with some additional margin at each end (we'll see why later). Draw lines from the Content and Time vertices to each point on the relevant scale - this gives us the CTC Triangle shown in Figure 2:&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;A href="/photos/andykramek/images/8479/original.aspx" target=_blank&gt;&lt;IMG src="/photos/andykramek/images/8479/original.aspx" border=0&gt;&lt;/A&gt;&lt;/P&gt;&lt;FONT face=Tahoma size=2&gt;&lt;SPAN&gt;
&lt;P class=bodytext&gt;To complete the triangle we need to add the Cost scale. To get this we need to calculate the cost of the project given various alternate elapsed time scales – remember that we have based the estimate on the defined scope. Changing the scope will give us a different cost for a given time scale. Table 1 shows how this works:&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;&lt;SPAN&gt;&lt;A href="/photos/andykramek/images/8480/original.aspx" target=_blank&gt;&lt;IMG src="/photos/andykramek/images/8480/original.aspx" border=0&gt;&lt;/A&gt;&lt;/SPAN&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;&lt;SPAN&gt;and using this as the basis for our time scale we get the final triangle (Figure 3):&lt;/SPAN&gt;&lt;/FONT&gt;&lt;/P&gt;&lt;FONT face=Tahoma size=2&gt;&lt;SPAN&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;&lt;SPAN&gt;&lt;A href="/photos/andykramek/images/8481/original.aspx" target=_blank&gt;&lt;IMG src="/photos/andykramek/images/8481/original.aspx" border=0&gt;&lt;/A&gt;&lt;/SPAN&gt;&lt;/FONT&gt;&lt;/P&gt;&lt;FONT face=Tahoma size=2&gt;&lt;SPAN&gt;
&lt;P class=bodytext&gt;All of our estimates relate to the original estimated scope and a time scale of 20 weeks, so now we can assess the impact of changes by simply reading off the appropriate scale. To ascertain the cost of doing the project in only 15 weeks, we do the following:&lt;SPAN&gt;&lt;SPAN&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;
&lt;DIV class=bodytext&gt;&lt;SPAN&gt;&lt;SPAN&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;Draw a line from the Cost/Content vertex to the 15 Week point on the Time axis&lt;SPAN&gt;&lt;SPAN&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/DIV&gt;
&lt;LI&gt;
&lt;DIV class=bodytext&gt;&lt;SPAN&gt;&lt;SPAN&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;Draw a line from the Time/Content vertex, through the intersection between our new line and the 100% Scope line and extend it to the cost axis to get the revised cost&lt;/DIV&gt;&lt;/LI&gt;&lt;/UL&gt;
&lt;P class=bodytext&gt;The value is about £30K, which equates to £2k per week and is equivalent to 24 weeks at the standard rate - doing things quicker always costs more!&lt;/P&gt;
&lt;P class=bodytext&gt;Similarly, we can estimate the reduction in cost if the project can be spread over a longer period. For example if we took 24 weeks for the project it cost only £22.5k.&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;Although this equates to reducing our weekly rate to about $940, we still only have 20 weeks worth of work to do and (hopefully) would be able to employ our developer gainfully elsewhere to make up the difference.&lt;/P&gt;
&lt;P class=bodytext&gt;Now what about those WIBNIFs? If there are no other changes in the original scope, but our client also wants the Report Generator (estimated at 3 weeks work – or an extra 15% on the original scope) we can immediately see our options:&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;
&lt;DIV class=bodytext&gt;To preserve the original cost, the project will have to take 6 weeks longer, reducing our cost to $960 per week, but gaining an additional 3 weeks of time in which to carry out the extra work&lt;/DIV&gt;
&lt;LI&gt;
&lt;DIV class=bodytext&gt;To deliver on the original time scale, the cost will have to go up to just over $31,000. Not only is this increasing the amount of work to fit in, but it is also increasing our risk of failure&lt;SPAN&gt;&lt;SPAN&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;/DIV&gt;
&lt;LI&gt;
&lt;DIV class=bodytext&gt;&lt;SPAN&gt;&lt;SPAN&gt;&lt;/SPAN&gt;&lt;/SPAN&gt;Propose a compromise. Increase the project time line by 4 weeks, and the cost to $28,000. This gives the a cost for the report generator of $3000 but gains us an extra week over estimate in which to do the additional work&lt;/DIV&gt;&lt;/LI&gt;&lt;/UL&gt;
&lt;P class=bodytext&gt;In precisely the same way we can rapidly asses the effect of scope changes. If the decision is made that User configuration is not required after all, but that an Ad-Hoc Query screen is, the net result is to change the scope by +5%, the estimate can now be revised as either:&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;
&lt;DIV class=bodytext&gt;Deliverable in the same time at an extra cost of £1.5k&lt;/DIV&gt;
&lt;LI&gt;
&lt;DIV class=bodytext&gt;Delivered for the same cost, 2 weeks later&lt;/DIV&gt;&lt;/LI&gt;&lt;/UL&gt;
&lt;P class=bodytext&gt;The triangle is really useful when you need, as in the example above, to cope with changes in scope (of the upward variety) at short notice and need to be sure that you can not only cover the costs, but can explain to the client why it is going to cost so much more. However, as you will no doubt have realized by now, the key to the triangle is the initial setting of the scales. This is only as good as the information which you use to generate your estimates, and the relationship which you determine between cost and time.&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;SPAN&gt;&lt;/SPAN&gt;Typically the cost relationship is not as simple as shown in this example – if we really wanted to increase the scope by 15% and deliver in the original time scale our poor developer would be working over 60 hours per week for five months (not a good scenario!). The reality is that we would need additional resources and that will change the cost relationship. Adding a developer is not simply a question of increasing the hourly billing rate, it also increases the &lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;fixed costs; each developer needs equipment, Employment, Administration and Management Overhead costs all increase too. Similarly if the workload falls below 40 hours a week it is not always possible to re-deploy the resource without losing income. &lt;/P&gt;
&lt;P class=bodytext&gt;The reality is that the relationship between cost and time is typically a stepped relation, rather than linear. In fact, getting this relationship right is one of the hardest parts of any project estimate, but once you have it, the CTC becomes a powerful tool for assessing risk and managing change.&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;/SPAN&gt;&lt;/FONT&gt;&amp;nbsp;&lt;/P&gt;&lt;/SPAN&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/FONT&gt;&lt;img src="http://weblogs.foxite.com/aggbug.aspx?PostID=8482" width="1" height="1"&gt;</description><category domain="http://weblogs.foxite.com/andykramek/archive/category/1080.aspx">Non-Technical</category><category domain="http://weblogs.foxite.com/andykramek/archive/category/1112.aspx">Project Management</category></item><item><title>Finding the length of a string in a specific font</title><link>http://weblogs.foxite.com/andykramek/archive/2009/05/18/8455.aspx</link><pubDate>Mon, 18 May 2009 21:49:00 GMT</pubDate><guid isPermaLink="false">8827bd1c-7596-4a8f-b0de-f59ce9ede522:8455</guid><dc:creator>andykr</dc:creator><slash:comments>3</slash:comments><comments>http://weblogs.foxite.com/andykramek/comments/8455.aspx</comments><wfw:commentRss>http://weblogs.foxite.com/andykramek/commentrss.aspx?PostID=8455</wfw:commentRss><description>&lt;P class=firstparagraph&gt;&lt;FONT face=Tahoma size=2&gt;One of the things that I often found myself doing when working in VFP was trying to decide how large a textbox had to be in order to accommodate the maximum length of the data that was permitted for its underlying source given a specific font and size. As we all know, unless you stick to non-proportional fonts, the number of pixels required to display a string can vary dramatically depending on the font. &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=firstparagraph&gt;&lt;FONT face=Tahoma size=2&gt;For example, the string "Andy Kramek" actually requires 88 pixels when displayed in Courier New, 10 point font. However, change the font to Arial and you need only 79, unless you make it Bold in which case you need 85. But if you change the font to Verdana then you need 85 for the plain text and 96 for Bold.&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=firstparagraph&gt;&lt;FONT face=Tahoma size=2&gt;Given the multiplicity of fonts available you could be forgiven for thinking that determining exactly how much room to allow would be a fairly normal thing to want to do and therefore could reasonably expect VFP would have native function that would tell you. Unfortunately, it doesn't. Now, at this point you may be thinking that this isn't really very important. After all, VFP sizes text boxes automatically using the underlying field definition when you drag a table field from the data environment, or project manager, on to a form.&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=firstparagraph&gt;&lt;FONT face=Tahoma size=2&gt;Unfortunately the native sizing algorithm is not very accurate! For example, dragging a column defined as VARCHAR(50) to a form creates a textbox 357 pixels wide when the font is defined as Arial 9pt. However, the actual size required for a character string of 50 characters (using the average character width for this font) is only about 250 pixels! Even worse, the sizing has nothing to do with the font defined for the form and, even when you change the form's font in the underlying class, VFP simply carries on sizing controls for its default (Arial 9) font. Now that is a lot of wasted space on a form and, even worse, can be confusing to users because the size of the textbox is not a very reliable guide to the amount of text that it can hold. &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=firstparagraph&gt;&lt;FONT face=Tahoma size=2&gt;We also have to consider the situation in which we need to concatenate data which is stored in individual fields together (to display a ‘full’ name for example, or a ‘City, State and Zip’ line for an address). VFP cannot help us here, and so the normal solution is to decide, empirically, how big to make the textbox. There are actually two issues involved here. The first is to determine the number of characters which we want the textbox to be able to handle and the second is to work out the amount of space that number of characters will need given a specific font setting. &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=subhead2&gt;&lt;STRONG&gt;&lt;FONT face=Arial&gt;Concatenating Fields&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/P&gt;
&lt;P class=firstparagraph&gt;&lt;FONT face=Tahoma size=2&gt;The default behavior of VFP when concatenating fields is to simply add together the defined lengths and create a new field whose width matches the total. Consider a set of name fields – let’s assume ‘FirstName’ is set up as 25 characters and ‘LastName’ as 40. Now VFP will always assume the worst case scenario when concatenating these fields and will generate a result field with a width of 65 characters (just run a SQL select that combines two character columns and you will see that this is indeed the case).&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;However, this does not represent the reality of the situation. Using the sample data that ships with VFP, we find that the average length of first names is actually 5.85 characters, and for last names it is only 6.83. So really we&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;could use a textbox capable of showing, say, 25 characters and be confident that we would handle the vast majority of cases. While it is impossible to be prescriptive about this sort of concatenation it is easy enough to set a target size that reflects the typical results.&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;So the number of characters is defined either by the underlying data source directly, or by some reasonable guess based on the required display.&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=subhead2&gt;&lt;STRONG&gt;&lt;FONT face=Arial&gt;But what about the font?&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/P&gt;
&lt;P class=firstparagraph&gt;&lt;FONT face=Tahoma size=2&gt;While we may be able to determine how many characters we want to display easily enough, as we have already seen, the size of the textbox that we need will depend upon the chosen font. In fact it is not only a question of the size of the font, but also of the style (e.g. Bold or Italic) and the font face. Even different fixed pitch fonts vary in their space requirement for individual characters.&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;Fortunately VFP does have functions, or more accurately a single, heavily overloaded, function to get this information. FONTMETRIC() can access twenty – yes, that’s right, 20 – different font attributes depending on the input parameter. I find it hard to remember the calling options for even one or two of these and even though IntelliSense makes it a little easier we still need to retrieve individual attributes one at a time. Since each returns the value for a single character, the whole process gets tedious, not to say messy, when you are trying to retrieve several attributes for the same string. &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=subhead2&gt;&lt;STRONG&gt;&lt;FONT face=Arial&gt;The SizeStr() Function&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/P&gt;
&lt;P class=firstparagraph&gt;&lt;FONT face=Tahoma size=2&gt;The solution is to create a simple wrapper function to determine the elements of the sizing that we are interested in. While we could grab all 20 attributes, in practice there are really only&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;three things we are interested in. The maximum length that the specified string could possibly require, the average and the exact lengths of the specified string. With this information we can make a sensible decision based on how typical our test string is, and how much variance we need to allow for in sizing our textbox. The &lt;I&gt;SizeStr()&lt;/I&gt; function displays the results in a little modeless form (Figure 1) and returns the exact length of the specified string in the specified font.&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=figure&gt;&lt;o:p&gt;&lt;FONT face=Tahoma size=2&gt;&amp;nbsp;&lt;A HREF="/photos/andykramek/images/8453/original.aspx" target=_blank&gt;&lt;IMG src="/photos/andykramek/images/8453/original.aspx" border=0&gt;&lt;/A&gt;&lt;/FONT&gt;&lt;/o:p&gt;&lt;/P&gt;
&lt;P class=figurecaption&gt;&lt;EM&gt;&lt;FONT face=Arial size=2&gt;Figure 1: The SizeStr() Result Screen (the function actually returns the Exact Width)&lt;/FONT&gt;&lt;/EM&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;The exact length of a string is actually calculated as a two step process. First we use the Visual FoxPro &lt;I&gt;TxtWidth()&lt;/I&gt; function to determine the number of “Average Character Equivalents” in the test string when the specified font is taken into account. This calculated value allows us to treat proportional fonts as if they were actually fixed and so simplify the ensuing calculation. Thus, while a string containing 5 letters will always return 5 with a fixed pitch font, a proportional font will give different results depending on both the font itself, and any additional styles that have been defined. Table 1 shows the results from TxtWidth() for the string “This is a Test String”.&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;EM&gt;&lt;FONT face=Arial size=2&gt;&lt;A HREF="/photos/andykramek/images/8454/original.aspx" target=_blank&gt;&lt;IMG src="/photos/andykramek/images/8454/original.aspx" border=0&gt;&lt;/A&gt;&lt;/FONT&gt;&lt;/EM&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;Having determined how many average character equivalents we have in our text string we can simply multiply this number by the Average Character size (&lt;I&gt;FontMetric(6)&lt;/I&gt;) to get the exact size which is returned by the function. To get the Maximum size for a string containing the specified number of characters we then multiply by the Maximum Character Size (&lt;I&gt;FontMetric(7)&lt;/I&gt;). Finally we multiply the Average Character size by the actual number of characters in the test string to determine the average length of the string.&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=subhead2&gt;&lt;STRONG&gt;&lt;FONT face=Arial&gt;But what about the height?&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/P&gt;
&lt;P class=firstparagraph&gt;&lt;FONT face=Tahoma size=2&gt;Unfortunately (again) VFP does not help us to decide how high a textbox should be when we change its font. &lt;I&gt;FontMetric()&lt;/I&gt; will give us the actual height of the character in a given font, but we also need to allow for border height when sizing a textbox. In &lt;I&gt;SizeStr()&lt;/I&gt; I do this by adding a factor of 28.125% of the character height in the specified font. This value has no theoretical basis, but, by inspection appears to be about right! The resulting value is shown on the display form.&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;&lt;STRONG&gt;&lt;EM&gt;The function is attached to this article and can be downloaded below&lt;/EM&gt;&lt;/STRONG&gt;. It accepts the following parameters:&lt;/FONT&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;
&lt;DIV class=bodytext&gt;&lt;FONT size=2&gt;&lt;FONT face=Tahoma&gt;tuInStr&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; [Required]&amp;nbsp; &lt;/SPAN&gt;The input string to be tested&lt;/FONT&gt;&lt;/FONT&gt;&lt;/DIV&gt;&lt;/LI&gt;
&lt;LI&gt;
&lt;DIV class=bodytext&gt;&lt;FONT size=2&gt;&lt;FONT face=Tahoma&gt;tcFName&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; [Optional (defaults to "Arial")]&amp;nbsp; &lt;/SPAN&gt;The name of the Font&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;/DIV&gt;&lt;/LI&gt;
&lt;LI&gt;
&lt;DIV class=bodytext&gt;&lt;FONT size=2&gt;&lt;FONT face=Tahoma&gt;&lt;SPAN&gt;&lt;/SPAN&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;FONT size=2&gt;&lt;FONT face=Tahoma&gt;tnFSize&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;[Optional (defaults to 9 point)]&amp;nbsp; &lt;/SPAN&gt;The font size (in points)&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/SPAN&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;/DIV&gt;&lt;/LI&gt;
&lt;LI&gt;
&lt;DIV class=bodytext&gt;&lt;FONT size=2&gt;&lt;FONT face=Tahoma&gt;tcFStyle&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;[Optional] &lt;/SPAN&gt;Font Style codes (Bold, Italic, Underline etc)&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;/DIV&gt;&lt;/LI&gt;&lt;/UL&gt;
&lt;P class=bodytext&gt;&lt;FONT size=2&gt;&lt;FONT face=Tahoma&gt;&lt;SPAN&gt;&lt;/SPAN&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;FONT face=Tahoma size=2&gt;The function pops up the modeless window shown at Figure 1, and when the window is closed returns the exact length of the input string in pixels. However, the parameters displayed in the form are all saved to an object in the function and it is simple to suppress the form and return the object directly.&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=subhead3&gt;&lt;FONT face=Arial size=2&gt;&lt;STRONG&gt;Example&lt;/STRONG&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=codefirstline&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;lnLen = SizeStr( “This is a test string”, “Garamond”, 12, “BI” )&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;STRONG&gt;&lt;FONT face="Courier New" size=2&gt;? lnLen&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;&amp;amp;&amp;amp; Returns 131&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;Hopefully you will find this little function as useful as I do.&lt;/FONT&gt;&lt;/P&gt;&lt;img src="http://weblogs.foxite.com/aggbug.aspx?PostID=8455" width="1" height="1"&gt;</description><enclosure url="http://weblogs.foxite.com/andykramek/attachment/8455.ashx" length="1847" type="application/x-zip-compressed" /><category domain="http://weblogs.foxite.com/andykramek/archive/category/1062.aspx">VFP How-To</category></item><item><title>Modeling Lookup Tables</title><link>http://weblogs.foxite.com/andykramek/archive/2009/05/10/8419.aspx</link><pubDate>Sun, 10 May 2009 10:59:00 GMT</pubDate><guid isPermaLink="false">8827bd1c-7596-4a8f-b0de-f59ce9ede522:8419</guid><dc:creator>andykr</dc:creator><slash:comments>6</slash:comments><comments>http://weblogs.foxite.com/andykramek/comments/8419.aspx</comments><wfw:commentRss>http://weblogs.foxite.com/andykramek/commentrss.aspx?PostID=8419</wfw:commentRss><description>&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;One topic that caught my eye on the Foxite forum recently was a discussion of how to implement Lookup Tables. Like many such questions it provoked strong opinions and definitive statements of the "Thou shalt ...." type (i.e. the type of "dogmatic logic" based on opinion, instead of&amp;nbsp;reasoned argument). The question centers on whether it is better to use a single table for all lookup data (the so-called "One True Lookup Table [OTLT] approach) or to have separate tables for each different set of lookup data that is required. As with many such data The usually quoted conventional wisdom is that:&lt;/FONT&gt;&lt;/P&gt;
&lt;BLOCKQUOTE dir=ltr&gt;
&lt;P class=bodytext&gt;&lt;EM&gt;&lt;FONT size=2&gt;&lt;FONT face=Arial&gt;Developers like the idea of a single lookup table, but Database Administrators do not&lt;/FONT&gt;&lt;/FONT&gt;&lt;/EM&gt;&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;and the reason why developers like them is that they simplify the creation of maintenance screens and allow generic code to be written to handle the population of controls in the user interface. Conversely the reasons why DBAs do not like them are that (according to an entry in "Systems Engineering and RDBMS" at http://decipherinfosys.wordpress.com/2007/02/01/otlt-one-true-lookup-table/ ) &lt;/FONT&gt;&lt;/P&gt;
&lt;BLOCKQUOTE dir=ltr&gt;
&lt;P class=bodytext&gt;&lt;I&gt;&lt;FONT size=2&gt;&lt;FONT face=Tahoma&gt;&lt;SPAN&gt;This is an OO design pushed into the database tier and poses many problems including :&lt;/SPAN&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;/I&gt;&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;UL&gt;
&lt;LI&gt;
&lt;DIV class=noteoneline&gt;&lt;FONT face=Arial size=2&gt;&lt;EM&gt;Not being able to force foreign keys (so your data integrity is compromised right there),&lt;/EM&gt;&lt;/FONT&gt;&lt;/DIV&gt;
&lt;LI&gt;
&lt;DIV class=noteoneline&gt;&lt;FONT face=Arial size=2&gt;&lt;EM&gt;Not being able to use the right data-types since everything has to be a string in order to accomodate all data-type values (this itself can give rise to bad data issues since a numeric(4,2) constraint cannot be enforced –&amp;gt; thus domain integrity is out of the door,&lt;/EM&gt;&lt;/FONT&gt;&lt;/DIV&gt;
&lt;LI&gt;
&lt;DIV class=noteoneline&gt;&lt;FONT face=Arial size=2&gt;&lt;EM&gt;Because of (b), the length for the string column will be huge,&lt;/EM&gt;&lt;/FONT&gt;&lt;/DIV&gt;
&lt;LI&gt;
&lt;DIV class=noteoneline&gt;&lt;EM&gt;&lt;FONT face=Arial size=2&gt;One has to then write complex SQL queries in order to retrieve the data and also take into account the type conversions that occur when you join this table with the other tables…implicit conversion is not allowed in all RDBMS and is not a good thing anyways.&lt;/FONT&gt;&lt;/EM&gt;&lt;/DIV&gt;&lt;/LI&gt;&lt;/UL&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;However, as all too often with such didactic statements, this is based on a couple of assumptions; first, that all lookup data must be contained within a single table and, second, that all "lookup data" is the same and must all be handled the same way. Now, if those assumptions are really correct, then there is no question that using a single table for all lookup data is a bad idea (for all the reasons given). However, are they really valid?&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;The key to understanding this issue lies in one of the most basic principles of good database design - which is that "a database must model the business process that it supports". When viewed from this perspective it turns out that the assumptions on which the argument is based are incorrect. What we are taking about here is what I defined in my article "Designing A Database – Part 2" (see November 2008 on this web log) as Support Data. In that article I said the following:&lt;/FONT&gt;&lt;/P&gt;
&lt;BLOCKQUOTE dir=ltr&gt;
&lt;P class=bodytext&gt;&lt;EM&gt;&lt;FONT face=Arial size=2&gt;Although support data cannot be derived from other data and therefore requires permanent storage, it differs from core data in that it is does not form part of the body of data upon which business applications operate directly. This includes all data that is used define how the system behaves (i.e. metadata and preference data), &lt;FONT color=#ff0000&gt;or how the data actually stored in the system is interpreted (i.e. look-up and reference data).&lt;/FONT&gt; The support data is a key element in the database in that it is primarily concerned with the translation of data into information – and this applies equally to metadata as it does to look-up or reference data. &lt;/FONT&gt;&lt;/EM&gt;&lt;/P&gt;&lt;/BLOCKQUOTE&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;Notice the highlighted section. The rules of data normalization require that we use Foreign Keys to reference values that would otherwise be repeated in a table and such values are usually referred to as "lookups" leading to the idea that they are all equivalent in some fashion. In reality that is not the case and there, as indicated, TWO types of data that have to be dealt with - "lookup" and "reference" data. In the previous article I did not expand on the distinction because it was not really relevant in that context. Let me do so now, because this distinction is, in my opinion, the key to the whole OTLT issue.&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;Lookup Data should, as the name implies, consist only of things that can be "looked up" or, to put it another way, consist of only one piece of significant information. So for a lookup, the foreign key directly substitutes for a denormalized value. Conversely, "Reference Data" may comprise many pieces of related information and a the foreign key is used to retrieve the set of information that is applicable. &lt;/FONT&gt;&lt;FONT face=Tahoma size=2&gt;You may be thinking, about now, that I am making an artificial distinction, but believe me, I am not. Table 1 shows a few examples of "lookup keys" and how they map to the different categories.&lt;/FONT&gt;&lt;/P&gt;&lt;A href="/photos/andykramek/images/8418/original.aspx" target=_blank&gt;&lt;IMG src="/photos/andykramek/images/8418/original.aspx" border=0&gt;&lt;/A&gt; 
&lt;P class=bodytext&gt;&lt;FONT size=2&gt;&lt;FONT face=Tahoma&gt;Notice that for all keys that fall into the lookup category there is always just one piece of information – a description, and nothing else. While in the reference data category there is always more than one piece of information associated with the key. I can't speak for anyone else but myself, but to me it is obvious that there is no way that storing "Customer Credit Terms" in the same data structure as "Units of Measure" would make any sense.&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;So the assumption that all lookup data is somehow the same is false to start with. For anything that falls into the Reference Data category, an appropriately designed table should always be used and I absolutely concur with the premise that doing anything else is poor design.&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;But what about the Lookup Data category? The fact is that all of this data is actually '&lt;EM&gt;enumeration&lt;/EM&gt;' data and that is probably a better term to use than "lookup" for this type of data. Reduced to basics it comes down to a Key/Value pair. Even so, if we were to use a single table for all such data we do run into the issues raised by opponents of the OTLT approach. Specifically, keys become difficult to define and either we have to enforce uniqueness on the "lookup key" or we have to introduce a 'Type' column to create compound key (i.e. Type + Value). Neither option is attractive and it would indeed complicate the SQL required if we had to adopt such a solution. &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;But hang on! We are talking about relational databases here (we wouldn't have foreign keys if we weren't)! So why are we constrained to use a SINGLE table? The short answer is that we are not and, for enumerations (i.e. that consisting only of code and description) we can use a PAIR of tables. The structure that I like to use is shown in Table 2.&lt;A href="/photos/andykramek/images/8421/original.aspx" target=_blank&gt;&lt;IMG src="/photos/andykramek/images/8421/original.aspx" border=0&gt;&lt;/A&gt;&lt;/FONT&gt;&lt;A href="/photos/andykramek/Enum_Table_Structure.aspx" target=_blank&gt;&lt;/A&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;Note that all of the issues disappear if we adopt this approach. Each table has its own (surrogate) primary key, and the relation between Type (header) and Enumeration (detail) is maintained by the foreign key in the detail table. There is no need to artificially restrict the key values in the detail table and a simple SQL inner join returns the appropriate set of enumerations. If there are specific sets of data that are used often by applications, we can define views for them and create either stored procedures, or functions, to handle specific requirements.&lt;/FONT&gt; &lt;FONT size=2&gt;&lt;FONT face=Tahoma size=2&gt;The added columns on the detail table are for the convenience of the UI developer. The "Active" flag is used to indicate whether the value should be made available in the UI, and the "Default" flag indicates which value (if any) in the set should be used as the default in the User Interface. They do not have any significance, or impact, at the database level.&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;So my position is that reference data requires its own tables, but enumerations can be handled using a header-detail pair of tables. This does, however, raise another question. Should enumerations even be stored in the database? After all this data can be handled directly in code if all it is doing is providing expansion of key values to their associated description. The short answer is that they most definitely should be, and there are several reasons.&lt;/FONT&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;
&lt;DIV class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;If enumerations are defined only in application code, then adding a new item to the selection always means recompiling, and re-distributing, the application. Admittedly if the new item requires special handling in some way, then its addition would still require code changes, but the addition of, say, a new contact type would probably not require any code change at all and can be handled by simply adding a row to a table and letting the code that accesses the enumeration tables return the new type.&lt;/FONT&gt;&lt;/DIV&gt;
&lt;LI&gt;
&lt;DIV class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;Unless the enumeration is stored in the database, there is no way to decode the enumeration key values for reports or when exporting data to another application (e.g. an Excel spreadsheet for reporting or analysis) or to a data warehouse.&lt;/FONT&gt;&lt;/DIV&gt;
&lt;LI&gt;
&lt;DIV class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;When multiple applications access the same database, there is not only the issue of ensuring that enumerations are available to each but also that of ensuring that they are synchronized and consistent. The scenario where one application interprets an enumeration key of 1234 as "Home Phone Number" while another defines it as&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;"President" does not bear thinking about!&lt;/FONT&gt;&lt;/DIV&gt;&lt;/LI&gt;&lt;/UL&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;So whether we are talking about an On Line Transaction Processing, a Management Information System, or a Data Warehouse, we always have to cater for two distinct types of "lookup data". The enumerations that are used solely to normalize values that would otherwise have to be repeated and the reference data that is used to manage various aspects of the application. They are totally different entities and should be handled in different ways. &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;So is there a case for the "One True Lookup Table"? Not in the way that it is usually defined. The trick is not to confuse reference data with enumerations and not try and shoe-horn reference data that has different attributes into a single common structure. However, a pair of tables can certainly be a much more efficient way to handle enumerations than having lots of separate, identical tables, each of which contains only a few static rows. &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;For example a CRM application I worked on recently has 40 rows in its lookup header table&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;(i.e. we would have required 40 tables if each type were handled separately) and a total of 303 entries in the detail table (there are 50 "States" and 42 "Providers", so the average number of 'rows' per type is only about 7). Can forty, identical, seven row tables really be more efficient than the header-detail pair? Hardly! &lt;/FONT&gt;&lt;/P&gt;&lt;A href="/photos/andykramek/images/8416/original.aspx" target=_blank&gt;&lt;/A&gt;&lt;/FONT&gt;&lt;img src="http://weblogs.foxite.com/aggbug.aspx?PostID=8419" width="1" height="1"&gt;</description><category domain="http://weblogs.foxite.com/andykramek/archive/category/1060.aspx">Data Management</category></item><item><title>Working with Vertical Tables</title><link>http://weblogs.foxite.com/andykramek/archive/2009/05/03/8369.aspx</link><pubDate>Sun, 03 May 2009 13:18:00 GMT</pubDate><guid isPermaLink="false">8827bd1c-7596-4a8f-b0de-f59ce9ede522:8369</guid><dc:creator>andykr</dc:creator><slash:comments>3</slash:comments><comments>http://weblogs.foxite.com/andykramek/comments/8369.aspx</comments><wfw:commentRss>http://weblogs.foxite.com/andykramek/commentrss.aspx?PostID=8369</wfw:commentRss><description>&lt;P class=bodytext&gt;&lt;FONT face=Tahoma&gt;What is a "Vertical Table"? &lt;BR&gt;&lt;/FONT&gt;&lt;FONT face=Tahoma&gt;The short answer is that a vertical table is one which does not store data elements in separate columns. Instead data is stored in rows, with each row containing a description of what is being stored (the “attribute”) and the actual value. In a normal table, the requirement to capture an additional attribute means adding an additional column to the table. The result is that the table gets wider (i.e. grows horizontally) as new attributes are added. &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma&gt;In the case of a vertical table, each row contains both the description of the attribute and its value. The result is that as additional attributes are added to the table it acquires more records (i.e. grows vertically) but never gets any wider. At its simplest, a vertical table consists of only two columns, the attribute descriptor and its value, hence the alternate name for this type of table; an ‘&lt;I&gt;attribute/value pair&lt;/I&gt;’ table.&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma&gt;Whilst vertical tables can be used in many scenarios, there are two key criteria that, when satisfied, indicate that a vertical table is actually required. The first is that the data being modeled is infinitely extensible. The second is that the data related to its owning entity is always accessed as a complete set. These criteria are directly related to the advantages (and limitations!) of vertical tables and so merit detailed attention.&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma&gt;The term "infinitely extensible data" is simply a fancy way of saying that there are no specific rules about the data in question. One of the most commonly encountered examples of infinitely extensible data is Postal Addresses. This may sound odd, but if you think about it, there are no hard-and-fast rules for how postal addresses are defined. For example the US postal address for an apartment might look something like this:&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma&gt;123 Crossover Street, Apt 12A&lt;BR&gt;Akorn, OH 44123&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma&gt;While an equivalent type of address in the UK might be:&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma&gt;12A Cowsfoot House&lt;BR&gt;234 Pastures Walk&lt;BR&gt;London&lt;BR&gt;SX1 3GG &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma&gt;Analysis shows that each actually contain the same basic set of attributes (Apartment Number, Street Address, City and Postal Code) but the way in which they are defined is totally different. Thus the UK postal code includes the County (more accurately it defines a unique "postal district" which means that the county is implicit) while the US zip code requires the State to be defined explicitly. So what is usually done is to create a table with columns for each attribute and screens that allow data for each column to be entered. The result is usually something like this:&lt;/FONT&gt;&lt;FONT face=Tahoma&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma&gt;&lt;A HREF="/photos/andykramek/images/8378/original.aspx" target=_blank&gt;&lt;IMG src="/photos/andykramek/images/8378/original.aspx" border=0&gt;&lt;/A&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;Which, of course, is perfectly OK - as long as the table was not defined with purely US coding in mind (i.e. 2-letter state codes and 5/9 digit zip code) but there is obviously no standard for the way address details are handled and, in practice, very few addresses really require all of these columns anyway. Now consider the same addresses as they might be stored in a vertical table:&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;A HREF="/photos/andykramek/images/8379/original.aspx" target=_blank&gt;&lt;IMG src="/photos/andykramek/images/8379/original.aspx" border=0&gt;&lt;/A&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma&gt;The first thing to notice, which is a benefit of this approach, is that we can now name our "columns" much more appropriately for each address. Second, we have no wasted space in the table. Each address consists of as many rows as it actually needs. However, there are some issues that do not arise in a more conventional table.&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma&gt;First, even though we know which rows belong to which owner, we have no way of knowing in which order they should be presented because we do not have standard column names that can be mapped to a UI or report. So we need an additional column for each set of attributes to define how the attributes should be interpreted. &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma&gt;Second, our UI (or report) needs to be able to dynamically assign the appropriate number of rows and their descriptions because, while the address for owner 123 has 5 attributes, owner 125 has only 4 while owner 124 has 6 attributes. More importantly the attributes that were used differ between addresses.&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma&gt;So, you may be wondering, why don't we all use vertical tables like this for handling addresses? The answer is twofold. First, that while addresses considered globally are infinitely extensible in any one context they are not. After all, all US addresses are handled the same way, as are all UK addresses. So in an application context there is no real need for this kind of extensibility, and even when there is, there are other ways to handle it. &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma&gt;The second reason is related to a major limitation of vertical tables, that is, in turn, based in the second criterion for requiring one; i.e. that the data is always accessed as a complete set by owner. What this means, in practice, is that vertical tables are really useful when you need to save and restore sets of data, but are less useful when you need to anyalze that data. &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma&gt;For example, with a vertical table it is easy enough to get the address for owner 124:&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma&gt;SELECT attribute, value &lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;FROM address &lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;WHERE owner_fk = 124 &lt;BR&gt;ORDER BY sequence&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma&gt;but it is difficult to construct parameterized queries on such tables because there are no defined columns by which to filter results. The only option is to search for specific “&lt;I&gt;attribute = value&lt;/I&gt;” combinations.&amp;nbsp;&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma&gt;Thus to find how many addresses we have in New York state the query would have to be written as:&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=code&gt;&lt;FONT face=Tahoma&gt;SELECT COUNT( DISTINCT owner_fk )&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;FROM address &lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;WHERE attribute = 'State'&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;AND value = 'NY'&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma&gt;And this, of course, assumes that all New York addresses were actually entered using the "State" attribute (and not a "County" or "Province" attribute). What is worse, grouped queries that are easily handled by conventional tables are even impossible (like totaling sales figures by State for example). &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma&gt;So, if not for addresses, then what should we use vertical tables for? &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma&gt;There are a myriad of possible uses but, in general, I have found that you cannot simply implement a vertical table in isolation. The schema required, if only to implement the UI, is generally much more complex. &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma&gt;Here is an example for handling Educational and Professional Qualifications. The problem with qualifications is that they truly are infinitely extensible, even within a single context. There are absolutely no "rules" and no standards that are applicable. &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma&gt;For example the "standard" abbreviation in the US for a nurse is "RN" (Registered Nurse). However, in the UK "RN" indicates "Royal Navy" and the standard nursing qualification is "SRN" (State Registered Nurse). The problem is not just international either, what qualification indicates a medical "Doctor"? In the US around 30 are recognized (including PhD - Doctor of Philosophy) and to make matters even worse, even within this set there are conflicts – DO could be either "Doctor of Osteopathy" or "Doctor of Optometry" while DPM is either a "Doctor of Pediatric Medicine", or a "Doctor of Podiatric Medicine" (better be sure and get the right one to treat your problem)!&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma&gt;The situation is even more complicated when you take into account the fact that there are at least three different, and unrelated, types of qualification that people hold:&lt;/FONT&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;&lt;FONT face=Tahoma&gt;Educational: These document your academic achievements and include things like your college degree and area of specialization. These are static because once you have gained an educational qualification, you have it for life &lt;/FONT&gt;
&lt;LI&gt;&lt;FONT face=Tahoma&gt;Professional: These document your competencies and include things like medical qualification boards and memberships of professional associations. These tend to be static, but can change, especially if there are various grades of attainment or membership &lt;/FONT&gt;
&lt;LI&gt;&lt;FONT face=Tahoma&gt;Certification: These document things that you are allowed to do and include things like practice licenses or specialist skills. What differentiates them from professional qualifications is that they expire and have to be renewed periodically&lt;/FONT&gt;&lt;/LI&gt;&lt;/UL&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma&gt;Thus we could have three completely different sets of information which all relate to a individual's qualifications and they require completely different sets of data. Educational qualifications typically include an awarding body, subject, date and grade. However, a time-based certification will also need an expiry date and a professional qualification may well need a Licensing Body and Number. &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma&gt;Obviously this data meets the "infinitely extensible" criterion. However, unlike addresses it also meets the second criteria because we would always want to retrieve the data in sets. For example, license numbers, in isolation, are meaningless because we would always need to know the context for a license (what it is for, who granted it, when it was awarded, when it expires and so on). Similarly the fact that someone has taken a C# programming course is not much use unless we also know the level for the course (Beginner? Advanced?), when and where it was taken and whether they passed or failed.&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma&gt;In order to model this we will need to use a vertical table to hold the actual data that relates to a qualification to but we will need additional tables to describe how that data is to be interpreted. &lt;/FONT&gt;&lt;A HREF="/photos/andykramek/images/8380/original.aspx" target=_blank&gt;&lt;IMG src="/photos/andykramek/images/8380/original.aspx" border=0&gt;&lt;/A&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma&gt;In order to support this, we also need to define attributes, and this too is a little more complex, requiring three tables as described below:&lt;/FONT&gt;&lt;FONT face=Tahoma&gt;&lt;A HREF="/photos/andykramek/images/8381/original.aspx" target=_blank&gt;&lt;IMG src="/photos/andykramek/images/8381/original.aspx" border=0&gt;&lt;/A&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma&gt;The full model for handling qualifications is shown at Figure 1 below:&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma&gt;&lt;A HREF="/photos/andykramek/images/8377/original.aspx" target=_blank&gt;&lt;IMG src="/photos/andykramek/images/8377/original.aspx" border=0&gt;&lt;/A&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma&gt;Notice, however, &amp;nbsp;that the attribute definition portion of the model is entirely generic and could be used defining any type of attributes. Consequently, for any data entity that needs attributes all you need is to define the attributes in this model and create the necessary "detail" table to link them to the owning entity. In fact, I usually define views to handle retrieving the attribute data so that the complexity of the model is hidden, thus to retrieve the generic set of attributes for an "address" I can query the view by name:&lt;BR&gt;&lt;/FONT&gt;&lt;FONT face="Courier New"&gt;SELECT * FROM dbo.v_trans_attrib WHERE attgrp_nme = 'Address' &lt;BR&gt;&lt;/FONT&gt;&lt;FONT face=Tahoma&gt;Returns:&lt;/FONT&gt;&lt;FONT face=Tahoma&gt;&lt;A HREF="/photos/andykramek/images/8382/original.aspx" target=_blank&gt;&lt;IMG src="/photos/andykramek/images/8382/original.aspx" border=0&gt;&lt;/A&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma&gt;Or I can use the attribute code directly and get the set of attributes for a Certification:&amp;nbsp;&lt;BR&gt;&lt;/FONT&gt;&lt;FONT face="Courier New"&gt;SELECT * FROM dbo.v_trans_attrib WHERE attgrp_cde = 'CERT' &lt;BR&gt;&lt;/FONT&gt;&lt;FONT face=Tahoma&gt;Returns:&lt;/FONT&gt;&lt;FONT face=Tahoma&gt;&lt;A HREF="/photos/andykramek/images/8383/original.aspx" target=_blank&gt;&lt;IMG src="/photos/andykramek/images/8383/original.aspx" border=0&gt;&lt;/A&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma&gt;By defining these views so that they include all relevant keys and codes it is a simple matter to have the UI generate the necessary data to retrieve the attribute sets, their associated lookup values and data. Since every table has a surrogate primary key, the update code is also very simple (and generic).&amp;nbsp;&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma&gt;OK, so vertical tables can work, but why bother? There are really two reasons. First, as I have tried to show, vertical tables can provide a flexible, and more importantly a generic, mechanism for handling totally disparate data that would otherwise require multiple tables with different columns. Second, and perhaps most important, vertical tables can handle changing data requirements without requiring changes to table structure. Adding a new attribute is merely a question of adding a new row.&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma&gt;They are not always applicable, but when you are dealing with data that is both extensible, and heavily set oriented, vertical tables offer a powerful and flexible way of handling the data. The additional planning and work involved in setting up a vertical table based model and creating the generic supporting mechanisms is, in my opinion anyway, amply repaid by the benefits achieved in terms of reduced maintenance and flexibility that they permit.&lt;BR&gt;&lt;/FONT&gt;&lt;/P&gt;&lt;A HREF="/photos/andykramek/images/8371/original.aspx" target=_blank&gt;&lt;/A&gt;&lt;A HREF="/photos/andykramek/images/8372/original.aspx" target=_blank&gt;&lt;/A&gt;&lt;A HREF="/photos/andykramek/images/8373/original.aspx" target=_blank&gt;&lt;/A&gt;&lt;A HREF="/photos/andykramek/images/8374/original.aspx" target=_blank&gt;&lt;/A&gt;&lt;A HREF="/photos/andykramek/images/8370/original.aspx" target=_blank&gt;&lt;/A&gt;&lt;A HREF="/photos/andykramek/images/8375/original.aspx" target=_blank&gt;&lt;/A&gt;&lt;A HREF="/photos/andykramek/images/8376/original.aspx" target=_blank&gt;&lt;/A&gt;&lt;img src="http://weblogs.foxite.com/aggbug.aspx?PostID=8369" width="1" height="1"&gt;</description><category domain="http://weblogs.foxite.com/andykramek/archive/category/1060.aspx">Data Management</category></item><item><title>Modeling Hierarchies (Part 2)</title><link>http://weblogs.foxite.com/andykramek/archive/2009/04/25/8332.aspx</link><pubDate>Sat, 25 Apr 2009 12:50:00 GMT</pubDate><guid isPermaLink="false">8827bd1c-7596-4a8f-b0de-f59ce9ede522:8332</guid><dc:creator>andykr</dc:creator><slash:comments>5</slash:comments><comments>http://weblogs.foxite.com/andykramek/comments/8332.aspx</comments><wfw:commentRss>http://weblogs.foxite.com/andykramek/commentrss.aspx?PostID=8332</wfw:commentRss><description>&lt;P class=firstparagraph&gt;&lt;FONT face=Tahoma size=2&gt;In the last article I gave the theoretical background to using mapping to model complex hierarchies. In this article I will work through an example that is intended to illustrate how the use of the mapping approach can solve some of the key issues associated with managing hierarchies in an application environment. &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=firstparagraph&gt;&lt;FONT face=Tahoma size=2&gt;For the purpose of this example consider the position of a sales representative, named Amanda Barry, who works out of the Massillon plant for a Company which has plants in various parts of the country. She is actually a member of&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;three separate hierarchies simultaneously. These are:&lt;/FONT&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;
&lt;DIV class=firstparagraph&gt;&lt;FONT face=Tahoma size=2&gt;Sales Team&lt;/FONT&gt;&lt;/DIV&gt;
&lt;LI&gt;
&lt;DIV class=firstparagraph&gt;&lt;FONT face=Tahoma size=2&gt;Plant Organization&lt;/FONT&gt;&lt;/DIV&gt;
&lt;LI&gt;
&lt;DIV class=firstparagraph&gt;&lt;FONT face=Tahoma size=2&gt;Customer &lt;/FONT&gt;&lt;/DIV&gt;&lt;/LI&gt;&lt;/UL&gt;
&lt;P class=firstparagraph&gt;&lt;FONT face=Tahoma size=2&gt;We’ll examine each in turn and model them accordingly&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;B&gt;&lt;SPAN&gt;&lt;FONT size=4&gt;The Sales Team hierarchy&lt;o:p&gt;&lt;/o:p&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/B&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;The company Sales Force is organized into teams which are based in geographic territories. The hierarchy describing this structure is shown at Figure 1, Note that these nodes represent not only logical groupings, but in some case are also actual positions within the company and have people associated with them. For example, Amanda is a member of the “&lt;I&gt;Great Lakes&lt;/I&gt;” team, which has a Manager (Charlie Davis). But the Great Lakes team is considered a part of the “&lt;I&gt;Northern Sub-Division&lt;/I&gt;” which is merely a convenient way of sub-dividing the four teams which comprise the Eastern Division . There is actually no real person associated with the “Northern &lt;I&gt;Sub-Division&lt;/I&gt;” Node.&lt;/FONT&gt;&lt;EM&gt;&lt;FONT face=Arial size=2&gt;&lt;A href="/photos/andykramek/images/8315/original.aspx" target=_blank&gt;&lt;IMG src="/photos/andykramek/images/8315/original.aspx" border=0&gt;&lt;/A&gt;&lt;/FONT&gt;&lt;/EM&gt;&lt;/P&gt;
&lt;P class=figure&gt;&lt;FONT face=Tahoma size=2&gt;To model this structure will require a minimum of two tables, as described above, one for the Node Definitions, and one for the Mapping data (the rip table). Since we already know that we will want to define multiple hierarchies, we will add a third table to the structure, to allow us to categorize nodes. The structure we will use is at Figure 2:&lt;/FONT&gt;&lt;EM&gt;&lt;FONT face=Arial size=2&gt;&lt;A href="/photos/andykramek/images/8329/original.aspx" target=_blank&gt;&lt;IMG src="/photos/andykramek/images/8329/original.aspx" border=0&gt;&lt;/A&gt;&lt;/FONT&gt;&lt;/EM&gt;&lt;EM&gt;&lt;FONT face=Arial size=2&gt;&lt;/FONT&gt;&lt;/EM&gt;&lt;/P&gt;
&lt;P class=figure&gt;&lt;EM&gt;&lt;FONT face=Arial size=2&gt;Figure 2: Basic table structure for mapping hierarchies &lt;/FONT&gt;&lt;/EM&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;To map the structure we require entries in these tables as shown at Table 2&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=tablecaption&gt;&lt;FONT face=Tahoma size=2&gt;&lt;A href="/photos/andykramek/images/8324/original.aspx" target=_blank&gt;&lt;IMG src="/photos/andykramek/images/8324/original.aspx" border=0&gt;&lt;/A&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;Now we can map the structure to the structure table by tracing the paths. Once all of the paths have been defined the data can be added to the hierarchy (the Node PKs are shown below the descriptions in Figure 1). The actual data is at Table 2.&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=note&gt;&lt;FONT face=Arial size=2&gt;&lt;EM&gt;NOTE: We use the “*” character not only to separate, but also to terminate, the key references in the rip field. The reason is so that we can query this field later and avoid issues associated with partial matches on key references when querying the table using the LIKE operator on the rip field. &lt;BR&gt;Consider the following pair of rip fields: “10*20*30” and “10*20*300”. &lt;BR&gt;Clearly they are not referring to the same node but if we were to query where the value is LIKE “10*20*30%” we would get both. By using a termination character we avoid this problem.&lt;/EM&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;&lt;A href="/photos/andykramek/images/8323/original.aspx" target=_blank&gt;&lt;IMG src="/photos/andykramek/images/8323/original.aspx" border=0&gt;&lt;/A&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;The final task is to link our people to the appropriate node in the hierarchy. For the purpose of this illustration we are not concerned with &lt;I&gt;how&lt;/I&gt; the Employee data is stored, but simply assume that somewhere there are tables that include a key value for each employee and the usual associated information (Name, Title, Date Joined etc). This data is represented by the “Person” table in Figure 3. &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;We use an allocation table to allow us to link a single employee to different nodes (we already know that Amanda is a member of three hierarchies, so she will have to be associated with at least three nodes, one in each hierarchy). &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=figure&gt;&lt;FONT face=Arial size=2&gt;&lt;EM&gt;&lt;A href="/photos/andykramek/images/8328/original.aspx" target=_blank&gt;&lt;IMG src="/photos/andykramek/images/8328/original.aspx" border=0&gt;&lt;/A&gt;&lt;/EM&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=noteoneline&gt;&lt;EM&gt;&lt;FONT face=Arial size=2&gt;The crucial point to notice is that this allocation table links people to the structure table, not to the node! &lt;/FONT&gt;&lt;/EM&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;The reason for this is simply that the node table is only a look-up which allows us to give a meaningful name to a node in the hierarchy. It has no significance whatsoever and indeed, the same name may be used in many places in different hierarchies (how many “Admin” departments are there in the world?). If we were to attach people directly to the node record we would require a separate node record for each individual occurrence of a node in a hierarchy, thereby making the relationship between a node and its structure one-to-one, forcing us back into a very rigid structure. &lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;Now we can add the necessary records to the allocation table for the people that we know about so far, plus the Vice President in Charge of Sales Nationally (Jane Kingsland) and the head of the Eastern Division (Martin Niles) and the manager and a sales representative for the Mid-West team :&lt;/FONT&gt;&lt;STRONG&gt;&lt;EM&gt;&lt;FONT face=Arial size=2&gt;&lt;A href="/photos/andykramek/images/8322/original.aspx" target=_blank&gt;&lt;IMG src="/photos/andykramek/images/8322/original.aspx" border=0&gt;&lt;/A&gt;&lt;/FONT&gt;&lt;/EM&gt;&lt;/STRONG&gt;&lt;/P&gt;
&lt;P class=subhead4&gt;&lt;STRONG&gt;&lt;FONT face=Arial size=4&gt;What does this give us?&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;It now allows us to create static views that query the hierarchy and return matching members. Here are the necessary definitions for two views. The first returns the rip field associated with a given node description:&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;IF EXISTS (SELECT * FROM sysobjects WHERE id = object_id('v_GetRip') and sysstat &amp;amp; 0xf = 2)&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT size=2&gt;&lt;STRONG&gt;&lt;FONT face="Courier New"&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;DROP VIEW v_GetRip&lt;BR&gt;&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;GO&lt;/STRONG&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=code&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;CREATE VIEW v_GetRip AS&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT size=2&gt;&lt;STRONG&gt;&lt;FONT face="Courier New"&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;SELECT NN.node_pk, NN.node_dsc, RTRIM( RF.node_path) + '%' AS rip_field&lt;BR&gt;&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT size=2&gt;&lt;STRONG&gt;&lt;FONT face="Courier New"&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;FROM node_stru RF&amp;nbsp;&lt;BR&gt;&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;STRONG&gt;&lt;FONT size=2&gt;&lt;FONT face="Courier New"&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;INNER JOIN node_def NN ON RF.node_fk = NN.node_pk&lt;/FONT&gt;&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/P&gt;
&lt;P class=code&gt;&lt;FONT face=Tahoma size=2&gt;A simple query is now all that is needed to return the appropriate rip field mask that defines the starting point for data selection. The important thing to recognize is that this ‘mask’ allows to extract information from any level of the hierarchy without the need to know anything about the level or the relationships between the nodes. For example:&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=code&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;SELECT rip_field FROM v_getrip WHERE node_dsc = ‘Great Lakes’ &lt;BR&gt;RETURNS “1*2*4*9*%”&lt;/STRONG&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=code&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;while&lt;/STRONG&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=code&gt;&lt;FONT size=2&gt;&lt;FONT face="Courier New"&gt;&lt;STRONG&gt;SELECT rip_field FROM v_getrip WHERE node_dsc LIKE ‘Mid%’ &lt;BR&gt;RETURNS “1*2*4*8*&lt;/STRONG&gt;&lt;SPAN&gt;%&lt;/SPAN&gt;&lt;STRONG&gt;”&lt;/STRONG&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;This view can then be combined with another static view to return a list of people who are members of whatever node we wish to query by:&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=codefirstline&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;IF EXISTS (SELECT * FROM sysobjects WHERE id = object_id('v_GetMembers') and sysstat &amp;amp; 0xf = 2)&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT size=2&gt;&lt;STRONG&gt;&lt;FONT face="Courier New"&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;DROP VIEW v_GetMembers&lt;BR&gt;&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;GO&lt;/STRONG&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=code&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;CREATE VIEW v_GetMembers AS&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;SELECT DISTINCT VR.node_dsc keyval, ND.node_pk, ND.node_dsc,PN.title_dsc, PN.fname_nme, PN.lname_nme&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT size=2&gt;&lt;STRONG&gt;&lt;FONT face="Courier New"&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;FROM v_GetRip VR&lt;BR&gt;&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT size=2&gt;&lt;STRONG&gt;&lt;FONT face="Courier New"&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;INNER JOIN ( node_stru NS INNER JOIN node_def ND ON NS.node_fk = ND.node_pk )&lt;BR&gt;&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT size=2&gt;&lt;STRONG&gt;&lt;FONT face="Courier New"&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;ON NS.node_path LIKE VR.rip_field&lt;BR&gt;&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT size=2&gt;&lt;STRONG&gt;&lt;FONT face="Courier New"&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;LEFT OUTER JOIN node_psn_alloc AL ON AL.nodestru_fk = NS.nodestru_pk&lt;BR&gt;&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;STRONG&gt;&lt;FONT size=2&gt;&lt;FONT face="Courier New"&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;LEFT OUTER JOIN person PN ON PN.psn_pk = AL.psn_fk&lt;/FONT&gt;&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;Simple queries on this view return the list of people who qualify:&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=codefirstline&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;SELECT * FROM v_getmembers WHERE node_dsc = 'Great Lakes' &lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;RETURNS&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;1&lt;SPAN&gt; &lt;/SPAN&gt;Great Lakes&lt;SPAN&gt; &lt;/SPAN&gt;Sales Rep&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;Amanda&lt;SPAN&gt; &lt;/SPAN&gt;Barry&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;STRONG&gt;&lt;FONT face="Courier New" size=2&gt;2&lt;SPAN&gt; &lt;/SPAN&gt;Great Lakes&lt;SPAN&gt; &lt;/SPAN&gt;Sales Manager&lt;SPAN&gt; &lt;/SPAN&gt;Charlie&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;Davis&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;while&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=code&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;SELECT * FROM v_getmembers WHERE keyval = 'Mid-West'&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;RETURNS&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;Mid-West&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;8&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;Mid-West&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;Sales Manager&lt;SPAN&gt; &lt;/SPAN&gt;Ellen&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;Franks&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;STRONG&gt;&lt;FONT face="Courier New" size=2&gt;Mid-West&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;8&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;Mid-West&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;Sales Rep&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;George&lt;SPAN&gt; &lt;/SPAN&gt;Harrison&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;and&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=code&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;SELECT * FROM v_getmembers WHERE node_dsc = 'Northern Sub-Division'&lt;BR&gt;RETURNS&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;Northern Sub-Division&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;4&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;Northern Sub-Division&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;NULL&lt;SPAN&gt;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;NULL&lt;SPAN&gt;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;NULL&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;Northern Sub-Division&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;8&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;Mid-West&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;Sales Manager&lt;SPAN&gt; &lt;/SPAN&gt;Ellen&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;Franks&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;Northern Sub-Division&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;8&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;Mid-West&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;Sales Rep&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;George&lt;SPAN&gt; &lt;/SPAN&gt;Harrison&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;Northern Sub-Division&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;9&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;Great Lakes&lt;SPAN&gt;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;Sales Manager&lt;SPAN&gt; &lt;/SPAN&gt;Charlie&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;Davis&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;STRONG&gt;&lt;FONT face="Courier New" size=2&gt;Northern Sub-Division&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;9&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;Great Lakes&lt;SPAN&gt;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;Sales Rep&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;Amanda&lt;SPAN&gt; &lt;/SPAN&gt;Barry&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;Any query relating to People is now a simple matter. Assuming that we have sales information related to the ID of the person who made the sale, we can get the total sales for any node in the hierarchy by simply summing the sales associated with the list of people who are related to that node:&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=codefirstline&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;SELECT SUM( sales)&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;FROM sales_data WHERE sales_person_id IN&amp;nbsp;&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;STRONG&gt;&lt;FONT size=2&gt;&lt;FONT face="Courier New"&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;(SELECT psn_pk FROM v_getmembers WHERE node_desc = ‘Great Lakes’ )&lt;/FONT&gt;&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;The same method can be used to extract the members of a Sales Team&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=codefirstline&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;SELECT name FROM emp_data WHERE person_pk IN&lt;/STRONG&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=codelastline&gt;&lt;STRONG&gt;&lt;FONT size=2&gt;&lt;FONT face="Courier New"&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;(SELECT psn_pk FROM v_getmembers WHERE node_desc = ‘Great Lakes’ )&lt;/FONT&gt;&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;
&lt;P class=bodytext&gt;&lt;STRONG&gt;&lt;FONT face=Arial size=4&gt;The Plant Hierarchy&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;The second hierarchy of which our notional person is a member is the “plant hierarchy”, part of which is&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;illustrated in Figure 4.&amp;nbsp;&lt;/FONT&gt;&lt;o:p&gt;&lt;FONT face=Tahoma size=2&gt;&amp;nbsp;&lt;A href="/photos/andykramek/images/8327/original.aspx" target=_blank&gt;&lt;IMG src="/photos/andykramek/images/8327/original.aspx" border=0&gt;&lt;/A&gt;&lt;/FONT&gt;&lt;/o:p&gt;&lt;/P&gt;
&lt;P class=figurecaption&gt;&lt;FONT face=Tahoma size=2&gt;We can simply add the data for this hierarchy to our existing tables (Table 4) in exactly the same way as for the Sales Organization detailed above. In this case the Node Category will be "2" and the various plants are added as new nodes – their structure being added to the Node_Stru table as before:&lt;/FONT&gt;&lt;o:p&gt;&lt;FONT face=Tahoma size=2&gt;&lt;A href="/photos/andykramek/images/8321/original.aspx" target=_blank&gt;&lt;IMG src="/photos/andykramek/images/8321/original.aspx" border=0&gt;&lt;/A&gt;&amp;nbsp;&lt;/FONT&gt;&lt;/o:p&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;FONT face=Tahoma size=2&gt;Now we need to associate the Sales Representatives with the plants which they represent (Table 5). In this case only Amanda Barry (Massillon) and George Harrison (Columbus) are directly associated with plants, so only two new entries are required in the allocation table (obviously there would be other people associated with the plants hierarchy but the objective here is to focus only on the Sales side of the business):&lt;/FONT&gt;&lt;o:p&gt;&lt;FONT face=Tahoma size=2&gt;&lt;A href="/photos/andykramek/images/8320/original.aspx" target=_blank&gt;&lt;IMG src="/photos/andykramek/images/8320/original.aspx" border=0&gt;&lt;/A&gt;&amp;nbsp;&lt;/FONT&gt;&lt;/o:p&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;FONT face=Tahoma size=2&gt;Our existing views, of course, will work just as well with this hierarchy as with the Sales &lt;/FONT&gt;&lt;FONT face=Tahoma size=2&gt;hierarchy. Thus:&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=codefirstline&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;SELECT * FROM v_getmembers WHERE keyval LIKE 'North Ohio%'&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;RETURNS&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;North Ohio&lt;SPAN&gt; &lt;/SPAN&gt;15&lt;SPAN&gt; &lt;/SPAN&gt;North Ohio&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;NULL&lt;SPAN&gt;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;NULL&lt;SPAN&gt;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;NULL&lt;SPAN&gt;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;NULL&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;North Ohio&lt;SPAN&gt; &lt;/SPAN&gt;17&lt;SPAN&gt; &lt;/SPAN&gt;Cleveland&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;NULL&lt;SPAN&gt;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;NULL&lt;SPAN&gt;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;NULL&lt;SPAN&gt;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;NULL&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;STRONG&gt;&lt;FONT face="Courier New" size=2&gt;North Ohio&lt;SPAN&gt; &lt;/SPAN&gt;18&lt;SPAN&gt; &lt;/SPAN&gt;Massillon&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;Sales Rep&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;Amanda&lt;SPAN&gt; &lt;/SPAN&gt;Barry&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;1&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;o:p&gt;&lt;FONT face=Tahoma size=2&gt;&amp;nbsp;&lt;/FONT&gt;&lt;/o:p&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;FONT face=Tahoma size=2&gt;Notice that we can now sum our sales by any node in either hierarchy using the same basic query. To get all the sales for all Ohio Plants the query is simply:&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=codefirstline&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;SELECT SUM( sales )&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;FROM sales_data WHERE sales_person_id IN&amp;nbsp;&lt;BR&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;STRONG&gt;&lt;FONT size=2&gt;&lt;FONT face="Courier New"&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/SPAN&gt;(SELECT psn_pk FROM v_getmembers WHERE keyval = ‘Ohio Plants’ )&lt;/FONT&gt;&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;FONT face=Tahoma size=2&gt;and to get a list of representatives who are working out of any given plant, we use exactly the same query as we used to get a sales team:&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;SPAN&gt;&lt;STRONG&gt;&lt;FONT size=2&gt;SELECT SUM( sales )&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;FROM sales_data WHERE sales_person_id IN&amp;nbsp;&lt;BR&gt;&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/SPAN&gt;&lt;FONT face="Courier New"&gt;&lt;STRONG&gt;&lt;FONT size=2&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/SPAN&gt;(SELECT psn_pk FROM v_getmembers WHERE keyval = ‘Columbus’ )&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;STRONG&gt;&lt;FONT face="Courier New" size=2&gt;&lt;/FONT&gt;&lt;/STRONG&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;STRONG&gt;&lt;FONT face=Arial size=4&gt;The Customer Hierarchy&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;Adding a customer hierarchy is as simple as adding the plant hierarchy. Figure 5 shows a partial hierarchy for a large customer group and shows the entries for the Node and Node_Stru tables directly on the diagram:&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;o:p&gt;&lt;FONT face=Tahoma size=2&gt;&amp;nbsp;&lt;A href="/photos/andykramek/images/8326/original.aspx" target=_blank&gt;&lt;IMG src="/photos/andykramek/images/8326/original.aspx" border=0&gt;&lt;/A&gt;&lt;/FONT&gt;&lt;/o:p&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;SPAN&gt;&lt;FONT size=2&gt;One more thing that we need to do to accommodate this new hierarchy is to link our sales people to the appropriate node in the hierarchy. For the sake of illustration, we are mapping our Sales Reps to the lowest level of this hierarchy (Amanda Barry to 'NE Ohio [Node 24]' and George Harrison to SE Ohio[Node 25]), the Division Head to the Region level (Martin Niles to NE US [Node 22]) and our VP to National Level&amp;nbsp;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;o:p&gt;&lt;FONT face=Tahoma size=2&gt;&amp;nbsp;&lt;A href="/photos/andykramek/images/8319/original.aspx" target=_blank&gt;&lt;IMG src="/photos/andykramek/images/8319/original.aspx" border=0&gt;&lt;/A&gt;&lt;/FONT&gt;&lt;/o:p&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;The net result is that the VP (Jane Kingsland) is now associated with two structures; first, she is attached to the organization at the National Sales node, and second, to the customer organization at the "Wendy's USA" node. Similarly other members participate in multiple structures, so that our friend Amanda is actually in three. She is associated with the sales tree at the 'Great Lakes' level, with plants at 'Massillon' and customers at 'SE Ohio'. However, no matter how many structures an entity is associated with, we still only ever have one record in the person table and one record in the node definition table!&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=subhead2&gt;&lt;STRONG&gt;&lt;FONT face=Arial&gt;&lt;FONT size=4&gt;Maintaining the structure&lt;/FONT&gt; &lt;/FONT&gt;&lt;/STRONG&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;As can be seen, using the approach detailed here it is very easy to map, and to retrieve the data associated with, any number of hierarchies. By defining simple views to hide the complexity of the actual structure it is possible to support any desired user interface and even to dynamically add and delete entire hierarchies. The structures illustrated here show the bare minimum of information that is necessary to manage the data but do show the principles that underlie the approach. An obvious refinement to the Node Structure table is to add a “level” key, to permit data retrieval across all nodes that lie at a specific level of a hierarchy (for example, this would make implementing a TreeView to display the contents of a hierarchy very easy).&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;By modifying the node structure table so that it includes an additional foreign key for the parent node, and the level, the structure can be maintained using triggers that, when inserting a new record, generate the rip field and even level automatically based upon the rip field and level of the node to which the new record is being related (Figure 6). &lt;/FONT&gt;&lt;o:p&gt;&lt;FONT face=Tahoma size=2&gt;&lt;A href="/photos/andykramek/images/8325/original.aspx" target=_blank&gt;&lt;IMG src="/photos/andykramek/images/8325/original.aspx" border=0&gt;&lt;/A&gt;&amp;nbsp;&lt;/FONT&gt;&lt;/o:p&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;FONT face=Tahoma size=2&gt;The resulting table, for all hierarchies is shown at Table 7:&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;o:p&gt;&lt;FONT face=Tahoma size=2&gt;&amp;nbsp;&lt;A href="/photos/andykramek/images/8318/original.aspx" target=_blank&gt;&lt;IMG src="/photos/andykramek/images/8318/original.aspx" border=0&gt;&lt;/A&gt;&lt;/FONT&gt;&lt;/o:p&gt;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;FONT face=Tahoma size=2&gt;&lt;/FONT&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class=MsoNormal&gt;&lt;FONT face=Tahoma size=2&gt;The T-SQL code for the insert trigger is very simple:&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;/P&gt;&lt;FONT face="Courier New"&gt;&lt;STRONG&gt;
&lt;P class=codefirstline&gt;&lt;FONT size=2&gt;IF OBJECT_ID('node_stru_ins') IS NOT NULL&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;DROP TRIGGER node_stru_ins&lt;BR&gt;GO&lt;BR&gt;/*********************************************************************&lt;BR&gt;TRIGGER.: node_stru_ins&lt;BR&gt;Function: Insert Trigger for Structure table that automatically creates &lt;BR&gt;........: the Rip Field when adding a new item of structural information&lt;BR&gt;Called..: INSERT INTO node_stru ( node_fk, parent_node_fk ) &lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;VALUES ( &amp;lt;emporg_pk&amp;gt;, &amp;lt;parent_pk&amp;gt; )&lt;BR&gt;**********************************************************************/&lt;BR&gt;CREATE TRIGGER node_stru_ins ON node_stru FOR INSERT &lt;BR&gt;AS&lt;BR&gt;BEGIN&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=code&gt;&lt;FONT size=2&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;UPDATE TSU &lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;SET node_lvl = CASE WHEN TSU.parent_fk IS NULL THEN 1 ELSE PSU.node_lvl + 1 END,&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;node_path = CASE WHEN TSU.parent_fk IS NULL &lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;THEN '*' ELSE RTRIM(PSU.node_path) END &lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;+ CAST( TSU.node_fk AS VARCHAR( 10 ) ) + '*'&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;FROM node_stru TSU &lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;INNER JOIN inserted INS ON INS.nodestru_pk = TSU.nodestru_pk&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;LEFT OUTER JOIN node_stru PSU ON TSU.parent_fk = PSU.node_fk&lt;BR&gt;END&lt;BR&gt;GO&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=codefirstline&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=bodytext&gt;&lt;FONT face=Tahoma size=2&gt;To return the data in a form suitable for display in a treeview we need a little more code, but not very much. Here is a stored procedure that accepts either the PK of a node, or the actual Node Description and uses the rip field to get the nodes, and sequence them correctly, so that they can be displayed.&lt;/FONT&gt;&lt;/P&gt;&lt;FONT face=Tahoma&gt;
&lt;P class=codefirstline&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;IF OBJECT_ID('GetTree') IS NOT NULL&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;DROP PROCEDURE GetTree&lt;BR&gt;GO&lt;BR&gt;/********************************************************************************&lt;BR&gt;SP Name.: GetTree&lt;BR&gt;Function: Retrieve a Tree from the specified root node&lt;BR&gt;Author..: Andy Kramek&lt;BR&gt;Syntax..: EXEC GetTree @vRoot&lt;BR&gt;Params..: @vRoot Either Title/Location Name or PK for the node_def Table&lt;BR&gt;********************************************************************************/&lt;BR&gt;CREATE PROCEDURE GetTree @vRoot SQL_VARIANT&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;AS&lt;BR&gt;SET NOCOUNT ON&lt;/STRONG&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=code&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;/* Figure out what we got given here */&lt;BR&gt;DECLARE @node_pk INT, @node_dsc VARCHAR(50)&lt;BR&gt;SET&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;@node_pk = 0&lt;BR&gt;IF @vRoot &amp;gt; 0&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;/* Primary Key */&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;SET&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;@node_pk = CAST( @vRoot AS INTEGER )&lt;BR&gt;ELSE&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;BEGIN&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;/* Node Name */&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;SET @node_dsc = LTRIM( RTRIM( CAST( @vRoot AS VARCHAR(50) )))&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;/* Get the pk for the specified node */&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;SELECT @node_pk = node_pk FROM node_def WHERE node_dsc = @node_dsc&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;END&lt;/STRONG&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=code&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;/* We now have a PK so get the rip field as a mask for the specified node */&lt;BR&gt;DECLARE @Rip VARCHAR(1000), @Max INT&lt;BR&gt;SELECT @Rip = RTRIM(node_path) + '%' FROM node_stru WHERE node_fk = @node_pk&lt;/STRONG&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=code&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;/* Get all matching data, INCLUDING the root node, into a temporary table */&lt;BR&gt;SELECT EO.node_dsc, ES.node_lvl, CAST( 0 AS bit ) haschild_flg, &lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;CAST( 0 AS bit ) isleaf_flg,&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;ES.node_path, EO.node_pk, ES.parent_fk, ES.nodestru_pk&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;INTO #tempstru &lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;FROM node_def EO, node_stru ES&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;WHERE EO.node_pk = ES.node_fk&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;AND ES.node_path LIKE @Rip&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;ORDER BY ES.node_lvl, ES.parent_fk, EO.node_dsc&lt;/STRONG&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=code&gt;&lt;FONT face="Courier New" size=2&gt;&lt;STRONG&gt;/* Now we need to process the data to set the Child and Leaf flags */&lt;BR&gt;IF @@ROWCOUNT &amp;gt; 0&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp; &lt;/SPAN&gt;BEGIN&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;/* Declare the variables we'll need for the cursor operation */&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;DECLARE @urrip VARCHAR(1000), @nLevel INT, @HasCh BIT, @sLeaf BIT, @nodestru_pk INT &lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;/* And the ones for manipulation */&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&lt;/SPAN&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;DECLARE @LastKey INT, @lnCnt INT, @LeafKey INT&lt;/STRONG&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P class=code&gt;&lt;FONT size=2&gt;&lt;STRONG&gt;&lt;FONT face="Courier New"&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;/* Create a cursor for the specified fields */&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;DECLARE curelems CURSOR FOR&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;SELECT node_path, node_lvl, haschild_flg, isleaf_flg, nodestru_pk&lt;BR&gt;&lt;SPAN&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/SPAN&gt;FROM #tempstru&