Агрегации в SQLite

Я хочу создать SQL-запрос, который может суммировать суммы взносов по месяцам. Обычно это не было бы слишком сложно, так как вы просто суммируете стоимость рассрочки и месяц group by. Однако проблема не так проста, и в оставшейся части поста я проиллюстрирую, почему, и буду просить людей о любой помощи, которую могут предложить.

Прежде всего, важно отметить столбец installments. Если installments равен 1, это означает, что полная стоимость оплачивается во время покупки. Если installments больше 1, это означает, что общая стоимость выплачивается как в текущем, так и в следующих месяцах. Например, если мы видим transaction_id 9 и 10, это транзакция на 100 долларов с двумя частями, что означает, что 50 долларов будут выплачены в феврале, а 50 долларов будут выплачены в марте.

Учтите, что мы хотим видеть ежемесячные счета за credit_card_id = 11111111. Если мы посмотрим на столбец installments, мы увидим, что правильный вывод должен быть следующим:

  • Январь: 19,99 + 75,3
  • Февраль: 1337 + 75,3
  • Март: 75,3

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

Агрегации в SQLite

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

select strftime('%m', transaction_date) as Month, total_value, installment_value, installments 
from transactions 
WHERE credit_card_id = '11111111';

который выводит таблицу, которая выглядит так: Агрегации в SQLite

Однако было неочевидно, как разделить 3 периода рассрочки на 01, 02 и 03, поэтому я создал новую таблицу со столбцом txn, который предназначен для присвоения идентификатора уникальным транзакциям, которые можно рассматривать как 1 группа.

CREATE TABLE transactions (
    transaction_id int primary key,
    credit_card_id int,
    transaction_date timestamp,
    merchant_name varchar(256),
    total_value decimal(19,4),
    installment_value decimal(19,4),
    installments int,
    txn int
);

insert into transactions values(1,11111111,'2018-01-10T00:00:00','Colorful Soaps', 19.99, 19.99, 1, 1);
insert into transactions values(2,22222222,'2018-01-11T00:01:00','Cantina da Mamma',43.5,43.5,1,2);
insert into transactions values(3,33333333,'2018-01-12T01:02:00','Boulevard Hotel',129,129,1,3);
insert into transactions values(4,11111111,'2018-01-15T11:11:11','Micas Bar',225.9,75.3,3,4);
insert into transactions values(5,11111111,'2018-01-15T11:11:11','Micas Bar',225.9,75.3,3,4);
insert into transactions values(6,11111111,'2018-01-15T11:11:11','Micas Bar',225.9,75.3,3,4);
insert into transactions values(7,22222222,'2018-01-18T22:10:01','IPear Store',9999.99,9999.99,1,5);
insert into transactions values(8,11111111,'2018-02-20T21:08:32','Forrest Paintball',1337,1337,1,6);
insert into transactions values(9,44444444,'2018-02-22T00:05:30','Unicorn Costumes',100,50,2,7);
insert into transactions values(10,44444444,'2018-02-22T00:05:30','Unicorn Costumes',100,50,2,7);

Мои вопросы

  1. Можно ли получить вывод в формате, который я указал выше, в SQLite, и если да, то как?
  2. Обязательно ли мне иметь колонку txn, чтобы получить эту информацию?

Спасибо за помощь.

ReactJs | Supabase | Добавление данных в базу данных
ReactJs | Supabase | Добавление данных в базу данных
Это и есть ваш редактор таблиц в supabase.👇
Понимание Python и переход к SQL
Понимание Python и переход к SQL
Перед нами лабораторная работа по BloodOath:
3
0
248
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

SQLlite имеет ROW_NUMBER() (проверено здесь SQLlite).

   SELECT 
        installment_month
        ,credit_card_id
        ,SUM(installment_value) 
    FROM (
        SELECT 
            CASE WHEN strftime('%m',transaction_date) + ROW_NUMBER () OVER(PARTITION BY credit_card_id, transaction_date ORDER BY transaction_date) - 1 > 12
                    THEN strftime('%Y',transaction_date)*100+strftime('%m',transaction_date) + ROW_NUMBER () OVER(PARTITION BY credit_card_id, transaction_date ORDER BY transaction_date) - 1 + 88
                    ELSE strftime('%Y',transaction_date)*100+strftime('%m',transaction_date) + ROW_NUMBER () OVER(PARTITION BY credit_card_id, transaction_date ORDER BY transaction_date) - 1
            END as installment_month
            ,* 
        from transactions
        ) AS a
    GROUP by installment_month, credit_card_id

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

SNR, не могли бы вы объяснить, почему / как происходит сбой, если рассрочка превышает два года?

g.humpkins 27.10.2018 20:50

Измените данные в таблице, выполнив взнос больше 1 декабря. Принятый ответ не удастся. Сделайте то же самое с датой рассрочки более 13 декабря, мой ответ не будет успешным. @Caius Jard ответит, что он не подведет, пока ваш календарь достаточно велик. Ответ просто должен заключаться в том, что сейчас генерируется слишком много строк для каждого credit_card_id, и мы не получим ожидаемого результата.

SNR 27.10.2018 21:42

@ g.humpkins (редактирование отправлено) вы можете выполнить требования, используя select distinct внутри предложения from, используя вывод inner join. Вы получите ожидаемый результат. Но, может быть, есть способ получше, давайте подождем и посмотрим ...

SNR 27.10.2018 22:04
Ответ принят как подходящий

Предполагая, что вы используете версию SQLite 3.25+, рассмотрите возможность использования CTE и оконная функция, который создает текущий счетчик по тем же credit_card_id и transaction_date и использует это значение для добавления необходимых месяцев к дате транзакции. Оттуда агрегируйте в соответствии с новой расчетной датой install_date.

WITH cte AS
   (SELECT *,
       DATE(transaction_date, 
            '+' || (ROW_NUMBER() 
                      OVER(PARTITION BY transaction_date, credit_card_id 
                           ORDER BY transaction_date) - 1)
                || ' month'
            ) AS install_date       
    FROM transactions)

SELECT credit_card_id, 
       STRFTIME('%Y', install_date) AS install_year, 
       STRFTIME('%m', install_date) AS install_month, 
       SUM(installment_value) AS sum_installment_value
FROM cte 
GROUP BY credit_card_id, 
         STRFTIME('%Y', install_date), 
         STRFTIME('%m', install_date)
ORDER BY credit_card_id, 
         STRFTIME('%Y', install_date), 
         STRFTIME('%m', install_date);

Демо Rextesterс использованием PostgreSQL, поскольку AFAIK без онлайн-скрипта (SQLFiddle, SQLiteonline, DBFiddle и т. д.) поддерживает SQLite с оконными функциями

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

SNR 27.10.2018 07:54

Вот решение, которое не требует нумерации строк / будет работать в гораздо более старом SQLite (в любой версии, которая поддерживает date () в основном). Он просто полагается на соединение с календарной таблицей (которую вы можете создать с помощью различных методов, но в связанном примере я просто сгенерировал N строк, создав таблицу и выполнив прямую вставку данных, которые мне нужны для запроса), имеет одну строку для 1-го числа каждого месяца. Он использует декартово условие соединения, которое вызывает, например, 3 строки для каждого платежа, состоящего из 3-х частей:

select 
  t.credit_card_id,
  date(cal.d, '-1 month') as month_of_installment,
  sum(t.installment_value)
from
  cal inner join transactions t on
  t.transaction_date between date(cal.d, '-'||installments||' months') and cal.d
group by
  t.credit_card_id,
  date(cal.d, '-1 month')

Вы можете увидеть настройку на https://www.db-fiddle.com/f/ogj2hK3cMwqp46MY6uVwX8/0

Между прочим, в вашем вопросе или в данных вашего примера что-то не так.

Ежемесячные платежи для кредитной карты ID 11111111 составляют:

2018-01   245.89
2018-02   1562.9
2018-03   225.9

Данные вашего примера содержат 3 одновременных платежа на счет Micas Bar. Мы знаем, что они разные, потому что у них другой идентификатор транзакции, несмотря на идентичные другие данные. Таким образом, январь составляет 75,3 + 75,3 + 75,3 + 19,99, а не только 19,99 + 75,3, как утверждается в вопросе.

Чтобы узнать больше о том, как работает запрос, запустите несгруппированную форму:

select 
  t.credit_card_id,
  date(cal.d, '-1 month') as month_of_installment,
  t.*
from
  cal inner join transactions t on
  t.transaction_date between date(cal.d, '-'||installments||' months') and cal.d
order by
  t.credit_card_id,
  date(cal.d, '-1 month')

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

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

SNR 27.10.2018 22:27

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