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

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?

Related

Complex Oracle SQL query

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

Select rows when a value appears multiple times

I have a table like this one:
+------+------+
| ID | Cust |
+------+------+
| 1 | A |
| 1 | A |
| 1 | B |
| 1 | B |
| 2 | A |
| 2 | A |
| 2 | A |
| 2 | B |
| 3 | A |
| 3 | B |
| 3 | B |
+------+------+
I would like to get the IDs that have at least two times A and two times B. So in my example, the query should return only the ID 1,
Thanks!
In MySQL:
SELECT id
FROM test
GROUP BY id
HAVING GROUP_CONCAT(cust ORDER BY cust SEPARATOR '') LIKE '%aa%bb%'
In Oracle
WITH cte AS ( SELECT id, LISTAGG(cust, '') WITHIN GROUP (ORDER BY cust) custs
FROM test
GROUP BY id )
SELECT id
FROM cte
WHERE custs LIKE '%aa%bb%'
I would just use two levels of aggregation:
select id
from (select id, cust, count(*) as cnt
from t
where cust in ('A', 'B')
group by id, cust
) ic
group by id
having count(*) = 2 and -- both customers are in the result set
min(cnt) >= 2 -- and there are at least two instances
This is one option; lines #1 - 13 represent sample data. Query you might be interested in begins at line #14.
SQL> with test (id, cust) as
2 (select 1, 'a' from dual union all
3 select 1, 'a' from dual union all
4 select 1, 'b' from dual union all
5 select 1, 'b' from dual union all
6 select 2, 'a' from dual union all
7 select 2, 'a' from dual union all
8 select 2, 'a' from dual union all
9 select 2, 'b' from dual union all
10 select 3, 'a' from dual union all
11 select 3, 'b' from dual union all
12 select 3, 'b' from dual
13 )
14 select id
15 from (select
16 id,
17 sum(case when cust = 'a' then 1 else 0 end) suma,
18 sum(case when cust = 'b' then 1 else 0 end) sumb
19 from test
20 group by id
21 )
22 where suma = 2
23 and sumb = 2;
ID
----------
1
SQL>
You can use group by and having for the relevant Cust ('A' , 'B')
And query twice (I chose to use with to avoid multiple selects and to cache it)
with more_than_2 as
(
select Id, Cust, count(*) c
from tab
where Cust in ('A', 'B')
group by Id, Cust
having count(*) >= 2
)
select *
from tab
where exists ( select 1 from more_than_2 where more_than_2.Id = tab.Id and more_than_2.Cust = 'A')
and exists ( select 1 from more_than_2 where more_than_2.Id = tab.Id and more_than_2.Cust = 'B')
What you want is a perfect candidate for match_recognize. Here you go:
select id_ as id from t
match_recognize
(
order by id, cust
measures id as id_
pattern (A {2, } B {2, })
define A as cust = 'A',
B as cust = 'B'
)
Output:
Regards,
Ranagal

Oracle SQL - Assign queue of customers to list of Employees

I need to distribute list of employees to customers.
For example:
Table 1: List of Employees: A, B & C
Table 2: List of Customers: 1, 2, 3, 4, 5, 6, 7, 8, 9
The needed result:
|------------|------------|
| Customers | Employees |
|------------|------------|
| 1 | A |
| 2 | B |
| 3 | C |
| 4 | A |
| 5 | B |
| 6 | C |
| 7 | A |
| 8 | B |
| 9 | C |
|------------|------------|
You can use ROW_NUMBER() to assign a number on the fly to the employees, and the MOD() to do the rolling join. For example:
select
c.id,
e.name
from (
select t.*,
row_number() over(order by name) as rn
from employees t
) e
join customers c on e.rn =
mod(rn, (select count(*) from customers)) + 1
Number all rows, then use a modulo function for the join:
with e as
(
select employee, row_number() over (order by employee) as rn
from employees
)
, c as
(
select customer, row_number() over (order by customer) as rn
from customers
)
select c.customer, e.employee
from c
join e on e.rn - 1 = mod(c.rn - 1, (select count(*) from e))
order by c.customer;
Demo: https://dbfiddle.uk/?rdbms=oracle_18&fiddle=43a6ca7469dff023d5513fa209e33ea7
You can use MOD function for this purpose. Try below code.
CREATE TABLE EMP
AS
SELECT 'A' AS EMP FROM DUAL
UNION ALL
SELECT 'B' AS EMP FROM DUAL
UNION ALL
SELECT 'C' AS EMP FROM DUAL;
CREATE TABLE CUST
AS
SELECT '1' AS CUST FROM DUAL
UNION ALL
SELECT '2' AS CUST FROM DUAL
UNION ALL
SELECT '3' AS CUST FROM DUAL
UNION ALL
SELECT '4' AS CUST FROM DUAL
UNION ALL
SELECT '5' AS CUST FROM DUAL
UNION ALL
SELECT '6' AS CUST FROM DUAL
UNION ALL
SELECT '7' AS CUST FROM DUAL
UNION ALL
SELECT '8' AS CUST FROM DUAL
UNION ALL
SELECT '9' AS CUST FROM DUAL;
SELECT CUST, EMP
FROM (SELECT ROW_NUMBER () OVER (PARTITION BY 1 ORDER BY EMP) AS ID, EMP
FROM EMP) EMP
INNER JOIN CUST ON MOD (TO_NUMBER (CUST.CUST) - 1, 3) = EMP.ID - 1
ORDER BY 1;

how to write sql query to select rows with max value in one column

My Table looks like this.
Id | Name | Ref | Date | From
10 | Ant | 100 | 2017-02-02 | David
10 | Ant | 300 | 2016-01-01 | David
2 | Cat | 90 | 2017-09-09 | David
2 | Cat | 500 | 2016-02-03 | David
3 | Bird | 150 | 2017-06-28 | David
This is the result I want.
Id | Name | Ref | Date | From
3 | Bird | 150 | 2017-06-28 | David
2 | Cat | 500 | 2016-02-03 | David
10 | Ant | 300 | 2016-01-01 | David
My target is the highest Ref per Id, ordered by Order Date desc.
Could you please tell me about how to write a sql query using pl/sql.
This kind of requirement (where you need the max or min by one column, grouped by another, but you need all the data from the max or min row) is pretty much what analytic functions are for. I used row_number - if ties are possible, you need to clarify the assignment (see my Comment under your question), and depending on the details, another analytic function may be more appropriate - perhaps rank().
with
my_table ( id, name, ref, dt, frm ) as (
select 10, 'Ant' , 100, date '2017-02-02', 'David' from dual union all
select 10, 'Ant' , 300, date '2016-01-01', 'David' from dual union all
select 2, 'Cat' , 90, date '2017-09-09', 'David' from dual union all
select 2, 'Cat' , 500, date '2016-02-03', 'David' from dual union all
select 3, 'Bird', 150, date '2017-06-28', 'David' from dual
)
-- End of simulated table (for testing purposes only, not part of the solution).
-- SQL query begins BELOW THIS LINE.
select id, name, ref, dt, frm
from (
select id, name, ref, dt, frm,
row_number() over (partition by id order by ref desc, dt desc) as rn
from my_table
)
where rn = 1
order by dt desc
;
ID NAME REF DT FRM
-- ---- --- ---------- -----
3 Bird 150 2017-06-28 David
2 Cat 500 2016-02-03 David
10 Ant 300 2016-01-01 David
You can use this
SELECT
Id
,Name
,Ref
,[Date]
FROM(
SELECT
*
, ROW_NUMBER() OVER(PARTITION BY ID ORDER BY Ref DESC) AS Row#
FROM yourtable
) A WHERE Row# = 1
ORDER BY A.[Date] DESC
Another solution with a self join (Idea came from here: How can I SELECT rows with MAX(Column value), DISTINCT by another column in SQL? ):
with
my_table ( id, name, ref, dt, frm ) as (
select 10, 'Ant' , 100, date '2017-02-02', 'David' from dual union all
select 10, 'Ant' , 300, date '2016-01-01', 'David' from dual union all
select 10, 'Ant' , 300, date '2015-01-01', 'David' from dual union all
select 2, 'Cat' , 90, date '2017-09-09', 'David' from dual union all
select 2, 'Cat' , 500, date '2016-02-03', 'David' from dual union all
select 3, 'Bird', 150, date '2017-06-28', 'David' from dual
)
-- End of simulated table (for testing purposes only, not part of the solution).
-- SQL query begins BELOW THIS LINE.
select m1.*
from my_table m1
left join my_table m2
on m1.id = m2.id and (
-- this is basically a comparator: order by ref desc, dt desc
m1.ref < m2.ref or (
m1.ref = m2.ref and
m1.dt < m2.dt
)
) where m2.id is null order by m1.dt desc
;
ID NAME REF DT FRM
---------- ---- ---------- --------- -----
3 Bird 150 28-JUN-17 David
2 Cat 500 03-FEB-16 David
10 Ant 300 01-JAN-16 David
Use the "better than" SQL principal:
select a.Id, a.Name, a.Ref, a.Dt, a.frm
from table_name a
left join table_name b on a.id = b.id and b.ref > a.ref -- b.ref > a.ref would make b.ref "better" that a
where b.id is null -- Now check and make sure there is nothing "better"
group by a.id;
SELECT Id, Name, Max(Ref) as Ref, Min(`Date`) as `Date`
From Forge
Group By Id, Name
Order by Min(`Date`) desc;

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