How can I join these 3 tables - sql

I have 3 tables:
Trip Promotion Promotion Cost.
1 ---- M 1 --------- M
Sample data include:
TripID TripName Date
XYZ123 Hawaii 09/06/09
YTU574 Japan 09/09/09
GHR752 US 11/07/09
PromotionID TripID Name
1 XYZ123 Poster
2 XYZ123 Brochure
3 GHR752 TV ad
CostID PromotionID Cost
1 1 $50
2 1 $100
3 1 $120
4 3 $2000
5 2 $500
I'm trying to build a query like this:
TripID Number of Promotions Total Cost
XYZ123 2 $770
GHR752 1 $2000
What I have is this:
SELECT
Trip.TripID, Count(Trip.TripID) AS [Number Of Promotions], Sum(PromotionCost.Cost) AS SumOfCost
FROM
Trip
INNER JOIN
(Promotion
INNER JOIN
PromotionCost ON Promotion.PromotionID = PromotionCost.PromotionID
) ON Trip.TripID = Promotion.TripID
GROUP BY
Trip.TripID;
And it gives me something like this:
TripID Number of Promotions Total Cost
XYZ123 4 $770
GHR752 1 $2000
I'm not sure why the Number of Promotions is messed up like that for the first one (XYZ123). It seems that somehow the JOIN is affecting it because if I use this:
SELECT
Trip.TripID, Count(Trip.TripID) AS [Number Of Promotions],
FROM
Trip
INNER JOIN
Promotion ON Trip.TripID = Promotion.TripID
GROUP BY
Trip.TripID;
It gives me the right number of promotions which is just 2.

You can add up the cost for each promotion in a subquery. That way, you only get one row for each promotion, and COUNT works to calculate the number of promotions per trip. For example:
select
t.TripId
, count(p.PromotionId) as [Number of Promotions]
, sum(pc.PromotionCost) as [Total Cost]
from trip t
left join promotions p on p.TripId = t.TripId
left join (
select
PromotionId
, PromotionCost = sum(cost)
from Promotions
group by PromotionId
) pc on pc.PromotionId = p.PromotionId
group by t.TripId
In case MS Access does not allow subqueries, you can store the subquery in a view, and join on that.

You can try to compensate for the duplicate Promotion rows by using COUNT(DISTINCT):
SELECT Trip.TripID, Count(DISTINCT Promotion.PromotionID) AS [Number Of Promotions],
Sum(PromotionCost.Cost) AS SumOfCost
FROM Trip INNER JOIN Promotion ON Trip.TripID = Promotion.TripID
INNER JOIN PromotionCost ON Promotion.PromotionID = PromotionCost.PromotionID
GROUP BY Trip.TripID;
What's going on is that by default, COUNT() counts the rows produced after all joins have been done. There are four promotion costs for TripID XYZ123, so four rows, even though the TripId occurs multiple times among those four rows.
It's easier to visualize if you try a similar query without the GROUP BY:
SELECT Trip.TripID, Promotion.PromotionID, PromotionCost.Cost
FROM Trip INNER JOIN Promotion ON Trip.TripID = Promotion.TripID
INNER JOIN PromotionCost ON Promotion.PromotionID = PromotionCost.PromotionID;
You'll see the four rows for XYZ123 (with duplicate PromotionID values), and one row for GHR752.
Re comments that MS Access doesn't support COUNT(DISTINCT): if that's the case, then you shouldn't do this in a single query. Do it in two queries:
SELECT Trip.TripID, SUM(PromotionCost.Cost) AS SumOfCost
FROM Trip INNER JOIN Promotion ON Trip.TripID = Promotion.TripID
INNER JOIN PromotionCost ON Promotion.PromotionID = PromotionCost.PromotionID
GROUP BY Trip.TripID;
SELECT Trip.TripID, Count(Promotion.PromotionID) AS [Number Of Promotions]
FROM Trip INNER JOIN Promotion ON Trip.TripID = Promotion.TripID
GROUP BY Trip.TripID;
The alternative is a very convoluted solution using subqueries, described in this article at Microsoft:
http://blogs.msdn.com/access/archive/2007/09/19/writing-a-count-distinct-query-in-access.aspx

Not the answer to your question but a useful recommendation (I hope): convert your query into a view by using the visual designer of SQL Server Management Studio, and examine the generated SQL code. You don't have to actually keep and use the generated view, but it is a good way of learning by example. I do that whenever I'm struggled with a complex query.
EDIT. Shame on me, I hand't read the tags: the question is MS-Access related, not SQL Server related. Anyway I think that my advice is still valid as far as concept-learning is the concern, since the SQL syntax is similar.

Related

SQL Sum Names and Get How Many Times they occur in the database as a Percentage

I have a database where I store names every computer make and model when they get booked into our system as ticket. I want to SUM up the unique values and display a table showing how many the computer manufacturer name appears.
i.e.
HP 30%
Dell 10%
Toshiba 40%
Sony 20%
My query looks like this which shows every occurrence from every ticket. I am not sure how to go about translating to the table above:
SELECT
ComputerMake.ComputerMakeName
FROM
Ticket
INNER JOIN Asset ON Ticket.AssetID = Asset.AssetID
INNER JOIN ComputerMake ON Asset.ComputerMakeID = ComputerMake.ComputerMakeID
I am sure its a simple job of summing up each ComputerMakeName but this is beyond my basic SQL experience!
Thank you in advanced.
So first you need to count how many times each computer make is booked, and then calc the percentage:
SELECT
ComputerMake.ComputerMakeName
, count(*) * 100 / (select count(*) from FROM
Ticket
INNER JOIN Asset ON Ticket.AssetID = Asset.AssetID
INNER JOIN ComputerMake ON Asset.ComputerMakeID = ComputerMake.ComputerMakeID) as count_computers
FROM
Ticket
INNER JOIN Asset ON Ticket.AssetID = Asset.AssetID
INNER JOIN ComputerMake ON Asset.ComputerMakeID = ComputerMake.ComputerMakeID
group by ComputerMake.ComputerMakeName
Consider using CTE to simplify the query and avoid redundancy
WITH computer_CTE(computerName) AS
(
SELECT ComputerMake.ComputerMakeName
FROM Ticket
INNER JOIN Asset ON Ticket.AssetID = Asset.AssetID
INNER JOIN ComputerMake ON Asset.ComputerMakeID = ComputerMake.ComputerMakeID
)
Select computerName, (Count(*) * 100 / (Select Count(*) From computer_CTE)) as Percent
From computer_CTE
Group By computerName

Access 2002 SQL for joining three tables

I have been trying to get this to work for a while now. I have 3 tables. First table has the Sales for customers which include the CustomerID, DateOfSales (Which always has the first of the month). The second table has the CustomerName, CustomerID. The third table has which customers buy what product lines. They are stored by CustomerID, ProductID.
I want to get a list (from one SQL hopefully) that has ALL the customers that are listed as buying a certain ProductID AND the maxDate from the Sales. I can get all of them IF there are sales for that customer. How the heck do I get ALL customers that buy the certain ProductID AND the maxDate from Sales or NULL if there is no sales found?
SalesList |CustomerList|WhoBuysWhat
----------|------------|-----------
maxDate |CustomerID |CustomerID
CustomerID| |ProductID=17
This is as close as I got. It gets all max dates but only if there have been sales. I want the CustomerID and a NULL for the maxDate if there were no sales recorded yet.
SELECT WhoBuysWhat.CustomerID, CustomerList.CustomerName,
Max(SalesList.MonthYear) AS MaxOfMonthYear FROM (CustomerList INNER
JOIN SalesList ON CustomerList.CustomerID = SalesList.CustomerID) INNER
JOIN WhoBuysWhat ON CustomerList.CustomerID = WhoBuysWhat.CustomerID
WHERE (((SalesList.ProductID)=17)) GROUP BY WhoBuysWhat.CustomerID,
CustomerList.CustomerName;
Is it possible or do I need to use multiple SQL statements? I know we should get something newer than Access 2002 but that is what they have.
You want LEFT JOINs:
SELECT cl.CustomerID, cl.CustomerName,
Max(sl.MonthYear) AS MaxOfMonthYear
FROM (CustomerList as cl LEFT JOIN
(SELECT sl.*
FROM SalesList sl
WHERE sl.ProductID = 17
) as sl
ON cl.CustomerID = sl.CustomerID
) LEFT JOIN
WhoBuysWhat wbw
ON cl.CustomerID = wbw.CustomerID
GROUP BY cl.CustomerID, cl.CustomerName;

SELECT table 2 insite a COUNT comparing to table 1

I have four tables: SCHED_FLIGHT, AIRCRAFT, PLANETYPE and RESERVATIONS. I want to do something like this:
select S.SCHED_NO as "Scheduled Flight Number", P.CAPACITY - COUNT (SELECT * FROM RESERVATIONS R WHERE R.SCHED_NO = S.SCHED_NO) "Remaining Seats"
from PLANETYPE P, AIRCRAFT A, SCHED_FLIGHT S
where S.SERIAL_NO = A.SERIAL_NO and A.TYPE_NO = P.TYPE_NO;
As you can see, each SCHEDULED_FLIGHT has a AIRCRAFT SERIAL_NO, each AIRCRAFT has a PLANETYPE TYPE_NO and each PLANETYPE has a different capacity, so, each SCHED_FLIGHT has a capacity based on the plane and I want to get the number of remaining seats counting the number of RESERVATIONS made to that flight.
Of course the code doesn't work, but I have no idea how to solve this problem. Any tips?
edit: I've received some answers, but first I was just showing two tables as an example - but actually there's 4 tables involved and I would have to include a where clause or multiple joins... so I'm still confused. What should I do now? Look at my code.
Update:
I'm not sure that all of columns correspond to your real columns, but this query might look like this:
SELECT
SF.SCHED_NO AS 'Scheduled Flight Number',
SF.CAPACITY - COUNT (R.SCHED_NO) AS 'Remaining Seats'
FROM
(
SELECT
SF.SCHED_NO,
P.CAPACITY
FROM SCHEDULED_FLIGHT SF
INNER JOIN AIRCRAFT A
ON SF.AIRCRAFT_SERIAL_NO = A.SERIAL_NO
INNER JOIN PLANETYPE P
ON A.PLANETYPE_NO = P.PLANETYPE_NO
) SF
LEFT JOIN RESERVATIONS R
ON SF.SCHED_NO = R.SCHED_NO
GROUP BY SF.SCHED_NO, SF.CAPACITY
I would be inclined to approach this as an aggregation with a join. First, aggregate the reservations by flight number. Then join that back to FLIGHT and calculate the remaining capacity:
SELECT F.FLIGHT_NO, (F.CAPACITY - COALESCE(cnt, 0)) as Remaining
FROM FLIGHT F LEFT JOIN
(SELECT R.FLIGHT_NO, COUNT(*) as cnt
FROM RESERVATIONS R
GROUP BY R.FLIGHT_NO
) R
ON R.FLIGHT_NO = F.FLIGHT_NO;

SQL filtering data

I need some advice on how to query a database more efficiently.
The situation can be explained as this. If a customer comes into the store and buys something that creates a sale in the [sales table] this table links via transaction reference to [transactions table] but there may be a number of items purchased, each of these creates a row in the [products table].
To add to this there are also two promotion tables that indicate whether the product is on price promotion [promotions] or on a multibuy promotion[sales flag]. Both of these also link back to the sales table.
I want to be able to bring back all the rows so every product of every transaction that was in one of the promotions is available.
The conditions work as follows
Product ¦ Price promo (promotions-promotion no) ¦Multibuy(salesflag-On promotion)
-----------------------------------
1.Product A -1 0 = neither price promo nor multibuy
2.Product B -1 1 = multibuy only ie 0 false 1 true
3.Product C 8245 0 = price promo only can be any number other than -1
These are the three scenarios yet they could all happen in one customer transaction.
so how could I check every transaction for the circumstances of products B and C and return the full basket if either is present?
All I can think to do is a further select within the where clause but this would return all the relevant transaction numbers and the main query would then search the entire database for these transaction numbers it seems a very long winded way as it is a huge database I'm working on + I'm fairly basic level sql at the moment.
SELECT
TRANS.TransactionReference
,TRANS.TID
,CONVERT(VARCHAR(10),T.Day,103) as 'Date'
,PROD.HierarchyLevel2
,PROD.HierarchyLevel3
,PROD.HierarchyLevel4
,PROD.ProductDescription
,PROD.ExternalProductCode
,PROD.Size
,CUST.CardNumber
,S.SalesQty /*unless Group everything somehow will get multiple lines per qty sold all with qty of 1*/
,S.SalesValue
,STOR.Area
,STOR.StoreCode
,STOR.StoreName
FROM Reporting.Sales as S
INNER JOIN Reporting.Products as PROD
on S.ProductID = PROD.ProductID
INNER JOIN Reporting.Time as T
on S.DateID = T.DateID
INNER JOIN Reporting.Stores as STOR
on S.StoreID = STOR.StoreID
INNER JOIN Reporting.Promotions as PROM
on S.PromotionID = PROM.PromotionID
INNER JOIN Reporting.SalesFlags as FLAG
on S.SalesFlagID = FLAG.SalesFlagID
INNER JOIN Reporting.Transactions as TRANS
on S.TID = TRANS.TID
INNER JOIN Reporting.Customers as CUST
on S.CustomerID = CUST.CustomerID
WHERE
T.Day BETWEEN '2014-01-23' and '2014-02-11'
AND TRANS.TransactionReference IN(SELECT distinct TRANS.TransactionReference
FROM Reporting.Sales as S
INNER JOIN Reporting.Transactions as TRANS
on S.TID = TRANS.TID
INNER JOIN Reporting.Promotions as PROM
on S.PromotionID = PROM.PromotionID
INNER JOIN Reporting.SalesFlags as FLAG
on S.SalesFlagID = FLAG.SalesFlagID
INNER JOIN Reporting.Time as T
on S.DateID = T.DateID
WHERE
PROM.PromotionNumber = '-1'
AND FLAG.OnPromotion = 1)
AND T.Day BETWEEN '2014-01-23' and '2014-02-11'
ORDER BY
TRANS.TransactionReference
;
The above would only take account of the sales where a basket contained a multibuy and i havent worked through how to include the sales where the basket can also contain a price promo. But already I'm leaving the query for 10 minutes and getting no results back- just keeps running.
The inner joins I have used I believe are all primary keys as the database has been set up cleverly to all link back to the sales table through keys built into this table.
Thanks in advance,
Kind regards,
Adam.

SQL reporting joining three tables

Firstly, my SQL knowledge is little rusty. I am trying to generate a report of reviews each patient has gone through for a time period. A review is done as part of a Doctors' round. The following are the corresponding tables with relevant columns:
Patients: (id, name)
Rounds: (id, patient_id, date)
Reviews: (id, round_id, review)
The report should look like the following:
Patient | Reviews
_________________________
Patient 1 | 2
_________________________
Patient 2 | 1
_________________________
Patient 3 | 0
_________________________
I tried the following SQL statement:
SELECT
p.name as patient,
COUNT(r.round_id) as reviews
FROM
patients as p
JOIN rounds as ro ON p.id = ro.patient_id
JOIN reviews as r ON ro.id = r.round_id
WHERE
r.review_date between '2012-02-01' AND '2012-02-29'
GROUP BY
p.name
But, the above query only returns rows where reviews count is > 1. I want it to return even if the count is 0.
The simplest way to Join the tables, and to include instances where there is no match in one of those tables, is to use a LEFT OUTER JOIN. This will match all records to the left, regardless of whether a match was found on the right side of the JOIN.
Since your r.review_date is in your WHERE clause, no matches can occur unless there is a review between those dates. So to include instances where there is no review, you must allow for that in your WHERE clause by adding "OR r.review_date IS NULL" as below. You may also want to consider filtering on the round.date field instead, so that you are only looking at instances where there were valid rounds performed within that time frame. ie. "WHERE ro.date between '2012-02-01' AND '2012-02-29'"
eg.
SELECT
p.name as patient,
COUNT(r.round_id) as reviews
FROM
patients as p
JOIN rounds as ro ON p.id = ro.patient_id
LEFT OUTER JOIN reviews as r ON ro.id = r.round_id
WHERE
r.review_date between '2012-02-01' AND '2012-02-29' OR r.review_date IS NULL
GROUP BY
p.name
Note: If you want to report records without any rounds, you will also have to make the first JOIN a LEFT OUTER JOIN as well.
FROM
patients as p
LEFT OUTER JOIN rounds as ro ON p.id = ro.patient_id
LEFT OUTER JOIN reviews as r ON ro.id = r.round_id
SELECT p.name AS patient
, COUNT(r.ID) AS reviews
FROM patients AS p
LEFT JOIN rounds AS ro ON p.id = ro.patient_id
LEFT JOIN reviews AS r ON ro.id = r.round_id
AND r.review_date BETWEEN '2012-02-01'
AND '2012-02-29'
GROUP BY p.name
Will get you a list of all patients, including those who have not had a round or a review in between your specific dates. Patients with no rounds or reviews will have a 0.