Related
I have a POST table, a CATEGORY table, a ACTION table and ACTION_TYPE table, I explain the ACTION table contains all the actions that were made, and the table ACTION_TYPE contains the actions details for example the ACTION whose ID = 4 has ACTION_TYPE_ID = 1 for POST_ID 6, which mean an action was made for post number 50, we can have many actions for one post_id
The POST table
id title content category_id
---------- ---------- ---------- ------------
1 title1 Text... 1
2 title2 Text... 1
3 title3 Text... 1
4 title4 Text... 3
5 title5 Text... 2
6 title6 Text... 1
The CATEGORY table
id name
---------- ----------
1 category_1
2 category_2
3 category_3
The ACTION_TYPE table
id name
---------- ----------
1 updated
2 deleted
3 restored
4 hided
The ACTION table
id post_id action_type_id date
---------- ---------- -------------- -----
1 1 1 2017-01-01
2 1 1 2017-02-15
3 1 3 2018-06-10
4 6 1 2019-08-01
5 5 2 2019-12-09
6 2 3 2020-04-27
7 2 1 2020-07-29
8 3 2 2021-03-13
Now I explain the case, I actually have two queries a query to count the posts for each category and another to count the actions performed on each post by category which work perfectly.
Here is my first query
select categories, count(*) as cnt_posts_per_cat
from(
select
case
when p.category_id is not null then c.name
end as categories
from post p
left join category c on p.category _id = c.id
)
group by categories
;
Which brings this result
categories cnt_posts_per_cat
---------- -------------------
category_1 4
category_2 1
category_3 1
Ans here is my second query
select categories, count(*) as cnt_actions_per_cat
from(
select distinct ac.post_id AS action_post_id, max(ac.date) over (partition by ac.post_id) as max_date,
case
when ac.action_type_id is not null then act.name
end as actions,
case
when p.category_id is not null then c.name
else 'na'
end as categories
from action ac
left join post p on ac.post_id = p.id
left join category c on p.category _id = c.id
left join action_type act on ac.action_type_id = act.id
where act.name in ('restored','deleted','updated')
)
group by categories
;
Which brings this correct result because it's seclect the last action for each action_type
categories cnt_actions_per_cat
---------- -------------------
category_1 3
category_2 1
category_3 na
But I would like to have a single result table for both queries at the same time as follow :
Here the result expected to be
categories cnt_posts_per_cat cnt_actions_per_cat
---------- ----------------- -------------------
category_1 4 3
category_2 1 1
category_3 1 na
i was trying union and union all but it's not correct it return following result
categories cnt_posts_per_cat
---------- -----------------
category_1 7
category_2 2
category_3 1
Best regards
How about correlated subqueries?
Sample data:
SQL> with
2 post (id, category_id) as
3 (select 1, 1 from dual union all
4 select 2, 1 from dual union all
5 select 3, 1 from dual union all
6 select 4, 3 from dual union all
7 select 5, 2 from dual union all
8 select 6, 1 from dual
9 ),
10 category (id, name) as
11 (select 1, 'category_1' from dual union all
12 select 2, 'category_2' from dual union all
13 select 3, 'category_3' from dual
14 ),
15 action_type (id, name) as
16 (select 1, 'updated' from dual union all
17 select 2, 'deleted' from dual union all
18 select 3, 'restored' from dual union all
19 select 4, 'hided' from dual
20 ),
21 action (id, post_id, action_type_id) as
22 (select 1, 1, 1 from dual union all
23 select 2, 1, 1 from dual union all
24 select 3, 1, 3 from dual union all
25 select 4, 6, 1 from dual union all
26 select 5, 5, 2 from dual union all
27 select 6, 2, 3 from dual union all
28 select 7, 2, 1 from dual union all
29 select 8, 3, 2 from dual
30 )
Query begins here:
31 select c.name,
32 --
33 (select count(*)
34 from post p
35 where p.category_id = c.id
36 ) cnt_posts_per_cat,
37 --
38 (select count(*)
39 from action a join post p on p.id = a.post_id
40 join action_type t on t.id = a.id
41 where p.category_id = c.id
42 and t.name in ('restored', 'deleted', 'updated')
43 ) cnt_actions_per_cat
44 from category c
45 order by c.name;
NAME CNT_POSTS_PER_CAT CNT_ACTIONS_PER_CAT
---------- ----------------- -------------------
category_1 4 3
category_2 1 0
category_3 1 0
SQL>
I have a dataset in which one column is Branch-ID and other one is Branch Manager and it looks as follows in the given url.
dataset
I want to combine the branch managers into one single column based on the branch-id. For example if Bob and Sandra are two different branch-managers but have the same branch id which is branch-id=1, then we should concatenate them together as Bob-Sandra and place them in a separately created column.
I have attached the expected output for the above dataset. expected_output_dataset
I am currently using Oracle Analytics Cloud Professional Version.
I don't know Oracle Analytics, but - if it has anything to do with an Oracle database and its capabilities, then listagg helps.
Sample data in lines #1 - 10; query you might be interested in begins at line #11.
SQL> with test (account_id, branch_id, branch_manager) as
2 (select 1, 123, 'Sandra' from dual union all
3 select 3, 124, 'Martha' from dual union all
4 select 4, 125, 'John' from dual union all
5 select 6, 126, 'Andrew' from dual union all
6 select 7, 126, 'Mathew' from dual union all
7 select 2, 123, 'Michael' from dual union all
8 select 5, 125, 'David' from dual union all
9 select 8, 126, 'Mark' from dual
10 )
11 select a.account_id, a.branch_id, a.branch_manager,
12 b.concatenated_column
13 from test a join (select branch_id,
14 listagg(branch_manager, '-') within group (order by null) concatenated_column
15 from test
16 group by branch_id
17 ) b on b.branch_id = a.branch_id;
ACCOUNT_ID BRANCH_ID BRANCH_ CONCATENATED_COLUMN
---------- ---------- ------- -------------------------
1 123 Sandra Michael-Sandra
3 124 Martha Martha
4 125 John David-John
6 126 Andrew Andrew-Mark-Mathew
7 126 Mathew Andrew-Mark-Mathew
2 123 Michael Michael-Sandra
5 125 David David-John
8 126 Mark Andrew-Mark-Mathew
8 rows selected.
SQL>
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.
I would like to pivot a select statement.
Columns "Country", "Store" and "Sales" are given.
Now I would like to have an output like:
Store1 Store2 Store3
Country1 2342 2342 5675
Country2 5753 3274 7326
Country3 1543 4367 3367
So basically I need the salescount for every Store, for every Country.
The Input comes from (example):
Country: StoreNr: ProductSold:
Belgium 23 Car
Belgium 23 House
Netherland 23 Car
Output would be:
Store23
Belgium 2
Netherlands 1
If the number of stores is finite, you could use one of these approaches:
Using count() aggregate function combined with case expression:
-- sample of data. just for the sake of demonstration
SQL> with t1(Country, StoreNr, ProductSold) as(
2 select 'Belgium' , 23, 'Car' from dual union all
3 select 'Belgium' , 23, 'House' from dual union all
4 select 'Netherland', 23, 'Car' from dual union all
5 select 'Belgium' , 25, 'House' from dual
6 )
7 select country
8 , count(case
9 when StoreNr = 23
10 then 1
11 end) as storeNr_23
12 , count(case
13 when StoreNr = 25
14 then 1
15 end) as storeNr_25
16 from t1
17 group by country
18 ;
Result:
COUNTRY STORENR_23 STORENR_25
---------- ---------- ----------
Belgium 2 1
Netherland 1 0
Starting from Oracle 11g and up, the pivot operator as follows:
select *
from (Select country as country
, country as country_cnt
, StoreNr
from t1)
pivot( -- list all store numbers here
count(country_cnt) for storenr in ( 23 as StoreNr_23
, 25 as StoreNr_25)
)
Result:
COUNTRY STORENR_23 STORENR_25
---------- ---------- ----------
Belgium 2 1
Netherland 1 0
my table;
Date | Cost
01.01.2010 | 100
02.01.2010 | 200
03.01.2010 | 300
04.01.2010 | 400
10.01.2010 | 800
11.01.2010 | 800
12.01.2010 | 800
25.01.2010 | 500
26.01.2010 | 500
05.02.2010 | 600
13.02.2010 | 700
15.02.2010 | 700
ı want to make "date between '01.01.2010' and '28.02.2010' " weekly view
Week 1 | Week 2 | week 3 | week . .. .
1000 | 2400 | 0 | 32432.... . .
How to make pls help thank you ?
SQL> create table mytable (the_date,cost)
2 as
3 select date '2010-01-01', 100 from dual union all
4 select date '2010-01-02', 200 from dual union all
5 select date '2010-01-03', 300 from dual union all
6 select date '2010-01-04', 400 from dual union all
7 select date '2010-01-10', 800 from dual union all
8 select date '2010-01-11', 800 from dual union all
9 select date '2010-01-12', 800 from dual union all
10 select date '2010-01-25', 500 from dual union all
11 select date '2010-01-26', 500 from dual union all
12 select date '2010-02-05', 600 from dual union all
13 select date '2010-02-13', 700 from dual union all
14 select date '2010-02-15', 700 from dual
15 /
Table created.
This query uses MAX-DECODE as a standard pivot technique. If you are on version 11, you can also use the PIVOT operator. The below version will work on any version.
SQL> select nvl(max(decode(the_week,'01',cost)),0) "Week 1"
2 , nvl(max(decode(the_week,'02',cost)),0) "Week 2"
3 , nvl(max(decode(the_week,'03',cost)),0) "Week 3"
4 , nvl(max(decode(the_week,'04',cost)),0) "Week 4"
5 , nvl(max(decode(the_week,'05',cost)),0) "Week 5"
6 , nvl(max(decode(the_week,'06',cost)),0) "Week 6"
7 , nvl(max(decode(the_week,'07',cost)),0) "Week 7"
8 , nvl(max(decode(the_week,'08',cost)),0) "Week 8"
9 , nvl(max(decode(the_week,'09',cost)),0) "Week 9"
10 from ( select to_char(the_date,'ww') the_week
11 , sum(cost) cost
12 from mytable
13 where the_date between date '2010-01-01' and date '2010-02-28'
14 group by to_char(the_date,'ww')
15 )
16 /
Week 1 Week 2 Week 3 Week 4 Week 5 Week 6 Week 7 Week 8 Week 9
---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------
1000 2400 0 1000 0 600 1400 0 0
1 row selected.
Regards,
Rob.
select to_char(date, 'ww'), sum(cost)
from table
group by to_char(date, 'ww');
Or something along those lines should bring sums by week with the week number in the result. Link to Oracle 11g to_char syntax and link to format values. If that doesn't do it and you don't need the week number trunc(date, 'DAY') might be what you're looking for.
Not elegant solution, but its works...
SELECT SUM(Week1) Week1, SUM(Week2) Week2 ... SUM(Week36) Week36,
SUM(Week36) Week37
FROM (SELECT DECODE(WeekNo, 1, Cost, 0) Week1,
DECODE(WeekNo, 2, Cost, 0) Week2,
...
DECODE(WeekNo, 36, Cost, 0) Week36,
DECODE(WeekNo, 37, Cost, 0) Week37
FROM (SELECT to_char(DateFrom, 'IW') WeekNo, SUM(cost) Cost
FROM (SELECT trunc(SYSDATE) + LEVEL - 1 DateFrom,
LEVEL * 100 Cost
FROM dual
CONNECT BY LEVEL < 40)
GROUP BY to_char(DateFrom, 'IW')))