Aug 15, 2008

Microsoft SQL Server – nested loops without cursors

I can’t think of a good enough examples on this topic. However, I’ll try to explain.

Let’s say we have 3 tables. Products, Sizes and Items. The Products table holds your products for example - T-Shirt, Shoes and so on. Sizes table holds the sizes for those items - Large, Medium and Small. The Items table holds a combination of valid items - like : Shoes Large, Shoes Small, Shoes Medium and so on.

Let’s say that you have 3 records in the Products table and 3 records in the Sizes table. You need to create all combinations between them (Shoes S, Shoes M, Shoes L …).

So in C# would look like this:

foreach(Record recordProduct in Products)
{
foreach(Record recordSize in Sizes)
{
// Check if the compound record (recordProduct.ProductID,
recordSize.SizeID) exist in Items table
// add it if it doesn't.
}
}

I think most of the developers will use a cursor to achieve this. But I didn’t. I don’t like cursors very much so I tried to do it my own way.

Here are the create scripts for those tables first:

Products:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Products](
[ProductID] [int] IDENTITY(1,1) NOT NULL,
[ProductName] [nvarchar](50) COLLATE Latin1_General_CI_AS NULL
)
ON [PRIMARY]


Sizes:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Sizes](
[SizeID] [int] IDENTITY(1,1) NOT NULL,
[SizeName] [nvarchar](50) COLLATE Latin1_General_CI_AS NULL
)
ON [PRIMARY]


And finally, Items:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Items](
[ItemID] [int] IDENTITY(1,1) NOT NULL,
[SizeID] [int] NOT NULL,
[ProductID] [int] NOT NULL
)
ON [PRIMARY]

We want to add all the combinations in the Items table. To make it a bit more complex we would imagine that some of the items will be already generated so prior adding a combination we would like to check if it does not exist.
Here is my script to achieve this:

INSERT INTO Items
SELECT S.SizeID, P.ProductID FROM Sizes S, Products P
WHERE (SELECT ItemID FROM Items WHERE SizeID=S.SizeID AND ProductID=P.ProductID) IS NULL

3 lines of code…
Run it. It should affect some rows when starting it for first time. Run it again, without adding new records to Products or Sizes. It should say: “0 rows affected.”. Now add a product or a size or both. Run again. It will generate the new combinations.

Seems to be fine.

Considerations:
First of all I don’t think you will have to update such table on a daily basis. You may have a table to connect two tables but typically you will not need to generate all possible combinations. You may want to do this if you are updating some database on staging / production server for example.

Second - I am not quite if this approach is faster than the cursors / temp tables or any other approaches you may come up with, so use your head, don’t trust me. I am proven to make fundamental mistakes (see Check if a character is lower case post for reference ;)).

Third - I think you can use this to add another nested loop, so you can have nested loop in a nested loop in a nested loop. Or, with other words, to generate all possible combination between 3 or 4 or even more tables. I can not think of something to stop you doing so.

And last but not least - if you really need to access a combination of two tables, you may consider using view to select them. This way you will not have a physical table to take care of. It’s up to you.

I really love critics, positive or negative ;)

Aug 14, 2008

Skype4COMLib and Blogger - get your posts as a mood text in skype (or do whatever you want with skype ;).

My dear friend Bobby is going on vacation today at 6 p.m. Since I’m going to miss her while she’s taking her vacation on Black Sea I decided to play a bit with Skype4COM and write a small program for her.


This is where everything started ;) (thanks, Bobby for the inspiration ;).
She had her skype mood text set to how many days are until her vacation takes place. I wanted to help her so I wrote a simple tool to countdown until 6 p.m. and change her Skype mood accordingly.

Well I am not going to explain you how I did this. I got better idea later. I decided to make a mood changer for me. The mood changer will take my blogger posts and rotate them as skype mood text.

Skype Mood Text Changer, made with Skype4COM Lib and .NET C#

First of all, where should I get my posts from? Well, there are two ways I can think of. The first one is to use the Blogger API (login with the user, get all user blogs, get all user posts, etc.).
The second one is to use the RSS feed that blogger automatically creates for each blog.
Since this is very small tool I think the first approach is too “heavy” and “complex”. After all we aren’t going to post to the blog or anything that will require us to login in Blogger. So I took the RSS approach.

Let’s see how you can create a small program to do this for you:
1. Start new windows application.
2. Right click your References folder in the solution explorer and choose Add Reference …
3. In the dialog box click on the “COM” tab and find Skype4COMLib.
4. Click OK.

Now you have that skype thing referenced in your project. Add it to your using clause:

using SKYPE4COMLib;

After we are here, and we know we are going to use RSS, we will need Xml namespace as well. Add it too:

using System.Xml;

Okay. In my approach, I wanted to display random post on every 10 seconds. This means I will need to store a post title -> post link pair somewhere. I did this in a generic List of string[] arrays. I am not quite sure how optimized is this but this is only sample and it works fine for me. If you can think of something more optimized - please do not hesitate to write a comment ;). For now, however, add the System.Collections namespace to your using clause:

using System.Collections;

Okay, we have everything we will need to create our small tool. Now add a timer on your form. It’s timer1 in my sample. Set it's interval to 10000 (10 seconds).

Add the following global variables to your project:

List<string[]> posts = new List<string[]>();
private SKYPE4COMLib.Skype skype;

Here is how your Form_Load event should look like:

    
private void Form1_Load(object sender, EventArgs e)
     {
        skype =
new SkypeClass();
        
this.WindowState = FormWindowState.Minimized;
        GetBlogs();
        timer1.Enabled =
true;
     }

What I want you to note in the Page_Load is that I am assigning the skype variable. This way it will remain linked to your skype until the program is runned.

I used routine GetBlogs() in this project, here is it:

private void GetBlogs()
{
XmlDocument docRSS = new XmlDocument();
docRSS.Load(
"http://donchevp.blogspot.com/feeds/posts/default?alt=rss");
    
foreach (XmlNode nodeItem in docRSS.SelectNodes("/rss/channel/item"))
    {
    
string title = nodeItem.SelectSingleNode("title").InnerText;
        
string href = nodeItem.SelectSingleNode("link").InnerText;
posts.Add(
new string[] { href, title });
    }

}


NOTE: replace the url in the docRSS.Load() method, otherwise you will promote my blog ;).

This routine simply takes all the posts and stores them in the List of string arrays as link, title pairs.
Now we need to display the posts as title “-“ link. This will happen in the timer1_Tick event:

private void timer1_Tick(object sender, EventArgs e)
{
int iPostIndex = int.MinValue;
    
if (posts.Count > 0)
    {
    
Random rnd = new Random();
        iPostIndex = rnd.Next(0, posts.Count);
        skype.CurrentUserProfile.MoodText = posts[iPostIndex][1] +
" - " + posts[iPostIndex][0];
}
}

Everything seems to work fine now. Run your program. Skype should ask you if you are sure you want to give access to this program to use skype. Agree with that. You should now see your posts rotating each 10 seconds.

Here is the complete code (I will also upload the project on Chameleon Bulgaria site, as codes in Blogger doesn’t appear very smoothly):

Form1.cs:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using SKYPE4COMLib;
using System.Xml;
using System.Collections;

namespace dayOffCounter
{
  
public partial class Form1 : Form
   {
    
List<string[]> posts = new List<string[]>();
    
private SKYPE4COMLib.Skype skype;
    
public Form1()
     {
        InitializeComponent();
     }

    
private void Form1_Load(object sender, EventArgs e)
     {
        skype =
new SkypeClass();
        
this.WindowState = FormWindowState.Minimized;
        GetBlogs();
        timer1.Enabled =
true;
     }

    
private void GetBlogs()
     {
        
XmlDocument docRSS = new XmlDocument();
        docRSS.Load(
"http://donchevp.blogspot.com/feeds/posts/default?alt=rss");
        
foreach (XmlNode nodeItem in docRSS.SelectNodes("/rss/channel/item"))
        {
          
string title = nodeItem.SelectSingleNode("title").InnerText;
          
string href = nodeItem.SelectSingleNode("link").InnerText;

           posts.Add(
new string[] { href, title });
        }
     }

    
private void timer1_Tick(object sender, EventArgs e)
     {
        
int iPostIndex = int.MinValue;
        
if (posts.Count > 0)
        {
          
Random rnd = new Random();
           iPostIndex = rnd.Next(0, posts.Count);
           skype.CurrentUserProfile.MoodText = posts[iPostIndex][1] +
" - " + posts[iPostIndex][0];
        }
     }
   }
}

Things to consider: Unfortunately skype has some constraints we can not avoid. First of all - the text seems to be too short to hold some of my posts, which results in links not being rendered correctly and leading to a wrong page (or even worse - to a page that doesn’t exist). I really wish to be able to set click here links as a mood text but it is not possible for now (at least I couldn’t find such functionality).

Happy codding!

Skype4COM Lib - how to register Skype ?


I wrote few tools, which only worked on my computer and not on the computer of my collegue - Bobby. She was having newer version of Skype and in the beggining I assumed it is a protocol problem. It finally appeared that the problem is her computer not having Skype4COMLib registered. So if you run in such a trouble, do the following :

1. Find the Skype4COM.dll file on your computer (typically it is in : C:\Program Files\Common Files\Skype)
2. Go to your Start Menu -> Run ...
3. Type cmd
4. In the command prompt type : regsvr32 Skype4COM.dll or "regsvr32 C:\Program Files\Common Files\Skype\Skype4COM.dll" depending on in which directory you are with the cmd (note the quotes that surround the path ;) ...


Everything should be fine now.

NOTE: I am writing this post not because it is something complex, but because I often forget how to register something ... Especially Skype :). Now I (and you) will have a reference, so don't be shy, add this post to your bookmarks :P

P.S. I hope I will be able to write a small tool to register Skype4COMLib