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
Related
I am using the basic chinook database and I am trying to get a query that will display the worst selling genres. I am mostly getting the answer, however there is one genre 'Opera' that has 0 sales, but the query result is ignoring that and moving on to the next lowest non-zero value.
I tried using left join instead of inner join but that returns different values.
This is my query currently:
create view max
as
select distinct
t1.name as genre,
count(*) as Sales
from
tracks t2
inner join
invoice_items t3 on t2.trackid == t3.trackid
left join
genres as t1 on t1.genreid == t2.genreid
group by
t1.genreid
order by
2
limit 10;
The result however skips past the opera value which is 0 sales. How can I include that? I tried using left join but it yields different results.
Any help is appreciated.
If you want to include genres with no sales then you should start the joins from genres and then do LEFT joins to the other tables.
Also, you should not use count(*) which counts any row in the resultset.
SELECT g.name Genre,
COUNT(i.trackid) Sales
FROM genres g
LEFT JOIN tracks t ON t.genreid = g.genreid
LEFT JOIN invoice_items i ON i.trackid = t.trackid
GROUP BY g.genreid
ORDER BY Sales LIMIT 10;
There is no need for the keyword DISTINCT, since the query returns 1 row for each genre.
When asking for the top n one must always state how to deal with ties. If I am looking for the top 1, but there are three rows in the table, all with the same value, shall I select 3 rows? Zero rows? One row arbitrarily chosen? Most often we don't want arbitrary results, which excludes the last option. This excludes LIMIT, too, because LIMIT has no clause for ties in SQLite.
Here is an example with DENSE_RANK instead. You are looking for the worst selling genres, so we must probably look at the revenue per genre, which is the sum of price x quantity sold. In order to include genres without invoices (and maybe even without tracks?) we outer join this data to the genre table.
select total, genre_name
from
(
select
g.name as genre_name,
coalesce(sum(ii.unit_price * ii.quantity), 0) as total
dense_rank() over (order by coalesce(sum(ii.unit_price * ii.quantity), 0)) as rnk
from genres g
left join tracks t on t.genreid = g.genreid
left join invoice_items ii on ii.trackid = t.trackid
group by g.name
) aggregated
where rnk <= 10
order by total, genre_name;
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.
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;
I've been working on a school project past few days and I picked to work on a DVD club database. I have six tables, but for this question, only two are relevant. The clients table and the loans table. So, what I am trying to do is count for every client how many loans he's made so far and out of all pick the client with the max number of loans, so he can be rewarded the free DVD next month. Here is the code I've written, but it doesn't pick the specific client, it shows all the clients having the max number of loans of a specific client:
SELECT tblClients.Client_ID, MAX(x.Number_Of_Loans) AS MAX_NOL
FROM
(
SELECT COUNT(tblLoans.Client_ID) AS Number_Of_Loans
FROM tblClients, tblLoans WHERE tblClients.Client_ID=tblLoans.Client_ID
GROUP BY tblLoans.Client_ID
)x, tblClients, tblLoans
WHERE tblClients.Client_ID=tblLoans.Client_ID
GROUP BY tblClients.Client_ID, tblClients.Given_Name,
tblClients.Family_Name, tblClients.Phone, tblClients.Address, tblClients.Town_ID
Use the following
SELECT TOP 1 tblClients.Client_ID,COUNT(tblLoans.Client_ID) AS MAX_NOL
FROM tblClients, tblLoans
WHERE tblClients.Client_ID=tblLoans.Client_ID
GROUP BY tblClients.Client_ID
ORDER BY COUNT(tblLoans.Client_ID) DESC
You can do this with a single aggregate GROUP, ordered by the client with the max loans:
SELECT TOP 1 tblClients.Client_ID, tblClients.Given_Name, tblClients.Family_Name,
tblClients.Phone, tblClients.Address, tblClients.Town_ID,
COUNT(x.Number_Of_Loans) AS MAX_NOL
FROM
tblClients INNER JOIN tblLoans
ON tblClients.Client_ID=tblLoans.Client_ID
GROUP BY tblClients.Client_ID, tblClients.Given_Name, tblClients.Family_Name,
tblClients.Phone, tblClients.Address, tblClients.Town_ID
ORDER BY MAX_NOL DESC;
Any selected columns from the client need to be included in the GROUP, and I would recommend using JOINs instead of WHERE joins.
Edit
What might be tidier is to split the determination of the ClientId with the most loans and the concern of fetching the rest of the client's data, like so (rather than the ungainly GROUP BY over many columns):
SELECT c.Client_ID, c.Given_Name, c.Family_Name,
c.Phone, c.Address, c.Town_ID,
x.MaxLoans
FROM
tblClients c
INNER JOIN
(SELECT TOP 1 tblClients.Client_ID, COUNT(tblLoans.Client_ID) AS MaxLoans
FROM tblClients
INNER JOIN tblLoans
ON tblClients.Client_ID=tblLoans.Client_ID
GROUP BY tblClients.Client_ID
ORDER BY MaxLoans DESC) x
ON c.Client_ID = x.Client_ID;
I have a pair of SQL server tables.
P contains id and name.
PR contains id, interestrate, tiernumber, fromdate, todate and P.id. PR may contain many rows listed per p.id / tier. (tiers are a list of rates a product may have in any given date period.)
eg: Product 1 tier 1 starts 1/1/2008 to 1/1/2009 and has 6 rates shown 1 row per rate. Product 1 tier 2 starts 1/2/2009 etc etc etc
I need a view on this that shows the P.name and the PR.tiernumber and dates... BUT I want only one row to represent the tier.
This is easy:
SELECT DISTINCT P.ID, P.PRODUCTCODE, P.PRODUCTNAME, PR.TIERNO,
PR.FROMDATE, PR.TODATE, PR.PRODUCTID
FROM dbo.PRODUCTRATE AS PR INNER JOIN dbo.PRODUCT AS P
ON P.ID = PR.PRODUCTID
ORDER BY P.ID DESC
This gives me the exact right data... However: this disallows me to see the PR.ID as that would negate the distinct.
I need to limit the resultset because the user needs to just see just a list of tiers, I need to see the PR.ID displaying all of the data.
Any ideas?
SELECT P.ID, P.ACUPRODUCTCODE, P.PRODUCTNAME, PR.TIERNO,
PR.FROMDATE, PR.TODATE, PR.PRODUCTID, MIN(PR.ID)
FROM dbo.PRODUCTRATE AS PR INNER JOIN dbo.PRODUCT AS P
ON P.ID = PR.PRODUCTID
GROUP BY P.ID, P.ACUPRODUCTCODE, P.PRODUCTNAME, PR.TIERNO,
PR.FROMDATE, PR.TODATE, PR.PRODUCTID
ORDER BY P.ID DESC
Ought to do the job. GROUP BY in place of DISTINCT, with a summary function (MIN) to get a particular value back for the PR.ID.
It sounds like you want to accomplish two different things with the same query, which doesn't make sense. Either you want a list of product/tier/date information or you want a list of interest rates.
If you want to pick a particular PR.ID to go with your data then you need to decide on what the rule is for that - what determines which ID you want to get back?