Qt и SQLite

При использовании библиотеки Qt и встроенной в нее СУБД SQLite возникает желание проверять запросы вне исходного кода программы. Желательно проверять запросы с той же версией, что встроена в Qt.

Возьмем исходный код Qt. На данный момент я использую версию 5.4.0 и установил исходный код при установке Qt. Все действия происходят в терминале Линукса.

Перейдем в каталог с исходным кодом SQLite:

cd /opt/Qt5.4.0/5.4/Src/qtbase/src/3rdparty/sqlite

Соберем клиент SQLite и поместим его в /opt:

sudo gcc shell.c sqlite3.c -lpthread -ldl -o  /opt/sqlite3

Проверим версию:

/opt/sqlite3 --version

Готово.

Реклама

SQLite: случайные значения первичного ключа

В SQLite, если специально не указывать значение первичного ключа, то оно генерируется автоматически начиная с 1 так, что следующее значение на 1 больше предыдущего. Иногда требуется, чтобы значения первичного ключа были случайны. В SQLite этого достичь невозможно никаким образом.

Мною были предприняты две попытки, которые дали отрицательный результат.

Первая неудачная попытка

Можно попробовать задать значение по умолчанию для первичного ключа. Это значение задавалось бы функцией, которая генерирует случайные числа:

create table table1 (
  id integer primary key default (random()),
  field1 integer
);

Таблица создается без ошибок, но при вставке записей значение первичного ключа задается как обычно, то есть условие default игнорируется.

Вторая неудачная попытка

Во второй попытке я создал триггер, который должен был при вставке записи заменять в ней значение первичного ключа на случайное значение.

create table table1 (
  id integer primary key,
  field1 integer
);

create trigger random_primary_key
instead of insert on table1
when new.id is not null
begin
  insert into table1 values (
    random(),
    new.field1
  );
end;

Но SQLite выдал ошибку:

Error: near line 30: cannot create INSTEAD OF trigger on table: table1

Оказалось, что триггеры, которые заменяют записи, могут работать только с представлениями (view). Что удалось узнать только из исходного кода SQLite. Так как в документации это не разъясняется. В файле src/trigger.c в том месте где генерируется выше названная ошибка сказано:

INSTEAD of triggers are only for views and views only support INSTEAD of triggers.

Перевод: триггер INSTEAD только для представлений и только представления поддерживают этот триггер.

Удачная попытка

заключается в отказе от использования первичного ключа. Поле идентификатора можно сделать уникальным и задать случайное значение по умолчанию:

create table table1 (
  id integer unique not null default (random()),
  field1 integer
);

У меня были сомнения, будут ли работать внешние ключи на уникальном поле. Эксперимент дал положительный результат.

Компенсация сопротивления нагрузки в резистивном делителе

Компенсирующее сопротивление Rx рассчитывается по формуле:

R_x = \frac{R_1R_H}{R_2}

Компенсация нагрузки резистивного делителя

Для такого делителя работает обычная формула:

U_2 = U_1 \frac{R_2}{R_1 + R_2}

Без компенсирующего сопротивления делитель с нагрузкой рассчитывался бы по формуле:

U_2 = U_1 \frac{R_2 R_H}{R_2 R_H + R_1 R_H + R_1 R_2}

Пример

Если R1 = 1 кОм, R2 = 10 кОм и Rн = 100 кОм, то понадобится компенсирующее сопротивление Rx = 10 кОм. При этом, если все резисторы будут точностью 1%, то погрешность напряжения U2 будет ±0,18 %.

Извлечение древовидной структуры из базы данных

Для всех запросов в статье используется синтаксис СУБД SQLite. Для других СУБД синтаксис может незначительно отличаться.

Чтобы представить древовидную структуру в реляционной базе данных, можно создать следующую таблицу:

create table tree (
  id     integer,
  parent integer,
  name   text,

  primary key (id),
  foreign key (parent) references tree(id)
    on update cascade on delete cascade
);

В поле parent задается родитель для элемента древовидной структуры, причем в качестве значения этого поля используется значение поля id другого элемента. В поле name задается имя элемента.

Для наглядности зададимся древовидной структурой:

  • Овощи
    • Картофель
      • Аспиа
      • Виталот
      • Диво
    • Томаты
      • Томат обыкновенный
      • Томат Перуанский
  • Фрукты
    • Груши
      • Груша обыкновенная
    • Яблоки

Такую структуру можно создать следующими запросами:

insert into tree values ( 1, null, 'Овощи');
insert into tree values ( 2,    1, 'Картофель');
insert into tree values ( 3,    2, 'Аспиа');
insert into tree values ( 4,    2, 'Виталот');
insert into tree values ( 5,    2, 'Диво');
insert into tree values ( 6,    1, 'Томаты');
insert into tree values ( 7,    6, 'Томат обыкновенный');
insert into tree values ( 8,    6, 'Томат перуанский');
insert into tree values ( 9, null, 'Фрукты');
insert into tree values (10,    9, 'Груши');
insert into tree values (11,   10, 'Груша обыкновенная');
insert into tree values (12,    9, 'Яблоко');

Здесь null означает корневую группу.

Предположим, что требуется извлечь данные из таблицы tree в следующем виде:

/Овощи
/Фрукты
/Овощи/Картофель
/Овощи/Томаты
/Фрукты/Груши
/Фрукты/Яблоко
/Овощи/Картофель/Аспиа
/Овощи/Картофель/Виталот
/Овощи/Картофель/Диво
/Овощи/Томаты/Томат обыкновенный
/Овощи/Томаты/Томат перуанский
/Фрукты/Груши/Груша обыкновенная

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

with cte as (
  select
    id,
    parent,
    '/' || name as name
  from tree where parent is null
  
  union all
  
  select
    tree.id,
    tree.parent,
    cte.name || '/' || tree.name
  from cte, tree on tree.parent = cte.id
)
select name from cte;

Рекурсивные запросы поддерживаются в SQLite начиная с версии 3.8.3 от 3 февраля 2014 года.