Can't get sum of dispense amount to add grouped dispenses - sql

I am trying to get a list of people who have been into the store in the last 90 days and have had a dispense. I am trying to get a sum of the dispense amount and then group them by their full names and individual person ID. When doing this the sum of the dispense amount doesn't get added to it. See the query below.
select sum(d.dispense_amount),
p.full_name,
d.dispense_date,
p.person_id,
p.gender
from dispense d,
patients p
where d.pid = p.pid
and d.dispense_date >= sysdate -90
group by dispense_amount, p.person_id, p.full_name, d.dispense_date, p.gender;
The sum of the dispenses are not adding when doing this. I have attempted to split the query to show like this:
select sum(dispense_amount) from dispense
where pid = 34359820391
and dispense_date >= sysdate -90;
And the person here has the correct amount. When inputting the pid into the above query you get a different value.
Could someone advise?

Never use commas in the FROM clause. Always use proper, explicit JOIN syntax.
Then, I think you want:
select sum(d.dispense_amount),
p.full_name, p.person_id, p.gender
from patients p join
dispense d
on d.pid = p.pid
where d.dispense_date >= sysdate - 90
group by p.person_id, p.full_name, p.gender;
Do not include the dispense_date or dispense_amount in the group by. You only seem to want one row per person.

Related

Im trying to remove duplicates while getting a MAX result at the same time. I cant remove the duplicates

select distinct person.person_id, MAX(patient_encounter.enc_timestamp) as LastAppt
from patient_encounter inner join person on patient_encounter.person_id = person.person_id
where enc_timestamp between '2018-04-05 00:00:00.000' and '2020-04-05 23:59:59.999'
and patient_encounter.person_id = person.person_id
and billable_ind = 'y' and person.last_name <> 'ztest'
group by person.person_id, patient_encounter.enc_timestamp
order by person.person_id
I think you want:
select p.person_id, max(pe.enc_timestamp) as LastAppt
from patient_encounter pe inner join
person p
on pe.person_id = p.person_id
where pe.enc_timestamp >= '2018-04-05' and
pe.enc_timestamp < '2020-04-06' and
?.billable_ind = 'y' and -- what table is this in ???
p.last_name <> 'ztest'
group by p.person_id
order by p.person_id;
The GROUP BY specifies the definition of each row in the results set. This is specifying that you want one row per distinct value of person_id.
Notes:
The fix is to remove the timestamp from the GROUP BY.
SELECT DISTINCT is almost never needed with GROUP BY. In fact, it is rarely needed at all.
There is no reason to redundantly duplicate the JOIN conditions in the WHERE clause.
Table aliases make the query easier to write and to read.
You should qualify all column references. What table is billing_ind in?
You can simplify the date comparisons. As a benefit, you can think in terms of days rather than milliseconds.

Why is SQL subquery in SELECT clause computed multiple times?

SELECT
P.Name,
(SELECT AVG(P1.Salary)
FROM Payroll AS P1
WHERE P.Job = P1.Job)
FROM
Payroll AS P
The query is to compute the average salary for each person's job. Why does the subquery actually return multiple tuples instead of one number (the average salary)?
IT is normal,
Using it in this subquery what you are saying if for every P.Job, compute the average salary.
You might try it this way:
SELECT P.Name, p1.AvgSalaries
FROM Payroll AS P
inner join
(SELECT P1.Job, AVG(P1.Salary) as AvgSalaries
FROM Payroll AS P1 group by P1.Job
) p1
on P.Job = P1.Job
If you have multiple "Job" available in Payroll for each "Name", your query will return multiple rows per "Name". If I guess correct, you need some GROUP BY to apply as below to achieve your required output.
SELECT P.Name, (SELECT AVG(P1.Salary)
FROM Payroll AS P1
WHERE P.Job = P1.Job)
FROM Payroll AS P
GROUP BY P.Name,P.Job
I think you want to do:
SELECT P.Name, P.Job, AVG(P.Salary)
FROM Payroll AS P
GROUP BY P.Name,P.Job
Because,SELECT or WHERE clause works on row by row basis.
In a query you if you see in subquery part of select you have referenced outer main query column. For each row, it may have different values producing different results at each row.
Basically query is looking at each employee's job and finding out avg salary offered for that job and giving it for each of the employee.

NTILE Function and Using Inner Join in Oracle

I am supposed to use the given Database(Its pretty huge so I used codeshare) to list last names and customer numbers of top 5% of customers for each branch. To find the top 5% of customers, I decided to use the NTILE Function, (100/5 = 20, hence NTILE 20). The columns are pulled from two separate tables so I used Inner joins. For the life of me, I honesly cannot figure out where I am going wrong. I keep getting "missing expression" errors but Do not know what exactly I am missing. Here is the Database
Database: https://codeshare.io/5XKKBj
ERD: https://drive.google.com/file/d/0Bzum6VJXi9lUX1d2ZkhudTE3QXc/view?usp=sharing
Here is my SQL Query so far.
SELECT
Ntile(20) over
(partition by Employee.Branch_no
order by sum(ORDERS.SUBTOTAL) desc
) As Top_5,
CUSTOMER.CUSTOMER_NO,
CUSTOMER.LNAME
FROM
CUSTOMER
INNER JOIN ORDERS
ON
CUSTOMER.CUSTOMER_NO = ORDERS.CUSTOMER_NO
GROUP BY
ORDERS.SUBTOTAL,
CUSTOMER.CUSTOMER_NO,
CUSTOMER.LNAME;
You need to join Employee and the GROUP BY must include all non-aggregated expressions. You can use a subquery to generate the subtotals and get the NTILE in the outer query, e.g.:
SELECT
Ntile(20) over
(partition by BRANCH_NO
order by sum_subtotal desc
) As Top_5,
CUSTOMER_NO,
LNAME
FROM (
SELECT
EMPLOYEE.BRANCH_NO,
CUSTOMER.CUSTOMER_NO,
CUSTOMER.LNAME,
sum(ORDERS.SUBTOTAL) as sum_subtotal
FROM CUSTOMER
JOIN ORDERS
ON CUSTOMER.CUSTOMER_NO = ORDERS.CUSTOMER_NO
JOIN EMPLOYEE
ON ORDERS.EMPLOYEE_NO = EMPLOYEE.EMPLOYEE_NO
GROUP BY
EMPLOYEE.BRANCH_NO,
CUSTOMER.CUSTOMER_NO,
CUSTOMER.LNAME
);
Note: you might want to include BRANCH_NO in the select list as well, otherwise the output will look confusing with duplicate customers (if a customer has ordered from employees in multiple branches).
Now, if you want to filter the above query to just get the top 5%, you can put the whole thing in another subquery and add a predicate on the Top_5 column, e.g.:
SELECT CUSTOMER_NO, LNAME
FROM (... the query above...)
WHERE Top_5 = 1;

SQLite COUNT and LEFT JOIN - how to combine?

There are two tables: one (P) that contains list of products, the other one (H) contains history of products consumed. Every product can be consumed 0 or more times. I need to build a query that will return all products from P along with number of times every product has been consumed, sorted by the times it's been consumed. Here is what I did:
SELECT P.ID, P.Name, H.Date, COUNT(H.P_ID) as Count
FROM P
LEFT JOIN H
ON P.ID=H.P_ID
ORDER BY Count DESC
This seems to work only if history table contains data, but if it does not - the result is incorrect. What am I doing wrong?
You need a group by to get the counts that you need. You also need to apply an aggregate function to H.Date, otherwise it is not clear which date to pick:
SELECT P.ID, P.Name, COUNT(H.P_ID) as Count, MAX(H.Date) as LastDate
FROM P
LEFT JOIN H ON P.ID=H.P_ID
GROUP BY P.ID, P.Name
ORDER BY Count DESC
I picked MAX(H.Date) to produce the date of last consumption; if you need a different date from H, change the aggregating function.
I am not sure if sqlite lets you sort by alias; if it does not, replace
ORDER BY Count DESC
with
ORDER BY COUNT(H.P_ID) DESC

How to include "zero" / "0" results in COUNT aggregate?

I've just got myself a little bit stuck with some SQL. I don't think I can phrase the question brilliantly - so let me show you.
I have two tables, one called person, one called appointment. I'm trying to return the number of appointments a person has (including if they have zero). Appointment contains the person_id and there is a person_id per appointment. So COUNT(person_id) is a sensible approach.
The query:
SELECT person_id, COUNT(person_id) AS "number_of_appointments"
FROM appointment
GROUP BY person_id;
Will return correctly, the number of appointments a person_id has. However, a person who has 0 appointments isn't returned (obviously as they are not in that table).
Tweaking the statement to take person_id from the person table gives me something like:
SELECT person.person_id, COUNT(appointment.person_id) AS "number_of_appointments"
FROM appointment
JOIN person ON person.person_id = appointment.person_id
GROUP BY person.person_id;
This however, will still only return a person_id who has an appointment and not what I want which is a return with persons who have 0 appointments!
Any suggestions please?
You want an outer join for this (and you need to use person as the "driving" table)
SELECT person.person_id, COUNT(appointment.person_id) AS "number_of_appointments"
FROM person
LEFT JOIN appointment ON person.person_id = appointment.person_id
GROUP BY person.person_id;
The reason why this is working, is that the outer (left) join will return NULL for those persons that do not have an appointment. The aggregate function count() will not count NULL values and thus you'll not get a zero.
If you want to learn more about outer joins, here is a nice tutorial: http://sqlzoo.net/wiki/Using_Null
You must use LEFT JOIN instead of INNER JOIN
SELECT person.person_id, COUNT(appointment.person_id) AS "number_of_appointments"
FROM person
LEFT JOIN appointment ON person.person_id = appointment.person_id
GROUP BY person.person_id;
if you do the outer join (with the count), and then use this result as a sub-table, you can get 0 as expected (thanks to the nvl function)
Ex:
select P.person_id, nvl(A.nb_apptmts, 0) from
(SELECT person.person_id
FROM person) P
LEFT JOIN
(select person_id, count(*) as nb_apptmts
from appointment
group by person_id) A
ON P.person_id = A.person_id
USE join to get 0 count in the result using GROUP BY.
simply 'join' does Inner join in MS SQL so , Go for left or right join.
If the table which contains the primary key is mentioned first in the QUERY then use LEFT join else RIGHT join.
EG:
select WARDNO,count(WARDCODE) from MAIPADH
right join MSWARDH on MSWARDH.WARDNO= MAIPADH.WARDCODE
group by WARDNO
.
select WARDNO,count(WARDCODE) from MSWARDH
left join MAIPADH on MSWARDH.WARDNO= MAIPADH.WARDCODE group by WARDNO
Take group by from the table which has Primary key and count from the another table which has actual entries/details.
To change even less on your original query, you can turn your join into a RIGHT join
SELECT person.person_id, COUNT(appointment.person_id) AS "number_of_appointments"
FROM appointment
RIGHT JOIN person ON person.person_id = appointment.person_id
GROUP BY person.person_id;
This just builds on the selected answer, but as the outer join is in the RIGHT direction, only one word needs to be added and less changes. - Just remember that it's there and can sometimes make queries more readable and require less rebuilding.
The problem with a LEFT JOIN is that if there are no appointments, it will still return one row with a null, which when aggregated by COUNT will become 1, and it will appear that the person has one appointment when actually they have none. I think this will give the correct results:
SELECT person.person_id,
(SELECT COUNT(*) FROM appointment WHERE person.person_id = appointment.person_id) AS 'Appointments'
FROM person;