Find matching numbers - oracle

I'm trying to write a PLSQL query that will in two separate columns print random integers.
It will print 1000 numbers total (random 1 - 50 in each row).
What I need to figure out is how I after this has been done, replace the second column with either "yes" or "no" if it matches the first column
Such as:
Col A Col B
10 NO(42)
32 NO(12)
25 YES(25)
And so on.
This is my code:
CREATE TABLE table
(random_num INTEGER NOT NULL,
match INTEGER NOT NULL);
Declare
CURSOR cur_ IS
(Select
random_num,
match
from table);
Begin
FOR rec_ IN 1..1000
LOOP
INSERT INTO "table" (random_num,match) VALUES (DBMS_RANDOM.VALUE(1,50),DBMS_RANDOM.VALUE(1,50));
END LOOP;
END;
Now this works as I get two 1000 rows of each column with random numbers, but I need to implement this select:
SELECT random_num, CASE WHEN random_num = match THEN 'yes' ELSE 'no' END as match
FROM table
Into the loop so. Any takers on how I can do?

There's something wrong in what you said. You can't put yes (string) into an INTEGER datatype column.
This makes more sense:
Sample table:
SQL> CREATE TABLE test
2 (
3 random_num_1 INTEGER NOT NULL,
4 random_num_2 INTEGER NOT NULL,
5 match VARCHAR2 (3) NOT NULL
6 );
Table created.
Procedure: use local variables to store random numbers; then it is easy to compare them.
SQL> DECLARE
2 val1 NUMBER;
3 val2 NUMBER;
4 BEGIN
5 FOR i IN 1 .. 10 --> change it to 1000
6 LOOP
7 val1 := DBMS_RANDOM.VALUE (1, 50);
8 val2 := DBMS_RANDOM.VALUE (1, 50);
9
10 INSERT INTO test (random_num_1, random_num_2, match)
11 VALUES (val1,
12 val2,
13 CASE WHEN val1 = val2 THEN 'yes' ELSE 'no' END);
14 END LOOP;
15 END;
16 /
PL/SQL procedure successfully completed.
Result:
SQL> SELECT * FROM test;
RANDOM_NUM_1 RANDOM_NUM_2 MAT
------------ ------------ ---
45 31 no
40 48 no
43 27 no
49 41 no
6 38 no
5 18 no
18 35 no
15 34 no
11 19 no
37 39 no
10 rows selected.
SQL>

First if you want to produce random integers from 1 to 50 that are equaly distributed, you must be carefull.
[DBMS_RANDOM.VALUE (1, 50)][1] returns a decimal number greater than or equal than 1 and less than 50.
example
select DBMS_RANDOM.VALUE (1, 50) col from dual;
COL
----------
30,4901593
You cast the result in INTEGER type that performs rounding, so you will see all numbers, but the 1 and 50 will appear only half frequenty as other numbers.
So a better way to get random integers 1 .. 50 is 1 + trunc(50*DBMS_RANDOM.VALUE)
VALUE without parameters returns [0,1)
Also typically if you do not need to use PL/SQL do not use it
create table tab1 as
select 1 + trunc(50*DBMS_RANDOM.VALUE) col1, 1 + trunc(50*DBMS_RANDOM.VALUE) col2
from dual connect by level <= 10 /* increase as much rows are needed */
and add the MATCH column as virtual
alter table tab1
add (match varchar2(3) generated always as (
case when col1 = col2 then 'YES' else 'NO' end ) virtual);
COL1 COL2 MAT
---------- ---------- ---
33 6 NO
26 28 NO
35 22 NO
30 27 NO
17 45 NO
31 4 NO
11 21 NO
2 48 NO
35 25 NO
39 15 NO

Related

For each company in a table Company, I want to create a random number of rows between 50 and 250 in table Employee in PL/SQL?

For each company entry in a table Company, I want to create a random number of rows between 50 and 250 in table Employee in PL/SQL.
Here's one option, based on data in Scott's sample schema.
Departments (that's your company):
SQL> SELECT * FROM dept;
DEPTNO DNAME LOC
---------- -------------- -------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
Target table:
SQL> CREATE TABLE employee
2 (
3 deptno NUMBER,
4 empno NUMBER PRIMARY KEY,
5 ename VARCHAR2 (10),
6 salary NUMBER
7 );
Table created.
Sequence (for primary key values):
SQL> CREATE SEQUENCE seq_e;
Sequence created.
Here's the procedure: for each department, it creates L_ROWS number of rows (line #8) (I restricted it to a number between 1 and 5; your boundaries would be 50 and 250). It also creates random names (line #16) and salaries (line #17):
SQL> DECLARE
2 l_rows NUMBER;
3 BEGIN
4 DELETE FROM employee;
5
6 FOR cur_d IN (SELECT deptno FROM dept)
7 LOOP
8 l_rows := ROUND (DBMS_RANDOM.VALUE (1, 5));
9
10 INSERT INTO employee (deptno,
11 empno,
12 ename,
13 salary)
14 SELECT cur_d.deptno,
15 seq_e.NEXTVAL,
16 DBMS_RANDOM.string ('x', 7),
17 ROUND (DBMS_RANDOM.VALUE (100, 900))
18 FROM DUAL
19 CONNECT BY LEVEL <= l_rows;
20 END LOOP;
21 END;
22 /
PL/SQL procedure successfully completed.
Result:
SQL> SELECT *
2 FROM employee
3 ORDER BY deptno, empno;
DEPTNO EMPNO ENAME SALARY
---------- ---------- ---------- ----------
10 1 ZMO4RFN 830
10 2 AEXL34I 589
10 3 SI6X38Z 191
10 4 59EWI42 397
20 5 DBAMQDA 559
20 6 79X78JV 491
30 7 56ITU5V 178
30 8 09KPAIS 297
30 9 VQUVWDP 446
40 10 AHJZNVJ 182
40 11 0XWI3GC 553
40 12 7GNTCG4 629
40 13 23G871Z 480
13 rows selected.
SQL>
Adapting my answer to this question:
INSERT INTO employees (id, first_name, last_name, department_id)
SELECT employees__id__seq.NEXTVAL,
CASE FLOOR(DBMS_RANDOM.VALUE(1,6))
WHEN 1 THEN 'Faith'
WHEN 2 THEN 'Tom'
WHEN 3 THEN 'Anna'
WHEN 4 THEN 'Lisa'
WHEN 5 THEN 'Andy'
END,
CASE FLOOR(DBMS_RANDOM.VALUE(1,6))
WHEN 1 THEN 'Andrews'
WHEN 2 THEN 'Thorton'
WHEN 3 THEN 'Smith'
WHEN 4 THEN 'Jones'
WHEN 5 THEN 'Beirs'
END,
d.id
FROM ( SELECT id,
FLOOR(DBMS_RANDOM.VALUE(50,251)) AS num_employees
FROM departments
ORDER BY ROWNUM -- Materialize the sub-query so the random values are individually
-- generated.
) d
CROSS JOIN LATERAL (
SELECT LEVEL
FROM DUAL
CONNECT BY LEVEL <= d.num_employees
);
fiddle

Updated Question-Oracle case statement to extract value for all occurance associated with a substring in a single line

I want to write a case statement which can extract value for a particular substring from a column named details which has multiple occurrences for [address] is it possible with REGEX along with case?
sample Data in the column:
[address]:kattengat 28
[address]:1012 SZ
[address]: Amsterdam
The below SQL only outputs:
kattengat 28
Select case when to_number(instr(details),'[address')>0 then substr(details,REGEXP_INSTR(details,'address',1,1)+8,instr(substr(details,REGEXP_INSTR(details,'address',1,1)+8,'[')-1)) else '' end from table_name;
Expected output is :
kattengat 28 1012 SZ Amsterdam
Create table statement:
Create table test (id number(10), details clob);
Insert statement :
insert into test (id, details) values (1,to_clob ('[ADDRESS ] kattengat 28
[NAME ] ALEX
[PHONE ] 65438
[ADDRESS ] 1012 SZ
[DOB ] 1st Jan 1998
[ADDRESS ] Amsterdam')):
Please note I don't want to concat and add statements rather looking for a solution which can extract values associated with the substring [address] based on the number of occurrences of the substring in a single line
Here's one option:
SQL> with test (col) as
2 (select '[address]:kattengat 28
3 [address]:1012 SZ
4 [address]: Amsterdam' from dual
5 )
6 select trim(replace(regexp_substr(replace(col, chr(10), '#'), '[^#]+', 1, column_value), '[address]:', '')) result
7 from test cross join
8 table(cast(multiset(select level from dual
9 connect by level <= regexp_count(col, ':')
10 ) as sys.odcinumberlist));
RESULT
--------------------------------------------------------------------------------
kattengat 28
1012 SZ
Amsterdam
SQL>
What does it do?
lines #1 - 5 - sample data
line #6:
regexp_substr part of code is responsible for splitting source column value into separate rows
it affects not the original value, but the one whose new line character (chr(10)) is replaced (the 2nd replace) by #, and that character is used as a separator for regexp_substr
the 1st replace removes [address]: from the source
trim removes leading/trailing empty strings (as the one in front of "Amsterdam"
lines #8 - 10 are here to remove duplicate values from the final result (if source table doesn't contain only one row, as in my example). If it actually does, then code can be somewhat simplified.
With sample data you posted later (by the way, are you sure there are spaces in front of [NAME] etc.? I guess NOT!):
SQL> select * from test;
ID DETAILS
---------- --------------------------------------------------
1 [ADDRESS ] kattengat 28
[NAME ] ALEX
[PHONE ] 65438
[ADDRESS ] 1012 SZ
[DOB ] 1st Jan 1998
[ADDRESS ] Amsterdam
Code I previously posted, slightly modified because previously address was in lower case, there were NO spaces within square brackets, and there was a colon sign):
SQL> with temp as
2 (select trim(replace(regexp_substr(replace(details, chr(10), '#'), '[^#]+', 1, column_value), '[ADDRESS ]', '')) result
3 from test cross join
4 table(cast(multiset(select level from dual
5 connect by level <= regexp_count(details, '\[')
6 ) as sys.odcinumberlist))
7 )
8 select *
9 from temp
10 where instr(result, '[') = 0;
RESULT
--------------------------------------------------------------------------------
kattengat 28
1012 SZ
Amsterdam
SQL>
If you want to get result in one line, you could aggregate values returned by that query as
SQL> with temp as
2 (select trim(replace(regexp_substr(replace(details, chr(10), '#'), '[^#]+', 1, column_value), '[ADDRESS ]', '')) result,
3 column_value cv
4 from test cross join
5 table(cast(multiset(select level from dual
6 connect by level <= regexp_count(details, '\[')
7 ) as sys.odcinumberlist))
8 )
9 select listagg (result, ', ') within group (order by cv) final_result
10 from temp
11 where instr(result, '[') = 0;
FINAL_RESULT
--------------------------------------------------------------------------------
kattengat 28, 1012 SZ, Amsterdam
SQL>

How can i split a parameter in plsql

Hi Guys How can i split a parameter in plsql purun_kod is like 100,150,255,777 and i wanna split it and assign to v_urun_kod
thanks for your help.
My code is somewhat like:
function police_ara( purun_kod in acente.levha_no%type,
ppolice_no in varchar2) return sys_refcursor is
vret sys_refcursor;
v_urun_kod varchar(1000);
begin
v_urun_kod := purun_kod;
open vret for
with policeler as
(select distinct ph.police_hareket_id ph_police_hareket_id,
p.urun_kod
(select max(pho.police_hareket_id) from police_hareket_otr pho
where pho.police_hareket_id = ph.police_hareket_id
and (pho.durum_kod1 = 0 or pho.durum_kod2 = 0 or pho.durum_kod3 = 0 or pho.durum_kod4 = 0)) pho_police_hareket_id,
ph.odeme_arac_kod,
ph.police_id
from police p
inner join police_hareket ph
and (ph.ekbelge_no>0 or (select count(1)
from police_kotasyon pk
where pk.police_hareket_id = ph.police_hareket_id
and pk.kotasyon_seviyesi = 3
and rownum = 1)>0)
left join police_prim pp
on (pp.police_hareket_id = ph.police_hareket_id and pp.para_birim_kod = ph.para_birim_kod)
left join musteri_rol mr
on (mr.musteri_rol_id = pa.acente_id)
where (p.urun_kod = purun_kod or purun_kod is null)
and(p.police_no = ppolice_no or ppolice_no is null)
select urun_kod,
acente_kod,
brut_prim
from policeler
left join police_musteri pm_sg
on pm_sg.police_hareket_id = ph_police_hareket_id
and pm_sg.rol_id = pck_const_rol.sigortali
and pm_sg.sira_no = 1
left join musteri m_sg
on (m_sg.musteri_id = pm_sg.musteri_id)
order by police_id;
return vret;
end;
This is how to split a comma-separated values string into rows. It is a SQL*Plus example, just to show how it works:
SQL> var purun_kod varchar2(100);
SQL> exec :purun_kod := '100,150,255,777';
PL/SQL procedure successfully completed.
SQL> select regexp_substr(:purun_kod, '[^,]+', 1, level) urun_kod
2 from dual
3 connect by level <= regexp_count(:purun_kod, ',') + 1;
URUN_KOD
----------------------------------------------------------------------
100
150
255
777
SQL>
Currently, code you posted doesn't make much sense as you never use v_urun_kod. As the function returns ref cursor, there's no much sense in running that code for each separate value; you might do that in a loop, but that wouldn't work as you'd want it to (at least, I think so).
Looking at it, I guess that something like this might do what you're looking for:
SQL> create or replace
2 function police_ara( purun_kod in varchar2,
3 ppolice_no in varchar2)
4 return sys_refcursor is
5 vret sys_refcursor;
6 -- v_urun_kod varchar(1000); --> no need for that variable any more
7 begin
8 open vret for
9 with policeler as
10 (select distinct ph.police_hareket_id ph_police_hareket_id,
11 p.urun_kod,
12 (select max(pho.police_hareket_id)
13 from police_hareket_otr pho
14 where pho.police_hareket_id = ph.police_hareket_id
15 and ( pho.durum_kod1 = 0
16 or pho.durum_kod2 = 0
17 or pho.durum_kod3 = 0
18 or pho.durum_kod4 = 0
19 )
20 ) pho_police_hareket_id,
21 --
22 ph.odeme_arac_kod,
23 ph.police_id
24 from police p
25 --> this is new
26 join (select regexp_substr(purun_kod, '[^,]+', 1, level) urun_kod
27 from dual
28 connect by level <= regexp_count(purun_kod, ',') + 1
29 ) x on x.urun_kod = p.urun_kod
30 --> end of "this is new"
31 inner join police_hareket ph on 1 = 1 --> you're missing join condition here
32 and ( ph.ekbelge_no > 0
33 or (select count(1)
34 from police_kotasyon pk
35 where pk.police_hareket_id = ph.police_hareket_id
36 and pk.kotasyon_seviyesi = 3
37 and rownum = 1
38 ) > 0
39 )
40 left join police_prim pp
41 on ( pp.police_hareket_id = ph.police_hareket_id
42 and pp.para_birim_kod = ph.para_birim_kod
43 )
44 left join musteri_rol mr
45 on (mr.musteri_rol_id = pa.acente_id)
46 where ( p.urun_kod = purun_kod
47 or purun_kod is null
48 )
49 and ( p.police_no = ppolice_no
50 or ppolice_no is null
51 )
52 )
53 select urun_kod,
54 acente_kod,
55 brut_prim
56 from policeler
57 left join police_musteri pm_sg
58 on pm_sg.police_hareket_id = ph_police_hareket_id
59 and pm_sg.rol_id = pck_const_rol.sigortali
60 and pm_sg.sira_no = 1
61 left join musteri m_sg
62 on (m_sg.musteri_id = pm_sg.musteri_id)
63 order by police_id;
64 return vret;
65 end;
66 /
Warning: Function created with compilation errors.
Pay attention to the following:
line 6: there's no need for v_urun_kod variable any more
lines 25 - 30: this is a new piece of code which utilizes the above posted example. That SELECT is used as an inline view and joined with the police table's urun_kod column (at least, that's what I think you should do)
line 31: you're missing the ON clause here; fix it yourself, I don't know what should that be
the function is created with compilation errors as I don't have your tables; other than that, it might be OK:
SQL> show err
Errors for FUNCTION POLICE_ARA:
LINE/COL ERROR
-------- -----------------------------------------------------------------
8/5 PL/SQL: SQL Statement ignored
43/21 PL/SQL: ORA-00942: table or view does not exist
SQL>

Generate Random String in PL/SQL (oracle 12c)

I'm trying to generate a Random String using PL/SQL with only 2 fixed words. It's this possible?
Is this what you're looking for?
SQL> with
2 -- two fixed words
3 test as
4 (select 'fixed words' col from dual),
5 -- split them to rows
6 inter as
7 (select level lvl, regexp_substr(col, '.', 1, level) let
8 from test
9 connect by level <= length(col)
10 )
11 -- aggregate them back, randomly
12 select listagg(let, '') within group (order by dbms_random.value(1, max_lvl)) result
13 from inter
14 join (select max(lvl) max_lvl from inter) on 1 = 1;
RESULT
--------------------------------------------------------------------------------
reiosdwxf d
SQL> /
RESULT
--------------------------------------------------------------------------------
fe ixoddrws
SQL> /
RESULT
--------------------------------------------------------------------------------
wdxeorsdfi
SQL>

split records into buckets based on a sum of counts

i have a table that looks like below. i need to find a way to pick out phone numbers based on a sum of counts (the number will always be different but let's use 130 for this example).
So one of the solutions would be rows 1 through 5 and 11 (if you add up CountOfPeople values from those rows you will get 130). or 1-4,6,7,9,11,12. it doesn't matter which phone numbers are picked, as long as the total is 130.
sometimes you might not be able to get exactly 130, so "as close as possible but not exceeding" would be the rule.
is there a way to do this?
AutoID Phone Number Count Of People
1 5565787 57
2 2342343 30
3 2654456 17
4 3868556 12
5 9856756 12
6 9756456 4
7 4346365 4
8 2376743 3
9 9756343 3
10 2524349 3
11 2029393 2
12 9285656 1
I'm not sure that problem could be solved with pure SQL. But you can use table functions. Here is a little example for your problem.
First of all, we need to create table type:
create type t_bucket_row as object(
phone_number varchar2(10),
count_of_people number,
bucket_no number);
/
create type t_bucket_table as table of t_bucket_row;
/
And table with test data:
create table test_data as
with t as (
select 1 AutoID, '5565787' Phone_Number, 57 Count_Of_People from dual union all
select 2, '2342343', 30 from dual union all
select 3, '2654456', 17 from dual union all
select 4, '3868556', 12 from dual union all
select 5, '9856756', 12 from dual union all
select 6, '9756456', 4 from dual union all
select 7, '4346365', 4 from dual union all
select 8, '2376743', 3 from dual union all
select 9, '9756343', 3 from dual union all
select 10, '2524349', 3 from dual union all
select 11, '2029393', 2 from dual union all
select 12, '9285656', 1 from dual)
select * from t;
Then we create a function that implements an algorithm of distribution of clients (sorry, there is no comments in the code how it works, but it works; I can write it later if you need). Here we create a variable of table type, fill it with phones and bucket numbers, then return it from a function. After that, in SQL query, we use function's result as table in FROM clause. Parameter p_sum is your desired sum of counts of clients:
create or replace function get_buckets(p_sum number) return t_bucket_table is
buckets t_bucket_table := t_bucket_table();
type bucket_sums is table of number index by binary_integer;
sums bucket_sums;
counter number := 0;
found boolean;
begin
sums(1) := 0;
-- next line was edited to fix bug in resuult of distribution:
for i in (select t.*, rownum from test_data t order by t.count_of_people desc) loop
buckets.extend;
counter := counter + 1;
buckets(counter) := t_bucket_row(i.phone_number, i.count_of_people, 0);
if i.count_of_people > p_sum then
continue;
end if;
found := false;
for j in 1..sums.count loop
if sums(j) + i.count_of_people <= p_sum then
sums(j) := sums(j) + i.count_of_people;
buckets(counter).bucket_no := j;
found := true;
exit;
end if;
end loop;
if not found then
sums(sums.count + 1) := i.count_of_people;
buckets(counter).bucket_no := sums.count;
end if;
end loop;
return buckets;
end;
/
Now we can execute this function. Result is:
SQL> select * from table(get_buckets(130));
PHONE_NUMB COUNT_OF_PEOPLE BUCKET_NO
---------- --------------- ----------
5565787 57 1
2342343 30 1
2654456 17 1
3868556 12 1
9856756 12 1
9756456 4 2
4346365 4 2
2376743 3 2
9756343 3 2
2524349 3 2
2029393 2 1
9285656 1 2
12 rows selected.
Buckets distribution:
select bucket_no, sum(count_of_people) from table(get_buckets(130)) group by bucket_no;
BUCKET_NO SUM(COUNT_OF_PEOPLE)
---------- --------------------
1 130
2 18
If count_of_people is more than p_sum, it goes to bucket "0":
SQL> select * from table(get_buckets(35));
PHONE_NUMB COUNT_OF_PEOPLE BUCKET_NO
---------- --------------- ----------
5565787 57 0
2342343 30 1
2654456 17 2
3868556 12 2
9856756 12 3
9756456 4 1
4346365 4 2
2376743 3 3
9756343 3 3
2524349 3 3
2029393 2 2
9285656 1 1
12 rows selected.
SQL> select bucket_no, sum(count_of_people) from table(get_buckets(35)) group by bucket_no;
BUCKET_NO SUM(COUNT_OF_PEOPLE)
---------- --------------------
1 35
2 35
3 21
0 57
You can also try to use User-Defined Aggregate function. Will try to show You in a little example.
First of all, we need to create table types:
create or replace type TTN as table of number;
/
Then we are creating the routines that need to be implemented to define a user-defined aggregate function.
create or replace type TO_BALANCED_BUCKET as object
(
summ TTN,
result int,
static function ODCIAggregateInitialize(sctx in out nocopy TO_BALANCED_BUCKET) return number,
member function ODCIAggregateIterate(self in out nocopy TO_BALANCED_BUCKET, value in number)
return number,
member function ODCIAggregateTerminate(self in TO_BALANCED_BUCKET,
returnValue out number,
flags in number) return number,
member function ODCIAggregateMerge(self in out nocopy TO_BALANCED_BUCKET, ctx2 in TO_BALANCED_BUCKET)
return number
)
/
create or replace type body TO_BALANCED_BUCKET is
static function ODCIAggregateInitialize(sctx in out nocopy TO_BALANCED_BUCKET) return number is
begin
sctx := TO_BALANCED_BUCKET(TTN(0), 1);
return ODCIConst.Success;
end;
member function ODCIAggregateIterate(self in out nocopy TO_BALANCED_BUCKET, value in number)
return number is
b_FoundGroup boolean := false;
begin
if value > 130 then
result := 0;
else
for li in 1..summ.count loop
if summ(li) + value <= 130 then
b_FoundGroup := true;
summ(li) := summ(li) + value;
result := li;
exit;
end if;
end loop;
if not b_FoundGroup then
summ.extend;
summ(summ.count) := value;
result := summ.count;
end if;
end if;
return ODCIConst.Success;
end;
member function ODCIAggregateTerminate(self in TO_BALANCED_BUCKET,
returnValue out number,
flags in number) return number is
begin
returnValue := self.result;
return ODCIConst.Success;
end;
member function ODCIAggregateMerge(self in out nocopy TO_BALANCED_BUCKET, ctx2 in TO_BALANCED_BUCKET)
return number is
begin
return ODCIConst.Error;
end;
end;
/
Then we are creating the aggregate function itself.
create or replace function balanced_bucket(input number) return number
parallel_enable
aggregate using TO_BALANCED_BUCKET;
/
And finally the query itself
with test_data as (
select 1 as AutoID, '5565787' as Phone_Number, 12 as Count_Of_People from dual union all
select 2, '2342343', 3 from dual union all
select 3, '2654456', 1 from dual union all
select 4, '3868556', 12 from dual union all
select 5, '9856756', 4 from dual union all
select 6, '9756456', 4 from dual union all
select 7, '4346365', 57 from dual union all
select 8, '2376743', 3 from dual union all
select 9, '9756343', 3 from dual union all
select 10, '2524349', 30 from dual union all
select 11, '2029393', 2 from dual union all
select 12, '9285656', 17 from dual
)
select t.phone_number, t.count_of_people,
balanced_bucket(t.count_of_people) over(order by t.count_of_people desc) balanced_bucket
from test_data t
Hope this solution will help. The algorithm of distribution of clients is Dmity's.
For the "first bucket" solution this is a nice exercise in recursive subquery factoring. The following query gives you such a bucket (although with phone numbers concatenated to a single string):
with source$ as (
select 1 as AutoID, '5565787' as Phone_Number, 12 as Count_Of_People from dual union all
select 2, '2342343', 3 from dual union all
select 3, '2654456', 1 from dual union all
select 4, '3868556', 12 from dual union all
select 5, '9856756', 4 from dual union all
select 6, '9756456', 4 from dual union all
select 7, '4346365', 57 from dual union all
select 8, '2376743', 3 from dual union all
select 9, '9756343', 3 from dual union all
select 10, '2524349', 30 from dual union all
select 11, '2029393', 2 from dual union all
select 12, '9285656', 17 from dual
),
permutator$ (autoid, phone_number, count_of_people, autoid_list, phone_number_list, count_of_people_sum, count_of_people_list) as (
select S.autoid, phone_number, count_of_people,
to_char(autoid), cast(phone_number as varchar2(4000)), count_of_people, to_char(count_of_people)
from source$ S
union all
select S.autoid, S.phone_number, S.count_of_people,
P.autoid_list||'|'||S.autoid, P.phone_number_list||'|'||S.phone_number, P.count_of_people_sum + S.count_of_people, P.count_of_people_list||'+'||S.count_of_people
from permutator$ P
join source$ S
on S.autoid > P.autoid
where P.count_of_people_sum + S.count_of_people <= 130
)
search depth first by autoid asc set siblings_order$,
priority_ordered$ as (
select P.*,
row_number() over (partition by null order by abs(count_of_people_sum-130), siblings_order$ asc) as your_best_call$
from permutator$ P
)
select autoid_list, phone_number_list, count_of_people_sum, count_of_people_list
from priority_ordered$
where your_best_call$ = 1
;
... and if you rather want a row-by-row list of the original items, then replace the last ...
select autoid_list, phone_number_list, count_of_people_sum, count_of_people_list
from priority_ordered$
where your_best_call$ = 1
;
... with ...
select autoid, count_of_people, phone_number
from priority_ordered$ PO
start with your_best_call$ = 1
connect by PO.autoid_list||'|'||prior PO.autoid = prior PO.autoid_list
;
With a little help from the object-relational features of Oracle the phone number collection may be, in a very elegant way, resolved by a collector object (an object which collects data to its member collection attribute via a member method, returning a new instance of its class). A small example of SQL*Plus spools for this solution:
SQL> set verify off
SQL> define maxcountofpeoplesum = 130
SQL> ##23023283-split-records-into-buckets-based-on-a-sum-of-counts.sql
COUNT_OF_PEOPLE_SUM AUTOID PHONE_NUMBER COUNT_OF_PEOPLE
------------------- ---------- --------------- ---------------
130 1 5565787 12
130 2 2342343 3
130 3 2654456 1
130 5 9856756 4
130 6 9756456 4
130 7 4346365 57
130 10 2524349 30
130 11 2029393 2
130 12 9285656 17
9 rows selected.
SQL> define maxcountofpeoplesum = 15
SQL> ##23023283-split-records-into-buckets-based-on-a-sum-of-counts.sql
COUNT_OF_PEOPLE_SUM AUTOID PHONE_NUMBER COUNT_OF_PEOPLE
------------------- ---------- --------------- ---------------
15 1 5565787 12
15 2 2342343 3
SQL> define maxcountofpeoplesum = 200
SQL> ##23023283-split-records-into-buckets-based-on-a-sum-of-counts.sql
COUNT_OF_PEOPLE_SUM AUTOID PHONE_NUMBER COUNT_OF_PEOPLE
------------------- ---------- --------------- ---------------
148 1 5565787 12
148 2 2342343 3
148 3 2654456 1
148 4 3868556 12
148 5 9856756 4
148 6 9756456 4
148 7 4346365 57
148 8 2376743 3
148 9 9756343 3
148 10 2524349 30
148 11 2029393 2
148 12 9285656 17
12 rows selected.
SQL> define maxcountofpeoplesum = 147
SQL> ##23023283-split-records-into-buckets-based-on-a-sum-of-counts.sql
COUNT_OF_PEOPLE_SUM AUTOID PHONE_NUMBER COUNT_OF_PEOPLE
------------------- ---------- --------------- ---------------
147 1 5565787 12
147 2 2342343 3
147 4 3868556 12
147 5 9856756 4
147 6 9756456 4
147 7 4346365 57
147 8 2376743 3
147 9 9756343 3
147 10 2524349 30
147 11 2029393 2
147 12 9285656 17
11 rows selected.
I'm pretty sure the query could be enhanced to query all buckets, as Dmitry's solution does, but that would result in an even huger and possibly badly performing query. Dmitry's solution looks much simpler and more straightforward for your problem.
Enjoy.

Resources