Home > Back-end >  Problem with SQL query,highest amount of something
Problem with SQL query,highest amount of something

Time:05-05

Hi i have this problem where i need to do this:

-For each trainer who has given only Yoga lessons, show the name, the surname, and the city of the gym where he has given the highest number of lessons.

From this table:

TRAINER (SSN*, NameT, Surname, City)

GYM (CodG*, NameG, Address, City)

SPECIALTY (CodS*, NameS, Description)

LESSON (SSN*, CodG*, Date*, CodS, ParticipantsNumber)

Asterisks are the primary keys. what i have done for now is this:

SELECT T.Name,T.Surname,G.City
FROM TRAINER AS T,LESSON AS L,SPECIALTY AS S,GYM AS G
WHERE T.SSN=L.SSN AND S.CodS=L.CodS AND G.CodG=L.CodG
AND T.SSN NOT IN(/*SSN of instructor who have given at least one lesson other than Yoga*/
                    SELECT T1.SSN
                    FROM TRAINER AS T1,LESSON AS L1,SPECIALTY AS S1
                    WHERE T1.SSN=L1.SSN AND S1.CodS=L1.CodS AND S1.NameS<>'Yoga')

What I really can't do is the part of the gym with the highest amount of lessons, since from what i understood MAX(COUNT *) is not doable, maybe this could be done with correlations between queries but i don't see how.

Any help would be much aprecciated,thanks in advance!

P.S i mainly use APEX ORACLE

CodePudding user response:

Start by adding count(1) as num_times to the original query , then use row_number() to find out the rank, then filter out only those with _rank=1

with data as (
SELECT T.Name,T.Surname,G.City, count(1) as num_times
FROM TRAINER AS T,LESSON AS L,SPECIALTY AS S,GYM AS G
WHERE T.SSN=L.SSN AND S.CodS=L.CodS AND G.CodG=L.CodG
AND T.SSN NOT IN(/*SSN of instructor who have given at least one lesson other than Yoga*/
                    SELECT T1.SSN
                    FROM TRAINER AS T1,LESSON AS L1,SPECIALTY AS S1
                    WHERE T1.SSN=L1.SSN AND S1.CodS=L1.CodS AND S1.NameS<>'Yoga'),
first_pass as (
group by 1,2,3)
select
*, row_number() over (partition by name, surname order by num_times desc) as _rank
)
select * from first_pass where _rank = 1

CodePudding user response:

For each person/gym combination, you can use a aggregation to count the classes and use conditional aggregation to count if any were not yoga; then, avoiding querying the tables twice, you can use analytic functions to rank the gyms and total the number of non-yoga lessons; and finally, in the outer query, can filter for only those highest-ranked lessons where there were no non-yoga lessons:

SELECT namet, surname, city
FROM   (
  SELECT l.ssn,
         MAX(namet) AS namet,
         MAX(surname) AS surname,
         g.codg,
         MAX(city) AS city,
         SUM(COUNT(CASE WHEN s.NameS  <> 'Yoga' THEN 1 END))
           OVER (PARTITION BY l.ssn)
           AS num_other_lessons,
         RANK() OVER (PARTITION BY l.ssn ORDER BY COUNT(*) DESC) AS gym_rank
  FROM   lesson l
         INNER JOIN trainer t   ON (l.ssn = t.ssn)
         INNER JOIN gym g       ON (l.codg = g.codg)
         INNER JOIN specialty s ON (l.cods = s.cods)
  GROUP BY
         l.ssn,
         g.codg
)
WHERE  num_other_lessons = 0
AND    gym_rank = 1

Which, for the sample data:

CREATE TABLE TRAINER (SSN PRIMARY KEY, NameT, Surname) AS
SELECT 1, 'Alice', 'Abbot' FROM DUAL UNION ALL
SELECT 2, 'Betty', 'Baron' FROM DUAL UNION ALL
SELECT 3, 'Carol', 'Count' FROM DUAL;

CREATE TABLE GYM (CodG PRIMARY KEY, NameG, City) AS
SELECT 10, 'GymA', 'Adelaide' FROM DUAL UNION ALL
SELECT 11, 'GymB', 'Berlin'   FROM DUAL UNION ALL
SELECT 12, 'GymC', 'Caracas'  FROM DUAL UNION ALL
SELECT 13, 'GymD', 'Dubai'    FROM DUAL;

CREATE TABLE SPECIALTY (CodS PRIMARY KEY, NameS, Description) AS
SELECT 20, 'Yoga',      'Descr1' FROM DUAL UNION ALL
SELECT 21, 'Not Yoga',  'Descr2' FROM DUAL;

CREATE TABLE LESSON (SSN, CodG, "DATE", CodS, ParticipantsNumber,
  PRIMARY KEY (SSN, CodG, "DATE")
) AS
SELECT 1, 10, DATE '1970-01-01'   LEVEL - 1, 20, 1 FROM DUAL CONNECT BY LEVEL <= 4 UNION ALL
SELECT 1, 11, DATE '1970-01-01'   LEVEL - 1, 20, 1 FROM DUAL CONNECT BY LEVEL <= 3 UNION ALL
SELECT 2, 10, DATE '1970-01-01'   LEVEL - 1, 20, 1 FROM DUAL CONNECT BY LEVEL <= 2 UNION ALL
SELECT 2, 12, DATE '1970-01-01'   LEVEL - 1, 21, 1 FROM DUAL CONNECT BY LEVEL <= 1 UNION ALL
SELECT 3, 10, DATE '1970-01-01'   LEVEL - 1, 20, 1 FROM DUAL CONNECT BY LEVEL <= 1 UNION ALL
SELECT 3, 13, DATE '1970-01-01'   LEVEL - 1, 20, 1 FROM DUAL CONNECT BY LEVEL <= 3;

Outputs:

NAMET SURNAME CITY
Alice Abbot Adelaide
Carol Count Dubai

db<>fiddle here

CodePudding user response:

I would do something like this:

select t.namet, t.surname, g.city
from   (
  select ssn, min(codg) keep (dense_rank last order by cnt) as codg
  from   (
    select ssn, codg, count(*) as cnt
    from   lesson
    where  ssn in (
             select ssn
             from   trainer
             where  ssn not in (
               select l.ssn
               from   lesson l join specialty s on l.cods = s.cods
               where  s.names <> 'Yoga'
             )
           )
    group  by ssn, codg
  )
  group  by ssn
) sq
       join trainer t on t.ssn  = sq.ssn
       join gym     g on g.codg = sq.codg
;

The heavy lifting is done in the subquery sq - finding the ssn of trainers who only teach Yoga, then for each such teacher counting lessons at each gym and selecting only the gym with the highest number of lessons (the gym with the lowest code, in case of ties). Note the aggregate function last used in the select list: min(codg) keep (dense_rank LAST order by cnt) This is a very useful aggregate function that many programmers apparently never learn about.

Then the rest is a trivial join to the trainers and gyms tables.

Note - while I didn't use the with clause (perhaps they didn't teach it yet), I still chose to use modern, ANSI join syntax, rather than what they seem to teach you. You may want to ask your instructors: (1) why they are using the old Oracle join syntax, which even Oracle themselves recommend against; and (2) check the column name DATE in your last table. That is a reserved keyword; it can't be used as a column name. Your instructor should know better.

  • Related