image

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

В этой статье я покажу, каким образом такая концепция позволяет быстро и удобно балансировать запись и чтение в базу данных без какого-либо изменения логики работы. Похожий функционал попытались реализовать в современных коммерческих СУБД (в частности, Oracle и Microsoft SQL Server). В конце статьи я покажу, что получилось у них, мягко говоря, не очень.

Описание


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

В функциональной базе данных это будет выглядеть следующим образом:
CLASS Department ‘Отдел’;
name ‘Наименование’ = DATA STRING[100] (Department);

CLASS Employee ‘Сотрудник’;
department ‘Отдел’ = DATA Department (Employee);
salary ‘Зарплата’ =  DATA NUMERIC[10,2] (Employee);

countEmployees ‘Кол-во сотрудников’ (Department d) = 
    GROUP SUM 1 IF department(Employee e) = d;
salarySum ‘Суммарная зарплата’ (Department d) = 
    GROUP SUM salary(Employee e) IF department(e) = d;

SELECT name(Department d), countEmployees(d), salarySum(d);
Сложность выполнения этого запроса в любой СУБД будет эквивалентна O(кол-во сотрудников), так как для этого вычисления нужно просканировать всю таблицу сотрудников, а затем сгруппировать их по отделу. Также будет некоторая небольшая (считаем, что сотрудников гораздо больше чем отделов) добавка в зависимости от выбранного плана O(log кол-ва сотрудников) или O(кол-во отделов) на группировку и прочее.

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

В предложенной реализации функциональная СУБД сформирует один подзапрос, который вычислит нужные значения по отделу, а затем сделает JOIN с таблицей отделов для получения имени. Однако, для каждой функции при объявлении есть возможность задать специальный маркер MATERIALIZED. Система автоматически создаст соответствующее поле под каждую такую функцию. При изменении значения функции будет в той же транзакции изменяться и значение поля. При обращении к этой функции будет идти обращение уже к преподсчитанному полю.

В частности, если поставить MATERIALIZED для функций countEmployees и salarySum, то в таблице со списком отделов добавятся два поля, в которых будут хранится количество сотрудников и их суммарная зарплата. При любом изменении сотрудников, их зарплат или принадлежности к отделам система будет автоматически изменять значения этих полей. Приведенный же выше запрос станет обращаться непосредственно к этим полям и будет выполнен за O(кол-во отделов).

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

Пример:
employeesCount ‘Количество сотрудников с зарплатой > N’ (Department d, NUMERIC[10,2] N) = 
    GROUP SUM salary(Employee e) IF department(e) = d AND salary(e) > N;
Эта функция определена для бесконечного количества значений числа N (например, подходит любое отрицательное значение). Поэтому на нее нельзя поставить MATERIALIZED. Таким образом, это логическое, а не техническое ограничение (то есть не потому, что мы не смогли это реализовать). В остальном — никаких ограничений. Можно использовать группировки, сортировки, AND и OR, PARTITION, рекурсии и т.д.

Например, в задаче 2.2 предыдущей статьи можно поставить MATERIALIZED на обе функции:
bought 'Купил' (Customer c, Product p, INTEGER y) = 
    GROUP SUM sum(Detail d) IF 
        customer(order(d)) = c AND 
        product(d) = p AND 
        extractYear(date(order(d))) = y MATERIALIZED;
rating 'Рейтинг' (Customer c, Product p, INTEGER y) = 
    PARTITION SUM 1 ORDER DESC bought(c, p, y), p BY c, y MATERIALIZED;
SELECT contactName(Customer c), name(Product p) WHERE rating(c, p, 1997) < 3;
Система сама создаст одну таблицу с ключами типов Customer, Product и INTEGER, добавит в нее два поля и будет обновлять в них значения полей при любых изменениях. При дальнейших обращениях к этим функциям не будет происходить их расчет, а будут считываться значения из соответствующих полей.

При помощи этого механизма можно, например, избавляться от в рекурсий (CTE) в запросах. В частности, рассмотрим группы, которые образуют дерево при помощи отношения child/parent (у каждой группы есть ссылка на своего родителя):
parent = DATA Group (Group);
В функциональной базе данных логику рекурсий можно задать следующим образом:
level (Group child, Group parent) = RECURSION 1l IF child IS Group AND parent == child
                                                             STEP 2l IF parent == parent($parent);
isParent (Group child, Group parent) = TRUE IF level(child, parent) MATERIALIZED;
Так как для функции isParent проставлен MATERIALIZED, то под нее будет создана таблица с двумя ключами (группами), в которой поле isParent будет истинным только, если первый ключ является потомком второго. Количество записей в этой таблице будет равно количеству групп, умноженному на среднюю глубину дерева. Если необходимо, например, посчитать количество потомков определенной группы, то можно обращаться к этой функции:
childrenCount (Group g) = GROUP SUM 1 IF isParent(Group child, g);
Никакого CTE в SQL запросе при этом не будет. Вместо этого будет простой GROUP BY.

При помощи этого механизма можно также легко делать денормализацию базы данных при необходимости:
CLASS Order 'Заказ';
date 'Дата' = DATA DATE (Order);

CLASS OrderDetail 'Строка заказа';
order 'Заказ' = DATA Order (OrderDetail);
date 'Дата' (OrderDetail d) = date(order(d)) MATERIALIZED INDEXED;
При обращении к функции date для строки заказа будет идти чтение из таблицы со строками заказов поля, по которому есть индекс. При изменении даты заказа система будет сама автоматически пересчитывать денормализованую дату в строке.

Преимущества


Для чего весь этот механизм нужен? В классических СУБД, без переписывания запросов, разработчик или DBA могут лишь изменять индексы, определять статистику и подсказывать планировщику запросов, как их выполнять (причем HINT'ы есть только в коммерческих СУБД). Как бы они не старались, они не смогут первый запрос в статье выполнить за О (кол-во отделов) без изменения запросов и дописывания триггеров. В предложенной же схеме, на этапе разработки можно не задумываться о структуре хранения данных и о том, какие агрегации использовать. Это все можно спокойно менять на лету уже непосредственно в эксплуатации.

На практике это выглядит следующим образом. Некоторые люди разрабатывают непосредственно логику на основе поставленной задачи. Они не разбираются ни в алгоритмах и их сложности, ни в планах выполнения, ни в типах join’ов, ни в любой другой технической составляющей. Эти люди — скорее бизнес-аналитики, чем разработчики. Затем, все это идет в тестирование или эксплуатацию. Включается логирование длительных запросов. Когда обнаруживается долгий запрос, то уже другими людьми (более техническими — по сути DBA) принимается решение о включении MATERIALIZED на некоторой промежуточной функции. Тем самым немного замедляется запись (так как требуется обновление дополнительного поля в транзакции). Однако, значительно ускоряется не только этот запрос, но и все другие, которые используют эту функцию. При этом принятие решения о том, какую именно функцию материализовать принимается относительно несложно. Два основных параметра: кол-во возможных входных значений (именно столько записей будет в соответствующей таблице), и насколько часто она используется в других функциях.

Аналоги


В современных коммерческих СУБД есть схожие механизмы: MATERIALIZED VIEW с FAST REFRESH (Oracle) и INDEXED VIEW (Microsoft SQL Server). В PostgreSQL MATERIALIZED VIEW не умеет обновляться в транзакции, а только по запросу (да еще с совсем жесткими ограничениями), так что его не рассматриваем. Но у них есть несколько проблем, что значительно ограничивает их использование.

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

Во-вторых, у них есть огромное количество ограничений:

Oracle
5.3.8.4 General Restrictions on Fast Refresh

The defining query of the materialized view is restricted as follows:
  • The materialized view must not contain references to non-repeating expressions like SYSDATE and ROWNUM.
  • The materialized view must not contain references to RAW or LONG RAW data types.
  • It cannot contain a SELECT list subquery.
  • It cannot contain analytic functions (for example, RANK) in the SELECT clause.
  • It cannot reference a table on which an XMLIndex index is defined.
  • It cannot contain a MODEL clause.
  • It cannot contain a HAVING clause with a subquery.
  • It cannot contain nested queries that have ANY, ALL, or NOT EXISTS.
  • It cannot contain a [START WITH …] CONNECT BY clause.
  • It cannot contain multiple detail tables at different sites.
  • ON COMMIT materialized views cannot have remote detail tables.
  • Nested materialized views must have a join or aggregate.
  • Materialized join views and materialized aggregate views with a GROUP BY clause cannot select from an index-organized table.

5.3.8.5 Restrictions on Fast Refresh on Materialized Views with Joins Only

Defining queries for materialized views with joins only and no aggregates have the following restrictions on fast refresh:
  • All restrictions from "General Restrictions on Fast Refresh".
  • They cannot have GROUP BY clauses or aggregates.
  • Rowids of all the tables in the FROM list must appear in the SELECT list of the query.
  • Materialized view logs must exist with rowids for all the base tables in the FROM list of the query.
  • You cannot create a fast refreshable materialized view from multiple tables with simple joins that include an object type column in the SELECT statement.

Also, the refresh method you choose will not be optimally efficient if:
  • The defining query uses an outer join that behaves like an inner join. If the defining query contains such a join, consider rewriting the defining query to contain an inner join.
  • The SELECT list of the materialized view contains expressions on columns from multiple tables.

5.3.8.6 Restrictions on Fast Refresh on Materialized Views with Aggregates

Defining queries for materialized views with aggregates or joins have the following restrictions on fast refresh:

Fast refresh is supported for both ON COMMIT and ON DEMAND materialized views, however the following restrictions apply:
  • All tables in the materialized view must have materialized view logs, and the materialized view logs must:
    • Contain all columns from the table referenced in the materialized view.
    • Specify with ROWID and INCLUDING NEW VALUES.
    • Specify the SEQUENCE clause if the table is expected to have a mix of inserts/direct-loads, deletes, and updates.

  • Only SUM, COUNT, AVG, STDDEV, VARIANCE, MIN and MAX are supported for fast refresh.
  • COUNT(*) must be specified.
  • Aggregate functions must occur only as the outermost part of the expression. That is, aggregates such as AVG(AVG(x)) or AVG(x)+ AVG(x) are not allowed.
  • For each aggregate such as AVG(expr), the corresponding COUNT(expr) must be present. Oracle recommends that SUM(expr) be specified.
  • If VARIANCE(expr) or STDDEV(expr) is specified, COUNT(expr) and SUM(expr) must be specified. Oracle recommends that SUM(expr *expr) be specified.
  • The SELECT column in the defining query cannot be a complex expression with columns from multiple base tables. A possible workaround to this is to use a nested materialized view.
  • The SELECT list must contain all GROUP BY columns.
  • The materialized view is not based on one or more remote tables.
  • If you use a CHAR data type in the filter columns of a materialized view log, the character sets of the master site and the materialized view must be the same.
  • If the materialized view has one of the following, then fast refresh is supported only on conventional DML inserts and direct loads.
    • Materialized views with MIN or MAX aggregates
    • Materialized views which have SUM(expr) but no COUNT(expr)
    • Materialized views without COUNT(*)

    Such a materialized view is called an insert-only materialized view.
  • A materialized view with MAX or MIN is fast refreshable after delete or mixed DML statements if it does not have a WHERE clause.
    The max/min fast refresh after delete or mixed DML does not have the same behavior as the insert-only case. It deletes and recomputes the max/min values for the affected groups. You need to be aware of its performance impact.
  • Materialized views with named views or subqueries in the FROM clause can be fast refreshed provided the views can be completely merged. For information on which views will merge, see Oracle Database SQL Language Reference.
  • If there are no outer joins, you may have arbitrary selections and joins in the WHERE clause.
  • Materialized aggregate views with outer joins are fast refreshable after conventional DML and direct loads, provided only the outer table has been modified. Also, unique constraints must exist on the join columns of the inner join table. If there are outer joins, all the joins must be connected by ANDs and must use the equality (=) operator.
  • For materialized views with CUBE, ROLLUP, grouping sets, or concatenation of them, the following restrictions apply:
    • The SELECT list should contain grouping distinguisher that can either be a GROUPING_ID function on all GROUP BY expressions or GROUPING functions one for each GROUP BY expression. For example, if the GROUP BY clause of the materialized view is "GROUP BY CUBE(a, b)", then the SELECT list should contain either "GROUPING_ID(a, b)" or "GROUPING(a) AND GROUPING(b)" for the materialized view to be fast refreshable.
    • GROUP BY should not result in any duplicate groupings. For example, "GROUP BY a, ROLLUP(a, b)" is not fast refreshable because it results in duplicate groupings "(a), (a, b), AND (a)".

5.3.8.7 Restrictions on Fast Refresh on Materialized Views with UNION ALL

Materialized views with the UNION ALL set operator support the REFRESH FAST option if the following conditions are satisfied:
  • The defining query must have the UNION ALL operator at the top level.

    The UNION ALL operator cannot be embedded inside a subquery, with one exception: The UNION ALL can be in a subquery in the FROM clause provided the defining query is of the form SELECT * FROM (view or subquery with UNION ALL) as in the following example:
    CREATE VIEW view_with_unionall AS
    (SELECT c.rowid crid, c.cust_id, 2 umarker
     FROM customers c WHERE c.cust_last_name = 'Smith'
     UNION ALL
     SELECT c.rowid crid, c.cust_id, 3 umarker
     FROM customers c WHERE c.cust_last_name = 'Jones');
    
    CREATE MATERIALIZED VIEW unionall_inside_view_mv
    REFRESH FAST ON DEMAND AS
    SELECT * FROM view_with_unionall;
    
    Note that the view view_with_unionall satisfies the requirements for fast refresh.
  • Each query block in the UNION ALL query must satisfy the requirements of a fast refreshable materialized view with aggregates or a fast refreshable materialized view with joins.

    The appropriate materialized view logs must be created on the tables as required for the corresponding type of fast refreshable materialized view.
    Note that the Oracle Database also allows the special case of a single table materialized view with joins only provided the ROWID column has been included in the SELECT list and in the materialized view log. This is shown in the defining query of the view view_with_unionall.
  • The SELECT list of each query must include a UNION ALL marker, and the UNION ALL column must have a distinct constant numeric or string value in each UNION ALL branch. Further, the marker column must appear in the same ordinal position in the SELECT list of each query block. See "UNION ALL Marker and Query Rewrite" for more information regarding UNION ALL markers.
  • Some features such as outer joins, insert-only aggregate materialized view queries and remote tables are not supported for materialized views with UNION ALL. Note, however, that materialized views used in replication, which do not contain joins or aggregates, can be fast refreshed when UNION ALL or remote tables are used.
  • The compatibility initialization parameter must be set to 9.2.0 or higher to create a fast refreshable materialized view with UNION ALL.

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

Microsoft SQL Server

Additional Requirements


In addition to the SET options and deterministic function requirements, the following requirements must be met:
  • The user that executes CREATE INDEX must be the owner of the view.
  • When you create the index, the IGNORE_DUP_KEY option must be set to OFF (the default setting).
  • Tables must be referenced by two-part names, schema.tablename in the view definition.
  • User-defined functions referenced in the view must be created by using the WITH SCHEMABINDING option.
  • Any user-defined functions referenced in the view must be referenced by two-part names, <schema>.<function>.
  • The data access property of a user-defined function must be NO SQL, and external access property must be NO.
  • Common language runtime (CLR) functions can appear in the select list of the view, but cannot be part of the definition of the clustered index key. CLR functions cannot appear in the WHERE clause of the view or the ON clause of a JOIN operation in the view.
  • CLR functions and methods of CLR user-defined types used in the view definition must have the properties set as shown in the following table.
    Property Note
    DETERMINISTIC = TRUE Must be declared explicitly as an attribute of the Microsoft .NET Framework method.
    PRECISE = TRUE Must be declared explicitly as an attribute of the .NET Framework method.
    DATA ACCESS = NO SQL Determined by setting DataAccess attribute to DataAccessKind.None and SystemDataAccess attribute to SystemDataAccessKind.None.
    EXTERNAL ACCESS = NO This property defaults to NO for CLR routines.
  • The view must be created by using the WITH SCHEMABINDING option.
  • The view must reference only base tables that are in the same database as the view. The view cannot reference other views.
  • The SELECT statement in the view definition must not contain the following Transact-SQL elements:
    COUNT ROWSET functions (OPENDATASOURCE, OPENQUERY, OPENROWSET, AND OPENXML) OUTER joins (LEFT, RIGHT, or FULL)
    Derived table (defined by specifying a SELECT statement in the FROM clause) Self-joins Specifying columns by using SELECT * or SELECT <table_name>.*
    DISTINCT STDEV, STDEVP, VAR, VARP, or AVG Common table expression (CTE)
    float1, text, ntext, image, XML, or filestream columns Subquery OVER clause, which includes ranking or aggregate window functions
    Full-text predicates (CONTAINS, FREETEXT) SUM function that references a nullable expression ORDER BY
    CLR user-defined aggregate function TOP CUBE, ROLLUP, or GROUPING SETS operators
    MIN, MAX UNION, EXCEPT, or INTERSECT operators TABLESAMPLE
    Table variables OUTER APPLY or CROSS APPLY PIVOT, UNPIVOT
    Sparse column sets Inline (TVF) or multi-statement table-valued functions (MSTVF) OFFSET
    CHECKSUM_AGG

    1 The indexed view can contain float columns; however, such columns cannot be included in the clustered index key.
  • If GROUP BY is present, the VIEW definition must contain COUNT_BIG(*) and must not contain HAVING. These GROUP BY restrictions are applicable only to the indexed view definition. A query can use an indexed view in its execution plan even if it does not satisfy these GROUP BY restrictions.
  • If the view definition contains a GROUP BY clause, the key of the unique clustered index can reference only the columns specified in the GROUP BY clause.

Здесь видно, что индусов не привлекали, так как они решили делать по схеме “сделаем мало, но хорошо”. То есть у них мин на поле побольше, но их расположение прозрачнее. Больше всего огорчает вот это ограничение:
The view must reference only base tables that are in the same database as the view. The view cannot reference other views.

В нашей терминологии это означает, что функция не может обращаться к другой материализованной функции. Это рубит всю идеологию на корню.
Также вот это ограничение (и дальше по тексту) очень сильно уменьшает варианты использования:
The SELECT statement in the view definition must not contain the following Transact-SQL elements:
COUNT ROWSET functions (OPENDATASOURCE, OPENQUERY, OPENROWSET, AND OPENXML) OUTER joins (LEFT, RIGHT, or FULL)
Derived table (defined by specifying a SELECT statement in the FROM clause) Self-joins Specifying columns by using SELECT * or SELECT <table_name>.*
DISTINCT STDEV, STDEVP, VAR, VARP, or AVG Common table expression (CTE)
float1, text, ntext, image, XML, or filestream columns Subquery OVER clause, which includes ranking or aggregate window functions
Full-text predicates (CONTAINS, FREETEXT) SUM function that references a nullable expression ORDER BY
CLR user-defined aggregate function TOP CUBE, ROLLUP, or GROUPING SETS operators
MIN, MAX UNION, EXCEPT, or INTERSECT operators TABLESAMPLE
Table variables OUTER APPLY or CROSS APPLY PIVOT, UNPIVOT
Sparse column sets Inline (TVF) or multi-statement table-valued functions (MSTVF) OFFSET
CHECKSUM_AGG

Запрещены OUTER JOINS, UNION, ORDER BY и прочие. Возможно проще было указать, что можно использовать, чем то, что нельзя. Список вероятно был бы гораздо меньше.

Подводя итог: огромный набор ограничений в каждой (замечу коммерческой) СУБД vs никаких (за исключением одного логического, а не технического) в LGPL технологии. Однако следует отметить, что реализовать этот механизм в реляционной логике несколько сложнее, чем в описанной функциональной.

Реализация


Как это работает? В качестве «виртуальной машины» используется PostgreSQL. Внутри есть сложный алгоритм, который занимается построением запросов. Вот исходный код. И там не просто большой набор эвристик с кучей if’ов. Так что, если есть пару месяцев на изучение, то можете попробовать разобраться в архитектуре.

Работает ли это эффективно? Достаточно эффективно. К сожалению, доказать это тяжело. Могу лишь сказать, что если рассмотреть тысячи запросов, которые есть в больших приложениях, то в среднем они эффективнее, чем у хорошего разработчика. Отличный SQL-программист может написать любой запрос эффективнее, но на тысяче запросов у него просто не будет ни мотивации, ни времени это делать. Единственное, что я могу сейчас привести как доказательство эффективности — это то, что на базе платформы, построенной на этой СУБД работают несколько проектов ERP-системы, в которых есть тысячи различных MATERIALIZED функций, с тысячей пользователей и террабайтными базами с сотнями миллионов записей, работающих на обычном двух-процессорном сервере. Впрочем, любой желающий может проверить/опровергнуть эффективность, скачав платформу и PostgreSQL, включив логирование SQL-запросов и попробовав изменять там логику и данные.

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

Комментарии (96)


  1. GlukKazan
    18.07.2019 10:55

    Во-первых, можно включить материализацию только, если у вас уже был создан обычный VIEW.
    Нет, не так. Материализованные представления с обычными вообще никак не связаны. Для создания мат.представления никакого «обычного VIEW» не требуется. И нельзя создать «обычное VIEW» а потом как-то его материализовать. View и Materialized View это просто совсем разные объекты. Оптимизатор умеет материализовывать части запросов, по ходу дела, да, но это немного другая история.
    Иначе придется переписывать остальные запросы на обращение к вновь созданному представлению

    Тоже отсебятина. Oracle уже давно умеет переписывать запросы «на лету». Правда механизм это довольно капризный и работает только при наличии прямых рук.

    Замечание про «тысячу индусов» оставим на вашей совести, она у вас видимо большая.


    1. CrushBy Автор
      18.07.2019 11:05

      Я имел ввиду немного другое. Давайте на примере. Предположим, у вас не хранится количество сотрудников в отделе. И при этом есть сто запросов (отчеты всякие и прочие), которые для подсчета количества сотрудников делают это подзапросом. Если бы изначально вы создали VIEW, а затем во всех этих ста запросах обращались бы к нему, то достаточно было бы изменить VIEW на MATERIALIZED VIEW, и они начали бы обращаться уже к преподсчитанным данным. Если же эти сто запросов в явную высчитывали количество через подзапросы, то вам бы создание MATERIALIZED VIEW не помогло бы.
      Но я допускаю, что возможно я не в курсе последних версий Oracle.

      Oracle уже давно умеет переписывать запросы «на лету»

      А можете дать, пожалуйста, ссылку на этот механизм — я хочу почитать про него.


      1. GlukKazan
        18.07.2019 11:15

        Вот здесь например. Но более понятно написано у Тома Кайта в «Oracle для профессионалов» (самая первая книжка и легко гуглится). В общем, если кратко, Materialized View появились в Oracle в три шага.

        1. Просто snapshot-ы с ручным обновлением, к которым можно обращаться как к таблицам
        2. Обновление по commit-у, в том числе запросов с группировками и агрегациями (здесь куча ограничений, да)
        3. Оптмизатор научился догадываться сам, где можно переписать запрос так, чтобы он обращался к мат.представлению (здесь ещё ограничения и кучерявая настройка)

        В общем, из-за 3 пункта не надо переписывать «кучу запросов». В 9-ой версии это уже всё было (если не в 8i), так что довольно древние фичи.


        1. CrushBy Автор
          18.07.2019 11:26

          Спасибо за ссылку. Я только не понял: он с FAST REFRESH работает? Почему в их первом же примере не включен FAST REFRESH? Ведь нельзя же переписывать запросы, если данные в этом VIEW «несвежие»: будет другой результат. Более того, если в одной транзакции идет запись, то после изменения данных, которые затрагивают VIEW нужно их учитывать во последующих запросах в транзакции.


          1. GlukKazan
            18.07.2019 11:32

            Вот с этим и связаны все причуды тонкой настройки. В общем, там есть уровни «доверия» к данным и если оптимизатор посчитает данные не свежими, то не станет их использовать. С FAST REFRESH переписывание запросов ортогонально, то есть оптимизатор может работать с любыми мат.представлениями. Гляньте Кайта, если будет возможность, книжка полезная и понятная.


            1. CrushBy Автор
              18.07.2019 11:38

              Я почитаю конечно Кайта, но на это, к сожалению, уйдет время, и я не смогу вам сейчас отвечать по этой книжке.

              не станет их использовать

              И тогда начнет все рассчитывать заново? А если там материализация из сотни миллионов записей?
              Простите, но ваше описание того, как работает Oracle сводится к тому, что там есть какой-то механизм, но использовать его крайне опасно, так как подводных мин — огромное число. Для многих людей это равносильно тому, что лучше вообще не использовать (и все мои знакомые Oracle разработчики по этой причиние никогда его не используют).


              1. GlukKazan
                18.07.2019 11:56

                Точно также как я не готов обсуждать концепции вашего продукта, вы не готовы обсуждать концепции Oracle (и не надо этого делать). Спорный абзац в статье рекомендую переписать или убрать вообще. Оптимизатор не будет ничего «рассчитывать заново» — это не его задача. Он просто определяет может использовать заранее рассчитанные данные или нет, в соответствии с заданными настройками. И я не давал вам описания того «как работает Oracle». Я рассказывал только о его мат.представлениях да и то, весьма поверхностно. Хотите подробностей — читайте документацию или Кайта.


                1. NitroJunkie
                  18.07.2019 12:08
                  -2

                  вы не готовы обсуждать концепции Oracle (и не надо этого делать)

                  Лично я очень даже готов. Но почему-то очень много людей работающих с СУБД, на конкретные вопросы начинают отсылать к книжкам, со словами там все есть. А это книжки на 200 страниц. И делать так на мой взгляд в обсуждении некорректно. Потому как я же не напишу вам, прочитал 200 страниц ничего не нашел, вы все врете.

                  Ну и смущает чаще другое. Очень часто возникает ощущение, что люди сами не знают как, то что они обсуждают, работает и работает ли вообще. При этом эти же люди ратуют за то что SQL запросы должны быть прозрачны и предсказуемы (причем не их выполнение а сами запросы), хотя как именно они выполняются никто не знает.


                  1. GlukKazan
                    18.07.2019 12:14

                    Лично я очень даже готов

                    Нет, не готовы, поскольку не знаете концепций Oracle, про которые пишите. В результате, вы пишите ерунду, которую кто-то, также не знакомый с этими концепциями, может принять на веру. Кстати, глава про мат.представления много меньше 200 страниц. Найдите время, почитайте.


                    1. NitroJunkie
                      18.07.2019 15:23

                      Прочитал всю 13 главу. Как я и думал. Хотите в OLTP использовать нужен REFRESH ON COMMIT. В остальных случаях данные тупо будут неправильные.

                      Собственно он сам пишет, что в OLTP использовать материализованные представления нельзя, но обоснует это почему-то большим количеством возникающих update conflict'ов. То есть понятно что если материализовать прибыль предприятия, вся база будет в update conflict'ах. Но если, к примеру, остаток на складе материализовать то все же будет ок. Более того так все и делают, только руками.

                      Про переписывание там вообще оригинально написано, как будто Oracle реально текст запроса сравнивает (то есть алиас изменил все уже не используется), но по факту простые случаи он действительно вроде разбирает (хотя учитывая ограничения на REFRESH ON COMMIT там все случаи простые).

                      В любом случае, ничего нового, в контексте того что мы тут обсуждают я не узнал.


                1. CrushBy Автор
                  18.07.2019 12:49

                  Спорный абзац в статье рекомендую переписать или убрать вообще

                  Я переписал его с учетом ваших замечаний:
                  но многие запросы их не всегда используют, а высчитывают заново

                  Так будет точнее?


  1. NitroJunkie
    18.07.2019 11:05

    Oracle уже давно умеет переписывать запросы «на лету»

    Вы имеете ввиду находить существующие MATERIALIZED VIEW в базе и прямо внутри какого-то другого запроса «выделять» часть join'ов / union'ов из этого MATERIALIZED VIEW и заменять эту часть на join 'ы/ union'ы с MATERIALIZED VIEW? И я так понимаю MATERIALIZED VIEW должен естественно быть FAST REFRESH? И как это все в транзакции главное работает, когда часть данных уже поменялось, а MATERIALIZED VIEW еще не обновился?


    1. GlukKazan
      18.07.2019 11:26

      На лету ничего находить не надо. Все связи между объектами Oracle постоянно хранятся в метаданных. На счёт join-ов и тем более union-ов вряд ли. Мат. представления больше про OLAP и группировки. Если обновление по commit-у, то с транзакциями всё замечательно работает, поскольку для других транзакций это выглядит как одновременное изменение агрегатов и исходных таблиц. Другой вопрос, что на обновление по commit-у имеется куча ограничений и не везде оно возможно. Мат. представления, как и большинство механизмов в Oracle создавались для решения конкретных задач, а не для сферической идеи «материализовать всё что можно».


      1. CrushBy Автор
        18.07.2019 11:33

        сферической идеи «материализовать всё что можно»

        Прошу прощения за замечание, но идея — не сферическая, а имеет вполне конкретное практическое применение. Позволяет «на лету» балансировать запись и чтение.


        1. GlukKazan
          18.07.2019 11:35

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


      1. NitroJunkie
        18.07.2019 11:38

        Мат. представления больше про OLAP и группировки.

        Тут скорее правильнее говорить про OLAP. Группировок и в OLTP вагон и маленькая тележка, то есть это общее понятие и в OLAP и в OLTP.
        Мат. представления, как и большинство механизмов в Oracle создавались для решения конкретных задач, а не для сферической идеи «материализовать всё что можно».

        Если я правильно понимаю, мат. представления в Oracle создавались именно для аналитики (OLAP). FAST REFRESH прилепили потом именно для OLTP, но что-то пошло не так, и по факту они только SELECT SUM() GROUP BY умеют делать. А остальное -OUTER JOIN, UNION'ы, analytic function, recursive cte они так и не научились делать.

        Собственно, если я правильно понял, в статье речь шла про OLTP, соответственно «переписывать», если и можно то, только FAST REFRESH view, а это получается ограничения в квадрате. То есть ограничения FAST REFRESH умножить на всякие странные ограничения вроде
        ENFORCED
        This is the default mode. The optimizer only uses fresh data from the materialized views and only use those relationships that are based on ENABLED VALIDATED primary, unique, or foreign key constraints.


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


        1. GlukKazan
          18.07.2019 12:02

          Если я правильно понимаю, мат. представления в Oracle создавались именно для аналитики (OLAP). FAST REFRESH прилепили потом именно для OLTP, но что-то пошло не так, и по факту они только SELECT SUM() GROUP BY умеют делать. А остальное -OUTER JOIN, UNION'ы, analytic function, recursive cte они так и не научились делать.

          Послушайте, не надо ничего домысливать. Вы прекрасно начали абзац, но затем у вас опять «что-то пошло не так». Все ограничения мат.представлений были прекрасно известны на момент разработки их архитектуры, а вовсе не проявились в процессе их разработки, как вы пытаетесь это показать. Вы занимаетесь агрессивным маркетингом своего продукта — прекрасно, но зачем поливать грязью другие продукты? При всех (хорошо известных) ограничениях мат.представлений, на текущий момент, это лучшее решение, на фоне предоставляемых другими СУБД. И ещё раз. Я не вашу статью обсуждаю, а всего один, вполне конкретный абзац.


          1. NitroJunkie
            18.07.2019 12:22

            Во-первых, я не хочу обидеть вас или Oracle (поверьте он не нуждается ни вашей, ни чьей либо еще защите). Мне самому не очень понравилась эта фраза про тысячу индусов, скорее всего причина все-таки другая — а именно legacy. Но не суть.

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

            То есть по вашему все эти ограничения они вставили специально?
            прекрасно, но зачем поливать грязью другие продукты?

            А где вы увидели поливание грязью. Oracle — отличный продукт, для реализации ACID, определения порядков, типов join, паралеллизма и т.п. И скорее всего лучший из существующих на рынке. Но реализовать по настоящему крутые возможности у них по разным причинам не получилось. Но это не повод, что у других это не получится, и тем более не повод не указывать на отсутствие этих возможностей у других, даже если это Oracle.


            1. GlukKazan
              18.07.2019 12:32

              поверьте он не нуждается ни вашей, ни чьей либо еще защите

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

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

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

              Вы здесь свой продукт собрались продвигать или обсуждать недостатки Oracle? Продвигаете — продвигайте, продукты других компаний не трогайте.


              1. NitroJunkie
                18.07.2019 12:47

                Я защищаю людей, которые могут прочитать в вашей статье ерунду про Oracle.

                Честно не могу найти эту главу Том Кайта про материализованные представления (уже полчаса ищу эту книжку), может поделитесь в личку где ее можно достать?
                Потому как во всяком случае, вот здесь, materialized view описывается именно как view, который просто хранится как таблица:
                docs.oracle.com/cd/E11882_01/server.112/e40540.pdf
                Да реализация у него естественно отличается от обычного VIEW, добавляются всякие materialized view logs в master таблицы, для обеспечения инкрементального обновления (что естественно для не FAST REFRESH view), но логически тоже самое.
                Да, без них предложенный механизм не смог бы работать. То есть они (индусы) может быть и рады бы обойтись без ограничений, но это был бы совсем другой механизм, до которого пока никто (включая корейцев и китайцев) не додумался.

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

                А если туда добавить, что механизм переписывания запросов есть в некоторых СУБД (кстати в MSSQL я помню он вроде тоже есть), но к ограничениям FAST REFRESH добавляется еще список ограничений, что делает этот механизм нежизнеспособным, по вашему будет корректно?
                Продвигаете — продвигайте, продукты других компаний не трогайте.

                Это как вы себе представляете? То есть если Tesla продвигает электромобили, им нельзя говорить что бензиновые загрязняют окружающую среду?


                1. GlukKazan
                  18.07.2019 12:59
                  +1

                  Да реализация у него естественно отличается от обычного VIEW

                  View — это просто SQL-запрос, сохранённый в словаре с каким-то именем. Materialized View — по сути таблица, с сегментом данных в тэйблспейсе и некоторыми дополнительными возможностями. И то и другое можно использовать в SQL-запросах, как и табличные функции, например, но все три — это совсем разные объекты.
                  что делает этот механизм нежизнеспособным

                  Если вы к этому добавите «по нашему мнению», будет вполне здоровая субъективная критика. А вот абзац с индусами так просто не лечится.
                  То есть если Tesla продвигает электромобили, им нельзя говорить что бензиновые загрязняют окружающую среду?

                  Ой, не кивайте на других. То что делает Тесла — на совести Теслы, то что делаете вы — на вашей совести.


                  1. NitroJunkie
                    18.07.2019 14:04

                    View — это просто SQL-запрос, сохранённый в словаре с каким-то именем. Materialized View — по сути таблица, с сегментом данных в тэйблспейсе и некоторыми дополнительными возможностями. И то и другое можно использовать в SQL-запросах, как и табличные функции, например, но все три — это совсем разные объекты.

                    Так я вроде так и написал. Логически (с точки зрения использования) Materialized View это тоже самое, что View. Физически (с точки зрения реализации) да отличается, но в этом и фокус абстрагирования, что субьекту все равно что там внутри.
                    Если вы к этому добавите «по нашему мнению», будет вполне здоровая субъективная критика. А вот абзац с индусами так просто не лечится.

                    Ну там уже поменяли на «не всегда». Хотя версия, которую я предлагал (с вашей поправкой) мне кажется лучше, но пусть будет не всегда.
                    Ой, не кивайте на других. То что делает Тесла — на совести Теслы, то что делаете вы — на вашей совести.

                    А как вообще можно продвигать на рынок что-то не сравнивая с конкурентами. Если первый же вопрос — зачем нам еще один новый продукт.


                    1. GlukKazan
                      18.07.2019 14:17
                      +1

                      Понимаете в чём дело, вы сравниваете ERP и RDBMS. Ну это как шурупы с гвоздями. И делаете это, несколько экспрессивно.


                      1. NitroJunkie
                        18.07.2019 14:41

                        Это да. Если бы не одно но, что у ERP и RDBMS (во всяком случае больших и сложных) очень сильно пересекаются области применения (по моему во всяком случае опыту). Собственно RDBMS (имеется ввиду написание на хранимках, PL/SQL и т.п., а не просто как подложка под ORM) сейчас по факту чаще всего и применяются там где ERP по производительности не вытягивает (банки и ритейл).

                        Не на сайтиках же Oracle используют.


                        1. lair
                          18.07.2019 15:06
                          +1

                          Собственно RDBMS (имеется ввиду написание на хранимках, PL/SQL и т.п., а не просто как подложка под ORM) сейчас по факту чаще всего и применяются там где ERP по производительности не вытягивает

                          Гм. Вот есть функция ERP "загрузка данных о заказах из удаленной системы". Ну, в смысле, есть API endpoint, куда удаленная система стучится, чтобы залить свою информацию о заказах в ERP, где с ней уже работают пользователи. Предположим, "ERP по производительности не вытягивает", то есть этот endpoint не отрабатывает за нужное время. И как вы тут предлагаете "использовать RDBMS"?


                          Мне вообще кажется, что говорить "у ERP и RDBMS [...] очень сильно пересекаются области применения" — это что-то очень странное, потому что у ERP — это система, которой пользуются пользователи, через UI, она (обычно) выражена в терминах их пользовательских задач, а RDBMS — это нечто, чем пользователи (обычно) не пользуются напрямую, потому что она не выражена в терминах их задач.


                          1. NitroJunkie
                            18.07.2019 15:10

                            Я сейчас не ERP-системы, а ERP-платформы (SAP, Axapta, 1C) имел ввиду.


                            1. GlukKazan
                              18.07.2019 15:12

                              Они работают поверх RDBMS (как правило). Из чего следует, что они «пересекаются» с RDBMS?


                              1. NitroJunkie
                                18.07.2019 15:25

                                Давайте так, чтобы не уходить в абстрактные рассуждения, приведу пример. OEBS продукт знаете. А SAP ERP. Вы же понимаете, что технически они полные конкуренты. И один считай на Oracle RDBMS + Oracle Forms (или что у них там сейчас для UI), второй на SAP Platform.


                                1. GlukKazan
                                  18.07.2019 19:42
                                  +1

                                  Давайте так. Я сейчас выскажу известную, но не очень афишируемую мысль. У Oracle конечно есть ERP (и скорее всего не один), но он меня мало интересует. Единственное, что есть хорошего у Oracle это базы данных. ERP от Oracle, Oracle Forms, APEX и прочее — всё это для мазохистов.


                                  1. NitroJunkie
                                    18.07.2019 19:55
                                    -2

                                    Давайте так. В условиях когда даже при использовании ERP платформ приходится отказываться от ORM, автоматических блокировок и других абстракций этих ERP платформ в пользу SQL ради масштабируемости, а SQL используется только для ACID и порядка / типов JOIN (без materialized view, predicate pushdown, trigger'ов и т.п. так как все они поддерживаются в каких-то частных случаях или с непонятной производительностью) любая ERP, что 1С ERP, что SAP ERP — это лютый ад (там от полотен императивного кода по генерации SQL запросов кровь из глаз идет). А когда все вокруг мазохисты — no one is. Так что сравнение вполне корректное.


                                    1. GlukKazan
                                      18.07.2019 20:09
                                      +1

                                      Вот опять я не улавливаю вашей логической цепочки. Допустим, я согласен с тем что все ERP — ад. Как из этого следует, что сравнивать возможности RDBMS с ERP корректно? И при чём тут вообще ORM? Вы ведь про Materialized View говорили? Так вот, возможно для вас это сюрприз, но… они не в Oracle Forms.


                                      1. NitroJunkie
                                        18.07.2019 20:49

                                        Смотрите, я сказал, что OEBS (сделан только с использованием RDBMS + UI в виде Oracle Forms) и SAP ERP (сделан с использованием ERP платформы + RDBMS внизу) прямые конкуренты. Вы сказали что OEBS — для мазохистов и нельзя так сравнивать, я ответил что это не аргумент. Итого возвращаемся к изначальному тезису. OEBS vs SAP ERP. В OEBS — бизнес-логика сделана в RDBMS, в SAP ERP — в ERP платформе (которая впрочем тоже юзает RDBMS но уже в меньшей степени). Соответственно они обе используются для решения одной задачи (программирования бизнес-логики), то есть конкуренты, то есть сравнение корректно.


                                        1. GlukKazan
                                          18.07.2019 21:13

                                          Вы сказали что OEBS — для мазохистов и нельзя так сравнивать

                                          Неа. Я сказал (и продолжаю говорить) что возможности ERP и RDBMS нельзя сравнивать. Вообще говоря, это никак не связано с OEBS (он мне вообще никак не интересен). Так что нет, не возвращаемся. Статья посвящена мат. представлениям. Покажите мне их в OEBS или SAP ERP, тогда «вернёмся».


                                          1. NitroJunkie
                                            18.07.2019 21:24

                                            Понимаете в чём дело, вы сравниваете ERP и RDBMS. Ну это как шурупы с гвоздями. И делаете это, несколько экспрессивно.

                                            Вообще это вы первые сказали про сравнение ERP и RDBMS (я если честно в тот момент не понял причем тут ERP, но раз вы спросили). А слово ERP первый раз до этого встречалось только в статье в качестве примера проектов реализованных с использованием подходов прозрачной материализации функций / view. Хотя кроме ERP на ней и CRM и BPM и ECM проекты реализовывались и проблема прозрачной материализации была везде (балансирование записи и чтения это вообще универсальная проблема).


                                            1. GlukKazan
                                              18.07.2019 21:35

                                              То есть вы сделали ещё одну RDBMS, Да?


                                              1. NitroJunkie
                                                18.07.2019 21:45
                                                -2

                                                Я бы сказал мы сделали надстройку над RDBMS. Такую Turbo-RDBMS на стероидах: с представлениями с возможностью материализаций, constraint'ов и trigger'ов без ограничений, динамической физической моделью, более понятной функциональной логикой (вместо реляционной), с очень умным оптимизатором, ну и с логикой представлений, метапрограммированием, явной типизацией, модульностью и кучей чего еще в комплекте.


                                                1. GlukKazan
                                                  18.07.2019 21:51
                                                  +1

                                                  Вы сделали конструктор для разработки ERP и CRM, так? Что-то вроде OEBS или 1С? Ну так и сравнивайте свой продукт с OEBS-ом. Зачем вы пытаетесь сравнивать его с Oracle Database? Turbo RDBMS вместе со стероидами — это ваш маркетинг. Это почти также смешно как термин «постреляционная» (тоже есть такая линейка маркетинга). Перестаньте сравнивать несравнимое.


                                                  1. NitroJunkie
                                                    18.07.2019 22:06
                                                    -1

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

                                                    Фактически продукт в статье решает те же задачи, что и связка Oracle Database + Oracle Forms. Как впрочем и SAP/Axapta/1C. Он обобщает и то и другое, поэтому его можно сравнивать и с тем и другим.

                                                    Хотя если так не подходит, его можно использовать / рассматривать чисто как DBMS и соответственно сравнивать чисто с RDBMS (которую впрочем lsFusion использует как более низкоуровневую виртуальную машину). Ну то есть это как грубо говоря C и Assembler.


                                                    1. GlukKazan
                                                      18.07.2019 22:30
                                                      +1

                                                      Только вот роль Oracle в вашей связке выполняет PostgreSQL? Я ничего не путаю? Так что нет. Ваш продукт не DBMS. он решает совсем другие задачи. Кстати, кто вам сказал, что задача RDBMS «разработка любых информационных систем»? Вас обманули. Задача DBMS — надёжное и эффективное хранение данных.


                                                      1. NitroJunkie
                                                        18.07.2019 22:35

                                                        Задача DBMS — надёжное и эффективное хранение данных.

                                                        А зачем тогда туда добавили хранимые процедуры, триггерах, view и вообще эти две буквы PL? То есть почему язык PL/SQL, а не SQL?

                                                        То есть то что на Oracle целые АБС и тот же OEBS, или Oracle Retail реализованы это не считается?


                                                        1. lair
                                                          19.07.2019 00:55

                                                          Затем, что долгое время считалось, что данные надо обрабатывать как можно ближе к месту их хранения.


                                                        1. GlukKazan
                                                          19.07.2019 09:03

                                                          А зачем тогда туда добавили хранимые процедуры, триггерах, view и вообще эти две буквы PL? То есть почему язык PL/SQL, а не SQL?

                                                          Вы не поверите, затем чтобы решить задачу эффективного хранения данных. Впрочем, товарищ lair, чуть выше, об этом уже сказал. Представьте, что у вас есть 100500 записей, которые надо нетривиально обработать. Что дешевле: выкачать их туда где есть годный ЯП и обработать там или обработать прям в базе и отдать клиенту небольшой результат? Кстати, в такой модной технологии как Hadoop, идея обработки данных по месту их размещения всё ещё в тренде.


                                                          1. NitroJunkie
                                                            19.07.2019 09:52

                                                            Подождите вы сказали что:

                                                            Задача DBMS — надёжное и эффективное хранение данных.

                                                            Мое утверждение было, что кроме хранения есть еще обработка и изменение данных. И там где Оракл в основном используется (по моему опыту, это ритейл, банки и другие финансы), его используют именно в варианте с PL/SQL (то есть на нем вся бизнес-логика). Что логично, так как в противном случае, это все равно что стейк из мраморной говядины с кетчупом есть. И с этой точки зрения область применения lsFusion практически полностью совпадает с областью применения Oracle.


                                                            1. GlukKazan
                                                              19.07.2019 11:01
                                                              +1

                                                              Данные которые нельзя обрабатывать никому не нужны. Так что «эффективность хранения» подразумевает и их обработку. Но вы опять начинаете за здравие а кончаете как-то не так. Как из этого посыла следует что «область применения lsFusion практически полностью совпадает с областью применения Oracle»?

                                                              Вы PostgreSQL используете! Вот его область применения, да, практически совпадает с областью применения Oracle Database.


                                                              1. NitroJunkie
                                                                19.07.2019 11:07

                                                                Вы PostgreSQL используете! Вот его область применения, да, практически совпадает с областью применения Oracle Database.

                                                                Мы с вами о разных вещах говорим. Вы видели системы где вся бизнес-логика на PL/SQL реализована (то есть не на PL/SQL только UI на Delphi / Oracle Forms)? Я видел. Более того все системы, которые я видел где в качестве БД Oracle именно так и реализованы (а я видел их достаточно много). Потому как Oracle стоит дохера денег, и если нужна просто подложка для ORM в основном использовали MySQL, Firebird, Postgres или на крайний случай MSSQL.


                                                                1. GlukKazan
                                                                  19.07.2019 11:17

                                                                  Я видел даже системы только на PL/SQL (напоминаю, это язык и Oracle Forms тоже). Как с этим связаны попытки сравнения вашего продукта с СУБД? Вы ставите знак равенства между PL/SQL и Oracle Database? Так это неправильно (как я уже говорил, PL/SQL есть не только в базе данных). Про ORM тут вообще ни к месту. Совершенно ортогональная к нашему диалогу вещь.

                                                                  Еще раз, на пальцах. То что вы продаёте это PostgreSQL (СУБД) + Ваш продукт (не СУБД). Да, работают они вместе. Но из чего следует ваше желание сравнивать ваш продукт (не СУБД) с Oracle Database (СУБД)? Сравнивайте с Oracle Forms и все будут довольны!


                                                                  1. NitroJunkie
                                                                    19.07.2019 11:39

                                                                    Еще раз, на пальцах. То что вы продаёте это PostgreSQL (СУБД) + Ваш продукт (не СУБД). Да, работают они вместе. Но из чего следует ваше желание сравнивать ваш продукт (не СУБД) с Oracle Database (СУБД)? Сравнивайте с Oracle Forms и все будут довольны!


                                                                    А материализация представлений, ограничения и триггеры на представления, predicate pushdown (и всякие другие оптимизации такого плана), материализация подзапросов, наследование таблиц/объектов это все тоже Oracle Forms? То есть я не спорю часть функционала пересекается и с Oracle Forms, но значительная часть пересекается и с RDBMS.


                                                                    1. GlukKazan
                                                                      19.07.2019 11:46

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

                                                                      То что вы придумали со всем оптимизациями и прочим, это как раз таки такой интеллектуальный ORM. Который работает вместе с СУБД, а не вместо неё.


                                                                      1. NitroJunkie
                                                                        19.07.2019 11:57

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

                                                                        Ну я прочитал уже все что можно про материализованные представления, и именно такой же механизм. Только реализованный с тучей ограничений.
                                                                        Вот к примеру статья где это как раз обсуждается.
                                                                        www.dba-oracle.com/t_sql_patterns_materialized_view.htm

                                                                        То что вы придумали со всем оптимизациями и прочим, это как раз таки такой интеллектуальный ORM. Который работает вместе с СУБД, а не вместо неё.

                                                                        Нет никакого ORM, по большому счету данные вообще не покидают сервер БД, грубо говоря только при отображении пользователю и только видимый блок. То есть еще меньше чем в связке Oracle + Oracle Forms.
                                                                        И тут правильно говорить что и вместе и вместо. То есть predicate pushdown, проталкивание LIMIT внутрь подзапросов, преобразование GROUP LAST в подзапрос и т.п. SELECT LIMIT 1 и кучу других оптимизаций, которые по идее должны были делать сами СУБД это вместо. ACID, типы JOIN'ов и параллелизм к примеру да — это вместе (так как lsFusion этим не занимается).


                                                                        1. GlukKazan
                                                                          19.07.2019 12:25
                                                                          +1

                                                                          Ну как же нет? Если он крякает как утка… Вы преобразуете SQL-запросы перед передачей их в СУБД, что-то кэшируете, возможно управляете распределёнными транзакциями (если нет — то я разочарован). Что это как не ORM? Впрочем, у вас ведь не OO сверху? Ну ладно, будем называть это XRM.

                                                                          Что касается материализованных представлений — это механизм реализованный внутри СУБД. Вы же сделали клиентский кэш (ну или кэш на уровне сервере приложений, не важно). Это принципиально разные механизмы и их нельзя напрямую сравнивать. За сим, пережёвывать дальше эту тему не вижу большого смысла.


                                                                          1. NitroJunkie
                                                                            19.07.2019 12:32

                                                                            Что это как не ORM?

                                                                            Я может отстал от жизни, но ORM это когда данные пересылаются на сервер приложений и создают там прикладные объекты соответствующие этим данные и там же они обрабатываются.
                                                                            Что касается материализованных представлений — это механизм реализованный внутри СУБД. Вы же сделали клиентский кэш (ну или кэш на уровне сервере приложений, не важно).

                                                                            Еще раз все делается на уровне СУБД. Никаких клиентских кэшей и кэшей на уровне сервере приложений нет. Данные не покидают СУБД пока вы не начнете показывать данные пользователю (то есть используете логику представлений).

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


                                                                            1. lair
                                                                              19.07.2019 13:09
                                                                              +1

                                                                              Я может отстал от жизни, но ORM это когда данные пересылаются на сервер приложений

                                                                              Нет. ORM — это когда данные преобразуются из реляционной формы в объектную.


                                                            1. lair
                                                              19.07.2019 13:07

                                                              И там где Оракл в основном используется (по моему опыту, это ритейл, банки и другие финансы), его используют именно в варианте с PL/SQL (то есть на нем вся бизнес-логика).

                                                              Гм. Даже если допустить, что это верно для Oracle (во что я тоже не верю), для других RDBMS, например, MS SQL и MySQL, это совершенно точно не верно.


                                                        1. GlukKazan
                                                          19.07.2019 09:05

                                                          То есть то что на Oracle целые АБС и тот же OEBS, или Oracle Retail реализованы это не считается?

                                                          А вы не путайте клиентский PL/SQL и серверный. Язык один — задачи разные. Если не очень понятно, рассмотрите, в качестве аналогии, JavaScript, с его Node.js


                                                          1. NitroJunkie
                                                            19.07.2019 09:46

                                                            А вы не путайте клиентский PL/SQL и серверный

                                                            А можно подробнее про «клиентский» PL/SQL. А то я долго гуглил ничего не нашел про это? И Node.js это библиотека, а PL это единый язык с SQL встроенный в то же синтаксическое дерево практически бесшовно. Нут аналогом скорее C# и linq to sql, и то его там не так бесшовно встраивали.


                                                            1. GlukKazan
                                                              19.07.2019 10:41

                                                              Ну, во первых. Node.js — это сервер, на котором крутится JavaScript (но это к делу не относится). Что касается клиентского PL/SQL, то он есть в Oracle Forms. То есть, язык тот же что и в хранимках, но используется на клиентском компьютере для обработки событий экранных форм. Так что, если желаете гуглить, гуглите Oracle Forms и просвещайтесь. По поводу бесшовности PL и SQL, даже комментировать не буду (отсутствие этой бесшовности уже с десяток лет у всех ораклоидов в печёнках сидит).


                                                              1. NitroJunkie
                                                                19.07.2019 10:58

                                                                По поводу бесшовности PL и SQL, даже комментировать не буду (отсутствие этой бесшовности уже с десяток лет у всех ораклоидов в печёнках сидит).

                                                                Это да, вот прямо сейчас нарвался, на то, что EXECUTE в SQL это выполнение prepared statement, а в pgplsql выполнение строки. И долго втыкал в syntax error в при выполнении команды в psql :)


                            1. lair
                              18.07.2019 15:32

                              Их области применения все равно пересекаются мало, потому что их задачи разные. Или вы хотите сказать, что "когда люди пишут ERP, и у них не справляется ERP-платформа, они начинают работать напрямую с RDBMS"?


                              1. NitroJunkie
                                18.07.2019 16:31

                                Или вы хотите сказать, что «когда люди пишут ERP, и у них не справляется ERP-платформа, они начинают работать напрямую с RDBMS»?

                                Ирония, но практически так и есть. Если вы откроете код Управления торговлей в 1С, вы увидите что в большинстве ключевых документов, все делается через формирование больших строк запросов и записи их результатов во времянки. То есть там ORM с большего похерили и перешли по сути на ручное написание запросов.


                                1. lair
                                  18.07.2019 16:33

                                  Важно понимать разницу между "миновали ORM" и "отказались от платформы". Ну и да, надо понимать, к чему приводит отказ от ORM в конкретной задаче.


                                  1. NitroJunkie
                                    18.07.2019 16:36

                                    Ну они по сути отказались там и от идеологии справочник-документ-регистр, то есть проведения / распроведения (там они по факту вручную диффграмму строят, иначе опять таки легло бы все по производительности). То есть по факту в типовых они 1С-платформу грубо говоря как POJO(Java) или Delphi используют. Хорошо хоть до хранимок не дошли :)


                        1. GlukKazan
                          18.07.2019 15:11

                          Смешались в кучу люди, кони. А CRM и RDBMS тоже пересекаются? Может будем сравнивать ERP с CRM или CAD?


  1. lair
    18.07.2019 12:48

    На практике это выглядит следующим образом.[...] все это идет в тестирование или эксплуатацию. [...] Когда обнаруживается долгий запрос, то [...] принимается решение о включении MATERIALIZED на некоторой промежуточной функции. Тем самым немного замедляется запись [...] значительно ускоряется не только этот запрос, но и все другие, которые используют эту функцию

    Я так понимаю, основной профит возникает из разницы между "немного замедляется запись" и "значительно ускоряется [...] запрос". Возникает разумный вопрос: что в концепции "функциональной базы данных" гарантирует, что первое сильно меньше второго?


    И, раз уж зашла речь о функциональном поведении, чем это лучше, чем мемоизация?


    1. CrushBy Автор
      18.07.2019 12:51

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


      1. lair
        18.07.2019 12:53

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

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


        1. CrushBy Автор
          18.07.2019 14:01

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


    1. NitroJunkie
      18.07.2019 14:10

      И, раз уж зашла речь о функциональном поведении, чем это лучше, чем мемоизация?

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


      1. lair
        18.07.2019 14:15

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

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


        Логически, можно считать, что материализация не отличается от мемоизации.

        За исключением как минимум того, что мемоизация — ну, по крайней мере, как я ее понимаю — делается по запросу, а ваша материализация — всегда.


        1. NitroJunkie
          18.07.2019 14:37

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

          Возможность инкрементального обновления — вещь относительная. Инкрементальное обновление может быть более эффективное, может быть менее эффективное, но возможно всегда.
          Так к примеру GROUP SUM, GROUP LAST, MAX, MIN с индексами, (+), RECURSION, взаимоисключающие выбор (EXCLUSIVE, ABSTRACT) — обновляются очень эффективно, композиция, +, IF, CASE — просто эффективно, PARTITION (разбиение) ORDER, GROUP LAST MAX MIN без индексов — наименее эффективно, но все равно за пределы разбиения / группировки не выходят (то есть на самом деле даже вручную вы их сильно эффективнее не обновите)
          За исключением как минимум того, что мемоизация — ну, по крайней мере, как я ее понимаю — делается по запросу, а ваша материализация — всегда.

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


          1. lair
            18.07.2019 14:41

            Возможность инкрементального обновления — вещь относительная.

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


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

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


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

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


          1. GlukKazan
            18.07.2019 15:07

            Инкрементальное обновление… возможно всегда

            Хмм. GROUP AVG тоже инкрементально обновите? Только не SUM/CNT — это уже доработка напильником. Но ладно, бог с ним со средним. Как на счёт инкрементального обновления медианы? Только на серьёзных объёмах. Миллион записей например.


            1. NitroJunkie
              18.07.2019 15:15

              AVG (а точнее медиана) попадает в группу LAST, MAX, MIN без индексов, то есть наименее эффективно. Обновляется плохо, но с другой стороны требует чтение данных только в пределах изменившихся групп (и если есть какая-то сильно селективная группировка например инвойс, проблемы не возникает). Ни шага влево, ни шага вправо.

              Еще раз инкрементальность это количественный показатель. Он бывает лучше и хуже. Просто в том же Oracle там два режима — или SELECT SUM GROUP BY, или полный перерасчет.


              1. GlukKazan
                18.07.2019 15:25
                +1

                Ой, ну мы же это проходили. Какие два режима? О чём вы? Зачем писать о том, в чём не разбираешься? То что агрегат можно рассчитать всегда, это и так понятно (и не обязательно приплетать сюда Oracle). Когда говорят об инкрементальном обновлении, обычно имеют в виду, что обновление происходит без полного перечитывания исходных данных. Поэтому оно и называется инкрементальным. Остальное (лучше или хуже) — это уже демагогия. Это как осетрина, понимаете?


                1. NitroJunkie
                  18.07.2019 15:35

                  Подождите, ну так AVG происходит тоже без без полного перечитывания исходных данных. Если к примеру вы считаете AVG по инвойсу, или по товару, складу, вам достаточно перечитать только данные по инвойсу, или по товару складу. Фокус конечно определить инвойсы, по которым произошло изменение и только для них пересчитать. И это реальный живой кейс (ну точнее AVG обычно на SUM, CNT разбивают, правильнее говорить о медиане).

                  Строго говоря при любом инкрементальном обновлении нужно что-то перечитать.

                  Какие два режима? О чём вы? Зачем писать о том, в чём не разбираешься?

                  Честно прочитал всю Главу про материализованные представления и документацию. И там чтобы получить корректные данные всего 2 варианта: или REFRESH ON COMMIT с тучей ограничений, или никак.


                  1. GlukKazan
                    18.07.2019 15:46

                    Строго говоря при любом инкрементальном обновлении нужно что-то перечитать.

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


                    1. NitroJunkie
                      18.07.2019 15:53

                      Именно так она и делает в случае GROUP SUM, RECURSION и + скажем. В композиции если идет f(g(a), h(a)), и изменяется h(a), ей нужно только для измененных h(a) дочитать f(g(a), h(a)) и обновить только эти записи на новые значения. И т.п.


            1. CrushBy Автор
              18.07.2019 15:20

              Только не SUM/CNT — это уже доработка напильником

              Это почему напильником? Так гораздо эффективнее инкрементность, и со средним мы обычно так и делаем. Материализуем сумму и количество.


              1. GlukKazan
                18.07.2019 15:33

                Все так делают. Но это означает, что «из коробки», то есть сам инструмент инкрементально обновлять среднее не умеет. Как и медиану.


                1. NitroJunkie
                  18.07.2019 15:39

                  Если вы сделаете
                  median = GROUP SUM MEDIAN(quantity(InvoiceDetail id)) BY invoice(id) MATERIALIZED;
                  Он отлично будет обновляться при любых изменениях в том числе, если InvoiceDetail перепривязать к другому инвойсу. И высчитывать будет данные только для инвойса, то есть в среднем для 100 записей, чего чаще всего вполне достаточно (хотя под ответственность DBA). В Oracle вы этого не сделаете никак. Придется руками разработчику за всем этим следить.


                  1. GlukKazan
                    18.07.2019 15:43

                    Я же спросил, миллион записей она так сколько по времени (и памяти) будет «инкрементить»? И так на каждом добавлении? Спасибо, я лучше как все, руками.


                    1. NitroJunkie
                      18.07.2019 15:47

                      Да хоть миллиард. Проверено опытом. Если в одном инвойсе 100 записей, больше 100 записей оно не прочитает никогда.

                      PS: если invoice = DATA Invoice (InvoiceDetail) INDEXED естественно.

                      И так на каждом добавлении?

                      Один раз на транзакцию. И только если изменилось quantity или invoice (привязка к инвойсу). И руками вы скорее всего будете делать точно также, если у вас максимум 100 записей (ну если только вы не за миску риса работаете и можете потратить неограниченное время на premature optimization)


                      1. GlukKazan
                        18.07.2019 15:53

                        Знаете, я вот тут немножко как раз DBA. И Oracle разработчик. И как раз сейчас занимаюсь задачей в которой в сутки падает примерно 6 миллионов записей. Я прекрасно знаю сколько времени занимает сканирование миллиона записей с (пусть даже SSD) диска. И сколько всё это займёт памяти если загрузить в оперативку. Не надо мне тут про «хоть миллиард».


                        1. NitroJunkie
                          18.07.2019 16:03

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

                          И как вы понимаете записей там уже далеко за миллиард.

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

                          И кстати крутится все на Postgres и на 2 серверах типа такого:
                          ark.intel.com/content/www/ru/ru/ark/products/120484/intel-xeon-gold-5115-processor-13-75m-cache-2-40-ghz.html
                          Это я еще мощный нашел.


                1. CrushBy Автор
                  18.07.2019 15:40

                  Можно при желании сделать синтаксический сахар, который будет «невидимо» для пользователя создавать два таких поля. Просто вы привели случаи, которые на практике используются достаточно редко. В отличии от многих других, которые не поддерживаются ни Oracle, ни MSSQL.


                  1. GlukKazan
                    18.07.2019 15:44

                    Речь не о сахаре. Речь о производительности.


                    1. CrushBy Автор
                      18.07.2019 15:51

                      Так производительность даже сейчас, если сделать SUM и COUNT функции MATERIALIZED будет максимальная. Добавление и изменение за O (кол-во изменений), чтение за O(1). Естественно умноженное все на логарифм для пробега по индексам.


            1. eefadeev
              19.07.2019 14:55

              Только на серьёзных объёмах. Миллион записей например.

              В мало-мальски серьёзных системах миллион записей — это не то что «не серьёзно», про это даже говорить неприлично! :)


              1. NitroJunkie
                19.07.2019 14:59
                -1

                Это да. Забавно, когда люди считают что миллион это много. Хотя конечно я помню как мы сами в свое время на эти грабли наступили, когда пришлось почти все счетчики с int'ов на long'и переделывать. :( В long пока не упирались :) Хотя кто знает…


  1. bgnx
    18.07.2019 14:08

    Не совсем ясно как эта база данных решает известную проблему денормализации — происходит трек зависимостей и полное перевычисление или анализируются выражения для инкрементальных изменений? Решена ли проблема ромбовидных зависимостей (когда вычисление какой-то вьюхи может запуститься дважды)? Что насчет возможности получения реалтайм-оповещений об изменениях материализованной вьюхи? Что насчет возможности инкрементальной синхронизации (когда при восстановлении оборванного соединения через которого клиент получал оповещения от материализованной вьюхи будет происходить загрузка не всех данных вьюхи а только тех которые изменились за время оффлайна) ?


    1. NitroJunkie
      18.07.2019 14:24

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

      Материализация это и есть денормализация. Обновление инкрементальное, то есть грубо говоря если идет x = GROUP SUM(g(a)) BY f(a), то при изменении f(a) с b на d, x(b) уменьшается на f(a), а x(d) увеличивается на f(a) и делается это естественно не императивно (со всякими N+1) а одним скомпилированным и оптимизированным запросом.
      Решена ли проблема ромбовидных зависимостей (когда вычисление какой-то вьюхи может запуститься дважды)?

      Механизму инкрементальности — фиолетово, можете одну функцию хоть сто раз использовать.
      Что насчет возможности получения реалтайм-оповещений об изменениях материализованной вьюхи?

      Я бы сказал, там сделано даже больше, там сама «материализованная вьюха» не нужна, можно делать так:
      WHEN CHANGED(x(a)) DO
      EMAIL…
      хотя конечно если x(a) materialized это работает куда эффективнее, так как может обновляться инкрементально, как в ответе выше.
      Что насчет возможности инкрементальной синхронизации (когда при восстановлении оборванного соединения через которого клиент получал оповещения от материализованной вьюхи будет происходить загрузка не всех данных вьюхи а только тех которые изменились за время оффлайна) ?

      Этот механизм работает в рамках одного сервера. Для распределенных серверов нужно по хорошему теми же событиями делать
      WHEN CHANGED(x(a)) DO
      EXTERNAL LSF x(a) посылаем изменения на удаленный сервер
      А на удаленном записываем в первичное свойство x(a), и дальше весь стек событий / ограничений / материализаций отрабатывает уже на этом удаленном сервере автоматически.


  1. NitroJunkie
    18.07.2019 14:22

    del


  1. eefadeev
    18.07.2019 20:17

    Типичная OLTP база данных работает с соотношениями запись: чтение от 1:1000 до 1:1000000 и более. Не очень понятно что понимается под декларируемой «балансировкой чтения и записи»?


    1. CrushBy Автор
      18.07.2019 20:22

      К сожалению, это не всегда так. Возьмем, например, обычную учетную систему типа 1С, в которой документы, которые изменяют остатки пишутся допустим 100 раз в день. При этом читаются остатки во много раз чаще (отчеты, заказы и прочее).

      UPD: прошу прощения, неправильно комментарий прочитал.
      Собственно в этом и смысл, чтобы ускорить значительно 1000 и 1000000 чтений, замедлив эту одну запись.


      1. eefadeev
        18.07.2019 21:17

        Уменьшить затраты на чтение (путём переноса нагрузки на этап записи) можно только в ограниченном наборе ситуаций, большинство которых связано с расчётами тех или иных агрегатов. Но для этого не нужны никакие специальны БД — всё это прекрасно делается на обычных SQL базах (хотя и путём ощутимого усложнения логики как на самой БД, так и во внешних по отношению к ней приложениях)


        1. CrushBy Автор
          18.07.2019 21:25

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

          А можно поподробнее? Почему в ограниченных? В описанной концепции можно в любых случаях.

          Но для этого не нужны никакие специальны БД — всё это прекрасно делается на обычных SQL базах (хотя и путём ощутимого усложнения логики как на самой БД, так и во внешних по отношению к ней приложениях)

          Так в этом и основной смысл и преимущество технологии. Что это можно сделать вообще не изменяя логику программы.


          1. lair
            19.07.2019 00:56

            Вы уж определитесь: в любых случаях или в большинстве?