getting maximum number from many-to-many relation (not properly ended) - oracle

i have 2 tables with many-to-many relation
book
-----
|ID |
-----
1
2
3
book_category
-----------------------
|book_id|category_id |
-----------------------
1 |1
2 |1
3 |2
category
----------
|id|name |
----------
1 |1
2 |1
3 |2
i need to show the name of the most common category
i'm trying this query
select name,num from category inner join
(select category_id, count(book_id) as num from book_category group by
category_id) on id = category_id
where num >= all (select count(book_id) from book_category group by
category_id) on id = category_id)
and getting sql command not properly ended.
tried many different thing still not working, any help is appreciated.
thanks ^_^

Starting from inner query toward outer ones, we take count(book_id) for all category_id, rank them in descending order and then filter the entry/entries based on the rank = 1 condition. Even if there are more than one category_id with same highest count(book_id), you will see all of them in the result.
SELECT B.CATEGORY_ID, B.NUM
FROM
(
SELECT A.CATEGORY_ID, A.NUM, DENSE_RANK() OVER (ORDER BY A.NUM DESC) AS MOST_COMMON_RANK
FROM
(SELECT CATEGORY_ID, COUNT(BOOK_ID) AS NUM
FROM BOOK_CATEGORY
GROUP BY CATEGORY_ID ORDER BY COUNT(BOOK_ID) DESC) A
) B
WHERE B.MOST_COMMON_RANK = 1;

Related

Oracle SQL Total Products By Category

I have an exercise that says that I have to do this query: Names of the product categories (CategoryName) and total number of products for each of the categories
I have two tables:
-The first table is called "Categories" where the category of the products is found
-The second table is called "Products" and it contains the products
The primary key "Categoryid" of Categories is shared as a foreign with Products, so I think what to do is count how many products each id has and display the name on the left
I am going to leave two examples with the content of the two tables, since the two tables cannot be joined, but not how to count the number of products for each category
Table Categories:
| Categoryid | Categoryname ||
| -------- | ------------- ||
| 1 | Beverages ||
| 2 | Condiments ||
| 3 | Confections ||
Table Products:
| Productid | Productname | Categoryid ||
| -------- | ------------- | ---------- ||
| 1 | Chai | 1 ||
| 2 | Chang | 1 ||
| 3 | Tofu | 5 ||
How it should come out:
| CategoryName | TotalProducts||
| -------- | ----------- ||
| Beverages | 10 ||
| Condiments | 5 ||
| Confections | 3 ||
I don't know how to count the number of products for each category
i try this:
SELECT Categoryname COUNT(*)
FROM Categories JOIN Products ON Categories.Categoryid=Products.Categoryid;
Looks like outer join (so that you would display categories that don't have any products), counting products (not "generally" because you'd get false result; I'll show what I mean).
Sample data (your data is wrong; you can't have Tofu in category 5 if that category doesn't exist; foreign key constraint wouldn't allow it):
SQL> with
2 categories (categoryid, categoryname) as
3 (select 1, 'beverages' from dual union all
4 select 2, 'condiments' from dual union all
5 select 3, 'confections' from dual union all
6 select 5, 'category 5' from dual
7 ),
8 products (productid, productname, categoryid) as
9 (select 1, 'chai' , 1 from dual union all
10 select 2, 'chang', 1 from dual union all
11 select 3, 'tofu' , 5 from dual
12 )
Query: count products (line #14):
13 select c.categoryname,
14 count(p.productid) number_of_products
15 from categories c left join products p on p.categoryid = c.categoryid
16 group by c.categoryname;
CATEGORYNAME NUMBER_OF_PRODUCTS
-------------------- ------------------
beverages 2
category 5 1
condiments 0
confections 0
SQL>
If you used count(*) (line #14), you'd get wrong result as you'd count category itself:
13 select c.categoryname,
14 count(*) number_of_products
15 from categories c left join products p on p.categoryid = c.categoryid
16 group by c.categoryname;
CATEGORYNAME NUMBER_OF_PRODUCTS
-------------------- ------------------
beverages 2
category 5 1
condiments 1 --> no products in
confections 1 --> these two categories!
SQL>
However, if you aren't interested in categories that don't have any products, inner join and count(*) (as well as count(p.productid)) would do:
13 select c.categoryname,
14 count(*) number_of_products
15 from categories c join products p on p.categoryid = c.categoryid
16 group by c.categoryname;
CATEGORYNAME NUMBER_OF_PRODUCTS
-------------------- ------------------
beverages 2
category 5 1
SQL>
For each category I listed all the products and its count.
CREATE TABLE categories(
category_id, category_name) AS
SELECT 1, 'Beverages' FROM DUAL UNION ALL
SELECT 2, 'Condiments' FROM DUAL UNION ALL
SELECT 3, 'Confections' FROM DUAL;
CREATE TABLE products(
product_id, product_name, category_id) AS
SELECT 1, 'Chai',1 FROM DUAL UNION ALL
SELECT 2, 'Chang',1 FROM DUAL UNION ALL
SELECT 3, 'Tofu', 2 FROM DUAL;
select c.category_id,
c.category_name,
listagg(p.product_name,', ') within group(order by p.product_name) product_list,
count(p.product_name) cnt
from categories c,
products p
where c.category_id = p.category_id(+)
group by c.category_id,
c.category_name
order by c.category_id,
c.category_name
/
CATEGORY_ID CATEGORY_NA PRODUCT_LIST CNT
----------- ----------- -------------------- ----------
1 Beverages Chai, Chang 2
2 Condiments Tofu 1
3 Confections 0

How to get rank in laravel

In my Laravel 5.3 app, votes table has a net votes column. I'd like to find the video's rank according to net votes. I'd like to display the rank like below list. I know sql #raw method. but, I want to use Laravel method. because, there are some other tables jointed with this user table and some other shorting also need to be done.
video table:
id | net_votes| video_id |
------ | -------: |:-------: |
1 | 5 | 1 |
2 | 11 | 2 |
3 | 3 | 1 |
4 | 6 | 3 |
5 | 5 | 2 |
I want to get results like this
id | net_votes| rank
------ | -------: |:----:
2 | 11 | 1
4 | 6 | 2
1 | 5 | 3
5 | 5 | 4
3 | 3 | 5
I am using this code now. its working. but I want to use Laravel Eloquent Method.
$score_board_list = DB::select("SELECT *, total, #r:=#r+1 as rank,
#l:=total FROM ( select username, first_name, video_title,
net_votes, sum(net_votes) as total from videos
LEFT JOIN users ON videos.user_id = users.id
LEFT JOIN profile ON users.id = profile.user_id
group by videos.id order by total desc, videos.created_at desc ) totals, (SELECT #r:=0, #l:=NULL) rank");
To do that
Stored your subquery into a variable
$subquery = "(
SELECT username,
first_name,
video_title,
net_votes,
Sum(net_votes) AS total
FROM videos
LEFT JOIN users
ON videos.user_id = users.id
LEFT JOIN profile
ON users.id = profile.user_id
GROUP BY videos.id
ORDER BY total DESC,
videos.created_at DESC ) totals";
then equivalent for
Select * from (subquery)
into Eloquent is
DB::table(DB::raw('subquery'))
then to select a custom column
// for example
->select(DB::raw('#r:=#r+1 as rank'))
So your query builder would be like this
$subquery = "(
SELECT username,
first_name,
video_title,
net_votes,
Sum(net_votes) AS total
FROM videos
LEFT JOIN users
ON videos.user_id = users.id
LEFT JOIN profile
ON users.id = profile.user_id
GROUP BY videos.id
ORDER BY total DESC,
videos.created_at DESC ) totals";
$score_board_list = DB::table(DB::raw($subquery))
->select(
'*',
'total',
DB::raw('#r:=#r+1 as rank'),
DB::raw('#l:=total'))
->get();

Using partition by to get count in Oracle

I have a EMP table. I need to get number of employees in each department grouped by country name = 'INDIA','USA', 'AUSTRALIA'.
For example,
DEPARTMENT | #EMPLOYEE(INDIA) | #EMPLOYEE(USA) | # EMPLOYEE(AUSTRALIA)
ACCOUNTING | 5 |2 | 3
IT | 5 |2 | 1
BUSINESS | 1 |4 | 3
I need to use Partition BY to do it. I am able to use PARTITION by to get the total count of employees for each department. But I am not able to subgroup by country name.
Please give me suggestions.
Thank you.
Consider conditional count.
SELECT DEPARTMENT,
COUNT(CASE WHEN Country = 'INDIA' THEN 1 END) as emp_india,
COUNT(CASE WHEN Country = 'USA' THEN 1 END) as emp_usa,
COUNT(CASE WHEN Country = 'AUSTRALIA' THEN 1 END) as emp_australia
GROUP BY DEPARTMENT

NULL values not found in cursor

I am trying to:
Create a cursor that gets all the current prices of items in a store.
I bulk collect the cursor and loop upserting by using MERGE statement into STORE_INVENTORY table.
Now I want to NULL out the PRICE column in the STORE_INVENTORY table that are not in the cursor.
How can step 3 be done? I can do step 1 and 2 already as I have already updated or inserted the items that are pulled from the cursor.
Here is some example data:
There are three source tables where it is updated by an external party. My objective is to take these three sources of data and merge it into a singular table.
SOURCE TABLES
ITEM_TYPES
DESC_ID | TYPE
A | Kitchen
B | Bath
ITEM_MANIFEST
LOC_ID | ORIGIN
U | USA
C | CHINA
ITEM_PRICE
ITEM_ID | PRICE | DESC_ID | LOC_ID | DATE
0 | 3.99 | A | U | 9/11/2015
1 | 2.99 | B | C | 9/11/2015
2 | 1.99 | A | U | 9/05/2015
DESTINATION TABLE
STORE_INVENTORY
ITEM_ID | TYPE | ORIGIN | PRICE
0 | Kitchen | CHINA | 3.99
8 | Bath | USA | 2.99
So after I execute the SQL Procedure that has a date as a parameter. It will only pull from ITEM_PRICE if it's after the given date.
If execute the procedure with the passed in date 9/10/2015
Expected Output
STORE_INVENTORY
0 | Kitchen | USA | 3.99
1 | Bath | China | 2.99
8 | Bath | USA | NULL
So, something like this, then?
drop table item_description;
drop table item_manifest;
drop table item_price;
drop table store_inventory;
create table item_description
as
select 'A' desc_id, 'Kitchen' type from dual union all
select 'B' desc_id, 'Bath' type from dual;
create table item_manifest
as
select 'U' loc_id, 'USA' origin from dual union all
select 'C' loc_id, 'CHINA' origin from dual;
create table item_price
as
select 0 item_id, 3.99 price, 'A' desc_id, 'U' loc_id, to_date('11/09/2015', 'dd/mm/yyyy') dt from dual union all
select 1 item_id, 2.99 price, 'B' desc_id, 'C' loc_id, to_date('11/09/2015', 'dd/mm/yyyy') dt from dual union all
select 2 item_id, 1.99 price, 'A' desc_id, 'U' loc_id, to_date('05/09/2015', 'dd/mm/yyyy') dt from dual;
create table store_inventory
as
select 0 item_id, 'Kitchen' type, 'CHINA' origin, 3.99 price from dual union all
select 8 item_id, 'Bath' type, 'USA' origin, 2.99 price from dual;
select * from store_inventory;
ITEM_ID TYPE ORIGIN PRICE
---------- ------- ------ ----------
0 Kitchen CHINA 3.99
8 Bath USA 2.99
select coalesce(ip.item_id, si.item_id) item_id,
coalesce(id.type, si.type) type,
coalesce(im.origin, si.origin) origin,
ip.price
from item_description id
inner join item_price ip on (id.desc_id = ip.desc_id and ip.dt > to_date('10/09/2015', 'dd/mm/yyyy')) -- use a parameter for the date here
inner join item_manifest im on (ip.loc_id = im.loc_id)
full outer join store_inventory si on (si.item_id = ip.item_id);
ITEM_ID TYPE ORIGIN PRICE
---------- ------- ------ ----------
0 Kitchen USA 3.99
8 Bath USA
1 Bath CHINA 2.99
merge into store_inventory tgt
using (select coalesce(ip.item_id, si.item_id) item_id,
coalesce(id.type, si.type) type,
coalesce(im.origin, si.origin) origin,
ip.price
from item_description id
inner join item_price ip on (id.desc_id = ip.desc_id and ip.dt > to_date('10/09/2015', 'dd/mm/yyyy')) -- use a parameter for the date here
inner join item_manifest im on (ip.loc_id = im.loc_id)
full outer join store_inventory si on (si.item_id = ip.item_id)) src
on (src.item_id = tgt.item_id)
when matched then
update set tgt.type = src.type,
tgt.origin = src.origin,
tgt.price = src.price
when not matched then
insert (tgt.item_id, tgt.type, tgt.origin, tgt.price)
values (src.item_id, src.type, src.origin, src.price);
commit;
select * from store_inventory;
ITEM_ID TYPE ORIGIN PRICE
---------- ------- ------ ----------
0 Kitchen USA 3.99
8 Bath USA
1 Bath CHINA 2.99
Obviously, your procedure would have an input parameter of DATE datatype to pass into the query, and your query would use the parameter, rather than a hardcoded date like I did in my example. E.g. ip.dt > p_cutoff_date
I can do step 1 and 2 already as I have already updated or inserted
the items that are pulled from the cursor.
Hmm. These steps seem unnecessary - why not do them as part of the MERGE statement? What does the store_inventory table look like before you do your insert/update from the cursor? Also, what is the cursor you're using to do this?
couldn't you do a date-limited subselect of ITEM_PRICE.PRICE, after pulling in the TYPE and ORIGIN via the main join to ITEM_PRICE, without limiting on date?
i.e. something like.
select ITEM_ID, TYPE, ORIGIN
/* not selecting PRICE in the main join */
,(select PRICE from ITEM_PRICE where your join conditions
and DATE >= your param)
from ITEM_TYPES, ITEM_MANIFEST, ITEM_PRICE
where your join conditions, but no criteria on DATE
Sorry, would be clearer and easier to type up if you had provided your existing query.
From re-reading your question, I am unsure if you are inserting only 2 rows but want to get 3. Or if you have 3 rows, but you want to NULL out the missing price.
If the target table already has the 3 rows, then, instead of doing a CURSOR based approach (which can be slow on high volumes and is fussy to write), why not do an UPDATE instead, with DATE as a criteria? The NULL will be assigned to price if there is no match, that's how UPDATEs work.
UPDATE STORE_INVENTORY set PRICE
= (select PRICE from ITEM_PRICE where your join conditions
and DATE >= your param)

Retrieve from Oracle db key value pair

I need to retrieve 3 values with different key from a key value pair table.
My database schema as follows. I need to reach to table3 from table1 by taking the E_SUBID and then joining the table2 with E_SUBID. Once table1 and table2 are joined I need take to take E_CID from table2 to join it with table2 E_CID to get the "Attr_Value" keeping E_CID as a criteria.
Table1
------------------------
|E_SUBID| B_LocationID |
|1 100 |
|2 101 |
|3 102 |
Table2
-----------------
|E_CID | E_SUBID|
|10 1 |
|11 2 |
|12 3 |
Table3
---------------------------------
|E_CID | Attr_name | Attr_Value |
|10 Product Samsung |
|10 Model Smartphone |
|10 usage daily |
|11 Product Apple |
|11 Model Ipad |
|11 usage everyday |
|12 Model smartwatch |
I have been successful to join table1,table2 and table3 but I cannot get the required output which as follows
OUTPUT
|Product | Model | Usage |
Samsung Smartphone daily
Apple Ipad everyday
null smartwatch null
The query which joins table1, table2 and table3 as follows
select distinct t3.Attr_value as Product
from table1 t1, table2 t2, table3 t3
where t1.E_SUBID = t2.E_SUBID and
t2.E_CID = t3.E_CID and
t3.Attr_name=?????
order by Product;
Thank you for your time.
In a case like this, you can join to table3 as often as you need to for each attribute name you wish to display:
select
p.attr_value product,
m.attr_value "model", -- Quotes to escape reserved word
u.attr_value usage
from table1 t1
join table2 t2 on t1.e_subid = t2.e_subid
left outer join table3 p on t2.e_cid = p.e_cid and p.attr_name = 'Product'
left outer join table3 m on t2.e_cid = m.e_cid and m.attr_name = 'Model'
left outer join table3 u on t2.e_cid = u.e_cid and u.attr_name = 'Usage'
order by 1;
Edit
Based on the comment, by making table3 optional (outer join) the query should return all rows and whether or not a Model or Usage or Product has been defined.
Try as below ... Basically you are trying to transpose the rows to column in table3.
Select Product, "Model", Usage
From
(
Select
t1.E_SUBID,
t2.E_CID,
Max(Case when T3.Attr_name = 'Product' Then T3.Attr_Value else null end) Product,
max(Case when T3.Attr_name = 'Model' Then T3.Attr_Value else null end) Model,
max(Case when T3.Attr_name = 'Usage' Then T3.Attr_Value else null end) Usage
From Table1 t1,
Table2 t2,
Table3 t3
Where
t1.E_SUBID = t2.E_SUBID
and t2.E_CID = t3.E_CID
group by t1.t1.E_SUBID,t2.E_CID
);

Resources