Перевод статьи про древовидные структуры в SQL на английский:
Показаны сообщения с ярлыком SQL. Показать все сообщения
Показаны сообщения с ярлыком SQL. Показать все сообщения
16 янв. 2025 г.
29 нояб. 2016 г.
GDMNN: Задача #1
В продолжение вчерашнего разговора. Первая задача: представить альтернативную структуру таблиц для документов, бухгалтерских проводок и складского движения.
Цель:
Цель:
- Унифицировать механизмы фиксации и учета движения, обобщив и распространив их не только на движение ТМЦ, но и на движение (изменение, трансформацию) любого объекта учета.
- Избавиться от дублирования данных и непрозрачных функций преобразования (поля документа => аналитические признаки в проводках).
- Любое поле документа может быть использовано в качестве аналитического признака при построении отчетов.
- Отойти от ограниченной структуры шапка-позиции. Для сложных документов предусмотреть наличие нескольких датасетов с произвольным уровнем вложенности.
- Для сумовых данных, используемых с целью ускорения выборок (INV_BALANCE), предусмотреть неблокирующую схему обновления, чтобы отказаться от автокомита в складских документах (комита частично введенного документа).
- Статус документа: черновик, отложенный, готовый.
- Уменьшить размер базы и, как следствие, увеличить скорость операций по изменению и выборке.
Labels:
база данных,
идеи,
оптимизация,
проблема,
Firebird,
SQL
24 апр. 2016 г.
Задание на неделю #1. Firebird 3
На этой неделе произошло событие, которого мы с нетерпением ждали шесть долгих лет -- вышла третья версия сервера Firebird. В двух словах предыстория такова: Interbase, от которого в 2000-м году отпочковался Firebird, был задуман в те времена, когда многопроцессорные системы с десятками гигабайт оперативной памяти встречались разве что в фантастических рассказах. Когда же научно-технический прогресс догнал и перегнал самые смелые фантазии, именно внутренняя архитектура сервера стала основным тормозом. По сути, в многопользовательском сценарии у системного администратора было два выбора: или использовать оперативную память для кэширования данных, но тогда запросы из всех подключений будут выполняться только на одном процессоре/ядре (архитектура SuperServer), или задействовать все процессоры/ядра, но тогда не будет общего кэша и скорость будет зависеть от производительности дисковой подсистемы (архитектура Classic). Тот случай, когда хрен редьки не слаще.
Изменения в архитектуре SuperServer движка Firebird 3 теперь позволяют последнему выполнять запросы сетевых клиентов параллельно на разных процессорах/ядрах, при доступе к общему кэшу, что теоретически делает SuperServer выбором по умолчанию при развертывании системы на предприятии. Как оно получится на практике -- зависит от надежности тройки. В начале 2000-х мы перевели всех клиентов с SuperServer на Classic по двум причинам: падение процесса SuperServer означает обрыв всех коннектов и потерю данных во всех открытых транзакциях у сетевых пользователей; выполнение тяжелого запроса одним из клиентов практически парализует работу всех остальных.
У одного нашего клиента база данных имеет размер 50 Гб, количество одновременных подключений 50-60, на сервере 128 Гб оперативной памяти и 20 физических ядер. План: использовать SuperServer, установить кэш размером 50 Гб и выделить под буфер сортировки 32 Гб. Учитывая, что дисковая подсистема построена на RAID контроллере с 2 Гб энергонезависимой памяти, теоретически, это позволит вообще исключить прямое обращение к дискам. Т.е. производительность сервера базы данных будет определяться процессором, памятью и скоростью обмена с RAID контроллером.
Напомним, что именно производительность дисковой подсистемы всегда возглавляла список факторов, влияющих на общую производительность СУБД. Мы ожидаем ускорения выполнения запросов минимум на порядок. О достигнутых результатах напишем в этом блоге.
Практически все нововведения из третьей версии найдут применение в Гедымине:
-
Передача НУЛЛ значений по сети битовой маской.
Сейчас каждое поле с НУЛЛ значением занимает при передаче 4 байта + длина поля. Например, в таблице с бухгалтерскими проводками у нас десятки полей-аналитических признаков, которые могут быть не заполнены. Экономия может достигать нескольких сотен байт на каждую запись.
Возможность увеличить TCP буфер до 32 Кб и применить компрессию данных при передаче.
В интернете есть свидетельства о том, что скорость возрастает десятикратно при подключении по сетям с большой латентностью. Например, к серверу в удаленном офисе по VPN каналу.
Шифрование файла базы данных. Теперь злоумышленник не сможет получить доступ к конфиденциальной информации просто переписав файл на сервер с чистой установкой Firebird и известным паролем к учетной записи SYSDBA.
Window функции в SQL запросах позволяют объединять вместе данные и агрегатные значения по заданным группам этих данных. До Firebird 3 такую задачу можно было решить либо подзапросами (крайне медленно, так как подзапрос будет выполняться для каждой записи), либо выполнением в цикле отдельных запросов для каждой группы и объединением их результатов в единый набор данных с помощью EXECUTE BLOCK или STORED PROCEDURE, либо алгоритмической обработкой на клиенте (например, внутри Fast Report при построении отчета).
“Linger” Database Closure for Superserver -- период времени, в течение которого сервер сохраняет в памяти все ресурсы, после закрытия последнего подключения к базе данных -- позволит ускорить загрузку пакета пространств имен, так как переподключение к базе данных выполняется после каждого ПИ, содержащего метаданные.
DDL триггеры позволят отказаться от выполнения процедуры at_p_sync при старте системы для синхронизации информации о структуре базы данных с содержимым AT_ таблиц.
Другие изменения и улучшения, на которых мы подробнее остановимся в следующих выпусках.
Labels:
безопасность,
идеи,
Firebird,
SQL,
Weekly
27 янв. 2016 г.
Поиск кольцевых ссылок в реляционной базе данных рекурсивным SQL запросом
Наш предыдущий подход к поиску кольцевых ссылок, изложенный тут, заключался в том, чтобы дать рекурсивному запросу возможность зациклиться и контролировать количество итераций. При достаточно большом их числе мы могли сказать, что скорее всего в данных присутствует цилическая связь. Само число следовало подбирать эмпирически, анализируя данные конкретной задачи.
Между тем, существует способ однозначного выявления циклов без "наматывания" кругов по графу. Рассмотрим граф, показанный на рисунке ниже:
В базе данных он хранится в виде матрицы смежностей в таблице t:
CREATE TABLE t (
a INTEGER,
b INTEGER
)
Соответственно, исходные данные:
INSERT INTO t VALUES (1, 2); INSERT INTO t VALUES (2, 3); INSERT INTO t VALUES (3, 4); INSERT INTO t VALUES (3, 5); INSERT INTO t VALUES (3, 6); INSERT INTO t VALUES (2, 7); INSERT INTO t VALUES (6, 1);Для поиска кольцевых ссылок напишем запрос:
with recursive tree as
(
select distinct
a as initial, a, b, -1 as prev
from
t
union all
select
iif(tr.initial <> tt.a, tr.initial, -1) as initial,
tt.a,
tt.b,
tr.a as prev
from
t tt JOIN tree tr ON
tr.b = tt.a and tr.initial > 0
)
select
*
from
tree
where
initial = -1
Запрос покажет все первые сегменты A - B (и предыдущие, т.е. сегменты непосредственно приведшие к циклу, PREV - A), всех возможных циклов в заданных данных. В нашем случае:
INITIAL A B PREV -1 1 2 6 -1 2 3 1 -1 2 7 1 -1 3 4 2 -1 3 5 2 -1 3 6 2 -1 6 1 3При необходимости проверить не входит ли в кольцо конкретный узел, следует прописать его ИД в первом запросе рекурсивного CTE:
with recursive tree as
(
select distinct
a as initial, a, b, -1 as prev
from
t
where
a = _some_id_
union all
select
iif(tr.initial <> tt.a, tr.initial, -1) as initial,
tt.a,
tt.b,
tr.a as prev
from
t tt JOIN tree tr ON
tr.b = tt.a and tr.initial > 0
)
select
*
from
tree
where
initial = -1
20 февр. 2015 г.
Интервью с Jim Starkey, создателем СУБД Interbase.
I think we're living in a world where the expertise of programmers is now very heavily oriented towards mobile applications and fancy GUIs. So dealing with practical aspects of data management in the application just isn't going to happen.
Labels:
база данных,
тэхналёгіі,
цікава,
SQL
14 янв. 2015 г.
Temporal SQL
Слюнки текут. Доживем ли мы до поддержки Temporal SQL в Firebird? В кратце: для указанных таблиц сервер связывает значение записи с определенным временным интервалом. Разумеется, интервалов может быть несколько и для каждого будет храниться своя запись. При извлечении указываем период и, вуаля, получаем актуальные значения именно для этого периода. Например, печатаем накладную за 1930 г. -- видим Кооператив "Геркулес". Тот же бланк, но за 2000 г. -- ООО "Геркулес". А в 2015 г. может и целое ЧУП "Геркулес", если не прикроют конечно.
Labels:
SQL
16 дек. 2014 г.
Древовидная структура -> HTML
В копилку полезных запросов. Берем дерево команд Исследователя и получаем на выходе HTML код.
EXECUTE BLOCK RETURNS(name VARCHAR(200)) AS DECLARE VARIABLE prev_indent INTEGER = 0; DECLARE VARIABLE indent INTEGER; DECLARE VARIABLE I INTEGER; DECLARE VARIABLE n VARCHAR(200); BEGIN name = '
- ';
SUSPEND;
FOR
WITH RECURSIVE
group_tree AS (
SELECT id, parent, name,
CAST('' AS VARCHAR(255)) AS indent
FROM gd_command
WHERE parent IS NULL
UNION ALL
SELECT g.id, g.parent, g.name,
h.indent || rpad('', 2)
FROM gd_command g JOIN group_tree h
ON g.parent = h.id
)
SELECT
CHARACTER_LENGTH(gt.indent),
TRIM(gt.name)
FROM
group_tree gt
INTO :indent, :n
DO BEGIN
I = :indent - :prev_indent;
IF (:I > 0) THEN
BEGIN
name = '
- ' || :n || ' '; SUSPEND; END name = '
- ';
SUSPEND;
prev_indent = :indent;
END
IF (:I < 0) THEN
BEGIN
name = '
5 июл. 2014 г.
Синхронизация БД с несколькими приложениями Android
Здесь мы писали о синхронизации между Андроид устройством и сервером Гедымин.
Поскольку у нас появляется второе приложение, упомянутый алгоритм следует расширить для поддержки произвольного количества приложений и произвольного количества наборов данных в рамках каждого приложения. А именно:
-
В таблицу с версиями данных добавляем поля:
-
ИД приложения, для которого готовятся данные. Целое число. Определяется проектировщиком/разработчиком.
РУИД потребителя данных.
8 июн. 2014 г.
Пропали буквы "И" и "ш" после восстановления базы mediawiki
Вот же послал нам высший разум наказание в виде так называемой СУБД MySQL. Неважно, как круто корпорация Oracle выглядит в бизнес отчетах, важно, что ваши бесценные данные могут испортиться в результате простой операции архивного копирования. А если архивируется база mediawiki, где в односимвольных кодировках записаны строки в UTF-8, которые при бэкапе будут побуквенно переведены в UTF-8... Удивительно, но есть люди, которые даже могут во всем этом разобраться.
Из кириллического подмножества символов UTF-8 почему-то не повезло буквам "ш" (0xD188) и "И" (0xD098). Вместо них пишутся в файл коды 0xD13F и 0xD03F.
Лечим следующим образом. После восстановления из архива базы mediawiki сформируем запросы для обновления символьных полей. Для буквы "И":
SELECT
CONCAT('UPDATE ', table_name, ' SET ', column_name,
' = REPLACE(', column_name, ',
CONCAT( CHAR(208), CHAR(63) ),
CONCAT( CHAR(208), CHAR(152) ));')
FROM columns
WHERE
table_schema = 'your_schema_name_here'
AND data_type = 'varchar'
И для буквы "ш":
SELECT
CONCAT('UPDATE ', table_name, ' SET ', column_name,
' = REPLACE(', column_name, ',
CONCAT( CHAR(209), CHAR(63) ),
CONCAT( CHAR(209), CHAR(136) ));')
FROM columns
WHERE
table_schema = 'your_schema_name_here'
AND data_type = 'varchar'
Результаты приведенных запросов -- готовые SQL команды для замены неправильных последовательностей байтов на правильные во всех строковых полях. Экспортируем их в текст и выполняем на базе данных.
UPD: Версия MySQL 5.5.32-cll-lve. Бэкап базы данных через phpMyAdmin 4.1.8 и cPanel 11.42.1.
14 мая 2014 г.
CURRENT OF vs поиск по ключу
Какое же утро без хорошего эксперимента. Берем встроенный сервер Firebird 2.5.3 и чистую БД:
Page size 8192 ODS version 11.2 Page buffers 75 Database dialect 3 Attributes force writeСоздаем две идентичных по структуре таблицы:
create table t1 (i integer not null) create table t2 (i integer not null)Одинаково заполняем каждую из них миллионом случайных значений:
execute block
as
declare variable i integer = 0;
declare variable v integer = 0;
begin
while (i < 1000000) do
begin
v = round(rand() * 2000000000);
insert into t1 values (:v);
insert into t2 values (:v);
i = :i + 1;
end
end
Для таблицы t1 создаем индекс:
create index t1x on t1 (i)Делаем коммит транзакции и переподключение к базе данных. Организуем скан таблицы t1 и случайным образом удаляем четверть записей через поиск по индексированному полю:
execute block
as
declare variable i integer;
begin
for select i from t1 into :i
do
begin
if (rand() < 0.25) then
delete from t1 where i = :i;
end
end
Статистика выполнения:
Execute : 10 717.00 ms Read : 239 855 Writes : 6 250 Fetches : 4 264 681 Marks : 250 286 IR : 250 286 NIR : 999 934 Deletes : 250 286Не совсем понятно почему NIR не миллион. Все остальное вполне логично. Коммит транзакции и еще раз удаляем четверть, теперь от оставшихся записей:
Execute : 582 243.00 ms Read : 414 519 Writes : 246 390 Fetches : 5 712 020 Marks : 944 206 IR : 187 214 NIR : 749 693 Deletes : 187 214 Expunges : 250 286Почти 10 минут! Обратите внимание на сумасшедшее количество Writes и Marks. Сборка мусора и перестроение индекса? Далее, берем таблицу t2 и удаляем из нее четверть записей, но с помощью курсора и конструкции CURRENT OF:
execute block
as
declare variable c cursor for
(select i from t2);
declare variable i integer;
begin
open c;
while (1=1) do
begin
fetch c into :i;
if (row_count = 0) then
leave;
if (rand() < 0.25) then
delete from t2 where current of c;
end
close c;
end
Статистика:
Execute : 8 362.00 ms Read : 6 138 Writes : 6 064 Fetches : 2 762 582 Marks : 250 100 NIR : 1 000 000 Deletes : 250 100Обратите внимание, насколько меньше чтений по сравнению с удалением по индексированному полю. Выполнение на 20% быстрее за счет того, что не надо дергать индекс. Коммит транзакции и еще раз удаляем четверть записей:
Execute : 8 252.00 ms Read : 6 137 Writes : 6 067 Fetches : 3 587 315 Marks : 693 789 NIR : 749 900 Deletes : 187 455 Expunges : 250 100Выполнился даже быстрее чем первый запрос, несмотря на сборку мусора, и в 70 (!!!) раз быстрее чем запрос с удалением по индексированному полю. Непонятно почему так велико значение Marks, ведь удаляется всего 187 455 записей. Подсчитаем количество записей в первой таблице:
select count(*) from t1Статистика:
Execute : 493 697.00 ms Read : 181 339 Writes : 181 084 Fetches : 3 009 620 Marks : 561 642 NIR : 562 500 Expunges : 187 214Все ясно, сборка мусора напоролась на индекс. 8 минут на кофе. Теперь считаем во второй:
select count(*) from t2Статистика:
Execute : 6 443.00 ms Read : 6 137 Writes : 6 064 Fetches : 2 261 901 Marks : 374 910 NIR : 562 445 Expunges : 187 455Упс! Мы ожидали увидеть сравнимые цифры по времени выполнения для первой и второй таблицы, но разница составила 76 (!!!) раз. Все из-за наличия в первой таблице индекса, который при подсчете количества никак не используется, но катастрофически тормозит сборку мусора. Попытаемся выяснить как влияет наличие индекса на выполнение операции CURRENT OF. Возвращаемся к исходной базе данных. Будем удалять записи из таблицы t1 с помощью конструкции CURRENT OF:
Execute : 8 050.00 ms Read : 6 138 Writes : 6 064 Fetches : 2 765 717 Marks : 251 145 NIR : 1 000 000 Deletes : 251 145В одно время с удалением из таблицы, по которой нет индекса. Второй цикл удаления:
Execute : 688 480.00 ms Read : 240 628 Writes : 240 341 Fetches : 4 592 695 Marks : 945 817 NIR : 748 855 Deletes : 186 248 Expunges : 251 145Самое большое время, которое нам пришлось наблюдать сегодня. Сборка мусора и индекс явно не дружат друг с другом. Для оценки влияния сборки мусора повторим все операции с самого начала на исходной базе данных с флагом подключения no_garbage_collect. Первое удаление из таблицы t1 (поиск по индексированному полю):
Execute : 12 418.00 ms Read : 239 735 Writes : 6 231 Fetches : 4 260 434 Marks : 249 809 NIR : 999 939 IR : 249 809 Deletes : 249 809Странно, но NIR снова не миллион. Куда деваются около шестидесяти чтений непонятно. Второе удаление:
Execute : 9 563.00 ms Read : 181 842 Writes : 6 189 Fetches : 3 702 914 Marks : 187 849 NIR : 750 162 IR : 187 849 Deletes : 187 849Записей стало меньше и время сканирования и удаления уменьшилось соответственно. Для второй таблицы удаление через курсор. Первый проход:
Execute : 8 112.00 ms Read : 6 138 Writes : 6 064 Fetches : 2 764 184 Marks : 250 634 NIR : 1 000 000 Deletes : 250 634Второй:
Execute : 8 034.00 ms Read : 6 137 Writes : 6 064 Fetches : 2 574 131 Marks : 187 283 NIR : 749 366 Deletes : 187 283Обратите внимание, что цифры идентичны тем, которые мы имели при включенной сборке мусора. Т.е. нет индекса и сборка мусора не проблема. Получаем количество записей для первой таблицы:
Execute : 546.00 ms Read : 6 137 Writes : 2 Fetches : 2 012 281 Marks : 0 NIR : 562 342Для второй:
Execute : 531.00 ms Read : 6 137 Writes : 2 Fetches : 2 012 281 Marks : 0 NIR : 562 083Выводы:
-
Нижесказанное применимо к частным случаям массовой обработки данных.
Если по таблице нет индексов, то включение/выключение сборки мусора практически не влияет на производительность.
Если по таблице созданы индексы, то сборка мусора способна радикально, в десятки и сотни раз, замедлить ход процесса.
В нашем примере CURRENT OF был на 20-25% быстрее чем поиск по индексированному полю.
CURRENT OF может применяться на таблицах, где индексов нет вообще. Совершенно очевидно, что удаление с поиском по неиндесированному полю выполнялось бы в нашем случае часами, если не сутками.
Labels:
архитектура,
база данных,
оптимизация,
полезное,
проблема,
производительность,
Firebird,
SQL
1 апр. 2014 г.
На заметку. Если в процедуре встретится конструкция:
... INSERT INTO table1 SELECT * FROM table2; ...то возникнет зависимость между процедурой и всеми полями из table2, хотя они тут и не прописаны явно.
Labels:
SQL
14 янв. 2014 г.
Попытка №2
Упростим ситуацию и будем рассматривать только удаление из базы данных документов указанных типов, даты меньше заданной (D). Под документом в широком смысле мы понимаем:
-
Cовокупность записей в таблице GD_DOCUMENT, шапка и позиции.
Присоединенные к ним записи 1-к-1 (включая записи в специфических таблицах документов, связанные через поле documentkey).
Связанные с ними таблицы с уточняющей информацией (например, USR$INV_ADDINFO). По сути это 1-к-1, но через отдельное поле внешний ключ. Из структуры БД мы не можем знать, что некоторая таблица несет уточняющую информацию, поэтому определим список таких таблиц через константу в программе.
Бухгалтерские проводки (AC_RECORD, AC_ENTRY).
Складское движение (INV_CARD, INV_MOVEMENT).
Ко всему вышеперечисленному связанные записи в кросс-таблицах множеств.
-
Вычисляем бухгалтерские и складские остатки на заданную дату. Сохраняем их в базе.
Формируем массив М из ИД записей, которые останутся в результирующей базе:
-
Сканируем все таблицы, не относящиеся к документам. Добавляем в М ИД записи (если он есть и целочисленный) и идентификаторы всех ссылок на таблицы документов. Прочие ссылки нас не интересуют, так как все записи из прочих таблиц в любом случае будут сохранены.
Выбираем из таблиц документов указанных типов записи с датой больше либо равно D. Сканируем их аналогичным образом, добавляя ИД в М.
Сканируем аналогичным образом таблицы документов остальных типов.
-
Пользователю рекомендуется провести бэкап-разбэкап исходной базы перед началом процесса.
Кэш не должен превышать 500 Мб.
Режим принудительной записи должен быть отключен
Если задействован наш механизм замены внешних ключей, то отключенные ключи должны быть включены.
Аудит средствами платформы Гедымин должен быть отключен.
Labels:
база данных,
идеи,
производительность,
SQL
4 янв. 2014 г.
Синхронизация данных между мобильным устройством на Android и сервером Firebird SQL
Ради скорости и независимости от сетевого соединения мобильное приложение должно использовать локальную базу, которая периодически синхронизируется с рабочей базой данных предприятия. Пусть мобильная БД состоит из таблиц М1, М2,... Мn и используется только для чтения. Таблицы имеют целочисленные первичные ключи. Рассмотрим вариант однонаправленной синхронизации:
-
Версию данных будем обозначать целым числом и хранить в отведенной для этого таблице. 0 -- соответствует чистой БД.
На сервере создадим таблицы S1, S2,... Sn, идентичные по структуре таблицам из мобильной БД. Указанные таблицы хранят текущую версию данных на сервере.
На сервере создадим GLOBAL TEMPORARY таблицы уровня транзакции T1, T2,... Tn, также как и S идентичные по структуре таблицам из мобильной БД.
На сервере создадим таблицу для команд обновления данных. Каждая запись таблицы хранит в текстовом виде список команд для перевода версии J в версию K.
Команда состоит из заголовка и тела (или только из заголовка). Поддерживаются следующие команды:
-
RALL -- очистить все таблицы.
R -- очистить одну таблицу. В заголовке передается имя таблицы.
D -- удалить записи из таблицы. В заголовке передается имя таблицы. В теле -- список ИД удаляемых записей.
U -- вставить или обновить записи в таблице. В заголовке передается имя таблицы и список полей. В теле передаются значения полей для каждой записи. Первое значение -- всегда ИД записи. Так как присутствие идентификатора обязательно, в списке имя поля первичного ключа не перечисляется.
-
Стартует транзакция.
Сформированные данные данные помещаются в таблицы T.
На основе сравнения S и T создаются и записываются в журнал команды изменения данных от версии N к N + 1.
Таблицы S очищаются, в них переносятся записи из T.
Инкрементируется номер версии данных.
Транзакция завершается.
-
Мобильное приложение подключается к серверу и сообщает свою версию структуры БД и версию данных (V).
Если версия структуры БД нас не устраивает, то сообщаем пользователю, что приложение следует обновить.
Из журнала извлекаем команды для обновления данных от версии V к N и передаем их мобильному приложению.
Если у нас нет прямого перехода от V к N, то последовательно ищем команды для апгрейда от V до V + 1 и формируем из них единый список для инкрементного обновления.
Передаем список команд на мобильное устройство.
По окончании процесса применения полученных команд инкрементируем номер версии данных мобильного приложения.
Если мобильное приложение передало на сервер 0, то вместо передачи длинной цепочки последовательных обновлений, сразу формируем команды на основе содержимого таблиц S.
Процесс обновления выполняется на одной транзакции, которая подтверждается только в случае успешного выполнения всех команд.
28 сент. 2013 г.
Неприятное ограничение рекурсивных CTE
Простейший способ заехать в дурдом -- это отладка рекурсивных запросов в уме. В предыдущем примере у нас закралась ошибка. Объекты, которые ни от кого не зависят, не попадут в итоговую выборку. Но, только в том случае, если глубина уровня их вложенности больше единицы. Всему виной JOIN at_namespace_file_link l ON l.filename = f.filename во второй части CTE. По логике вещей, тут должен быть LEFT JOIN, но рекурсивное CTE внутри себя не может участвовать во внешних объединениях. C'est la vie. И никакой возможности разрулить данную ситуацию внутри самого CTE не просматривается. Остается городить огород с объединением двух выборок.
WITH RECURSIVE
ns_tree AS (
SELECT
f.filename,
CAST((f.xid || '_' || f.dbid) AS VARCHAR(1024)) AS path,
l2.uses_xid,
l2.uses_dbid
FROM
at_namespace_file f
JOIN at_namespace_file_link l
ON l.uses_xid = f.xid AND l.uses_dbid = f.dbid
LEFT JOIN at_namespace_file_link l2
ON l2.filename = f.filename
WHERE
l.filename = :filename
UNION ALL
SELECT
f.filename,
(t.path || '-' || f.xid || '_' || f.dbid)
AS path,
l.uses_xid,
l.uses_dbid
FROM
ns_tree t
JOIN at_namespace_file f
ON t.uses_xid = f.xid AND t.uses_dbid = f.dbid
JOIN at_namespace_file_link l
ON l.filename = f.filename
WHERE
POSITION ((f.xid || '_' || f.dbid)
IN t.path) = 0)
SELECT
t.filename
FROM
ns_tree t
UNION
SELECT
f.filename
FROM
ns_tree t
JOIN at_namespace_file f
ON f.xid = t.uses_xid AND f.dbid = t.uses_dbid
LEFT JOIN at_namespace_file_link l
ON l.filename = f.filename
WHERE
l.filename IS NULL
14 сент. 2013 г.
Список объектов в соответствии с заданными зависимостями
Если в предыдущем примере мы ранжировали заданный список объектов, то следующий пример возвращает упорядоченный в соответствии с иерархией зависимости список объектов, от которых зависит указанный объект.
WITH RECURSIVE
ns_tree AS (
SELECT
f.filename,
CAST((f.xid || '_' || f.dbid) AS VARCHAR(1024)) AS path,
l2.uses_xid,
l2.uses_dbid
FROM
at_namespace_file f
JOIN at_namespace_file_link l
ON l.uses_xid = f.xid AND l.uses_dbid = f.dbid
LEFT JOIN at_namespace_file_link l2
ON l2.filename = f.filename
WHERE
l.filename = :filename
UNION ALL
SELECT
f.filename,
(t.path || '-' || f.xid || '_' || f.dbid)
AS path,
l.uses_xid,
l.uses_dbid
FROM
ns_tree t
JOIN at_namespace_file f
ON t.uses_xid = f.xid AND t.uses_dbid = f.dbid
JOIN at_namespace_file_link l
ON l.filename = f.filename
WHERE
POSITION ((f.xid || '_' || f.dbid)
IN t.path) = 0)
SELECT
*
FROM
ns_tree
Таблица at_namespace_file содержит список объектов, а at_namespace_file_link -- связи между ними. Вычисление пути (path) и дополнительная проверка через функцию POSITION позволяют избежать зацикливания на кольцевых ссылках, если такие попадутся в исходных данных.
Labels:
SQL
2 сент. 2013 г.
Удаление гланд автогеном...
Таблицу со строковым первичным ключем подключить к визуальным контролам Гедымина нельзя, так как все они поголовно спроектированы для работы с целочисленными идентификаторами. Выход -- добавить вычисляемую колонку в запрос, которую заполнять хэшем от строкового значения. Встроенная функция Firebird HASH() возвращает 64-х битное целое (тип BIGINT), которое следует сдвигать вправо, пока значение не войдет в допустимый для 32-х битных целых диапазон.
В следующем примере мы генерируем ключи типа INTEGER по наименованиям контактов (что не совсем корректно, так как сами наименования у нас могут быть неуникальными).
select
cast(iif(log(2, hash(name)) > 30,
bin_shr(hash(name),
cast(log(2, hash(name)) as integer) - 30),
hash(name)) as integer),
name
from
gd_contact
В некоторых случаях может подойти и заполнение колонки последовательными значениями генератора:
select gen_id(my_gen, 1), name from gd_contact
Labels:
SQL
1 сент. 2013 г.
Построение списка зависимых объектов
Замечательная задача для проверки программиста на знание SQL и реляционных БД. Пусть в одной таблице хранится список объектов. Объекты могут зависеть друг от друга. Связи хранятся в другой таблице ввиде пар ключей: объект - объект, от которого зависит данный объект. Требуется построить упорядоченный список, чтобы для любого объекта в нем, все объекты, от которых он зависит, располагались перед ним.
Решение с использованием рекурсивного CTE:
WITH RECURSIVE
ns_tree AS (
SELECT
n.filename AS headname,
0 AS usescount,
CAST((n.xid || '_' || n.dbid) AS VARCHAR(1024)) AS path,
n.filename
FROM
at_namespace_file n
UNION ALL
SELECT
t.headname,
(t.usescount + 1) AS usescount,
(t.path || '-' || n.xid || '_' || n.dbid) AS path,
n.filename
FROM
ns_tree t
JOIN at_namespace_file_link l
ON l.filename = t.filename
JOIN at_namespace_file n
ON l.uses_xid = n.xid and l.uses_dbid = n.dbid
WHERE
POSITION ((n.xid || '_' || n.dbid) IN t.path) = 0
)
SELECT
t.headname, sum(t.usescount)
FROM
ns_tree t
GROUP BY
1
ORDER BY
2
В приведенном запросе at_namespace -- список объектов, а at_namespace_link -- связи между ними. Вычисление пути (path) и дополнительная проверка через функцию POSITION позволяют избежать зацикливания на кольцевых ссылках, если такие попадутся в исходных данных.
Labels:
исходный код,
программирование,
школа,
SQL
29 авг. 2013 г.
Подключение Embedded SWI-Prolog в Гедымин
Есть два варианта подключения Пролога. Первый -- мы обращаемся к embedded SWI-Prolog из кода на VBScript через глобальный объект (назовем его PL). Второй -- алгоритм пишется непосредственно на Прологе, а доступ к функционалу платформы происходит через Foreign Predicates.
Обращение к Прологу через глобальный объект мы рассмотрим на сравнительном примере доступа к реляционным данным из макроса на Гедымине. Пусть данные находятся в двух таблицах: GD_CONTACT(ID, CITYKEY, NAME) и GD_PLACE(ID, NAME):
ID CITYKEY NAME ====================== 1 1 Company A 2 1 Company B 3 2 Company C ID NAME ====================== 1 Minsk 2 BeriozaДля извлечения cписка компаний по заданному наименованию города мы пишем SQL запрос, который выполняем через объект класса TIBSQL:
... Dim q Set q = Creator.GetObject(nil, "TIBSQL", "") Set q.Transaction = gdcBaseManager.ReadTransaction q.SQL.Text = _ "SELECT c.name " & _ "FROM gd_contact c JOIN gd_place p " & _ " ON p.id = c.citykey " & _ "WHERE p.name = 'Minsk' " q.ExecQuery While Not q.EOF ... q.Next WEnd ...Теперь изобразим тоже самое на Prolog. Исходные данные в виде предикатов Prolog выглядят так:
gd_contact(1, 1, 'Company A'). gd_contact(2, 1, 'Company B'). gd_contact(3, 2, 'Company C'). gd_place(1, 'Minsk'). gd_place(2, 'Berioza').Правило связи компании с городом через ключи:
bycity(City, Name) :- gd_place(CityID, City), gd_contact(_, CityID, Name).Тогда запрос на извлечение всех минских компаний будет выглядеть следующим образом:
? bycity('Minsk', Name).
Что мы видим из данного примера?
-
Необходим класс для доступа к Прологу из макросов. Далее по тексту будем называть его PL. Экземпляр данного класса создается стандартным образом через глобальный объект Designer/Creator. Каждый экземпляр может иметь свое имя.
PL должен иметь метод для формирования предикатов на основании заданной SQL выборки. Пример кода:
... Call PL.MakePredicates(_ "SELECT id, citykey, name FROM gd_contact", _ gdcBaseManager.ReadTransaction, _ "gd_contact", "name") ...Последний параметр -- имя выборки, которое будет использоваться для формирования имени .pl файла в режиме отладки. Пусть это и не касается непосредственно сопряжения Пролога с Гедымином, но здесь стоит вспомнить об идее изоляции разработчика от работы с SQL запросами и инкапсуляции последних внутрь т.н. SQL объекта. SQL объект -- это заранее написанный, проверенный и оптимизированный SQL запрос, загружаемый на базу стандартным образом через настройки или пространства имен. Для выполнения, программист обращается к нужному SQL объекту по его РУИДу. PL должен иметь метод для формирования предикатов на основании бизнес-класса. На вход передается класс-подтип, подмножество, дополнительные условия выборки. Сразу следует договориться о стандартном механизме (способе) загрузки данных мастер-дитэйл (например, документ накладная с позициями).
... Call PL.MakePredicates2(_ "TgdcCompany", "", "ByID", Array(777777), "", _ gdcBaseManager.ReadTransaction, _ "gd_contact", "name") ...PL должен иметь метод для формирования предикатов на основании существующего набора данных (дата-сета). PL должен иметь метод для ручного формирования произвольных предикатов. PL должен иметь метод для вычисления цели. Результат вычисления может быть как одиночное значение, так и выборка. Для работы с последней предусмотрены методы итерации (EOF, First, Next) и методы для обращения к параметрам по имени. С помощью соответствующих методов результат вычисления может быть напрямую помещен в указанную таблицу в базе, клиент-датасет в оперативной памяти, или на его основе могут быть созданы новые бизнес-объекты в БД. Предикаты, которые создает разработчик, должны храниться в базе данных аналогично тому, как сейчас хранится код VBScript. В дереве Проводника Редактора скрипт-объектов следует добавить корневой раздел для кода на Прологе. Внутри раздела пролог-скрипты могут размещаться в папках. Минимальная единица для загрузки в объект PL -- один пролог-скрипт. Каждый скрипт имеет уникальное имя и РУИД. Загрузка осуществляется по РУИДу. Внутри скрипта могут встречаться директивы %#INCLUDE для загрузки других пролог-скриптов (аналогично директиве '#INCLUDE для VBScript). Стандартные библиотеки SWI-Prolog, которые в дистрибутиве занимают 8 Мб в 377 файлах, будут храниться в базе данных точно так же, как и обычные пролог-скрипты. Отладка:
-
Для отладки на компьютере должен быть установлен полный вариант SWI-Prolog и, возможно, SWI-Prolog Editor.
Местоположение SWI-Prolog и каталога для временных файлов задается в параметрах платформы.
Режим отладки активизируется в параметрах редактора скрипт-объектов для списка конкретных экземпляров PL, заданных их именами. Если список пуст, то режим активируется для всех PL объектов.
Когда активирован режим дебаггера предикаты записываются в текстовые файлы .pl программ во временном каталоге. При этом файлы формируются следующим образом:
-
Для каждого пролог-скрипта отдельный файл.
Результаты вызова MakePredicatesX помещаются в файлы в соответствии с заданными именами.
Labels:
архитектура,
идеи,
prolog,
SQL
17 авг. 2013 г.
16 авг. 2013 г.
Просмотр объекта по ссылке
Добавлено в Гедымин. Если в окне редактора SQL, в таблице с результатом выполнения запроса, установить курсор на поле-ссылку, то с помощью кнопок на панели инструментов (на рисунке ниже) можно: открыть объект на просмотр/изменение, удалить объект, открыть форму просмотра объекта.
Подписаться на:
Сообщения (Atom)


