9737
@ Подписаться
Сотни бизнес-методик. Тысячи кейсов. Обновления.

сегодня 13889 Подписчиков

Политика конфиденциальности Этот сайт использует cookies, чтобы повысить удобство его использования Вами Понятно

Идеализация структуры данных. Идеальная функция (приложение ТРИЗ к IT)

Обсуждения-аналоги

Скрыть / Показать Сортировать по дате
2015-10-28 18:48:52
Редакция » Всем
Уважаемые Коллеги!

Ранее обещанный материал о приложении ТРИЗ к программированию:

"Идеализация структуры данных" доступен по ссылке.

Обсуждение - здесь.

Спасибо, 
2015-11-01 01:36:01
Всеволод » Сергей В. Сычёв

Здравствуйте, Коллеги!

Большое спасибо за интересную статью. Появилось несколько вопросов.

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

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

2. Неявно предполагается наличие свойства, уникального для данного типа набора. Так?

Например, свойство Фамилия маркирует набор типа Человек, благодяря чему можно сделать выборку всех людей. Однако, такого маркирующего свойства может не случиться. Или оно изменится в будущем, а мы не учли. Напрашивается простое решение: добавить в каждый набор обязательное свойство Тип (Человек, Документ, Пароход, ...) Но тогда как тень отца Гамлета снова появляются сущности или объекты, которые теперь именуются набором и имеют Тип. Причем, структура данных позволяет Тип не указывать, а если код рассчитан на то, что Тип существует - опять глюки. Для сравнения: если существует таблица Пароходы, то все, что в ней обязательно и только Пароходы. А если есть столбец с уникальным id, то каждый Пароход учтен и не потеряется.

И еще пример. Если мне звонит верстальщик, и жалуется, что у него с газетной полосы пропала статья, я смотрю в таблицу Статьи, нахожу по значениям свойств потерявшуюся (у нее, например, слетел линк на полосу) и быстро восстанавливаю. В сычевской структуре, так просто, видимо, не получится?

2015-11-01 13:13:25
Сергей В. Сычёв » Всеволод

Уважаемый Всеволод!
 

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

Если Вы имеете в виду ситуацию, показанную ниже синим (один набор, одинаковое имя, разные значения), то это может быть и не ошибка. Это кому-то зачем-то потребовалось "кантри" и "рок-н-ролл" получить/обработать общим набором. Если это позволить сделать, у него получится.

Sets
Set_idName_idValue_id___________
1 3 4 Все записи Стинга
2 2 2 Весь
рок-н-ролл
2 2 5 Все записи кантри
3 1 1 Все рыжие исполнители
... ... ...  


Values

Value_IdValues
1 Рыжие
2 Рок-н-ролл
3 Блондины
4 Стинг
5 Кантри
6 Брюнеты
7 ... и т.д. ...

Константы ("Names") в подключаемом файлe:

$Цвет волос автора = 1
$Музыкальные жанры = 2
$Исполнители = 3
$.......=........

Неявно предполагается наличие свойства, уникального для данного типа набора.... Однако, такого маркирующего свойства может не случиться. Или оно изменится в будущем, а мы не учли. ........ добавить в каждый набор обязательное свойство Тип... Но тогда как тень отца Гамлета снова появляются сущности или объекты, которые теперь именуются набором и имеют Тип. Причем, структура данных позволяет Тип не указывать, а если код рассчитан на то, что Тип существует - опять глюки. Для сравнения: если существует таблица Пароходы, то все, что в ней обязательно и только Пароходы. А если есть столбец с уникальным id, то каждый Пароход учтен и не потеряется.

Это не "тип". Это любой "маркер", который Вы можете завести, например, в список констант (Names). Но это не тип, также как ID - это не "тип", верно ведь? Вы можете его использовать, можете не использовать, можете изменять и т.д. При этом, код вовсе не рассчитан на то, что "какой-то тип существует": как были неизменные идеальные функции вида:

  • any($set_Id, $name, $value) - так и остались. 

В общем, нет никакой необходимости заводить таблицу "Пароходы" :)

И еще пример. Если мне звонит верстальщик, и жалуется, что у него с газетной полосы пропала статья, я смотрю в таблицу Статьи, нахожу по значениям свойств потерявшуюся (у нее, например, слетел линк на полосу) и быстро восстанавливаю. В сычевской структуре, так просто, видимо, не получится?

Получится даже очень просто: одним get'ом. Если Вы знаете заголовок статьи, можете сделать запрос, который вернёт (например) id статьи, саму статью и/или линк на неё.

И фраза "я смотрю в таблицу" ложная. Компьютер должен осуществить поиск в базе, а не Ваши глаза. Например, на этом Форуме более 20 тысяч разных обсуждений и вот попробуйте просмотреть глазами предполагаемую таблицу "Обсуждения". А есть проекты, как Вы понимаете, и гораздо крупнее.

В целом, частый аргумент: "Я зато быстро посмотрю в конкретной таблице "Статьи (Пароходы и т.д.)" ошибочен. Он может работать короткое время, когда у Вас мало записей. А Вы представьте мысленно, что их сотни тысяч и многие внешне похожи (например, разных записей, начинающихся со слова "Барби" в номенклатурном справочнике могут быть тысячи; записей, которые трудно прочесть глазами - например, "артикулов поставщика (рандомных)" - десятки тысяч и т.п.) - глядеть - не наглядеться.

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

И, конечно, когда в базе "завелись"  "Статьи" и "Пароходы", стоит перечитать первые 2 способа построения структуры данных, описанные в начале статьи - будут те же проблемы. 

Большое Спасибо за Ваши вопросы, спрашивайте ещё :)

2015-11-02 01:32:08
Всеволод » Сергей В. Сычёв
Здравствуйте, Коллеги!

Если Вы имеете в виду ситуацию, показанную ниже синим (один набор, одинаковое имя, разные значения), то это не ошибка. Это кому-то зачем-то потребовалось "кантри" и "рок-н-ролл" получить/обработать общим набором. Если это позволить сделать, у него получится.

Да. Гибкость большая. Но, я предполагаю, что придется тщательнее следить за целостностью данных. В случае когда, например, нельзя допускать несколько одноименных значений в наборе.

У себя для основных свойств мы используем структуру 1. Она названа “слабой” несправедливо. У нее есть сильные стороны: ее трудно испортить (изменить). А нам это и надо. Мы знаем крупного европейского разработчика, который тоже использует структуру 1. При этом в каждой таблице сущностей оставляют несколько пустых полей на всякий случай. Если что-то потом понадобится докрутить. Такая структура (1) понятна заказчику, под нее он соглашается выделять бюджет и/или ждать, когда перепишут программу под его новые требования. Интересно, изменится ли такая ситуация?

Также мы используем структуру EAV обычную для второстепенных свойств. По ходу дела туда накидывают что угодно. Теперь там бардак; таблица разрослась и стала тормозить. Но с этим справились кешированием. Тормозила, ибо вся из стрингов. Сущность описывается не целочисленным id, а строкой “id+тип”. От типа не отвязались, чтобы определять чьи же это свойства. Вдобавок там остается мусор из свойств удаленных объектов. Чистить пока некому.

Что касается EAV по Сычёвски, то, видимо, понадобится генерация уникального id для таблицы наборов, в которой столбец Set_id будет внешним ключом. Не знаю, понадобится ли еще один столбец с уникальным id для таблицы наборов? По умолчанию, таковой мы везде вставляем. В случае шардинга таблицы Values, соответствующий столбец в таблице наборов перестанет быть внешним ключом. Так?
2015-11-02 14:31:42
Всеволод » Всем

Здравствуйте, Коллеги!

Еще ремарка. Часто заказчик хочет (хотя и не говорит этого в слух, потому что считает, что это не возможно или труднореализуемо) хранить историю изменения свойств. Т.е. при изменении свойства, не затирать предыдущее, а добавить еще одно значение и time stamp изменения. При этом актуальным будет только одно значение - последнее. А остальные нужны для особых случаев (отчетов, поисков и т.п.)
А еще лучше хранить и id пользователя, который изменил свойство (его ip, фото, отпечатки пальцев...) В структуре (1) мы просто добавим соответствующие столбцы.
Реализуемо ли это в EAV по Сычевски?

2015-11-02 15:18:41
Сергей В. Сычёв » Всеволод
Уважаемый Всеволод!

Да. Гибкость большая. Но, я предполагаю, что придется тщательнее следить за целостностью данных. В случае когда, например, нельзя допускать несколько одноименных значений в наборе.

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

В функции Add ядра (то есть, уже на уровне запроса к базе) используем проверку. 

Например, вот такая конструкция (функция Add создаст её автоматически):

UPDATE OR INSERT INTO SETS (SET_ID, NAME_ID, VALUE_ID) VALUES (1, 1, 12) matching (SET_ID, NAME_ID);

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

Sets
Set_idName_idValue_id___________
1 3 4 Все записи Стинга
2 2 2 Весь 
рок-н-ролл
2 2 5 Все записи кантри
3 1 1 Все рыжие исполнители
... ... ...  


Sets

Set_idName_idValue_id___________
1 3 4 Все записи Стинга
2 2 5 Все записи кантри
3 1 1 Все рыжие исполнители
... ... ...  


У себя для основных свойств мы используем структуру 1. Она названа “слабой” несправедливо. У нее есть сильные стороны: её трудно испортить (изменить). А нам это и надо.


Это только кажется. Прочитайте, например, материал "Освобождение узников оператора IF", начиная прямо с первой истории.


Мы знаем крупного европейского разработчика, который тоже использует структуру 1. При этом в каждой таблице сущностей оставляют несколько пустых полей на всякий случай. Если что-то потом понадобится докрутить. Такая структура (1) понятна заказчику, под нее он соглашается выделять бюджет и/или ждать, когда перепишут программу под его новые требования. Интересно, изменится ли такая ситуация?

Нередко подобное происходит потому, что аутсорсинговая компания сдаёт в аренду "людей х часы". Поэтому мотивация её понятна. А при сычёвских методах разводить опытного Заказчика на лишнюю работу уже не получится.

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

1) Идеально - это когда программу сможет в отсутствие Автора не только сопровождать, но и развивать программист с меньшей квалификацией, чем Автор программы.

2) Идеально - когда добавление новой функциональности в программу не потребует внесения изменений и/или добавлений в программный код (более того, в совершенно идеальной программе увеличение функциональности приводит к сокращению кода); причём всё это без ущерба для любых параметров (например, быстродействия и т.д.).

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

Также понятно, что когда Заказчик платит и не спорит, то разработчик становится «крупным и европейским», но это как русский ремонт квартиры - прораб надувает смету и сроки, а Клиент страдает и беднеет.

Чтобы такого в мире было поменьше и написана наша статья.

Большое Спасибо за Ваши вопросы, 

2015-11-02 15:26:33
Александр Сычёв » Всеволод

Уважаемый Всеволод!

Также мы используем структуру EAV обычную для второстепенных свойств. По ходу дела туда накидывают что угодно. Теперь там бардак; таблица разрослась и стала тормозить. Но с этим справились кешированием. Тормозила, ибо вся из стрингов.

Наверное, не надо так делать. То есть, не надо делить свойства на «первостепенные» и «второстепенные» - это не только потребует создания кода, который будет их «различать и взвешивать», но и потребует бизнес-процесса по созданию поддержи всей этой функциональности. А есть риск, что за этим просто перестанут следить.

Тем более, не надо делать одну базу для «первостепенных» свойств, другую - для «второстепенных», даже ещё столь разных архитектур, что поддержка каждой влечёт за собой необходимость создания и поддержки разного кода, увеличение документации и необходимость слежения за ней (документацию тоже надо обновлять). Делайте 1 базу как здесь  :).

Сущность описывается не целочисленным id, а строкой “id+тип”. От типа не отвязались, чтобы определять чьи же это свойства. Вдобавок там остается мусор из свойств удаленных объектов. Чистить пока некому.

Этим опять-таки должно заниматься ядро, а не человек, что оно - в нашем случае - и делает (это видно даже и в статье - в разделе «про код» посмотрите комментарии к реализациям функций Add и Delete)

Удаляя набор, ядро "подчищает" базу, удаляя из неё соответствующие значения Values, если они не встречались ни в каком другом наборе.

Поскольку в нашем случае Values вынесены в отдельную таблицу, для определения этого, достаточно просмотреть столбец из численных идентификаторов в таблице Sets, а не проводить сравнение строк (каковое сравнение строк и тормозит разные другие реализации EAV).

Что касается EAV по Сычёвски, то, видимо, понадобится генерация уникального id для таблицы наборов, в которой столбец Set_id будет внешним ключом. Не знаю, понадобится ли еще один столбец с уникальным id для таблицы наборов? По умолчанию, таковой мы везде вставляем. В случае шардинга таблицы Values, соответствующий столбец в таблице наборов перестанет быть внешним ключом. Так?

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

Шардинг таблицы Sets назван редким случаем потому, что эта таблица состоит из полей, в которых нет ничего, кроме Id. И поиск по этой таблице - самый быстрый из возможных. Только, когда проект становится действительно громадным, эту таблицу можно порезать.

В случае с шардингом таблицы Values (когда она очень длинная и строковая) соответствующее поле в таблице Sets действительно перестает быть внешним ключом, но все функции слежения за целостностью данных в этом случае мы поручаем ядру. Так, например, эту проверку делают функции Add и Delete.

Спасибо, 

2015-11-02 18:10:46
Александр Сычёв » Всеволод
Уважаемый Всеволод!

Часто заказчик хочет (хотя и не говорит этого в слух, потому что считает, что это не возможно или труднореализуемо) хранить историю изменения свойств. Т.е. при изменении свойства, не затирать предыдущее, а добавить еще одно значение и time stamp изменения. При этом актуальным будет только одно значение - последнее. А остальные нужны для особых случаев (отчетов, поисков и т.п.)

Способ первый:

Похожая задача возникает при синхронизации данных. Там для её решения используются два дополнительных поля:

1) Updated - хранит дату последнего изменения текущей стройки

2) Deleted - флаг, принимающий значения 0 или 1 в зависимости от того, была удалена на одном из клиентов строка (1) или нет (0).

Эти два поля появляются в каждой из таблиц (и Sets, и Values). Они используются для того, чтобы устройство, запрашивающее у сервера разницу со своим текущим состоянием базы данных, могло узнать:

1) Какие данные изменились со времени последней сверки.

2) Какие именно изменения произошли: какие строки добавить, а какие наоборот удалить.

Недостатком этого способа является тот факт, что таблица излишне разрастается, поскольку хранит неактуальные данные. Поэтому мы любим второй способ.

Способ второй: По факту удаления, соответствующие записи перемещаются в другие таблицы.

То есть, из Sets - в Deleted_Sets и из Values - в Deleted_Values. При этом, поле Deleted становится ненужным, поскольку актуальные данные все время хранятся в действующей базе, а удаленные - в "исторической", откуда их можно в любой момент восстановить.

В отличие от способа 1, здесь удалённые данные хранятся отдельно от актуальных и не влияют на скорость поиска. Поле Updated при этом остается.

Важно: Поле UpdatedDeleted - в случае 1) хранит информацию об отдельной записи в таблице, а не о наборе в целом. Оно не используется при работе непосредственно с данными и (в случае с синхронизацией) присутствует только в эталонной базе на сервере. На клиентских устройствах этого поля нет вовсе.

А еще лучше хранить и id пользователя, который изменил свойство (его ip, фото, отпечатки пальцев...) В структуре (1) мы просто добавим соответствующие столбцы. Реализуемо ли это в EAV по Сычевски?

Если Вы, тем самым, хотите узнать "Кто это сделал", можете добавить соответствующие свойства (пара "Name - Value") Вашему набору. Например, свойства "Дата правки" и "Кто правил". Или добавить свойство "История наборов" и (при желании) хранить всю историю в поле Value.

Так у вас всегда хранится информация о том, кто в последний раз правил каждый объект.

Спасибо,
2015-11-03 11:33:28
Всеволод » Всем

Здравствуйте, Коллеги! Спасибо за пояснения!

Все же, вопросы остаются:

1. Зачем, например, в функции Add обновлять значение, которое и так есть?

add (null, $login, "admin3") - сама создаст и выполнит:

SELECT VALUE_ID FROM VALUES_STRING WHERE VAL = 'admin3';
UPDATE OR INSERT INTO VALUES_STRING (VALUE_ID, VAL) VALUES (1, 'admin3') matching (VALUE_ID);
INSERT INTO SETS (SET_ID, NAME_ID, VALUE_ID) VALUES (1, 0, 1);


Я бы так сделал:  

-----------------

DECLARE @pID INT;
begin tran
SELECT @pID = VALUE_ID FROM VALUES_STRING WHERE VAL = 'admin3';
if @@rowcount = 0
                                begin
                                        INSERT INTO VALUES_STRING (VAL) VALUES ('admin3');
                                        SELECT @pID = @@IDENTITY;
                                end
commit tran;
INSERT INTO SETS (SET_ID, NAME_ID, VALUE_ID) VALUES (1, 0, @pID);


2. Непонятно как функция Add чистит базу. Вы пишете в разделе про быстродействие:

... функция add проверяет (поисковая операция), есть ли в таблице Values значение Value. Если нет, то создает (операция добавления).
... Также проводится проверка, есть ли другие наборы с таким же свойством (с тем же самым Name и тем же самым Value_id) - это быстрая поисковая операция. 
Если других наборов нет, удаляет значение Value из таблицы Values.


Но мы же это значение только что добавили?! А если оно было, то оно и нужно.

Другое дело, если функция add($SetId, $Name, $Value) не нашла ни одного набора и собирается сообщить об ошибке добавления. Тогда можно заодно проверить, а другие наборы есть с такими $Name и $Value и, если нет, значение удалить. Но как мы попадем на эту ветку алгоритма? (см. 2а)

2а. Будет ли идентификатор набора нужен за пределами базы? Я предполагаю, что программа-клиент будет запрашивать что вроде этого:

В набор, у которого свойство guid = '4cfc0d4f-77c5-42dd-9dc8-7f5146f34809', добавить свойство filename со значением 'myPhoto.jpg'.  А если набора с таким свойством (guid) нет, ничего не делать. И значения свойства filename не проверяются на актуальность.

2015-11-03 16:37:32
Александр Сычёв » Всеволод
Уважаемый Всеволод!

"Зачем, например, в функции Add обновлять значение, которое и так есть?"


Описанные запросы пишутся не человеком, а программой автоматически по параметрам, которые она получила на вход функции Add. Это позволяет с помощью простых обращений говорить программе, что мы хотим, не думая в принципе о том, какой, при этом, нужно составить запрос. Мы можем и языка SQL не знать - "робот знает" (хоть он и "тупой").

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

В последнем случае простой INSERT добавит в существующую таблицу ещё одну строку, а нам надо именно обновить существующую.

Да, в случаях, когда уникальный ключ есть, UPDATE может быть и лишним, но вреда от него нет, а "робота" не надо учить различать разные случаи.

SELECT VALUE_ID FROM VALUES_STRING WHERE VAL = 'admin3';
UPDATE OR INSERT INTO VALUES_STRING (VALUE_ID, VAL) VALUES (1, 'admin3') matching (VALUE_ID);
INSERT INTO SETS (SET_ID, NAME_ID, VALUE_ID) VALUES (1, 0, 1);

"Я бы сделал так...(SQL)"

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

"Непонятно как функция Add чистит базу."

Здесь имеются в виду следующие ситуации:

Ситуация 1: Если свойства нет в базе, добавить в соответствующую таблицу Values и создать (заменить) ссылку в поле Value_id таблицы Sets.

Ситуация 2: Если свойство уже есть в базе, создать (заменить) ссылку в поле Value_id таблицы Sets.

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

Если таковых не осталось, устаревшее значение удаляется из таблицы Values. Для проверки этого факта, нужно просмотреть столбец из численных идентификаторов (а не строк).

"Другое дело, если функция add($SetId, $Name, $Value) не нашла ни одного набора и собирается сообщить об ошибке добавления."

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

"Будет ли идентификатор набора нужен за пределами базы?"

Конечно. Он является тем самым полем, которое связывает несколько совершенно различных свойств в один набор. В статье описано, что, если мы, к примеру, вызовем функцию Get c параметрами: get (21, null, null); мы получим набор со всеми его свойствами (в виде массива). Если же мы вызовем функцию Get так: get (21, $email, null); мы получим конкретное значение свойства $email у набора с идентификатором 21.

"Набор, у которого свойство guid = '4cfc0d4f-77c5-42dd-9dc8-7f5146f34809', добавить свойство filename со значением 'myPhoto.jpg'.А если набора с таким свойством (guid) нет, ничего не делать. И значения свойства filename не проверяются на актуальность."

Вы можете поступить следующим образом
:

В Вашем справочнике должны быть к примеру такие записи:

$guid = 21;
$filename = 34;

Тогда в самой программе у вас будет примерно следующее: (Код прокомментирован для большего понимания):

$ids = get(null, $guid,'4cfc0d4f-77c5-42dd-9dc8-7f5146f34809');

$id
= $ids[0];
//Поскольку предполагается, что значения $guid всегда уникальны можно сразу работать с первым. (Если это не так, можно действовать соответственно)
 
if($id){
  $valid = checkActuality('myPhoto.jpg');
//функция, которая проверит является ли файл  'myPhoto.jpg' "Актуальным". Эту функцию должен описать программист.

  if($valid) add($id,$filename,'myPhoto.jpg');
}

Спасибо,
 
2015-11-04 15:46:19
Всеволод » Александр Сычёв

Здравствуйте, Александр! Примеры кода в Вашей статье - это просто иллюстрация или код, который можно читать и понимать? Извините, я его не понимаю.

2) Add ( $Set_id, $Name, $Value) // Добавляет набору (Set) с идентификатором id свойство Name со значением Value или обновляет его, если такое свойство уже есть.
 

add (null, $password,"123456")- сама создаст и выполнит:

Почему первый параметр null, а не конкретное значение? Можно было бы понимать, что нужно обновить свойства Name у ВСЕХ наборов, но тогда как различить этот случай с добавлением 1, когда просто добавляется новый набор?

Далее (допустим была опечатка, и вместо null стоит на самом деле 1):

1) SELECT VALUE_ID FROM VALUES_STRING WHERE VAL = '777';

2) UPDATE OR INSERT INTO VALUES_STRING (VALUE_ID, VAL) VALUES (12, '777') matching (VALUE_ID);

3) SELECT VALUE_ID FROM SETS WHERE SET_ID = 1 AND NAME_ID = 1;

4) SELECT SET_ID FROM SETS WHERE VALUE_ID = 2;

5) UPDATE OR INSERT INTO SETS (SET_ID, NAME_ID, VALUE_ID) VALUES (1, 1, 12) matching (SET_ID, NAME_ID);

Для чего нужны строки 3) и 4) ?

По строке 5): Если указанный набор не найден и SET_ID внешний ключ от автоинкрементного первичного ключа (есть таблица из одного столбца, которая раздает новые идентификаторы), то INSERT с заданным id не пройдет.

Вариант с зачисткой:

Возможна ситуация:

1)SELECT VALUE_ID FROM VALUES_STRING WHERE VAL = 'admin6';
2)UPDATE OR INSERT INTO VALUES_STRING (VALUE_ID, VAL) VALUES (11, 'admin6') matching (VALUE_ID);
3)SELECT VALUE_ID FROM SETS WHERE SET_ID = 1 AND NAME_ID = 0;
4)SELECT SET_ID FROM SETS WHERE VALUE_ID = 1;
5)DELETE FROM VALUES_STRING WHERE VALUE_ID = 1;
6)UPDATE OR INSERT INTO SETS (SET_ID, NAME_ID, VALUE_ID) VALUES (1, 0, 11) matching (SET_ID, NAME_ID);

3) берем предыдущее значение свойства заданного набора

4)берем множество наборов с этим же значением

где проверка, что можно удалять?

5) удаляем устаревшее значение

2015-11-04 18:09:31
Александр Сычёв » Всеволод

Добрый день.

add (null, $password,"123456")- сама создаст и выполнит:
Почему первый параметр null, а не конкретное значение?

Здесь создается новый набор, которому добавляется свойство $password со значением "123456".

Когда мы передаем функции Add null вместо конкретного SetId , она создает новый.

Об этом сказано в статье:

  • Add (null, $Name, $Value) // Добавляет новый набор (Set) со свойством Name и значением Value в базу данных 

Так что, никакой опечатки. 

Для чего нужны строки 3) и 4) ?

Эти строки получают информацию для проверки: можно или нельзя значение удалить.
Sql запросы генерируются функциями ядра. Их результаты обрабатываются функциями на языке php, и, в зависимости от них, принимается решение об удалении записи из таблицы Values.

Где проверка, что можно удалять?

Сама проверка (функция ядра) выглядит так:

$sets_with_cv =
get_brick($field_setId,$table_sets,$field_valueId,$current_value_id);
if(count($sets_with_cv)<2){
delete_brick($table,array($field_valueId),array($current_value_id));  
}

В статье не опубликован код самого ядра, цитата:

"(...) С учётом описанной выше идеализации кода, реализуем "общение" программы с базой данных через универсальный интерфейс ("ядро") - так, чтобы программа (как и положено по всем канонам) не знала ничего об устройстве базы данных, а базе данных было всё равно, какая там программа. Здесь назовём лишь ключевые "ядерные функции" (статья есть статья), а если будут многочисленные запросы на публикацию ядра целиком, то опубликуем отдельно".

Но если кратко пояснить, здесь происходит:

get_brick – генерирует запрос на получение записи, в данном случае запрос:

  SELECT SET_ID FROM SETS WHERE VALUE_ID = 1;

Находит, наборы, которые содержат ссылку на запись в таблице Values с идентификатором 1.

Если таковых 1 или меньше запускаем delete_brick который создаст запрос:

  DELETE FROM VALUES_STRING WHERE VALUE_ID = 1;

который удалит более ненужное значение. 

Это и является ответом на Ваш следующий вопрос.

C Уважением, 

2015-11-07 23:50:29
Всеволод » Александр Сычёв
Здравствуйте, Александр!

Спасибо за пояснения. Ядро, видимо, не такое простое как структура данных. Из примеров я понял, что таблица значений зашардирована по типу. Т.е. есть 2 таблицы значений для строк (VALUES_STRING) и для чисел (VALUES_INT). В этом случае только из таблицы наборов невозможно понять, на какое значение ссылается набор. Это знает только программа-клиент ядра. Само ядро не знает. Значит, если set1 ссылается на числовое значение 7770777 с id = 1, a set2 на строковое 'elephant' с id = 1, то при устаревании значения 'elephant' оно не удалится, потому что есть набор set1 который имеет такую же ссылку. А если устарели оба значения, то и удалять их нужно сразу из двух таблиц VALUES_STRING и VALUES_INT.

По Вашему опыту, не возникает ли каких-либо трудноуловимых ошибок из-за того, что при шардировании "перемешиваются" идентификаторы значений в таблице наборов?

Спасибо,
2015-11-09 18:07:27
Александр Сычёв » Всеволод
Уважаемый Всеволод!

Ядро, видимо, не такое простое как структура данных. 

Всего 8 функций, работающих с данными (в текущей версии) и 2 функции: db_connect и db_disconnect . Общий объём кода ядра 10 килобайт :)

Значит, если set1 ссылается на числовое значение 7770777 с id = 1, a set2 на строковое 'elephant' с id = 1, то при устаревании значения 'elephant' оно не удалится, потому что есть набор set1 который имеет такую же ссылку.

Для каждого значения Value автоматически определяется свой уникальный идентификатор, независимо от того, в какой из таблиц Values это значение хранится. Добавили это пояснение в статью, спасибо.

В этом случае только из таблицы наборов невозможно понять, на какое значение ссылается набор

Это можно определить по имени свойства. В статье описано, что распределение по различным таблицам происходит на основе имени свойства. Так, например, если нам нужно определить значение свойства $count, ядро точно знает, что искать его нужно в таблице Values_int. Можно реализовать и иные принципы распределения, например, по размеру строки или др.

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

По Вашему опыту, не возникает ли каких-либо трудноуловимых ошибок из-за того, что при шардировании "перемешиваются" идентификаторы значений в таблице наборов?

Как видно, перемешивания идентификаторов не происходит.

Спасибо за Ваши вопросы,
2015-11-11 23:29:55
Всеволод » Александр Сычёв

Здравствуйте, Александр!

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

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

Напрашивается простое решение: положить коды имен и диспетчер в ту же базу в отдельные таблицы. Но это шаг назад к усложнению базы.

2-й вопрос: почему Вы не используете хранимые процедуры? Они избавят от генерации sql кода, его передачи и компиляции. А ядро будет просто выбирать нужную процедуру и отправлять параметры. Наверное, не используете, потому что диспетчер в ядре и поэтому нужно запросы ядром создавать. Если же диспетчер будет в базе, то это даст и надежность хранения и возможность использовать хранимые процедуры.  

Я думаю, Вы хотите иметь возможность переписывать диспетчер в ядре. Но если база уже наполнена, этого сделать нельзя по смыслу. Диспетчер и база станут нераздельны. И новому диспетчеру понадобится новая база. Так может им и быть вместе?

С уважением,

2015-11-11 23:41:18
Всеволод » Александр Сычёв
Еще момент: если уже есть 1000 имен свойств в текстовом файле и Вам говорят завести еще 10 новых, то нужно открыть в редакторе длинный список, придумать 10 новых уникальных имен (!!!) и аккуратно вписать, ничего не испортив. А потом еще в диспетчер добавить, который тоже будет иметь много строк. Чтобы исключить ошибки, понадобятся специальные сервисы для обслуживания длинных списков, которые уже есть в СУБД.
2015-11-13 19:59:27
Александр Сычёв » Всеволод

Уважаемый Всеволод!

Если кто-то по каким-либо причинам поменяет тип у свойства, т.е. перепишет диспетчер или потеряет его (это же обычный текстовый файл), то вся база "нажитая непосильным трудом" превратится в хаос.

Да, нет. Список имен (не типов) имеет достаточно простую структуру, и работа с ним вполне автоматизирована (в т.ч., "защита от дурака есть", да и бэкапится). Похожую систему использует операционная система "Андроид". Там каждое приложение имеет такой справочник, который называется R.java. В нем хранится соответствие читаемых для человека идентификаторов ресурсов (текстов, изображений и т.д.) и чисел, которые использует сама ОС для работы с приложением. Помех работе системы наличие такого файла не создает. Хранение и сопровождение его требует гораздо меньших затрат нежели хранение любого другого скрипта системы.

Я думаю, Вы хотите иметь возможность переписывать диспетчер в ядре 

Конечно. Ведь по мере развития любого проекта добавляется новая функциональность, новые данные и т.д. И, по условию задачи, мы не знаем какие новые потребности возникнут у Клиента в будущем (да, и он не знает). Поэтому должна быть возможность при минимуме усилий добавить в базу новую структуру, не добавляя её (если выражаться по тризовски). В нашем случае, это будет лишь тупая таблица Values. Добавить её несложно.

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

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

Если же у Вас вдруг поменялась сама схема шардинга таблиц Values, то при такой простой структуре написать транспорт данных для полностью нового Диспетчера не составляет труда. И процесс транспорта займёт гораздо меньшее время, чем в случае с "обычной структурой", даже вполне себе нормализованной.

Почему Вы не используете хранимые процедуры?

Тогда при работе с разными СУБД нам придется учитывать их специфику и "подгонять" процедуры под них. У Клиентов могут работать: а) разные реляционные БД (SQL-сервер, Firebird, Sqlite, Oracle, PostgreSQL, MySQL и др.); б) NoSQL , файловые БД - тоже разные. Более того, вдруг проект у Клиента "переедет" на другую СУБД, а диалекты процедурных языков разных СУБД могут быть несовместимы. См., например.

Даже, если и насобирать заранее "заготовок" под каждую, что мы будем делать, если понадобится обновить Составитель Запросов или Диспетчер? А зачем нам это всё (при "сычёвской-то структуре", которой вообще всё равно какая там база). Если можно об этом не думать, то лучше и не думать.

С другой стороны, и сам проект (Owl's) тоже будет развиваться. Получается, если нужно будет сделать обновление у Клиента, то надо либо писать скрипты, либо связываться с Клиентом и объяснять его админу, что и как сделать. Скорее всего, никто ничего делать не будет.

Если же Составитель запросов и Диспетчер изолированы от базы, то они будут работать независимо от её типа. А обновление ядра будет заключаться в замене файла на новую версию.

«Еще момент: если уже есть 1000 имен свойств в текстовом файле и Вам говорят завести еще 10 новых, то нужно открыть в редакторе длинный список, придумать 10 новых уникальных имен (!!!) и аккуратно вписать, ничего не испортив. А потом еще в диспетчер добавить, который тоже будет иметь много строк. Чтобы исключить ошибки, понадобятся специальные сервисы для обслуживания длинных списков, которые уже есть в СУБД.»

Давайте сравним "обычную" структуру данных с рассмотренной в статье и посмотрим настолько ли это большая проблема если действовать “as Owls”.

А. Предположим, имеется "обычная" вполне нормализованная структура.

У нас уже есть 1000 различных свойств разбросанных по разным таблицам (!!!). Нам нужно добавить 10 новых.

1. Если повторение названий столбцов в разных таблицах не критично, то проблема с выбором имени стоит не так остро.

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

2. Далее надо определить в какую конкретно таблицу добавить новый столбец (при тысяче свойств их возможно будет очень много).

3. После чего изменить структуру таблицы, добавив новый столбец (а, возможно, новую таблицу).

4. При этом, будем помнить о том, что, возможно, потребуется добавить интерфейсные таблицы для работы с новым свойством.

и 

5. Повторить 10 раз.
 

B. «As Owls»

У нас уже есть тысяча различных свойств которые описаны в одном подключаемом файле. Нам нужно добавить 10 новых.

1. Регламент всегда требует уникальности имён свойств. Автоматизировать проверку занято ли читаемое для человека имя свойства (например, само слово «email» в строке $email = 8) достаточно легко (если не хочется использовать сервис Ctrl+F который уже встроен в текстовый редактор).

Уникальность чисел, соответствующих читаемому для человека названию (например, само число 8 в строке $email = 8), можно обеспечить простой нумерацией новых свойств подряд. То есть, новому свойству всегда ставится в соответствие число на единицу больше последнего.

2. Повторить 10 раз.

Как видно, сложностей, при решении такой задачи не возникает. Более того, в общем случае, для нормализованной базы данных, её решение будет намного сложнее (уже и при 60 свойствах это может быть крайне неприятно).

Если же действовать «по Сычёвски», то, и при 60-ти, и при 1000 свойств, задача совершенно не сложная. И, как уже было описано ранее, описанный процесс можно полностью автоматизировать.

Спасибо, 

2015-11-14 22:57:59
Всеволод » Александр Сычёв

Здравствуйте, Александр!

Если нам потребуется добавить в базу особый тип данных

Да. Например, DateTime. Его можно хранить и как int, но у этого типа есть особенность: мало повторов, много уникальных значений; каждую секунду, а то и чаще, новое значение появляется. Поэтому есть соблазн DateTime хранить сразу в таблице наборов в столбце VALUE_ID как значение int, а не ссылку. Но это нарушает единство модели. Там ссылка, а здесь оно выступает как значение. И еще, для типа DateTime, как правило, существует много сервисов, которые хочется иметь, а конвертировать туда-сюда не хочется. Вы как с датами обходитесь?

Тогда (в случае с хранимыми процедурами) при работе с разными СУБД нам придется учитывать их специфику и "подгонять" процедуры под них

Да. Но и сам генератор sql-запросов тоже должен учитывать специфику разных СУБД. В MS SQL, например, нет конструкции

UPDATE OR INSERT INTO... , а реализация с помощью обычных UPDATE и INSERT вырастает в длинную строку запросов.

С уважением,

2015-11-15 17:20:48
Александр Сычёв » Всеволод
Уважаемый Всеволод!

Поэтому есть соблазн DateTime хранить сразу в таблице наборов в столбце VALUE_ID как значение int, а не ссылку. Но это нарушает единство модели.

Мне кажется, что код тогда сильно усложнится, например, придётся встраивать проверку в генератор запросов и др. 

Вы как с датами обходитесь?

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

Но и сам генератор sql-запросов тоже должен учитывать специфику разных СУБД. В MS SQL, например, нет конструкции UPDATE OR INSERT INTO... , а реализация с помощью обычных UPDATE и INSERT вырастает в длинную строку запросов.

Ну, не такую уж и длинную, если подумать :). В любом случае, лучше решать задачи связанные со "спецификами" в одном месте, а не распространять их по Пользователям.

------------------------

Если же говорить совсем уж стратегически, то, при такой структуре, не нужно СУБД и не нужен вообще SQL. С ним приходится считаться, поскольку реляционные базы очень распространены и SQL - соответственно. А, с технической точки зрения, когда у Вас на всё про всё 5 полей, из которых 4 - это ID,  зачем нужно "промежуточное звено" в виде SQL.

Спасибо,


2015-11-18 17:14:54
Всеволод » Александр Сычёв

Здравствуйте, Александр!

Я посчитал количество запросов. Оно ужасно. Или это так кажется?
Поясню.
Допустим в таблице (по структуре 1) лежат документы, имеющие 10 свойств, и требуется получить все документы, где свойство act = 777.
Обойдемся ОДНИМ запросом:

SELECT * FROM docs WHERE act = 777

И получим, допустим, 20 документов, которые и отобразим.

Теперь рассмотрим Сычевскую структуру.

set_ids = get(null, act, 777);//получим 20 чисел

для каждого числа сделаем:

get(set_id, null, null);//получим 10 пар чисел, это имена и id свойств

и еще нужно взять сами значения свойств:

SELECT VAL FROM нужная_таблица WHERE VALUE_ID = n;

Итого запросов: 10 * 20 + 20 + 1 = 221 селект. Вместо одного.

Вопрос: Вы как-то боретесь с ростом запросов? Начальство, да и клиент, если ему рассказать, не  пропустит такую структуру в разработку.

Думают так: пусть лучше программист один раз перепишет (для того его и держим), и прорамма будет потом нормально работать. И не надо будет переживать,
что сервер не выдержит, и надо будет объяснять клиенту, что нужно покупать новый.

Еще раз напишу, что на мой взгляд сложность никуда не делась. Она приобрела другую форму.

С уважением,

2015-11-18 18:27:21
Александр Сычёв » Всеволод
Добрый вечер!

Я посчитал количество запросов. Оно ужасно. Или это так кажется?... = 221 селект.

Не 221, а всего 2 (два). (Чуть позже напишу код - сейчас еду в автобусе между городами).

Думают так: пусть лучше программист один раз перепишет (для того его и держим), и программа будет потом нормально работать.
 

Пишет их не человек, а ядро. Программист должен писать так, чтобы работал компьютер, а не программист вместо компьютера.

C Уважением,
2015-11-19 17:00:05
Александр Сычёв » Всеволод
Добрый день!

Не 221, а всего 2 (два)

Прошу прощения, я ошибся. Не 2 (два), а 4 (четыре). Но это все равно не 221 и не сильно меняет дело.
 
Рассмотрим подробно:
 
1. Сначала ядро должно определить какой идентификатор Value_id соответствует значению 777.
 
SELECT VALUE_ID FROM VALUES_INT WHERE VAL = 777;
 
2. Далее ядро находит наборы, имеющее заданное свойство с заданным значением:
 
SELECT SET_ID FROM SETS WHERE NAME_ID = 5 AND VALUE_ID = 44;
 
3. Теперь надо найти значения всех десяти интересных нам свойств у полученных наборов. Для начало надо определить: какие идентификаторы Value_id соответствуют интересующим нас значениям.
 
Ядро создаст запрос, используя оператор IN:
 
SELECT SET_ID, NAME_ID, VALUE_ID FROM SETS WHERE NAME_ID IN (2,3,4,5,6,7,8,9,10,11) AND SET_ID IN (21,22,23,24,25,26,27,28,29,30);
 
Это уже 1 запрос, а не 200.
 
4. Теперь установим каким значениям VAL соответствуют полученные Value_id.
 
После обработки полученных данных сгенерируется запрос типа этого:
 
SELECT VAL FROM VALUES_STRING WHERE VALUE_ID IN (2,3,4,5,44,51);
 
Это 1 запрос, а не 6.
 
Итого имеем всего 4 запроса.
 
C Уважением, 
 
P.S. Соглашусь с тем, что из статьи это неочевидно. Там приведен код развернутый, написанный для более простого понимания.

P.P.S. Если Ваше имя (да, и компания, где Вы работаете) несекретно, сообщите, чтобы мы могли поблагодарить Вас в конце статьи за это обсуждение. Оно очень ценное. И, разумеется, ждём новых вопросов.

Спасибо,
 
2015-11-21 17:44:40
Всеволод » Александр Сычёв

Здравствуйте, Александр!

Спасибо за пояснения. В последнем запросе, наверное, имелось ввиду:

SELECT VALUE_ID, VAL FROM VALUES_STRING WHERE VALUE_ID IN (2,3,4,5,44,51)

иначе не разберемся со значениями; к чему они относятся. И таких запросов будет столько, сколько понадобится таблиц со значениями:

SELECT VALUE_ID, VAL FROM VALUES_DATE_TIME WHERE VALUE_ID IN (2002,2003,2004,2005,2044,2051) и т.д.

Еще момент:

Функцию Add ( $Set_id, $Name, $Value) можно понимать двояко:

1. Добавляет набору (Set) с идентификатором id свойство Name со значением Value или обновляет его, если такое свойство уже есть. Как указано в статье.

2. Даже если такое свойство уже есть, добавим второй экземпляр (см. обсуждение выше):

Set_id Name_id Value_id
2  2  2  (Весь рок-н-ролл)
2  2  5  (Все записи кантри)

где константы ("Names"): $Музыкальные жанры = 2


В "печально знаменитой" структуре 1 в этой ситуации было бы так:

Музыкальные жанры
2, 5

или

Музыкальные жанры
рок-н-ролл, кантри


Не знаю, нужно ли исключать полные дубли (2 2 2) и (2 2 2)? Возможно, и они в ряде ситуаций понадобятся. Можно же записать:

Музыкальные жанры
рок-н-ролл, рок-н-ролл

Я думаю, в связи с вышесказанным будет актуальной функция:


Update($Set_id, $Name, $Value), которая обновляет свойство или добавляет, если его нет.
А функция Add ($Set_id, $Name, $Value) - всегда добавляет, даже если уже есть. Два будет; значит, так надо.

Про имя и т.п. написал в почту.

С уважением,

2015-11-22 10:43:25
Александр Сычёв » Всеволод
Добрый день!

В последнем запросе, наверное, имелось ввиду:

SELECT VALUE_IDVAL FROM VALUES_STRING WHERE VALUE_ID IN (2,3,4,5,44,51)

Конечно, именно это. Спасибо.

Я думаю, будет актуальной функция: Update($Set_id, $Name, $Value), которая обновляет свойство или добавляет, если его нет.

Можно подумать и над организацией данных. Например, сделать надсистемный переход и создать свойство, которое называется "Подборка жанров" и поступить так (иные варианты тоже возможны):

Sets

Set_idName_idValue_id___________
   1     4    4 Все записи Стинга
   2     2    5 Все записи кантри
   3     1    1 Все рыжие исполнители
   4     3    7  Кантри, рок-н-ролл
  ...    ...   ...  


Values

Value_IdValues
1 Рыжие
2 Рок-н-ролл
3 Блондины
4 Стинг
5 Кантри
6 Брюнеты
7 Кантри, рок-н-ролл
8 Кантри, рок-н-ролл, блюз
  ... и т.д. ...

Константы ("Names") в подключаемом файлe:

$Цвет волос автора = 1 
$Музыкальные жанры = 2 
$Подборка жанров = 3
$Исполнители = 4
$.......=........

Можно, конечно, рассмотреть вопрос и в общем виде: если, ситуация (по условию задачи) требует именно наличия функции добавления второго значения для того же имени свойства, то создавать отдельно функцию Update будет, всё же, не вполне рационально. Ведь её код будет фактически повторять код функции Add, за исключением участка где определяется Id, для которого производится добавление и участка и где производится удаление устаревших данных.

В таком модельном случае, лучше, видимо, добавить к функции Add дополнительный булевский параметр, который будет выбирать: добавить ли принудительно второе значение или обновить текущее.

Например: Add($Set_id, $Name, $Value, $Append=false), где добавление второго значения происходит только, когда $Append==true. При этом, Add($Set_id, $Name, $Value) будет работать как раньше.

Такое решение также подразумевает, необходимость и соответствующей функции Delete($Set_id, $Name, $Value), удаляющей запись о свойстве с конкретным значением (ведь теперь у одного свойства может быть сколько угодно значений, и мы должны знать, какое конкретно удалить).

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

C Уважением,

2015-11-22 15:54:33
Всеволод » Александр Сычёв

Здравствуйте, Александр!

Похоже, наметились такие тенденции:

1. Если значений свойства м.б. несколько, перечислить их через запятую в одной строке. Это позволит не изменять структуру таблиц и функции. Но это хорошо, если значение нужно только для вывода на экран. Мы так делали, пользователь смотрел на данные, а потом захотел их фильтровать. Когда понадобится анализ, например, фильтрация, без id не обойдемся.

Пример1: 'рок, рок Live' Нужно вывести только рок. Поиск по подстроке не сработает.
Пример2: есть значения 'Классическая', 'Опера'. При фильтрации по 'Классическая' значение 'Опера' тоже должно быть.

Есть таблица о вхождении значения 'Опера' в  'Классическая', а там id. Значит нужно парсить строку, получать id, возносить молитвы, чтобы не оказалось одинаковых строк с разными id (за 2 года могли навводить что угодно), объяснять руководству почему не передали id сразу, ведь это очевидно (теперь очевидно; раньше нет).

Хорошо бы иметь средство простого (мгновенного) перехода от перечисления через запятую в виде строки к массиву id на
всех уровнях программы. Сейчас с этим борются нормализацией, чтобы везде были только id.

2. В долгоживущих программах у функций отрастает последний параметр: bool trigger. В одном случае так, в другом иначе. Не добавлять ли его сразу при разработке? wsdl-сервис генерит 2 варианта одной функции: ту, которую программист написал, и вторую такую же + хвост (object userParam), где object - ссылка на что угодно. Если в хвост передать массив object[], то покроем все возможные в будущем изменения. Но программист будет вынужден написать разбор 'хвоста'. Передан ли массив? Какова его длина? Соответствуют ли элементы массива тому, что ожидалось? Если нет, сообщить об ошибке (по каждому варианту). Код станет длинным, разветвится за счет сервиса обслуживания самой функции, а не полезной работы.

Что лучше: иметь одну, но сложную функцию или несколько, но простых?

Еще момент про количество вызовов: 1 или 4. Если вызовы асинхронны, т.е. ответ приходит как событие, то придется вместо одного обработчика события написать 4  и соотнести их между собой; это же одна операция. А если цикл или два цикла

Сложность программы сильно возрастет. Кто ловил неконтролируемо размножившиеся обработчики и перепутавшиеся события, тот поймет. Для сложных случаев нам пришлось написать свою собственную систему генерации событий, чтобы полностью её контролировать в дополнение к стандартной. Для нас, если в одной операции более двух обработчиков событий, это уже сигнал тревоги: потенциальные глюки.

С уважением,

2015-11-25 18:46:03
Александр Сычёв » Всеволод
Уважаемый Всеволод!

1. Если значений свойства м.б. несколько, перечислить их через запятую в одной строке (...) Сейчас с этим борются нормализацией, чтобы везде были только id.

В этом случае можно хранить подобное поле например в виде свойства $genre_list со значением «;4;5;» где 4 и 5 идентификаторы наборов, каждый из которых представляет из себя отдельный жанр.

Подробнее:

$genre_name = 9;
$genre_list= 11;


set_id

name_id

value_id

______________

4

9

32

Rock

5

9

36

Folk

7

11

54

;4;5;



value_id

val

32

Rock

36

Folk

54

;4;5;


Программа считает хранящиеся в этом поле идентификаторы, а затем по ним мы сможем обратиться к набору с нужным id.

При таком подходе Вы сможете отфильтровывать жанры, причем «Rock» не налетит на «Live Rock».

Также, заведя отдельные наборы для каждого жанра, мы сможем добавить им самые разные свойства, например, свойство $type (или какое угодно другое) в значении которого будет находиться «Classical» или  «Modern» (или что угодно другое). Это позволит Вам провести отбор жанров по самым разным свойствам жанра, каким бы разборчивым в музыке не был Пользователь.

И ненужно «таблиц о вхождении значения 'Опера' в  'Классическая' » (и тем более ненужно "возносить молитвы" :)).

Что лучше: иметь одну, но сложную функцию или несколько, но простых?

Лучше сделать несколько простых функций, которые, уже при необходимости, на месте соберутся в более сложную конструкцию.

Как правило, если мы делаем одну большую «суперумную» функцию, то рано или поздно код её сильно разрастется, поскольку всегда найдется новая задача, с которой она должна справиться.

Именно поэтому и становится актуальным массив object[], ведь если каждый раз добавлять новый параметр в функцию, то каждый раз придется обновлять её вхождение во всех скриптах.

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

Если мы имеем несколько простых функций-кирпичиков, из которых уже на месте собираем нужную нам программу, то, при появлении новой задачи, которую они решить не могут, мы просто напишем еще одну такую же маленькую (Тупую) функцию, которую затем и будем использовать в сочетании с другими.

В случае необходимости, просмотреть и разобраться в том, как работают две или три короткие и «тупые» функции намного проще чем разбираться в том как работает весь огромный «гений». (А ведь это придется делать каждый раз, когда мы захотим сделать его «еще умнее». В тоже время, нам абсолютно все равно, как работает большая часть наших «кирпичиков», если мы захотим добавить к ним еще один).

Еще момент про количество вызовов: 1 или 4. Если вызовы асинхронны (...)

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

Спасибо,

P.S. Я не получил Вашего письма, продублируйте, пожалуйста.

2015-12-01 00:35:04
Всеволод » Александр Сычёв
Здравствуйте,Александр!

Асинхронные вызовы понадобятся, когда код запрашивает удаленную базу.

Рассмотрим простую задачу: отобразить документы за заданный пользователем период. В структуре будет 1 вызов.

Например, такой:

SELECT*FROM docs WHERE date not between @pFrom and @pTo,

Который запакуется в асинхронный метод:

DocData[] GetDocsAsync(DateTime from, DateTime to, bool between_or_not_between)

Допустим, на клиенте будет несколько диапазонов, которые обработаются так:
public void ShowDocs()

//--
core.GetDocsCompleted+= EventHandler<GetDocsCompletedEventArgs>(core_GetDocsCompleted);
//нужен обработчик, который примет ответ из удаленной базы, когда он вернется
//нужно уследить, чтобы объявление обработчика не попало внутрь цикла, иначе они размножатся

for//(цикл по всем диапазонам)
{
core.GetDocsAsync(from, to, between_or_not_between); //собственно вызов
}
//--
void core_GetDocsCompleted(object sender, GetDocsCompletedEventArgs e)
{

//--
Show(e.Result,table);

//вe.Resultпришли наши данные, которые и покажем; данные появляются порциями по мере вызова обработчика от каждого диапазона

//--

}

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

Наверное, в Сычевской структуре несколько запросов нужно запаковать в один асинхронный метод, который будет сразу возвращать конечные данные. Что-то вроде этого:

SNV[]GetNameValByProperty(intSet_id,int Name,object Value);

Где:

public class SNV

{

public int Set_id;

public int Name;

public object Value;

Так GetNameValByProperty(null, act, 777) вернет все наборы c именами и значениями,у которых свойство act=777.

В статье речь шла только о проверке равенства параметров. Что делать с остальными операциями, особенно between и not between,где два операнда?

Можно было бы в методе GetNameValByProperty(int Set_id,int Name, object Value) первым параметром передавать код операции =,!=,>,<,>=,<=

Но у between два операнда. (Разбить на 2 части не получится. Все равно придется передавать второй параметр: and или or). Опять хвост вырос:

GetNameValByProperty(between, date, ’2015-01-01’ ’2016-01-01’);

//все наборы c именами и значениями, у которых свойство date между ‘2015-01-01’и‘2016-01-01’

}

С уважением,
2015-12-02 18:43:39
Александр Сычёв » Всеволод
Добрый вечер!

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

Да, именно так. Например, remote_get ($set_ids,$names,$values), которая передаст запросы пачкой.

В статье речь шла только о проверке равенства параметров. Что делать с остальными операциями, особенно between и not between,где два операнда (особенно)?

Мы уже передавали функции get массивы имен и значений произвольной длинны для сравнения (когда останавливались на IN запросе), так что нам не составит труда передать массив длиной два.

Что делать с остальными операциями?

То есть, каким образом мы в принципе можем уведомить нашу функцию о том, что делать с параметрами, если данные сами по себе не содержат об этом никакой информации. (Смешивать данные и действия над ними в одной переменной не самая лучшая идея). Хороший вопрос.

Мысль-1. Добавим функции get дополнительный параметр $conditions, который хранит в себе последовательность операций над параметрами вида array(>,<=,~), значок ~ используется для запроса типа like. Если в массив передается всего один элемент, то соответствующая операция для всех значений $values.

Значение по умолчанию «=» Т.е.,  get ($set_ids,$names,$values,$conditions = “=”);

Примечание 1. Допускается ввод одного строкового параметра, поскольку мы хотим добиться и некоторого удобства для программиста. Если нам нужно какое-либо нестандартное сравнение, мы передаем его в параметре conditions. (Требуется это на самом деле не так часто).

Примечание 2Between – частный случай операторов >= и <=, который просто описывается по другому.

Пример:

Запросы:

  • Select Value_id from Values_Date VAL Between '10/15/2009' and '10/18/2009';
  • Select Value_id from Values_Date VAL >='10/15/2009' and VAL <='10/18/2009';
Выдадут совершенно одинаковые результаты. Так что, для единообразия лучше использовать операторы <,>,<=,>=.

К тому же, ситуацию, где нам, к примеру, не нужно включать одну из границ, Between не обработает.

Мысль-2. Если уж добавлять параметр в идеальную функцию, то надо сокращать другие функции, иначе будет не по тризовски. Например, вместо 3-х функций (get, add, delete) заведём в ядре вот такой "веполь":

any ($action, $set_ids,$names,$values, $conditions); 

$action – принимает три значения:
  • «=» - для получения данных;
  • «+» - для добавления данных; 
  • «-» - для удаления данных.
В принципе можно сделать и совсем формально any ($action, $data, $conditions) , чтобы был совсем уж "веполь": («действие», «данные», «условия/состояния»), но это уже как удобнее.

С Уважением, 
2016-02-04 16:39:22
Редакция » Всем
Уважаемые Коллеги!

Ещё одно обсуждение, посвящённое этой структуре проходит по-соcедству.

Спасибо, 
2016-03-11 12:27:04
Всеволод » Всем

Здравствуйте, Коллеги!

Не сталкивались ли Вы с ситуацией, когда ядро, реализующее SQL-запросы к Сычевской базе данных, и нормально работающее в обычных проектах, приводит к дедлокам (deadlock) в проектах нагруженных? Иначе говоря, прибежало множество пользователей, стало что-то активно делать (что именно, сказать затруднительно, т.к. запросы единообразны), и база (MS SQL) стала отклонять часть запросов с ошибкой: "Transaction (Process ID 60) was deadlocked on lock | communication buffer resources with another process and has been chosen as the deadlock victim."  

У нас это впервые; Структуры 1 с этим справляются.

Объединяете ли Вы серию простых SQL-запросов в одну транзакцию? Например, так:

begin tran

... наши запросы, реализующие, например, обновление свойства...

end commit tran

Мы объединяем.

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

С уважением,

2016-03-11 18:38:15
Александр Сычёв » Всеволод
Уважаемый Всеволод!

Мы не объединяем запросы в транзакции. С проблемой deadlock-ов пока не сталкивались. При нашем ядре, при описанной структуре, такое теоретически возможно в следующих ситуациях:

1. Происходят конкурирующие изменения в таблице SETS. Но это возможно только, если несколько человек единовременно правят один и тот же набор. Например, одна и та же анкета правится с двух компьютеров. С подобного рода конфликтом следует разбираться на уровне интерфейса и уже там решать, позволять ли единовременное редактирование несколькими людьми. 

2. Происходят конкурирующие изменения в одной из таблиц Values. Тут рассмотрим подробнее:
     
Когда может происходить изменение таблицы Values?

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

2.2. Когда из нее удаляется более не использующееся значение. Тут снова рассмотрим подробнее. Когда из таблицы Values удаляется значение?

2.3. Когда удаляется последний набор, обращавшийся к этому значению.

2.4. Когда последний набор, использовавший это значение, стал использовать другое значение.

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

Как мы уже заметили выше, с такими ситуациями нужно бороться на уровне интерфейса.

Не могли бы Вы привести более конкретную ситуацию когда возникает подобная проблема при использовании транзакций?

Спасибо,
 
2016-03-12 01:11:11
Всеволод » Александр Сычёв

Здравствуйте, Александр!

Мы используем Сычевскую структуру в экспериментальном режиме для формирования отчетов о времени работы над документами. Документов 2 типа: "статья" и "часть статьи" или "блок". Монопольный доступ существует только на блок. Одну статью могут править несколько пользователей: корректор - текст, художник - картинку, креативщик - заголовок. Мультидоступ к статье - одна из обязательных фич. В реальной работе мультидоступ практикуют редко. Однако, не удалось получить из лога очевидных свидетельств о конкретных причинах дедлока. Может быть, данные пользователи его практикуют часто. А возможны и другие причины.

Когда документ открывается, в базу пишется набор со свойствами этого документа, а также время открытия и время закрытия; последнее пока не известно и берется равным 01.01.1900г. Эта дата является маркером случая, когда документ не был закрыт корректно, а повис/слетел/что-то еще.
Реальное время закрытия добавляется в набор позже, при корректном закрытии документа. По нашему опыту, дедлоки случаются чаще всего при обработке дат. Видимо, даты обрабатываются медленно.

Блок может занимать пользователь или программная функция. В последнем случае время обработки документа (блока) менее секунды. Такие наборы не интересны и удаляются при закрытии документа. Теоретически удаление набора может начаться до того как предыдущая транзакция добавления данного набора завершилась. Это вторая возможная причина. А есть записи о дедлоках, которые под упомянутые две причины не подходят, но там тоже участвуют даты.

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

Записей в таблице наборов: 1 767 635.
Количество значений: целых чисел - 22 635, строк - 6 024, дат - 328 542

Пример отклоненного запроса по добавлению/изменению значения в наборе:

DECLARE @pID INT;
SELECT @pID = VALUE_ID FROM values_dt WHERE VAL = @pVal; -- есть ли такое значение
if @@rowcount = 0
begin
 INSERT INTO val_id DEFAULT VALUES; -- сгенерить id значения
 SELECT @pID = @@IDENTITY;
 INSERT INTO values_dt (Value_id, VAL) VALUES (@pID, @pVal); -- новая дата
end

DECLARE @pPrevVal INT;
DECLARE @pCnt INT;

begin tran -- начало танзакции

SELECT @pPrevVal = VALUE_ID FROM sets WHERE set_id = @pSet and Name_id = @pName;
SELECT @pCnt = COUNT(Set_id) FROM sets WHERE Value_id = @pPrevVal;
if @pCnt = 1
 begin DELETE FROM values_dt WHERE VALUE_ID = @pPrevVal;
 end
 
UPDATE sets SET VALUE_ID = @pID WHERE Set_id = @pSet and Name_id = @pName;
if @@rowcount = 0

begin INSERT INTO sets (SET_ID, NAME_ID, VALUE_ID) VALUES (@pSet, @pName, @pID);

end

commit tran -- конец транзакции

2016-03-13 15:10:39
Александр Сычёв » Всеволод
Уважаемый Всеволод!

По поводу операций добавления.


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

По поводу конфликтов записи.

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

В случае со свойством "дата правки" это сильно снизит вероятность столкновений.

Есть еще пара моментов.

Блок может занимать пользователь или программная функция. В последнем случае время обработки документа (блока) менее секунды. Такие наборы не интересны и удаляются при закрытии документа.

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

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

Почему такое в принципе возможно? Легко можно представить ситуацию, в которой N операций изменения набора происходят параллельно, но разве последовательное добавление и удаление не должно осуществляться в одном потоке? (И снова спросим: стоит ли действительно хранить такой набор в базе?)

Каждое значение получается уникальным и таблица значений дат (values_dt) быстро растет.

Удаляет ли Ваше ядро значения, которые дальше не будут использоваться? Если да, то все имеющиеся в values_dt записи используются каким-либо из наборов. Чтобы уменьшить число таких записей можно выставить точность их хранения. Это достаточно простая задача, которая легко решается на уровне интерфейса.

Спасибо,

P.S. Пользуясь случаем, снова сошлюсь на мысль о том, что "смешивать ядерные функции с хранимыми процедурами" - это не лучшая идея.

2016-03-15 00:42:24
Всеволод » Александр Сычёв

Здравствуйте, Александр!

Почему мы ввели транзакции? Воспользуюсь Вашим примером:

delete (1,$login)- сама создаст и выполнит:
SELECT NAME_ID,VALUE_ID FROM SETS WHERE SET_ID = 1 AND NAME_ID = 0;
SELECT SET_ID FROM SETS WHERE VALUE_ID = 11;
DELETE FROM VALUES_STRING WHERE VALUE_ID = 11; (здесь функция Delete "подчищает" базу) Если после того как определено, что надо подчистить, но до того как реально подчищено другой пользователь в другом наборе добавил ссылку на тоже значение, а потом сработает зачистка, то будет повисшая ссылка (VALUE_ID без VAL). То же в добавлении.
DELETE FROM SETS WHERE SET_ID = 1 AND NAME_ID = 0;

Поэтому зачистку и удаление/добавление мы объединили в транзакцию.

Вторая реализация зачистки без явной транзакции одним запросом (в нем будет скрытая транзакция):

DELETE FROM values_dt WHERE Value_id IN 
            (SELECT Value_id FROM sets WHERE Value_id IN
            (SELECT Value_id FROM sets WHERE SET_ID = @pSet_id AND NAME_ID = @pName)
            GROUP BY Value_id HAVING COUNT(Set_id)<2); -- найти множество Value_id для зачистки и зачистить
           
DELETE FROM sets WHERE SET_ID = @pSet_id AND NAME_ID = @pName; -- удалить свойство набора

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

Точность дат нужна посекундная. Исходя из реальных данных посекундная точность уменьшит количество значений примерно на 15%. (Неиспользуемые даты удаляются.)

Если "короткие" наборы (документ был открыт менее 3-х секунд) не записывать сразу в базу, то придется его записать в базу_2 (специально созданный для этого кеш) пока не выяснилось, какой он. Кеша к Сычевской структуре пока не сделали. А он, наверное, понадобится не только для данной задачи?

С уважением,

2016-03-17 15:09:52
Александр Сычёв » Всеволод
Уважаемый Всеволод!

В данной ситуации различные СУБД могут обрабатывать такой подход по разному. Некоторые (к примеру, Oracle) могут воспринимать изоляцию данных в транзакции таким образом, что всё, что происходит внутри транзакции видимо только внутри транзакции до тех пор, пока она не подтверждена. Select, затрагивающий изменяемое значение, при этом, не блокируется, и выберет значение которое впоследствии удалится, не дожидаясь окончания транзакции. В этом случае транзакции нам не помогут.
 
Сейчас проблема с теряющимися данными решается добавлением булевского поля locked в таблицы Values. По умолчанию оно имеет значение false. Значение true выставляется для строки в тот момент, когда она затронута функцией ADD. По окончании работы полю возвращается значение false. Строка может быть удалена только если поле locked имеет значение false. Таким образом, данные не теряются. При этом, мы не вводим долгих транзакций, которые могут "налететь" друг на друга. 

Вероятность Deadlock-а, при этом, значительно снижается, а если он, всё-таки, происходит, мы можем сразу же принять решение, что делать с заблокированными запросами. 
  
Для работы именно с транзакциями, мы используем функции begin_transaction() и commit_transaction($yes_or_no), а запросы, которые хотим объединить в транзакцию, заключаем между этими функциями. Это даёт большую гибкость, поскольку мы можем сразу в программном коде составлять любые удобные нам транзакции, не переписывая и не добавляя хранимые процедуры.

Так или иначе, мы предпочитаем обходиться без хранимых процедур. Хотим иметь возможность работать с несколькими базами разных типов одновременно, несмотря на то, что, как сказано выше, "диалекты процедурных языков разных СУБД могут быть несовместимы". 

Спасибо,
 
2016-03-18 15:15:53
Всеволод » Александр Сычёв

Уважаемый, Александр!

мы используем функции begin_transaction() и commit_transaction($yes_or_no)

Значит, Вы сами, на ядре, ведете журнал транзакций и организуете откат? Используете ли Вы "свои" индексы?

В нашем ядре каждый запрос исполняется независимо от остальных - в отдельной транзакции

Зачем тогда булевское поле locked в таблице Values? Да и как оно поможет? Допустим, функция Add1 начала работу и заблокировала значение. В это время вторая функция Add2 видит, что значение существует, хоть и заблокировано и добавляет на него ссылку. После этого Add1 удаляет значение и возникает повисшая ссылка.
Если же, Add2 не допускается к чтению заблокированного значения, то она может добавить то же значение с  новым id. Если Add1 не понадобится удалять значение, то появится дубль.

Поле locked добавляет вероятность появления ложно заблокированных значений. Функция заблокировала и, не успев освободить, сбойнула. Значит нужна еще и  транзакция на все операции от блокировки до освобождения, чтобы откатить в случае чего. У нас есть поле locked в других таблицах по Структуре1, но без транзакций (они замедляют работу). Админу приходится специальным инструментом регулярно, хоть и не часто, разблокировать ложные блокировки.

С уважением,

2016-03-19 15:54:18
Александр Сычев » Всеволод
Уважаемый Всеволод!

Идея в том, что "запертое" значение может быть прочитано, но не может быть удалено. Алгоритм работает так:

Функция Add1 (или Delete1) начала свою работу и обнаружила значение которое нужно удалить. Она его не запирает. В то же самое время начала работу функция Add2. И обнаружила значение на которое нужно создать ссылку. Она его запирает. Запертое значение не может быть удалено. По окончании работы функция Add2 отпирает значение.

Таким образом если функция Add2 создает ссылку на значение, конкурирующая функция Add1 (или Delete1) работающая в тот же самый момент, не может это значение удалить.

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

Но у нас самих пока не возникало в них необходимости.

Индексы пока тоже не используются.


С уважением,
Александр Сычев. 
2016-05-19 12:52:38
Алексей » Всеволод
Добрый день, Всеволод.

Подскажите, пожалуйста, удалось ли вам решить проблему дедлоками?

Спасибо.
2016-05-26 11:48:01
Всеволод » Алексей

Здравствуйте, Алексей!


Подскажите, пожалуйста, удалось ли вам решить проблему дедлоками? 

Дедлоки порешали. Пока. По нашему опыту, решение проблемы с производительностью приводит к тому, что программу начинают активно использовать (или приходит новый клиент, которому все надо быстрее-больше-за_те_же_деньги) и через некоторое время снова что-нибудь отстает.

С уважением,

2016-05-26 16:04:38
Павел Друбич » Всеволод
Здравствуйте,

решение проблемы с производительностью приводит к тому, что программу начинают активно использовать (или приходит новый клиент, которому все надо быстрее-больше-за_те_же_деньги) и через некоторое время снова что-нибудь отстает.

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

C пожеланиями,
2016-07-29 07:59:10
Евгений » Александр Сычёв
Александр, приветствую!

Правильно ли я понимаю, что помимо самого ядра (с реализацией функционала CRUD) необходимо будет написать ещё одну прослойку над-ядра, которая будет решать вот такие задачи с "221 запрос, свёрнутые в 4 select-а". И только потом, собственно говоря, основной скрипт вызывает эту заточенную под конкретную задачу функцию над-ядра (в свою очередь вызывающий CRUD-функции базового ядра)?

С уважением, 
2016-07-29 12:27:18
Александр Сычёв » Евгений
Добрый день, Евгений!

Совершенно не обязательно. Ведь если Вы предаете вашим функциям add, get и delete массив идентификаторов, имен или значений, то при составлении запроса к базе, как уже было сказано выше, Вы можете использовать оператор "IN" для работы с диапазонами значений, а для того, чтобы подготовить сам массив достаточно сделать так:

$sql .= "IN (".implode(", ", array_unique($values)).")";

(Если я верно понял Ваш вопрос).

С Уважением, 
2016-07-29 14:21:57
Евгений » Александр Сычёв
Александр, 

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

Т.е., получается так:

1. Базовое ядро - EAV Owls + CRUD
           ||
           \/
2. Специфическое ядро - реализующее select-ы (и другой функционал) под конкретный проект
           ||
           \/
3. Основные скрипты проекта, реализующие клиентскую и серверную логику на уровне интерфейсов (уровень ничего не знающий об уровне 1)

Всё верно?

Или же можно каким-то образом 2 уровень - свернуть в основные скрипты (3 уровень)?


PS. Понимаю, что жутко не хватает какого-нибудь прикладного примера. Кстати, присоединяюсь к просьбе выложить реализацию базового ядра (10 кб который), так как в теории всё красиво, хочется также насладиться и красотой практики. Александр, если это Вы всё сами придумали, то снимаю перед Вами шляпу!


С уважением,
2016-07-31 11:39:45
Александр Сычёв » Евгений
Уважаемый Евгений!

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

Нет, не придётся. Нет необходимости в уровне 2 (cпецифическое ядро - реализующее select-ы (и другой функционал) под конкретный проект). Не то, чтобы он "свёрнут", просто не требуется "затачивать" select-ы (и другой функционал) под конкретный проект.

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

Пример, да, нужен. Мы сейчас ведём одновременно сразу 8 разных проектов на базе "EAV as Owl's" - так что, сквозному примеру есть откуда взяться. Если хотите обсудить какой-либо Ваш, можно сбоку на левой панели Форума ещё одну ветку завести.

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

Более того, существует уже и "ядро-2", которое сразу создаёт действующие пользовательские интерфейсы. Ну, то есть, получает на вход "anything from anywhere" и создаёт Вам, например, сразу специфическое действующее мобильное приложение на Java с пользовательским интерфейсом соответствующим стандартам Google Material Design и со специфическими же функциями.

Большое Спасибо за Вашу оценку нашего проекта,
2016-08-04 15:45:38
Евгений » Александр Сычёв
Александр, добрый день!

Не совсем непонятно - как же не придётся делать надстройку 2-го уровня над базовым ядром, если постоянно будут возникать задачи, типа выше "221 запрос, свёрнутый в 4 селекта". Это же ведь не на базовом уровне ядра реализуется. Ведь в ядре у нас строго CRUD-функционал! Или же Вы эту функцию "221" - хотите вставить сразу в основной проект клиентского приложения (3-й уровень)?

И в нарисованной 3-х уровневой модели - клиентский (3 уровень) уровень проекта так ничего и не знает про 2-ой уровень специфики проекта. И как там всё устроено. Просто делается вызов функций и всё. Т.е., канонический подход к разработке приложения соблюдён...

Вы пишите: "реализацию базового ядра не только хочется выложить, но и следующую статью ему посвятить. Однако, при этом, возникнет вопрос: за счёт чего буду жить я, другие участники проекта, а также за счёт чего будет развиваться сам проект?"

Ну, тогда, я думаю, что если Вы голодаете, и Вам требуется финансовая поддержка, или что-то ещё, что позволит "развивать проект", то тогда стОит озвучить свои условия, чтобы народ начал использовать Ваше изобретение по своим возможностям. Если голодаете, то приглашаю в Самару в гости - накормлю и напою... :)

Что касается моего случая, то сейчас (на стадии стартапа нового проекта) нахожусь на перекрёстке выбора: либо остановиться на классической схеме "одна таблица - хранение одних типов сущностей", либо на Вашей модели. В теории Ваша модель интересна, но практическую реализацию пока сложно оценить, так как классическую модель у меня было возможность пощупать на HL-проектах, а вот Вашу модель - пока ещё нет. Т.е., получается, что чтобы проверить её прикладную эффективность, нужно будет сначала реализовать ядро, и сверху накатить специфику, чтобы потом только погонять систему на большом количестве данных. Это как бы оттягивает создание самого проекта, так как придётся повозиться со многими тестами на производительность.

Что касается классической схемы. Я написал там своего рода мета-конструктор данных, в котором заведение новых типов, сущностей, связей и отношений между таблицами - всё делается в некотором конфигураторе, поэтому оперировать таблицами и данными на уровне СУБД практически не приходится. Да и пользовательские интерфейсы CRUD тоже строятся все автоматически, исходя из существующих мета=данных. Т.е., это своего рода конструктор ЛЕГО, из которого я собираю специфику конкретного проекта. И задача поддержки и развития проекта (с мета-информацией о проекте) для заказчиков - уже достаточно просто решается, как и в Вашей модели.

Вот сижу, "чешу репу, не чеша"... :)


PS. По поводу "ядра-2". Слайды есть?

С уважением,
2016-08-04 17:00:00
Александр Сычёв » Евгений
Уважаемый Евгений!

1. Про "221 запрос, свёрнутый в 4 селекта", ведь вроде бы, ответил тут. Используйте оператор "IN" для работы с диапазонами значений. А для того, чтобы подготовить сам массив, сделайте так:

$sql .= "IN (".implode(", ", array_unique($values)).")";

И всё.

2. Мы не голодаем, но спасибо за приглашение :). За "харчи" тоже не работаем, а коммерческое предложение можем направить. Как написано выше, мы сейчас ведём одновременно несколько проектов на базе "EAV as Owl's" - присоединяйтесь. Увидите "ядро" и "накатите специфику" и т.д.

Последняя версия ядра умеет самостоятельно и незаметно "шардить" - то есть, ежели у Вас действительно предполагается большой или сверхбольшой объём данных, то оно само будет делать так, чтобы всё было "шустро", когда данных станет много.

3. Что касается "классических реляционных схем", то, при предполагаемом большом объёме данных, их просто нельзя использовать. С того момента как распространились мобильные устройства и объёмы данных в базах проектов стали расти громадными темпами, типовые реляционные структуры вытесняются из употребления. Они медленнее, они нормально не масштабируются (поскольку обилие типов, сущностей, связей и отношений между таблицами приводит к тому, что надежность и устойчивость к разделению становятся взаимоисключающими свойствами).

А как написано в статье во вполне "приличной" хорошо нормализованной структуре, например, при 12 сущностях может быть до 90 таблиц, а, если их больше, то формула там приведена - можно посчитать. И это всё надо ещё понимать и сопровождать, и обрабатывать.

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

Или, перефразируя Алана Кея, можно сказать:

"Или мы построим рабочую модель, чтобы (большой объём кода) схлопнулся во что-нибудь простое как уравнения Максвелла для электромагнитных волн или простое как Конституция США, которую можно носить в кармане рубашки, или же мы создадим вовсе даже не Конституцию, а - наоборот - (ответ на всякий простой вопрос) будет представлять собой 3 кубических мили прецедентного права, как это происходит по факту".

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

По поводу "ядра-2".

Слайды есть?

Поскольку оно вполне защищено в отличие от "ядра-1", то можно пользоваться не слайдами.

Cпасибо,

P.S. Если Вы по адресу: any@triz-ri.ru пришлёте Ваш скайп, мы с Вами свяжемся и обсудим деловые вопросы.
2016-08-04 19:13:10
Евгений » Александр Сычёв
Александр,

1. Я про IN и говорю. Что специфика проекта же не заканчивается на выборке только одних данных, и что этих IN-ов может быть сотни, которые и увязываются между собою в каждом конкретном проекте (специфика). И только потом уже уровень приложения основывается на этом промежуточном уровне бизнес-логики.

2. Присоединиться я смогу, если, наверное, позволят мои компетенции. Просто, возможно, моя прикладная сфера может не соответствовать Вашему направлению. Хотя, наверное, нужно посмотреть, какие точки соприкосновения мы сможем здесь найти.

3. Если честно, то я пока не вижу победного шествия новомодных хипстерских баз данных: как подавляющее большинство пользуется MySql, Oracle, Firebird, MSSql - так и пользуется... :) И что-то я пока не заметил, чтобы они уступали по производительности "новым" субд-движкам. :) Возможно, я ошибаюсь...

Что касается надёжности и устойчивости - они всё же не от количества связей зависят, а от "прослойки" между креслом и экраном компьютера... :)

Про нормализацию и размер кода. Если, конечно, простите "тупо" сидеть и на каждую таблицу писать свой интерфейс и свои отношения, то да, нормализация - это погибель человеческая... Но если у Вас в базе данных наряду с самими таблицами, хранится информация о бизнес-логике этих таблиц, то всё значительно упрощается. При этом - количество кода никак не увеличивается, так как существующий движок при построении интерфейсов клиентского приложения берёт метаданные из базы, на основании которых и отстраивает эти интерфейсы. (Могу показать, кстати, как это работает, если интересно). При этом соблюдается принцип - клиент ничего не знает про базу данных. :)

Про начало пути - спасибо! Как говорится в мультике: лучше день потерять, зато потом за 5 минут долететь! (с) "Крылья, ноги, хвост"  https://www.youtube.com/watch?v=i_8-e1dK7xM

4. Скайп отправил. Жду приглашения... :)

С уважением,
2016-08-05 12:10:37
Всеволод » Александр Сычёв

Здравствуйте, Александр!

Кроме того, несмотря на то, что Клиентская часть "не знает" о структуре и т.п., количество программного кода, которое будет на стороне Клиента (если будет "классический подход" ) также будет расти лавинообразно - могу пояснить почему.

Поясните, пожалуйста, если есть время.

Более того, существует уже и "ядро-2", которое сразу создаёт действующие пользовательские интерфейсы. Ну, то есть, получает на вход "anything from anywhere" и создаёт Вам, например, сразу специфическое действующее мобильное приложение на Java с пользовательским интерфейсом...

Мы у себя реализовали следующее: ядро возвращает данные на клиент не в виде привычных "программистских" структур данных (массивы, переменные), а уже "упакованными" в интерфейс пользователя (u-iface). Для этого u-iface представляется в виде простенького языка разметки (у нас своя разметка, но поскольку требования к u-iface постоянно растут, то стало ясно, что сразу надо было брать html). Если появится новый клиент, например, мобильный, для него потребуется написать только парсер разметки u-iface, чтобы он начал отображать формы с данными.

У Вас по другому?

P.S. Начальство, однако, по-прежнему предпочитает hand-made формы, которые дизайнер отрисовывал по-пиксельно.

P.P.S. Программисты уже пишут не программы, а программы, которые пишут программы. Не пора ли выбирать новую профессию? =))

2016-08-05 12:47:59
Евгений » Всеволод
Всеволод, приветствую!

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

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

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

С уважением, 
2016-08-05 17:31:01
Всеволод » Евгений

Здравствуйте!

вообще не нужно html-разметку передавать, достаточно мета-структуру данных и способ построения формы

С ростом требований способ построения формы будет все более походить на html - разметку. Или иной способ построения любых форм. Что Вы подразумеваете под движком? Парсер разметки на клиенте остался. Генерируется форма автоматически на сервере для того, чтобы программист не занимался рисованием [формы] в рабочее время =) А затем наполнением "рисунка" данными (binding). А затем перерисовкой, если что-то поменялось. 

С уважением,

2016-08-05 17:59:23
Александр Сычёв » Евгений

Уважаемый Евгений!

Я про IN и говорю. Что специфика проекта же не заканчивается на выборке только одних данных, и что этих IN-ов может быть сотни, которые и увязываются между собою в каждом конкретном проекте (специфика). И только потом уже уровень приложения основывается на этом промежуточном уровне бизнес-логики.

Число IN-ов, на самом деле, не зависит от специфики. Давайте рассмотрим, в каких случаях в принципе могут возникнуть "сотни" IN-ов.
 

Ситуация первая: Нам нужно выбрать сотню свойств у объекта. Эту ситуацию мы уже рассмотрели выше.
 

Ситуация вторая: Нам нужно выбрать свойства у сотни объектов. Напомним что функция Get принимает на вход массивы из идентификаторов, имен и свойств, то есть она умеет работать с множеством объектов сразу.

А значит, чтобы выбрать данные для сотни объектов нам не обязательно выполнять сотню Get-ов. Нам нужно выполнить ОДИН Get для сотни объектов.

Мы просто положим их в массив $ids и вызовем, например, Get ($ids, $names, null);

Множество идентификаторов здесь свернутся в один IN таким же образом, как сворачиваются $names.

Важно помнить, что, поскольку в нашей базе все объекты хранятся в одинаковом формате, мы можем в массив $ids передать идентификаторы сразу нескольких разных по типу объектов. И запрос Get ($ids,null,null); все равно отработает.

На остальные вопросы - в следующем сообщении.

Cпасибо,

P.S. Письмо получили - в понедельник постучимся в скайп.
 

2016-08-05 19:34:21
Александр Сычёв » Евгений
Уважаемый Евгений!

Если честно, то я пока не вижу победного шествия новомодных хипстерских баз данных: как подавляющее большинство пользуется MySql, Oracle, Firebird, MSSql - так и пользуется... :)

Много чего есть на свете, но ищешь не то, что часто, а то, что ценно. Много ларьков, мало "Walmart'ов".

Между тем, речь идёт не о разных СУБД, а о структурах данных, которые там будут заведены. Тут можно, конечно, обсудить разные тонкости, в том числе, где хранить бизнес-логику и др. Но когда у Вас всего 2 таблицы из 4-х полей, причём 3 поля из 4-х - это ID, и это годится для любой бизнес-логики, то можно обсудить уже что-то иное :).

Этот наш Owl's работает в разных проектах на разных системах: на MySql, на Firebird, на MSSql, на файловой NoSQL базе, на файловой 1С-базе и легко переносится/распыляется куда и как надо независимо от объёма данных (в силу предельной простоты и универсальности). Так что тут нет предмета для спора. 

И что-то я пока не заметил, чтобы они уступали по производительности "новым" субд-движкам. :) Возможно, я ошибаюсь...


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

Что касается надёжности и устойчивости - они всё же не от количества связей зависят, а от "прослойки" между креслом и экраном компьютера... :)

От количества связей зависят очень сильно.  

Спасибо,
 
2016-08-25 13:13:30
Всеволод » Александр Сычёв

Здравствуйте, Александр!

"Мы сейчас ведём одновременно сразу 8 разных проектов на базе "EAV as Owl's" - так что, сквозному примеру есть откуда взяться"

Имеется ли в виду, что 8 разных проектов используют одну таблицу SETS? Как Вы считаете, целесообразно ли проектировать ситуацию, когда одно ядро будет иметь несколько таблиц SETS? С одной стороны, можно все хранить в одной таблице и при необходимости ее шардить. С другой, клиент может потребовать на всякий случай отделить его данные по одному проекту от данных по другим проектам.

Применялась ли структура "EAV as Owl`s" для хранения бинарных массивов? Есть задача оперировать большим количеством превьюшек. Размеры: от нескольких Кб до 1 Гб. Традиционно они хранились как файлы на диске. Появилось желание засунуть их в базу для упрощения способа доступа к ним. Хотя клиент скорее всего возмутится исчезновением привычных файлов, которые можно открыть "вручную".

С уважением,

2016-08-27 15:01:47
Александр Сычёв » Всеволод

Добрый день, Всеволод.

Имеется ли в виду, что 8 разных проектов используют одну таблицу SETS?

Нет, одно ядро обслуживает 8 разных проектов (в принципе, сколько угодно может обслуживать). 

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

Более того, поскольку, как сказано выше, ядро может обслуживать сколько угодно баз (и проектов) одновременно, можно разделить данные по одному большому проекту на несколько разных баз.

Как Вы считаете, целесообразно ли проектировать ситуацию, когда одно ядро будет иметь несколько таблиц SETS?

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

Применялась ли структура "EAV as Owl′s" для хранения бинарных массивов? Есть задача оперировать большим количеством превьюшек. Размеры: от нескольких Кб до 1 Гб.

Применяется чаще для хранения ссылок на них. Исключение - тексты статей и документов. Что касается изображений, видео и прочей музыки :), то на подобные объекты в базе хранятся ссылки.

С Уважением, 

2016-08-27 15:29:32
Александр Сычёв » Всеволод

"Мы у себя реализовали следующее: ядро возвращает данные на клиент не в виде привычных "программистских" структур данных (массивы, переменные), а уже "упакованными" в интерфейс пользователя (u-iface). Для этого u-iface представляется в виде простенького языка разметки (у нас своя разметка, но поскольку требования к u-iface постоянно растут, то стало ясно, что сразу надо было брать html). Если появится новый клиент, например, мобильный, для него потребуется написать только парсер разметки u-iface, чтобы он начал отображать формы с данными.

У Вас по другому?

У нас это разделено на 2 ядра. Ядро, которое вычитывает данные из базы, и ядро, которое формирует разметку. Оба независимы друг от друга, поэтому легко могут изменяться и подстраиваться под изменяющиеся требования.

P.S. Начальство, однако, по-прежнему, предпочитает hand-made формы, которые дизайнер отрисовывал по-пиксельно.

Не скажу Вам пока за веб-страницы, но андроидные мобильные приложения у нас программно (то есть, автоматически) сразу получаются лучше, чем дизайнер с программистом в 4-е руки cделают. И уж точно быстрее. Программа и лучше прорисует, и java-код приложения создаст сразу и компактнее, чем программист; и стандарт "Google Material Design" формализуется достаточно хорошо. И программу можно "учить".

P.P.S. Программисты уже пишут не программы, а программы, которые пишут программы. Не пора ли выбирать новую профессию? =))

Вечная профессия - это изобретатель. Остальные краткосрочны. :)

C Уважением,
 

2016-08-28 00:32:51
Всеволод » Александр Сычёв

Здравствуйте, Александр!

Большое спасибо за Ваши ответы! В нашей практике мы каждому клиенту по серверу ставим. Соответственно, своя копия софта и своя база на проект (на клиента).

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

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

И логическое: лог проекта хотим хранить в SETS_LOG, а документы проекта в SETS_DOC и т.п. И придется сообщать ядру о таком нашем желании; само оно не догадается. Появляется индекс для таблицы SETS, который надо передавать в функции Add, Del, Get. Надо так надо, но не нарушит ли это исходные принципы простоты?

В будущем, наверное, будут полезными стандарты использования структуры "EAV as Owl's" подобные тем, что когда-то выпускала Sun для java-разработчиков.  

С уважением,

2016-09-01 11:18:42
Александр Сычёв » Всеволод

Уважаемый Всеволод!

... лог проекта хотим хранить в SETS_LOG, а документы проекта в SETS_DOC и т.п. ...

Обратите внимание на более общий аспект. Если Вы будете делать таблицы Sets "по именам" (Names) (если я верно понял Ваш пример), Вы можете отказаться от поля NAME_ID в этой таблице, что увеличит и быстродействие, и простоту "шардинга".

... И придется сообщать ядру о таком нашем желании; само оно не догадается...

Ну, последняя версия догадывается :) ...

В будущем, наверное, будут полезными стандарты использования структуры "EAV as Owl's"подобные тем, что когда-то выпускала Sun для java-разработчиков.  

Скорее всего, да. 

Спасибо,

2016-09-02 12:58:49
Всеволод » Александр Сычёв

Здравствуйте, Александр!

Таблицы SETS делаем по задачам. Допустим, какой-то модуль программы породил много своих данных (что-то вроде лога). Данные других модулей решили не смешивать с той таблицей. У разных модулей разная интенсивность использования, разное сопровождение, разная "программная жизнь", в зависимости от выполняемой пользовательской функции. 

Имена в разных SETS могут пересекаться.

Я думаю, что для каждой SETS лучше иметь свои наборы таблиц значений VALUES_INT, VALUES_STRING и т.д. Потому что: а) значения не смешиваются в одной таблице "из общих соображений", б) чтобы проверить используется ли данное значение или его уже можно удалить, не надо проверять все SETS, а только "свою".

Однако, если сделать для значений единую индексацию, и завести поле с количеством ссылок на данное значение из различных SETS (добавили +1, удалили -1), то и проверять придется только этот счетчик. Тогда можно будет обойтись одним набором значений для всех SETS. Не знаю, какой вариант предпочтительней?

Если сделать единую индексацию для наборов во всех SETS, то в момент генерации нового Set_ID, можно записать индекс таблицы SETS, в которой набор пропишется. Так мы получим "адресную книгу" всех наборов по их индексам и можно не указывать при вызове функций Get, Del, Add (как Update) таблицу SETS. Но тут уже появляются дополнительные операции по запросу "адреса" набора, который в нашем случае и так известен.

С уважением,

2016-09-06 19:39:32
Александр Сычев » Всеволод
Уважаемый Всеволод

Имена в разных SETS могут пересекаться.

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

Таблицы SETS делаем по задачам.

По задачам можно делить не только таблицы SETS но и саму базу разделить на несколько (Самый простой пример - хранить документы в одной базе, а товары в другой).

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

Поскольку наши имена уникальны диспетчеру легко определить по переданному имени в какой базе искать наш Set и ему будут не нужны никакие индексы и адресные книги. Таким образом мы, кстати, и получим, что для каждой сущности имеются свои наборы таблиц значений VALUES_INT, VALUES_STRING и т.д.

Спасибо,
2016-09-12 21:11:39
Всеволод » Александр Сычев

Здравствуйте, Александр!

Спасибо за Ваш ответ. Очень интересное решение с уникальностью имен. Оно как бы давно напрашивалось. Например, мы приняли "не гласное соглашение", что имена от 0 до 1000 - строки, от 1001 до 2000 - даты и т.д. для удобства определения таблицы значений. Также уже "всплывало" свойство-маркер, по которому определялся тип сущности. Например, сущность документ_в_годовом_отчете имела свойство id_year, а документ_в_месячном_отчете - id_month. А по сути это один и тот же id документа, который раздвоился и стал маркером.

В будущем (не знаю, в ближайшем или нет) мы можем столкнуться с необходимостью переписать толстый клиент под HTML5. Если у Вас продается какой-либо робот для этой задачи, можно списаться по почте.

С уважением,

2016-09-14 17:14:38
Всеволод » Александр Сычев

Здравствуйте, Александр!

Приходилось ли Вам блокировать свойство в наборе? Например, если нужно сделать следующее:

int Set_id = Get(null, id, 777);

if (Set_id > 0) Add(Set_id, name, "Иван");

else

      Add(null, new int[]{id, name}, new object[]{777, "Иван"});

Между Get и Add кто-то может этот набор (если он существует) удалить. Для предотвращения этого можно добавить в таблицу SETS поле locked и сделать Get с блокировкой свойства id у существующего набора.

int Set_id = Lock(null, id, 777);

Функция Lock заблокирует свойство id и вернет Set_id или создаст новый набор с id=777, заблокирует и вернет Set_id.

Соответственно, Del проверяет locked.

2-й вариант на уровне кода не лучше ли? В набор добавляем свойство locked.

int Set_id = Get(null, id, 777);

if (Set_id > 0)

{

      int result = Add(Set_id, locked, 1);

      if (result >= 0)  // успешная блокировка

         {

                  Add(Set_id, name, "Иван");

                  Add(Set_id, locked, 0);

          }

     else // набор успели удалить

        {          

                  Set_id = Add(null, new int[]{id, locked}, new object[]{777, 1});

                  Add(Set_id, name, "Иван");

                  Add(Set_id, locked, 0);

          }

}

 

С уважением,

2016-09-15 14:18:21
Александр Сычёв » Всеволод
Добрый день, Всеволод,

Приходилось ли Вам блокировать свойство в наборе?

А, вроде бы этот вопрос мы обсуждали тут и тут. Или дело в другом, и я неверно понял?

Например, мы приняли "не гласное соглашение", что имена от 0 до 1000 - строки, от 1001 до 2000 - даты и т.д.

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

С Уважением, Александр Сычёв


2016-09-16 09:40:33
Александр Сычёв » Всеволод
В будущем (не знаю, в ближайшем или нет) мы можем столкнуться с необходимостью переписать толстый клиент под HTML5. Если у Вас продается какой-либо робот для этой задачи, можно списаться по почте.

Конечно, можно написать подробнее на admin3@triz-ri.ru 

С Уважением, 
2016-09-16 21:25:51
Всеволод » Александр Сычёв

Александр, ранее речь шла о блокировке значений. Последний вопрос о возможной блокировке наборов в таблицах SETS. На почту отослал.

2016-09-22 18:09:41
Александр Сычев » Всеволод
Добрый день, Всеволод,


О возможной блокировке наборов в таблицах SETS.

Для блокировки набора целиком можно обойтись без дополнительного поля. Достаточно добавить новое свойство "Редактируется кем-либо". Назовем его $edited_by.

Это свойство имеет значение 0 если объект никем не правится. В тот момент когда кто-то начинает этот набор редактировать, мы присваиваем свойству $edited_by значение 1 (или можно даже записать туда идентификатор редактирующего пользователя). При выходе из редактирования значение свойства снова выставляется равным 0.

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

В общих чертах алгоритм такой:
При открытии окна проверить значение свойства $edited_by.
Если значение null или 0:
     Пустить в окно редактирования.
     Установить в значение свойства $edited_by идентификатор текущего пользователя.
     По окончании редактирования становить в значение свойства $edited_by на 0.
Иначе показать уведомление.

С Уважением,  
Уважаемые Коллеги!

Если Вам нравится наш Форум, Вы можете поддержать его, отправив любую сумму (тогда выберите опцию "Спасибо за Форум").

Вы также можете поддержать конкретное обсуждение и получить гарантированный ответ от наших специалистов (тогда выберите опцию "Прошу эксперта ответить в этой теме").
Задайте Ваш вопрос здесь.

Большое Спасибо!


Яндекс.Метрика