How to get the max count of an attribute with 3 tables? - sql

I need to query which author sold the most books and how many books the author sold.
select a.firstname ||''|| a.lastname as fullname,
max(count(datesold))
from author a,
transaction t,
book b
where a.authorid = b.authorid
and b.bookid = t.bookid
group by
a.firstname,
a.lastname;
It gave me an error of not a single-group group function.
Any idea what is the issue here?

With some sample data
SQL> with
2 author (authorid, firstname, lastname) as
3 (select 1, 'Stephen', 'King' from dual union all
4 select 2, 'Jo' , 'Nesbo' from dual),
5 book (bookid, authorid) as
6 (select 100, 1 from dual union all
7 select 200, 1 from dual union all
8 select 300, 2 from dual
9 ),
10 transaction (trans_id, bookid) as
11 (select 1, 100 from dual union all
12 select 2, 100 from dual union all
13 select 3, 100 from dual union all
14 select 4, 300 from dual
15 ),
query uses the RANK analytic function which ranks rows by number of rows in the transaction table (it says how many books were sold). Finally, fetch row(s) that rank as highest:
16 temp as
17 (select a.firstname || ' ' || a.lastname AS fullname,
18 count(t.bookid) cnt,
19 rank() over (order by count(t.bookid) desc) rnk
20 from author a join book b on a.authorid = b.authorid
21 join transaction t on t.bookid = b.bookid
22 group by a.firstname, a.lastname
23 )
24 select fullname, cnt
25 from temp
26 where rnk = 1;
FULLNAME CNT
------------- ----------
Stephen King 3
SQL>

You can use:
select MAX(a.firstname ||' '|| a.lastname) as fullname,
COUNT(datesold)
from author a
INNER JOIN book b
ON (a.authorid = b.authorid)
INNER JOIN transaction t
ON (b.bookid = t.bookid)
GROUP BY
a.authorid
ORDER BY
COUNT(datesold) DESC
FETCH FIRST ROW ONLY;
Do not aggregate by firstname and lastname as there are many people in the world with identical names and you do not want to count everyone with the same name as a single person.
Which, for the sample data:
CREATE TABLE author (authorid, firstname, lastname, dateofbirth) AS
SELECT 1, 'Alice', 'Adams', DATE '1900-01-01' FROM DUAL UNION ALL
SELECT 2, 'Alice', 'Adams', DATE '1910-01-01' FROM DUAL UNION ALL
SELECT 3, 'Betty', 'Baron', DATE '1920-01-01' FROM DUAL UNION ALL
SELECT 4, 'Carol', 'Corrs', DATE '1930-01-01' FROM DUAL UNION ALL
SELECT 5, 'Carol', 'Corrs', DATE '1940-01-01' FROM DUAL;
CREATE TABLE book (bookid, authorid) AS
SELECT 1, 1 FROM DUAL UNION ALL
SELECT 2, 2 FROM DUAL UNION ALL
SELECT 3, 3 FROM DUAL UNION ALL
SELECT 4, 4 FROM DUAL UNION ALL
SELECT 5, 5 FROM DUAL;
CREATE TABLE transaction (bookid, datesold) AS
SELECT 1, DATE '1970-01-01' FROM DUAL UNION ALL
SELECT 1, DATE '1970-01-02' FROM DUAL UNION ALL
SELECT 1, DATE '1970-01-03' FROM DUAL UNION ALL
SELECT 1, DATE '1970-01-04' FROM DUAL UNION ALL
SELECT 3, DATE '1970-01-01' FROM DUAL UNION ALL
SELECT 4, DATE '1970-01-01' FROM DUAL UNION ALL
SELECT 4, DATE '1970-01-02' FROM DUAL UNION ALL
SELECT 5, DATE '1970-01-01' FROM DUAL UNION ALL
SELECT 5, DATE '1970-01-02' FROM DUAL UNION ALL
SELECT 5, DATE '1970-01-03' FROM DUAL;
Outputs:
FULLNAME
COUNT(DATESOLD)
Alice Adams
4
db<>fiddle here

Related

SQL Lookup to another table to do aggregation and merge it back to the outer query

Query: Being able to do lookup from Table A to Table B and use an aggregation function based on date criteria referencing date fields from Table A with the date fields from Table B.
Scenario:
I have a car table (contains CAR_ID,Car_START_DT,Car_END_DT) and a car_payments table (contains CAR_ID, Car_Payment_DT, Car_Payment_Amt).
For every car in the car table, I would like to do a lookup into car_payments table using CAR_ID and aggregate by counting the number of Car_Payment_Amt records between the Car_START_DT and Car_END_DT (from car table) using Car_Payment_DT.
For my attempt, I created a subquery to COUNT(Car_Payment_Amt) GROUP BY CAR_ID under car_payments table and JOIN it with Car table based on CAR_ID to get the results but realized that the subquery will be taking longer than expected as the data size grow larger.
How can I do this efficiently using SQL? I did a search and people are saying that using correlated query but it has performance bottleneck. Are there any other options?
Just use a simple join
select
c.car_id, count(cp.payment_amt) as pmt_count
from
car c
left join
car_payment cp on cp.car_id = c.car_id
and cp.payment_dt between c.car_start_dt and c.car_end_date
group by
c.car_id
You can try it without grouping like here:
SELECT c.CAR_ID,
(Select Count(*) From payments
Where CAR_ID = c.CAR_ID And PAY_DATE Between c.START_DATE And c.END_DATE) "NO_OF_PAYS"
FROM cars c
which with following sample data:
WITH
cars (CAR_ID, START_DATE, END_DATE) AS
(
Select 1, To_Date('01.01.2023', 'dd.mm.yyyy'), To_Date('04.01.2023', 'dd.mm.yyyy') From Dual Union All
Select 2, To_Date('01.01.2023', 'dd.mm.yyyy'), To_Date('06.01.2023', 'dd.mm.yyyy') From Dual Union All
Select 3, To_Date('03.01.2023', 'dd.mm.yyyy'), To_Date('08.01.2023', 'dd.mm.yyyy') From Dual Union All
Select 4, To_Date('03.01.2023', 'dd.mm.yyyy'), To_Date('10.01.2023', 'dd.mm.yyyy') From Dual Union All
Select 5, To_Date('05.01.2023', 'dd.mm.yyyy'), To_Date('12.01.2023', 'dd.mm.yyyy') From Dual Union All
Select 6, To_Date('07.01.2023', 'dd.mm.yyyy'), To_Date('14.01.2023', 'dd.mm.yyyy') From Dual
),
payments (CAR_ID, PAY_DATE, AMAUNT) AS
(
Select 1, To_Date('01.01.2023', 'dd.mm.yyyy'), 100 From Dual Union All
Select 1, To_Date('03.01.2023', 'dd.mm.yyyy'), 100 From Dual Union All
Select 1, To_Date('05.01.2023', 'dd.mm.yyyy'), 100 From Dual Union All
Select 1, To_Date('06.01.2023', 'dd.mm.yyyy'), 100 From Dual Union All
Select 3, To_Date('05.01.2023', 'dd.mm.yyyy'), 300 From Dual Union All
Select 3, To_Date('08.01.2023', 'dd.mm.yyyy'), 300 From Dual Union All
Select 3, To_Date('11.01.2023', 'dd.mm.yyyy'), 300 From Dual Union All
Select 5, To_Date('11.01.2023', 'dd.mm.yyyy'), 500 From Dual Union All
Select 5, To_Date('13.01.2023', 'dd.mm.yyyy'), 500 From Dual Union All
Select 6, To_Date('06.01.2023', 'dd.mm.yyyy'), 600 From Dual Union All
Select 6, To_Date('12.01.2023', 'dd.mm.yyyy'), 600 From Dual
)
... results as
-- R e s u l t :
-- CAR_ID NO_OF_PAYS
-- ---------- ----------
-- 1 2
-- 2 0
-- 3 2
-- 4 0
-- 5 1
-- 6 1

How to filter records based on another grouped by column in a same table

I have a sample table as below:
Target output:
I have tried with below script but only number of count produced as output:
select ID, count(COUNTRY) from test
group by COUNTRY
having count(COUNTRY)=3;
Could anyone here please help how can I get the targeted output? Thanks.
Here's one option - a subquery:
Sample data:
SQL> with test (id, name, country) as
2 (select 1, 'Mike' , 'Australia' from dual union all
3 select 2, 'Jason', 'Australia' from dual union all
4 select 3, 'Lee' , 'China' from dual union all
5 select 4, 'Simon', 'India' from dual union all
6 select 5, 'Alex' , 'Malaysia' from dual union all
7 select 6, 'John' , 'Australia' from dual
8 )
Query:
9 select id, country
10 from test
11 where country in (select country
12 from test
13 group by country
14 having count(*) = 3
15 )
16 order by id;
ID COUNTRY
---------- ---------
1 Australia
2 Australia
6 Australia
SQL>
You can use analytic functions and only scan the table once:
SELECT id, name, country
FROM (
SELECT id, name, country,
COUNT(*) OVER (PARTITION BY country) AS people_per_country
FROM table_name
)
WHERE people_per_country = 3;
Which, for the sample data:
CREATE TABLE table_name (id, name, country) AS
SELECT 1, 'Mike' , 'Australia' FROM DUAL UNION ALL
SELECT 2, 'Jason', 'Australia' FROM DUAL UNION ALL
SELECT 3, 'Lee' , 'China' FROM DUAL UNION ALL
SELECT 4, 'Simon', 'India' FROM DUAL UNION ALL
SELECT 5, 'Alex' , 'Malaysia' FROM DUAL UNION ALL
SELECT 6, 'John' , 'Australia' FROM DUAL;
Outputs:
ID
NAME
COUNTRY
1
Mike
Australia
2
Jason
Australia
6
John
Australia
db<>fiddle here

How to get mean of exams by client with 2 tables?

I know a little bit of sql, only the basic, now I need to create a analytic query but can't do this yet.
I have 2 tables on my db oracle, client and exams:
I am tried a lot of ways to get the mean of exams by client, but no success yet.4
The result expected is:
exams = 13
clients = 6
13/6= 2.166666666...7
How can I do that?
If you have clients who have not taken any exams then you want:
SELECT AVG(COUNT(e.nu_ordem)) AS avg_exames_by_client
FROM cliente c
LEFT OUTER JOIN exames e
ON (c.id = e.id_cliente)
GROUP BY c.id;
or:
SELECT (SELECT COUNT(*) FROM exames) / (SELECT COUNT(*) FROM cliente)
AS avg_exames_by_client
FROM DUAL;
Which, for the sample data:
CREATE TABLE cliente (id PRIMARY KEY) AS
SELECT 1 FROM DUAL UNION ALL
SELECT 2 FROM DUAL UNION ALL
SELECT 3 FROM DUAL UNION ALL
SELECT 4 FROM DUAL UNION ALL
SELECT 5 FROM DUAL UNION ALL
SELECT 6 FROM DUAL;
CREATE TABLE exames (nu_ordem PRIMARY KEY, id_cliente) AS
SELECT 1, 1 FROM DUAL UNION ALL
SELECT 2, 5 FROM DUAL UNION ALL
SELECT 3, 5 FROM DUAL UNION ALL
SELECT 4, 2 FROM DUAL UNION ALL
SELECT 5, 6 FROM DUAL UNION ALL
SELECT 6, 1 FROM DUAL UNION ALL
SELECT 7, 1 FROM DUAL UNION ALL
SELECT 8, 4 FROM DUAL UNION ALL
SELECT 9, 5 FROM DUAL UNION ALL
SELECT 10, 3 FROM DUAL UNION ALL
SELECT 11, 6 FROM DUAL UNION ALL
SELECT 12, 2 FROM DUAL UNION ALL
SELECT 13, 1 FROM DUAL;
Both output:
AVG_EXAMES_BY_CLIENT
2.166666666666666667
If you then add a couple of clients but no more exams:
INSERT INTO cliente (id)
SELECT 7 FROM DUAL UNION ALL
SELECT 8 FROM DUAL
Then the average is:
AVG_EXAMES_BY_CLIENT
1.625
db<>fiddle here
You can try below formula to get the result -
SELECT COUNT(*)/COUNT(DISTINCT id_cliente)
FROM exams;

Group and exclude rows that had empty values aggregated - oracle sql

I have a oracle sql table that looks like so
"STUDENT_ID","FULL_NAME","SEMESTER_ID","STIP_ID"
"1","Liam Bottrill","1","1"
"1","Liam Bottrill","2","3"
"1","Liam Bottrill","3","2"
"1","Liam Bottrill","4","5"
"2","Maurits Smitham","1","6"
"2","Maurits Smitham","2",""
"2","Maurits Smitham","3","2"
"2","Maurits Smitham","4","6"
"43","Jackie Cotton","1",""
"43","Jackie Cotton","2",""
"43","Jackie Cotton","3",""
"43","Jackie Cotton","4",""
I want to group this table by "STUDENT_ID" and exclude from result any students that have any of "STIP_ID" rows empty
Im aiming for result like this:
"STUDENT_ID","FULL_NAME"
"1","Liam Bottrill"
Liam Bottrill should be displayed while Maurits Smitham and Jackie Cotton should be excluded from result
Can you please help me with such aggregate function?
Here is one way, using aggregation:
SELECT *
FROM yourTable
WHERE STUDENT_ID IN (
SELECT STUDENT_ID
FROM yourTable
GROUP BY STUDENT_ID
HAVING COUNT(CASE WHEN STIP_ID IS NULL THEN 1 END) = 0
);
Another way, using exists logic:
SELECT t1.*
FROM yourTable t1
WHERE NOT EXISTS (
SELECT 1
FROM yourTable t2
WHERE t2.STUDENT_ID = t1.STUDENT_ID AND
t2.STIP_ID IS NULL
);
You can group by the identifier and then use conditional aggregation to find the student where the count when STIP_ID is NULL (which, in Oracle, is the same as an empty string):
SELECT student_id,
MAX(full_name) AS full_name
FROM table_name
GROUP BY student_id
HAVING COUNT(CASE WHEN stip_id IS NULL THEN 1 END) = 0;
Which, for your sample data:
CREATE TABLE table_name (STUDENT_ID, FULL_NAME, SEMESTER_ID, STIP_ID) AS
SELECT 1, 'Liam Bottrill', 1, 1 FROM DUAL UNION ALL
SELECT 1, 'Liam Bottrill', 2, 3 FROM DUAL UNION ALL
SELECT 1, 'Liam Bottrill', 3, 2 FROM DUAL UNION ALL
SELECT 1, 'Liam Bottrill', 4, 5 FROM DUAL UNION ALL
SELECT 2, 'Maurits Smitham', 1, 6 FROM DUAL UNION ALL
SELECT 2, 'Maurits Smitham', 2, NULL FROM DUAL UNION ALL
SELECT 2, 'Maurits Smitham', 3, 2 FROM DUAL UNION ALL
SELECT 2, 'Maurits Smitham', 4, 6 FROM DUAL UNION ALL
SELECT 43, 'Jackie Cotton', 1, NULL FROM DUAL UNION ALL
SELECT 43, 'Jackie Cotton', 2, NULL FROM DUAL UNION ALL
SELECT 43, 'Jackie Cotton', 3, NULL FROM DUAL UNION ALL
SELECT 43, 'Jackie Cotton', 4, NULL FROM DUAL;
Outputs:
STUDENT_ID
FULL_NAME
1
Liam Bottrill
db<>fiddle here

Print message when no data is found

Need a query to get the Employee name, total fuel used by each employee.
If fuel is not used by an employee then the second column should have a
text “No fuel used”.
These are the following two tables:
Table1: EmployeeID, FirstName
1 Vikas
2 nikita
3 Ashish
4 Nikhil
5 anish
Table2: ID, Fuel
1 10
2 9
3 8
4 6
5 12
6 11
7 10
8 9
9 8
10 10
11 9
12 12
13 7
14 15
where The column table2.ID is a foreign key to table1.EmployeeID.
This is code which I have written, Which is most probably wrong.
select ID, FirstName, sum(table2.Fuel) sum_fuel
from table2,table1
where EmployeeID=ID IN (
select ID, coalesce(ID, 'No-fuel used') as ID
from table1 t1
left join table2 t2 on t2.ID = t1.EmployeeID
)
group by fuel
order by ID DESC;
As you can see from two tables that employee with from 1 to 5 of table1 are in table2. So for these employee I need to show total fuel used by every individual. And for employee with ID from 6 to 14 are not available in table1 so for these employee “No fuel used” message should be printed.
You can use a left join. This way, whenever the Id values for tables don't match you'll get null values for sum(fuel) value, and will assign the string 'No fuel used'for sum_fuel column by using nvl() function:
with table1( EmployeeID, FirstName ) as
(
select 1,'Vikas' from dual union all
select 2,'nikita' from dual union all
select 3,'Ashish' from dual union all
select 4,'Nikhil' from dual union all
select 5,'anish' from dual union all
select 15,'pratteek' from dual
), table2( ID, Fuel ) as
(
select 1, 10 from dual union all
select 2, 9 from dual union all
select 3, 8 from dual union all
select 4, 6 from dual union all
select 5, 12 from dual union all
select 6, 11 from dual union all
select 7, 10 from dual union all
select 8, 9 from dual union all
select 9, 8 from dual union all
select 10, 10 from dual union all
select 11, 9 from dual union all
select 12, 12 from dual union all
select 13, 7 from dual union all
select 14, 15 from dual
)
select EmployeeID, FirstName, nvl(to_char(sum(t2.Fuel)),'No fuel used') as sum_fuel
from table1 t1
left join table2 t2
on t1.EmployeeID = t2.ID
group by EmployeeID, FirstName
order by EmployeeID desc;
EMPLOYEEID FIRSTNAME SUM_FUEL
---------- --------- ------------
15 pratteek No fuel used
5 anish 12
4 Nikhil 6
3 Ashish 8
2 nikita 9
1 Vikas 10
Demo
This may work---
SELECT ID
, FirstName
, CASE
WHEN SUM(f.Fuel) > 0 THEN CAST(SUM(f.Fuel) AS NVARCHAR(25))
ELSE 'No fuel used'
END sum_fuel
FROM #emp e
LEFT JOIN #fuel f ON e.EmployeeID = f.id
GROUP BY ID,FirstName
ORDER BY ID DESC