SQL: как проверить, нет ли совпадений или дыр в платежных записях - PullRequest
1 голос
/ 31 января 2020

У меня есть таблица PaymentSchedules с информацией о процентах и ​​датами от / до, для которых эти проценты действительны, ресурс за ресурсом:

| auto_numbered | res_id | date_start | date_end   | org   | pct |
|---------------+--------+------------+------------+-------+-----|
|             1 | A      | 2018-01-01 | 2019-06-30 | One   | 100 |
|             2 | A      | 2019-07-01 | (NULL)     | One   |  60 |
|             3 | A      | 2019-07-02 | 2019-12-31 | Two   |  40 |
|             4 | A      | 2020-01-01 | (NULL)     | Two   |  40 |
|             5 | B      |     (NULL) | (NULL)     | Three | 100 |
|             6 | C      | 2018-01-01 | (NULL)     | One   | 100 |
|             7 | C      | 2019-11-01 | (NULL)     | Four  | 100 |

(Записи № 3 и № 4 можно суммировать только на одну строку, но специально продублированы, чтобы показать, что существует множество комбинаций date_start и date_end.)

Быстрое чтение данных:

  • Орг "Один" полностью оплачивает ресурс А до 2019-06-30; затем он продолжает оплачивать 60% стоимости, но остальная часть (40%) оплачивается организацией "Два" с 2019-07-02.

    Это должно начаться с 2019-07-01. ... небольшая ошибка кодирования ... провоцирует разрыв в 1 день.

  • Орган "Три" всегда полностью оплачивает ресурс B.

  • Организация "Один" полностью оплачивает ресурс C с 2018-01-01 ... но, начиная с 2019-01-11, организация "Четыре" оплачивает его ...

    ... и там есть ошибка кодирования: у нас есть 200% ресурса C, учитываемого с 2019-11-01: запись № 6 должна была быть закрыта (date_end установлено на 2019- 10-31), но не имеет ...

Итак, когда мы генерируем финансовый отчет за 2019 год (с 2019-01-01 по 2019-12-31) у нас будут ошибки в расчетах ...

Итак, вопрос: как мы можем быть уверены, что у нас нет пересекающихся платежей за ресурсы, или, наоборот, "дыры" для некоторых период времени?

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

Я не понимаю, как выполнить такой запрос. Кто-нибудь в состоянии дать подсказки, чтобы поставить меня в нужное русло?

РЕДАКТИРОВАТЬ - Работа с SQL Сервер и Oracle.

РЕДАКТИРОВАТЬ - - Мне не принадлежит БД, я не могу добавлять триггеры или представления. Мне нужно иметь возможность обнаруживать вещи «после фактов» ... Нужно легко обнаружить конфликтные записи или «пропущенные» (в случае «дыр в периодах»), исправить их вручную, а затем повторно запустить финансовый отчет.

РЕДАКТИРОВАТЬ - Если мы проведем анализ на 2019 год, потребуется следующий отчет:

| res_id | pct_sum |       date |
|--------+---------+------------|
| A      |      60 | 2019-07-01 |
| C      |     200 | 2019-11-01 |
| C      |     200 | 2019-11-02 |
| C      |     200 |        ... |
| C      |     200 |        ... |
| C      |     200 |        ... |
| C      |     200 | 2019-12-30 |
| C      |     200 | 2019-12-31 |

или, конечно, даже намного лучшая версия - конечно, недостижимая? - когда один тип проблемы будет присутствовать один раз с соответствующим диапазоном дат, для которого наблюдается проблема:

| res_id | pct_sum | date_start |   date_end |
|--------+---------+------------+------------|
| A      |      60 | 2019-07-01 | 2019-07-01 |
| C      |     200 | 2019-11-01 | 2019-12-31 |

РЕДАКТИРОВАТЬ - Код скрипты: дБ <> скрипка здесь

Ответы [ 2 ]

0 голосов
/ 02 февраля 2020

Вот неполная попытка для Sql Сервера.

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

Затем оставьте присоединение «что может быть» к существующим диапазонам дат.

Но я сомневаюсь, что это можно сделать на sql, который будет работать как для Oracle, так и для MS Sql Сервер.
Конечно, оба имеют оконные функции и CTE.
Но функции datetime редко бывают одинаковыми для разных RDMS.

Так что я сдаюсь.
Может быть, кто-то еще найдет более простое решение.

create table PaymentSchedules
(
  auto_numbered int identity(1,1) primary key,
  res_id varchar(30),
  date_start date,
  date_end date,
  org varchar(30),
  pct decimal(3,0)
)
GO
insert into PaymentSchedules
(res_id, org, pct, date_start, date_end)
values
  ('A', 'One',   100, '2018-01-01', '2018-06-30')
, ('A', 'One',   100, '2019-01-01', '2019-06-30')
, ('A', 'One',    60, '2019-07-01', null)
, ('A', 'Two',    40, '2019-07-02', '2019-12-31')
, ('A', 'Two',    40, '2020-01-01', null)
, ('B', 'Three', 100, null,         null)
, ('C', 'One',   100, '2018-01-01', null)
, ('C', 'Four',  100, '2019-11-01', null)
;
GO
8 rows affected
declare @MaxEndDate date;
set @MaxEndDate = (select max(iif(date_start > date_end, date_start, isnull(date_end, date_start))) from PaymentSchedules);

;with rcte as
(
  select res_id
  , datefromparts(year(min(date_start)), month(min(date_start)), 1) as month_start
  , eomonth(coalesce(max(date_end), @MaxEndDate)) as month_end
  , 0 as lvl
  from PaymentSchedules
  group by res_id
  having min(date_start) is not null

  union all

  select res_id
  , dateadd(month, 1, month_start)
  , month_end
  , lvl + 1
  from rcte
  where dateadd(month, 1, month_start) < month_end
)
, cte_gai as
(
select c.res_id, c.month_start, c.month_end
, t.org, t.pct, t.auto_numbered
, sum(isnull(t.pct,0)) over (partition by c.res_id, c.month_start) as res_month_pct
, count(t.auto_numbered) over (partition by c.res_id, c.month_start) as cnt
from rcte c
left join PaymentSchedules t
  on t.res_id = c.res_id
 and c.month_start >= datefromparts(year(t.date_start), month(t.date_start), 1)
 and c.month_start <= coalesce(t.date_end, @MaxEndDate)
)
select *
from cte_gai
where res_month_pct <> 100
order by res_id, month_start

GO
res_id | month_start | month_end  | org  | pct  | auto_numbered | res_month_pct | cnt
:----- | :---------- | :--------- | :--- | :--- | ------------: | :------------ | --:
A      | 2018-07-01  | 2019-12-31 | <em>null</em> | <em>null</em> |          <em>null</em> | 0             |   0
A      | 2018-08-01  | 2019-12-31 | <em>null</em> | <em>null</em> |          <em>null</em> | 0             |   0
A      | 2018-09-01  | 2019-12-31 | <em>null</em> | <em>null</em> |          <em>null</em> | 0             |   0
A      | 2018-10-01  | 2019-12-31 | <em>null</em> | <em>null</em> |          <em>null</em> | 0             |   0
A      | 2018-11-01  | 2019-12-31 | <em>null</em> | <em>null</em> |          <em>null</em> | 0             |   0
A      | 2018-12-01  | 2019-12-31 | <em>null</em> | <em>null</em> |          <em>null</em> | 0             |   0
C      | 2019-11-01  | 2020-01-31 | One  | 100  |             7 | 200           |   2
C      | 2019-11-01  | 2020-01-31 | Four | 100  |             8 | 200           |   2
C      | 2019-12-01  | 2020-01-31 | One  | 100  |             7 | 200           |   2
C      | 2019-12-01  | 2020-01-31 | Four | 100  |             8 | 200           |   2
C      | 2020-01-01  | 2020-01-31 | One  | 100  |             7 | 200           |   2
C      | 2020-01-01  | 2020-01-31 | Four | 100  |             8 | 200           |   2

дБ <> скрипка здесь

0 голосов
/ 31 января 2020

Я не даю здесь полного ответа, но думаю, что вы после курсоров (https://docs.microsoft.com/en-us/sql/t-sql/language-elements/declare-cursor-transact-sql?view=sql-server-ver15).

Это позволяет вам перебирать базу данных, проверяя все записи.

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

Я знаю, что некоторые люди нашли способ переписать курсоры, используя циклы (хотя, вероятно,), поэтому вам нужно понять курсор, понять, как вы его реализуете, а затем перевести его в аль oop. (https://www.sqlbook.com/advanced/sql-cursors-how-to-avoid-them/)

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

Алгоритм должен выглядеть примерно так: иметь table1 и table2 (table2 является копией table1, https://www.tutorialrepublic.com/sql-tutorial/sql-cloning-tables.php)

  1. итерация по всем записям (я бы использовал в первом случае курсор для этого) из таблицы1. Взять запись из таблицы 1.
  2. , если перекрывающиеся даты (проверить это по таблице 2) сделать что-то
  3. иначе сделать что-то еще
  4. выбрать другую запись из таблицы1 и go для Шаг 2.
  5. Удалить ненужные таблицы
...