Преобразуйте SQL Server varbinary (max) в набор первичных ключей типа int - PullRequest
1 голос
/ 29 февраля 2012

Отказ от ответственности: не мой код, не мой дизайн базы данных!

У меня есть столбец censusblocks(varbinary(max), null) в таблице базы данных MS SQL Server 2008 (для простоты назовите его foo).

Этот столбец на самом деле является null или длинным списком от 1 до n из int. int s на самом деле являются внешними ключами другой таблицы (назовите ее censusblock с pk id типа int), нумерацией от 1 до ~ 9600000.

Я хочу запросить , чтобы извлечь список censusblocks из foo, и использовать извлеченный список int из каждой строки, чтобы найти соответствующую строку censusblock. Есть длинный скучный остаток запроса, который будет использоваться оттуда, но начинать его нужно с блоков переписи, извлеченных из столбца foo таблицы censusblocks.

Это преобразование и поиск в настоящее время обрабатываются на среднем уровне, с небольшим служебным классом .NET для преобразования из List<int> в byte[] (и наоборот), который затем записывается в / считывается из дБ как varbinary. Я хотел бы сделать то же самое, чисто в SQL.

Требуемый запрос будет выглядеть примерно так:

SELECT f.id, c.id
FROM foo f 
LEFT OUTER JOIN censusblock c ON 
c.id IN f.censusblocks --this is where the magic happens
where f.id in (1,2)

Что приведет к:

f.id   |   c.id

 1         8437314
 1         8438819
 1         8439744
 1         8441795
 1         8442741
 1         8444984
 1         8445568
 1         8445641
 1         8447953
 2         5860657
 2         5866881
 2         5866881
 2         5866858
 2         5862557
 2         5870475
 2         5868983
 2         5865207
 2         5863465
 2         5867301
 2         5864057
 2         5862256

Примечание: 7-значные результаты совпадают. Диапазон, как указано выше, 1-7 цифр.

Фактический столбец censusblocks выглядит как

SELECT TOP 2 censusblocks FROM foo

, что приводит к

censublocks

0x80BE4280C42380C7C080CFC380D37580DC3880DE8080DEC980E7D1
0x596D3159858159856A59749D59938B598DB7597EF7597829598725597A79597370

Для дальнейшего пояснения, вот основные методы преобразования классов .NET:

    public static List<int> getIntegersFromBytes(byte[] data)
    {
        List<int> values = new List<int>();
        if (data != null && data.Length > 2)
        {
            long ids = data.Length / 3;
            byte[] oneId = new byte[4];
            oneId[0] = 0;
            for (long i = 0; i < ids; i++)
            {
                oneId[0] = 0;
                Array.Copy(data, i * 3, oneId, 1, 3);
                if (BitConverter.IsLittleEndian)
                { Array.Reverse(oneId); }
                values.Add(BitConverter.ToInt32(oneId, 0));
            }}
        return values;
    }

    public static byte[] getBytesFromIntegers(List<int> values)
    {
        byte[] data = null;
        if (values != null && values.Count > 0)
        {
            data = new byte[values.Count * 3];
            int count = 0;
            byte[] idBytes = null;
            foreach (int id in values)
            {
                idBytes = BitConverter.GetBytes(id);
                if (BitConverter.IsLittleEndian)
                { Array.Reverse(idBytes); }
                Array.Copy(idBytes, 1, data, count * 3, 3);
                count++;
            } }
        return data;
    }

Ответы [ 3 ]

3 голосов
/ 29 февраля 2012

Пример того, как это можно сделать.Это вряд ли масштабируется блестяще.Если у вас есть таблица чисел в вашей базе данных, ее следует использовать вместо nums_cte.

. Это работает путем преобразования двоичного значения в буквенную шестнадцатеричную строку с последующим чтением его в виде 8-символьных кусков

-- create test data
DECLARE @foo TABLE
(id int ,
 censusblocks varbinary(max)
)

DECLARE @censusblock TABLE
(id int)

INSERT @censusblock (id)
VALUES(1),(2),(1003),(5030),(5031),(2),(6)


INSERT @foo (id,censusblocks)
VALUES (1,0x0000000100000002000003EB),
(2,0x000013A6000013A7)


--query
DECLARE @biMaxLen bigint
SELECT @biMaxLen = MAX(LEN(CONVERT(varchar(max),censusblocks,2))) FROM @foo

;with nums_cte
AS
(
    SELECT TOP (@biMaxLen) ((ROW_NUMBER() OVER (ORDER BY a.type) - 1) * 8) AS n
    FROM master..spt_values as a
    CROSS JOIN master..spt_values as b
)
,binCTE
AS
(
    SELECT d.id, CAST(CONVERT(binary(4),SUBSTRING(s,n + 1,8),2) AS int) as cblock
    FROM (SELECT Id, CONVERT(varchar(max),censusblocks,2) AS s FROM @foo) AS d
    JOIN nums_cte
    ON n < LEN(d.s)
)
SELECT *
FROM    binCTE as b
LEFT
JOIN    @censusblock c
ON      c.id = b.cblock
ORDER BY b.id, b.cblock

Вы также можете рассмотреть возможность добавления существующих методов преобразования .Net в базу данных в виде сборки и доступа к ним через функции CLR.

1 голос
/ 01 марта 2012

Это не по теме, но я не удержался от написания этих преобразований, поэтому они используют IEnumerable s вместо массивов и List s. Это не может быть быстрее само по себе, но является более общим и позволит вам выполнить преобразование без загрузки массива целом сразу, что может быть полезно, если массивы, с которыми вы имеете дело, большие.

Вот оно, за что оно стоит:

static IEnumerable<int> BytesToInts(IEnumerable<byte> bytes) {

    var buff = new byte[4];

    using (var en = bytes.GetEnumerator()) {

        while (en.MoveNext()) {

            buff[0] = en.Current;
            if (en.MoveNext()) {
                buff[1] = en.Current;
                if (en.MoveNext()) {
                    buff[2] = en.Current;
                    if (en.MoveNext()) {
                        buff[3] = en.Current;
                        if (BitConverter.IsLittleEndian)
                            Array.Reverse(buff);
                        yield return BitConverter.ToInt32(buff, 0);
                        continue;
                    }
                }
            }

            throw new ArgumentException("Wrong number of bytes.", "bytes");

        }

    }

}

static IEnumerable<byte> IntsToBytes(IEnumerable<int> ints) {

    if (BitConverter.IsLittleEndian)
        return ints.SelectMany(
            b => {
                var buff = BitConverter.GetBytes(b);
                Array.Reverse(buff);
                return buff;
            }
        );

    return ints.SelectMany(BitConverter.GetBytes);

}

Ваш код, похоже, любит кодировать int в 3 байта вместо 4, что может вызвать проблемы со значениями, которые не помещаются в 3 байта (включая отрицательные значения) - это намеренно?

Кстати, вы должны иметь возможность адаптировать этот (или ваш) код для выполнения в SQL Server CLR . Это не совсем «в SQL», но «в СУБД».

0 голосов
/ 29 февраля 2012

Вы можете использовать Convert (int, censusBlock) для преобразования значения varchar в значение int.
Вы можете присоединиться к этому столбцу. Или я неправильно понял вопрос?

...