Мне нужна помощь с проблемой 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 нет успешной оплаты.

0
VMC 8 Июл 2016 в 10:34
Простите за это! Сначала я написал пример, а затем создал тестовую базу данных. Я обновил вопрос, указав ожидаемые результаты тестовых данных. Спасибо!
 – 
VMC
8 Июл 2016 в 12:52
Как вы подсчитываете последовательности платежей для каждой подписки, например, если у вас есть seq1: 0,2 и seq2: 1,2 для subscription_id = 1, как вы узнаете, что у вас было 2 платежа с 2 попытками каждый, а не 2 платежа с 3 и 1 попытка соответственно. В db у вас будет 0,1,2,2
 – 
Natasha
8 Июл 2016 в 13:30
Ваш вопрос может быть похож на stackoverflow.com/questions/9231447/…
 – 
Natasha
8 Июл 2016 в 13:40
Ваши проблемы возникают из-за дизайна вашей базы данных. Есть таблица со всеми выплатами (один раз). Платеж - это сущность, поэтому для него есть таблица. Дайте каждому из них номер. Когда вы добавляете попытки платежа, включайте это число в свою таблицу. И все твои проблемы уйдут.
 – 
Solarflare
8 Июл 2016 в 13:40
Я не уверен, что согласен с оценкой Solaflare.
 – 
Strawberry
8 Июл 2016 в 13:59

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, вероятно, придется переместить в справочную таблицу.

0
Solarflare 8 Июл 2016 в 23:20
Добавлен столбец invoice_no с NULL для последовательностей с неуспешной оплатой в конце, теперь выбирать данные очень просто! Спасибо за поддержку.
 – 
VMC
13 Июл 2016 в 16:02