Использование и преобразование типов данных SQL к типам языка программирования С/С++
Вернуться в Доки-Токи
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Автор: Andrey Rjavskov(Rzhavskov) as rjaan <rjaan@yandex.ru> Мало написать базу данных и спроектировать её с учётом форм нормализаций, но так же необходимо обеспечить правильное преобразование данных из SQL к тому языку программирования на котором будет написан клиент СУБД PostgreSQL. В этой статье пойдет речь как преобразовывать типы данных СУБД PostgreSQL, далее по тексту SQL, к основным типам языка C/C++. А именно пойдет речь как преобразовывать основные типы данных SQL , к которым относятся целочисленные, бинарные, символьные(текстовые) и серийные типы. Так же здесь расскажу о том, как избегать использование монетарного типа, который был не так давно запрещён для использования. Типы времени достойны отдельного разговора, поэтому им будет посвящена отдельная статья. Ниже в таблице представлены основные типы SQL c учетом их преобразования к основным языка программирования С для архитектуры процессора x86, которая имеется в виду в этой статье. Эти типы представляют целые числа, которые не имеют фрактальную часть, но их допустимые значения будут находится в различных диапазонах этих значений. Попытка выхода за пределы такого диапазона будет сопровождаться ошибкой. К целочисленным типам относятся smallint,integer, bigint. В документация к PostgreSQL сказано, что тип smallint рекомендуется использовать, когда нужно рационально использовать дисковое пространство, но надо помнить, что у этого диапазона слишком маленький размах в значениях(см. пред. табл.). Тип integer обычно используется для представления целочисленных значений, а вот тип bigint следует использовать только тогда, когда есть в этом необходимость. Потому что тип integer намного быстрее. Теперь, что касается экономии дискового пространства за счет использования типа smallint тут вопрос спорный и требует отдельного рассмотрения. Давайте проиллюстрируем применение целочисленных типов с использованием API-функций библиотеки libpq, которая предоставляет собой C-клиент для PostgreSQL для использования в программах, написанных на C/C++. Но сначала, разработчику в ОС Debian GNU/Linux нужно установить следующие пакеты: Сразу хочу сказать, что реализация API-функций на С, не запрещает их использовать при программировании на языка С++. Поэтому, примеры того как использовать libpq привожу на С, а вы в свою очередь, эти же примеры с успехом можете применять у себя в программах, написанных на C++. Ещё несколько слов о используемой здесь аутентификации, которая предусматривается демоном PostgreSQL. Для авторизированного доступа пользователей к базам данных мной используется связывание пользователей ОС и ролей SQL, имеющие право создавать соединения с сервисом SQL. Как сделать такое связывание рекомендую почитать статью ОС Debian GNU/Linux: Разграничение прав доступа пользователей в СУБД PostgreSQL. Поэтому не удивляетесь, что при соединении с SQL-сервером автор не указывает имена ролей(пользователей) и их пароли. Теперь через psql(1) создаем базу данных test_db, с которой и будем далее тестировать наши типы SQL c типами C.
Далее, пишем клиент, который добавляет записи в таблицу substances:
SQL-таблица substances состоит из атрибутов, т.е. из столбцов, имеющие только целочисленные типы и хранит по-мимо идентификатора сущности(id), еще класс(class) и вид(kind). Мало того, контрольное ограничение UNIQUE атрибута id, снимает необходимость вручную создавать индексы, которые нужны будут во-время операций выброк с 1000000 сущностей, которые будем делать с помощью SQL-запроса SELECT через psql(1).А добавлять эти сущности будем с помощью теста test-substance. Наклонять типы в сторону переполнения целого будем позже, а сейчас просто протестистируем каждый на своем PC, сколько займет времени, чтобы записать 1000000 записей в таблицу substances. Запускаем тест test-substance, который добавляет 1000000 сущностей в SQL-таблицу substances. После выполнения теста, делаем выборку через SELECT для последних десяти сущностей из 1000000 добавленных. Время выполнения теста test-substance достаточно велико, что свидетельствуют цифры дампа test-substance. Файлы исходного кода и данных test-substance Makefile test-config.h test-common.h test-common.c test-substance.c Дамп выполнения test-substance
Дамп psql(1). Выполнение SELECT(выборка последних 10 записей из SQL-таблицы substance)
Во время тестирования добавления 1000000 записей в таблицу substances, у меня родилась идея наклонить SQL-тип smallint в сторону переполнения целого(Integer Underflow). Для чего написал тест, который добавляет в таблицу substances записи со значениеми атрибута kind, удовлетворяющие условию переполнения целого(Integer Underflow). В id будут записываться итерации выполнения теста. По ним мы сможем судить на какой итерации произошло это переполнение. Как мы можем увидеть из дампа ниже, SQL-тип smallint соответствует С-типу signed short int, а С-тип usigned short int не может быть использован, т.к. неизбежно приведет к переполнению целого. Поэтому С-тип usigned short лучше не использовать, чтобы не обременять себя лишними проверками. Так же по аналогии с SQL-типом smallint, с SQL-типами integer и bigint не стоит использовать C-типы unsigned long int и соответственно unsigned long long int. Файлы исходного кода и данных test- substance
Makefile
test-config.h
test-common.h
test-common.c
test-overtype.c Дамп psql(1). Выполнение test-substance Дамп psql(1). Выполнение SELECT(все записи таблицы substance)
3. Типы с произвольным количеством разрядовК таким SQL-типам относятся decimal и numеric. SQL-тип numеric позволяет масштабировать значения десятичных чисел до 1^1000 и производить c ними точные вычисления. Этот тип является полностью эквивалентным к decimal. Тип numеric рекомендуется использовать для хранения денежно-финансовых сумм, где требуется точность вычислений с ними. Этот тип должен применяться взамен монетарного типа. Декларация типа numеric состоит из двух частей: дробной части и целой. Дробная часть определяет количество разрядов после запятой в правой части. Целая часть определяет количество разрядов(имеется в виду в десятичном исчислении) до запятой в левой части числа. Принято следующее представление SQL-типа: NUMERIC(precision, scale) где precision — задает масштаб целой части. Масштаб применяется для ограничения ее размерности. Значение precision должно быть в диапазоне 1 от 1000. Значение precision равное 1,2 запрещают использование целой части, потому что значение атрибута типа numeric должено быть меньше единицы. Соответственно, значение precision равное 3 устанавливает лимит значений атрибута типа numeric меньше 10, 4 — меньше 100, 5 — меньше 1000 и т.д. scale задет количество разрядов в правой части, т.е. задает количество знаков после запятой. Так scale равное 2 установит соответственно два знака после запятой, поэтому число 0.239 будет округлено по правилам математического округления до 0.24 и записано в таблицу. Дамп psql(1). Выполнение INSERT(вставка записи в таблицу price_list)
Рассмотрим следующий пример. Создадим таблицу price_list с двумя атрибутами текстовым product и числовым price. Соответственно, назначим атрибуту price тип TEXT, а атрибуту product тип NUMERIC(10,2), что нам позволит существенно сэкономить память, которая будет выделяться для каждой записи. Таким образом, мы устанавливаем максимальное число 9 999 999 999 для значения атрибута NUMERIC, что будет вряд ли превышено в средних компаниях чьи финансовые операции вряд перевалят за 1 трлн. рублей за одну операцию, что не во всех финансовых институтах и организациях это мы сможем наблюдать. В то же время, например для розничной торговли можно использовать ограничение в 1 млрд. рублей. Дамп psql(1). Cоздание SQL-таблицы price_list
Теперь напишем небольшой тест test-numeric, который будет вносить записи в таблицу price_list . А именно цены на овощи:
Исходный код программы test-numeric Makefile test-config.h test-common.h test-common.c test-numeric.c Дамп выполнения test-numeric
Дамп psql(1). Выполнение SELECT(выборка всех записей из таблицы price_list)
4. Типы чисел с плавающей точкойТипы real и double относятся к неточным числовым типам. Под неточностью тут понимается то, что некоторые значения не могут быть непосредственно конвертированы к внутреннему формату и сохраняются приближено. Поэтому в результате сохранения и перевода этих значений этого типа могут иметь некоторые отличные значения. Если есть необходимость в соблюдении точности вычислений, например в денежно-финансовых суммах, рекомендуется использовать NUMERIC. В то же время, если Вы хотите использовать типы с плавающей точкой в каких-либо важных вычислениях, особенно, где возможно возникновение пограничных случаев( бесконечности и переполнения), Вам необходимо использовать их очень осторожно. Ещё нужно помнить, что сравнение двух чисел с плавающей точкой может приводить к их эквивалентности или не приводить. При использовании на сборках ядра для 32-х разрядной архитектуре x86, точность составляет диапазон значений 1E-37 – 1E+37 для одиночной точности с количеством знаков после запятой не менее 6-ти и диапазона значений 1E-307 – E+308 для двойной точности с кол-вом знаков не менее 15-ти после запятой. 5. Серийные типыЭти типы позволяют индексировать записи по уникальным числовым значениям в таблицах вместо использования специальных последовательностей, как показано ниже в примере. Следующая запись:
будет эквивалентна:
В этой таблице создаётся целочисленный столбец, который по-умолчанию увеличивает на одно значение каждую запись, используя для этого генератор последовательности. В следующем примере мы увидим, что после создания таблицы tablename вместе с ней была создана последовательность. Дамп psql(1). Выполнение CREATE TABLE(создание sql-таблицы tablename)
Как видно из дампа psql(1), после того как мы создали таблицу tablename с единственным атрибутом colname, по-мимо таблицы ещё была создана последовательность tablename_colname_seq. Поэтому утверждать, что данный тип позволяет избежать использование последовательностей не совсем верно. Если мы взглянем на таблицу tablename более внимательнее то увидим, что кроме контрольного ограничения NOT NULL ещё используется DEFAULT, который с помощью встроенной функции nextval() вызывает последовательность, в результате обращения к которой возвращается следующее значение colname и которое будет затем, записано в только что созданную запись(new) таблицы. Чтобы пощупать это на практике предлагаю добавить в таблицу tablename текстовый столбец dsc и затем добавить в неё несколько записей, а потом посмотреть её содержимое через SELECT. Дамп psql(1). Выполнение INSERT(вставка записей в таблицу tablename)
При использовании типа SERIAL в атрибуте colname автоматически создается ограничение NOT NULL, которое вводит запрет на ввод нулевых значений. Но, так как вам скорее всего захочется использовать ограничение UNIQUE или PRIMARY KEY, чтобы исключить вероятность появления дублирующих значений, то вам нужно будет устанавливать эти контрольные ограничения самим, так как они не создаются автоматически. Ниже показано как добавлять в таблицу контрольное ограничение для исключения дублирующих значений атрибута colname в записях таблицы tablename. Дамп psql(1). Выполнение ALTER TABLE(добавление контрольных ограничений)
6. Символьные типыSQL определяет два основных типа character varying(n) и character(n), где n принимает только положительные значения. Эти типы предназначены для хранения строк длинной в n-символов . При попытке сохранить в ячейки строку больше чем n-символов приведет к ошибке или в некоторых случаях просто она будет обрезана по максимальной длине ячейки, указанной при определении типа. При помещении строки меньше чем n, все остальные ячейки будут заполонены пробелами автоматически.
Так же нужно помнить, что varchar(n) и char(n) являются псевдонимами SQL-типа character varying(n) и, соответственно, character(n) . SQL-тип character без объявление его длинны будет эквивалентен character(1) . Для указание длинны размера строки так же используется character varying. СУБД PostgreSQL имеет тип text, который позволяет хранить строки любой длинны. Будьте осторожны при использовании SQL-типа text, т.к. он не включен в стандрат SQL, и соответсвенно может не поддерживаться другими СУБД. Ниже приводится примеры использования символьных типов. Создаем таблицу test_chars с единственным атрибутом colchars и добавляем запись из четырех символов. После чего проверяем через SELECT, что запись добавлена в таблицу вместе с выводом длинны записанных символьных данных с помощью символьную SQL-функции char_length. Дамп psql(1). Примеры использования символьных типов
Ниже мы записываем ещё несколько записей разной длины до тех пор пока не сталкнемся с ошибкой. Она выделена жирным курсивом, и следующим за ней предупреждение при выполнении INSERT, что произошло неявное обрезание до 5 символов. Но, мы так же видим, что ошибка не была обнаружена при записи good с некоторым количеством пробелов, которая превышает размер в 5-ть символов. Дамп psql(1). Примеры использования символьных типов. Исключительные ситуации
После выполнения проверочного SELECT мы выясняем, что too long был оберзан доtoo l, до длинны, которая составляет 5-ть символов. Тип bytea позволяет хранить бинарные строки, являющейся последовательностью чисел в восьмеричной системе отсчета. Отличие данного типа от символьных, состоит в том, что он хранит числа в восьмеричной системе отсчёта отличных от нуля и диапазона «не печатаемых» символов, кроме чисел находящихся вне диапазона значений от 32 до 127. Так же не зависит от того, сколько байт требуется для поддержки локали. При вводе чисел в восьмеричной системе, их необходимо экранировать, когда они используются как часть SQL-выражения. В общем, нужно конвертировать десятичное значение в трех-значащее число и присоединить его к двум обратным слэшам, которые должны быть вначале. В следующей таблице отображены символы, которые должны быть экранированы. В табл. 3, даны примеры того как экранируются числа в восьмеричной системе исчисления.
Как мне удалось вычитать из документации RedHat к базам данных, этот тип рекомендуется использовать для хранения данных больших размеров. Столбец с типом bytea может содержать свыше 1 гигабайт бинарных данных, которые содержаться в таблицы и отправляются к клиентским программам по запросам. Для обработки таких запросов клиент должен иметь большое количество оперативной памяти для работы с этими данными. Дамп psql(1). Выполнение CREATE TABLE(создание таблицы images)
Теперь остается написать нам небольшой тест test-bytea чей код будет написан на С, который будет добавлять тестовое изображение в SQL-таблицу images через API библиотеки libpq-5.0, которая входит в состав дистрибутива Debian GNU/Linux. Она будет делать:
Файлы исходного кода и данных test-bytea Makefile test-config.h bytea-common.h bytea-common.c test-bytea.c tux.png Дамп выполнения test-bytea
Дамп psql(1). Выполнение SELECT(Выборка всех записей из таблицы images)
Некоторые замечания к test-bytea В процессе написания теста test-bytea выяснилось, что SQL-тип bytea не такой простой как кажется на первый взгляд. Во-первых, с ним нельзя использовать PQexec(), которая заставляет оперировать с текстовыми массивами фиксированной длинны. Поэтому автор выбрал для выполнения SQL-выражения, добавляющий массив типа bytea API-функцию PQexecParams(), которая позволяет передавать текстовые массивы переменной длинны. Во-вторых, для добавление в PostgreSQL данных с типом bytea мне пришлось изменить параметр client_encoding() в файле postgresql.conf, который устанавливает кодировку, используемую по-умолчанию SQL_ASCII. Но это не очень хороший подход , поэтому лучше для этого использовать в SQL-запросе INSERT INTO images ( dsc, img ) VALUES ( $1::text, $2::bytea ); функцию convert_to( $2, 'SQL_ASCII')::bytea со значением в втором параметре SQL_ASCII', задающую кодировку ASCII. Иначе, если используется UTF-8 или какая-нибудь другая кодировка выполнение SQL-команды INSERT может завершится сбоем из-за неправильной интерпретации символов. Тогда SQL-выражение примет вид: INSERT INTO images(dsc,img) VALUES($1::text,convert_to($2,'SQL_ASCII')::bytea);", 2, NULL, q, NULL, NULL, 0 );. 9. Булевский ТипСУБД PostgreSQL обеспечивает поддержку стандартом SQL тип boolean. Этот тип может иметь только два состояние: "true" или "false". Третье состояние "unknown" представлено в SQL нулевым значением. Размерность этого типа 1 байт. Для его хранение в С рекомендуется использовать тип char. Значение "true" может принимать следующие значения: TRUE 't' 'true' 'y' 'yes' '1' Значение "false" может принимать следующие значения: FALSE 'f' 'false' 'n' 'no' '0' Использование TRUE и FALSE является предпочтительным в SQL-выражении. Следующий пример иллюстрирует использование этого типа. Дамп psql(1). Пример использование булевского типа
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Copyright © 2010 rjaan as Andrey Rjavskov(Rzhavskov) <rjaan@yandex.ru> <arjavskov@gmail.com> |
Сайт создан в системе uCoz
|