Я пытаюсь идентифицировать строки в определенном наборе данных с помощью столбца суммы в долларах, сумма которых равна определенной сумме. Я знаю сумму и количество строк, которые в сумме составляют эту сумму.
Параметры: 4 записи на сумму до $15,68
набор данных > 450 тыс. записей
Итак, есть ли способ найти 4 строки, сумма которых равна 15,68 доллара США?
скажем, таблица имеет уникальный идентификатор для каждой строки с именем ID, а затем соответствующий столбец суммы.
ID Amount
22 $11.55
33 $5.55
44 $3.33
55 $2.33
66 $1.11
77 $.99
88 $-1.77
91 $0.00
99 $-5.00
100 $3.33
111 $4.44
122 $5.00
133 $4.00
144 $2.24
поэтому я хочу получить набор результатов из последних 4 записей из этого набора выборок.


Один из методов состоит в том, чтобы объединить ваши данные в декартовом виде четыре раза, чтобы создать каждую возможную комбинацию из четырех строк, затем сложить суммы, найти комбинации, которые составляют вашу целевую сумму, а затем снова вернуться к исходным строкам:
WITH data AS (select 22 id,11.55 amount from dual union all
select 33,5.55 from dual union all
select 44,3.33 from dual union all
select 55,2.33 from dual union all
select 66,1.11 from dual union all
select 77,.99 from dual union all
select 88,-1.77 from dual union all
select 91,0.00 from dual union all
select 99,-5.00 from dual union all
select 100,3.33 from dual union all
select 111,4.44 from dual union all
select 122,5.00 from dual union all
select 133,4.00 from dual union all
select 144,2.24 from dual)
SELECT DISTINCT data.*
FROM (SELECT d1.id id1,
d2.id id2,
d3.id id3,
d4.id id4,
d1.amount + d2.amount + d3.amount + d4.amount total_amount
FROM data d1,
data d2,
data d3,
data d4) x,
data
WHERE x.total_amount = 15.68
AND data.id IN (x.id1,x.id2,x.id3,x.id4)
Вероятно, вы также можете использовать иерархический запрос (CONNECT BY...) и оконную функцию для агрегирования по пути иерархии. Это было бы более гибко, поскольку не было бы структурно жестко запрограммировано ровно 4 значения.
Это объединит строки друг с другом, поэтому, если у вас есть одна строка с суммой 15.68/4, она будет сопоставлена рабочий пример
@MT0: Хорошая мысль, но, по моему мнению, в запросе нет ясности по этому поводу. ОП придется решить, хотят ли они разрешить несколько строк в наборе результатов или нет.
Используйте INNER JOIN, чтобы соединить таблицу сама с собой 4 раза, и объединяйте так, чтобы значения располагались в порядке возрастания, чтобы вы не повторяли комбинации в разных порядках или строки, неоднократно соединяемые сами с собой:
SELECT d1.id AS id1,
d1.amount AS amount1,
d2.id AS id2,
d2.amount AS amount2,
d3.id AS id3,
d3.amount AS amount3,
d4.id AS id4,
d4.amount AS amount4
FROM data d1
INNER JOIN data d2
ON (d1.id < d2.id)
INNER JOIN data d3
ON (d2.id < d3.id)
INNER JOIN data d4
ON (d3.id < d4.id)
WHERE d1.amount + d2.amount + d3.amount + d4.amount = 15.68;
Какие выходы:
Если вы хотите, чтобы значения были в виде строк, тогда UNPIVOT:
SELECT id,
amount
FROM (
SELECT d1.id AS id1,
d1.amount AS amount1,
d2.id AS id2,
d2.amount AS amount2,
d3.id AS id3,
d3.amount AS amount3,
d4.id AS id4,
d4.amount AS amount4
FROM data d1
INNER JOIN data d2
ON (d1.id < d2.id)
INNER JOIN data d3
ON (d2.id < d3.id)
INNER JOIN data d4
ON (d3.id < d4.id)
WHERE d1.amount + d2.amount + d3.amount + d4.amount = 15.68
)
UNPIVOT (
(amount, id) FOR type IN (
(amount1, id1) AS 1,
(amount2, id2) AS 2,
(amount3, id3) AS 3,
(amount4, id4) AS 4
)
);
Какие выходы:
Вы также можете решить эту проблему с помощью рекурсивного запроса:
WITH hierarchy (id1, amount1, id2, amount2, id3, amount3, id4, amount4, lvl, total) AS (
SELECT id, amount,
CAST(NULL AS NUMBER), CAST(NULL AS NUMBER),
CAST(NULL AS NUMBER), CAST(NULL AS NUMBER),
CAST(NULL AS NUMBER), CAST(NULL AS NUMBER),
1, amount
FROM data
UNION ALL
SELECT h.id1,
h.amount1,
CASE lvl + 1 WHEN 2 THEN d.id ELSE h.id2 END,
CASE lvl + 1 WHEN 2 THEN d.amount ELSE h.amount2 END,
CASE lvl + 1 WHEN 3 THEN d.id ELSE h.id3 END,
CASE lvl + 1 WHEN 3 THEN d.amount ELSE h.amount3 END,
CASE lvl + 1 WHEN 4 THEN d.id ELSE h.id4 END,
CASE lvl + 1 WHEN 4 THEN d.amount ELSE h.amount4 END,
lvl + 1, amount + total
FROM data d
INNER JOIN hierarchy h
ON COALESCE(h.id3, h.id2, h.id1) < d.id AND h.lvl < 4
)
SELECT id, amount
FROM (
SELECT *
FROM hierarchy
WHERE lvl = 4
AND total = 15.68
)
UNPIVOT (
(amount, id) FOR type IN (
(amount1, id1) AS 1,
(amount2, id2) AS 2,
(amount3, id3) AS 3,
(amount4, id4) AS 4
)
);
Что также выводит:
Нет необходимости использовать дорогостоящие множественные самосоединения, вы можете упростить задачу, используя аналитические функции Sum() Over() и LAG() Over() с предложением оконного режима (... Rows Between ...).
Мощность/стоимость — 1/34.
WITH -- S a m p l e D a t a :
tbl AS
( Select 22 ID, 11.55 AMOUNT From Dual Union All
Select 33, 5.55 From Dual Union All
Select 44, 3.33 From Dual Union All
Select 55, 2.33 From Dual Union All
Select 66, 1.11 From Dual Union All
Select 77, .99 From Dual Union All
Select 88, -1.77 From Dual Union All
Select 91, 0.00 From Dual Union All
Select 99, -5.00 From Dual Union All
Select 100, 3.33 From Dual Union All
Select 111, 4.44 From Dual Union All
Select 122, 5.00 From Dual Union All
Select 133, 4.00 From Dual Union All
Select 144, 2.24 From Dual
),
В коде используются параметры, и он должен выдавать результат для любого количества строк (а не только 4) и любой суммы, которую вы привязали к параметрам.
... создайте cte (назовите его Calc), чтобы получить сумму всех строк :NumRowsParameter (4) вместе с начальными и конечными идентификаторами, общий результат которых равен :SumRowsParameter (15.68)
calc AS
( Select *
From ( Select ID, AMOUNT,
Sum(AMOUNT) OVER(Order By ID Rows Between :NumRowsParameter - 1 Preceding And Current Row) "SUM_ROWS",
LAG(ID, :NumRowsParameter - 1) Over(Order By ID) "ID_START", ID "ID_END"
From tbl
Order By ID
)
Where SUM_ROWS = :SumRowsParameter
)
... внутренне присоедините вашу таблицу к вышеуказанному cte и отфильтруйте результат с помощью предложения ON соединения
-- M a i n S Q L :
SELECT tbl.*
FROM calc
INNER JOIN tbl ON(tbl.ID Between calc.ID_START And calc.ID_END)
ORDER BY tbl.ID
/* R e s u l t : ( for :NumRowsParameter = 4 and :SumRowsParameter = 15.68 )
ID AMOUNT
--- ------
111 4.44
122 5
133 4
144 2.24 */
/* R e s u l t : ( for :NumRowsParameter = 5 and :SumRowsParameter = 13.31 )
ID AMOUNT
--- ------
33 5.55
44 3.33
55 2.33
66 1.11
77 0.99 */
/* R e s u l t : ( for :NumRowsParameter = 1 and :SumRowsParameter = 3.33 )
ID AMOUNT
--- ------
44 3.33
100 3.33 */
Вы думаете, что ОП ищет четыре последовательные строки. Я не. Что ж, желаемый результат в запросе — четыре последовательные строки, поэтому ОП должен уточнить, так ли это просто так или это часть задачи.
@ThorstenKettner Да, я так понял. Я могу ошибаться, конечно. Об этом прямо не говорится, но если они не являются последовательными, то данные выборки и ожидаемый результат ДОЛЖНЫ быть разными.
Уточните: 1. Должен ли результат быть четырьмя последовательными строками (упорядоченными по идентификатору), как в вашем примере? 2. Разрешаете ли вы строке появляться в наборе результатов более одного раза?