Blog Home  Home RSS 2.0 Atom 1.0 CDF  
Ruminations of a Developer - Wednesday, March 08, 2006
Mark Bonafe
 
 Wednesday, March 08, 2006
Situation:
I have a SQL Table that is self linking.  Records act as parent and child.  In this case, it is for a packaging system where it is possible to have multiple packages containing a commodity (product).  For example, a pallet containing 50 boxes, each box contains 24 bags of potato chips.  The application allows for an unlimited number of package to package relationships.

Here's a sample diagram from SQL Server.



Problem:

The customer requires a report where only the commodity is displayed.  The commodity may be burried very deep in the chain of packages.  And because the commodity is only aware of the immediate package above it (using the example above, the boxes), getting to the commodity can be difficult.  To help illustrate, here's a perfectly valid example from the application.




This is a very complex pallet to say the least.  Probably one that would never happen, but it is still "valid" as far as the application is concerned.

The report requirements state that, given the CargoLineId (represented by the green ball, pallet), list the commodities.  The report is not interested in displaying any details about the packaging.

Solution:
Using the self-join of table BookingPackage we can determine the immediate children of each record.  The child package will have a ParentBookingPackageId that matches the parent BookingPackageId.  Each package may or may not contain a commodity.  What we must do is walk our way down the tree of packages to get to the commodities.

The solution presented here could be used differently depending on the solution required.  Use the example as a guide and change it to suit your particular needs.

Here is the code in T-SQL:
/*
Create a temporary table to hold all PackageIds.
*/
DECLARE @PackageIdList TABLE (
    PackageId int )

/*
Create a temporary table to hold the CommodityLineIds.
This table eventually contains every Commodity attached
to the booking and is used as the result set.
*/
DECLARE @CommodityIdList TABLE (
    CommodityLineId int )

/*
Insert the top level packages
@BookingId is passed in as a parameter
*/
I
NSERT INTO @PackageIdList
SELECT BookingPackageId
FROM BookingPackage pkg
    INNER JOIN BookingCargoLine cargo on (cargo.BookingCargoLineId = pkg.BookingCargoLineId)
WHERE
    cargo.BookingId = @BookingId

/*
Get a count of second level packages
*/
DECLARE @pkgCount int
SET @pkgCount = (
    SELECT COUNT(BookingPackage.BookingPackageId)
    FROM BookingPackage
    INNER JOIN @PackageIdList list on (list.PackageId = BookingPackage.ParentBookingPackageId)
    WHERE BookingPackage.BookingPackageId NOT IN (SELECT PackageId FROM @PackageIdList)
    )

WHILE @pkgCount > 0
BEGIN
    /*
    Insert the package list into the temporary Package table
    If the PackageId is already collected into the temporary table,
    do not add it.
    With each pass, sub-packages are added to the list.
    */
    INSERT INTO @PackageIdList
    SELECT BookingPackage.BookingPackageId
    FROM BookingPackage
    INNER JOIN @PackageIdList list on (list.PackageId = BookingPackage.ParentBookingPackageId)
    WHERE BookingPackage.BookingPackageId NOT IN (SELECT PackageId FROM @PackageIdList)

    /*
    We just added packages to the list, check to see if there are more to add.
    Eventually, we will get to the lowest level of sub-packages and
    the count will be zero; ending the WHILE loop.
    */
    SET @pkgCount = (
        SELECT COUNT(BookingPackage.BookingPackageId)
        FROM BookingPackage
        INNER JOIN @PackageIdList list on (list.PackageId = BookingPackage.ParentBookingPackageId)
        WHERE BookingPackage.BookingPackageId NOT IN (SELECT PackageId FROM @PackageIdList)
        )
END

/*
Now that we have a list packages, we can derive the
Commodities that belong to each one.
*/
INSERT INTO @CommodityIdList
SELECT BookingCommodityId
FROM BookingCommodity
WHERE BookingPackageId IN (SELECT PackageId FROM @PackageIdList)
/*
Now we have a table of commodities that we can use in the rest of
the procedure as a joining table to retrieve the commodity details
for the report.
*/
*** END OF T-SQL

The secret to this working is the WHILE loop and the table @PackageIdList being used as a filter to determine the depth of the package tree.  We are inserting into the @PackageIdList table and, at the same time, using it as an inner join for parent packages.  The where clause is used to make sure we don't pump duplicate values into the table.

    INSERT INTO @PackageIdList
    SELECT BookingPackage.BookingPackageId
    FROM BookingPackage
    INNER JOIN @PackageIdList list on (list.PackageId = BookingPackage.ParentBookingPackageId)
    WHERE BookingPackage.BookingPackageId NOT IN (SELECT PackageId FROM @PackageIdList)

The final step is to fill a table variable, @CommodityIdList, with the list of commodity ids belonging to the main cargo line, or package.

     INSERT INTO @CommodityIdList
     SELECT BookingCommodityId
     FROM BookingCommodity
     WHERE BookingPackageId IN (SELECT PackageId FROM @PackageIdList)

If we wanted, we could have put a lot more information into this table.  I decided to use it as a joining table to get the details I need later on in the procedure.

Conclusion:
We end up with a result that mirrors recursion in T-SQL.  We didn't have to use cursors or extra procedures to make it work.  Now, if we were only using SQL Server 2005, we could achieve the same result with one statement using the WITH clause.  I'll find a good example and post on that next.
3/8/2006 4:34:11 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0]    |  Trackback
 Tuesday, February 21, 2006

I just read a very nice article on ASP.NET Best practices by Ali Khan.  It covers some items that most .NET developers should know by heart and others that we do know but sometimes forget.

The only thing I can find to disagree with is tip number 15.  It talks about using stored procedures instead of "ad-hoc" queries.  The speed benefit of this approach has met skepticism lately by numerous developers.  I definitely fall into this category.  I prefer keeping the maintenance of queries outside of the database.

2/21/2006 9:28:07 AM (Eastern Standard Time, UTC-05:00)  #    Comments [0]    |  Trackback
 Monday, February 20, 2006

So now you have a nice report written to an HTML file.  How do you open it for the user to view it using IE?  You could try this:

System.Diagnostics.Process.Start("iexplore.exe","MyReport.htm");

But this hasn't always worked for me.  Here is a sure fire way to put it all together.

Add a project reference to the COM library, Microsoft Internet Controls.  Add these two using statements:

using SHDocVw;
using System.Runtime.InteropServices;

Where you need to open the browser, add the following code:

explorer = new InternetExplorer(); 
if (explorer != null

     explorer.Visible = true
     object x = null;
     explorer.Navigate(@"MyReport.htm
", ref x, ref x, ref x, ref x); 
}

Thanks to a good friend of mine, Micheal Beall of Overdrive Technologies, for helping me with this.

***Originally posted on DotNetJunkies on February 16, 2005

2/20/2006 2:10:49 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0]    |  Trackback

Printing a web page that represents a report has been difficult for me in the past.  I want to print a header and/or a footer that repeats on every printed page.  The screen version certainly isn't paginated, so how can you print a good looking report?  Style (or CSS) to the rescue. 

Put the following Style tag, or something similar, in the header of the page.

<STYLE TYPE=”text/cssMEDIA=”screen, print>
<!--
TABLE {
  table-layout: fixed;
  border: 0;
  cellspacing: 1;
  cellpadding: 1;
  font-family: Arial;
  font-size: 8pt;
  }
TH {
  font-family: Arial;
  color: black;
  background-color: lightgrey;
  text-decoration: underline;
  }
THEAD {
  display: table-header-group;
  }
TFOOT {
  display: table-footer-group;
  }
-->
</STYLE>

Since nearly every report uses an HTML Table, this works very well.  The THEAD and TFOOT styles is what makes the table a report.  If you don't want to setup a style tag, you can enter the style right into the table.

<table style="table-layout:fixed">
    <colgroup>
        <col width="150"/>
        <col width="100"/>
        <col width="150"/>
    </colgroup>
    <thead style="display:table-header-group">
        <tr>
            <td>Header column 1</td>
            <td>Header column 2</td>
            <td>Header column 3</td>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>Body column 1</td>
            <td>Body column 2</td>
            <td>Body column 3</td>
        </tr>
    </tbody>
    <tfoot style="display:table-footer-group">
        <tr>
            <td>Footer column 1</td>
            <td>Footer column 2</td>
            <td>Footer column 3</td>
        </tr>
    </tfoot>
</table>
 
Sounds too easy to be true but it works very nicely.  Of course, adding more style (bolding, underlining, background and foreground colors, etc.) makes this a very nice reporting option.

***Originally posted on DotNetJunkies on February 16, 2005

2/20/2006 2:08:19 PM (Eastern Standard Time, UTC-05:00)  #    Comments [1]    |  Trackback

After examining numerous articles on printing with C#, I was about ready to toss in the towel and just copy & paste to Word (yuck).  Then I found How To Print the Content of a RichTextBox Control By Using Visual C# .NET on Microsoft's support site.

I was sure that printing a RichTextbox should be pretty easy.  All the articles made it very difficult, though.  This article shows the “correct” way to print.  The entire contents are printed; pictures, other graphics, colors, etc.

***Originally posted on DotNetJunkies on January 11, 2005

2/20/2006 2:05:53 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0]    |  Trackback
 Sunday, February 19, 2006

Welcome to my developer blog!  The entries here will focus on my experiences and thoughts as a software professional.  The first entries will be transferred from my first blog on dotnetjunkies.  I haven't posted there for quite a while.  I will post on a variety of topics; coding tips, interesting articles, cool products, project methodologies, etc.  If it has anything to do with software development, I'll be talking about it here.

My writing style is to be straight forward, sometimes from the hip.  I can be hard on others at times.  But I can also be very gratious when I find or read something with which I like or agree.  I can dish it out and I can take it.  If you read anything here that you agree, or disagree, with, please let me know.  I don't take things personally. 

Conversation, including an occasional heated disagreement, is great for the mind.  It solidifies your character.  So let's get started...

2/19/2006 12:05:13 PM (Eastern Standard Time, UTC-05:00)  #    Comments [2]    |  Trackback
Copyright © 2010 Mark Bonafe. All rights reserved.