I had an issue recently where I needed to sort on a uniqueidentifier column and read the data in .Net. I found that .Net sorts Guids differently than SQL Server.
You can see for yourself. Run the following code.
DECLARE @t TABLE (
g uniqueidentifier
);
INSERT INTO @t ( g ) VALUES ( '00000000-0000-0000-0000-000000000001' );
INSERT INTO @t ( g ) VALUES ( '00000000-0000-0000-0000-000000000010' );
INSERT INTO @t ( g ) VALUES ( '00000000-0000-0000-0000-000000000100' );
INSERT INTO @t ( g ) VALUES ( '00000000-0000-0000-0000-000000001000' );
INSERT INTO @t ( g ) VALUES ( '00000000-0000-0000-0000-000000010000' );
INSERT INTO @t ( g ) VALUES ( '00000000-0000-0000-0000-000000100000' );
INSERT INTO @t ( g ) VALUES ( '00000000-0000-0000-0000-000001000000' );
INSERT INTO @t ( g ) VALUES ( '00000000-0000-0000-0000-000010000000' );
INSERT INTO @t ( g ) VALUES ( '00000000-0000-0000-0000-000100000000' );
INSERT INTO @t ( g ) VALUES ( '00000000-0000-0000-0000-001000000000' );
INSERT INTO @t ( g ) VALUES ( '00000000-0000-0000-0000-010000000000' );
INSERT INTO @t ( g ) VALUES ( '00000000-0000-0000-0000-100000000000' );
INSERT INTO @t ( g ) VALUES ( '00000000-0000-0000-0001-000000000000' );
INSERT INTO @t ( g ) VALUES ( '00000000-0000-0000-0010-000000000000' );
INSERT INTO @t ( g ) VALUES ( '00000000-0000-0000-0100-000000000000' );
INSERT INTO @t ( g ) VALUES ( '00000000-0000-0000-1000-000000000000' );
INSERT INTO @t ( g ) VALUES ( '00000000-0000-0001-0000-000000000000' );
INSERT INTO @t ( g ) VALUES ( '00000000-0000-0010-0000-000000000000' );
INSERT INTO @t ( g ) VALUES ( '00000000-0000-0100-0000-000000000000' );
INSERT INTO @t ( g ) VALUES ( '00000000-0000-1000-0000-000000000000' );
INSERT INTO @t ( g ) VALUES ( '00000000-0001-0000-0000-000000000000' );
INSERT INTO @t ( g ) VALUES ( '00000000-0010-0000-0000-000000000000' );
INSERT INTO @t ( g ) VALUES ( '00000000-0100-0000-0000-000000000000' );
INSERT INTO @t ( g ) VALUES ( '00000000-1000-0000-0000-000000000000' );
INSERT INTO @t ( g ) VALUES ( '00000001-0000-0000-0000-000000000000' );
INSERT INTO @t ( g ) VALUES ( '00000010-0000-0000-0000-000000000000' );
INSERT INTO @t ( g ) VALUES ( '00000100-0000-0000-0000-000000000000' );
INSERT INTO @t ( g ) VALUES ( '00001000-0000-0000-0000-000000000000' );
INSERT INTO @t ( g ) VALUES ( '00010000-0000-0000-0000-000000000000' );
INSERT INTO @t ( g ) VALUES ( '00100000-0000-0000-0000-000000000000' );
INSERT INTO @t ( g ) VALUES ( '01000000-0000-0000-0000-000000000000' );
INSERT INTO @t ( g ) VALUES ( '10000000-0000-0000-0000-000000000000' );
SELECT * FROM @t order by g ;
It returns the data in the following bizarre order. Keep in mind the first row is the “smallest” number.
g |
---|
01000000-0000-0000-0000-000000000000 |
10000000-0000-0000-0000-000000000000 |
00010000-0000-0000-0000-000000000000 |
00100000-0000-0000-0000-000000000000 |
00000100-0000-0000-0000-000000000000 |
00001000-0000-0000-0000-000000000000 |
00000001-0000-0000-0000-000000000000 |
00000010-0000-0000-0000-000000000000 |
00000000-0100-0000-0000-000000000000 |
00000000-1000-0000-0000-000000000000 |
00000000-0001-0000-0000-000000000000 |
00000000-0010-0000-0000-000000000000 |
00000000-0000-0100-0000-000000000000 |
00000000-0000-1000-0000-000000000000 |
00000000-0000-0001-0000-000000000000 |
00000000-0000-0010-0000-000000000000 |
00000000-0000-0000-0001-000000000000 |
00000000-0000-0000-0010-000000000000 |
00000000-0000-0000-0100-000000000000 |
00000000-0000-0000-1000-000000000000 |
00000000-0000-0000-0000-000000000001 |
00000000-0000-0000-0000-000000000010 |
00000000-0000-0000-0000-000000000100 |
00000000-0000-0000-0000-000000001000 |
00000000-0000-0000-0000-000000010000 |
00000000-0000-0000-0000-000000100000 |
00000000-0000-0000-0000-000001000000 |
00000000-0000-0000-0000-000010000000 |
00000000-0000-0000-0000-000100000000 |
00000000-0000-0000-0000-001000000000 |
00000000-0000-0000-0000-010000000000 |
00000000-0000-0000-0000-100000000000 |
In the end, I decided to SELECT two bigint columns that indicate how SQL Server is sorting the data. This is CPU intensive, so it isn’t ideal, however it shows SQL Server’s strange sorting behaviour of the uniqueidentifier column.
CREATE FUNCTION dbo.GuidHigh
(
@g uniqueidentifier
)
RETURNS bigint
AS
BEGIN
DECLARE @s varchar(40);
SET @s = @g;
-- @s is in the format 3B3A8D04-5D0C-4E0C-AC69-EFC14EE7D849
SET @s = REPLACE(@s, '-', '');
-- @s is in the format 3B3A8D045D0C4E0CAC69EFC14EE7D849
DECLARE @highA varchar(40);
DECLARE @highB varchar(40);
SET @highA = SUBSTRING(@s, 21, 12);
SET @highB = SUBSTRING(@s, 17, 4);
DECLARE @high varchar(40);
SET @high = @highA + @highB;
DECLARE @MinBigInt numeric(21,0);
SET @MinBigInt = 9223372036854775808;
RETURN CAST(dbo.[HexStrToNumeric](@high) - @MinBigInt as bigint);
END
GO
CREATE FUNCTION dbo.[GuidLow]
(
@g uniqueidentifier
)
RETURNS bigint
AS
BEGIN
DECLARE @s varchar(40);
SET @s = @g;
-- @s is in the format 3B3A8D04-5D0C-4E0C-AC69-EFC14EE7D849
SET @s = REPLACE(@s, '-', '');
-- @s is in the format 3B3A8D045D0C4E0CAC69EFC14EE7D849
DECLARE @lowA varchar(40);
DECLARE @lowB varchar(40);
DECLARE @lowC varchar(40);
DECLARE @lowD varchar(40);
DECLARE @lowE varchar(40);
DECLARE @lowF varchar(40);
DECLARE @lowG varchar(40);
DECLARE @lowH varchar(40);
SET @lowA = SUBSTRING(@s, 15, 2);
SET @lowB = SUBSTRING(@s, 13, 2);
SET @lowC = SUBSTRING(@s, 11, 2);
SET @lowD = SUBSTRING(@s, 9, 2);
SET @lowE = SUBSTRING(@s, 7, 2);
SET @lowF = SUBSTRING(@s, 5, 2);
SET @lowG = SUBSTRING(@s, 3, 2);
SET @lowH = SUBSTRING(@s, 1, 2);
DECLARE @low varchar(40);
SET @low = @lowA + @lowB + @lowC + @lowD + @lowE + @lowF + @lowG + @lowH;
DECLARE @MinBigInt numeric(21,0);
SET @MinBigInt = 9223372036854775808;
RETURN CAST(dbo.[HexStrToNumeric](@low) - @MinBigInt as bigint);
END
GO
-- do not include "0x" in the parameter, just a string like "8E75EF35FF75A977"
CREATE FUNCTION dbo.[HexStrToNumeric](@hexstr varchar(16))
RETURNS numeric(21, 0) -- enough for 2^64
AS
BEGIN
DECLARE @hex char(2), @i int, @count int, @result numeric(21, 0), @power numeric(21, 0);
SET @result = 0;
SET @count = LEN(@hexstr)
SET @i = 1
SET @power = 1;
WHILE (@i <= @count)
BEGIN
SET @power = @power * 16;
SET @i = @i + 1
END;
SET @i = 1
WHILE (@i <= @count)
BEGIN
SET @power = @power / 16;
SET @hex = SUBSTRING(@hexstr, @i, 1)
SET @result = @result + @power *
CASE WHEN @hex LIKE '[0-9]'
THEN CAST(@hex as int)
ELSE CAST(ASCII(UPPER(@hex))-55 as int)
END
SET @i = @i + 1
END
RETURN @result
END
GO