Monday, January 27, 2014

Unique Filtered Index

Consider a scenario where you need to create a table in which you need to store contact information, and for one of the columns, we need to store passport number for each contact, if one is available. Assuming that passport number is unique for each contact, but not all have passport, how can we ensure the uniqueness for the passport number enter while allowing null values for those contacts who don't have passport number?

Unique constraint/index would come to mind. However, the problem of using unique constraint/index in this scenario is while unique constraint/index would ensure that the data enter is unique, a column with unique constraint/index can only allow one null value in that column.

A quick demo for this using unique index (but the result should be similar if we use unique constraint as well):

IF (OBJECT_ID('dbo.ContactInfo') IS NOT NULL)
BEGIN
DROP TABLE dbo.ContactInfo
END

CREATE TABLE dbo.ContactInfo (
ContactID INT PRIMARY KEY IDENTITY (1,1),
ContactName VARCHAR(100) NOT NULL,
PassportNumber VARCHAR(25) NULL);

CREATE UNIQUE INDEX UI_PassportNumber
ON dbo.ContactInfo(PassportNumber)

Now let's try to test to make sure that we can't enter duplicate passport number:

INSERT INTO dbo.ContactInfo (ContactName, PassportNumber) VALUES ('John Doe', 'B1234567890');
INSERT INTO dbo.ContactInfo (ContactName, PassportNumber) VALUES ('Jane Doe', 'B1234567890');

We would get the following messages:

(1 row(s) affected)
Msg 2601, Level 14, State 1, Line 2
Cannot insert duplicate key row in object 'dbo.ContactInfo' with unique index 'UI_PassportNumber'. The duplicate key value is (B1234567890).
The statement has been terminated.

Which is to be expected, it would insert the information for John Doe just fine, but then it would failed to insert the information for Jane Doe since Jane Doe’s passport number is the same as John Doe’s.

image

Now let’s test to make sure that we can enter multiple contact info with blank passport number.

INSERT INTO dbo.ContactInfo (ContactName, PassportNumber) VALUES ('Mary Doe', NULL);
INSERT INTO dbo.ContactInfo (ContactName, PassportNumber) VALUES ('Jack Doe', NULL);

We would get the following messages:

(1 row(s) affected)
Msg 2601, Level 14, State 1, Line 2
Cannot insert duplicate key row in object 'dbo.ContactInfo' with unique index 'UI_PassportNumber'. The duplicate key value is (<NULL>).
The statement has been terminated.

It would insert the contact info for Marry Doe just fine, but it would fail when trying to insert the contact info for Jack Doe. This is because the unique constraint can allow only one null value.

image

Possible Solution

So how we can ensure that our passport column can accept null values while ensure uniqueness for the non-null value? With SQL Server 2008 and above, one possibility is by using the unique filtered index. A quick demo for this one:

IF (OBJECT_ID('dbo.ContactInfo') IS NOT NULL)
BEGIN
DROP TABLE dbo.ContactInfo
END

CREATE TABLE dbo.ContactInfo (
ContactID INT PRIMARY KEY IDENTITY (1,1),
ContactName VARCHAR(100) NOT NULL,
PassportNumber VARCHAR(25) NULL);

CREATE UNIQUE INDEX UI_PassportNumber
ON dbo.ContactInfo(PassportNumber)
WHERE PassportNumber IS NOT NULL

Let’s try to see what happen if we try to enter contact info with duplicate passport number:

INSERT INTO dbo.ContactInfo (ContactName, PassportNumber) VALUES ('John Doe', 'B1234567890');
INSERT INTO dbo.ContactInfo (ContactName, PassportNumber) VALUES ('Jane Doe', 'B1234567890');

We would get the following messages:

(1 row(s) affected)
Msg 2601, Level 14, State 1, Line 2
Cannot insert duplicate key row in object 'dbo.ContactInfo' with unique index 'UI_PassportNumber'. The duplicate key value is (B1234567890).
The statement has been terminated.

That works as expected. Only the record for John Doe that got inserted.

image

Now let’s try to see what happen if we try to enter multiple contact info with blank passport number:

INSERT INTO dbo.ContactInfo (ContactName, PassportNumber) VALUES ('Mary Doe', NULL);
INSERT INTO dbo.ContactInfo (ContactName, PassportNumber) VALUES ('Jack Doe', NULL);

We would get the following messages:

(1 row(s) affected)

(1 row(s) affected)


Both records are being inserted just fine, which what we are expecting.

image

Conclusion

We can use unique filtered index as a way to enforce uniqueness on filtered data. In the example above, it is to enforce uniqueness on a not null value on the passport number column. There is one caveat. Unlike unique constraint/index where it can be referenced by a foreign key constraint, unique filtered index can’t be referenced by a foreign key constraint.

So if we continue on from the above examples and try to create a new table named PassportVisit with the following definition:

IF (OBJECT_ID('dbo.PassportVisit') IS NOT NULL)
BEGIN
DROP TABLE dbo.PassportVisit
END

CREATE TABLE dbo.PassportVisit (
PassportNumber VARCHAR(25) FOREIGN KEY REFERENCES dbo.ContactInfo(PassportNumber),
DateofVisit DateTime,
CountryofVisit VARCHAR(50))

So we are trying to reference the passport number column on the passport visit table to the passport number on the contact info table. This will be successful if we have a unique constraint/index. But it will fail if we have unique filtered index. It will fail with the following messages:

Msg 1776, Level 16, State 0, Line 6
There are no primary or candidate keys in the referenced table 'dbo.ContactInfo' that match the referencing column list in the foreign key 'FK__PassportV__Passp__73B0DA39'.
Msg 1750, Level 16, State 0, Line 6
Could not create constraint. See previous errors.

Sunday, June 9, 2013

List SSAS User Roles Using PowerShell (Part 2)

A while ago, I have a blog post in regards to using PowerShell to list user roles and also their members. The blog post can be found here. I’ve gotten a few questions about expanding the PowerShell script to:

  1. List user roles and their members for all databases within a particular SQL Server Analysis Services (SSAS) server.
  2. Output the result into one table

Here’s a PowerShell script that can be use to accomplish those goals

[Reflection.Assembly]::LoadWithPartialName("Microsoft.AnalysisServices")

# SSAS server name variable
$SSASServerName = "ServerName"

# Try to connect to the SSAS server
$SSASServer = New-Object Microsoft.AnalysisServices.Server
$SSASServer.Connect($SSASServerName)

# Object to store the result
$Result = @()

# Get the SSAS databases and loop thru each of them
foreach ($DB in $SSASServer.Databases)
{
# Get the SSAS database
$SSASDatabase = $SSASServer.Databases.Item($DB.name)

# Get the roles available within the SSAS database and loop thru each of them
foreach ($Role in $SSASDatabase.Roles)
{
# Get the members within the role and loop thru each one of them
foreach ($UserName in $Role.Members)
{
# Create a new object that would store the database name, role name and member user name
$ItemResult = New-Object System.Object
$ItemResult | Add-Member -type NoteProperty -name DatabaseName -value $DB.Name
$ItemResult | Add-Member -type NoteProperty -name RoleName -value $Role.Name
$ItemResult | Add-Member -type NoteProperty -name UserName -value $UserName.Name

# Put the item result and append it to the result object
$Result +=$ItemResult
}
}
}

$Result | Select DatabaseName, RoleName, UserName | format-table -auto -wrap | Out-String

Thursday, June 6, 2013

List SQL Server Analysis Services Database Properties Using PowerShell

If you have SQL Server Analysis Services (SSAS) and want to list the SSAS databases using PowerShell, here’s a PowerShell script that can do that:

[Reflection.Assembly]::LoadWithPartialName("Microsoft.AnalysisServices")

# Variable for your SSAS server name
$SSASServerName = "ServerName"

# Connect to the SSAS server
$SSASServer = New-Object Microsoft.AnalysisServices.Server
$SSASServer.Connect($SSASServerName)

# Try to list the SSAS database Name
$SSASServer.Databases | Select Name

It turns out that you can do much more than listing your SSAS database name with this. You can get the SSAS database collation, compatibility level, created date, last processed, etc. To get the full list of properties that are available to you, try to run the following PowerShell script:

[Reflection.Assembly]::LoadWithPartialName("Microsoft.AnalysisServices")

# Variable for your SSAS server name
$SSASServerName = "ServerName"

# Connect to the SSAS server
$SSASServer = New-Object Microsoft.AnalysisServices.Server
$SSASServer.Connect($SSASServerName)

# Try to list the SSAS database properties that are available to you
$SSASServer.Databases | Get-Member -MemberType property | FT -wrap | Out-String

So now, if I want to get a list of SSAS database name, collation, compatibility level, created data and last processed date, I can run the following PowerShell script:

[Reflection.Assembly]::LoadWithPartialName("Microsoft.AnalysisServices")

# Variable for your SSAS server name
$SSASServerName = "ServerName"

# Connect to the SSAS server
$SSASServer = New-Object Microsoft.AnalysisServices.Server
$SSASServer.Connect($SSASServerName)

# Try to list the SSAS Database Name, Collation, Compatibility Level, Created Time, Estimated Size, Last Processed Date
$SSASServer.Databases | Select Name, Collation, CompatibilityLevel, CreatedTimeStamp, EstimatedSize, LastProcessed | FT -wrap | Out-String

Related Blog Post:

List SSAS User Roles Using PowerShell

Friday, May 24, 2013

Ubuntu + VirtualBox 4.2.12 Guest Additions = Low-Graphic Mode Error

I am using VirtualBox to run several test virtual machines. For most part, it has been pretty good. It’s lightweight and relatively easy and simple to use. Recently, I’ve upgraded VirtualBox to version 4.2.12. The upgrade process itself went well.

Then I started to update the guest additions on each of the virtual machines. Everything seems to be good… until it’s time to update the guest additions on Ubuntu virtual machine (I have Ubuntu 12.04 LTS). When I installed the guest additions, it seems to went well, it uninstall the previous version and install the guest additions for version 4.2.12. But then after I reboot that virtual machine, I got the following error message/ warning:

The system is running in low-grahics mode

Your screen, graphic card, and input device settings could not be detected correctly. You will need to configure these yourself.

image

I could not even get into the logon screen. Interesting …

Researching around the internet, it looks like there is a problem with Ubuntu and VirtualBox 4.2.12 guest additions. So I decided to try to revert back the guest addition to VirtualBox 4.2.10 guest additions. I was able to get the ISO for VirtualBox 4.2.10 guest additions from here.

Then I do the following:

  1. Start the Ubuntu virtual machine
  2. When I get “The system is running in low-graphics mode” error/warning, I press the CRTL-ALT-F1 to enter the tty1
  3. In VirtualBox menu for that Ubuntu system, I mount the VirtualBox 4.2.10 guest additions ISO that I have downloaded earlier. I just went to Devices – CD/DVD Devices – Choose a virtual CD/DVD disk file. Then select the ISO file.
  4. After login in tty1, I ran the command below. It would mount the cd and then run the VirtualBox 4.2.10 guest additions for Linux. It would uninstall the VirtualBox 4.2.12 guest additions and install the VirtualBox 4.2.10 guest additions.
    sudo mount /dev/cdrom /cdrom
    cd cdrom
    sudo sh VBoxLinuxAdditions.run
  5. Then once it’s done, I reboot the system by running the following command:
    sudo reboot

Once I did those, the Ubuntu virtual machine would boot fine.

I also have tried a fresh install of Ubuntu (creating a new virtual machine in VirtualBox 4.2.12). It was fine before I installed the guest additions on it... well I can only get 1024x768 as the max screen resolution, but I can access the Unity desktop. However, after I installed the VirtualBox 4.2.12 guest additions and reboot the system, I got the same error/warning message. Installing the VirtualBox 4.2.10 guest additions seems to be ok.

Monday, May 20, 2013

Good Bye Google Reader, Hello Microsoft Outlook

I’ve been using Google Reader to display RSS feeds from different sources (blogs, news sites, etc.). It certainly has done its job well. Recently, Google has decided to discontinue Google Reader and it will go offline on 1st July 2013.

Truth be told, there are many alternatives out there that I can use to substitute Google Reader. But for now, I am going to use Microsoft Outlook. It makes sense for me since I already have Microsoft Outlook installed on my computer as part of the Microsoft Office Suite and I have been using Microsoft Outlook on daily basis for a while now. In addition, the RSS feed would appear just like a typical Outlook e-mail message (a format in which I have already familiar with).

To add RSS feed to Microsoft Outlook is pretty straight forward. On the left folder navigation, there should be “RSS Feeds” folder. Right click on that folder and select “Add a New RSS Feed”. Then after that you can enter the link to the RSS subscription that you have. Once done, it would create a new folder and Microsoft Outlook would drop any new feed from that RSS subscription to that new folder. Each RSS subscription would have a different folder.

If you have several RSS subscriptions that you want to import from Google Reader to Microsoft Outlook, you should be able to do that by exporting the data from your Google Reader account by going to Google Takeout. You can just export and download your Google Reader account (in the form of a zip file). Inside that zip file, there should be “Subscriptions.xml” file. Extract that file. Then, in Microsoft Outlook, right click on “RSS Feeds” folder, and select “Import an OPML file”. When given the chance, select the XML file that you have extracted. Microsoft Outlook should be able to read the XML file and list the subscriptions that you have in Google Readers. It would also allow you to select which RSS subscription that you want to add in Microsoft Outlook.

I must admit that Microsoft Outlook lacks the features that Google Reader have. For one, in Google Reader, when you enter it, you can see the new RSS feeds from all your subscription in one page (Google Reader was able to aggregate the RSS feeds). In addition, you can organize your RSS subscriptions very easily in Google Reader. These features just don’t seems to be there in Microsoft Outlook (at least as far as I am aware of).

Google Reader is in the “cloud”. So as long as you have access to a compatible browser and the Internet, you can access your RSS subscriptions easily. With Microsoft Outlook, currently you will be limited to your local computer, where you have Microsoft Outlook installed and the RSS subscription(s) setup. Unless, if you are using Microsoft Exchange hosted e-mail. In that case, you might be able to see your RSS subscription(s) from web mail as well. Just be aware that the RSS feeds might be counted against your Exchange mailbox size quota.

Microsoft Outlook as RSS reader certainly has its limitations compared to Google Reader. However, for now, it satisfies my need as a simple and easy to use RSS reader (at least until I find a better one).

Additional Notes:

Alternatively, instead of Microsoft Outlook, you can also use Windows Live Mail (you might also need to have Windows Live Account), which is part of the Windows Essentials and its free.