Wednesday, 9 February 2011

Splitting strings with auxiliary table of numbers

Couple of posts back I was showing how to combine several tags into a single string, separated with spaces.

Today, my dear Reader, I am going to show you how to split them back into the set of rows.

First, we are going to create an auxiliary table of numbers.
This is an extremely useful thing by itself, which I learned from the book of Itzik Ben-Gan: Inside Microsoft® SQL Server® 2008: T-SQL Programming, Microsoft Press, 2010.


IF OBJECT_ID (N'dbo.Numbers', N'U') IS NOT NULL
        DROP TABLE dbo.Numbers;
GO

CREATE TABLE dbo.Numbers
(
        N INT NOT NULL PRIMARY KEY
)
GO

SET NOCOUNT ON;
DECLARE @max INT = 1000
                , @rc INT = 1;
INSERT INTO dbo.Numbers VALUES(1);

WHILE @rc * 2 <= @max
BEGIN
        INSERT INTO dbo.Numbers
        SELECT n + @rc FROM dbo.Numbers;
       
        SET @rc = @rc * 2;
END

INSERT INTO dbo.Numbers
SELECT n + @rc FROM dbo.Numbers WHERE n + @rc <= @max;
GO

Then we do the trick:

IF OBJECT_ID('dbo.Split', 'IF') IS NOT NULL
DROP FUNCTION dbo.Split;
GO

CREATE FUNCTION dbo.Split (
        @string NVARCHAR(1000)
        , @separator NVARCHAR(1)
)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN
        WITH separated AS
        (
                SELECT
                        SUBSTRING(@separator + @string, n, LEN(@separator + @string)) s
                FROM dbo.Numbers
                WHERE
                        n <= LEN(@separator + @string)
                        AND SUBSTRING(@separator + @string, n, 1) = @separator
        )
        , cut AS
        (
                SELECT
                        LTRIM(SUBSTRING (s, 1, CHARINDEX(@separator, s + @separator, 2) - 1)) s
                FROM separated
        )
        , filtered (tag) AS
        (
                SELECT s
                FROM cut
                WHERE LEN(s) > 0
        )
        SELECT tag from filtered
GO

Tuesday, 8 February 2011

Page 7

This is fantastic innovative pager.
That's it for today.

Monday, 7 February 2011

Love to windowing functions

Technically, this post is not about romama.
This is just about how emotionally I feel about windowing functions in Sql Server 2005/2008.

Let's say we have a table



CREATE TABLE Products
(
        ProductID int NOT NULL,
        ProductName varchar(25),
        Price money NULL,
        CONSTRAINT PK_Products PRIMARY KEY NONCLUSTERED (ProductID)
 )
INSERT INTO Products VALUES (1, 'Widgets', 25)
INSERT INTO Products VALUES (2, 'Gadgets', 50)
INSERT INTO Products VALUES (3, 'Thingies', 75)
INSERT INTO Products VALUES (4, 'Whoozits', 90)
INSERT INTO Products VALUES (5, 'Whatzits', 5)
INSERT INTO Products VALUES (6, 'Gizmos', 15)
INSERT INTO Products VALUES (7, 'Widgets', 24)
INSERT INTO Products VALUES (8, 'Gizmos', 36)
INSERT INTO Products VALUES (9, 'Gizmos', 36)
GO



Removing duplicates has never been easier:



WITH a AS (
SELECT ROW_NUMBER() OVER (PARTITION BY ProductName ORDER BY ProductId) DupOrder
FROM Products )
DELETE FROM a WHERE DupOrder > 1



Fap-fap-fap

Sunday, 6 February 2011

Notes: First preview

So, let's see what we've got. 
Bold line is a note optional title.
Text inside a thin green rectangle is a main note text.
And we have tags below.

Everything is editable in place: you click on the text and you are good to go.
And main text area will kindly expand to make it even easier.

Saturday, 5 February 2011

Aggregate string concatenation

Here's my db diagram:

Now I'm writing a stored procedure to get all the notes for the given user.
No surprises I want CreationDate, Title, NoteText, and Color, but the thing is I also want to get all the tags related to every note.
In my perfectly normalized database tags are stored in a separate table, one record per tag.
But I want all the note tags to be fetched as a single string, separated with spaces.

So, I will use the beautiful and extremely fast technique devised by Michael Rys, a program manager with the Microsoft SQL Server development team, and Eugene Kogan, a technical lead on the Microsoft SQL Server Engine team.


IF OBJECT_ID(N'dbo.GetNotes', N'P') IS NOT NULL
        DROP PROCEDURE dbo.GetNotes;
GO

CREATE PROCEDURE dbo.GetNotes(@UserName NVARCHAR(256))
AS
BEGIN
        SET NOCOUNT ON;

        SELECT
                n.Id
                , n.CreationDate
                , n.Title
                , n.NoteText
                , c.Color
                , LTRIM((                       
                        SELECT
                                ' ' + t.TagText AS [text()]
                        FROM
                                dbo.Tag t
                        JOIN
                                dbo.Note2Tag nt ON t.Id = nt.TagId
                        JOIN
                                dbo.Note n1 ON nt.NoteId = n1.Id
                        WHERE
                                n1.Id = n.Id
                        FOR XML PATH('')
                )) as Tags
        FROM
                dbo.Note n
        JOIN
                dbo.Category c ON n.CategoryId = c.Id
        WHERE
                n.UserName = @UserName
END

Tuesday, 1 February 2011

Categories under test

Live critical:
1.       Create a category.
2.       Try to create a category with an empty title.
3.       Cancel category creation with an empty title.
4.       Cancel category creation with a valid title.
5.       Edit a category, change name only.
6.       Edit a category, change color only.
7.       Edit a category, change name and color.
8.       Cancel a category editing.
9.       Delete a category.

Other:
10.   Try to create a category with the name longer than 256 characters.
11.   Create a category with some Unicode characters.
12.   Open a category editor from manually created URL, ID set to non-existing value. Should work the same a new category creation.
13.   Open a category editor from manually created URL, invalid ID. Should redirect to an error page.