Complex Oracle SQL query - sql

I started learning SQL a few weeks back on my own and came upon an interesting problem that I can't seem to solve. Here's the database structure:
Material (name, price, origin_country)
Product (name, price)
Quantities (productName, materialName, quantity)
The query should show all data of the Material that's being used in the most Products that cost 20.000 or less.
This is what I have so far:
select max(count(m.name))
from Materials m
join Quantities q on (m.name=q.materialName)
join Product p on (p.name=q.productName)
where p.price < 20000
group by m.name
This, in theory, should show show the maximum number that a type of material appears in (and it indeed does so in practice). The problem is, I have no idea how to implement this in a way that it can show me the data from the Material table.

Join the tables together, filter out the products that are too expensive and then use the COUNT analytic function to find the number of products and ORDER BY that count in descending order and return the FIRST ROW WITH TIES:
SELECT M.*,
P.name AS ProductName,
p.Price,
Q.Quantity,
COUNT( DISTINCT p.ROWID ) OVER ( PARTITION BY m.ROWID ) AS num_products
FROM Material M
INNER JOIN Quantities Q
ON ( m.Name = Q.MaterialName )
INNER JOIN Product P
ON ( Q.ProductName = P.Name )
WHERE p.price <= 20000
ORDER BY num_products DESC
FETCH FIRST ROW WITH TIES;
(I used the ROWID pseudo-column in the count just in-case there were two products or materials with the same name then it would not group them together. If this is not going to be the case then you can just use p.Name and m.Name instead.)
Which, for the sample data:
CREATE TABLE Material( name, price, origin_country ) AS
SELECT 'M1', 1, 'Place1' FROM DUAL UNION ALL
SELECT 'M2', 2, 'Place2' FROM DUAL UNION ALL
SELECT 'M3', 3, 'Place3' FROM DUAL;
CREATE TABLE Product (name, price) AS
SELECT 'P123', 10 FROM DUAL UNION ALL
SELECT 'P13', 8 FROM DUAL UNION ALL
SELECT 'P223', 5 FROM DUAL;
CREATE TABLE Quantities (productName, materialName, quantity) AS
SELECT 'P123', 'M1', 1 FROM DUAL UNION ALL
SELECT 'P123', 'M2', 1 FROM DUAL UNION ALL
SELECT 'P123', 'M3', 1 FROM DUAL UNION ALL
SELECT 'P13', 'M1', 1 FROM DUAL UNION ALL
SELECT 'P13', 'M3', 1 FROM DUAL UNION ALL
SELECT 'P223', 'M2', 1 FROM DUAL UNION ALL
SELECT 'P223', 'M2', 1 FROM DUAL UNION ALL
SELECT 'P223', 'M3', 1 FROM DUAL;
Outputs:
NAME | PRICE | ORIGIN_COUNTRY | PRODUCTNAME | PRICE | QUANTITY | NUM_PRODUCTS
:--- | ----: | :------------- | :---------- | ----: | -------: | -----------:
M3 | 3 | Place3 | P123 | 10 | 1 | 3
M3 | 3 | Place3 | P223 | 5 | 1 | 3
M3 | 3 | Place3 | P13 | 8 | 1 | 3
db<>fiddle here

Related

Subqueries with Group By

I have looked around on the internet for a while for a way to make this query work but am unable to work it out so far. I am trying to return the item descriptions and the quantity of items sold, This is what I have got at the moment:
SELECT itemdesc, quantity, (SELECT COUNT(quantity) FROM invoiceitem
WHERE invoiceitem.itemno = item.itemno
GROUP BY COUNT(invoiceitem.quantity)) Quantity
FROM item;
I am very lost at the moment, not sure if I am even linking the right tables together, can provide an ER Diagram if it helps, any help would be greatly appreciated, thankyou.
ANSWER:
SELECT item.itemdesc, (SELECT SUM(invoiceitem.quantity) FROM invoiceitem
WHERE invoiceitem.itemno = item.itemno
GROUP BY item.itemdesc) Quantity
FROM item
ORDER BY quantity;
Thankyou all!
Your outer query:
SELECT itemdesc,
quantity
/* ignoring the subquery */
FROM item;
Will not work as the item table does not have a quantity column.
If you intended to use the itemprice column then your query would be:
SELECT itemdesc,
itemprice,
( SELECT COUNT(quantity)
FROM invoiceitem
WHERE invoiceitem.itemno = item.itemno
) AS Quantity
FROM item
Which, for the sample data:
CREATE TABLE item (
itemno PRIMARY KEY,
itemdesc,
itemprice
) AS
SELECT 1, 'ItemA', 1 FROM DUAL UNION ALL
SELECT 2, 'ItemB', 2 FROM DUAL UNION ALL
SELECT 3, 'ItemC', 3 FROM DUAL UNION ALL
SELECT 4, 'ItemD', 4 FROM DUAL UNION ALL
SELECT 5, 'ItemE', 5 FROM DUAL;
CREATE TABLE invoiceitem( itemno, quantity ) AS
SELECT 1, 10 FROM DUAL UNION ALL
SELECT 1, 20 FROM DUAL UNION ALL
SELECT 1, 30 FROM DUAL UNION ALL
SELECT 3, 40 FROM DUAL UNION ALL
SELECT 4, 50 FROM DUAL UNION ALL
SELECT 5, NULL FROM DUAL;
Outputs:
ITEMDESC | ITEMPRICE | QUANTITY
:------- | --------: | -------:
ItemA | 1 | 3
ItemB | 2 | 0
ItemC | 3 | 1
ItemD | 4 | 1
ItemE | 5 | 0
And equivalent query using a join would be (assuming that item.itemno is a primary key):
SELECT MAX( i.itemdesc ) AS itemdesc,
MAX( i.itemprice ) AS itemprice,
COUNT(ii.quantity) AS Quantity
FROM item i
LEFT OUTER JOIN invoiceitem ii
ON ( ii.itemno = i.itemno )
GROUP BY i.itemno
You need to use LEFT OUTER JOIN rather than INNER JOIN to join the corresponding rows with zero items in the invoiceitem table.
db<>fiddle here
Group by like below should work. Please check.
select
item.itemdesc, count(invoiceitem.quantity) Quantity
from
item item
join invoiceitem invoiceitem on item.itemno = invoiceitem.itemno
group by
item.itemdesc
Thanks

How can I use the self join to find equal values within a group?

I am looking to find instances of GROUPID where all price values are 0. The following is a simplified version of what I am looking at
--------------------------------
| Groupid | Price | Customer|
--------------------------------
| 001 | 9 | 4 |
| 001 | 0 | 4 |
| 002 | 4 | 4 |
| 002 | 4 | 4 |
| 003 | 0 | 4 |
| 003 | 0 | 4 |
| 004 | 4 | 4 |
| 004 | 7 | 4 |
--------------------------------
I am attempting to use the following query to find all GROUPID where both PRICE values for that particular group = 0.
SELECT * FROM MYTABLE WHERE GROUPID IN
(SELECT TB1.GROUPID FROM MYTABLE TB1 JOIN MYTABLE TB2 ON TB1.GROUPID = TB2.GROUPID
AND TB1.PRICE = 0 AND TB2.PRICE = 0)
and CUSTOMER = 4
ORDER BY GROUPID;
This query returns:
| Groupid | Price | Customer|
--------------------------------
| 001 | 9 | 4 |
| 001 | 0 | 4 |
| 003 | 0 | 4 |
| 003 | 0 | 4 |
--------------------------------
In my case, I only need it to return GROUPID 003.
I'd also like to ask for assistance in modifying the query to return all non 0 equal PRICE values within a groupid. It doesn't have to be in the same query as above. For example the return would be:
| Groupid | Price | Customer|
--------------------------------
| 002 | 4 | 4 |
| 002 | 4 | 4 |
Any help would be appreciated. Thank you for your time.
If all the prices are zero, then look at the minimum and maximum price for the groupid:
select groupid
from mytable t
group by groupid
having min(price) = 0 and max(price) = 0;
I should point out that no self-join is required for this.
Try this:
SELECT * FROM MYTABLE as m1 where Price = 0
and not exists (
select 1
from MYTABLE as m2
where m2.Groupid = m1.Groupid
and m2.Price <> 0
)
You can list the the rows, whose group_id has no rows with non-zero price
select groupid, price, customer from mytable t
where not exists (
select 1 from mytable where group_id = t.group_id
and price != 0
);
I would do it by counting the number of distinct prices over each group (and I'm assuming that each group will have the same customer) and then filtering on the price(s) you're interested in.
For example, for prices that are 0 for all rows in each groupid+customer:
WITH mytable AS (SELECT '001' groupid, 9 price, 4 customer FROM dual UNION ALL
SELECT '001' groupid, 0 price, 4 customer FROM dual UNION ALL
SELECT '002' groupid, 4 price, 4 customer FROM dual UNION ALL
SELECT '002' groupid, 4 price, 4 customer FROM dual UNION ALL
SELECT '003' groupid, 0 price, 4 customer FROM dual UNION ALL
SELECT '003' groupid, 0 price, 4 customer FROM dual UNION ALL
SELECT '004' groupid, 4 price, 4 customer FROM dual UNION ALL
SELECT '004' groupid, 7 price, 4 customer FROM dual)
SELECT groupid,
customer,
price
FROM (SELECT groupid,
customer,
price,
COUNT(DISTINCT price) OVER (PARTITION BY groupid, customer) num_distinct_prices
FROM mytable)
WHERE num_distinct_prices = 1
AND price = 0;
GROUPID CUSTOMER PRICE
------- ---------- ----------
003 4 0
003 4 0
Just change the and price = 0 to and price != 0 if you want the groups which have the same non-zero price for all rows. Or simply remove that predicate altogether.
EDIT
Gordon's is the best solution for the first part:
SELECT groupid
FROM mytable t
GROUP BY GroupID
HAVING (MAX(price) = 0 and MIN(price) = 0)
And for the second part:
SELECT groupid
FROM mytable t
GROUP BY GroupID
HAVING MIN(price) <> 0 AND (MAX(price) = MIN(price))
My original one:
SELECT groupid
FROM mytable t
GROUP BY GroupID
HAVING SUM(Price) =0
This assumes, there are no negative prices.
To the second part of your question:
SELECT groupid
FROM mytable t
WHERE Price > 0
GROUP BY GroupID, Price
HAVING COUNT(price) > 1
In your sample data, you have only one customer. I assume if you had more than one customer, you would still want to return the rows where the groupid has the same price, across all rows and all customers. If so, you could use the query below. It is almost the same as Boneist's - I just use min(price) and max(price) instead of count(distinct), and I don't include customer in partition by.
If the price may be NULL, it will be ignored in the computation of max price and min price; if all the NON-NULL prices are equal for a groupid, all the rows for that group will be returned. If price can be NULL and this is NOT the desired behavior, that can be changed easily - but the OP will have to clarify.
The query below retrieves all the cases when there is a single price for the groupid. To retrieve only the groups where the price is 0 (an additional condition), add and price = 0 to the WHERE clause of the outer query. I added more test data to illustrate some of the cases the query covers.
with
test_data ( groupid, price, customer ) as (
select '001', 9, 4 from dual union all
select '001', 0, 4 from dual union all
select '002', 4, 4 from dual union all
select '002', 4, 4 from dual union all
select '003', 0, 4 from dual union all
select '003', 0, 4 from dual union all
select '004', 4, 4 from dual union all
select '004', 7, 4 from dual union all
select '002', 4, 8 from dual union all
select '005', 2, 8 from dual union all
select '005', null, 8 from dual
),
prep ( groupid, price, customer, min_price, max_price) as (
select groupid, price, customer,
min(price) over (partition by groupid),
max(price) over (partition by groupid)
from test_data
)
select groupid, price, customer
from prep
where min_price = max_price
;
GROUPID PRICE CUSTOMER
------- --------- ---------
002 4 8
002 4 4
002 4 4
003 0 4
003 0 4
005 8
005 2 8
This may be what you want:
SELECT * FROM MYTABLE
WHERE GROUPID NOT IN (
SELECT GROUPID
FROM MYTABLE
WHERE Price <> 0)
and just change the last line for the other query:
SELECT * FROM MYTABLE
WHERE GROUPID NOT IN (
SELECT GROUPID
FROM MYTABLE
WHERE Price = 0)
I would do it very similarly to what Gordon posted
SELECT groupId
FROM MyTable
GROUP BY groupId
HAVING SUM(price) = 0

SQL query to select e.g. buyer which made 3 specific buys

I have a table like this:
ToyStore
+----+------------+-------------------+
| ID | NAME | PURCHASE |
+----+------------+-------------------+
| 1 | Ramesh | Teddy bear |
| 2 | Khilan | Drum |
| 3 | Chaitali | Chess |
| 4 | Hardik | Wooden sword |
|... | ... | ... |
+----+------------+-------------------+
I need to select all the buyers which have made all 3 purchases - Teddy bear, Chess and Wooden sword and their purchases
As a result there should be something like this:
+--------+-------------------+
| NAME | PURCHASE |
+--------+-------------------+
| Ramesh | Teddy bear |
| Ramesh | Chess |
| Ramesh | Wooden sword |
| Khilan | Teddy bear |
| Khilan | Chess |
| Khilan | Wooden sword |
+--------+-------------------+
Thanks in advance
select * from ToyStore ts1 where
exists (select 1 from ToyStore ts2 where
ts1.name=ts2.name and purchase ='Teddy bear')
and
exists (select 1 from ToyStore ts2 where
ts1.name=ts2.name and purchase ='Chess')
and
exists (select 1 from ToyStore ts2 where
ts1.name=ts2.name and purchase ='Wooden sword')
If you want to find all names with exact one row of each matching toy and only These three rows you could use the following query:
(of course there are smarter ways, but this is very easy to understand and to Change)
With
-- Data
TOYSTORE AS (
SELECT 1 ID, 'Ramish' NAME, 'Teddy Bear' PURCHASE FROM DUAL UNION ALL
SELECT 2, 'Khilan', 'Drum' FROM DUAL UNION ALL
SELECT 3, 'Chaitali', 'Chess' FROM DUAL UNION ALL
SELECT 4, 'Hardik', 'Wooden sword' FROM DUAL UNION ALL
SELECT 5, 'Hardik', 'Chess' FROM DUAL UNION ALL
SELECT 6, 'Hardik', 'Teddy Bear' FROM DUAL UNION ALL
SELECT 7, 'Chaitali', 'Chess' FROM DUAL UNION ALL
SELECT 8, 'Chaitali', 'Wooden sword' FROM DUAL UNION ALL
SELECT 9, 'Chaitali', 'Teddy Bear' FROM DUAL UNION ALL
SELECT 10, 'Khilan', 'Chess' FROM DUAL UNION ALL
SELECT 11, 'Khilan', 'Wooden sword' FROM DUAL UNION ALL
SELECT 12, 'Khilan', 'Teddy Bear' FROM DUAL
),
-- Matching items
MyMatches AS (
SELECT 'Teddy Bear' PURCHASE FROM DUAL UNION ALL
SELECT 'Chess' FROM DUAL UNION ALL
SELECT 'Wooden sword' FROM DUAL
),
-- all single hits of matching items
MyStrikes AS (
SELECT NAME, PURCHASE, COUNT(*) CNT
FROM TOYSTORE
NATURAL JOIN MyMatches
GROUP BY NAME, PURCHASE
HAVING COUNT(*) = 1
),
-- all names with exactly one item of each kind
My3Strikes AS (
SELECT NAME, SUM(CNT) CNT
FROM MyStrikes
GROUP BY NAME
HAVING SUM(CNT) = 3
),
-- number of all not matching items
MyBlanks AS (
SELECT NAME, COUNT(PURCHASE) CNT --COUNT(PURCHASE) CNT
FROM TOYSTORE
WHERE NOT PURCHASE IN ('Teddy Bear', 'Chess', 'Wooden sword')
GROUP BY NAME
),
-- all names
MyNames AS (
SELECT D.NAME
FROM TOYSTORE D
GROUP BY D.NAME
)
-- the result
SELECT D.NAME
FROM MyNames D
JOIN My3Strikes S ON S.Name = D.Name
LEFT JOIN MyBlanks B ON B.Name = D.Name
WHERE B.CNT IS NULL
;
There are some things not completely clear for me. Do you want to know, whether any 3 items are in list? If so, the next query would help.
SELECT NAME, COUNT(PURCHASE)
FROM TOYSTORE
GROUP BY NAME
HAVING COUNT(PURCHASE) = 3
gives all names, that have exactly 3 items in his list.
This query you need for an inner join:
SELECT T.NAME, T.PURCHASE
FROM TOYSTORE T
JOIN (
SELECT NAME, COUNT(PURCHASE)
FROM TOYSTORE
GROUP BY NAME
HAVING COUNT(PURCHASE) = 3
) I
ON I.NAME = T.NAME
Does this solve your Problem?
If not, tell us more.
Questions may be:
What, if someone buys 2 teddybears and 1 sword?
What, if someone buys all three items and another more item?

Select Duplicate records in Oracle

I have a table below in Oracle
Table1
State | Product |other fields
CA | P1 | xxxx
OR | P1 | xxxx
OR | P1 | xxxx
OR | P1 | xxxx
WA | P1 | xxxx
VA | P2 | xxxx
My Output should be only select if State has been occurred more than once.
State | Product |other fields
OR | P1 | xxxx
If you just want to count duplicate states then:
SELECT DISTINCT
State,
Product,
Other_Fields
FROM (
SELECT t.*,
COUNT(1) OVER ( PARTITION BY State ) AS cnt
FROM Table1 t
)
WHERE cnt > 1;
If you want to consider duplicate rows (considering all fields) then:
SELECT *
FROM Table1
GROUP BY State, Product, Other_Fields
HAVING COUNT(1) > 1;
select state, product, column_3, column_4
from (
select state, product, column_3, column_4,
count(*) over (partition by state) as cnt
from the_table
) t
where cnt > 1;
You could use ROW_NUMBER analytic function.
For example,
SQL> WITH sample_data AS(
2 SELECT 'CA' State, 'P1' product FROM dual UNION ALL
3 SELECT 'OR', 'P1' product from dual union all
4 SELECT 'OR', 'P1' product FROM dual UNION ALL
5 SELECT 'OR', 'P1' product FROM dual UNION ALL
6 SELECT 'WA', 'P1' product FROM dual UNION ALL
7 SELECT 'VA', 'P2' product from dual
8 )
9 -- end of sample_data mimicking real table
10 SELECT distinct state,
11 product
12 FROM
13 (SELECT state,
14 product,
15 row_number() OVER(PARTITION BY state ORDER BY product) rn
16 FROM sample_data
17 )
18 WHERE rn >1;
ST PR
-- --
OR P1
SQL>

Selecting groups of rows where at least one row of each group meets a criteria

I'm trying to SELECT groups of rows having one row with a certain criteria.
I've tried it with CASE WHEN statements without any success. Keep in mind this table has hundred of records.
What I'm trying to accomplish is this:
One row of the group must have a subcategory equal to "GAMECONSOLE".
Rows having the same category, description and section form one group.
The ID is different so MIN and MAX does not work either.
ID SECTION DESCRIPTION CATEGORY SUBCATEGORY
21349 14010014 TODDLER TOY GAMECONSOLE
21278 14010014 TODDLER TOY BICYCLE
21431 15020021 TODDLER TOY CHESS
In this example the two first rows should be selected because they form one group and one row of the group is a "GAMECONSOLE".
CASE WHEN is used when you have to take a decision within a column expression. Filtering on row level must be done in a WHERE clause:
SELECT T.id, T.section, T.description, T.category, T.subcategory
FROM
myTable T
INNER JOIN myTable S
ON T.section = S.section AND
T.description = S.description AND
T.category = S.category
WHERE
S.subcategory = 'GAMECONSOLE'
You can join the table with itself on the columns that have to be equal. The table with alias S selects the right subategory. T selects all corresponding rows of the groups.
SELECT a1.ID
, a1.SECTION
, a1.DESCRIPTION
, a1.CATEGORY
, a1.SUBCATEGORY
FROM MyTable a1
INNER JOIN MyTable a2 ON a2.DESCRIPTION = a1.DESCRIPTION
AND a2.CATEGORY = a1.CATEGORY
AND a2.SECTION = a1.SECTION
WHERE a2.SUBCATEGORY = 'GAMECONSOLE'
-- you may want to further filter the Where clause and apply a group by or distinct to get the actual results you are wanting
Your description sounds like:
select
...
from
(
select
...
,sum(case when subcategory = 'GAMECONSOLE' then 1 else 0 end)
over (partition by category, description, section) as cnt
from tab
) dt
where cnt > 0
SELECT *
FROM myTable T
WHERE Section = (SELECT Section
FROM myTable Q
WHERE Q.subcategory = 'GAMECONSOLE')
Using an analytic function you can get the answer without using a self join.
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE TEST( ID, SECTION, DESCRIPTION, CATEGORY, SUBCATEGORY ) AS
SELECT 1, 1, 'TODDLER', 'TOY', 'GAMECONSOLE' FROM DUAL
UNION ALL SELECT 2, 1, 'TODDLER', 'TOY', 'BICYCLE' FROM DUAL
UNION ALL SELECT 3, 2, 'TODDLER', 'TOY', 'CHESS' FROM DUAL
UNION ALL SELECT 4, 3, 'COMPUTERS', 'SOFTWARE', 'BOOK' FROM DUAL
UNION ALL SELECT 5, 4, 'COMPUTERS', 'SOFTWARE', 'SOFTWARE' FROM DUAL
UNION ALL SELECT 6, 5, 'COMPUTERS', 'HARDWARE', 'MONITOR' FROM DUAL
UNION ALL SELECT 7, 6, 'COMPUTERS', 'HARDWARE', 'GAMECONSOLE' FROM DUAL
UNION ALL SELECT 8, 7, 'COMPUTERS', 'HARDWARE', 'KEYBOARD' FROM DUAL
UNION ALL SELECT 9, 8, 'TODDLER', 'BEDDING', 'BED' FROM DUAL
Query 1:
SELECT ID, SECTION, DESCRIPTION, CATEGORY, SUBCATEGORY
FROM (
SELECT t.*,
COUNT( CASE SUBCATEGORY WHEN 'GAMECONSOLE' THEN 1 END ) OVER ( PARTITION BY DESCRIPTION, CATEGORY ) AS HAS_SUBCATEGORY
FROM TEST t
)
WHERE HAS_SUBCATEGORY > 0
Results:
| ID | SECTION | DESCRIPTION | CATEGORY | SUBCATEGORY |
|----|---------|-------------|----------|-------------|
| 8 | 7 | COMPUTERS | HARDWARE | KEYBOARD |
| 7 | 6 | COMPUTERS | HARDWARE | GAMECONSOLE |
| 6 | 5 | COMPUTERS | HARDWARE | MONITOR |
| 3 | 2 | TODDLER | TOY | CHESS |
| 2 | 1 | TODDLER | TOY | BICYCLE |
| 1 | 1 | TODDLER | TOY | GAMECONSOLE |
Try
SELECT * FROM <TABLE_NAME> WHERE SUBCATEGORY like "GAMECONSOLE";
or
SELECT * FROM <TABLE_NAME> WHERE SUBCATEGORY = "GAMECONSOLE";
Replace <TABLE_NAME> with the actual table name.
Further readings:
https://dev.mysql.com/doc/refman/5.0/en/select.html