web-dev-qa-db-ja.com

SQL Server GUIDソートアルゴリズム。なぜですか?

UniqueIdentifiersの問題

私たちは既存のデータベースを使用しており、uniqueidentifiersを主キーと一部のテーブルの一部のnull許容列の両方として(残念ながら!)広範囲に使用しています。これらのテーブルで実行される一部のレポートがこれらのuniqueidentifiersで並べ替えられる状況に遭遇しました。これは、意味のある並べ替えを行う列が他にないためです(皮肉なことではありません)。その目的は、挿入された順序で項目を表示するようにソートすることでしたが、NewSequentialId()を使用して挿入されなかったため、時間の無駄です。

ソートアルゴリズムについての事実

とにかく、SQL Serverは、末尾の5番目のバイトグループ(6バイト)から始まり、1番目のバイトグループ(4バイト)に向かって移動し、3番目のバイトグループ(2バイト)の順序を右から左に逆にして、一意の識別子をバイトグループに基づいて並べ替える左右に、

私の質問

私は、この種のことが少しでも役立つ実際の状況があるかどうか知りたいと思っていました。

SQL Serverはどのようにuniqueidentifierを内部に格納しますか?

参考:

Alberto FerrariによるSQL Serverの発見GUID sort

以下のデータを持つuniqueidentifier列でOrder Byを使用すると、Uniqueidentifiersは以下のようにソートされます。

以下のデータは昇順で並べ替えられており、並べ替えの優先順位は5番目のバイトグループから1番目のバイトグループに向かって(逆方向)であることに注意してください。

-- 1st byte group of 4 bytes sorted in the reverse (left-to-right) order below -- 

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

-- 2nd byte group of 2 bytes sorted in the reverse (left-to-right) order below -- 

00000000-0100-0000-0000-000000000000
00000000-1000-0000-0000-000000000000
00000000-0001-0000-0000-000000000000
00000000-0010-0000-0000-000000000000

-- 3rd byte group of 2 bytes sorted in the reverse (left-to-right) order below -- 

00000000-0000-0100-0000-000000000000
00000000-0000-1000-0000-000000000000
00000000-0000-0001-0000-000000000000
00000000-0000-0010-0000-000000000000

-- 4th byte group of 2 bytes sorted in the straight (right-to-left) order below -- 

00000000-0000-0000-0001-000000000000
00000000-0000-0000-0010-000000000000
00000000-0000-0000-0100-000000000000
00000000-0000-0000-1000-000000000000

-- 5th byte group of 6 bytes sorted in the straight (right-to-left) order below -- 

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

コード:

Albertoのコードは、ソートが個々のビットではなくバイトで行われることを示すように拡張されました。

With Test_UIDs As (--                     0 1 2 3  4 5  6 7  8 9  A B C D E F
            Select ID =  1, UID = cast ('00000000-0000-0000-0000-100000000000' as uniqueidentifier)
    Union   Select ID =  2, UID = cast ('00000000-0000-0000-0000-010000000000' as uniqueidentifier)
    Union   Select ID =  3, UID = cast ('00000000-0000-0000-0000-001000000000' as uniqueidentifier)
    Union   Select ID =  4, UID = cast ('00000000-0000-0000-0000-000100000000' as uniqueidentifier)
    Union   Select ID =  5, UID = cast ('00000000-0000-0000-0000-000010000000' as uniqueidentifier)
    Union   Select ID =  6, UID = cast ('00000000-0000-0000-0000-000001000000' as uniqueidentifier)
    Union   Select ID =  7, UID = cast ('00000000-0000-0000-0000-000000100000' as uniqueidentifier)
    Union   Select ID =  8, UID = cast ('00000000-0000-0000-0000-000000010000' as uniqueidentifier)
    Union   Select ID =  9, UID = cast ('00000000-0000-0000-0000-000000001000' as uniqueidentifier)
    Union   Select ID = 10, UID = cast ('00000000-0000-0000-0000-000000000100' as uniqueidentifier)
    Union   Select ID = 11, UID = cast ('00000000-0000-0000-0000-000000000010' as uniqueidentifier)
    Union   Select ID = 12, UID = cast ('00000000-0000-0000-0000-000000000001' as uniqueidentifier)
    Union   Select ID = 13, UID = cast ('00000000-0000-0000-0001-000000000000' as uniqueidentifier)
    Union   Select ID = 14, UID = cast ('00000000-0000-0000-0010-000000000000' as uniqueidentifier)
    Union   Select ID = 15, UID = cast ('00000000-0000-0000-0100-000000000000' as uniqueidentifier)
    Union   Select ID = 16, UID = cast ('00000000-0000-0000-1000-000000000000' as uniqueidentifier)
    Union   Select ID = 17, UID = cast ('00000000-0000-0001-0000-000000000000' as uniqueidentifier)
    Union   Select ID = 18, UID = cast ('00000000-0000-0010-0000-000000000000' as uniqueidentifier)
    Union   Select ID = 19, UID = cast ('00000000-0000-0100-0000-000000000000' as uniqueidentifier)
    Union   Select ID = 20, UID = cast ('00000000-0000-1000-0000-000000000000' as uniqueidentifier)
    Union   Select ID = 21, UID = cast ('00000000-0001-0000-0000-000000000000' as uniqueidentifier)
    Union   Select ID = 22, UID = cast ('00000000-0010-0000-0000-000000000000' as uniqueidentifier)
    Union   Select ID = 23, UID = cast ('00000000-0100-0000-0000-000000000000' as uniqueidentifier)
    Union   Select ID = 24, UID = cast ('00000000-1000-0000-0000-000000000000' as uniqueidentifier)
    Union   Select ID = 25, UID = cast ('00000001-0000-0000-0000-000000000000' as uniqueidentifier)
    Union   Select ID = 26, UID = cast ('00000010-0000-0000-0000-000000000000' as uniqueidentifier)
    Union   Select ID = 27, UID = cast ('00000100-0000-0000-0000-000000000000' as uniqueidentifier)
    Union   Select ID = 28, UID = cast ('00001000-0000-0000-0000-000000000000' as uniqueidentifier)
    Union   Select ID = 29, UID = cast ('00010000-0000-0000-0000-000000000000' as uniqueidentifier)
    Union   Select ID = 30, UID = cast ('00100000-0000-0000-0000-000000000000' as uniqueidentifier)
    Union   Select ID = 31, UID = cast ('01000000-0000-0000-0000-000000000000' as uniqueidentifier)
    Union   Select ID = 32, UID = cast ('10000000-0000-0000-0000-000000000000' as uniqueidentifier)
)
Select * From Test_UIDs Order By UID, ID
37
Kash

アルゴリズムは、SQL Serverの担当者がドキュメント化しています: SQL Server 2005でのGUIDの比較

さらに、ソートはバイトグループのエンディアンに従います(ここを参照: グローバルに一意の識別子 )。グループ10-15および8-9はビッグエンディアン(ウィキペディアの記事のData4に対応)として格納されているため、ビッグエンディアンとして比較されます。他のグループはリトルエンディアンを使用して比較されます。

31
Simon Mourier

ネクロマンシング。
受け入れられた回答が少し曖昧であると感じる人のための特別なサービス。
コードはそれ自体で語り、魔法の部分は

System.Guid g
g.ToByteArray();
int[] m_byteOrder = new int[16] // 16 Bytes = 128 Bit 
    {10, 11, 12, 13, 14, 15, 8, 9, 6, 7, 4, 5, 0, 1, 2, 3};


public int Compare(Guid x, Guid y)
{
    byte byte1, byte2;

    //Swap to the correct order to be compared
    for (int i = 0; i < NUM_BYTES_IN_GUID; i++)
    {
        byte1 = x.ToByteArray()[m_byteOrder[i]];
        byte2 = y.ToByteArray()[m_byteOrder[i]];
        if (byte1 != byte2)
            return (byte1 < byte2) ? (int)EComparison.LT : (int)EComparison.GT;
    } // Next i 

    return (int)EComparison.EQ;
}

完全なコード:

namespace BlueMine.Data
{


    public class SqlGuid
        : System.IComparable
        , System.IComparable<SqlGuid>
        , System.Collections.Generic.IComparer<SqlGuid>
        , System.IEquatable<SqlGuid>
    {
        private const int NUM_BYTES_IN_GUID = 16;

        // Comparison orders.
        private static readonly int[] m_byteOrder = new int[16] // 16 Bytes = 128 Bit 
        {10, 11, 12, 13, 14, 15, 8, 9, 6, 7, 4, 5, 0, 1, 2, 3};

        private byte[] m_bytes; // the SqlGuid is null if m_value is null


        public SqlGuid(byte[] guidBytes)
        {
            if (guidBytes == null || guidBytes.Length != NUM_BYTES_IN_GUID)
                throw new System.ArgumentException("Invalid array size");

            m_bytes = new byte[NUM_BYTES_IN_GUID];
            guidBytes.CopyTo(m_bytes, 0);
        }


        public SqlGuid(System.Guid g)
        {
            m_bytes = g.ToByteArray();
        }


        public byte[] ToByteArray()
        {
            byte[] ret = new byte[NUM_BYTES_IN_GUID];
            m_bytes.CopyTo(ret, 0);
            return ret;
        }

        int CompareTo(object obj)
        {
            if (obj == null)
                return 1; // https://msdn.Microsoft.com/en-us/library/system.icomparable.compareto(v=vs.110).aspx

            System.Type t = obj.GetType();

            if (object.ReferenceEquals(t, typeof(System.DBNull)))
                return 1;

            if (object.ReferenceEquals(t, typeof(SqlGuid)))
            {
                SqlGuid ui = (SqlGuid)obj;
                return this.Compare(this, ui);
            } // End if (object.ReferenceEquals(t, typeof(UInt128)))

            return 1;
        } // End Function CompareTo(object obj)


        int System.IComparable.CompareTo(object obj)
        {
            return this.CompareTo(obj);
        }


        int CompareTo(SqlGuid other)
        {
            return this.Compare(this, other);
        }


        int System.IComparable<SqlGuid>.CompareTo(SqlGuid other)
        {
            return this.Compare(this, other);
        }


        enum EComparison : int
        {
            LT = -1, // itemA precedes itemB in the sort order.
            EQ = 0, // itemA occurs in the same position as itemB in the sort order.
            GT = 1 // itemA follows itemB in the sort order.
        }


        public int Compare(SqlGuid x, SqlGuid y)
        {
            byte byte1, byte2;

            //Swap to the correct order to be compared
            for (int i = 0; i < NUM_BYTES_IN_GUID; i++)
            {
                byte1 = x.m_bytes[m_byteOrder[i]];
                byte2 = y.m_bytes[m_byteOrder[i]];
                if (byte1 != byte2)
                    return (byte1 < byte2) ? (int)EComparison.LT : (int)EComparison.GT;
            } // Next i 

            return (int)EComparison.EQ;
        }


        int System.Collections.Generic.IComparer<SqlGuid>.Compare(SqlGuid x, SqlGuid y)
        {
            return this.Compare(x, y);
        }


        public bool Equals(SqlGuid other)
        {
            return Compare(this, other) == 0;
        }


        bool System.IEquatable<SqlGuid>.Equals(SqlGuid other)
        {
            return this.Equals(other);
        }


    }


}
4
Stefan Steiger

ここに別のアプローチがあります。 GUIDは、SQL Serverで発生するような通常の文字列比較の準備が整うだけです。これはJavaScriptですが、任意の言語に変換するのは非常に簡単です。

function guidForComparison(guid) {
  /*
  character positions:  
            11111111112222222222333333
  012345678901234567890123456789012345

  00000000-0000-0000-0000-000000000000

  byte positions:  
                          111111111111
  00112233 4455 6677 8899 001122334455
  */
  return guid.substr(24, 12) + 
         guid.substr(19, 4) + 
         guid.substr(16, 2) + 
         guid.substr(14, 2) + 
         guid.substr(11, 2) + 
         guid.substr(9, 2) + 
         guid.substr(6, 2) +
         guid.substr(4, 2) +
         guid.substr(2, 2) +
         guid.substr(0, 2);
};
0
Bernard