Функциональная СУБД

Мир баз данных давно захвачен реляционными СУБД, в которых используется язык SQL. Настолько сильно, что появляющиеся разновидности называют NoSQL. Им удалось отбить себе определенное место на этом рынке, но реляционные СУБД умирать не собираются, и продолжают активно использоваться для своих целей.

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

Введение


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

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

Таблицы и поля


Простой объект Sku с полями наименование и цена:

Реляционная

CREATE TABLE Sku
(
    id bigint NOT NULL,
    name character varying(100),
    price numeric(10,5),
    CONSTRAINT id_pkey PRIMARY KEY (id)
)

Функциональная
CLASS Sku;
name = DATA STRING[100] (Sku);
price = DATA NUMERIC[10,5] (Sku);

Мы объявляем две функции, которые принимают на вход один параметр Sku, и возвращают примитивный тип.

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

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

Реляционная

CREATE TABLE prices
(
    skuId bigint NOT NULL,
    storeId bigint NOT NULL,
    supplierId bigint NOT NULL,
    dateTime timestamp without time zone,
    price numeric(10,5),
    CONSTRAINT prices_pkey PRIMARY KEY (skuId, storeId, supplierId)
)

Функциональная
CLASS Sku;
CLASS Store;
CLASS Supplier;
dateTime = DATA DATETIME (Sku, Store, Supplier);
price = DATA NUMERIC[10,5] (Sku, Store, Supplier);

Индексы


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

Реляционная

CREATE INDEX prices_date
    ON prices
    (skuId, storeId, supplierId, dateTime)

Функциональная
INDEX Sku sk, Store st, Supplier sp, dateTime(sk, st, sp);

Задачи


Начнем с относительно простых задач, взятых из соответствующей статьи на Хабре.

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

CLASS Department;
name = DATA STRING[100] (Department);

CLASS Employee;
department = DATA Department (Employee);
chief = DATA Employee (Employee);
name = DATA STRING[100] (Employee);
salary = DATA NUMERIC[14,2] (Employee);

Задача 1.1


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

Реляционная

select a.*
from   employee a, employee b
where  b.id = a.chief_id
and    a.salary > b.salary

Функциональная
SELECT name(Employee a) WHERE salary(a) > salary(chief(a));

Задача 1.2


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

Реляционная

select a.*
from   employee a
where  a.salary = ( select max(salary) from employee b
                    where  b.department_id = a.department_id )

Функциональная
maxSalary 'Максимальная зарплата' (Department s) = 
    GROUP MAX salary(Employee e) IF department(e) = s;
SELECT name(Employee a) WHERE salary(a) = maxSalary(department(a));

// или если "заинлайнить"
SELECT name(Employee a) WHERE 
    salary(a) = maxSalary(GROUP MAX salary(Employee e) IF department(e) = department(a));

Обе реализации эквивалентны. Для первого случая в реляционной базе можно использовать CREATE VIEW, который таким же образом сначала посчитает для конкретного отдела максимальную зарплату в нем. В дальнейшем я для наглядности буду использовать первый случай, так как он лучше отражает решение.

Задача 1.3


Вывести список ID отделов, количество сотрудников в которых не превышает 3 человек.

Реляционная

select department_id
from   employee
group  by department_id
having count(*) <= 3

Функциональная
countEmployees 'Количество сотрудников' (Department d) = 
    GROUP SUM 1 IF department(Employee e) = d;
SELECT Department d WHERE countEmployees(d) <= 3;

Задача 1.4


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

Реляционная

select a.*
from   employee a
left   join employee b on (b.id = a.chief_id and b.department_id = a.department_id)
where  b.id is null

Функциональная
SELECT name(Employee a) WHERE NOT (department(chief(a)) = department(a));

Задача 1.5


Найти список ID отделов с максимальной суммарной зарплатой сотрудников.

Реляционная

with sum_salary as
  ( select department_id, sum(salary) salary
    from   employee
    group  by department_id )
select department_id
from   sum_salary a       
where  a.salary = ( select max(salary) from sum_salary )

Функциональная
salarySum 'Максимальная зарплата' (Department d) = 
    GROUP SUM salary(Employee e) IF department(e) = d;
maxSalarySum 'Максимальная зарплата отделов' () = 
    GROUP MAX salarySum(Department d);
SELECT Department d WHERE salarySum(d) = maxSalarySum();


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

Задача 2.1


Какие продавцы продали в 1997 году более 30 штук товара №1?

Доменная логика (как и раньше на РСУБД пропускаем объявление):
CLASS Employee 'Продавец';
lastName 'Фамилия' = DATA STRING[100] (Employee);

CLASS Product 'Продукт';
id = DATA INTEGER (Product);
name = DATA STRING[100] (Product);

CLASS Order 'Заказ';
date = DATA DATE (Order);
employee = DATA Employee (Order);

CLASS Detail 'Строка заказа';

order = DATA Order (Detail);
product = DATA Product (Detail);
quantity = DATA NUMERIC[10,5] (Detail);

Реляционная

select LastName
from Employees as e
where (
  select sum(od.Quantity)
  from [Order Details] as od
  where od.ProductID = 1 and od.OrderID in (
    select o.OrderID
    from Orders as o
    where year(o.OrderDate) = 1997 and e.EmployeeID = o.EmployeeID)
) > 30

Функциональная
sold (Employee e, INTEGER productId, INTEGER year) = 
    GROUP SUM quantity(OrderDetail d) IF 
        employee(order(d)) = e AND 
        id(product(d)) = productId AND 
        extractYear(date(order(d))) = year;
SELECT lastName(Employee e) WHERE sold(e, 11997) > 30;

Задача 2.2


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

Расширяем доменную логику из предыдущего примера:
CLASS Customer 'Клиент';
contactName 'ФИО' = DATA STRING[100] (Customer);

customer = DATA Customer (Order);

unitPrice = DATA NUMERIC[14,2] (Detail);
discount = DATA NUMERIC[6,2] (Detail);

Реляционная

SELECT ContactName, ProductName FROM (
SELECT c.ContactName, p.ProductName
, ROW_NUMBER() OVER (
    PARTITION BY c.ContactName
    ORDER BY SUM(od.Quantity * od.UnitPrice * (1 - od.Discount)) DESC
) AS RatingByAmt
FROM Customers c
JOIN Orders o ON o.CustomerID = c.CustomerID
JOIN [Order Details] od ON od.OrderID = o.OrderID
JOIN Products p ON p.ProductID = od.ProductID
WHERE YEAR(o.OrderDate) = 1997
GROUP BY c.ContactName, p.ProductName
) t
WHERE RatingByAmt < 3

Функциональная
sum (Detail d) = quantity(d) * unitPrice(d) * (1 - discount(d));
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;
rating 'Рейтинг' (Customer c, Product p, INTEGER y) = 
    PARTITION SUM 1 ORDER DESC bought(c, p, y), p BY c, y;
SELECT contactName(Customer c), name(Product p) WHERE rating(c, p, 1997) < 3;

Оператор PARTITION работает по следующему принципу: он суммирует выражение, указанное после SUM (здесь 1), внутри указанных групп (здесь Customer и Year, но может быть любое выражение), сортируя внутри групп по выражениям, указанным в ORDER (здесь bought, а если равны, то по внутреннему коду продукта).

Задача 2.3


Сколько товаров нужно заказать у поставщиков для выполнения текущих заказов.

Опять расширяем доменную логику:
CLASS Supplier 'Поставщик';
companyName = DATA STRING[100] (Supplier);

supplier = DATA Supplier (Product);

unitsInStock 'Остаток на складе' = DATA NUMERIC[10,3] (Product);
reorderLevel 'Норма продажи' = DATA NUMERIC[10,3] (Product);

Реляционная
select s.CompanyName, p.ProductName, sum(od.Quantity) + p.ReorderLevel — p.UnitsInStock as ToOrder
from Orders o
join [Order Details] od on o.OrderID = od.OrderID
join Products p on od.ProductID = p.ProductID
join Suppliers s on p.SupplierID = s.SupplierID
where o.ShippedDate is null
group by s.CompanyName, p.ProductName, p.UnitsInStock, p.ReorderLevel
having p.UnitsInStock < sum(od.Quantity) + p.ReorderLevel

Функциональная
orderedNotShipped 'Заказано, но не отгружено' (Product p) = 
    GROUP SUM quantity(OrderDetail d) IF product(d) = p;
toOrder 'К заказу' (Product p) = orderedNotShipped(p) + reorderLevel(p) - unitsInStock(p);
SELECT companyName(supplier(Product p)), name(p), toOrder(p) WHERE toOrder(p) > 0;

Задача со звездочкой


И последней пример лично от меня. Есть логика социальной сети. Люди могут дружить друг с другом и нравится друг другу. С точки зрения функциональной базы данных это будет выглядеть следующим образом:
CLASS Person;
likes = DATA BOOLEAN (Person, Person);
friends = DATA BOOLEAN (Person, Person);

Необходимо найти возможных кандидатов на дружбу. Более формализовано нужно найти всех людей A, B, C таких, что A дружит с B, а B дружит с C, A нравится C, но A не дружит с C.
С точки зрения функциональной базы данных запрос будет выглядеть следующим образом:
SELECT Person a, Person b, Person c WHERE 
    likes(a, c) AND NOT friends(a, c) AND 
    friends(a, b) AND friends(b, c);

Читателю предлагается самостоятельно решить эту задачу на SQL. Предполагается, что друзей гораздо меньше чем тех, кто нравится. Поэтому они лежат в отдельных таблицах. В случае успешного решения есть также задача с двумя звездочками. В ней дружба не симметрична. На функциональной базе данных это будет выглядеть так:
SELECT Person a, Person b, Person c WHERE 
    likes(a, c) AND NOT friends(a, c) AND 
    (friends(a, b) OR friends(b, a)) AND 
    (friends(b, c) OR friends(c, b));

UPD: решение задачи с первой и второй звездочкой от dss_kalika:
SELECT 
   pl.PersonAID
  ,pf.PersonAID
  ,pff.PersonAID
FROM Persons                 AS p
--Лайки                      
JOIN PersonRelationShip      AS pl ON pl.PersonAID = p.PersonID
                                  AND pl.Relation  = 'Like'
--Друзья                     
JOIN PersonRelationShip      AS pf ON pf.PersonAID = p.PersonID 
                                  AND pf.Relation = 'Friend'
--Друзья Друзей              
JOIN PersonRelationShip      AS pff ON pff.PersonAID = pf.PersonBID
                                   AND pff.PersonBID = pl.PersonBID
                                   AND pff.Relation = 'Friend'
--Ещё не дружат         
LEFT JOIN PersonRelationShip AS pnf ON pnf.PersonAID = p.PersonID
                                   AND pnf.PersonBID = pff.PersonBID
                                   AND pnf.Relation = 'Friend'
WHERE pnf.PersonAID IS NULL 

;WITH PersonRelationShipCollapsed AS (
  SELECT pl.PersonAID
        ,pl.PersonBID
        ,pl.Relation 
  FROM #PersonRelationShip      AS pl 
  
  UNION 

  SELECT pl.PersonBID AS PersonAID
        ,pl.PersonAID AS PersonBID
        ,pl.Relation
  FROM #PersonRelationShip      AS pl 
)
SELECT 
   pl.PersonAID
  ,pf.PersonBID
  ,pff.PersonBID
FROM #Persons                      AS p
--Лайки                      
JOIN PersonRelationShipCollapsed  AS pl ON pl.PersonAID = p.PersonID
                                 AND pl.Relation  = 'Like'                                  
--Друзья                          
JOIN PersonRelationShipCollapsed  AS pf ON pf.PersonAID = p.PersonID 
                                 AND pf.Relation = 'Friend'
--Друзья Друзей                   
JOIN PersonRelationShipCollapsed  AS pff ON pff.PersonAID = pf.PersonBID
                                 AND pff.PersonBID = pl.PersonBID
                                 AND pff.Relation = 'Friend'
--Ещё не дружат                   
LEFT JOIN PersonRelationShipCollapsed AS pnf ON pnf.PersonAID = p.PersonID
                                   AND pnf.PersonBID = pff.PersonBID
                                   AND pnf.Relation = 'Friend'
WHERE pnf.[PersonAID] IS NULL 


Заключение


Следует отметить, что приведенный синтаксис языка — это всего лишь один из вариантов реализации приведенной концепции. За основу был взят именно SQL, и целью было, чтобы он максимально был похож на него. Конечно, кому-то могут не понравится названия ключевых слов, регистры слов и прочее. Здесь главное — именно сама концепция. При желании можно сделать и C++, и Python подобный синтаксис.

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

  • Простота. Это относительно субъективный показатель, который не очевиден на простых случаях. Но если посмотреть более сложные случаи (например, задачи со звездочками), то, на мой взгляд, писать такие запросы значительно проще.
  • Инкапсуляция. В некоторых примерах я объявлял промежуточные функции (например, sold, bought и т.д.), от которых строились последующие функции. Это позволяет при необходимости изменять логику определенных функций без изменения логики зависящих от них. Например, можно сделать, чтобы продажи sold считались от совершенно других объектов, при этом остальная логика не изменится. Да, в РСУБД это можно реализовать при помощи CREATE VIEW. Но если всю логику писать таким образом, то она будет выглядеть не очень читабельной.
  • Отсутствие семантического разрыва. Такая база данных оперирует функциями и классами (вместо таблиц и полей). Точно также, как и в классическом программировании (если считать, что метод — это функция с первым параметром в виде класса, к которому он относится). Соответственно, «подружить» с универсальными языками программирования должно быть значительно проще. Кроме того, эта концепция позволяет реализовывать гораздо более сложные функции. Например, можно встраивать в базу данных операторы вида:
    CONSTRAINT sold(Employee e, 12019) > 100 IF name(e) = 'Петя' MESSAGE  'Что-то Петя продает слишком много одного товара в 2019 году';

  • Наследование и полиморфизм. В функциональной базе данных можно ввести множественное наследование через конструкции CLASS ClassP: Class1, Class2 и реализовать множественный полиморфизм. Как именно, возможно напишу в следующих статьях.

Несмотря на то, что это всего лишь концепция, у нас есть уже некоторая реализация на Java, которая транслирует всю функциональную логику в реляционную логику. Плюс к ней красиво прикручена логика представлений и много чего другого, благодаря чему получается целая платформа. По сути, мы используем РСУБД (пока только PostgreSQL) как «виртуальную машину». При такой трансляции иногда возникают проблемы, так как оптимизатор запросов РСУБД не знает определенной статистики, которую знает ФСУБД. В теории, можно реализовать систему управления базой данных, которая будет использовать в качестве хранилища некую структуру, адаптированную именно под функциональную логику.
lsFusion
45,11
Не очередной язык программирования
Поделиться публикацией

Похожие публикации

Комментарии 278

    +1
    Тот же эллипсоид, вид сбоку: "Реализация сервера объектного представления средствами реляционной СУБД"
    20 лет назад мы обертывали РСУБД слоем реализации ООП (не путать с ORM), сегодня вы снова оборачиваете движок Кодда в функциональную абстракцию.
    Вывод: выбор в пользу реляционой модели в 1970-х был обоснованным :)
      0
      Функциональное программирование тоже появилось давно, но только сейчас оно стало более менее популярно.
      К сожалению, в вашей ссылке не очень понял, в чем схожесть. Тут важнее не объекты, а функции.
        0
        Схожесть в подходе: берем реляционный движок и прикручивем к нему сверху новый слой абстракций, позволяющий разрабатывать в удобной авторам парадигме (ООП в нашем случае, «фукциональщина» в вашем)
          0
          В данном случае, реляционный движок был взят только с целью упрощения реализации. В идеале лучше реализовывать специальный движок именно под эту логику.
            0
            Весьма спорное упрощение реализации. Открыт вопрос, например, интеграции этого в волшебную страну .NET
            Писать через DbScope? Путаница будет
            Используем EF — не пишем sql и так(если ошибаюсь, не кидайте тапками, лично им не пользовался нормально)
            Так в чем в итоге выигрыш? Возможно, для некоторых случаев это упростит задачу интеграции модели данных и БД, но смысл в этом небольшой
              +1
              Если вы не используете SQL, а используете базу данных только для сериализации/десериализации объектов, то Вам не нужна даже реляционная СУБД. Используйте лучше NoSQL базы данных.
                +1

                Не "не используем", а "не пишем" (в смысле, прикладной программист руками не пишет). А внизу там все равно РСУБД со всеми ее достоинствами.

                  0
                  А можете показать как задачу с 2* решить так чтобы «внизу РСУБД со всеми достоинствами»? На том же C#.
                    0
                    class Person
                    {
                      IEnumerable<Person> Likes {get;}
                      IEnumerable<Person> Friends {get;}
                    }
                    
                    //вариант 1
                    from a in persons
                    from b in a.Friends
                    from c in b.Friends.Except(a.Friends)
                    where a.Likes.Contains(c)
                    select (a, b, c)
                    
                    //вариант 2
                    persons
                    .SelectMany(a => a.Friends)
                    .SelectMany((a, b) => b.Friends.Except(a.Friends)))
                    .Where((a, b, c) => a.Likes.Contains(c))
                      +1
                      Так неправильно же работать будет. Это задача 1 со звездочкой. А я про задачу 2 со звездочкой.
                        0

                        Нет, это будет работать строго в соответствии с условиями задачи:


                        A дружит с B

                        from b in a.Friends


                        B дружит с C

                        from c in b.Friends


                        A нравится C

                        a.Likes.Contains(c)


                        A не дружит с C

                        from c in [...].Except(a.Friends)


                        дружба не симметрична

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

                          –1
                          A дружит с B

                          Так а где B дружит с A?
                          Коллекции Friends однонаправлены

                          Это как? Что вообще такое «однонаправленные коллекции»?
                          И как вообще EF из вашего кода определяет он задачу с одной звездочкой или с двумя решает. Точнее приведите пожалуйста тогда решение задачи с одной звездочкой? Ну и SQL, который при этом .NET генерит, очень интересно посмотреть.
                            +1
                            Так а где B дружит с A?

                            А его в условиях нет. Но это легко сделать, очевидно:


                            IQueryable<(Person, Person)> FriendsOrFriendOf(this Person p)
                              => p.SelectMany(_ => AllOf<Person>).Where((a, b) => a.Friends.Contains(b) || b.Friends.Contains(a))

                            Это как?

                            Очень просто: a.Friends.Contains(b) не означает b.Friends.Contains(a).


                            И как вообще EF из вашего кода определяет он задачу с одной звездочкой или с двумя решает.

                            Из моего кода — никак. Это определяется на этапе создания модели данных, где и указывается, как связаны два объекта.


                            Ну и SQL, который при этом .NET генерит, очень интересно посмотреть.

                            Я уже писал один раз, повторю еще раз: .NET не генерит SQL. SQL генерят провайдеры, которых больше одного.

                              0
                              А его в условиях нет. Но это легко сделать, очевидно:

                              В задаче 2* есть. Это в задаче 1* нет.
                              И вы уверены, что там запрос как внизу в примере снизу с CTE и UNION'ами не получится, когда она будет две таблицы friends union'ить. И вы же вроде как говорили что || не поддерживаются. И поэтому очень интересно посмотреть на реальный запрос. Это же по идее должно быть очень просто. А то все же возмущаются, как это я не могу контролировать формирование SQL запросов.
                              Из моего кода — никак. Это определяется на этапе создания модели данных, где и указывается, как связаны два объекта.

                              Ну то есть, это не весь код. А можно тогда «всех посмотреть»?
                              Я уже писал один раз, повторю еще раз: .NET не генерит SQL. SQL генерят провайдеры, которых больше одного.

                              Ок, что сгенерит провайдер, который используется в большинстве случаев. Или что сгенерит самый лучший провайдер из существующих?
                                0
                                В задаче 2* есть.

                                Нет, в задаче 2* написано дословно следующее: "есть также задача с двумя звездочками. В ней дружба не симметрична.". Ничего больше.


                                И вы же вроде как говорили что || не поддерживаются.

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


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

                                А какая, собственно, разница? Обсуждается форма записи, а не эффективность генерации SQL-кода.

                        +2
                        А можете профайлером посмотреть, какой запрос к базе данных сформирует LINQ? Чисто для интереса, если не трудно. Не будет ли там случаем много маленьких запросов?
                          0

                          Нет, не могу: на этой машине нет ни среды разработки, ни SQL-сервера.


                          Ну и на всякий случай: запрос к БД формирует не LINQ, запрос к БД формирует провайдер, которых совсем не один.

                            0
                            Жаль.
                            А можете, раз пошла такая пьянка, еще и задачу 2 из сложных написать на LINQ (там где PARTITION)?
                              0
                              Для каждого покупателя (имя, фамилия) найти два товара (название), на которые покупатель потратил больше всего денег в 1997-м году.

                              customers
                              .SelectMany(c => c
                                .Orders
                                .Where(o => o.OrderDate.Year == 1997))
                                .SelectMany(o => o.Lines)
                                .GroupBy(
                                  ol => ol.Item,
                                  (item, lines) => (item, total: lines.Sum(l => l.Total))
                                .OrderByDesc((_, total) => total)
                                .Select((item, _) => item)
                                .Take(2)
                              )
                              .Select((customer, item) => (
                                customer.FirstName,
                                customer.LastName,
                                item.Name
                              ))
                                0
                                Спасибо. А еще подскажите — можно ли в LINQ как-то объявлять «промежуточные» функции? Опять же как в том примере, чтобы разбить на несколько объявлений. Это важно, что потом составные части могут использоваться в других выражениях и запросах.
                                И конечно было бы интересно увидеть запросы к базе данных (для PostgreSQL желательно).
                                  0
                                  А еще подскажите — можно ли в LINQ как-то объявлять «промежуточные» функции?

                                  Да, конечно. LINQ — это набор операций над IQueryable<T>, соответственно, вы просто объявляете методы (обычно это методы-расширения, самый близкий аналог функций из ФП), которые принимают IQueryable<A> и возвращают что-нибудь (например, IQueryable<B>). Эти методы дальше компонуются произвольным образом.


                                  Я недавно спрашивал про "как объявить предикат "скидочный товар"", так вот, в LINQ это делается так (я покажу два этапа, просто чтобы было видно два разных уровня компонуемости):


                                  Expression<Func<Item,bool>> IsDiscounted = item => item.Discount != null || item.SaleCampaign != null.
                                  
                                  //использование предиката
                                  someItems.Where(IsDiscounted)
                                  
                                  //функция для композиции
                                  IQueryable<Item> DiscountedItems(this IQueryable<Item> items) => items.Where(IsDiscounted);
                                  
                                  //и ее использование
                                  someItems.Where(...).OrderBy(...).DiscountedItems()
                                    0

                                    … на самом деле, одна важная оговорка: C#, как он есть сейчас, не умеет сам объединять деревья выражений (ну то есть нельзя сделать p => IsDiscounted(p) || IsRecommended(p) (ну или по крайней мере не умел тогда, когда мне это последний раз было нужно). Для AND это делается цепочкой Where, а вот для OR пришлось сильно извращаться. Но это не фундаментальное ограничение, нужные операторы-то в деревьях есть, просто у language development team другие задачи.

                                      0
                                      А можете все таки целиком пример разбить на те же функции, что и у меня? Я тоже хочу к задаче прикрепить.
                                        0
                                        А можете все таки целиком пример разбить на те же функции, что и у меня?

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


                                        IQueryable<decimal> TotalByProduct(this IQueryable<Order> orders) => orders
                                        .SelectMany(o => o.Lines)
                                        .GroupBy(
                                          l => l.Item,
                                          (item, lines) => (item, lines.Sum(l => l.Total))
                                        );
                                        
                                        IQueryable<Order> OrdersForYear(this IQueryable<Order> orders, int year)
                                        => orders.Where(o => o.OrderDate.Year == year)
                                        
                                        customers
                                        .SelectMany(c => c
                                          .OrdersForYear(1997)
                                          .TotalByProduct()
                                          .OrderByDesc((_, total) => total)
                                          .Select((item, _) => item)
                                          .Take(2)
                                        )
                                        .Select((customer, item) => (
                                          customer.FirstName,
                                          customer.LastName,
                                          item.Name
                                        ))
                                          0
                                          И Вы считаете, что это проще и понятнее, чем в моем решении? А для обычного человека (не матерого программиста)?

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

                                            Я считаю, что это не сложнее, чем в вашем решении. Ну и лично мне — понятнее, потому что нет совершенно неочевидных для меня умолчаний, сделанных в вашей платформе.


                                            Но я вроде и не утверждал, что это проще.


                                            И почему это бессмысленно?

                                            Бесмысленно разбивать на те же функции, что у вас. Я разбил на другие.

                                              0
                                              Речь сейчас шла не о Вас, а об обычным человеке с IQ чуть выше среднего, который не знаком с программированием. По-моему, мое решение проще. Но тут не будем спорить.

                                              В вашем решении есть нюанс. Вы в параметры везде передаете Order. А предположим, что продажи считаются не только на основе Orders, а на основе 10 других таблиц (но в конечном итоге все выводится в сумму по покупателю/продукту). Как тогда? Вы везде будете передавать все 10 классов? А как же инкапсуляция?
                                                0
                                                Речь сейчас шла не о Вас, а об обычным человеке с IQ чуть выше среднего, который не знаком с программированием.

                                                Я понятия не имею, где взять достаточное количество таких людей для проведения теста.


                                                Вы везде будете передавать все 10 классов?

                                                Нет, введу промежуточную операцию, которая соберет Order из ваших "десяти таблиц".

                                        0
                                        Странные конечно задачи у language development team. То есть AND поддержали, а OR нет? То есть два передних колеса сделаем, а без задних перебьются?
                                          0
                                          То есть AND поддержали, а OR нет?

                                          Нет, не поддержали ни то, ни другое.

                                            0
                                            Ну filter.filter.filter то можно писать. Это по сути AND. А с OR'ами как?
                                              0

                                              Я, вроде, выше уже все написал по этому поводу.

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

                                          Хотя я понимаю почему они не реализовали. Эта проблема с OR'ами первая с которой мы столкнулись 8 лет назад. С AND'ами все очень просто, а добавление OR'ов переводит задачу в плоскость булевой логики, оптимизация которой, как известно, NP-полная задача. И существующие модели работы с булевой логики для компиляции запросов не подходят, по разным причинам. Тот же ДНФ из-за компактности: (f1 or f2 or f3 or… fN) and (g1 or g2… or gM) при преобразовании в ДНФ даст N*M предикатов. И поддержка логики OR'ов впоследствии создает сложности везде. Но без нее никуда. Собственно задача с 2* яркий тому пример.

                                          А в .NET походу спасовали при первой сложности (хотя все же думаю какие-то частные случаи они поддержали, не верю что все так плохо)
                                            0
                                            Хотя я понимаю почему они не реализовали.

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

                              –1

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

                                –1
                                1. Можете показать, как у вас получилась NP-задача? У меня подсчет дает O(N^4), если Contains делается перебором, и вплоть до O(N^3), если там константная операция (например, хэштаблица).


                                2. Можете привести решение с меньшей О-оценкой, которое при этом было бы не хуже по читаемости?


                                  0
                                  1. в оценочной стоимости есть степень более 2-х-полиномиальная сложность, грубо но можно отнести к NP. Хотя она имеет и более лучшее решение, потому в этом плане к NP относить ее неправильно. Но Ваш приведенный пример приближает к этому
                                  2. с ходу не скажу, но в инете можно найти хорошие решения замены поиска подстроки на более оптимальные варианты, про остальное надо посидеть, подумать и поэкспериментировать
                                    0
                                    грубо но можно отнести к NP

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


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


                                    с ходу не скажу, но в инете можно найти

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


                                    В этом и заключается отличие конфигуратора от разработчика и программиста

                                    В чем конкретно оно заключается в этом примере?

                                      –1

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

                                        0
                                        знаю что можно решить без поиска подстроки

                                        Так, давайте начнем с простого вопроса: а при чем тут вообще поиск подстроки?


                                        использование подстроки-не самое лучшее решение

                                        А я и не использую подстроку.


                                        но быстро хорошее решение не готовы предоставить

                                        Я предоставил хорошее решение — в рамках тех критериев качества, которые обсуждаются в этом посте. Хотите критиковать? Объясните, почему это решение плохо с точки зрения этих критериев качества.

                                          –3

                                          уже писал выше-использование Contains и from-from

                                            0

                                            При чем тут подстрока? В задаче вообще строки (strings) не используются.


                                            Каким образом это говорит, что решение не удовлетворяет критерим качества?

                • НЛО прилетело и опубликовало эту надпись здесь
                    0
                    Кстати у SQL, на самом деле, есть как минимум один недостаток даже просто на уровне запросов. Они зачем-то разделили логику отбора на типы JOIN'ов и собственно WHERE. То есть для меня загадка, почему они не сделали просто условие IN JOIN, чтобы можно было писать:
                    SELECT FROM A JOIN B ON A.x=B.y WHERE IN JOIN A AND IN JOIN B — что эквивалентно INNER JOIN
                    SELECT FROM A JOIN B ON A.x=B.y WHERE IN JOIN A — эквивалентно LEFT JOIN
                    SELECT FROM A JOIN B ON A.x=B.y WHERE IN JOIN A OR IN JOIN B — эквивалентно UNION / FULL JOIN
                    Тогда такого огорода как в задаче с 2* (смотри решение внизу на SQL) не пришлось бы городить.
                      0

                      Самый серьезный недостаток SQL — его как бы не существует как единого языка. В каждой БД свои нюансы и моменты реализации. Так что для каждый БД по сути нужно учить свой sql диалект.

                        0
                        Вот в lsFusion эта проблема решается — она разными БД работает, а язык один
                          0

                          В обычных языках программирования эта проблема решается условными query builder или orm. Проблема в том, что даже sql-based база данных подвести под одну гребенку получается довольно плохо.


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


                          Боюсь в общем случае эта проблема так сложна, что решать ее не имеет смысла.

                          0
                          Это да. Но здесь-то речь идет о самом базовом функционале, который в SQL-92 есть.
                          0

                          1) WHERE взаимозаменяемо только с INNER JOIN.
                          Для остальных типов JOIN есть серьезные нюансы.


                          2) С каких пор UNION / FULL JOIN эквивалентны?


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

                            0
                            Для остальных типов JOIN есть серьезные нюансы.

                            Я знаю, серьезные, но не очень серьезные. Так как это преобразование из условия в типы JOIN и собственно WHERE (например как в задаче 2* и ее решении на SQL) и пришлось реализовывать. И каждый раз про себя матерился, что почему вот это сама СУБД не делает, у нее есть вся информация для этого, в частности статистика. Ладно там constraint'ы, trigger'ы и materialized без ограничений на view, там инкрементальность нужна нормальная и это существенно более сложная задача. Но преобразование как в задаче 2*, могли бы сделать, хотя бы Oracle, все таки компания с капитализацией под 150 млрд.
                            2) С каких пор UNION / FULL JOIN эквивалентны?

                            А я и не говорил что они эквивалентны, они просто решают одну задачу — OR оператор, то есть:
                            SELECT FROM A JOIN B ON A.x=B.y WHERE IN JOIN A OR IN JOIN B, если x и y ключи, можно сделать как
                            SELECT A.x FROM A UNION SELECT B.y FROM B, а можно:
                            SELECT COALESCE(A.x, B.y) FROM A FULL JOIN B ON A.x=B.y
                            Кстати, в том же постгрес приходится UNION использовать, так как в FULL JOIN шаг влево, шаг вправо — FULL JOIN is only supported with merge-joinable join conditions.
                            3) В SQL масса других странных вещей. Например, есть HAVING, который полностью эквивалентен подзапросу и WHERE на подзапрос. Причем с WHERE код более читаемый, т.к. уже можно использовать алиасы для вычисляемых колонок.

                            Да, HAVING меня тоже всегда забавлял. И мы его при компиляции не используем, так как смысла в нем достаточно мало.
                              0
                              Я знаю, серьезные, но не очень серьезные. Так как это преобразование из условия в типы JOIN и собственно WHERE (например как в задаче 2* и ее решении на SQL) и пришлось реализовывать.

                              Не совсем понял о чем вы… Сейчас условия в JOIN применяются к исходным данным. WHERE к результатам соединения.
                              У вас предикат в JOIN остался, но добавилось еще многословное объяснение в WHERE какие строчки оставлять, то, что сейчас подразумевается типом JOIN. Выглядит более заумно, а дает ли дополнительные возможности?


                              А я и не говорил что они эквивалентны, они просто решают одну задачу — OR оператор

                              Я думал что совершенно разные. UNION работает с множествами однотипных данных, грубо говоря ставит таблички с данными одну за другой по вертикали.
                              JOIN работает с разнотипными данными. По аналогии — ставит разные таблички рядом друг с другом по горизонтали.
                              То что вы продемонстрировали в примере — это выхолощенный случай когда выбирается одно ключевое поле., соответственно разницы нет.
                              Конечно, можно извращаться — сводя разнотипные данные к однотипным добивая их NULL колонками. Но только не от хорошей жизни.

                                0
                                Не совсем понял о чем вы… Сейчас условия в JOIN применяются к исходным данным. WHERE к результатам соединения.

                                Речь не про условия в JOIN. В то же Oracle их можно и в JOIN и в WHERE писать. Речь про типы JOIN.
                                У вас предикат в JOIN остался, но добавилось еще многословное объяснение в WHERE какие строчки оставлять, то, что сейчас подразумевается типом JOIN. Выглядит более заумно, а дает ли дополнительные возможности?

                                Дает в случаях когда есть OR.
                                То есть SELECT FROM A JOIN B JOIN C JOIN D WHERE (IN JOIN A OR IN JOIN B) AND (IN JOIN C OR IN JOIN D), потребует от вас написания четырех подзапросов через UNION (собственно это и есть задача с думая звездочками)
                                То что вы продемонстрировали в примере — это выхолощенный случай когда выбирается одно ключевое поле., соответственно разницы нет.

                                Тоже самое будет и с двумя и с тремя ключевыми полями. И в том числе когда есть не ключевые поля. Я вам больше скажу, в Postgres FULL JOIN вообще в частных случаях поддерживается, и там именно UNION'ами выкручиваются.
                                +1
                                Например, есть HAVING, который полностью эквивалентен подзапросу и WHERE на подзапрос

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

                                в смысле эквивалентен и смысла мало?
                                Напишите, пожалуйста, вот такой запрос на lsfusion и покажите, в какой SQL запрос он скомпилируется

                                select
                                    department_id,
                                    array_agg(id) as employees,
                                    sum(tax) as tax
                                from employee
                                group by
                                    department_id
                                having
                                    sum(salary) > 100000 and
                                    count(*) < 10 and
                                    count(distinct gender) = 2
                                

                                  0
                                  select
                                      department_id,
                                      employees,
                                      tax
                                  from
                                  (
                                     select
                                         department_id,
                                         array_agg(id) as employees,
                                         sum(tax) as tax,
                                         sum(salary) as salary,
                                         count(*)  as cnt,
                                         count(distinct gender) as genders
                                     from employee
                                     group by
                                         department_id
                                  )
                                  where
                                      salary > 100000 and
                                      cnt < 10 and
                                      genders = 2
                                    0
                                    да, согласен, просто неверно прочитал «Например, есть HAVING, который полностью эквивалентен подзапросу и WHERE на подзапрос» — почему-то у менять осталось впечатление, что будет что-то вроде: where (select sum(salary) ...) > 100000

                                    Не совсем согласен, что HAVING не нужен, но согласен, что функционально его можно заменить. Можете привести код на lsfusion или Вы не по этой части?
                                      0
                                      Я же ниже уже писал запрос. Или речь о каком-то другом?

                                      Если планировщик запроса HAVING никак не оптимизирует, то это просто синтаксический сахар.
                                      Недавно столкнулся с тем, что Oracle почему-то не понимает, что в запросе SELECT x FROM (SELECT x,… UNION SELECT x,...) WHERE x = 444 не догадывается «протолкнуть» выражение внутрь (он честно считает вложенный запрос, а потом фильтрует. PostgreSQL и подавно этого не умеет. Поэтому нам приходится в компиляторе запроса это делать самостоятельно. В итоге SQL запросы часто получаются очень странными с точки зрения человека (там дублируются подзапросы), но зато они гораздо эффективнее выполняются.
                                        0

                                        "Нет повести печальнее на свете, чем повесть о SQL-оптимизаторе"!


                                        Работаю с MS SQL 2008R2 — сложилось впечатление, что ядро оптимизатора там серьезно не меняли еще со времен Sybase.
                                        Некоторые запросы оптимизируются очень круто. Но иногда тупит на самых примитивных запросах, чем вводит меня в полный ступор.


                                        Из последнего, фрагмент запроса:


                                        SELECT 
                                           ...
                                        FROM 
                                            sometable t1
                                                 INNER JOIN sometable t2
                                                      ON t1.field = t2.field AND
                                                         t1.id <> t2.id
                                        WHERE
                                            ...

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


                                        Добавляю в WHERE условие:


                                        WHERE
                                             t1.field IS NOT NULL AND 
                                             t2.field IS NOT NULL AND 
                                             ...

                                        О чудо! Выполняется за доли секунды. Записей с заданным field порядка 1% от общего количества.


                                        Что мешает вложить это магическое заклинание в оптимизатор — хз. Ведь очевидно, что предикат в JOIN будет истинным только если field не NULL...

                                          0
                                          У постгрес кстати та же беда, помню при компиляции запросов приходилось искать такие случаи (когда IS NOT NULL существенно уменьшает статистику) и докидывать такие условия в WHERE.
                                            0
                                            справедливости ради надо сказать — сейчас 2019 год, с 2008R2 многое поменялось. Но вообще странно, да.
                                              0

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

                                              0
                                              2017 сервер. что так, что иначе — план запроса одинаковый.
                                                0

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


                                                Как вы умудрились все это восстановить по моему фрагменту запроса — загадка.


                                                У меня для запроса с доп. проверкой на NULL выбирается план с MERGE JOIN и статистика такая:


                                                Table 'Worktable'. Scan count 1130, logical reads 20345, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
                                                Table 'sometable'. Scan count 2, logical reads 73908, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
                                                 SQL Server Execution Times:
                                                   CPU time = 390 ms,  elapsed time = 1195 ms.

                                                Для запроса без доп. проверки на NULL выбирается план с HASH JOIN и статистика такая:


                                                Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
                                                Table 'sometable'. Scan count 2, logical reads 73908, physical reads 4822, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
                                                
                                                 SQL Server Execution Times:
                                                   CPU time = 27315782 ms,  elapsed time = 27328614 ms.
                                                  0
                                                  Использовал предоставленные данные. ~1кк записей и 1% данных. =)

                                                  Создал две таблицы, заполнил и запустил. (а потом ещё индексами покрыл разными и подёргал по-разному)

                                                  Что то какой то подозрительный план запроса во втором случае. Вообще не трогающий таблицу Worktable. =)

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

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

                                                  ЗЫ: нет, я не говорю, что он НИКОГДА не лажает (иначе у меня бы работы небыло), но в данном конкретном случае проблема, мне кажется, была не на его стороне.
                                                    0
                                                    Я только что проверил на SQL 2019, да, косячит. И в принципе понятно почему: он берет таблицу sometable t1 дальше смотрит его предикаты видит что в предикате t1.field=t2.field есть t2, которую он еще не считал, и благополучно этот предикат игнорирует, а должен был построить виртуальный предикат t1.field IS NOT NULL и использовать его. А он так не умеет.
                                                      0
                                                      Сможете написать тестовый скрипт это показывающий? )

                                                      Ну, кстати, предикат она умеет вставлять.

                                                      image

                                                        0
                                                        У меня целая статья на эту тему будет скоро.

                                                        Но вообще возьмите любую большую таблицу которую не жалко, добавьте туда поле, постройте индекс, заполните 100 полей и сделайте запрос.

                                                        Вот план 1
                                                        SELECT * FROM ShipmentDetail s1, ShipmentDetail s2 WHERE s1.sd=s2.sd AND s1.id <> s2.id AND s1.sd IS NOT NULL AND s2.sd IS NOT NULL

                                                        image

                                                        Вот план 2
                                                        SELECT * FROM ShipmentDetail s1, ShipmentDetail s2 WHERE s1.sd=s2.sd AND s1.id <> s2.id

                                                        image
                                                          0
                                                          =)) Скрины такого размера доставили много радости. (как и русская локализация)

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

                                                          Но, спасибо за интересный момент. =)
                                                          Мне будет интересно почитать статью.

                                                          ЗЫ: Ирония в том, что запросы
                                                          SELECT * 
                                                          FROM #tmp1 s1, #tmp1 s2 
                                                          WHERE s1.ID=s2.ID 
                                                            AND s1.GUID1 <> s2.GUID1          
                                                          OPTION (MAXDOP 1)   
                                                          
                                                          SELECT * 
                                                          FROM #tmp1 AS t1
                                                          JOIN #tmp1 AS t2 ON t2.[ID] = t1.[ID]
                                                                          AND t2.GUID1 <> t2.GUID1      
                                                          OPTION (MAXDOP 1)  


                                                          имеют разные планы. Причём последний — быстрее.
                                                          вполне вероятно что проблема в этом.

                                                          и запрос
                                                          SELECT * 
                                                          FROM #tmp1 AS t1
                                                          JOIN #tmp1 AS t2 ON t2.[ID] = t1.[ID]
                                                                          AND t2.GUID1 <> t2.GUID1      
                                                          OPTION (MAXDOP 1)  


                                                          быстрее

                                                          SELECT * 
                                                          FROM #tmp1 s1, #tmp1 s2 
                                                          WHERE s1.ID=s2.ID 
                                                            AND s1.GUID1 <> s2.GUID1 
                                                            AND s1.ID IS NOT NULL 
                                                            AND s2.ID IS NOT NULL            
                                                          OPTION (MAXDOP 1)   
                                                            0
                                                            И вы индекс по ID вообще построили? В этом же вся суть.

                                                            Ну и во времянках нет clustered index'а. Это все таки не Postgres. Там в принципе другие планы, можете попробовать не на времянках.

                                                            Возможно предикат она может добавлять, но видимо это делает грубо говоря в конце когда план уже построен и scan выбран, а должен сначала добавить предикат, а потом уже выбирать scan/seek или что там будет.
                                                              0
                                                              эээ. Да, построил. (ну, кластерный у меня по GUID столбцу) Потом ещё покрывающих индексов отсыпал до кучи.
                                                              И во времянках есть кластерные индексы. (и это видно в плане запроса)
                                                              И планы (и статистика) по ним строятся как и по обычным таблицам.

                                                              Не думаю, что логика там такая топорная, если честно.

                                                              Ну да не суть )

                                                              1. Да, данная проблема может быть в таких запросах, хоть оптимизатор и умеет сам ставить этот предикат.
                                                              2. Старый синтаксис хреново обрабатывается сервером.
                                                              3. Пути оптимизатора неисповедимы, поэтому стоит брать в свои руки критичные места.

                                                              Такие итоги устроят? )
                                                                0
                                                                Так а сбросьте какие у вас планы получаются? А то по тому скрину непонятно.
                                                                  0
                                                                  Блин.
                                                                  Я уже удалил тестовый пример (Спасибо голангу, блин), а рабочее время кончилось.
                                                                  Попробуйте написать тестовый пример сами — всё же это намного удобнее.
                                  0
                                  SELECT FROM A JOIN B ON A.x=B.y WHERE IN JOIN A

                                  GROUP SUM sum(Detail d) IF

                                  не знаю, что скажут носители английского, но для меня это всё выглядит очень коряво.
                                    0
                                    GROUP SUM sum(Detail d) IF

                                    Можно префиксно писать: GROUP SUM IF f(a) THEN g(a). Но так дольше, а IF очень частая операция. Но вообще тут речь шла не про lsFusion, а именно про SQL.
                                      0
                                      речь про то, что sql очень близок к анлийскому языку, а ваши предложения что в lsFusion, что в самом sql звучат (как мне кажется) коряво.
                                      почему не
                                      SELECT FROM A JOIN B ON A.x=B.y WHERE EXISTS ROW IN A
                                      

                                      про GROUP — IF точно не к месту (WHERE/FOR?), и в целом конструкция вызывает сомнения.
                                      ИМХО стоит обсудить сначала конструкции языка с носителями английского, чтобы код выглядел максимально естественно.
                            +1
                            А смысл? Тот же SQL что и был, только скобочек добавилось, сложность выборки, как была так и осталась. Написание каким было таким и осталось. Оборачивать выборку как раньше было нужно так и до сих пор нужно.
                              0
                              сложность выборки, как была так и осталась

                              Вот, например, задача со звездочкой. Сможете написать такое же на SQL? Вы считаете, что это одинаково сложно?
                                0
                                На SQL это напишет любой дурак, потому что задача — элементарная (для SQL).
                                А вот в чем смысл вашей функциональной модели и чем она лучше — я так и не понял. Вы придумали для нее синтаксис, но явно забыли придумать все остальное.
                                  0

                                  Если она элементарная, то напишите, пожалуйста. Я обновлю статью с решением на sql.

                                  0
                                  SELECT 
                                     pl.PersonAID
                                    ,pf.PersonAID
                                    ,pff.PersonAID
                                  FROM Persons                 AS p
                                  --Лайки                      
                                  JOIN PersonRelationShip      AS pl ON pl.PersonAID = p.PersonID
                                                                    AND pl.Relation  = 'Like'
                                  --Друзья                     
                                  JOIN PersonRelationShip      AS pf ON pf.PersonAID = p.PersonID 
                                                                    AND pf.Relation = 'Friend'
                                  --Друзья Друзей              
                                  JOIN PersonRelationShip      AS pff ON pff.PersonAID = pf.PersonID
                                                                     AND pff.PersonBID = pl.PersonID
                                                                     AND pff.Relation = 'Friend'
                                  --Ещё не дружат         
                                  LEFT JOIN PersonRelationShip AS pnf ON pnf.PersonAID = p.PersonID
                                                                     AND pnf.PersonBID = pff.PersonBID
                                                                     AND pnf.Relation = 'Friend'
                                  WHERE pnf.PersonAID IS NULL 


                                  Это сложно?
                                  Можно по аналогии с функционалом сделать через экзисты.
                                  А можно ещё через рекурсию указывать количество «рук» друзей.
                                  Итд итп.

                                  ЗЫ: единственное, это MS SQL, но не думаю что он сильно отличается от постгре.
                                    0

                                    Как бы да. А задача с двумя звездочками как тогда будет выглядеть?

                                      0
                                      Как бы нет (или вы боретесь просто за то что бы буковок было меньше?).
                                      «логических» связок столько же сколько и в функциональном примере.
                                      Каждый вызов функции — такой же запрос к «структуре» где хранятся взаимоотношения. (таблице)
                                      Это, собственно, и есть задача с двумя звёздочками.
                                      Если человек лайкает кого то, и если дружит с человеком, который дружит с тем кого лайкает — он его выберет.

                                      ЗЫ: вам выше правильно заметили. Вы придумали синтаксис (ок), но смысла в этом нет, потому что он не добавляет ни прозрачности, ни простора для оптимизации ни каких то ещё полезностей. Та же логика — вид сбоку. =)
                                        0

                                        Я не совсем понял какие колонки у вас в PersonRelationShip потому как вы там и к PersonID и к PersonAID и к PersonBID обращаетесь

                                          –1
                                          Вообще, если предположить что вместо pf.PersionID и pl.PersionID у вас pf.PersionBID и pl.PersionBID, то у вас получилось likes(a,b) AND friends(a,c) AND friends(b,c) AND NOT friends(a,b). Извините, что перевожу на функциональную, у меня от реляционной мозг сводит. Но likes(a,b) AND friends(a,c) AND friends(c,b) AND NOT friends(a,b) вроде не равно likes(a, c) AND NOT friends(a, c) AND (friends(a, b) OR friends(b, a)) AND (friends(b, c) OR friends(c, b)). Хотя надо конечно в булевом анализаторе проверить.
                                          Я конечно могу ошибаться но без UNION/FULL JOIN вы в двух звездочках не обойдетесь, так как в задаче с двумя звездочками OR'ы есть.
                                            +1
                                            Извините, что перевожу на функциональную, у меня от реляционной мозг сводит.

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


                                            Пусть имеется некоторый набор фактов


                                            likes(john, alice).
                                            likes(john, bob).
                                            likes(eve, bob).
                                            
                                            friends(john, bob).
                                            friends(bob, alice).
                                            friends(eve, alice).

                                            И тогда мы можем написать такой "запрос":


                                            suggestion(A,B,C) :- likes(A,C),
                                                                (friends(A,B); friends(B,A)),
                                                                (friends(B,C); friends(C,B)),
                                                                not(friends(A,C)).

                                            Выполним его и получим все варианты A,B,C, удовлетворяющие условию


                                            ?- suggestion(A,B,C).
                                            A = john,
                                            B = bob,
                                            C = alice ;
                                            A = eve,
                                            B = alice,
                                            C = bob ;
                                              0
                                              Функциональную я имел ввиду, что мне гораздо проще понимать логику в терминах функций, а не таблиц, как написал автор. Но может я один такой.

                                              Это все конечно красиво, но вопрос как выполнить этот запрос на SQL. Пролог конечно хороший язык, но я если честно не знаю как он на больших объемах данных работает. Ну и как остальные возможности SQL поддерживает, там группировки всякие и ACID.
                                                0
                                                вопрос как выполнить этот запрос на SQL

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

                                                  0
                                                  Хм. Интересно. А как у нее с ACID? И на сколько у нее мощные оптимизаторы, как она на больших объемах работает? И что с In-memory cache всякими? Просто если этого нет, то уж лучше все таки в SQL компилировать.
                                                    0

                                                    ACID есть. Насчет объёмов не знаю, надо гуглить или проверять.

                                                      +1
                                                      Datomic is a single-writer system. A single thread in a single process is responsible for writing transactions.

                                                      Вот это уже очень стремная штука. То есть по факту под нагрузкой такая база ляжет почти сразу.
                                                        0
                                                        Datomic Cloud is a new kind of database, designed to be:
                                                        — Transactional (ACID compliant and always consistent)
                                                        — Elastically read scalable (built for AWS auto-scaling)
                                                        — Immutable (accumulating facts over time)
                                                        — Queryable (via Datomic datalog, a powerful declarative query language)
                                                        — Flexible (in schema design and modification, deployment topology, and usage scenarios)

                                                        А еще в ней похоже нельзя изменять данные. Применимость соответственно от этого сильно страдает.
                                                          0

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

                                                            0
                                                            У нас немножко разная аудитория с Datomic. Они направлены на программистов, а мы (как и SQL, когда создавался) на бизнес-пользователей.

                                                            Но меня у них на сайте поразило вот что:
                                                            — distributed (conceived with the cloud in mind from day 1)
                                                            — transactional (it is fully ACID compliant and always consistent)

                                                            А как они решили CAP-теорему?
                                                              0
                                                              У нас немножко разная аудитория с Datomic. Они направлены на программистов, а мы (как и SQL, когда создавался) на бизнес-пользователей.

                                                              Вот только в итоге вышло, что бизнес-пользователям не пишут сами SQL и его пишут разработчики/DBA/аналитики.


                                                              А как они решили CAP-теорему?

                                                              А что не так?


                                                              Datomic is a single-writer system. A single thread in a single process is responsible for writing transactions.
                                                                +1
                                                                А dba и аналитики — это разве программисты? Здесь практически та же целевая аудитория.

                                                                CAP теорема значит по сути, что нельзя построить настоящий acid и распределенность. Вот и удивляюсь, что они это заявляют.
                                                                  +2
                                                                  CAP теорема значит по сути, что нельзя построить настоящий acid и распределенность.

                                                                  Нет, не значит. CAP значит, что распределенная система не может быть одновременно consistent, available и partition-tolerant, но не более того. ACID — это требование C. Если вы отказываетесь либо от availability, либо от partitioning tolerance, у вас получается распределенная система с ACID.

                                              0
                                              PersonAID — id персоны, исходной для отношения. (если я кого то лайкаю — то А — это мой id)
                                              PersonBID — id второго человека, к которому применяется отношение (тот, кого я лайкнул)

                                              Можно, конечно, обойтись без исходной таблицы Person, но так немного нагляднее получается.

                                              ЗЫ: Таблица, соответственно, состоит из PersonAID, PersonBID, RelationShip. =)
                                                0
                                                Я не про то, у вас там pf.PersionID. А pf это PersonRelationShip. У вас там три колонки получается PersionID, PersionAID и PersionBID.
                                                  0
                                                  Я ниже уже привёл исправленный вариант, это была опечатка (реально, я бы использовал немного другие имена, что б не так сливались...)
                                                  Спасибо что указали )
                                                  К сожалению, вчерашний коментарий я не могу отредактировать =(
                                              0
                                              Каждый вызов функции — такой же запрос к «структуре»

                                              Очевидно, что целью не ставилось сделать проще для компьютера, целью становилось сделать проще для человека

                                            0
                                            Спасибо за решение. Вставил в статью. Жду решения задачи с 2мя звездочками.
                                            Но Вы реально считаете, что Ваше решение проще? Если допустим взять человека, который не знает SQL.
                                              0
                                              Так оно не правильное, там как минимум колонки не правильные.

                                              И он считает что это решение с двумя звездочками.
                                                0
                                                SELECT 
                                                   pl.PersonAID
                                                  ,pf.PersonAID
                                                  ,pff.PersonAID
                                                FROM Persons                 AS p
                                                --Лайки                      
                                                JOIN PersonRelationShip      AS pl ON pl.PersonAID = p.PersonID
                                                                                  AND pl.Relation  = 'Like'
                                                --Друзья                     
                                                JOIN PersonRelationShip      AS pf ON pf.PersonAID = p.PersonID 
                                                                                  AND pf.Relation = 'Friend'
                                                --Друзья Друзей              
                                                JOIN PersonRelationShip      AS pff ON pff.PersonAID = pf.PersonBID
                                                                                   AND pff.PersonBID = pl.PersonBID
                                                                                   AND pff.Relation = 'Friend'
                                                --Ещё не дружат         
                                                LEFT JOIN PersonRelationShip AS pnf ON pnf.PersonAID = p.PersonID
                                                                                   AND pnf.PersonBID = pff.PersonBID
                                                                                   AND pnf.Relation = 'Friend'
                                                WHERE pnf.PersonAID IS NULL 


                                                Спасибо за уточнение. В одной связи и впрямь ошибся (вечер, однака)
                                                  0
                                                  Спасибо, обновил пост. Так а задачу с 2* решите? У меня это заняло 10 секунд, а сколько займет у вас?
                                                  Разница в том, что нужно смотреть связь Friend в обе стороны. То есть должна быть хоть одна из них.
                                                    0
                                                    Тут вопрос в хранении данных таких связей.

                                                    К примеру
                                                    Связи хранятся в одностороннем виде.
                                                    т.е. если (условно) отношения представлены в виде двух строчек в таблице.
                                                    ID 12352 PersonAID 100 PersonBID 101 RelationShip 'Like'
                                                    ID 12353 PersonAID 101 PersonBID 100 RelationShip 'Like'
                                                    Если вы понимаете о чём я.

                                                    Тогда как то так:
                                                    ;WITH PersonRelationShipCollapsed AS (
                                                      SELECT pl.PersonAID
                                                            ,pl.PersonBI
                                                            ,pl.Relation 
                                                      FROM PersonRelationShip      AS pl 
                                                    
                                                      UNION 
                                                    
                                                      SELECT pl.PersonBID AS PersonAID
                                                            ,pl.PersonAID AS PersonBID
                                                            ,pl.Relation
                                                      FROM PersonRelationShip      AS pl 
                                                    )
                                                    SELECT 
                                                       pl.PersonAID
                                                      ,pf.PersonAID
                                                      ,pff.PersonAID
                                                    FROM Persons                      AS p
                                                    --Лайки                      
                                                    JOIN PersonRelationShipCollapsed  AS pl ON pl.PersonAID = p.PersonID
                                                                                     AND pl.Relation  = 'Like'                                  
                                                    --Друзья                          
                                                    JOIN PersonRelationShipCollapsed  AS pf ON pf.PersonAID = p.PersonID 
                                                                                     AND pf.Relation = 'Friend'
                                                    --Друзья Друзей                   
                                                    JOIN PersonRelationShipCollapsed  AS pff ON pff.PersonAID = pf.PersonBID
                                                                                     AND pff.PersonBID = pl.PersonBID
                                                                                     AND pff.Relation = 'Friend'
                                                    --Ещё не дружат                   
                                                    LEFT JOIN PersonRelationShipCollapsed AS pnf ON pnf.PersonAID = p.PersonID
                                                                                       AND pnf.PersonBID = pff.PersonBID
                                                                                       AND pnf.Relation = 'Friend'
                                                    WHERE pnf.PersonAID IS NULL 


                                                    Хотел описать второй вариант, с экономией строк (когда в каждой строке хранится PersonAID, PersonBID, Relation и RelationTypeID: 1 от А к Б, 2 от Б к А, 3 — взаимно), но такой вариант сводится к первому на самом деле. =)
                                                      0
                                                      А вы уверены насчет плана выполнения такой штуки? Что она реально не будет сначала UNION по всей базе делать? Такой запрос положит всю базу. Правильный запрос я ниже приводил.

                                                      Ну и в любом случае это уже точно не проще, чем SELECT WHERE и булево условие.
                                                        0
                                                        А что у Вас вызывает сомнения в плане? )
                                                        Скорее всего он будет практически идентичен плану вашего запроса:
                                                        6 индекс сиков (в случае поиска по одной персоне) и два скана.
                                                        Или все 8 сканов, если поиск производится «вообще».

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

                                                        В любом случае — а кто вам сказал, что гипотетический функциональный язык сможет сделать это оптимальнее? )
                                                          0
                                                          А что у Вас вызывает сомнения в плане?

                                                          Что она реально UNION выполнит (а если likes мало, а friends много будет очень критично). Насколько она его INLINE'ть сможет.
                                                          В любом случае — а кто вам сказал, что гипотетический функциональный язык сможет сделать это оптимальнее? )

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

                                                            4. Сравнивал Ваш вариант и свой и нашёл ещё ошибку. Да что за жизнь то )

                                                            ;WITH PersonRelationShipCollapsed AS (
                                                              SELECT pl.PersonAID
                                                                    ,pl.PersonBID
                                                                    ,pl.Relation 
                                                              FROM #PersonRelationShip      AS pl 
                                                              
                                                              UNION 
                                                            
                                                              SELECT pl.PersonBID AS PersonAID
                                                                    ,pl.PersonAID AS PersonBID
                                                                    ,pl.Relation
                                                              FROM #PersonRelationShip      AS pl 
                                                            )
                                                            SELECT 
                                                               pl.PersonAID
                                                              ,pf.PersonBID
                                                              ,pff.PersonBID
                                                            FROM #Persons                      AS p
                                                            --Лайки                      
                                                            JOIN PersonRelationShipCollapsed  AS pl ON pl.PersonAID = p.PersonID
                                                                                             AND pl.Relation  = 'Like'                                  
                                                            --Друзья                          
                                                            JOIN PersonRelationShipCollapsed  AS pf ON pf.PersonAID = p.PersonID 
                                                                                             AND pf.Relation = 'Friend'
                                                            --Друзья Друзей                   
                                                            JOIN PersonRelationShipCollapsed  AS pff ON pff.PersonAID = pf.PersonBID
                                                                                             AND pff.PersonBID = pl.PersonBID
                                                                                             AND pff.Relation = 'Friend'
                                                            --Ещё не дружат                   
                                                            LEFT JOIN PersonRelationShipCollapsed AS pnf ON pnf.PersonAID = p.PersonID
                                                                                               AND pnf.PersonBID = pff.PersonBID
                                                                                               AND pnf.Relation = 'Friend'
                                                            WHERE pnf.[PersonAID] IS NULL 
                                                    0
                                                    Тогда это likes(a,b) AND friends(a,c) AND friends(b,c) AND NOT friends(a,b).

                                                    А нужно likes(a, c) AND NOT friends(a, c) AND
                                                    (friends(a, b) OR friends(b, a)) AND
                                                    (friends(b, c) OR friends(c, b)).

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

                                                    YOUR = LAB & FAC & FBC & !FAB
                                                    NEEDED = LAC & !FAC & (FAB | FBA) & (FBC | FCB)

                                                    А теперь берем YOUR & !NEEDED скармливаем его булевому оптимизатору

                                                    Получаем таблицу истинности берем первое TRUE.

                                                    FAB FAC FBA FBC FCB LAB LAC output
                                                    0 1 1 1 0 1 0 1

                                                    Соответственно возьмем A = 1, B =2, C=3, а значит в случаях когда таблица PersonRelationShip
                                                    A B Relation
                                                    1 2 L
                                                    1 3 F
                                                    2 1 F
                                                    2 3 F
                                                    У вас будет ошибка.
                                                      0
                                                      Очевидно, что условия были не очень ясно указаны.
                                                      К примеру, несимметричные отношения, по моему пониманию, подразумевали что нужно брать только тех, кого Я лайкнул и кого выбрал другом человек, которого выбрал другом Я.
                                                      А те, кого я не лайкнул, а кто меня лайкал в таком случае не должны попасть в выборку.

                                                      Но если не важно направление связи — то я описал варианты чуть выше.
                                                      Спасибо за живое участие, посмотрите, вдруг я и там опечатался )
                                                      Руки никак не дойдут создать таки эти таблицы и сделать тесты. Работа-с.
                                                +1

                                                Очевидно, что ваше решение сложнее, оно слишком объёмное. Вы сами запутались в нём (как минимум с именами полей)

                                                +1
                                                Для задачи «со звездочкой», КМК, подходит RDF и SPARQL. Там графы вообще естественная штука.
                                                Но это именно для конкретного случая, когда нужен поиск по базе графов — в большинстве случаев этого не надо от слова «совсем»

                                                З.Ы. И да, это было придумано уже давно и есть имплементации — просто не популярно.
                                                  0
                                                  Так тут не нужны никакие графы (а точнее рекурсия). Это чисто для SQL задача. Вон человек сверху утверждал, что она вообще элементарная, что ее любой дурак напишет.
                                                    +1
                                                    Я немножко не о том.
                                                    Конечно, можно и на SQL можно спроектировать и написать все что угодно.
                                                    Но основная мысль ТС, как я понял, что его новомодный подход может решать задачи проще/лучше, чем известные решения. Иначе бы не было такого срача в комментариях.

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

                                                    Если мы говорим о лучшем синтаксисе/хранилище для задачи — RDF + SPARQL для конкретной задачи выглядит лучше, чем вся эта функциональщина, КМК.

                                                    Это все, что я хотел сказать.
                                                      +1
                                                      Ок, а можете тогда показать как эта задача на RDF+SPARQL выглядит? Потому как здесь решение в 2 строки и с виду достаточно простое. И раз там выглядит лучше значит еще проще/короче должна быть, то есть можно за полминуты сделать.
                                                        0
                                                        Проблема в том, что в жизни, часто надо решать разные задачи в рамках одного большого приложения (например, ERP-системы). Вы предлагаете под каждый тип запроса использовать свою СУБД? Допусти RDF + SPARQL решает задачу со звездочкой. А остальные 8 задач эта связка тоже решает?
                                                          0

                                                          Есть мультипарадигменные БД, типа OrientDB или Cosmos DB.

                                                            0
                                                            Эх, жаль специалистов по ним нету в ветке. Интересно было бы посмотреть как эти задачи решаются на них. Устроить такую «олимпиаду среди баз данных».
                                                            Когда писал решения для этих восьми задач (так как рука набита), у меня ушло непосредственно на решение минут 10. Так что это времени не должно было много занять.
                                                      +1
                                                      Так давайте задачу усложним немного. Пусть они дружат по трое (большой шведской семьей). У меня в решении просто добавится один параметр везде. А как вы такую «дружбу» на графы переведете?
                                                      0
                                                      Ну, во-первых я бы для этого использовал графовую БД.
                                                      И в принципе в РСУБД вы этот код так же можете завернуть в функции и будет читаться так же.
                                                      Я имел ввиду алгоритмическую сложность достать данные с диска.
                                                        0
                                                        И в принципе в РСУБД вы этот код так же можете завернуть в функции и будет читаться так же.

                                                        пример можно?
                                                    0
                                                    del
                                                      0
                                                      Пока отвечал на комментарий выше, самому стало интересно, как решается задача с 2-мя звездочками.

                                                      Собственно скормил платформе эту задачу:
                                                      TABLE likes (Person, Person);
                                                      likes = DATA BOOLEAN (Person, Person) TABLE likes;
                                                      TABLE friends (Person, Person);
                                                      friends = DATA BOOLEAN (Person, Person) TABLE friends;

                                                      run() {
                                                          EXPORT FROM Person a, Person b, Person c WHERE 
                                                              likes(a, c) AND NOT friends(a, c) AND 
                                                              (friends(a, b) OR friends(b, a)) AND 
                                                              (friends(b, c) OR friends(c, b));
                                                      }

                                                      Получил вот такой запрос:
                                                      SELECT a.jkey0,
                                                             a.jkey1,
                                                             a.jkey2
                                                      FROM
                                                        (SELECT t0.key0 AS key0,
                                                                t1.key1 AS key1,
                                                                t0.key1 AS key2,
                                                         FROM likes t0
                                                         JOIN friends t1 ON t1.key0=t0.key1
                                                         JOIN friends t2 ON t2.key1=t1.key1
                                                         AND t2.key0=t0.key0
                                                      UNION 
                                                         SELECT t0.key0 AS key0,
                                                                      t1.key1 AS key1,
                                                                      t0.key1 AS key2,
                                                         FROM likes t0
                                                         JOIN friends t1 ON t1.key0=t0.key1
                                                         JOIN friends t2 ON t2.key1=t0.key0
                                                         AND t2.key0=t1.key1
                                                      UNION 
                                                         SELECT t0.key0 AS key0,
                                                                      t1.key0 AS key1,
                                                                      t0.key1 AS key2
                                                         FROM likes t0
                                                         JOIN friends t1 ON t1.key1=t0.key1
                                                         JOIN friends t2 ON t2.key1=t1.key0
                                                         AND t2.key0=t0.key0
                                                      UNION 
                                                         SELECT t0.key0 AS key0,
                                                                      t1.key0 AS key1,
                                                                      t0.key1 AS key2
                                                         FROM likes t0
                                                         JOIN friends t1 ON t1.key1=t0.key1
                                                         JOIN friends t2 ON t2.key1=t0.key0
                                                         AND t2.key0=t1.key0) s
                                                      LEFT JOIN friends t3 ON t3.key1=s.key2
                                                      AND t3.key0=s.key0
                                                      WHERE t3.key0 IS NULL

                                                      Но что-то я засомневался, кажется можно проще все же сделать. Кто нибудь знает правильное решение? Это вроде известная задача.
                                                        0
                                                        Это с двумя звёздочками?

                                                        Если отношения
                                                        1 3 L
                                                        1 2 F
                                                        2 3 F
                                                        Какие данные вернёт этот скрипт?

                                                        *там в selec части немного не те данные и лишние запятые
                                                        +1

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

                                                          0
                                                          А могут быть разные трактования?
                                                          Функция — в математике соответствие между элементами двух множеств, установленное по такому правилу, что каждому элементу одного множества ставится в соответствие некоторый элемент из другого множества

                                                          Например, из одного примера: bought: X -> Y, где X — это множество кортежей (Customer, Product, INTEGER), а Y — NUMERIC[14,3].
                                                            0
                                                            А могут быть разные трактования?

                                                            Конечно, могут. Особенно в части свойств.


                                                            Функция — в математике...

                                                            Вы вот пишете про математику, а в тексте пишете про функции из программирования ("Такая база данных оперирует функциями [...] точно также, как и в классическом программировании"), хотя это два разных понятия.


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

                                                              0
                                                              В декларативной логике, когда отсутствует состояние, функция из программирования = функции математической. Это в императивной логике функция может выполняться и изменять какое-то состояние.

                                                              Не очень понимаю, какими свойствами может обладать отображение одного множества на другое?
                                                                +1
                                                                функция из программирования = функции математической

                                                                … и это ваш случай?


                                                                какими свойствами может обладать отображение одного множества на другое?

                                                                Например, биективности или ее отсутствия.

                                                                  0
                                                                  Очевидно, что они в общем случае ни сюръективны, ни инъективны.

                                                                  Давайте более предметно. Это тоже самое, что спросить какими свойствами обладает стол. Очень многими — вопрос лишь в том, какие именно нужны в конкретном контексте. Если Вам интересно какое-то конкретно свойство, то спрашивайте.
                                                                    +1

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


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

                                                                      0

                                                                      Не является требованием для математического определения функции — и такие и такие бывают.
                                                                      Вы с чистой функцией перепутали.
                                                                      А что имеете ввиду?

                                                                        +1
                                                                        Не является требованием для математического определения функции — и такие и такие бывают

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


                                                                        Вы с чистой функцией перепутали.

                                                                        … что именно перепутал? Вот тут википедия пишет нам:


                                                                        Thus a pure function is a computational analogue of a mathematical function.
                                                                          –1

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

                                                                            +1

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

                                                                              –1

                                                                              Вы к вопросу о непрерывности и дискретности толкуете?
                                                                              Как уже было сказано, в СУБД область определения функции дискретна и определена содержимым самой СУБД.

                                                                                +1

                                                                                Нет, я "толкую" к списку свойств этих самых функций (и заодно операций над ними).

                                                                                  0

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

                                                                                    +1
                                                                                    Какие ещё свойства, кроме детерминированности интересуют?

                                                                                    Отсутствие побочных эффектов, конечно же.


                                                                                    Про детерминированность ответили уже.

                                                                                    В том смысле, что согласились, что ее нет, а потому это точно не математическая функция? Или что-то другое?

                                                                                      –1

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

                                                                                        +2

                                                                                        Это пока не спор, а всего лишь попытка выяснить, что же автор статьи понимает под центральным термином статьи.

                                                                                          –1
                                                                                          Вы неверно определяете центральный термин статьи и уводите дискуссию в сторону. Статья черным по белому про функциональные субд, а не про функции.
                                                                                            +2

                                                                                            Хорошо, что такое «функциональная СУБД»? В определении, понятное дело, не должно быть ни одного упоминания функций.

                                                                        0
                                                                        Будут детерминированы для текущего состояния базы данных. Так как функции рассчитываются от других функций, то на узлах дерева будут конечные точки — DATA-функции. Их значения и есть состояние базы данных.
                                                                          +1
                                                                          Будут детерминированы для текущего состояния базы данных.

                                                                          То есть только что сказанное вами выше "когда отсутствует состояние, функция из программирования = функции математической" к вашему случаю неприменимо, потому что у вас состояние есть. Следовательно, ваши функции — это не функции из математики.

                                                            0
                                                            Это не функциональная СУБД, а объектно-ориентированная СУБД. Лучше читать в англоязычной вики. Тема очень старая.
                                                            Первые публикации об объектно-ориентированных базах данных появились в середине 80-х годов.

                                                            Их кстати, изначально было два типа — чистые объектные и гибридные — объектная модель поверх классического движка
                                                              0
                                                              Чтобы не разводить демагогию давайте сравнивать с какой-то конкретной реализацией ООБД. В википедии нету ни одной ссылки на такие. Можете привести пример production-ready СУБД?
                                                              Как я раньше уже писал функциональные языки появились тоже очень давно, но популярность у них появилась только в последнее время (с появлением Scala, Clojure, поддержкой в JavaScript и прочим).
                                                                0
                                                                Как выше уже заметил lair, демагогия в определениях как раз идет от Вас. Стандартный SQL — уже декларативный [почти] функциональный язык. У Вас же просто объектное (причем простейшее, без вложенных объектов) расширение над RDBMS с попыткой изобрести велосипед новый язык запросов.

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

                                                                Еще можно посмотреть по популярности тут.
                                                                  0
                                                                  Давайте по существу. Я конкретно внизу статьи описал в чем преимущество по сравнению с SQL. Вы согласны, что такой язык проще? Решите задачу с двумя звездочками на SQL?

                                                                  Кроме того, это далеко не объектно ориентированная БД. В ООБД (как и в ООП) все крутится вокруг объекта. У него есть методы, поля и т.д. А что делать, когда функция от нескольких объектов? Она к какому из них должна относится с точки зрения ООБД?
                                                                    0
                                                                    По существу ООСУБД прошли долгий практический путь и сначала стоит изучить, что и как там сделано.
                                                                    А на Ваш хелловорлд в две таблички с очевидными выводами смотреть неинтересно — здесь не с чем спорить.

                                                                    Я бы развернул Ваш вопрос ровно наоборот — почему ООСУБД не используются повсеместно, если такие удобные?
                                                                      0
                                                                      По существу. Повторюсь, я описывал не ООСУБД, а функциональную СУБД. Если будет язык программирования (а они есть), в котором не будет методов, а только объекты и функции. Он будет объектно-ориентированным или нет?

                                                                      Я бы развернул Ваш вопрос ровно наоборот — почему ООСУБД не используются повсеместно, если такие удобные?

                                                                      Потому что они неудобны. А функциональная СУБД может быть таковой.

                                                                      Вы уходите от ответа. Мой пример был сделан за 10 секунд. Вы определитесь — на SQL его сделать просто или нет? Если да, то, пожалуйста, напишите ответ, и мы закончим этот бессмысленный спор.
                                                                        0
                                                                        Если будет язык программирования (а они есть), в котором не будет методов, а только объекты и функции. Он будет объектно-ориентированным или нет?

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

                                                                          0
                                                                          Смотря как ввести определение. Если даже оно не подходит под общепризнанное, то это не самоцель. Можно назвать Функцио-ориентированная СУБД, если так будет лучше.
                                                                            0
                                                                            Смотря как ввести определение.

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


                                                                            Ну и да, это начинает выглядеть как типичный marketing bullshit: давайте скажем, что наш продукт — X, потому что X сейчас модно и на волне, а внизу мелким шрифтом напишем "X означает Y".

                                                                              0
                                                                              вы называете функцией не то же самое

                                                                              Функция как раз точно такая же, как и в языках программирования. Речь шла о термине «функциональный». Если он вам не нравится, то я предложил использовать другой.

                                                                              Ну и да, это начинает выглядеть как типичный marketing bullshit: давайте скажем, что наш продукт — X, потому что X сейчас модно и на волне, а внизу мелким шрифтом напишем «X означает Y».

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

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


                                                                                Я описывал концепцию и хотел обсудить именно ее.

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

                                                              +1

                                                              Я сломался уже на первом примере


                                                              CLASS Sku;
                                                              name = DATA STRING[100] (Sku);
                                                              price = DATA NUMERIC[10,5] (Sku);

                                                              Почему это одна таблица, а не две? Движок объединяет таблицы с одинаковыми наборами ключевых полей?

                                                                0
                                                                name и price — это не таблицы, а функции. Именно классы соответствуют таблицам, а функции — полям.
                                                                Движок объединяет таблицы с одинаковыми наборами ключевых полей?

                                                                Да. Под каждый класс создается своя таблица, а функции раскидываются по таблицам с соответствующими ключами. Но при необходимости этим можно управлять вручную и создать под классы несколько таблиц, в явную указав в какую именно «положить» функцию.
                                                                  0
                                                                  CLASS a;
                                                                  CLASS b;
                                                                  CLASS c;
                                                                  
                                                                  f1 = DATA STRING[10] (a);
                                                                  f2 = DATA STRING[10] (a, c);
                                                                  f3 = DATA STRING[10] (b);
                                                                  f4 = DATA STRING[10] (a, c);
                                                                  

                                                                  такая декларация корректна? создадутся 3 таблицы? (для f1, f3, f2+f4)?
                                                                    0
                                                                    Да, совершенно верно.
                                                                      0
                                                                      у вас определённо не хватает ссылок между статьями по isFusion )
                                                                        0
                                                                        Так там справа же есть ссылки :) К блогу же компании привязано…
                                                                0
                                                                решение задачи * на SQL

                                                                select
                                                                    AB.first_person_id as a_person_id,
                                                                    AB.second_person_id as b_person_id,
                                                                    BC.second_person_id as c_person_id
                                                                from friends as AB
                                                                    inner join friends as BC on
                                                                        BC.first_person_id = AB.second_person_id
                                                                where
                                                                    not exists (select * from friends as tt where tt.first_person_id = AB.first_person_id and tt.second_person_id = BC.second_person_id) and
                                                                    exists (select * from likes as tt where tt.first_person_id = AB.first_person_id and tt.second_person_id = BC.second_person_id)
                                                                


                                                                Задача с ** на MS SQL может выглядеть примерно так:

                                                                with cte_friends as (
                                                                	select
                                                                		t.first_person_id,
                                                                		t.second_person_id,
                                                                		c.dummy_first_person_id,
                                                                		c.dummy_second_person_id
                                                                	from friends as t
                                                                		cross apply (values
                                                                			(t.first_person_id, t.second_person_id),
                                                                			(t.second_person_id, t.first_person_id)
                                                                		) as c(dummy_first_person_id, dummy_second_person_id)
                                                                )
                                                                select distinct
                                                                	AB.first_person_id as a_person_id,
                                                                	AB.second_person_id as b_person_id,
                                                                	BC.second_person_id as c_person_id
                                                                from cte_friends as AB
                                                                	inner join cte_friends as BC on
                                                                		BC.dummy_second_person_id = AB.dummy_first_person_id
                                                                where
                                                                	not exists (select * from friends as tt where tt.first_person_id = AB.first_person_id and tt.second_person_id = BC.second_person_id) and
                                                                	exists (select * from likes as tt where tt.first_person_id = AB.first_person_id and tt.second_person_id = BC.second_person_id)
                                                                


                                                                Я не тестировал запросы, но что-то подобное.
                                                                  0
                                                                  Такое уже кидали, и есть небольшой вопрос как это все будет выполняться. Но в любом случае вопрос, это точно проще, чем:
                                                                  EXPORT FROM Person a, Person b, Person c WHERE 
                                                                          likes(a, c) AND NOT friends(a, c) AND 
                                                                          (friends(a, b) OR friends(b, a)) AND 
                                                                          (friends(b, c) OR friends(c, b));
                                                                    0
                                                                    нет, явно не проще. В целом мне нравится Ваш подход, не уверен, нравится ли мне синтаксис обычных запросов, сходу он не читается так легко как SQL, но это может быть вопросом привычки, надо более вдумчиво прочитать статью.
                                                                    В принципе, думаю, что-то подобное Вашему выражению можно написать, например, в postgresql используя простые функции, каторые будут заинлайнены (я, к сожалению, не использую postrgresql в продакшн поэтому не 100% уверен).
                                                                    Нет времения сейчас пробовать, потому что рабочий день же, но, если не забуду, попробую позже
                                                                      0
                                                                      ну вот на постегрс можно написать что-то подобное прям сходу. Не уверен насчет производительности.

                                                                      dbfiddle.uk/?rdbms=postgres_11&fiddle=367e2b43f668490e4190fbecee647025

                                                                      create table friends (first_person_id int, second_person_id int);
                                                                      create table likes (first_person_id int, second_person_id int);
                                                                      create table person (id int, name varchar);
                                                                      
                                                                      create function person_friend_with_person(_first_person_id int, _second_person_id int)
                                                                      returns boolean
                                                                      as $$
                                                                      	select exists (select from friends where first_person_id = _first_person_id and second_person_id = _second_person_id);
                                                                      $$
                                                                      language sql;
                                                                      
                                                                      create function person_likes_person(_first_person_id int, _second_person_id int)
                                                                      returns boolean
                                                                      as $$
                                                                      	select exists (select from likes where first_person_id = _first_person_id and second_person_id = _second_person_id);
                                                                      $$
                                                                      language sql;
                                                                      
                                                                      insert into person select 1, 'A';
                                                                      insert into person select 2, 'B';
                                                                      insert into person select 3, 'C';
                                                                      
                                                                      insert into friends select 1, 2;
                                                                      insert into friends select 2, 3;
                                                                      
                                                                      insert into likes select 1, 3;
                                                                      
                                                                      select *
                                                                      from person as A, person as B, person as C
                                                                      where
                                                                      	(person_friend_with_person(A.id, B.id) or person_friend_with_person(B.id, A.id)) and
                                                                          (person_friend_with_person(B.id, C.id) or person_friend_with_person(C.id, B.id)) and
                                                                          person_likes_person(A.id, C.id) and not person_friend_with_person(A.id, C.id)
                                                                      
                                                                        0
                                                                        Что-то мне подсказывает, что по производительности это будет очень плохо. Особенно имея большой опыт работы с планировщиком postgres.
                                                                          0

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

                                                                            0
                                                                            Мой здравый смысл подсказывает, что если это и так, то только из-за or в условии.

                                                                            Это факт, or в жизни конечно встречается реже чем and, но не сильно. И поэтому игнорировать его это даже не знаю как назвать.
                                                                            Было бы интересно сравнить производительность sql запроса и sql запроса сгенерённого Вашей платформой на хорошем объеме данных.

                                                                            Ну запрос сгенерированный нашей платформой я уже привел. И он выполнится точно нормально, так как руками я бы также написал. Ваш возможно тоже, но учитывая сколько проблем пришлось решить с этим безудержно глупым оптимистом — планировщиком postgres (глупый еще ладно, но глупый оптимист — это всегда проблема), надеяться на это я бы не стал.
                                                                              0
                                                                              И поэтому игнорировать его это даже не знаю как назвать.

                                                                              Тут как-то Вы читаете между строк, я не предлагал игнорировать. В конкретном случае графов, если запрос который вы привели выполняется больше 1 раза в день я бы начал с изменения схемы данных. В запросе, генерируемом вашей платформой, насколько я понимаю, каждый or будет увеличивать количество обращений к таблице в два раза + потом все эти данные надо будет сджойнить во время юниона. Я бы точно в данном случае предпочел видеть и иметь возможность править sql запрос.
                                                                      0
                                                                      with cte_friends as (
                                                                      select
                                                                      t.first_person_id,
                                                                      t.second_person_id,
                                                                      c.dummy_first_person_id,
                                                                      c.dummy_second_person_id
                                                                      from friends as t
                                                                      cross apply (values
                                                                      (t.first_person_id, t.second_person_id),
                                                                      (t.second_person_id, t.first_person_id)
                                                                      ) as c(dummy_first_person_id, dummy_second_person_id)
                                                                      )

                                                                      лучше Union, спасибо )
                                                                        0
                                                                        не понял, сарказм это или нет. В плане экспрессивности мне cross apply нравится больше, ну и если фильтры применять, то применяются один раз, а не несколько, как в юнионе.
                                                                        В плане производительности бывает всякое.
                                                                          0
                                                                          Нет, это не сарказм.
                                                                          В плане производительности — это один констант скан, вместо двух или целых двух запросов к таблице, так что всяко лучше.

                                                                          =)
                                                                            0
                                                                            ну иногда Sql Server себя ведет странно в плане применения индексов на поля которые использованы в apply, это надо, по-крайней мере, иметь в виду. Но в целом да, я предпочитаю начинать с apply.
                                                                      0

                                                                      Вкину ещё решение 2* на Datomic


                                                                      Создание БД, схемы и накидывание сэмплов
                                                                      (require '[datomic.api :as d])
                                                                      (def db-uri "datomic:sql://hehe?jdbc:postgresql://localhost:5432/?user=datomic&password=datomic")
                                                                      (d/create-database db-uri)
                                                                      (def conn (d/connect db-uri))
                                                                      
                                                                      (def schema
                                                                        [{:db/ident :person
                                                                            :db/valueType :db.type/string
                                                                            :db/unique :db.unique/identity
                                                                            :db/cardinality :db.cardinality/one}
                                                                      
                                                                          {:db/ident :likes
                                                                            :db/valueType :db.type/ref
                                                                            :db/cardinality :db.cardinality/many}
                                                                      
                                                                          {:db/ident :friends
                                                                            :db/valueType :db.type/ref
                                                                            :db/cardinality :db.cardinality/many}])
                                                                      
                                                                      @(d/transact conn schema)
                                                                      
                                                                      @(d/transact conn [
                                                                        {:person "John"}
                                                                        {:person "Alice"}
                                                                        {:person "Bob"}
                                                                        {:person "Eve"}
                                                                      ])
                                                                      
                                                                      @(d/transact conn [
                                                                        {:person "John"
                                                                        :likes [[:person "Alice"] [:person "Bob"]]
                                                                        :friends [[:person "Bob"]]}
                                                                      
                                                                        {:person "Alice"}
                                                                      
                                                                        {:person "Bob"
                                                                        :friends [[:person "Alice"]]}
                                                                      
                                                                        {:person "Eve"
                                                                        :likes [[:person "Bob"]]
                                                                        :friends [[:person "Alice"]]}
                                                                      ])
                                                                      
                                                                      (def db (d/db conn))

                                                                      Сам запрос, один в один калька с пролога:


                                                                      (d/q '[:find (pull ?a [:person]) (pull ?b [:person]) (pull ?c [:person])
                                                                              :where[?a :likes ?c]
                                                                                    (or [?a :friends ?b] [?b :friends ?a])
                                                                                    (or [?b :friends ?c] [?c :friends ?b])
                                                                                    (not [?a :friends ?c])]
                                                                        db)

                                                                      Результат
                                                                      [[{:person "Eve"} {:person "Alice"} {:person "Bob"}]
                                                                      [{:person "John"} {:person "Bob"} {:person "Alice"}]]
                                                                        0
                                                                        Большое спасибо. Очень познавательно. Только есть несколько вопросов:
                                                                        • Где в ident likes задается, что именно person likes person?
                                                                        • Почему person — это String? Можно пример, если у person есть имя и фамилия? И можно транзакцию, как изменить у person имя?

                                                                          0

                                                                          В likes/friends любой другой ident фигачить. Нравиться может не только человек :)


                                                                          Person — string потому что мне было лень делать другой первичный ключ и я сделал его первичным ключем. А так оно добавляется аналогично:


                                                                          Заголовок спойлера
                                                                          (def schema
                                                                            [{:db/ident :person
                                                                                :db/valueType :db.type/uuid
                                                                                :db/unique :db.unique/identity
                                                                                :db/cardinality :db.cardinality/one}
                                                                          
                                                                              {:db/ident :name
                                                                                :db/valueType :db.type/string
                                                                                :db/cardinality :db.cardinality/one}
                                                                          
                                                                              {:db/ident :likes
                                                                                :db/valueType :db.type/ref
                                                                                :db/cardinality :db.cardinality/many}
                                                                          
                                                                              {:db/ident :friends
                                                                                :db/valueType :db.type/ref
                                                                                :db/cardinality :db.cardinality/many}])
                                                                          
                                                                          @(d/transact conn schema)
                                                                          
                                                                          (def john_id (str (java.util.UUID/randomUUID)))
                                                                          (def alice_id (str (java.util.UUID/randomUUID)))
                                                                          (def bob_id (str (java.util.UUID/randomUUID)))
                                                                          (def eve_id (str (java.util.UUID/randomUUID)))
                                                                          
                                                                          @(d/transact conn [
                                                                            {:person john_id :name "John"}
                                                                            {:person alice_id :name "Alice"}
                                                                            {:person bob_id :name "Bob"}
                                                                            {:person eve_id :name "Eve"}
                                                                          ])
                                                                          
                                                                          @(d/transact conn [
                                                                            {:person john_id 
                                                                            :likes [[:person alice_id] [:person bob_id]]
                                                                            :friends [[:person bob_id]]}
                                                                          
                                                                            {:person bob_id 
                                                                            :friends [[:person alice_id]]}
                                                                          
                                                                            {:person eve_id 
                                                                            :likes [[:person bob_id]]
                                                                            :friends [[:person alice_id]]}
                                                                          ])
                                                                          
                                                                          (def db (d/db conn))
                                                                          
                                                                          (d/q '[:find (pull ?a [:name]) (pull ?b [:name]) (pull ?c [:name])
                                                                                  :where[?a :likes ?c]
                                                                                        (or [?a :friends ?b] [?b :friends ?a])
                                                                                        (or [?b :friends ?c] [?c :friends ?b])
                                                                                        (not [?a :friends ?c])]
                                                                            db)

                                                                          Изменение имени — также, как в первом примере я докинул лайки и друзей в существующих людей


                                                                          @(d/transact conn [
                                                                            {:person john_id :name "JOJO"}])
                                                                            0
                                                                            Я правильно понял, что эта база данных не типизирована? То есть в ней вообще нету классов? Если допустим я хочу добавить phone для person, то я спокойно могу его добавить и для order?
                                                                            Боюсь даже предположить, как она тогда хранит данные в PostgreSQL.
                                                                            Можете рассказать как в такой схеме у нее будут храниться данные и какой будет компилироваться SQL-запрос? Выше NitroJunkie кидал текст запроса, который компилирует платформа lsFusion. А какой будет здесь?
                                                                              0

                                                                              Да, она динамически типизированна. Тут в начале можно почитать про её принципы.
                                                                              Нижележащую хранилку я так понял она использует как key-value storage.


                                                                              Таблица в Postgres
                                                                              CREATE TABLE datomic_kvs
                                                                              (
                                                                               id text NOT NULL,
                                                                               rev integer,
                                                                               map text,
                                                                               val bytea,
                                                                               CONSTRAINT pk_id PRIMARY KEY (id )
                                                                              )
                                                                              WITH (
                                                                               OIDS=FALSE
                                                                              );
                                                                                0
                                                                                Спасибо. Тогда использовать ее в production с такой моделью данных, в частности, для бизнес-приложений по факту невозможно. План выполнения запроса для вышеописанной логики будет ужасным. А если попробуете описать в ней 2й пример из сложной порции, то там будет еще хуже.

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

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


                                                                                  просто аналог классической NoSQL key-value базы данных

                                                                                  Что что, но это точно не просто key-value база. Нагуглил термин Дедуктивная база данных. Отлично подходит для Datomic.
                                                                                  Частью своей сути, ваша система тоже является дедуктивной БД поверх реляционной логики. Правда вы, как уже отметили несколько раз, переназвали термины своими словами.

                                                                                    –1
                                                                                    Но тут уже надо полноценно читать документацию и тестировать чтобы понять насколько оно всё внутри эффективно.

                                                                                    Не будет в любом случае выполнятся эффективно, так как в качестве «движка» она будет использовать PostgreSQL. И при такой структуре данных его оптимизатор точно не сможет эффективно выполнять данные запросы. Но если хотите это опровергнуть, то просто запустите пример, который вы написали и профайлером запишите запросы (а еще желательно с EXPLAIN ANALYZE). Увидите сами.
                                                                                    Также там будут большие вопросы с транзакционностью (в частности, с CONFLICT UPDATE), так как разные транзакции будут создавать на уровне PostgreSQL разные записи и не попадут в row conflict. И им придется тогда блокировать всю таблицу, что приведет к огромному падению производительности.

                                                                                    Нагуглил термин Дедуктивная база данных.

                                                                                    Я специально писал эту статью на примерах, чтобы избежать бесполезного терминологического спора. Приведите, пожалуйста, все таки аналог СУБД с типизацией и нормальным отображением на РСУБД или собственным движком.
                                                                                    Нет смысла лишний раз устраивать холивар между РСУБД и key-value СУБД. На эту тему написано сотни статей (в том числе и на хабре). Общий вывод, что у них разные сферы применения. Так вот у приведенной в статье базы данных сфера применения такая же как и у РСУБД. Datomic (судя по структуре хранения и описанию) — это классическая key-value база данных, так как там нету статической типизации. Сравнивать их бессмысленно.
                                                                                      +1

                                                                                      Логика выполнения запросов, поддержка ACID лежит на уровне Datomic. Вместо postgres туда можно сунуть DynamoDB или Кассандру какую-нибудь. Насколько качественно запросы реализованы в самом Datomic сходу сказать не могу. Выше мы уже выяснили, что из-за single writer для OLTP она скорее всего не очень. Для OLAP — запросы на Datalog (функциональная парадигма вида likes(a, c) AND NOT friends(a, c) в ваших терминах) могут быть более удобными и короткими чем sql, что и показывает пример задачек со звездочками.


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

                                                                                        –1
                                                                                        А причём здесь слово функциональная так никто кажется и не понял.


                                                                                        А почему реляционная СУБД называется именно так? Ведь там есть также таблицы, поля и связи между ними. Я думаю это потому, что там все «вертится» вокруг связей. Точно также и здесь я назвал ее функциональной (хотя можно еще функциоориентированной), так как все «вертится» вокруг функций. Но название тут не суть важно. Главное — парадигма. Но парадигм действительно много. Ценность статьи именно в конкретике (то есть как можно строить на основе ее СУБД + конкретная реализация).
                                                                                      –1
                                                                                      Правда вы, как уже отметили несколько раз, переназвали термины своими словами.

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

                                                                                        А как бы для вас мог выглядеть принятый вами валидированный аргумент против преимущества "на мой взгляд, писать такие запросы значительно проще"?

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

                                                                              Востребованны ли сейчас РСУБД как хранилища данных? Да. Что в них инновационного сейчас?

                                                                                0

                                                                                В момент их появления они были инновационными. Иначе бы ими сейчас никто не пользовался.

                                                                                  0
                                                                                  Иначе бы ими сейчас никто не пользовался.

                                                                                  Я не понимаю, как одно вытекает из другого. Сейчас РСУБД (как концепция) не инновационны, но при этом прекрасно востребованы.

                                                                                    0

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