Запрос для отображения всех покупок клиентов

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

DECLARE @Table1 table (Id_Client int, Value money) -- clients
-- Id_Client – client identifier, Value – loan amount

INSERT INTO @Table1 (Id_Client, Value)
SELECT 1, 24
UNION SELECT 2, 13
UNION SELECT 3, 2
UNION SELECT 4, 5

DECLARE @Table2 table (Id_Client int, DocDate datetime, Amount
money, Caption varchar(6)) -- purchases
-- Id_Client – client identifier, Amount – purchase amount,
DocDate – date of purchase, Caption – name of purchase

INSERT INTO @Table2 (Id_Client, Amount, DocDate, Caption)

SELECT 1, 5, '20051024', 'qh'
UNION SELECT 1, 9, '20051019', 'wj'
UNION SELECT 1, 3, '20051022', 'ek'
UNION SELECT 1, 8, '20051004', 'rl'
UNION SELECT 1, 6, '20051018', 'tz'
UNION SELECT 1, 5, '20050929', 'yx'
UNION SELECT 2, 11, '20051023', 'uc'
UNION SELECT 2, 6, '20051021', 'iv'
UNION SELECT 2, 45, '20051018', 'ob'
UNION SELECT 3, 4, '20051030', 'pn'
UNION SELECT 3, 2, '20051028', 'am'
UNION SELECT 4, 4, '20051021', 'sq'
UNION SELECT 4, 6, '20051023', 'dw'
UNION SELECT 4, 8, '20051023', 'fe'
UNION SELECT 4, 9, '20051023', 'gr'

Результат задачи должен быть таким

1 2005-10-24 00:00:00 5.00   qh
1 2005-10-22 00:00:00 3.00   ek
1 2005-10-19 00:00:00 9.00   wj
1 2005-10-18 00:00:00 6.00   tz
1 2005-10-04 00:00:00 1.00   rl
2 2005-10-23 00:00:00 11.00 uc
2 2005-10-21 00:00:00 2.00   iv
3 2005-10-30 00:00:00 2.00   pn
4 2005-10-23 00:00:00 5.00   gr

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

WITH AllPurchases AS (
    SELECT
        t2.Id_Client,
        t2.DocDate,
        t2.Amount,
        t2.Caption
    FROM
        Table2 t2
    WHERE
        t2.Id_Client IN (SELECT Id_Client FROM Table1 WHERE Value > 0)
)

SELECT 
    ap.Id_Client,
    ap.DocDate,
    ap.Amount,
    ap.Caption
FROM 
    AllPurchases ap
    INNER JOIN Table1 t1 ON ap.Id_Client = t1.Id_Client
WHERE 
    t1.Value > 0
ORDER BY 
    ap.Id_Client ASC, ap.DocDate DESC

Я использую SQL-сервер 2022

Пользователю был предоставлен кредит в размере 24 единиц. Объяснение результата. Мы отображаем данные в хронологическом порядке. В результате получилось 5 строк:

1) 24 - 5 = 19, remaining credit > 0, output 5
2) 24 - 3 = 16, remaining credit > 0, output 3
3) 16 - 9 = 7, remaining credit > 0, output 9
4) 7 - 6 = 1, remaining credit > 0, output 6
5) Most recent spending in reverse chronological order
1 - 5, < 0, do not output this, but output 1, that is, the remaining balance before the credit is less than 0

Какую СУБД вы используете? Похоже на SQL Server, но какой версии?

Dai 27.09.2023 14:47

Да, я использую SQL-сервер 2022.

John Smith 27.09.2023 14:53

Крайне неясно, как ожидаемый результат должен быть получен из исходных данных — не могли бы вы уточнить это, чтобы люди могли понять? Например, в результатах есть строка для Клиента 1 от 4 октября 2005 г. с заголовком «rl», но запись в таблице 2 для этого Клиента, Даты и Заголовка имеет другое значение. В вашем ожидаемом результате нет заголовков столбцов, которые бы указывали на то, что вы пытаетесь вычислить, и, судя по ответу, который я уже опубликовал, это не то, что подразумевается в тексте вашего вопроса, поэтому нам нужна более подробная информация, чтобы иметь возможность ответить.

3N1GM4 27.09.2023 15:29

После ваших правок до сих пор неясно, почему вы подаете заявку на кредит в ОБРАТНОМ хронологическом порядке. Похоже, кредит распространяется в первую очередь на самые последние покупки, а не на самые старые - верно?

3N1GM4 27.09.2023 15:42
ReactJs | Supabase | Добавление данных в базу данных
ReactJs | Supabase | Добавление данных в базу данных
Это и есть ваш редактор таблиц в supabase.👇
Понимание Python и переход к SQL
Понимание Python и переход к SQL
Перед нами лабораторная работа по BloodOath:
1
4
94
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Вы можете сделать это, используя оконные функции lag() для получения предыдущей строки и sum() для получения итоговой суммы:

AllPurchases используется для объединения данных из обеих таблиц и расчета промежуточной суммы.

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

cte3 отвечает за определение оставшегося кредита на основе определенных условий, включая сравнение текущего «total» и предыдущего «running_total», полученного в cte2.

WITH AllPurchases AS (
    SELECT
        t2.Id_Client,
        t2.DocDate,
        t2.Amount,
        t2.Caption,
        t1.Value as total,
        sum(Amount) over (partition by t2.Id_Client order by DocDate desc ROWS UNBOUNDED PRECEDING) as running_total
    FROM
        Table2 t2
    INNER JOIN Table1 t1 ON t1.Id_Client = t2.Id_Client
),
cte2 as (
  select *, lag(running_total) over (partition by Id_Client order by DocDate desc) as lag_running_total
  from AllPurchases
),
cte3 as (
  select *, case when total>= running_total then Amount
               when lag_running_total is null then total
               else total - lag_running_total
               end as remaining_credit
  from cte2
)
select Id_Client, DocDate, remaining_credit, Caption
from cte3
where remaining_credit > 0

Полученные результаты :

Id_Client   DocDate remaining_credit    Caption
1   2005-10-24 00:00:00.000 5.0000  qh
1   2005-10-22 00:00:00.000 3.0000  ek
1   2005-10-19 00:00:00.000 9.0000  wj
1   2005-10-18 00:00:00.000 6.0000  tz
1   2005-10-04 00:00:00.000 1.0000  rl
2   2005-10-23 00:00:00.000 11.0000 uc
2   2005-10-21 00:00:00.000 2.0000  iv
3   2005-10-30 00:00:00.000 2.0000  pn
4   2005-10-23 00:00:00.000 5.0000  dw

Демо здесь

Вот альтернативная версия:

SELECT  Id_Client
,   DocDate
,   CASE WHEN aggSum <= Value THEN Amount ELSE Amount + Value - aggSum END AS AmountTop
,   Caption
FROM    (
    SELECT  *
    ,   ROW_NUMBER() OVER(PARTITION BY ID_Client, CASE WHEN aggSum >= Value THEN 0 ELSE 1 END ORDER BY DocDate DESC, Caption DESC) AS sort
    FROM    (
        SELECT  SUM(Amount) OVER(PARTITION BY t2.ID_Client ORDER BY DocDate DESC, Caption DESC/* ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW*/) AS aggSum, t2.*, t.Value
        FROM    @Table2 t2
        INNER JOIN @Table1 t
            ON  t.Id_Client = t2.Id_Client
        ) x
    ) x
WHERE   aggSum <= Value OR sort = 1
ORDER BY Id_Client, DocDate DESC, Caption DESC

Он вычисляет промежуточную сумму и сравнивает ее с доступным кредитом.

Разделы ROW_NUMBER() разбиты на две разные группы: те, где кредит меньше совокупной потраченной суммы, и те, где она больше.

CASE WHEN aggSum >= Value THEN 0 ELSE 1 END поэтому, если клиент превышает свой кредит в агрегированной сумме, он создает группу 0, в противном случае - группу 1, тогда ORDER BY DocDate DESC создает последовательность значений 1, 2, 3. Это означает, что первая строка, в которой клиент превышает кредит, получает значение номера строки = 1, вторая строка получит = 2 и т. д.

И поскольку нас интересует первая строка, в которой кредит клиента перерасходован, мы включаем эту строку в наш расчет в часть WHERE aggSum <= Value OR sort = 1.

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

Не могли бы вы объяснить ваш алгоритм более подробно? Я просто не понимаю, что делает переменная сортировки и какие значения она принимает.

John Smith 27.09.2023 20:31

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

siggemannen 27.09.2023 20:43

Другие вопросы по теме