MIN and COUNT Oracle SQL Query - sql

I have tried to this query: What are the hospitals for each country with the lower number of doctors. (1st column: name of the country; 2nd column: name of the hospital. In case of there is more than hospital with the lower number of doctors it must appear on the result). But the result isn't what I expected and it has a syntax error.
I have these tables:
CREATE TABLE Hospital (
hid INT PRIMARY KEY,
name VARCHAR(127) UNIQUE,
country VARCHAR(127),
area INT
);
CREATE TABLE Doctor (
ic INT PRIMARY KEY,
name VARCHAR(127),
date_of_birth INT,
);
CREATE TABLE Work (
hid INT,
ic INT,
since INT,
FOREIGN KEY (hid) REFERENCES Hospital (hid),
FOREIGN KEY (ic) REFERENCES Doctor (ic),
PRIMARY KEY (hid,ic)
);
I tried with this:
SELECT DISTINCT H.country, H.name, MIN(*)
FROM Hospital H
WHERE H.hid IN (
SELECT COUNT(*)
FROM Work W, Doctor D
WHERE W.hid = H.hid AND W.ic = D.ic
GROUP BY H.country
)
GROUP BY H.country
;
Thanks.

SELECT country, name
FROM
(
SELECT hid, country, name, MIN(doctorCount)
FROM
(
SELECT a.hid, a.country, a.name, COUNT(b.hid) doctorCount
FROM Hospital a
LEFT JOIN Work b
ON a.hid = b.hid
GROUP BY a.hid, a.country, a.name
) x
GROUP BY hid, country, name
) y

Try this:
WITH doctorCount AS
(SELECT H.country country, H.hid hid, COUNT(*) dCount
FROM Work W, Doctor D, Hospital H
WHERE W.ic = D.ic
AND H.hid = W.hid
GROUP BY H.country, H.hid),
minCount AS
(SELECT D.country, MIN (D.dCount) lowCount
FROM doctorCount D
GROUP BY D.country)
SELECT D.country, H.name
FROM doctorCount D, Hospital H, minCount M
WHERE D.hid = H.hid
AND M.country = D.country
AND D.dCount = M.lowCount;

Related

How can I select only the max food_order_id?

How can I select only the max food_order_id?
SELECT
o.food_order_id,
fo.user_id,
f.name,
f.price,
o.quantity,
o.subtotal
FROM food_order fo
INNER JOIN order_item o ON fo.food_order_id = o.food_order_id
INNER JOIN food_item f ON o.food_id = f.food_id
WHERE fo.user_id = 12;
if I want to update or delete the information selected from table with how will I do it?
tables:
CREATE TABLE food_item (
food_id SERIAL PRIMARY KEY,
name TEXT,
image TEXT,
description TEXT,
price INT
);
CREATE TABLE food_order (
food_order_id SERIAL PRIMARY KEY,
user_id INT REFERENCES users(user_id)
);
CREATE TABLE order_item (
order_item_id SERIAL PRIMARY KEY,
food_id INT REFERENCES food_item(food_id),
food_order_id INT REFERENCES food_order(food_order_id),
quantity INT,
subtotal INT
);
In Postgres 13, you can use FETCH FIRST WITH TIES:
SELECT o.food_order_id, fo.user_id, f.name, f.price, o.quantity, o.subtotal
FROM food_order fo JOIN
order_item o
ON fo.food_order_id = o.food_order_id JOIN
food_item f
ON o.food_id = f.food_id
WHERE fo.user_id = 12
ORDER BY o.food_order_id DESC
FETCH FIRST 1 ROW WITH TIES;
In older versions, I would recommend:
SELECT *
FROM (SELECT o.food_order_id, fo.user_id, f.name, f.price, o.quantity, o.subtotal,
RANK() OVER (ORDER BY o.food_order_id DESC) as seqnum
FROM food_order fo JOIN
order_item o
ON fo.food_order_id = o.food_order_id JOIN
food_item f
ON o.food_id = f.food_id
WHERE fo.user_id = 12
) x
WHERE seqnum = 1;

SQL query issue with achieving an encompasses all effect

Below is the query I have been working on for this question.
Find the names of the companies which have employees residing in every city where employees of Mutual of Omaha live.
This means that if Mutual has employees in the cities Omaha, Lincoln, and Denver that the only company names it should return is a company that has employees in all 3 of those cities. This should also return Mutual.
The below query returns the company which has an employee in any of those three cities. The lastname is there for me to manually check which employees it is counting.
SELECT COMPANY_NAME, e1.lastname
FROM EMPLOYEE E1,WORKS W1
WHERE E1.CITY IN (SELECT CITY
FROM EMPLOYEE E2,WORKS W2
WHERE E2.firstname = W2.firstname
AND E2.lastname = W2.lastname
AND W2.COMPANY_NAME= 'Mutual of Omaha')
AND E1.firstname = W1.firstname
AND E1.lastname = W1.lastname;
I realized I didn't put the tables down so here they are
employee (Lastname, FirstName, MidInitial, gender, street, city)
works (Lastname, FirstName, MidInitial, company_name, salary)
manages(Lastname, FirstName, MidInitial, ManagerLastname, MFirstName, MMidInitial, start-date)
It's not the most elegant piece of code, but try using this one:
WITH tab AS (
SELECT DISTINCT W1.COMPANY_NAME,
E1.CITY
FROM EMPLOYEE E1
JOIN WORKS W1 ON (E1.firstname = W1.firstname AND E1.lastname = W1.lastname)
WHERE E1.CITY IN (SELECT DISTINCT E2.CITY
FROM EMPLOYEE E2,WORKS W2
WHERE E2.firstname = W2.firstname
AND E2.lastname = W2.lastname
AND W2.COMPANY_NAME= 'Mutual of Omaha')
)
SELECT tab.COMPANY_NAME
FROM tab
GROUP BY tab.COMPANY_NAME
HAVING COUNT(tab.CITY) = (SELECT COUNT(sub.CITY) FROM tab sub WHERE COMPANY_NAME = 'Mutual of Omaha')
This option uses the bitwise operator so is also limited by the number of cities it can use at once (~31 due to Int size limit)
------------Assign each distinct city a value that is x2 the previous (like binary counting)
create table #cityvalues (CityName varchar(100), ValueField int)
select distinct E1.CITY
into #while
FROM EMPLOYEE E1
while (select count(*) from #while) > 0
begin
insert into #cityvalues
select top 1 CITY, coalesce((select max(ValueField) from #cityvalues)*2, 1) from #while
delete from #while w where w.CITY in (select CityName from #CityValues)
end
--------------------------------------------------------------------------------
--------------Create a list of Company/City--------------------------------
create table #companycities (Compname varchar(100),CityName varchar(100))
insert into #companycities
select distinct
W1.COMPANY_NAME
,E1.CITY
FROM EMPLOYEE E1
JOIN WORKS W1 on E1.firstname = W1.firstname AND E1.lastname = W1.lastname
----------------------------------------------------------------------------
----This SUM function then creates a "list" of all cities for the company in a single field
select cc.Compname, sum(cv.ValueField) as AllCities
into #CompanyAllCities
from #companycities cc
join #cityvalues cv on cc.CityName = cv.CityName
group by Compname
-----------------------------------------------------------------
----------This query checks if the company's "list" contains the "list" for the joined company, excluding itself
select distinct cac1.Compname
from #CompanyAllCities cac1
join #CompanyAllCities cac2 on cac2.Compname = 'Mutual'
where cac2.AllCities & cac1.AllCities = cac2.AllCities
and cac1.Compname <> 'Mutual'
-------------------------------------------------------------
----------Tidy up after yourself----------
drop table #cityvalues,#CompanyAllCities,#companycities
-----------------------------------------
I would start with a CTE that has each company with the cities they operate in.
Then there are several options, but a self-join with aggregation does the count that you want:
with cw as (
select distinct e.city, w.company_name
from employee e join
works w
on e.firstname = w.firstname and
e.lastname = w.lastname and
e.midinitial = w.midinitial
)
select cw.company_name
from cw join
cw cwo
on cw.city = cwo.city and
cwo.company_name = 'Mutual of Omaha'
group by cw.company_name
having count(*) = (select count(*) from cw where
cw.company_name = 'Mutual of Omaha');
You can list and weight the cities where companies have employees, so you will get a single number representing each company based on where they have their employees, a sort of group number.
Then you can check which cities have the same group number.
;with
cities as(select ROW_NUMBER() over (order by city) city_id, city from (select distinct city from employees) c),
chk as (
select distinct company_name, city_id
from works w
join employees e on w.firstname = e.firstname and w.lastname = e.lastname
join cities c on c.city = e.city
),
cnt as (
select company_name, SUM(power(cast(2 as bigint), city_id-1)) n
from chk
group by company_name
)
select company_name
from cnt
where n = (select n from cnt where company_name = 'Mutual of Omaha')

Oracle - Find Max and Least records from table

I have the following patients and appointments tables.
Patient
CREATE TABLE Patient
(
patientID number(10),
firstName varchar2(50) NOT NULL,
middleName varchar2(50),
surName varchar2(50) NOT NULL,
p_age number(10) NOT NULL,
p_gender char(1),
p_address varchar2(200),
p_contact_no number(10),
medicalHistory varchar2(500),
allergies varchar2(200),
CONSTRAINT PK_Patient PRIMARY KEY (patientID)
);
Appointment
CREATE TABLE Appointment
(
appID number(10),
patientId number(10),
staffId number(10),
appDateTime TIMESTAMP(3),
CONSTRAINT PK_Appointment PRIMARY KEY (appID),
CONSTRAINT FK_Appointment_Patient FOREIGN KEY (patientId) REFERENCES Patient(patientID) ON DELETE CASCADE,
CONSTRAINT FK_Appointment_Staff FOREIGN KEY (staffId) REFERENCES Staff(staffID) ON DELETE CASCADE
);
I want to get the patient details of patients having most and least appointments.
I have written the query in SQL server before and now I want to change it to oracle. Can anyone help me?
This is what I have so far.
SELECT p.patientId, p.firstName,
Count(a.appId) AS Count,
MAX(Count(a.appId)) OVER () AS MaxMyGroup,
MIN(Count(a.appId)) OVER () AS MinMyGroup
FROM Patient p INNER JOIN Appointment a ON p.patientID = a.patientId
GROUP BY p.patientId, p.firstName
SQL Query
WITH s
AS (SELECT p.patientId, p.firstName,
Count(a.appId) AS [Count],
MAX(Count(a.appId)) OVER () AS [MaxMyGroup],
MIN(Count(a.appId)) OVER () AS [MinMyGroup]
FROM Patient p INNER JOIN Appointment a ON p.patientID = a.patientId
GROUP BY p.patientId, p.firstName)
SELECT patientId AS ID,
firstName AS 'First Name',
V.[Count] AS 'Appointment Count',
Agg AS 'MAX/MIN'
FROM s
CROSS APPLY (VALUES ( 'Most', CASE WHEN [Count] = [MaxMyGroup] THEN [Count] END),
('Least', CASE WHEN [Count] = [MinMyGroup] THEN [Count] END))
V(Agg, [Count])
WHERE V.[Count] IS NOT NULL
You are almost there with your query - you just need to then filter on whether the number of appointments is equal to either the minimum or maximum number of appointments. (You also probably want to use LEFT OUTER JOIN rather than INNER JOIN.)
SELECT patientId,
firstName,
NumAppt,
CASE NumAppt
WHEN MinAppt
THEN 'Least'
ELSE 'Most'
END AS category
FROM (
SELECT p.patientId,
p.firstName,
Count(a.appId) AS NumAppt,
MAX(Count(a.appId)) OVER () AS MaxAppt,
MIN(Count(a.appId)) OVER () AS MinAppt
FROM Patient p
LEFT OUTER JOIN Appointment a
ON ( p.patientID = a.patientId )
GROUP BY p.patientId, p.firstName
)
WHERE NumAppt IN ( MinAppt, MaxAppt );
Check if this helps.
SELECT *
FROM PATIENT P
WHERE EXISTS
(SELECT PATIENTID,
COUNT(*)
FROM APPOINTMENT A
GROUP BY PATIENTID
HAVING P.PATIENTID = A.PATIENTID
AND (COUNT(*) >=
(SELECT MAX(COUNT(*)) FROM APPOINTMENT A2 GROUP BY A2.PATIENTID
)
OR COUNT(*) <=
(SELECT MIN(COUNT(*)) FROM APPOINTMENT A3 GROUP BY A3.PATIENTID
) )
)

SQL Query, Average climbed and pair that has climbed the most peaks

My Databases look like so:
PEAK (NAME, ELEV, DIFF, MAP, REGION)
CLIMBER (NAME, SEX)
PARTICIPATED (TRIP_ID, NAME)
CLIMBED (TRIP_ID, PEAK, WHEN)
PEAK gives info about the mountain peaks that the user is interested in. The table lists the name of each peak, it elevation(in ft), its difficulty level(on a scale of 1-5), the map that it is located on, and the region of the Sierra Nevada that it is located in.
CLIMBER lists the members of club, and gives their name and gender.
PARTICIPATED gives the set of climbers who participated in each of the various climbing trips. The number of participants in each trip varies.
CLIMBED tells which peaks were climbed on each climbing trip, along w/ the data that each peak was climbed.
I need help with writing a query for the following:
Compute the average number of peaks scaled by the men in the club and by the women in the club.
Which pair of climbers have climbed the most peaks together, and how many peaks is that?
Who has climbed more than 20 peaks in some 60 day span?
For the first query, so far I have found a way to compute the total number of peaks climbed by either gender, for men:
SELECT SUM(C)
FROM
(SELECT CD.PEAK, COUNT(*) C
FROM CLIMBED CD
WHERE CD.TRIP_ID IN
(SELECT TRIP_ID
FROM PARTICIPATED PA
WHERE PA.NAME IN
(SELECT NAME
FROM CLIMBER
WHERE SEX = 'M'))
GROUP BY CD.PEAK) T;
For the second query, I have the following which I'm fairly sure isn't correct:
SELECT TEMP2.TRIP_ID, COUNT (*)
FROM
(SELECT P1.NAME, P2.NAME, P1.TRIP_ID
FROM PARTICIPATED P1, PARTICIPATED P2
WHERE P1.NAME <> P2.NAME AND
P1.TRIP_ID = P2.TRIP_ID) TEMP1,
(SELECT *
FROM CLIMBED) TEMP2
WHERE TEMP2.TRIP_ID = TEMP1.TRIP_ID
GROUP BY TEMP2.TRIP_ID;
Question 1:
For total number of trips (including every time a peak was climbed)
SELECT t1.sex, AVG(t1.peak_count) AS average
FROM
(SELECT sex, COUNT(trip_id) AS peak_count
FROM climber c LEFT JOIN participated p ON c.name = p.name GROUP BY c.name, c.sex) t1
For each time a UNIQUE peak was climbed:
SELECT t1.sex, AVG(t1.peak_count) AS average
FROM
(SELECT sex, COUNT(trip_id) AS peak_count
FROM climber c LEFT JOIN participated p ON c.name = p.name GROUP BY c.name, c.sex) t1
Question 2:
SELECT P1.Name, P2.Name, COUNT(DISTINCT p1.trip_id) AS trips
FROM participated p1 INNER JOIN participated p2 ON p1.trip_id = p2.trip_id
WHERE p1.name > p2.name -- > instead of <> gets only one of the pairs
GROUP BY P1.Name, P2.Name
HAVING COUNT(DISTINCT p1.trip_id) > 0
ORDER BY trips DESC
Question 3:
SELECT p.name, cl.when AS span_begin_date, DATEADD(day, 60, cl.when) AS span_end_date, count(c2.trip_id) AS peaks
FROM climbed cl LEFT JOIN
climbed c2 ON c2.when BETWEEN cl.when AND DATEADD(day, 60, cl.when)
GROUP BY p.name, cl.when, DATEADD(day, 60, cl.when)
HAVING COUNT(c2.trip_id) > 20
ORDER BY peaks
Here is my solution. If you provide sample data, this can be verified. For question 3, the some 60 day span is not clear. Can you please specify better?
Question 1
select x.sex, avg(x.peaks_escalated) as peaks
from (
select u.name, u.sex, count(distinct c.peak) as peaks_escalated
from t1_climbed c
inner join t1_participated p on c.trip_id = p.trip_id
inner join t1_climber u on p.name = u.name
group by u.name, u.sex ) x
group by x.sex
Question 2
with list1 as (
select u.name as member, c.trip_id, c.peak, c.when
from t1_climbed c
inner join t1_participated p on c.trip_id = p.trip_id
inner join t1_climber u on p.name = u.name
)
select a.member as m1, b.member as m2, count(distinct a.peak) as total
from list1 a inner join list1 b
on a.trip_id = b.trip_id
and a.peak = b.peak
and a.when = b.when
and a.member <> b.member
group by a.member, b.member
Oracle Setup:
CREATE TABLE PEAK (
NAME VARCHAR2(50) PRIMARY KEY,
ELEV INT,
DIFF INT,
MAP VARCHAR2(10),
REGION VARCHAR2(10)
);
CREATE TABLE CLIMBER (
NAME VARCHAR2(50) PRIMARY KEY,
SEX CHAR(1) CHECK ( SEX IN ( 'M', 'F' ) )
);
-- Created this to have a primary key
CREATE TABLE TRIPS (
TRIP_ID INT PRIMARY KEY
);
CREATE TABLE PARTICIPATED (
TRIP_ID INT REFERENCES TRIPS( TRIP_ID ),
NAME VARCHAR2(50) REFERENCES CLIMBER( NAME ),
PRIMARY KEY ( TRIP_ID, NAME )
);
CREATE TABLE CLIMBED (
TRIP_ID INT REFERENCES TRIPS( TRIP_ID ),
PEAK VARCHAR2(50) REFERENCES PEAK ( NAME ),
"WHEN" DATE
);
Question 1
SELECT sex,
AVG( num_peaks ) AS avg_peaks
FROM (
SELECT c.*,
COUNT( DISTINCT l.peak ) num_peaks
FROM CLIMBED l
INNER JOIN
PARTICIPATED p
ON ( p.trip_id = l.trip_id )
RIGHT OUTER JOIN
CLIMBER c
ON ( p.name = c.name )
GROUP BY c.name, c.sex
)
GROUP BY sex;
You need to OUTER JOIN climbers as they could have not participated in any trips (so having climbed 0 peaks) and this needs to be taken into account in the average. It is also possible that a person could have climbed a peak multiple times - when you want the number of peaks climbed by a person you want to exclude multiple climbs on the same peak and will need to use COUNT( DISTINCT ... ) (or another similar technique) - if you want to count multiple climbs then remove the DISTINCT keyword.
Question 2:
SELECT *
FROM (
SELECT name1,
name2,
COUNT( DISTINCT c.peak ) AS num_peaks_climbed
FROM (
SELECT p1.name AS name1,
p2.name AS name2,
p1.trip_id
FROM PARTICIPATED p1
INNER JOIN
PARTICIPATED p2
ON ( p1.trip_id = p2.trip_id AND p1.name < p2.name )
) p
INNER JOIN
climbed c
ON ( p.trip_id = c.trip_id )
GROUP BY name1, name2
ORDER BY num_peaks_climbed DESC
)
WHERE ROWNUM = 1;
Question 3:
SELECT *
FROM (
SELECT p.name,
COUNT( c.peak ) OVER ( PARTITION BY p.name
ORDER BY c."WHEN"
RANGE BETWEEN INTERVAL '-60' DAY PRECEDING
AND CURRENT ROW
) AS num_peaks_in_60_days,
c."WHEN" AS last_date_of_range
FROM PARTICIPATED p
INNER JOIN
climbed c
ON ( p.trip_id = c.trip_id )
)
WHERE num_peaks_in_60_days > 20;

How can this query be optimized?

I need to write this query in postgresql 9.3:
List the most popular movie in each country. The most popular movie/movies is the one that has got the highest average rating
across all the users of that country. In case of a tie, return all
movies order alphabetically. (2 columns)
Tables needed:
CREATE TABLE movie (
id integer,
name varchar(200),
year date
);
CREATE TABLE userProfile (
userid varchar(200),
gender char(1),
age integer,
country varchar(200),
registered date
);
CREATE TABLE ratings (
mid integer,
userid varchar(200),
rating integer
);
CREATE INDEX movie_id_idx ON movie (id);
CREATE INDEX userProfile_userid_idx ON userProfile (userid);
CREATE INDEX ratings_userid_idx ON ratings (userid);
CREATE INDEX ratings_mid_idx ON ratings (mid);
CREATE INDEX ratings_userid_mid_idx ON ratings (userid, mid);
Here is mine query:
CREATE TEMP TABLE tops AS SELECT country, name
FROM ratings AS r INNER JOIN userProfile AS u
ON r.userid=u.userid
INNER JOIN movie AS m ON m.id = r.mid LIMIT 0;
~10 min
CREATE TEMP TABLE avg_country AS
SELECT country, r.mid, AVG(rating) AS rate
FROM ratings AS r INNER JOIN userProfile AS u
ON r.userid=u.userid
GROUP BY country, r.mid;
~8 min
DO $$
DECLARE arrow record;
BEGIN
CREATE TABLE movie_names AS SELECT id, name FROM movie;
FOR arrow IN SELECT DISTINCT country FROM userProfile ORDER BY country
LOOP
CREATE TABLE movies AS SELECT mid FROM (SELECT MAX(rate) AS m_rate FROM avg_country
WHERE country=arrow.country) AS max_val CROSS JOIN LATERAL
(SELECT mid FROM avg_country
WHERE country=arrow.country AND rate=max_val.m_rate) AS a;
WITH names AS (DELETE FROM movie_names AS m
WHERE m.id IN (SELECT mid FROM movies) RETURNING name)
INSERT INTO tops
SELECT arrow.country, name FROM names ORDER BY name;
DROP TABLE movies;
END LOOP;
DROP TABLE movie_names;
END$$;
SELECT * FROM tops;
DROP TABLE tops, avg_country;
Thanks a lot in advance)
This is similar to kordirkos answer, but with one fewer subquery:
select country, movie_name, avg_rating
from (select u.country, m.name as movie_name, avg(r.rating) as avg_rating
rank() over (partition by u.country order by avg(r.rating) desc) as seqnum
from userProfile u join
ratings r
on u.userid = r.userid join
movie m
on r.mid = m.id
group by u.country, m.id -- `name` is not needed here because id is unique
) uc
where seqnum = 1;
Alternatively, if you want to get the list on one row per country:
select country, string_agg(movie_name, '; ') as most_popular_movies
from (select u.country, m.name as movie_name, avg(r.rating) as avg_rating
rank() over (partition by u.country order by avg(r.rating) desc) as seqnum
from userProfile u join
ratings r
on u.userid = r.userid join
movie m
on r.mid = m.id
group by u.country, m.id -- `name` is not needed here because id is unique
) uc
where seqnum = 1
group by country;
Use a plain, old-fashioned SQL - it is old but gold.
WITH q AS (
SELECT *,
dense_rank() over (partition by country order by avg_rating desc ) rank
FROM (
select u.country, m.name movie_name, avg( r.rating ) avg_rating
from userProfile u
join ratings r on u.userid = r.userid
join movie m on r.mid = m.id
group by u.country, m.name
) xx )
SELECT country, movie_name
FROM q WHERE rank <= 1