Въведение
Ежедневната работа рядко изисква съхраняване на двоични данни директно в колоните на базата данни. В някои случаи обаче е много полезно.
Противно на общоприетото мнение, байтовите масиви могат да помогнат със значително повече от просто съхраняване на големи двоични обекти (документи, мултимедия и т.н.). Също така, те могат да се използват за съхраняване на хеш стойности и примерни данни за по-бързо търсене/анализ на високо ниво. Или те могат да съдържат байтове, които са в състояние ON/OFF в някакво електронно реле. Веднага щом започнем да мислим за хардуерни данни, съхранявани в бази данни, приложенията стават по-очевидни.
За разлика от типовете данни VARCHAR, при които трябва да се грижите за съпоставянето и кодовите страници, двоичните типове данни са серии от байтове (понякога наричани байтови масиви в обектно-ориентирани езици за програмиране), или с фиксиран (BINAR), или променлив (VARBINAR) размер.
За да разберем по-добре подробностите за двоичните типове, първо ще направим кратко въведение в шестнадесетичните числа, които са начинът, по който тези данни се съхраняват вътрешно.
Шестнадесетични числа
Ако сте пропуснали часа по шестнадесетични числа в гимназията, добро въведение може да се намери на специална страница в Уикипедия. Там можете да се запознаете с този формат на номериране.
За да разберете тази статия, е важно да знаете, че SQL Server Management Studio показва двоични данни в шестнадесетичен формат с префикс „0x“.
Няма голяма разлика между шестнадесетичния и десетичния формат на номериране. Шестнадесетичният формат използва знаци с основа 16 (0-9 и A-F) вместо основата-10 на десетичната нотация (0-9). Стойностите A-F са числа 10-15 от десетичната номерация.
Ето защо използваме шестнадесетична нотация. Тъй като един байт съдържа 8 бита, позволяващи 256 дискретни цели числа, е изгодно да се представят байтовете в шестнадесетичен формат. Ако се насочваме към диапазон 0-256, той се представя като 00-FF в шестнадесетична нотация. Префиксът в Management Studio е за яснота на четенето, за да подчертае, че показваме шестнадесетични числа, а не по подразбиране десетични стойности.
Ръчно преобразуване на стойност с помощта на CAST()
Тъй като двоичните стойности в тесния си смисъл са низове, можем да ги преобразуваме от числов формат в символен формат с помощта на CAST или КОНВЕРТИРАНЕ SQL методи.
Разгледайте примера, който използва CAST метод:
SELECT CAST('HexTest' AS VARBINARY);
SELECT CAST(0x48657854657374 AS VARCHAR);
Използване на стилове за преобразуване с CONVERT()
CONVERT() метод, за разлика от CAST() , има допълнителна опция за използване на стилове за преобразуване.
Стиловете за преобразуване са шаблони за правила, използвани в процеса на преобразуване. CONVERT() се използва най-вече в операции за дата/час. Когато данните са в нестандартен формат, те могат да се използват при преобразуване на двоични стойности. Имайте предвид, че двоичните типове данни не поддържат автоматично преобразуване на типове данни без правилни стойности на параметрите. Тогава SQL Server ще хвърли изключение.
Ако погледнем CONVERT() дефиниране на метод, виждаме, че са необходими два задължителни и един незадължителен параметър.
Първият параметър е целевият тип данни, а вторият е стойността, от която искаме да преобразуваме. Третият параметър в нашия случай може да има стойност 1 или 2 . Стойност 1 означава, че CONVERT() трябва да разглежда входния низ като шестнадесетичен низ в текстов формат и стойност 2 означава, че искате да пропуснете 0x префикс.
Разгледайте примерите, показващи това поведение:
DECLARE @MyString NVARCHAR(500)='0x48657854657374';
SELECT CONVERT(VARBINARY(MAX), @MyString );
-- String value is directly converted to binary value - we wanted is to change the datatype
-- and not convert "0x.." prefix to the hexadecimal value
SELECT CONVERT(VARBINARY(MAX), @MyString, 1);
Разлика между BINARY и VARBINARY
С двоични данни можем да използваме два вида типове данни – фиксиран размер и променлив размер. Или са БИНАРНИ и ВАРБИНАРНИ.
Ако използваме променливата с фиксиран размер, съдържанието винаги се разширява до определения му размер с допълване от 0x00 … – няма подпълване в променлива дължина. Използването на операция за сумиране на тези променливи не се изпълнява добавяне. Стойностите се добавят една към друга. Същото е като с типовете низове.
За да демонстрираме поведението на префикса, ще използваме два прости примера с операцията двоичен сбор:
SELECT CAST('T' AS BINARY(1)) + CAST('e' AS BINARY(1)) + CAST('s' AS BINARY(1)) + CAST('t' AS BINARY(1));
SELECT CAST('T' AS BINARY(2)) + CAST('e' AS BINARY(2)) + CAST('s' AS BINARY(2)) + CAST('t' AS BINARY(2));
Всяка стойност в оператора BINARY(2) е постфиксирана с 0x00 стойности.
Използване на целочислени стойности с двоични типове данни
SQL Server идва с вградени методи за преобразуване между числови и двоични типове. Демонстрирахме това, когато обърнахме теста низ в двоичен формат и след това обратно във формат BIGINT, без да използвате функцията ASCII():
SELECT CAST('Test' AS VARBINARY(MAX));
SELECT CAST(CAST('Test' AS VARBINARY(MAX)) AS BIGINT);
Просто преобразуване между знакови и шестнадесетични стойности
За да конвертирате между чартърни и шестнадесетични стойности, е полезно да напишете персонализирана функция, която да изпълнява тази операция последователно. Един възможен подход е по-долу:
-- DROP FUNCTION dbo.FN_CH_HEX(@InputValue CHAR(1)
CREATE OR ALTER FUNCTION dbo.FN_CH_HEX(@InputValue CHAR(1))
RETURNS CHAR(2)
AS
BEGIN
RETURN(CONVERT(CHAR(2), CAST(@InputValue AS BINARY(1)), 2));
END;
-- SELECT dbo.FN_CH_HEX('A')
Този път използвахме стойността на параметъра 2 в CONVERT() функция. Показва, че тази операция не трябва да се съпоставя с ASCII код и да се показва без 0x... префикс.
Примерен казус:Съхраняване на снимки в двоичен тип на SQL Server
Обикновено подхождаме към този проблем, като внедряваме персонализирано Windows/уеб приложение или пишем персонализиран SSIS пакет с C# код. В този пример ще използвам само езика SQL. Може да бъде по-полезно, ако нямате достъп до предните инструменти на базата данни.
За да съхраняваме снимки в таблицата на базата данни, трябва да създадем таблица, която да ги съхранява. Таблицата трябва да включва колони, съдържащи името на картината и двоичното съдържание на картината:
-- DROP TABLE T_BINARY_DATA
CREATE TABLE T_BINARY_DATA
(
PICTURE_ID INT IDENTITY(1,1) PRIMARY KEY,
PICTURE_NAME NVARCHAR(100),
PICTURE_FILE_NAME NVARCHAR(500),
PICTURE_DATA VARBINARY(MAX)
)
GO
За да разрешим зареждането на двоични данни в екземпляр на SQL Server, трябва да конфигурираме сървъра с две опции:
- Активирайте опцията OLE Automation Procedures
- Предоставяне на привилегията BulkAdmin на потребителя, изпълняващ процеса на импортиране на изображения.
Скриптът по-долу ще изпълни задачата под потребител с високи привилегии на екземпляр на SQL Server:
USE MASTER
GO
EXEC sp_configure 'show advanced options', 1;
GO
RECONFIGURE;
GO
EXEC sp_configure 'Ole Automation Procedures', 1;
GO
RECONFIGURE;
GO
-- Add 'bulkadmin' to the correct user
ALTER SERVER ROLE [bulkadmin] ADD MEMBER [NT AUTHORITY\SYSTEM]
GO
Сега можем да започнем да пишем процедурата за импортиране и експортиране:
-- DROP PROCEDURE dbo.proc_ImportBinary
-- DROP PROCEDURE dbo.proc_ExportBinary
CREATE PROCEDURE dbo.proc_ImportBinary
(
@PICTURE_NAME NVARCHAR(100)
, @FOLDER_PATH NVARCHAR(500)
, @PICTURE_FILE_NAME NVARCHAR(500)
)
AS
BEGIN
DECLARE @OutputPath NVARCHAR(4000);
DECLARE @TSQLDYN NVARCHAR(4000);
SET @OutputPath = CONCAT(@OutputPath,'\',@PICTURE_FILE_NAME)
SET @TSQLDYN = 'INSERT INTO T_BINARY_DATA(PICTURE_NAME,PICTURE_FILE_NAME,PICTURE_DATA) '
+ 'SELECT ' + '''' + @PICTURE_NAME + '''' + ',' + '''' + @PICTURE_FILE_NAME + '''' + ', * '
+ ' FROM Openrowset( Bulk ' + '''' + @OutputPath + '''' + ', Single_Blob) as img'
EXEC (@TSQLDYN)
END
GO
CREATE PROCEDURE dbo.proc_ExportBinary (
@PICTURE_NAME NVARCHAR(100)
, @FOLDER_PATH NVARCHAR(500)
, @PICTURE_FILE_NAME NVARCHAR(500)
)
AS
BEGIN
DECLARE @Binary VARBINARY (max);
DECLARE @OutputPath NVARCHAR(4000);
DECLARE @Obj INT
SELECT @Binary = (
SELECT CONVERT(VARBINARY(max), PICTURE_DATA , 1)
FROM T_BINARY_DATA
WHERE PICTURE_NAME = @PICTURE_NAME
);
SET @OutputPath = CONCAT(@FOLDER_PATH, '\', @PICTURE_FILE_NAME);
BEGIN TRY
EXEC sp_OACreate 'ADODB.Stream', @Obj OUTPUT;
EXEC sp_OASetProperty @Obj ,'Type',1;
EXEC sp_OAMethod @Obj,'Open';
EXEC sp_OAMethod @Obj,'Write', NULL, @Binary;
EXEC sp_OAMethod @Obj,'SaveToFile', NULL, @OutputPath, 2;
EXEC sp_OAMethod @Obj,'Close';
EXEC sp_OADestroy @Obj;
END TRY
BEGIN CATCH
EXEC sp_OADestroy @Obj;
END CATCH
SET NOCOUNT OFF
END
GO
Сега можем да използваме тези процедури от всяко клиентско приложение по много прост начин.
Нека си представим, че имаме снимки в C:\Pictures\Inp папка. За да заредим тези снимки, трябва да изпълним следния код:
-- Load picture to table row
exec dbo.proc_ImportBinary ‘MyPic’, ‘C:\Pictures\Inp’, ‘MyPic.jpg’
По подобен начин можем да експортираме данни в C:\Pictures\Out папка:
exec dbo.proc_ExportBinary ‘MyPic’, ‘C:\Pictures\Out’, ‘MyPic.jpg’
Заключение
Изборът между двоични обекти или алтернативни средства за съхранение на двоични данни в база данни (например съхраняване на файлови пътеки в база данни и извличането им от дисковото/облачното хранилище) зависи от множество фактори.
Общото правило е, че ако файлът е по-малък от 256 килобайта, трябва да го съхранявате в колоните VARBINARY. Ако двоичните файлове са по-големи от един мегабайт, трябва да ги съхранявате във файловата система. Ако имате FILESTREAM наличен в SQL Server версии 2008 и по-нови, той поддържа файловете под транзакционен контрол като логическа част от базата данни.
Ако решите да съхранявате двоични файлове в таблицата на SQL Server, използвайте отделна таблица само за двоично съдържание. След това можете да оптимизирате местоположението му за съхранение и да получите достъп до двигателя, вероятно като използвате отделни файлове и файлови групи за тази таблица. Подробната информация е достъпна в официалната статия на Microsoft.
Във всеки случай тествайте и двата подхода и използвайте този, който най-добре отговаря на вашите нужди.