Форум по программированию Delphi Sources

 



Вернуться   Форум по программированию Delphi Sources > Все о Delphi > Базы данных
Ник
Пароль
Регистрация <<         Правила форума         >> FAQ Пользователи Календарь Поиск Сообщения за сегодня Все разделы прочитаны

Ответ
 
Опции темы Поиск в этой теме Опции просмотра
  #1  
Старый 17.02.2019, 14:16
Аватар для Guaho
Guaho Guaho вне форума
Новичок
 
Регистрация: 27.08.2017
Сообщения: 73
Версия Delphi: Delphi7
Репутация: 10
По умолчанию Хитрое соединение данных двух таблиц

Приветствую вас, форумчане!
В процессе разработки очередной БД у меня возник вопрос, для меня как для новичка достаточно сложный, на уровне нерешаемого. Речь идёт об одной возможности, которую очень хотелось бы реализовать, и состоит она в "хитром" соединении данных двух таблиц, находящихся в отношении "Мастер-Деталь".

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

И вот вопрос - как бы сделать так, чтоб в одном гриде отобразить объединённые данные из этих двух таблиц. А именно: в новом "суммирующем" гриде к существующим полям мастер-грида добавить новые поля с заголовками в виде имён параметров (а значения параметров из детальной таблицы станут значениями в добавленных полях "суммирующего" грида). Т.е. если посмотреть визуально, то детальный грид как бы разворачивается на 90 градусов и приклеивается к правой части мастер грида (см. прилагаемый скрин).

Повторюсь, возможность очень желательная, но как это сделать (и можно ли вообще), я слабо представляю. Т.к. число записей в детальной таблице переменное, надо или динамически создавать поля для датасета, который будет хранить совмещённые данные, а потом ещё и для грида, отображающего результирующее безобразие, или использовать какой-то хитрый StringGrid. Почему "хитрый" - потому что непременным условием задачи является возможность сортировки данных по столбцам, и я пока не знаю, существует ли такая возможность даже в продвинутых гридах (например, от TMS). Кроме того (и этот момент очень существенен!), мне нужна числовая сортировка по полям значений параметров, а не текстовая, каковая будет при использовании StringGrid-а, так что вариант с "суммирующим" датасетом и гридом предпочтительнее. Результирующий датасет (если это будет датасет) может быть Read-Only, тут главное - это возможность числовой сортировки по "приклеенным" столбцам.
Изображения
Тип файла: png Мастер.PNG (15.7 Кбайт, 0 просмотров)
Тип файла: png Деталь.PNG (12.5 Кбайт, 0 просмотров)
Тип файла: jpg Совмещение.jpg (79.6 Кбайт, 0 просмотров)

Последний раз редактировалось Guaho, 17.02.2019 в 14:20.
Ответить с цитированием
  #2  
Старый 17.02.2019, 21:39
Аватар для lmikle
lmikle lmikle вне форума
Модератор
 
Регистрация: 17.04.2008
Сообщения: 7,317
Версия Delphi: 7, XE3, 10.2
Репутация: 49087
По умолчанию

Вообще, такое можно сделать.
Вот только поддерживает ли это твой сервер или компонент.
Называется это Pivot Table, когда ты "разворачиваешь" таблицу.

Можно все написать руками, но работать будет не быстро (в зависимости от размера данных). По сути, тебе потребуется TMemTable или что-то подобное. Сначала получаем список всех возможных параметров (из detail). Потом в TMemTable создаем соотв. поля + поля master'а. Далее идем по датасетам и заполняем получившийся TMemTable.
Ну, и как ты понимаешь, такая таблица будет не редактируемой. В смысле, если ты захочешь сохранить изменения, то тебе потребуется написать соотв. код для переноса изменений из этой таблицы в БД (обычно это делается через OnAfterPost).
Ответить с цитированием
Этот пользователь сказал Спасибо lmikle за это полезное сообщение:
Guaho (20.02.2019)
  #3  
Старый 19.02.2019, 16:16
brookhut brookhut вне форума
Прохожий
 
Регистрация: 21.05.2017
Сообщения: 13
Версия Delphi: rad studio xe8
Репутация: 10
По умолчанию

смотря какой компонент какого разработчика использует автор, у DevExpress есть демка я думаю она подойдёт для реализации задумки автора, скрины в приложении, справа вверху основная инфа, жмём на плюсег слева от основной инфы, появляется доп инфа разделенная на закладки
Изображения
Тип файла: png Screenshot_3.png (65.4 Кбайт, 0 просмотров)
Тип файла: png Screenshot_1.png (62.7 Кбайт, 0 просмотров)
Ответить с цитированием
Этот пользователь сказал Спасибо brookhut за это полезное сообщение:
Guaho (20.02.2019)
  #4  
Старый 20.02.2019, 19:20
Аватар для Guaho
Guaho Guaho вне форума
Новичок
 
Регистрация: 27.08.2017
Сообщения: 73
Версия Delphi: Delphi7
Репутация: 10
По умолчанию

Спасибо всем откликнувшимся!
Использую механизм Absolute Database, а для визуализации - библиотеку EhLib. И вот в ней как раз есть и TMemTable, и PivotGrid (я об этом компоненте и подумал, размышляя о задаче, и даже пытался понять, что он делает, но с первого раза не вкурил). А MemTable, как оказалось, поддерживает режим "stand-alone" (в котором работает как ни с чем не связанный массив данных). Для моей задачи как раз то что надо. Ну и инфу по PivotGrid придётся покурить тщательнее )))
З.Ы. Вариант, чтоб что-то раскрывалось по нажатию "плюсиков", мне не подходит. Это, кстати, в EhLib-е есть. А тут нужно в итоге получить самый простой (с виду) грид с совмещёнными полями и данными, чтоб никаких лишних кликов, а только сортировка с целью сравнения значений параметров.
Ответить с цитированием
  #5  
Старый 25.02.2019, 22:01
DenSarych DenSarych вне форума
Прохожий
 
Регистрация: 02.08.2018
Сообщения: 1
Версия Delphi: Delphi 7
Репутация: 10
По умолчанию

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

Для этого на клиенте создаём sql-запрос вручную.
Нам потребуется два TQuery. Первый, назовём его QueryParams, который покажет нам сколько дополнительных столбцов нам потребуется. А во втором, назовём его QueryRes, мы построим sql-запрос, выполним его и отобразим полученные данные в DbGrid.

Допустим у нас есть три таблицы c колонками: TbMaster (TbMaster_id, Name); TbDetail (TbDetail_id, Name); TbMstDetail (TbMstDetail_id, TbMaster_id, TbDetail_id, Value). И нам требуется вывести в строку значения параметров Value. Другими словами, две таблицы связаны с помощью третьей связью многие ко многим, в которой и содержатся значения параметра.

Текст первого запроса QueryParams очищаем. QueryParams.SQL.Clear
Вводим в него sql-запрос наподобии следующего:
Код:
  
  select distinct md.TbDetail_id
    from TbMaster m
    left join TbMstDetail md on m.TbMaster_id = md.TbMaster_id
Выполним его. QueryParams.Open
Мы получим перечень наших дополнительных столбцов, которые используются в нашей выборке по таблице TbMaster. Количество столбцов возьмём из QueryParams.RecordCount. Кроме того, получим id наших параметров.

Теперь сформируем наш второй запрос QueryRes.
В нём нам нужно получить sql-запрос наподобии следующего (например, для трёх параметров c id: 10, 11, 12):
Код:
  select m.num, m.name, m.description
    ,md1.Value prm1 
    ,md2.Value prm2 
    ,md3.Value prm3 
  from TbMaster m
  left join TbMstDetail md1 on m.TbMaster_id = md1.TbMaster_id and md1.TbDetail_id = 10
  left join TbMstDetail md2 on m.TbMaster_id = md2.TbMaster_id and md2.TbDetail_id = 11
  left join TbMstDetail md3 on m.TbMaster_id = md3.TbMaster_id and md3.TbDetail_id = 12
  order by m.Name  
Текст второго запроса QueryRes очищаем.
Добавляем в него "обязательную часть", т.е. то что было первоначально на картинке "мастер" в первом посте:
Код:
  QueryRes.Sql.Add('select m.num, m.name, m.description');
Дальше в цикле for i:= 1 to QueryParams.RecordCount добавляем колонки, в которых будут отображаться значения параметров:
Код:
  QueryRes.Sql.Add('  , md'+IntToStr(i) +'.Value prm'+IntToStr(i));
Т.е. получаем строки:
Код:
  '  ,md1.Value prm1'
Добавляем в текст запроса ещё одну "обязательную часть":
Код:
  QueryRes.Sql.Add('  from TbMaster m');
A дальше в цикле for i:= 1 to QueryParams.RecordCount добавляем связи:
Код:
  QueryRes.Sql.Add('  left join TbMstDetail md'+IntToStr(i)
    +' on m.TbMaster_id = md'+IntToStr(i)+'.TbMaster_id and  md'+IntToStr(i)+'.TbDetail_id = '+IntToStr(n1));
Т.е. получаем строки:
Код:
    '  left join TbMstDetail md1 on m.TbMaster_id = md1.TbMaster_id and md1.TbDetail_id = 10'
Завершаем текст sql сортировкой, а при необходимости и условием отбора where:
Код:
  QueryRes.Sql.Add('  order by m.Name');
Наш запрос готов, запускаем его. QueryRes.Open
Переименовываем (или добавляем) столбцы в DbGrid.

Последний раз редактировалось DenSarych, 27.02.2019 в 08:23.
Ответить с цитированием
Этот пользователь сказал Спасибо DenSarych за это полезное сообщение:
Guaho (01.03.2019)
  #6  
Старый 01.03.2019, 21:33
Аватар для Guaho
Guaho Guaho вне форума
Новичок
 
Регистрация: 27.08.2017
Сообщения: 73
Версия Delphi: Delphi7
Репутация: 10
По умолчанию

Спасибо! Благодарю за столь подробное разъяснение!
Метод с виду подходящий, хотя и несколько сложноватый, как для меня, но тем не менее смысл понятен. Однако мне представляется более оптимальным другой путь, более простой, с использованием компонента TMemTableEh из библиотеки EhLib. Указанный компонент может работать как ни с чем не связанный набор данных. Этот внутренний датасет нужно сначала создать, определив его поля, причём определить их можно в Run Time, что мне и нужно. А далее с ним можно работать как душе угодно (мне будут нужны только функции сортировки и поиска). И вот в чём состоит тогда упрощённый метод:
1. Пробегаемся по всем записям мастер-датасета, из которых нужно составить сводную таблицу, и для каждой записи просматриваем список параметров, а из него формируем (занося в отдельную таблицу) все просмотренные параметры (если какой-то уже есть, его пропускаем). В итоге получаем датасет со списком имён всех параметров, которые должны быть показаны в сводной таблице.
2. Создаём внутренний датасет TMemTableEh, динамически создавая сначала поля мастер-таблицы, а затем поля имён параметров (из только что созданного датасета со списком имён всех параметров).
3. Заполняем датасет данными: перебираем все записи в мастере, формируя "мастерную" часть данных, а внутри этого цикла - перебираем детальные данные (т.е. параметры, привязанные к текущей мастер-записи), и из них формируем список значений для "детальной" части совмещённого датасета.
Время выполнения этой операции тут некритично, т.к. эта функция не будет основной, и, если надо, пользователь подождёт 1 - 2 секунды (хотя уверен, что всё выполнится намного быстрее, да и число записей не предполагается слишком большим).
Для визуализации надо будет динамически создавать столбцы в DBGridEh; если у меня это не получится, можно нагородить кучу полей с запасом.

Последний раз редактировалось Guaho, 01.03.2019 в 21:35.
Ответить с цитированием
  #7  
Старый 05.03.2019, 21:35
Аватар для Guaho
Guaho Guaho вне форума
Новичок
 
Регистрация: 27.08.2017
Сообщения: 73
Версия Delphi: Delphi7
Репутация: 10
По умолчанию

Мне самому трудно поверить, что таки получилось сделать эту функцию, реализация которой ещё недавно казалась почти невозможной, а в итоге оказалась ещё проще, чем можно было представить.
В общем, попробовал я работать с компонентом TMemTable от EhLib. Компонент оказался ужасно глючным: создание полей даже в Disign-time часто приводило к крэшу (с потерей части ранее записанных данных), в итоге на простейшую операцию ушла куча времени. Кроме того, часть созданных строковых полей оказалась "прОклятой" - туда не удавалась записать более 20 символов, хотя длина поля была указана = 250, а пересоздание поля не помогало. Динамически создать поле в датасете можно было без проблем (руководствуясь кодом из примеров), только подключить его к гриду - ну никак (может, в реальности ни фига и не создавалось). Ну и наконец, оказалось, что поля типа "Float" этот датасет не знает, только Integer или Variant. Я попробовал второе, в итоге получил сортировку по числовым полям как по текстовым... Полная ерунда в общем!
И тут меня осенило: а чего я зацепился за этот компонент? А что если поля датасета создать статически? Ведь в практической работе, для этой специфики, число параметров одного компонента редко превышает 10, ну 15 - потолок. И вот я сделал 20 статических полей для параметров (с железным запасом, а если вдруг что не влезет - отображаться не будет) в стандартном DBQuery (точнее, ABSDBQuery). Все эти поля - типа Float. Процедура сортировки у меня своя, отработанная уже. Остальное оказалось делом техники - заполнение заголовков (и их хинтов) грида, заполнение данными и значениями параметров. В прикреплённом изображении - вид того, что получилось (включена сортировка по одному из столбцов параметров + виден хинт на заголовок одного из столбцов).
Изображения
Тип файла: jpg Без имени-2.jpg (72.9 Кбайт, 0 просмотров)

Последний раз редактировалось Guaho, 05.03.2019 в 21:38.
Ответить с цитированием
Ответ



Опции темы Поиск в этой теме
Поиск в этой теме:

Расширенный поиск
Опции просмотра

Ваши права в разделе
Вы не можете создавать темы
Вы не можете отвечать на сообщения
Вы не можете прикреплять файлы
Вы не можете редактировать сообщения

BB-коды Вкл.
Смайлы Вкл.
[IMG] код Вкл.
HTML код Выкл.
Быстрый переход


Часовой пояс GMT +3, время: 19:35.


 

Сайт

Форум

FAQ

RSS лента

Прочее

 

Copyright © Форум "Delphi Sources", 2004-2019

ВКонтакте   Facebook   Twitter