Oracle - Find Max and Least records from table - sql

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
) )
)

Related

Oracle query for customers who buy popular products

I have three tables: customer, order and line items. They are set up as follows:
CREATE TABLE cust_account(
cust_id DECIMAL(10) NOT NULL,
first VARCHAR(30),
last VARCHAR(30),
address VARCHAR(50),
PRIMARY KEY (cust_id));
CREATE TABLE orders(
order_num DECIMAL(10) NOT NULL,
cust_id DECIMAL(10) NOT NULL,
order_date DATE,
PRIMARY KEY (order_num));
CREATE TABLE line_it(
order_id DECIMAL(10) NOT NULL,
line_id DECIMAL(10) NOT NULL,
item_num DECIMAL(10) NOT NULL,
PRIMARY KEY (order_id, line_id),
FOREIGN KEY (item_id) REFERENCES products);
I need to write a query that selects customers, their names and addresses who have purchased items that have been bought by 3 or more people. I have the following query:
SELECT cust_account.cust_id, cust_account.first, cust_account.last, cust_account.address
FROM cust_account
INNER JOIN orders ON cust_account.cust_id = orders.cust_id
INNER JOIN line_it ON orders.order_id = line_it.order_id
GROUP BY cust_account.cust_id, cust_account.last
HAVING COUNT(line_it.item_num) = (
SELECT COUNT (DISTINCT order_num > 3)
FROM line_it
);
Do I even need to make it a subquery? I am a bit lost. Appreciate any help, thanks.
Start with "items bought by 3 or more people". You can get these by doing:
select li.item_id
from line_item li join
order_info oi
on li.order_id = oi.order_id
group by li.item_id
having count(distinct oi.customer_id) >= 3;
Now you want customers in this set. Hmmmm:
select distinct ca.*
from customer_account ca join
orderinfo oi
on ca.customer_id = oi.customer_id join
line_item li
on li.order_id = oi.order_id
where li.item_id in (select li.item_id
from line_item li join
order_info oi
on li.order_id = oi.order_id
group by li.item_id
having count(distinct oi.customer_id) >= 3
);
You can also express this with window functions:
select distinct ca.*
from (select ca.*, count(distinct customer_id) over (partition by li.item_id) as num_customers_on_item
from customer_account ca join
orderinfo oi
on ca.customer_id = oi.customer_id join
line_item li
on li.order_id = oi.order_id
) ca
where num_customers_on_item >= 3;
You can use the following query
SELECT distinct customer_account.* FROM line_item, order_info ,customer_account where item_id in (
--Selecting only item purchased 3 or more
SELECT item_id FROM line_item group by item_id having count(1) >=3
)
and line_item.order_id = order_info.order_id
and customer_account.customer_id = order_info.customer_id
;

SQL Query for PL/SQL statement won't work

I am trying to execute an SQL statement that I am planning on using with a PL/SQL cursor down the road. It will fetch an employees name and the projects they are working on if they are working on more than 1 project. For some reason, the "having count(pno)>1" stipulation will not work here. It just says "no data found"
Is there anything I'm doing wrong? I included my DB code below the query.
Query:
select pname, fname
from project, works_on, employee
where pno=pnumber and essn=ssn
group by pname, fname
having count(pno)>1;
Works_on table:
create table works_on (
Essn char(9) not null,
Pno int not null,
hours decimal(3,1),
primary key(essn, pno),
foreign key(Essn) references employee,
foreign key(pno) references project);
Project table:
create table project (
Pname varchar2(15) not null,
Pnumber int not null,
Plocation varchar2(15),
Dnum int not null,
primary key (Pnumber),
unique (Pname),
foreign key(Dnum) references department(Dnumber));
Employee table:
create table employee (
Fname varchar2(15) not null,
Minit char(1),
Lname varchar2(15) not null,
Ssn char(9),
Bdate date,
Address varchar2(30),
Sex char(1),
Salary decimal(10,2),
super_ssn char(9),
dno int,
primary key (Ssn),
foreign key (dno) references department(Dnumber));
EDIT
I managed to make this work instead:
select fname, pname
from employee, works_on, project
where essn=ssn and pno=pnumber
group by fname, pname
having count(pnumber) > 1
What made pnumber work in place of pno?
My expected output is a list of Employee First Names and Project names where the employee is working on more than 1 project.
Something like this:
SELECT e.fname, p.projects
FROM (
SELECT w.essn,
LISTAGG( p.pname, ',' ) WITHIN GROUP ( ORDER BY p.pname ) AS projects
FROM works_on w
INNER JOIN
project p
ON ( w.pno = p.pnumber )
GROUP BY w.essn
HAVING COUNT( DISTINCT w.pno ) > 1
) p
INNER JOIN
employee e
ON ( p.essn = e.ssn )
or:
SELECT e.fname,
p.pname
FROM (
SELECT w.*,
COUNT( pno ) OVER ( PARTITION BY essn ) AS num_projects
FROM works_on w
) w
INNER JOIN
employee e
ON ( e.ssn = w.essn )
INNER JOIN
project p
ON ( w.pno = p.pnumber )
WHERE w.num_projects > 1

Joining two tables with aggregates

I've got two tables described below:
CREATE TABLE categories
(
id integer NOT NULL,
category integer NOT NULL,
name text,
CONSTRAINT kjhfskfew PRIMARY KEY (id)
)
WITH (
OIDS=FALSE
);
CREATE TABLE products_
(
id integer NOT NULL,
date date,
id_employee integer,
CONSTRAINT grh PRIMARY KEY (id)
)
WITH (
OIDS=FALSE
);
Now I have to do report in which I need following information:
categories.category, categories.name (all of them, so string_agg is ok) - could be many assigned to one category and products_.id_employee -> but not with comma as above with category name but the one with newest date assigned (and here is my problem);
I've tried already constructions as:
SELECT
DISTINCT ON (category ) category,
string_agg(name, ','),
(SELECT
id_employee
FROM products_
WHERE date = (SELECT
max(date)
FROM products_
WHERE id IN (SELECT
id
FROM categories
WHERE id = c.id)))
FROM categories c
ORDER BY category;
But PostgreSQL says that subquery is returning to many rows...
Please help!
EXAMPLE INSERTS:
INSERT INTO categories(
id, category, name)
VALUES (1,22,'car'),(2,22,'bike'),(3,22,'boat'),(4,33,'soap'),(5,44,'chicken');
INSERT INTO products_(
id, date, id_employee)
VALUES (1,'2009-11-09',11),(2,'2010-09-09',2),(3,'2013-01-01',4),(5,'2014-09-01',90);
OK, I've solved this problem.
This one works just fine:
WITH max_date AS (
SELECT
category,
max(date) AS date,
string_agg(name, ',') AS names
FROM test.products_
JOIN test.categories c
USING (id)
GROUP BY c.category
)
SELECT
max(id_employee) AS id_employee,
md.category,
names
FROM test.products_ p
LEFT JOIN max_date md
USING (date)
LEFT JOIN test.categories
USING (category)
WHERE p.date = md.date AND p.id IN (SELECT
id
FROM test.categories
WHERE category = md.category)
GROUP BY category, names;
It seems that id is being used to join the two tables, which seems strange to me.
In any case, the base query for the category names is:
SELECT c.category, string_agg(c.name, ','),
FROM categories c
group by c.category;
The question is: how to get the most recent name? This approach uses the row_number() function:
SELECT c.category, string_agg(c.name, ','), cp.id_employee
FROM categories c left outer join
(select c.category, c.name, p.id_employee,
row_number() over (partition by c.category order by date desc) as seqnum
from categories c left outer join
products_ p
on c.id = p.id
) cp
on cp.category = c.category and
cp.seqnum = 1
group by c.category, cp.id_employee;

How to use an aggregate function in SQL WITH JOINS?

The following is a query that I built to find the cheapest price for a particular movie along with the distributor who sells it, how do I manipulate it so that all of the movies are listed with their respective highest movie price along with the distributor who sells it?
/*finds the cheapest price for the movie "American Beauty" and the distributor who sells it*/
SELECT movies.title, movie_distributors.distributor_name, MIN(distributed_movie_list.unit_price) AS lowest_price
FROM distributed_movie_list
INNER JOIN movies ON distributed_movie_list.movie_id = movies.movie_id
INNER JOIN movie_distributors ON movie_distributors.distributor_id = distributed_movie_list.distributor_id
WHERE movies.title = 'American Beauty'
AND distributed_movie_list.unit_price =
(SELECT MIN(unit_price)
FROM distributed_movie_list
INNER JOIN movies ON distributed_movie_list.movie_id = movies.movie_id
WHERE movies.title = 'American Beauty')
GROUP BY movies.title, distributed_movie_list.distributor_id, movie_distributors.distributor_name;
The following tables are used for this query:
create table movies (
movie_id number(5),
title varchar2(30) not null,
category_code char(3) not null,
description varchar2(500),
released_by number(3) not null,
released_on date not null
);
create table movie_distributors(
distributor_id number(3),
distributor_name varchar2(30) not null,
location varchar2(40),
contact varchar2(40)
);
create table distributed_movie_list(
distribution_id number(8),
movie_id number(5) not null,
distributor_id number(3) not null,
distribute_type varchar2(10),
inventory_quantity number(3) default 0,
unit_price number(8,2)
);
The end result I'm looking for is a query that list distributors and highest prices for each movie. Any help would be greatly appreciated ; )
The end result I'm looking for is a query that list distributors and
highest prices for each movie
Then why not just GROUP BY title, distributor_name with MAX like so:
SELECT
m.movie_id,
m.title,
md.distributor_name,
MAX(d.unit_price) AS Highest_price
FROM distributed_movie_list AS d
INNER JOIN movies AS m ON d.movie_id = m.movie_id
INNER JOIN movie_distributors AS md ON md.distributor_id = d.distributor_id
GROUP BY m.movie_id,m.title,
md.distributor_name
HAVING MAX(d.unit_price) = (SELECT MAX(d2.unit_price)
FROM distributed_movie_list d2
WHERE m.movie_id = d2.movie_id)
The following should work on most RDBMSs:
SELECT DISTINCT m.title, md.distributor_name, dml.unit_price AS highest_price
FROM distributed_movie_list dml
INNER JOIN movies m ON dml.movie_id = m.movie_id
INNER JOIN movie_distributors md ON md.distributor_id = dml.distributor_id
INNER JOIN (SELECT movie_id, MAX(unit_price)
FROM distributed_movie_list
GROUP BY movie_id) dmlh
ON dml.unit_price = dmlH.unit_price AND dml.movie_id = dmlh.movie_id
A more efficient solution should be possible for RDBMSs (such as Oracle or SQLServer) that support ranking functions.
If you are trying to get the highest price for each movie -- and associated information -- then use the row_number() function. The following query returns all the information about the highest price for each movie:
select dml.*
from (select dml.*,
ROW_NUMBER() over (partition by movie_id order by unit_price desc) as seqnum
from distributed_movie_list dml
) dml
where seqnum = 1
You can join in other information that you want about movies and distributors.

MIN and COUNT Oracle SQL Query

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;