Avg used on count - sql

I have a two tables that have following attributes
DOCTORS OPERATIONS
D_ID DATE
Name TYPE
Specialiation DOCTORS_D_ID
PACIENTS_PACIENT_ID
I want to return name and ID of doctores that operated more than the average number of operations per doctor.
I have created following SQL command
SELECT Name D_ID,COUNT(*) FROM DOCTORS
JOIN OPERATION
ON D_ID = DOCTORS_D_ID
GROUP BY D_ID,Name
HAVING COUNT(*) > ( SELECT AVG(COUNT(DOCTORs_D_ID))
FROM OPERATIONS GROUP by DOCTORS_D_ID )
this result in following table
D_ID COUNTS(*)
Dr. Martin 3
In column D_ID is name instead of ID = only one of two attributes is returned in table. How can I return both - name and D_ID from this command?

I am not a fan of nested aggregation functions. I would just do this by calculating the average directly:
SELECT Name, D_ID, COUNT(*)
FROM DOCTORS JOIN
OPERATION
ON D_ID = DOCTORS_D_ID
GROUP BY D_ID, Name
HAVING COUNT(*) > (SELECT COUNT(*) / COUNT(DISTINCT DOCTORs_D_ID))
FROM OPERATIONS
);

There is an issue of not counting doctors who do no operations in the average (in which case the average from just using the operations table [or an inner join with the operations table] will be higher than the actual answer from taking the number of operations in the operations table and the number of doctors in the doctors table).
To compensate for this you can do:
SELECT Name,
D_ID,
num_operations
FROM ( SELECT Name,
D_ID,
COUNT( 1 ) OVER () AS num_doctors
FROM doctors ) d
LEFT OUTER JOIN
( SELECT DISTINCT
DOCTORS_D_ID,
COUNT( 1 ) OVER ( PARTITION BY DOCTORS_D_ID ) AS num_operations,
COUNT( 1 ) OVER () AS total_operations
FROM operations ) o
ON ( d.d_id = o.doctors_d_id )
WHERE num_operations > total_operations / num_doctors;
It has the added bonus using analytic functions to calculate the counts rather than performing a third table scan.

with num_operations as
select doctors_d_id,count( * ) as operations from operations
group by doctors_d_id and having count(*)>
(select avg(count(doctors_d_id) from operations group by doctors_d_id )
select doctors_d_id,operations,name from num_operation a, doctors b
where a.doctors_d_id=b.d_id

Related

Using JOIN in SQL with a bound

I'm new to SQL and I have a question about JOINs.
The question goes like this, There are 2 tables, The first table stores data about Patients and there is an attribute in the patient table called Field, which stores the medical field under which the patient was treated. The second table is called Doctors, and here there is an attribute called Specialization, which stores the medical field in which the doctor specializes.
Medical fields i.e Cardiology, Virology, and so on.
There can be more doctors who practice in the same specialization.
If I were to join the tables on the basis of the Doctors.Specialization and Patients.Field and a constraint of that each doctor will be matched with a maximum of 5 patients, Then what would be the query?
SELECT *
FROM Patients
inner join Doctors on Patients.Diagnosis = Doctors.Specialization;
I would solve it like this:
Join the two tables using specialization and diagnosis columns.
Rank doctors and patients by specialization using DENSE_RANK() analytic function
Filter the data. Patients' ranks must be in a range which's:
lower bound (exclusive) is: (doctors' rank - 1) * 5.
If doctor's rank is 1, then it's 0.
If doctor's rank is 2, then it's 5.
upper bound (inclusive) is: doctors' rank * 5.
If doctor's rank is 1, then it's 5.
If doctor's rank is 2, then it's 10.
WITH base AS (
SELECT d.specialization,
d.id AS doctor_id,
d.name AS doctor_name,
p.id AS patient_id,
p.name AS patient_name,
-- Rank doctors by specialization.
DENSE_RANK() OVER (
PARTITION BY d.specialization
ORDER BY d.id
) AS doc_spec_rank,
-- Rank patients by specialization
DENSE_RANK() OVER (
PARTITION BY d.specialization
ORDER BY p.id
) AS patient_spec_rank
FROM doctors d
INNER JOIN patients p
ON d.specialization = p.diagnosis
)
SELECT *
FROM base
WHERE (
(doc_spec_rank - 1) * 5 < patient_spec_rank
AND doc_spec_rank * 5 >= patient_spec_rank
)
ORDER BY specialization, doc_spec_rank, patient_spec_rank
;
Since you didn't provide your rbdms and test data, I took the liberty of creating a sample schema in Oracle 18c.
Here's a fiddle with the schema and the solution: https://dbfiddle.uk/4_kikOO7

Count on Table 1 based on Count with Clause on Table 2, sql

Table 1
Table 2
I need to find the Count of total number of unique stores that have "Achieved Date" not Null that achieved all of the "Achievement Ids" "enabled" on Table 2.
So far I can find the count of stores that achieved a hard coded number, but I'm not breaking through the part where I use the Count of Enabled Ids on table 2 to define what the number is.
SELECT
COUNT(*) AS count
FROM
(SELECT
StoreNumber, COUNT(*) as Achievements
FROM
StoreAchievementProgress
WHERE
AchievedDate IS NOT NULL
GROUP BY
StoreNumber) count
maybe this query
SELECT S.StoreNumber
FROM StoreAchievementProgress S
RIGHT JOIN (SELECT Id FROM Table2 WHERE Enabled=1 )T
ON T.Id=S.AchievementId
AND AchievedDate IS NOT NULL
GROUP BY S.StoreNumber
HAVING COUNT(1) = (SELECT COUNT(Id) FROM Table2 WHERE Enabled=1 )
Joining the stores with a count of their enabled achievements to how many they can get
SELECT COUNT(*) AS StoresFullAchievements
FROM
(
SELECT p.StoreNumber, COUNT(*) AS TotalEnabledAchievements
FROM StoreAchievementProgress p
JOIN Achievements a ON a.id = p.AchievementId
WHERE p.AchievedDate IS NOT NULL
AND a.Enabled = 1
GROUP BY p.StoreNumber
) AS s
JOIN
(
SELECT COUNT(*) AS TotalEnabled
FROM Achievements
WHERE Enabled = 1
) a
ON a.TotalEnabled = s.TotalEnabledAchievements

How to aggregate different CTEs in outer query SQL

i am trying to join two ctes to get the difference in performance of different countries and group on id here is my example
every campaign can be done in different countries, so how can i group by at the end to have 1 row per campaign id ?
CTE 1: (planned)
select
country
, campaign_id
, sum(sales) as planned_sales
from table x
group by 1,2
CTE 2: (Actual)
select
country
, campaign_id
, sum(sales) as actual_sales
from table y
group by 1,2
outer select
select
country,
planned_sales,
actual_sales
planned - actual as diff
from cte1
join cte2
on campaign_id = campaign_id
This should do it:
select
cte1.campaign_id,
sum(cte1.planned_sales),
sum(cte2.actual_sales)
sum(cte1.planned_sales) - sum(cte2.actual_sales) as diff
from cte1
join cte2
on cte1.campaign_id = cte2.campaign_id and cte1.country = cte2.country
group by 1
I would suggest using full join, so all data is included in both tables, not just data in one or the other. Your query is basically correct but it needs a group by.
select campaign_id,
sum(cte1.planned_sales) as planned_sales
sum(cte2.actual_sales) as actual_sales,
(coalesce(sum(cte1.planned_sales), 0) -
coalesce(sum(cte2.actual_sales), 0)
) as diff
from cte1 full join
cte2
using (campaign_id, country)
group by campaign_id;
That said, there is no reason why the CTEs should aggregate by both campaign and country. They could just aggregate by campaign id -- simplifying the query and improving performance.

How to combine 2 SQLs into single SQL

Table C(id, type) has list of all unique clients ids, with and without transactions. Every id is unique and has a single type.
Table T(date, id, type, money) is the transaction table, the id is not unique here.
Table C has more unique ids than in T, because not all clients are doing transactions.
The unique ids in the T table are subset of id's in the C table.
SQL for AVG(money) and STD(money) per type for T table:
SELECT
type,
AVG(money) AS avg_for_active_clients,
STDEV(money) AS stdev_for_active_clients,
COUNT(DISTINCT id) as cnt_active_clients
FROM (
SELECT id , type, sum(money) as money
FROM T
GROUP BY id, type
) A
GROUP BY type
SQL for AVG(money) and STD(money) per type for C table:
SELECT
type,
AVG(money) AS avg_for_all_clients,
STDEV(money) stdev_for_all_clients,
COUNT(DISTINCT id) as cnt_all_clients
FROM (
SELECT C.id, C.type , COALESCE(A.money, 0) as money FROM C
LEFT JOIN (
SELECT id , sum(money) as money
FROM T
GROUP BY id
) A
ON C.id = A.id
) B
GROUP BY type
Is it possible to combine 2 SQLs above into single SQL ?
My database is Redshift.
You can combine your select #1 with your select #2 vertically or horizontally.
To combine them vertically you can use UNION ALL. For example:
select #1
union all
select #2
To combine them horizontally you can use FULL JOIN. For example:
select *
from (
select #1
) x
full join (
select #2
) y on y.type = x.type

SQL ranking over two tables

I have two tables with user rankings.
Table rankingA and rankingB.
Each table has the columns:
user_id
points
group_id
Higher the points so higher the rank of the user/group...
Now i try to get the group ranking for the question which rank has my group.
So far i have this SQL:
select sum(ra.points) as rapoints, sum(rb.points) as rbpoints from public.rankinga ra
LEFT JOIN public.rankingb rb ON ra.group_id=rb.group_id and ra.user_id=rb.user_id where
ra.group_id=200;
It returns the points from rankinga and rankinb for the group 200.
How can i get the rankings of the group? I tryd it with:
row_number() OVER (ORDER BY sum(rb.points) DESC) AS rankb
but got a wrong result.
My expected result for group_id 200 is:
rapoints,rbpoints,rarank, rbrank
420, 10, 3, same points as group_id 300 so rbrank 2 or 3
How can i get this?
Setup
CREATE TABLE rankinga
(
user_id bigint,
group_id bigint,
points integer
)
CREATE TABLE rankingb
(
user_id bigint,
group_id bigint,
points integer
)
insert into public.rankinga (user_id,group_id,points) values (1,100,120),(2,100,300), (3,100,20),(4,200,300),(5,200,120),(6,300,600);
insert into public.rankingb (user_id,group_id,points) values (1,100,5),(2,100,3),(3,100,10),(4,200,2),(5,200,8),(6,300,10);
I think you want to do this with union all, aggregation, and the window function. Joining the tables is likely to miss rows (if users are in one table but not the other) or over count (if you join on group). So this may do what you want:
select group_id, sum(rapoints) as rapoints, sum(rbpoints) as rbpoints,
sum(rapoints) + sum(rbpoints) as points,
dense_rank() over (order by sum(rapoints) + sum(rbpoints) desc) as ranking
from ((select ra.group_id, sum(ra.points) as rapoints, 0 as rbpoints
from public.rankinga ra
group by ra.group_id
) union all
(select rb.group_id, 0, sum(rb.points) as rbpoints
from public.rankingb rb
group by rb.group_id
)
) ab
group by group_id;
If you want to select just one group, then put this in a subquery (or CTE) and then select the group.
Here is a SQL Fiddle.
EDIT:
If you want just the result for one group, you still need to calculate the values for all groups. So:
select ab.*
from (<above query here>) ab
where group_id = 200;