Russian Qt Forum
Ноябрь 27, 2024, 07:04 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: [1]   Вниз
  Печать  
Автор Тема: БД с самоописанием  (Прочитано 7593 раз)
QuAzI
Гость
« : Декабрь 18, 2012, 11:44 »

Т.к. 90% справочников в духе "Справочник банков", "Справочник сотрудников", "Справочник справочников" и прочая лапша сводятся к тупой таблице с редактированием полей, хочется сделать абстрактный шаблон, который можно было бы вызвать передав имя таблицы, а дальше чтобы он сам подтянул имена, ширину и тип колонок в таблице. Т.к. мысль собственно сама напрашивается при долгом возюкании с базами, стало интересно, может кто уже делал? Как храните в БД или получаете информацию о полях, о их внешних ключах (для автозабивания в comboBox), ведёте ли версионность всей базы. А то что-то потыкался и ничего интересного на эту тему не нашёл, только про организацию версионных миграций на хабре чутка есть.
Ну и может кто потычет в литературку на мотив Coding Convention но только для SQL? А то постоянно чуйство, что что-то не так делаю  Смеющийся
Записан
alex312
Хакер
*****
Offline Offline

Сообщений: 606



Просмотр профиля
« Ответ #1 : Декабрь 18, 2012, 12:00 »

Все твои хотелки называются ORM.
Вот какой-то обзорчик - http://habrahabr.ru/post/105191/
Записан
QuAzI
Гость
« Ответ #2 : Декабрь 18, 2012, 12:20 »

ORM - это обвязка каждой таблицы как отдельного объекта, тот же hibernate используют для описания каждой таблицы отдельную xml-ку, меня же как раз не устраивает такое возюкание кучи файлов, потому что в таком случае к контролю за версией ПО и версией БД надо ещё добавлять отслеживание этой мелкой лапши, на всех хостах.
И даже если я решу обвязать таблицу в объект... ну всё равно же имена полей надо откуда-то тянуть. Т.е. меня сейчас не интересует таблица, как объект, меня интересует получение информации о таблице, о каждом её поле. Некоторые умудряются тянуть данные из schema, видел как-то давно даже пару примеров, где народ в COMMENT указывал имя поля, которое потом выводилось юзверю. Но это было весьма коряво и не на всех СУБД провернёшь.
Я пока склоняюсь создать табличку tables, в которой буду хранить всё нужное, но это конечно велосипед, который стопудово уже кто-нить делал.

Чтобы было более понятно, от чего хочу избавиться: сотен записей вида
Код:
    model->setHeaderData(0, Qt::Horizontal, tr("IFI"));
    model->setHeaderData(1, Qt::Horizontal, tr("Name"));
    model->setHeaderData(2, Qt::Horizontal, tr("Address"));
... и такая лапша на каждую таблицу, на каждое поле, не считая размеры и прочее оформление

Иже в примере к вашей QsT, на каждую таблицу есть
Код:
<< QstField("LastName", FieldVisible,  "Фамилия", 100)
<< QstField("FirstName", FieldVisible,  "Имя", 100)
<< QstField("ParentName", FieldVisible,  "Отчество", 100)
<< QstField("vcBirthDate", FieldVisible,  "Дата\nрождения", 90)
<< QstField("Phone", FieldVisible,  "Контактный\nтелефон", 120)
<< QstField("[E-Mail]", FieldVisible,  "e-mail", 120)
Хотя в принципе рулять видимостью, именем и длинной поля можно было бы из самой БД, не дёргая обновление всего софта по организации на каждый такой чих
« Последнее редактирование: Декабрь 18, 2012, 12:30 от QuAzI » Записан
panAlexey
Гипер активный житель
*****
Offline Offline

Сообщений: 864

Акцио ЗАРПЛАТА!!!!! :(


Просмотр профиля
« Ответ #3 : Декабрь 18, 2012, 14:47 »

Т.к. 90% справочников в духе "Справочник банков", "Справочник сотрудников", "Справочник справочников" и прочая лапша сводятся к тупой таблице с редактированием полей, хочется сделать абстрактный шаблон, который можно было бы вызвать передав имя таблицы, а дальше чтобы он сам подтянул имена, ширину и тип колонок в таблице. Т.к. мысль собственно сама напрашивается при долгом возюкании с базами, стало интересно, может кто уже делал?

Делал:

Код:
void uoMainWindow::createTables()
{
QString dbName = qApp->applicationDirPath() + QDir::separator()+"main.db";
#ifdef Q_OS_LINUX
QDir home = QDir::home();
if (!home.exists(".uoReceptor")) {
home.mkpath(".uoReceptor");
}
dbName = home.absolutePath() + "/.uoReceptor/" + QDir::separator()+"main.db";
#endif


m_dbMan->setupDataBase(dbName);
m_receptTable = m_dbMan->addRefTable("ШаблоныPецептов","Шаблоны рецептов",6,120);
m_receptTable->setOption(codeType_Number, true);
m_receptTable->addField("Комментарий", fieldType_String,150);
m_templField = m_receptTable->addField("Шаблон", fieldType_TextHTML);
uoMdiObserver::instance()->m_receptTableId = m_receptTable->m_baseNom;

uoDbRefTable* tmlpTabl = m_dbMan->addRefTable("Лекарства","Лекарства",6, 70);
tmlpTabl->addField("ЛатИмя",fieldType_String,70)->m_descr = "Латинское наименование";
tmlpTabl->addField("Описание",fieldType_Text);
tmlpTabl->setOption(codeType_Number, true);

tmlpTabl = m_dbMan->addRefTable("Симптомы","Симптомы",6, 100);
tmlpTabl->setOption(codeType_Number, true);
tmlpTabl->addField("Описание",fieldType_Text);

tmlpTabl = m_dbMan->addRefTable("Заболевания","Заболевания",6, 100);
tmlpTabl->setOption(codeType_Number, true);
tmlpTabl->addField("Описание",fieldType_Text);

m_dbMan->restructuring();//  Реструктуризация
m_dbMan->createGuiFactory(this);
m_receptTemplDocWidget = new QDockWidget(tr("Recept template"), this);
m_receptTemplDocWidget->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);

uoUrl url = m_receptTable->getUoUrlByForm(uoFormT_Tree);
url.setWithElem(true);
m_docTreeFormRecept = m_dbMan->getForm(url);
if (m_docTreeFormRecept) {
m_docTreeFormRecept->setParent(m_receptTemplDocWidget);
m_receptTemplDocWidget->setWidget(m_docTreeFormRecept);
addDockWidget(Qt::LeftDockWidgetArea, m_receptTemplDocWidget);
m_actionShowDocRecept = m_receptTemplDocWidget->toggleViewAction();
}
}
строится меню, формы списков и т.п. многое хочется доделать, но руки пока не доходят.
« Последнее редактирование: Декабрь 18, 2012, 14:56 от panAlexey » Записан

Win Xp SP-2, Qt4.3.4/MinGW. http://trdm.1gb.ru/
QuAzI
Гость
« Ответ #4 : Декабрь 18, 2012, 16:44 »

Я как раз вот от этого хочу уйти.
Код:
	tmlpTabl->addField("ЛатИмя",fieldType_String,70)->m_descr = "Латинское наименование";
tmlpTabl->addField("Описание",fieldType_Text);
tmlpTabl->setOption(codeType_Number, true);

tmlpTabl = m_dbMan->addRefTable("Симптомы","Симптомы",6, 100);
tmlpTabl->setOption(codeType_Number, true);
tmlpTabl->addField("Описание",fieldType_Text);

tmlpTabl = m_dbMan->addRefTable("Заболевания","Заболевания",6, 100);
tmlpTabl->setOption(codeType_Number, true);
tmlpTabl->addField("Описание",fieldType_Text);
1) Ручное набивание всего и вся для каждой самой мелкой таблички. Не столько полезного кода, сколько рукоблудия с оформлением.
2) Именование/оформление может плыть в зависимости от версии софта (хотя одна БД казалось бы должна одинаково везде смотреться)
3) Когда ПО не одно (например админка отдельно, генератор отчётов отдельно, ещё какая-нить веб-морда отдельно), это вообще заканает когда число таблиц перевалит за пол ста.
Записан
panAlexey
Гипер активный житель
*****
Offline Offline

Сообщений: 864

Акцио ЗАРПЛАТА!!!!! :(


Просмотр профиля
« Ответ #5 : Декабрь 18, 2012, 20:24 »

Я как раз вот от этого хочу уйти.
Код:
	tmlpTabl->addField("ЛатИмя",fieldType_String,70)->m_descr = "Латинское наименование";
tmlpTabl->addField("Описание",fieldType_Text);
tmlpTabl->setOption(codeType_Number, true);

tmlpTabl = m_dbMan->addRefTable("Симптомы","Симптомы",6, 100);
tmlpTabl->setOption(codeType_Number, true);
tmlpTabl->addField("Описание",fieldType_Text);

tmlpTabl = m_dbMan->addRefTable("Заболевания","Заболевания",6, 100);
tmlpTabl->setOption(codeType_Number, true);
tmlpTabl->addField("Описание",fieldType_Text);
1) Ручное набивание всего и вся для каждой самой мелкой таблички. Не столько полезного кода, сколько рукоблудия с оформлением.
2) Именование/оформление может плыть в зависимости от версии софта (хотя одна БД казалось бы должна одинаково везде смотреться)
3) Когда ПО не одно (например админка отдельно, генератор отчётов отдельно, ещё какая-нить веб-морда отдельно), это вообще заканает когда число таблиц перевалит за пол ста.

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

Ты хочешь просто софтине указать таблицы в имеющейся БД что-бы она построила интерфейс.
Нормальное желание.
Теоретически можно доделать мою софтину, указав ей БД/юзера/пассворд и задав задачу "Считай ка мне в m_dbMan структуру и построй интерфейсы"

А откуда приемлемые имена/колонки для таблиц найдешь?
В комментариях к полям?
Можно завести пару табличек вроде словаря с сопоставлением и т.п.
Это не проблема.
Хотя моя на скулайт настроена.
« Последнее редактирование: Декабрь 18, 2012, 20:27 от panAlexey » Записан

Win Xp SP-2, Qt4.3.4/MinGW. http://trdm.1gb.ru/
xokc
Птица говорун
*****
Offline Offline

Сообщений: 976



Просмотр профиля
« Ответ #6 : Декабрь 19, 2012, 08:29 »

В Delphi есть похожий функционал - там DBGrid самостоятельно умеет вытаскивать наименование,  тип колонок и ширину полей из БД. Но всё равно нужно настраивать кучу вещей - начиная от видимости только нужных полей, регулировки ширины и т.п. Там правда это всё мышкой делалось, но всё равно доставало.
Так что искомое решение было бы полезным.
Записан
QuAzI
Гость
« Ответ #7 : Декабрь 19, 2012, 11:35 »

В общем для MySQL прокатывает что-то типа такого:
1) Строим список справочников
Код:
select TABLE_NAME, TABLE_COMMENT 
from information_schema.tables
WHERE (table_schema = 'ASRGTS') AND (TABLE_COMMENT LIKE 'Справочник%');
2) Получаем параметры справочника
Код:
SELECT COLUMN_NAME, DATA_TYPE,  CHARACTER_MAXIMUM_LENGTH, NUMERIC_PRECISION, NUMERIC_SCALE, COLUMN_COMMENT
FROM information_schema.columns
WHERE (table_name = 'CONTRACTS') AND (table_schema = 'ASRGTS');
Где DATA_TYPE будет содержать тип данных и если это varchar, то размер сохраняется в CHARACTER_MAXIMUM_LENGTH, если это число, то размеры целой/дробной части хранятся в NUMERIC_PRECISION/NUMERIC_SCALE.
В *_COMMENT соответственно описание объекта, которое можно вывернуть пользователю как имя справочника/колонки.
По минимуму как-то так. Ну и опять же для MySQL, на остальном надо проверять какие приколы будут.
А вот если хочется большего, как например ширину колонки, видимость, детальное описание (для подсказки), маску ввода и т.п. хранить - придётся держать отдельно таблицу-справочник под всё это дело. И это будет работать уже на любых СУБД.
Записан
алексей
Гость
« Ответ #8 : Декабрь 20, 2012, 10:13 »

Хранимая процедура (Firebird) - получение типа поля, порядкового номера
Код:
SET TERM ^ ;

CREATE PROCEDURE GET_FIELD_TYPE(
  TABLE_NAME CHAR(255) CHARACTER SET UTF8 COLLATE UTF8,
  FIELD_NAME CHAR(255) CHARACTER SET UTF8 COLLATE UTF8)
RETURNS(
  FIELD_POSITION SMALLINT,
  FIELD_TYPE_NAME CHAR(50) CHARACTER SET UTF8 COLLATE UTF8)
AS
BEGIN
FOR SELECT RDB$RELATION_FIELDS.RDB$FIELD_POSITION,
RDB$TYPES.RDB$TYPE_NAME
FROM RDB$RELATION_FIELDS
INNER JOIN RDB$FIELDS ON
(RDB$RELATION_FIELDS.RDB$FIELD_SOURCE = RDB$FIELDS.RDB$FIELD_NAME)
INNER JOIN RDB$TYPES ON
(RDB$FIELDS.RDB$FIELD_TYPE = RDB$TYPES.RDB$TYPE)
WHERE
((RDB$RELATION_FIELDS.RDB$RELATION_NAME=:TABLE_NAME)
AND
(RDB$TYPES.RDB$FIELD_NAME = 'RDB$FIELD_TYPE')
AND (RDB$RELATION_FIELDS.RDB$FIELD_NAME=:FIELD_NAME))
INTO FIELD_POSITION, FIELD_TYPE_NAME
DO
SUSPEND;
END^

SET TERM ; ^
Записан
QuAzI
Гость
« Ответ #9 : Декабрь 21, 2012, 16:41 »

Код:
CREATE TABLE IF NOT EXISTS columns_description(
  TABLE_NAME VARCHAR(255) NOT NULL COMMENT 'Имя таблицы',
  COLUMN_NAME VARCHAR(255) NOT NULL COMMENT 'Имя колонки',
  DESCRIPTION VARCHAR(255) DEFAULT NULL COMMENT 'Описание',
  WIDTH DECIMAL(10, 0) UNSIGNED NOT NULL DEFAULT 0 COMMENT 'Ширина',
  HIDDEN TINYINT(1) NOT NULL DEFAULT 0 COMMENT 'Спрятать',
  READONLY TINYINT(1) NOT NULL DEFAULT 0 COMMENT 'Только чтение',
  UNIQUE INDEX UK_columns_description (TABLE_NAME, COLUMN_NAME)
)
ENGINE = INNODB
CHARACTER SET utf8
COLLATE utf8_general_ci
COMMENT = 'Описание колонок БД'
SELECT TABLE_NAME,
       COLUMN_NAME,
       COLUMN_COMMENT AS DESCRIPTION,
       10*CHARACTER_MAXIMUM_LENGTH AS WIDTH
FROM information_schema.COLUMNS
WHERE (TABLE_SCHEMA = database()) AND (TABLE_NAME in (
    SELECT TABLE_NAME
    FROM information_schema.TABLES
    WHERE (TABLE_TYPE = 'BASE TABLE') AND (TABLE_SCHEMA = database())
));
Таблица создаётся, заполняется, но есть пара косяков, не пойму как правильно их разрулить.
1) Для полей полученных через SELECT не устанавливается COMMENT;
2) Поля не полученные через SELECT оказываются впереди остальных полей. Т.е. порядок полей при создании таблицы не соответствует тому, что получается в итоге. Это немного раздражает и хотелось бы иметь порядок который я сам задал.

Дальше простенькая вьюха
Код:
CREATE OR REPLACE SQL SECURITY INVOKER VIEW columns_schema
AS SELECT `c`.`TABLE_NAME` AS `TABLE_NAME`
     , `c`.`COLUMN_NAME` AS `COLUMN_NAME`
     , `c`.`DATA_TYPE` AS `DATA_TYPE`
     , `c`.`CHARACTER_MAXIMUM_LENGTH` AS `CHARACTER_MAXIMUM_LENGTH`
     , `c`.`NUMERIC_PRECISION` AS `NUMERIC_PRECISION`
     , `c`.`NUMERIC_SCALE` AS `NUMERIC_SCALE`
     , `c`.`COLUMN_COMMENT` AS `COLUMN_COMMENT`
     , `d`.`DESCRIPTION` AS `DESCRIPTION`
     , `d`.`HIDDEN` AS `HIDDEN`
     , `d`.`WIDTH` AS `WIDTH`
     , `d`.`READONLY` AS `READONLY`
FROM
  (`information_schema`.`COLUMNS` `c`
JOIN `columns_description` `d`
ON (((`c`.`TABLE_NAME` = `d`.`TABLE_NAME`) AND (`c`.`COLUMN_NAME` = `d`.`COLUMN_NAME`))))
WHERE
  (`c`.`TABLE_SCHEMA` = database());
Которая юзается как-то так
Код:
select * from columns_schema where TABLE_NAME='CONTRACTS';
Записан
Nimbus
Гость
« Ответ #10 : Январь 20, 2013, 10:23 »

Все твои хотелки называются ORM.
Вот какой-то обзорчик - http://habrahabr.ru/post/105191/
У фреймворка Wt есть клёвый ORM. Про него почему-то забыли
Записан
Страниц: [1]   Вверх
  Печать  
 
Перейти в:  


Страница сгенерирована за 0.109 секунд. Запросов: 22.