Версия для печати темы

Нажмите сюда для просмотра этой темы в обычном формате

Форум на CrossPlatform.RU _ Qt Модель/Представление _ просмотр таблицы с меняющимися данными, запоминание и выделение тек строки

Автор: Steklova Olga 3.2.2012, 21:20

Всем привет! ;)
Есть у меня две таблицы БД: T1 и T2, в каждой есть поля ID INTEGER (PK, автоинкрементное) и PARAM FLOAT. Для работы с таблицами использую QSqlTableModel и QTableView.

Во время работы программы в таблицу T1 записи только добавляются.
Организую заполнение и просмотр следующим образом:
- при поступлении первой записи добавляю ее в таблицу БД, выполняю select для модели, первую строку таблицы делаю текущей, запоминаю соотв. значение ID,
- при поступлении следующей записи добавляю ее в таблицу БД, выполняю select для модели, строку с запомненным ID делаю текущей, делаю скроллинг отображения для обеспечения видимости текущей строки,
- при изменении текущей строки оператором запоминаю значение ID. Тут все OK.

А вот с таблицей T2 посложнее...
Во время работы программы, периодически (вероятно, не чаще, чем 1 раз в 5 сек), для занесения в таблицу T2 приходит сразу целый массив записей (размер массива - от одной до 200 записей). Мне надо хранить в БД данные только последнего массива.
Данные разных массивов:
- могут полностью совпадать,
- могут отличаться всеми значениями,
- могут отличаться несколькими значениями,
- могут отличаться количеством значений.
Организую заполнение и просмотр следующим образом:
- при поступлении первого массива добавляю поступившие записи в таблицу БД, выполняю select для модели, первую строку таблицы делаю текущей,
- при поступлении следующего массива удаляю все записи из таблицы БД, добавляю поступившие записи в таблицу БД, выполняю select для модели, первую строку таблицы делаю текущей (пока так).

Как здесь просматривать таблицу с запоминанием и выделением текущей строки?
1) Запоминать ID текущей строки? Здесь это не поможет, так как поле автоинкрементное.
2) Сделать поле ID не автоинкрементным, не удалять предыдущий массив и добавлять новый, а обновлять массив? Этот вариант мне совсем не нравится, по-моему, он очень сложный.
3) Запоминать PARAM текущей строки, а при поступлении следующего массива искать в нем запись с запомненным PARAM и делать ее текущей? А если такая не найдется, то делать текущей первую. Но для этого придется сравнивать значения типа FLOAT, видимо используя какую-то точность сравнения. Или так и надо? :huh:
Других вариантов не придумала. У кого какие мысли есть по этому поводу? Спасибо.

Автор: ilyabvt 3.2.2012, 23:51

Цитата
3) Запоминать PARAM текущей строки, а при поступлении следующего массива искать в нем запись с запомненным PARAM и делать ее текущей? А если такая не найдется, то делать текущей первую. Но для этого придется сравнивать значения типа FLOAT, видимо используя какую-то точность сравнения. Или так и надо?

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

Автор: Steklova Olga 4.2.2012, 21:22

Цитата
Сравнивайте также как вы бы сравнивали целочисленные значения.
А это во всех СУБД срабатывает? Я работаю сейчас с Firebird.

Автор: ilyabvt 4.2.2012, 22:46

Цитата(Steklova Olga @ 5.2.2012, 0:22) *
Цитата
Сравнивайте также как вы бы сравнивали целочисленные значения.
А это во всех СУБД срабатывает? Я работаю сейчас с Firebird.

С Firebird не работал, но честно говоря не представляю, почему оно должно вдруг не сработать. Может вы меня не так поняли? Я не имел ввиду что нужно конвертировать в целочисленные, я имел ввиду что принцип их сравнения одинаковый

Автор: Steklova Olga 5.2.2012, 17:34

Цитата
Я не имел ввиду что нужно конвертировать в целочисленные, я имел ввиду что принцип их сравнения одинаковый
Спасибо, я так и поняла.

Автор: Steklova Olga 6.2.2012, 20:04

Однако не все так тривиально...
1) Зачем-то же написана функция qFuzzyCompare:

пример
double d1, d2;
bool b1, b2;

d1 = 15000.0000000000;
d2 = 15000.0000000150;
b1 = (d1 == d2); //false
b2 = qFuzzyCompare(d1, d2); //true

d1 = 15000.0000000000;
d2 = 15000.0000000151;
b1 = (d1 == d2); //false
b2 = qFuzzyCompare(d1, d2); //false
Поиск записи с запомненным PARAM сделала с помощью qFuzzyCompare, учитывая обработку нулевых значений (help по этой функции).
2) Оказалось, что точности FLOAT для поля PARAM в таблице БД мне не хватает, заменила на DOUBLE PRECISION.
3) После этого не могла понять, почему
- из IBExpert получается добавить запись со значением PARAM, равным
15000.0000000151 (при этом в БД оказывается записанным
15000.0000000150994),
- а из моей программы при добавлении записи со значением PARAM, равным
15000.0000000151 в БД оказывается записанным
15000.
Дело было в том, что при формировании строки запроса на добавление записи в таблицу БД
я переводила значение в строку с помощью setNum, но использовала prec = 6 (по умолчанию).
Заменила s.setNum(d, 'f') на s.setNum(d, 'f', 10).

Проверила работу, указав в Qt::DisplayRole для PARAM формат
QString("%1").arg(value.toDouble(), 0, 'f', 10, QLatin1Char('0'));

4) Остался вопрос, какое "универсальное" (для любого поля типа DOUBLE PRECISION) значение prec лучше указать у меня в setNum при формировании запроса в общем случае, если таких параметров как PARAM у меня несколько, и я не знаю заранее их точный диапазон и точность, только знаю, что DOUBLE PRECISION подходит. prec = 15 что-ли? :rolleyes:

Автор: Tonal 7.2.2012, 8:11

Цитата(Steklova Olga @ 7.2.2012, 0:04) *
Дело было в том, что при формировании строки запроса на добавление записи в таблицу БД
я переводила значение в строку с помощью setNum, но использовала prec = 6 (по умолчанию).
Заменила s.setNum(d, 'f') на s.setNum(d, 'f', 10).

Проверила работу, указав в Qt::DisplayRole для PARAM формат
QString("%1").arg(value.toDouble(), 0, 'f', 10, QLatin1Char('0'));

4) Остался вопрос, какое "универсальное" (для любого поля типа DOUBLE PRECISION) значение prec лучше указать у меня в setNum при формировании запроса в общем случае, если таких параметров как PARAM у меня несколько, и я не знаю заранее их точный диапазон и точность, только знаю, что DOUBLE PRECISION подходит. prec = 15 что-ли? :rolleyes:

Используй параметризированные запросы и биндинг переменных. Тогда тебе не нужно будет руками форматировать данные и натыкатся на точность. :)

Ну и почитай про http://ru.wikipedia.org/wiki/%D0%A7%D0%B8%D1%81%D0%BB%D0%B0_%D1%81_%D0%BF%D0%BB%D0%B0%D0%B2%D0%B0%D1%8E%D1%89%D0%B5%D0%B9_%D0%B7%D0%B0%D0%BF%D1%8F%D1%82%D0%BE%D0%B9 - будешь лучше ориентироватся. :)

По поводу стартового вопроса было бы неплохо уточнить, зачем нужна вообще выделенная строка в таблице T2, если данные там постоянно меняются.
Какую она несёт функциональную нагрузку?
Ежели это понять, можно дальше думаьт про механизм обеспечения постоянства этого выделения.

Автор: Steklova Olga 7.2.2012, 14:40

1)

Цитата
Используйте биндинг переменных
Понятно, как использовать биндинг, если надо добавить или обновить запись, а в запросе указать значения всех полей записи (конечно, кроме генератора).

А вот другой случай... И как Вы здесь прикрутите биндинг?
Например, есть таблица БД с полями ID (INTEGER, PK), PARAM (DOUBLE, необяз), FINT1 (INTEGER, необяз), FINT2 (INTEGER, необяз), FSTR (VARCHAR(20), необяз).
Для записи или обновления данных таблицы поступает структура, содержащая значения полей записи.
typedef struct {
    qint32 id;
    double param;
    qint32 fint1;
    qint32 fint2;
    QString str;
} tRec;
tRec Rec;
const qint32 null_qint32 = -1000;
const double null_double = -1000;
const double epsilon_double = 1.0;

Если поступает запись с новым id, то добавляем ее в таблицу.
Если поступает запись со старым id, то обновляем соотв. запись в таблице.
При формировании запросов из числа необязательных полей указываем только те, значения которых поступили в структуре, остальные значения полей должны остаться без изменения.
Используем следующие признаки того, что необязательные поля не поступили:
- для поля PARAM: param = null_double,
- для поля FINT1: fint1 = null_qint32,
- для поля FINT2: fint2 = null_qint32,
- для поля FSTR: str.isEmpty().

Сейчас я использую функцию, формирующую часть строки запроса для записи или обновления данных любой таблицы БД. Эта часть строки запроса имеет вид: "(<список наименований полей записи, поступивших в структуре>) VALUES (<список соотв. значений полей>)". Входным параметром функции является вектор. Элемент вектора - структура, содержащая информацию о поле таблицы, в том числе
    QString FieldName; //наименование поля таблицы
    QVariant FieldValue; //значение поля таблицы

//в самой функции
    if (vector[i].FieldValue.type() == QVariant::Double)
    {
        d = vector[i].FieldValue.toDouble();
        if (::qAbs(d - null_double) > epsilon_double)
        {
            s.setNum(d, 'f', 15); //15? здесь мне нужно универсальное значение prec
            sv += " " + s + ",";
            Flag_add = true;
        }
    }

2)
Цитата
По поводу стартового вопроса было бы неплохо уточнить, зачем нужна вообще выделенная строка в таблице T2, если данные там постоянно меняются.
Дело в том, что
- таблица предназначена для просмотра текущего массива оператором,
- для отображения таблицы на экране отведено не так много места, чтобы видеть сразу все записи, тем более, если их 200,
- не каждые 5 сек все значения массива меняются,
- Если оператор начнет скроллинг таблицы, а в это время, например, обновится массив только в одном значении (не в текущей строке), хочется, чтобы оператор спокойно остался на прежней текущей строке, а измененную строку он увидит, продолжив скроллинг.
Если не запоминать текущую строку, то невозможно будет спокойно просматривать массив, при каждом обновлении массива текущей будет становится первая строка.
Это будет даже в том случае, если данных разных массивов будут полностью совпадать, ведь я же удаляю старый массив и добавляю новый.

Автор: Tonal 8.2.2012, 8:51

1)
Ты же вроде бы писала, что T2 ты очищаешь и вставляешь данные по новой. Т. е. в этом случае у тебя только 1 sql для удаления и 1 для вставки. Как использовать биндинг вроде очевидно :)

Но даже если рассматривать более общий случай, который ты описываешь, то можно использовать биндинг несколькими способами:
1. Генерить параметрический sql (без данных) для каждого случая.
2. Использовать всегда полный insert/update с биндингом а в триггерах разбираться какие реально поля изменяются
3. Вынести логику разборок с данными на сервер - в сохранённую процедурку.
Во всех 3х случаях кроме первого дополнительно к удобству работы с данными получаем ускорение на единственной генерации запроса клиентом и подготовке (препарации) запроса сервером. Да и в 1ом случае можно добавить систему кеширования позволяющую воспользоваться этими преимуществами. :)

2)
Почему тогда просто не привязываться к порядковому номеру и верхней видимой строке?
Других-то ориентиров у пользователя всё равно нету...

Автор: Steklova Olga 8.2.2012, 12:14

Таблицы Т1 и Т2 - это один пример,
таблица БД с полями ID, PARAM, FINT1, FINT2, FSTR - это таблица Т3, другой пример.
В T1 записи только добавляются,
в T2 удаляются старые записи и добавляются новые,
в Т3 записи добавляются или обновляются в зависимости от поступившего ID
(UPDATE OR INSERT INTO T3 ... MATCHING (ID)).

Цитата(Tonal @ 8.2.2012, 8:51) *
Ты же вроде бы писала, что T2 ты очищаешь и вставляешь данные по новой. Т. е. в этом случае у тебя только 1 sql для удаления и 1 для вставки.
Да

Цитата(Tonal @ 8.2.2012, 8:51) *
Как использовать биндинг вроде очевидно
Так как в Т2 все поля обязательные, то для Т2 - да, очевидно.

Цитата(Steklova Olga @ 7.2.2012, 14:40) *
Если оператор начнет скроллинг таблицы, а в это время, например, обновится массив только в одном значении (не в текущей строке), хочется, чтобы оператор спокойно остался на прежней текущей строке, а измененную строку он увидит, продолжив скроллинг.
Если не запоминать текущую строку, то невозможно будет спокойно просматривать массив, при каждом обновлении массива текущей будет становится первая строка.
Это будет даже в том случае, если данных разных массивов будут полностью совпадать, ведь я же удаляю старый массив и добавляю новый.
Здесь я говорю о Т2. А под словом "обновится" я подразумеваю не то, что я делаю таблице UPDATE, а то, что меняются данные поступающего массива, например.
Поступает 1-й раз массив со значениями PARAM (1.23, 2.34, 300.45, 445.00),
оператор начинает его просмотр,
делает текущей запись со значением 300.45,
поступает 2-й раз массив со значениями PARAM (1.23, 2.34, 300.45, 1888.00),
<здесь я перерисовываю таблицу, но хочу, чтобы текущей осталась запись со значением 300.45>,
поступает 3-й раз массив со значениями PARAM (1.23, 2.34, 300.45, 1888.00, 2001.45),
<здесь я перерисовываю таблицу, но хочу, чтобы текущей осталась запись со значением 300.45>.

Цитата(Tonal @ 8.2.2012, 8:51) *
Почему тогда просто не привязываться к порядковому номеру и верхней видимой строке?
Других-то ориентиров у пользователя всё равно нету...
В соотв. с выше написанным, мне надо привязываться не к ID, а к PARAM, ориентир - это PARAM.

Но я-то хочу во всех случаях (для Т1, Т2, Т3 и любых других моих таблиц, в том числе с любым количеством необяз. полей) использовать одну функцию формирования запроса.
Поэтому я и пишу s.setNum(d, 'f', 15).

Читаю дальше Ваши предложения...
Цитата(Tonal @ 8.2.2012, 8:51) *
Но даже если рассматривать более общий случай, который ты описываешь, то можно использовать биндинг несколькими способами:
1. Генерить параметрический sql (без данных) для каждого случая.
Если я Вас правильно поняла, то в этом случае для Т3, в которой четыре необяз. поля, придется писать 16 параметр. sql (все комбинации).

Цитата(Tonal @ 8.2.2012, 8:51) *
2. Использовать всегда полный insert/update с биндингом а в триггерах разбираться какие реально поля изменяются
Я пока не настолько сильна в триггерах, чтобы такое написать. Пока что, использую триггеры только для генераторов.

Цитата(Tonal @ 8.2.2012, 8:51) *
3. Вынести логику разборок с данными на сервер - в сохранённую процедурку.
Очень хорошо, а что будет в этой хранимой процедуре? :scratch_one-s_head: Не могли бы Вы, Tonal, если не жалко, набросать код для случая обновления данных в таблице Т3?

Цитата(Tonal @ 8.2.2012, 8:51) *
Да и в 1ом случае можно добавить систему кеширования позволяющую воспользоваться этими преимуществами.
А эти слова для меня вообще пока как китайская грамота. :)

Автор: Tonal 9.2.2012, 9:45

Про Т2 и прзиционирование:
Что-то я не очень понял, если для Т2 используется та же структура, то почему не использовать ID для позиционирования.
В противном случае не ясно что делать при наличии 2х и более одинаковых (или очень близких) значений PARAM в массиве.
Как-то ты же должна задавать на этом массиве порядок/сортировку для выборки.
Иначе можно получить данные совершенно не в том порядке и позиционирование будет бессмысленно, потому что в "уже просмотренные" попадут совсем ни те данные.
Если порядок таки задан - то и запоминать позицию нужно ориентируясь на него.

Пример:

Цитата
Поступает 1-й раз массив со значениями PARAM (1.23, 2.34, 300.45, 445.00),
оператор начинает его просмотр,
делает текущей запись со значением 300.45,

Куда позиционироватся, если следующий запрос вернёт:
PARAM (445.00, 2.34, 300.45, 1.23)
или
PARAM (1.23, 2.34, 300.45, 300.45, 300.45, 300.45, 445.00)
или
PARAM (3.38, 1.32, 300.45001, 455.01, 300.44999)
И что это будет значить для оператора?

Про способы использования:
1. Я имел в виду, что ты всё оставляешь как сейчас, но при генерации текста запроса вместо данных подставляешь заполнители (например символы "?").
После этого биндишь переменные (как в справке по QSqlQuery)
В этом случае данные в сервер попадают минуя преображение в строку у тебя в коде и обратно на сервере.
Меньше преобразований - меньше накапливается ошибок. :)
Причём объект QSqlQuery после prepare можно не удалять, а сохранить где-то, и для следующего запроса только подставить (забиндить) новые параметры.
В случае Т3 можно сделать кеширование. Например QMap где ключом будет состав изменяемых полей а значением - препарированный запрос.

2. В триггере перед изменением можно проверить - если поле опциональное, восстановить предыдущее значение, например:
if (new.param is null or new.param = null_double) then
new.param = old.param;


3. Всё то же самое - определение состава обновляемых полей и выбор варианта запроса. Или его можно собрать в процедуре и выполнить через execute statiment.
Практически тот же вариант 1, но перенесённый на сервер. :)

Кстати, что делается в случае если param == null_double для записи с новым ID?

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

Автор: Steklova Olga 9.2.2012, 21:04

Привет! :) Пишу на этот раз сразу несколько сообщений.
09.02.2012 часть 1

уточнения про Т2

1)
Цитата(Tonal @ 9.2.2012, 9:45) *
не ясно что делать при наличии 2х и более одинаковых (или очень близких) значений PARAM в массиве
У меня для Т2 предполагается, что в одном и том же массиве не могут приходить одинаковые значения.
Близкими значения могут быть, но при этом они все равно будут разными.

2)
Цитата(Tonal @ 9.2.2012, 9:45) *
Как-то ты же должна задавать на этом массиве порядок/сортировку для выборки.
Сортировка Т2 установлена всегда по возрастанию значения PARAM.

3)
Цитата(Tonal @ 9.2.2012, 9:45) *
Цитата
Поступает 1-й раз массив со значениями PARAM (1.23, 2.34, 300.45, 445.00),
оператор начинает его просмотр,
делает текущей запись со значением 300.45,

Куда позиционироватся, если следующий запрос вернёт:
PARAM (445.00, 2.34, 300.45, 1.23)
- на 300.45, так как массив с моей точки зрения не изменился, ведь он всегда сортируется по возрастанию,

Цитата(Tonal @ 9.2.2012, 9:45) *
или
PARAM (1.23, 2.34, 300.45, 300.45, 300.45, 300.45, 445.00)
- такой массив не может поступить,

Цитата(Tonal @ 9.2.2012, 9:45) *
или
PARAM (3.38, 1.32, 300.45001, 455.01, 300.44999)
- на 1.32 (на min значение), так как значение 300.45 с моей точки зрения изменилось.

09.02.2012 часть 2
-----------------------------------------------------------------------------
уточненная постановка задачи !!!

4) Пока разбиралась с программой, уточнила постановку задачи.
Она оказалась проще, чем я рассчитывала. Поняла ее так.


Есть цикл работы, например, 5 сек. Он может измениться.

Есть "анализатор" и несколько "устройств". Со всеми устройствами анализатор работает одинаково.
Анализатор (с каким-то своим внутренним циклом) собирает данные от устройства,
на основе этих данных рассчитывает массив значений параметра PARAM этого устройства
и посылает этот массив мне (не чаще раза в цикл работы 5 сек) для сохранения в БД.
(Поэтому, в Т2 у меня есть еще поле с номером устройства TO_OBJ_ID, внешний ключ.)

Передача массива для устройства в каждом рабочем цикле не является обязательной.
Если анализатор насчитал новые значения параметра для устройства, то он передает массив.
Если массив для устройства "устаканился" (определился с нужной точностью, а данные от устройства
в последнее время резко не менялись), то анализатор приостанавливает для этого устройства
передачу массива в БД.

Получается, что массив для Т2 будет поступать ко мне только при его изменении.
И я была не права, когда сказала, что
Цитата(Steklova Olga @ 3.2.2012, 21:20) *
Данные разных массивов:
- могут полностью совпадать,

И тогда логичнее, как мне кажется, был бы следующий вариант А.

А) Вариант для случая, когда массив будет поступать ко мне только при его изменении.
При поступлении очередного массива
- удалить старый массив, добавить новый массив,
сделать текущей первую запись.
(Не "отлавливать блох", проверяя, осталось ли в новом массиве значение PARAM, бывшее текущим.
Тогда и оператор сразу увидит, что массив изменился, а не будет пребывать в неведении.)


Цитата(Tonal @ 9.2.2012, 9:45) *
Что-то я не очень понял, если для Т2 используется та же структура, то почему не использовать ID для позиционирования.

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

Б) Вариант для случая, когда массив будет поступать ко мне каждый цикл, даже если массив не изменился.
Этот вариант для моей программы, получается, не нужен, но остается интерен мне теоретически, на будущее.
При поступлении очередного массива
- отсортировать новый массив по возрастанию PARAM (?),
- сравнить новый массив со старым (?),
- если они абсолютно совпадают, то оставить все как было,
- если массивы хоть немного отличаются, то удалить старый массив, добавить новый массив,
сделать текущей первую запись.
Здесь тоже можно будет для позиционирования использовать ID вместо PARAM.
ID - автоинкрементное поле.
-----------------------------------------------------------------------------

Автор: Steklova Olga 10.2.2012, 10:14

09.02.2012 часть 3
5)

Цитата(Tonal @ 9.2.2012, 9:45) *
Кстати, что делается в случае если param == null_double для записи с новым ID?
Если для записи в Т3 поступит структура
с id, которого еще не было в таблице, и с param == null_double, то в Т3 будет добавлена запись.
В INSERT я укажу поле ID, равное id, и не укажу поле PARAM.
Неуказанное поле, насколько я понимаю, по умолчанию окажется == NULL.

6)
Цитата(Tonal @ 9.2.2012, 9:45) *
Про способы использования:
1. Я имел в виду, что ты всё оставляешь как сейчас, но при генерации текста запроса вместо данных подставляешь заполнители (например символы "?").
После этого биндишь переменные (как в справке по QSqlQuery)
В этом случае данные в сервер попадают минуя преображение в строку у тебя в коде и обратно на сервере.
Меньше преобразований - меньше накапливается ошибок.
Причём объект QSqlQuery после prepare можно не удалять, а сохранить где-то, и для следующего запроса только подставить (забиндить) новые параметры.
Это ясно (для Т1).

7)
уточняющий вопрос про prepare и addBindValue

Поля таблицы T2:
ID INTEGER NOT NULL (PK, автоинкрементное поле), N записи,
TO_OBJ_ID INTEGER NOT NULL (FK), N устройства,
PARAM DOUBLE PRECISION NOT NULL, значение параметра.
Таблица Т2 может содержать [0..200] значений PARAM для каждого TO_OBJ_ID.

А для обработки поступившего массива со значениями PARAM для одного конкретного TO_OBJ_ID
вариант а) лучше, чем б) и в)? (TO_OBJ_ID здесь одно и то же, PARAM - разные)
typedef struct {
    int obj_id;
    QVector<double> param;
} tobj;
tobj* obj;
...
int param_size = obj->param.size();
if (param_size > 200)
    param_size = 200;

а)
query.prepare("INSERT INTO T2 (TO_OBJ_ID, PARAM) VALUES (?, ?)");
for (int i = 0; i < param_size; i++) {
    query.addBindValue(obj->obj_id);
    query.addBindValue(obj->param[i]);
    query.exec();
}
б)
query.prepare(QString("INSERT INTO T2 (TO_OBJ_ID, PARAM) VALUES (%1, ?)").arg(obj->obj_id));
for (int i = 0; i < param_size; i++) {
    query.addBindValue(obj->param[i]);
    query.exec();
}
в)
query.prepare("INSERT INTO T2 (TO_OBJ_ID, PARAM) VALUES (" + QString::number(obj->obj_id) + ", ?)");
for (int i = 0; i < param_size; i++) {
    query.addBindValue(obj->param[i]);
    query.exec();
}

8 )
Цитата(Tonal @ 9.2.2012, 9:45) *
В случае Т3 можно сделать кеширование.
Например QMap где ключом будет состав изменяемых полей а значением - препарированный запрос.
Вы предлагаете использовать QMap<K, T>, структуру данных, которая содержит пары ключ-значение,
упрядоченные по возрастанию ключей?
Я понимаю так, что это будет
QMap<QString, QVariant> map;
где QString - наименование поля таблицы, QVariant - значения поля таблицы.
Вставляем в map только поступившие в Rec значения полей и их наименования,
для чего для каждого поля проверяем, равно ли его значение константе null_... для соотв. типа поля.
Из полученного списка keys получаем часть строки запроса с названиями полей,
дополняем строку соотв. кол-вом символов '?',
потом в цикле делаем addBindValue, занося в запрос значения полей из полученного списка values.

9) То, что Вы написали нужно проверять в триггере, понятно. Только я пока не создавала таких триггеров в базе, а делала проверки в тексте Qt.

10) С хранимками еще совсем не разбиралась. :unknw:

11)
Цитата(Tonal @ 9.2.2012, 9:45) *
Ну и я бы стал делать вариант 2 (с триггерами) и 3 (с хранимками) только в том случае,
если данные в базу могут поступать из разных приложений.
В противном случае это дополнительное усложнение схемы данных.
Уф, успокоили, что это необязательно для моего случая.
Мне еще не доводилось писать базы, данные в которые прямо записываются из разных приложений.
Сама из Qt-программы пишу данные в БД, выдаю результаты запросов к БД другим людям, модули которых
вместе с моими находятся в одном приложении.

Уважаю за терпение дочитавших до конца! Простите студента за многословие. :)

Автор: Tonal 10.2.2012, 12:00

Т. е. с позиционированием ты разобралась. :)
Могу только добавить, что раз есть порядок, то можно позиционироваться на первый элемент больший или равный текущему или на последний меньший или равный, на выбор. :)

5. Неуказанное в insert-е поле заполняется значением по умолчанию для этого поля, которое можно указать при создании таблицы (или при создании домена). Если значение по умолчанию не указано, подставляется null.

6. Тут возможны варианты с транзакциями: сохраняется ли препарированность между транзакциями или для новой транзакции нужно препарить запросы по новой?
Если мне склероз не изменяет, Firebird в последних версиях не требовал переподготавливать запросы каждый раз, но не знаю как к этому относится драйвер в Qt.

7. Есть ещё вариант а' - использовать биндинг по имени или номеру. Тогда obj_id можно забиндить 1 раз перед циклом.
Я бы выбрал его. :)

8. Ну, если совсем втупую то примерно так (псевдокод на псевдопитоне):

# Где-то объявлен кеш запросов. Для С++ - QMap<QList<QString>, QSqlQuery>;
queryCach = {}

# Функция для сохранения записи в базу. Для C++ void updateRec(const tRec& rec);
def updateRec(rec):
  # Ключь состоит из списка имён ненулевых полей. Для С++ - QList<QString>
  keys = []
  # Список значений. Для С++ - QList<QVariant>
  vals = []
  if rec.param != null_double:
    keys.append('param')
    vals.append(rec.param)
  if rec.fint1 != null_qint32:
    keys.append('fint1')
    vals.append(rec.fint1)
  ... # то же самое для остальных полей

  # Вытаскиваем запрос по ключу
  query = queryCach.get(keys)
  # Если запроса нет в кеше - создаём и запоминаем:
  if query is None:
    # Каждое имя поле из текущего набора keys подставляем в строчку '%s = ?'
    # и сцепляем эти строчки через запятую
    sql_fld = ', '.join('%s = ?' % fld for fld in keys)
    # Подставляем строку с полями в шаблон запроса
    sql = 'update %s set %s where id = ?' % (table_name, sql_fld)
    query = QSqlQuery()
    query.prepare(sql)
    queryCach[keys] = query

  # Биндим значения и выполняем
  for val in vals:
    query.addBindValue(val)
  query.addBindValue(rec.id)
  query.exec()

Хотя можно вместо списка с QVariant использовать словарь и биндить по именам. :)
Главная идея, что сам запрос для конкретной конфигурации полей ты создаёшь только если она появилась и лишь один раз.

Автор: Steklova Olga 10.2.2012, 17:01

Привет! :)
6.

Цитата
сохраняется ли препарированность запроса между транзакциями (Firebird, Qt)
я тоже не знаю.

7.
Цитата
Есть ещё вариант а' - использовать биндинг по имени или номеру. Тогда obj_id можно забиндить 1 раз перед циклом. Я бы выбрал его.

То, что надо! :) Нашла http://www.riverbankcomputing.co.uk/static/Docs/PyQt4/html/qsqlquery.html
//а1) Named binding using named placeholders:
query.prepare("INSERT INTO T2 (TO_OBJ_ID, PARAM) VALUES (:TO_OBJ_ID, :PARAM)");
query.bindValue(":TO_OBJ_ID", obj->obj_id);
for (int i = 0; i < param_size; i++) {
    query.bindValue(":PARAM", obj->param[i]);
    query.exec();
}
//а2) Positional binding using named placeholders:
query.prepare("INSERT INTO T2 (TO_OBJ_ID, PARAM) VALUES (:TO_OBJ_ID, :PARAM)");
query.bindValue(0, obj->obj_id);
for (int i = 0; i < param_size; i++) {
    query.bindValue(1, obj->param[i]);
    query.exec();
}
//а3) Binding values using positional placeholders:
query.prepare("INSERT INTO T2 (TO_OBJ_ID, PARAM) VALUES (?, ?)");
query.bindValue(0, obj->obj_id);
for (int i = 0; i < param_size; i++) {
    query.bindValue(1, obj->param[i]);
    query.exec();
}

//Заодно поняла, как забиндить значения NULL в поля разного типа. :)
query.prepare("UPDATE OR INSERT INTO T3 (ID, PARAM, FINT1, FSTR) "
              "VALUES (:ID, :PARAM, :FINT1, :FSTR) "
              "MATCHING (ID)");
query.bindValue(":ID", 1);
query.bindValue(":PARAM", QVariant(QVariant::Double)); //запишется NULL
query.bindValue(":FINT1", QVariant(QVariant::Int)); //запишется NULL
query.bindValue(":FSTR", QVariant(QVariant::String)); //запишется NULL
//
//query.bindValue(":PARAM", NULL); //запишется 0.0
//query.bindValue(":FINT1", NULL); //запишется 0
//query.bindValue(":FSTR", NULL); //запишется "0"
//
//query.bindValue(":FSTR", ""); //запишется ""
//
query.exec();

8. Спасибо, Tonal, за пример. :) Еще разбираюсь с ним...

Форум Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)