Клиент взаимодействует с базой данных.
С сайта http://corchaosis.ru, автор картины Jonathan Tiong.
Помимо того, что я являюсь программистом (преимущественно, это Delphi + всякие разные СУБД, в последнее время ОРАКЛ, + немного PHP), у меня есть хобби — это купля и продажа квартир. Я покупаю квартиру на этапе строительства от более менее надёжного застройщика по вкусной цене (например, сейчас таким застройщиком является Самолёт, квартиры возле м. Некрасовка продаются), дожидаюсь сдачи дома (часто на два года позже, с недорогими предложениями такое случается), делаю в ней ремонт и затем продаю за 95-100% её рыночной цены.
Так вот, я (как и все) столкнулся с проблемой отсутствия у РосРеестра транзакционности.
Проблема отсутствия у Росреестра транзакционности сделок
В программировании «Транзакция», а в недвижимости это «Сделка с альтернативой» (а также, как её часть, «Договор о банковской ячейке»), и там всё немного более сложно. Рассказываю.
Вася пришёл на просмотр квартиры, которую продаёт Петя. И Васе всё очень понравилось, в том числе и цена, но у Васи денег нет. Так начинается наша история.
Вася имеет свою недвижимость, которая имеет какие-то не особо нужные для него ценности — в соседнем доме жил Ломоносов, высота потолков семь с половиной метров, поблизости находится плодовощная база и рынок Садовод, можно дойти пешком на Аэроэкспресс, под квартирой есть подвал высотой 1 метр, над квартирой есть чердак удобный для астрономических наблюдений. Вася понимает что эти особенности повышают цену его квартиры, но не для него самого. И он решает квартиру Пети купить, а свою квартиру — продать. Но продать именно для того чтобы купить квартиру Пети, а не просто. На языке риэлторов это называется — «Альтернатива подобрана».
Теперь посмотрим на эту ситуацию со стороны Пети. Дело в том, что Пете тоже не интересно сидеть на обесценивающихся деньгах, он продаёт квартиру ради того, чтобы купить себе квартиру в эльфийском городе Валинор, но какую именно — ещё не смотрел. На языке риэлторов это называется — «Сделка с альтернативой».
Два эльфа Средиземья, Маглор и Маэдрос, обладают подходящей (критериям Пети) недвижимостью в городе Валинор, которую срочно распродают, так как отправляются служить Мелькору. На языке риэлторов это называется — «Свободная продажа».
Итак, Вася находит клиента Серёжу. Теперь, Петя находит два подходящих ему варианта в городе Валинор. Выходим на оформление сделки. Допустим для простоты, что никто из участников сделки не использует ипотеку и не имеет долевым собственником несовершеннолетних. Таким образом, теперь должны совершиться следующие действия:
1. Серёжа передаёт деньги Пете.
2. Вася передаёт свою квартиру Серёже.
3. Петя передаёт свою квартиру Васе.
4. Или Маглор, или Маэдрос, передают свою квартиру в Валиноре Пете и получают деньги Серёжи.
5. Малкор и Маэдрос идут в Мордор служить Мелькору.
Идеально было бы передать в Росреестр на выполнение следующий скрипт:
START TRANSACTION
Квартиру Васи отдать Серёже.
Квартиру Пети отдать Васе.
begin
Квартиру Малкора отдать Пете
Деньги Серёжи отдать Малкору
ЕСЛИ_ОШИБКА:
Квартиру Маэдроса отдать Пете
Деньги Серёжи отдать Маэдросу
end
COMMIT TRANSACTION
Это упрощённый скрипт сделки с альтернативой, предполагающий, что у всех квартир один взрослый (и дееспособный) собственник, что их стоимости равны, и что оплата риэлторов (если они есть) оплачивается вне привязки к этапам сделки.
Однако, Росреестр не поддерживает транзакционность. Все действия будут выполняться последовательно и независимо, друг за другом, без отката транзакции в целом если не выполнилось одно из них. Максимум, что можно достичь — учитывая, что Росреестр и МФЦ не работают с передачей наличных средств — это заложить деньги в банковскую ячейку, с условиями доступа к ним Васи, Пети, Серёжи (если вообще никакая сделка не зарегистрирована), и иных действующих лиц, по факту предъявления ими зарегистрированных Росреестром договоров. (И кстати, банки самостоятельно проверку подлинности договоров не осуществляют, то есть доверяют подлинности бумаг участников сделки).
Кроме рисков неполного выполнения транзакции, другая проблема в том, что если другие участники могут въехать в своё новое жильё не дожидаясь полного оформления (привет, вопрос недоплаты коммунальных платежей!), то Маглор и Маэдрос нескоро отправятся служить Мелькору, и возможно, Маглор не сможет подержать в своих руках сильмариллы, он просто не успеет. Сделки с недвижимостью выполняются последовательно, и оформление каждой сделки будет длиться не менее чем 9 рабочих дней.
Кроме этого, Росреестр не поддерживает обременение строящегося по ДДУ жилья, а мог бы, это элементарное действие в отношении простого фьючерса.
Теперь перейдём к недостаткам и моим хотелкам про СУБД
1) Первое — это отсутствие системы контроля версий. Если со стороны Delphi я веду разработку в своей песочнице, и сделанные мной изменения не появятся у других программистов до момента их коммита, то с СУБД не так. И даже если мне доверяют полный (по крайней мере, в рамках нужного для поставленной передо мной задачи) доступ к боевой БД, а такое случается, я не могу на ней разрабатывать. Пока я буду отлаживаться, всё рухнет. Это что за каменный век??? Сделайте песочницу разработчикам.
2) Второе — это отсутствие предустановленных стандартизированных таблиц, описывающих реальный мир. В каждой компании, где я работал, свой собственный формат таблицы, описывающий названия (на русском и (по крайней мере) английском языке, в разных падежах русского языка) двенадцати месяцев!
3) Третье — и тут я воспользуюсь терминологией Оракла — отсутствует возможность вызвать простой скрипт Insert или Update, использующий Returning, так, как мы вызываем Select. Возможно, это не проблемы Оракла, а проблемы стыка Delphi + Oracle.
4) Четвёртое — необходимость назначения создаваемым мной процедурам и функциям полномочий там, где я делать этого не хочу. Я не хочу задавать, а потом менять, полномочия пользователей процедуре и функции. Почему, если я явно не написал Grant-ы, система не могла бы сама посмотреть на задействованные объекты, и в соответствии с правами на действия с ними наделять или нет тех или иных пользователей правом на вызов функции? Я готов написать для этого при написании функций и процедур одно ключевое слово. Или, ещё лучше, пусть пользователь начнёт выполнение, а если ветка алгоритма приведёт его к запросу на который у пользователя нет прав, то выкинет с ошибкой.
hard_sign
1. Edition-based redefinition
2. TO_CHAR(SYSDATE,'MONTH','NLS_LANGUAGE=RUSSIAN')
3. Начиная с 11g точно есть, раньше – лень смотреть
4. Такое поведение по умолчанию некорректно, но если очень надо, можно за полчаса написать скрипт, выдающий соответствующие полномочия, с использованием USER-DEPENDENCIES
DragonSoft Автор
>> 2. TO_CHAR(SYSDATE,'MONTH','NLS_LANGUAGE=RUSSIAN')
Иногда нужен не именительный падеж. «Второго апреля», «Восьмого марта».
>> 3. Начиная с 11g точно есть, раньше – лень смотреть
Returning без into? Просто выдающий данные, как это делает Select? Можно пример?
У нас в текущей системе как раз 11g.
hard_sign
2. Вот тут непонятно, какой вопрос к разработчикам СУБД. Не так много языков, где есть понятие «падежа», и не во всех языках с падежами изменяется само слово
3. returning без into – очень странный запрос. Но если есть суровая революционная необходимость, то копайте в сторону ref_cursor или pipelined functions
DragonSoft Автор
2. Так это всё равно все фирмы делают, но во всех проектах по-разному. Почему не встроить в БД? В одном городе снесли все ограды и дорожки, а затем по протоптанным людьми тропинкам положили новые. Вот, если все люди топают по одной и той же тропинке, почему её не проложить как надо, организованно и стандартно?
3. Не думаю, что создать запись и в той же команде узнать её id (создаваемый автоинкрементно, в случае оракла через сиквенс) это странный запрос. Это самый распространённый запрос. Который хочется написать в PL/SQL Developer в одну строку, без создания переменной, вывода её значения и прочих шаманских танцев.
hard_sign
2. Потому что это не относится к СУБД. Управление НСИ – это совершенно отдельная область знаний.
3. Автоинкремент – это плохой шаблон, триггер – тоже плохой шаблон. Так писали 20 лет назад, а сейчас так писать не надо. Гораздо лучше взять из базы значение последовательности, а идентификатор новой записи вычислять как S*1000+i, где i – номер вставляемой записи. Как тысячу записей вставили – взяли новый элемент последовательности.
DragonSoft Автор
>> Гораздо лучше взять из базы значение последовательности, а идентификатор новой записи вычислять как S*1000+i, где i – номер вставляемой записи. Как тысячу записей вставили – взяли новый элемент последовательности.
Если так сделать, order by id не будет обеспечивать исторический порядок вставок в таблицу. Плюс проблемы со скоростью из-за дырок. Придётся делать order by datetime_creation, каковое поле может отсутствовать, это более ресурсоёмко, плюс учитывайте что время на компьютере меняется квантами (в винде в зависимости от настройки это от 10 до 55 милисекунд) то есть близкие по времени записи будут не в том порядке.
hard_sign
Использование первичного ключа для чего-то кроме первичного ключа – это тоже очень плохой шаблон. Судя по вашему описанию, там вся система – костыль на велосипеде копипейстом погоняет, а требования – к СУБД будущего…
DragonSoft Автор
Я вам назвал 4 причины, вы вместо того чтобы ответить продолжаете навешивать ярлыки. Вы институтский преподаватель?
Имеющий дыры — промежутки праймари кей работает медленнее — это из практики, не теории. Что вы выигрываете своим методом? Скорость добавления в базу? Для закачки больших массивов данных используют BCP.
hard_sign
Вообще-то тему «будущего» вы сами подняли в заголовке. СУБД будущего ориентируются всё-таки на современные подходы к разработке, а не на поддержку дремучего legacy. Как по мне, так будущее за KV-хранилищами (потому что при использовании JPA навороченный SQL не очень-то нужен) и умными сетевыми СХД (типа Amazon Aurora).
Своим методом я выигрываю уменьшение конкуренции за последовательность. Это не магический объект, а просто строка в системной таблице, обёрнутая «синтаксическим сахаром».
А вот мне даже любопытно, какая операция выполняется медленнее, если в PK есть промежутки? У меня фантазии не хватает.
DragonSoft Автор
Селекты, конечно. по id = ..., по id in(...,...,...), по id between.
Я не знаю точно, почему так.
hard_sign
Фундаментальных причин для этого нет. Ответ на вопрос «почему так» надо искать в условиях тестирования:
Ну и так далее.
akryukov
Вам правильно говорят, что у id не должно быть отношения порядка.
Да, придется делать поле datetime_creation, если вам нужно по нему выбирать.
С какой целью вам вообще нужно выбирать по дате вставки?
DragonSoft Автор
Чтобы договор номер 2 не создавался раньше договора номер 1?
Чтобы логировать и показывать события в правильной последовательности?
Чтобы 18 марта 2019 года фирма заключала договора от 20115 до 20267, а не перечислять их номера через запятые?
Поле datetime_creation всё равно делать, чтобы помнить дату и время. Но это не гарантирует правильный порядок, как я показал выше.
akryukov
Это какие-то особенные договора? Может быть надо более явно отразить эту логику в коде и требованиях? Предположу, что договор 1 например о покупке товара, а договор 2 о продаже товара. Может их надо связать с какой-то сущностью "сделки"?
Может сделать в договоре 2 сделать ссылку на договор 1? Что-то в духе "на основании такого то".
Показывать события в нужной последовательности можно и с помощью datetime_creation.
Выборку договоров за 18 марта 2019 года можно тоже сделать по полю datetime_creation.
Кстати, а как вы без даты создания узнаете что диапазон договоров за эту дату начинается именно с 20115 и заканчивается на 20267?
DragonSoft Автор
akryukov
Почему это лучше, чем передавать две даты?
Тут вопросы уже к заполняющей системе. Почему эти договоры заключаются одновременно, а не последовательно?
Что если в одном из договоров ошибка, а в других все нормально?
DragonSoft Автор
Они не заключаются одновременно, а вводятся. Например при заливке выполненных работ в систему учёта рабочего времени.
Про даты: а если той же функцией надо обработать 1 договор?
>> Что если в одном из договоров ошибка, а в других все нормально?
Это не повод для delete. В серьёзных системах никогда ничего не delete. Выставляют состояние — ошибка, удалён, клиент передумал, менеджер выдумал договор который не заключался, и всё такое.
akryukov
Я так понимаю, вы делаете одно и то же действие как над пачкой договоров, так и над одним.
Значит у вас будет функция обработки одного договора, которая принимает один id и другая функция, которая принимает диапазон дат и вызывает первую функцию.
Предполагаю, что следующее возражение будет о том, что у вас там глобальные переменные меняются и side-эффекты есть. Я угадал?
DragonSoft Автор
Представьте что функция упаковывает переданные в неё договора в email письмо и отсылает директору. Зачем ему 250 писем вместо одного письма? Если он попросил один договор прислать, то ему пришлют. При чём тут глобальные переменные…
akryukov
Значит все таки side-эффекты.
Это все у вас в СУБД делается что ли?
Ну тогда три функции. Одна с фильтром по диапазону дат, другая с указанием конкретного договора. Обе вызывают третью, в которой уже отправка емейла.
Это ведь наверняка два разных сценария использования. Там точно нужен один и тот же емейл?
Может быть при отправка одного договора "на посмотреть", его надо как-то иначе оформить?
akryukov
Кстати если вы этот номер потом где-нибудь печатаете, то разглашаете часть бизнес-информации. Контрагент может понять сколько у вас было других договоров за какой-то период времени.
DragonSoft Автор
Вы тоже. Это решается иначе.
akryukov
Как?
DragonSoft Автор
Самое простое — Назначать клиенту с названием «ООО Рога и Копыта» буквенный номер договора, образованный транслитом по первым 5 согласным названия: Договор RgKpt-0001, договор RgKpt-0015…
Сокращение хранить в базе как атрибут клиента, который редактируется вручную и должен быть уникальным.
А там, по задаче. Если не всегда есть интернет — формировать UID, если делать примитивно — то xor 1234567, и так далее по обстоятельствам.
Если надо показать себя крутым — то *1000+rand(1000) :)
akryukov
Ну то есть вы сделаете другое поле, которое будет служить суррогатным ключом, а id будете держать чтобы помнить очередность заключения договоров?
Не проще ли сокращение сделать первичным ключом, а очередность помнить по дате?
Вот это тоже непонятно. Зачем нам его редактировать?
Ну то есть вы его распечатали на какой-то официальной бумажке с печатями и подписями, а потом менеджер может просто взять и изменить идентификатор в базе и концы в воду.
DragonSoft Автор
ОК, редактируется вручную при первом вводе клиента, далее не редактируется. Всё равно договор привязан к клиенту не по RgKpt а по id. Да, при такой конфигурации в таблице два уникальных поля. Но вы же не будете ссылаться на эту таблицу из других таблиц через поле-текст?
akryukov
Ну тут два варианта.
Вы серьезно думаете, что экономя на джойне по строковому id вы что-то выигрываете в первом варианте?
DragonSoft Автор
Обычно есть «номер договора» — который печатают в документах и в нём могут быть буквы, и id договора — целочисленное поле. Оба поля уникальны в таблице договоров (либо поле номер не уникально, если в таблице хранятся и те договора, номер которых формирует контрагент — тогда есть шанс что два разных контрагента одинаково назовут договор с вами). Это более-менее стандарт…
Дату нельзя использовать для определения порядка следования, я дважды написал, почему.
Если менеджер носит свой ноутбук и заключает договора в тех местах где не всегда ловит интернет, добавьте UID.
akryukov
Не все и "по разному" тоже не просто так взялось. Почему вам нужна именно таблица для падежей месяцев? Почему не функция с case when then… end?