Мне нужна помощь с проблемой MySQL. У меня есть две таблицы: одна называется subscription
, а другая - payment
. Каждая подписка предусматривает несколько платежей. Некоторые платежи не инициированы (0
), некоторые не выполняются (1
), а некоторые успешны (2
).
Каждая подписка может генерировать несколько попыток оплаты, пока не будет успешной. Например:
- При первой попытке возникли проблемы с сетевым подключением, и она получает
status = 0
- Вторая попытка достигает платежного API, но данные кредитной карты неверны или недостаточно средств, поэтому он получает
status = 1
- Третья попытка успешна, и она получает
status = 2
Первых двух может быть больше двух, на самом деле это может выглядеть так:
0, 1, 0, 0, 1, 1, 1, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 2, 0, 1, 2, 1, 0, 2, 0, 2, 1, 2, 2
Таким образом, каждая подписка имеет несколько последовательностей платежей (ежемесячно или часто, не обязательно с датой), которые можно определить по их статусу с помощью этого регулярного выражения [0|1]*2
, получив что-то вроде:
0, 1, 0, 0, 1, 1, 1, 1, 2, // 9
0, 0, 1, 2, // 4
1, 0, 1, 2, // 4
2, // 1
2, // 1
0, 1, 2, // 3
1, 0, 2, // 3
0, 2, // 2
1, 2, // 2
2 // 1
Но, конечно же, поскольку существует несколько подписок, платежные строки смешиваются в базе данных, единственное, что их связывает, - это subscription_id
.
Мне нужно получить количество подписок, по которым была произведена успешная оплата с первой попытки, второй попытки, третьей, ..., 10-й попытки, 10+ попыток.
В приведенном выше примере это должно быть:
attempts count
9 1
4 2
3 2
2 2
1 3
Это возможно?
Тестовые данные
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
SET time_zone = "+00:00";
CREATE TABLE IF NOT EXISTS `payment` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`subscription_id` int(11) DEFAULT NULL,
`status` smallint(6) NOT NULL,
PRIMARY KEY (`id`),
KEY `IDX_6D28840D9A1887DC` (`subscription_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci ROW_FORMAT=COMPRESSED AUTO_INCREMENT=36 ;
INSERT INTO `payment` (`id`, `subscription_id`, `status`) VALUES
(1, 1, 1),(2, 1, 2),(3, 1, 2),(4, 2, 2),(5, 3, 2),(6, 4, 2),(7, 5, 2),
(8, 6, 1),(9, 6, 2),(10, 7, 2),(11, 7, 2),(12, 8, 0),(13, 8, 1),(14, 8, 2),
(15, 8, 2),(16, 9, 1),(17, 9, 2),(18, 9, 2),(19, 9, 1),(20, 9, 2),(21, 10, 0),
(22, 10, 1),(23, 10, 1),(24, 10, 1),(25, 10, 1),(26, 11, 0),(27, 11, 0),(28, 11, 1),
(29, 11, 1),(30, 11, 1),(31, 8, 0),(32, 8, 1),(33, 8, 2),(34, 10, 1),(35, 10, 2);
CREATE TABLE IF NOT EXISTS `subscription` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`status` smallint(6) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci ROW_FORMAT=COMPRESSED AUTO_INCREMENT=12 ;
INSERT INTO `subscription` (`id`, `status`) VALUES
(1, 1),(2, 1),(3, 1),(4, 1),(5, 1),(6, 1),(7, 1),
(8, 1),(9, 1),(10, 0),(11, 0);
ALTER TABLE `payment`
ADD CONSTRAINT `sub_id` FOREIGN KEY (`subscription_id`) REFERENCES `subscription` (`id`);
Примечание. Тестовые данные не похожи на приведенный выше пример. Для тестовых данных результат должен выглядеть так:
subscription_id cntAttempts [attempts counted]
1 2 1, 2,
1 1 2,
2 1 2,
3 1 2,
4 1 2,
5 1 2,
6 2 1, 2,
7 1 2,
7 1 2,
8 3 0, 1, 2,
8 1 2,
9 2 1, 2,
9 1 2,
9 2 1, 2,
10 7 0, 1, 1, 1, 1, 1, 2
11 5 0, 0, 1, 1, 1,
8 3 0, 1, 2,
Для subscription_id = 10
последние два платежа были вставлены после 11
и 8
в конце.
Ожидаемый конечный результат:
paymentAttemptsCount count
1 9
2 5
3 1
4 0
5 1
6 0
7 1
8 0
9 0
10 0
Примечание. По подписке с id = 11
нет успешной оплаты.
1 ответ
Как уже упоминалось, на самом деле вам следует добавить столбец для paymentid / invoicenumber (который будет внешним ключом для таблицы счетов-фактур), чтобы сгруппировать несколько попыток оплаты вместе - поскольку они принадлежат друг другу, и ваша модель данных должна представлять эту логику. Ваша проблема кажется более сложной, потому что вам не хватает этих данных.
Вы можете рассчитать этот идентификатор во время выполнения (по крайней мере, если вы добавили их по порядку и не смешивали платежи для одного и того же subscription_id
, например, потому что счет-фактура может быть еще открыт в следующем месяце, и теперь вы пытаетесь выставить счет для обоих) . Для вашего первого запроса вы можете, например, использовать
select subscription_id,
count(*) as cntAttempts,
group_concat(status order by id) as attempts_counted
from
(SELECT id, subscription_id, status,
@pid := CASE WHEN @last_status = 2 or subscription_id <> @last_id
THEN @pid + 1 else @pid END as pid,
@last_id := subscription_id,
@last_status := status
from (select @last_id := 0, @pid := 0, @last_status := 0) init, payment
order by subscription_id, id
) as payment_grpd
group by pid, subscription_id;
Ваш второй запрос должен будет снова сгруппировать этот результат, например, и будет выглядеть так:
select cntAttempts,
count(*) as count
from
(select pid,
count(*) as cntAttempts
from
(SELECT id, subscription_id, status,
@pid := CASE WHEN @last_status = 2 or subscription_id <> @last_id
THEN @pid + 1 else @pid END as pid,
@last_id := subscription_id,
@last_status := status
from (select @last_id := 0, @pid := 0, @last_status := 0) init, payment
order by subscription_id, id
) as payment_grpd
group by pid, subscription_id
) as subcounts
group by cntAttempts;
Подзапрос payment_grpd
пересчитает ваш идентификатор. Если вы добавите недостающее отношение, ваш первый запрос будет просто выглядеть как
select subscription_id,
count(*) as cntAttempts,
group_concat(status order by id) as attempts_counted
from payment
group by pid, subscription_id;
И аналогично для второго. А для нормализации subscription_id
, вероятно, придется переместить в справочную таблицу.
invoice_no
с NULL
для последовательностей с неуспешной оплатой в конце, теперь выбирать данные очень просто! Спасибо за поддержку.
Похожие вопросы
Связанные вопросы
Новые вопросы
mysql
MySQL — это бесплатная система управления реляционными базами данных (RDBMS) с открытым исходным кодом, которая использует язык структурированных запросов (SQL). НЕ ИСПОЛЬЗУЙТЕ этот тег для других БД, таких как SQL Server, SQLite и т. д. Это разные БД, которые используют свои собственные диалекты SQL для управления данными. В вопросе всегда указывайте точную версию сервера. Версии 5.x сильно отличаются по своим возможностям от версий 8+.