How can I nest a query as a variable in SQL? - sql-server

Obviously, SQL isn't my first language, so I need help with something that is probably trivial.
I have the following query:
SELECT Airports.IATA_Code,
COUNT(*) AS Departures,
(SELECT COUNT(*) FROM Flights WHERE DestinationAirportId = 63384) AS Arrivals,
SUM(Flights.Tickets) AS Tickets,
SUM(Flights.Fare * Flights.Tickets) As Revenue,
AVG(Flights.Demand) AS Demand
FROM Flights
LEFT JOIN Airports
ON Flights.OriginAirportId = Airports.Id
WHERE AnalysisId = 2
GROUP BY IATA_Code
ORDER BY Tickets DESC
This query works fine, but I need to replace the hard-coded id of 63384 with the actual Airport Id. This would be Airports.Id but when I try that, I get the following error:
Column 'Airports.Id' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
Solved!
Just needed to group by the Airport Id as well:
SELECT Airports.IATA_Code,
COUNT(*) AS Departures,
(SELECT COUNT(*) FROM Flights WHERE DestinationAirportId = Airports.Id) AS Arrivals,
SUM(Flights.Tickets) AS Tickets,
SUM(Flights.Fare * Flights.Tickets) As Revenue,
AVG(Flights.Demand) AS Demand
FROM Flights
LEFT JOIN Airports
ON Flights.OriginAirportId = Airports.Id
WHERE AnalysisId = 2
GROUP BY IATA_Code, Airports.Id <---------------------------
ORDER BY Tickets DESC

Just guessing here... there should be a FK on OriginAirportId referencing Airports.Id. If that's the case, you can do an inner join instead of left join.
Also, try using CROSS APPLY if that's an option for you.
SELECT a.IATA_Code,
COUNT(*) AS Departures,
t.Arrivals,
SUM(f.Tickets) AS Tickets,
SUM(f.Fare * f.Tickets) As Revenue,
AVG(f.Demand) AS Demand
FROM Flights f
INNER JOIN Airports a
ON f.OriginAirportId = a.Id
CROSS APPLY (
SELECT COUNT(*) AS Arrivals
FROM Flights f1
WHERE f1.DestinationAirportId = a.Id) t
WHERE AnalysisId = 2
GROUP BY IATA_Code, a.Id
ORDER BY Tickets DESC
I didn't test this code so please just use it as reference only please.
Or you can even try this...
;WITH AirportDepartureCount AS (
SELECT
OriginAirportId AS AirportId,
Count(*) AS DepartCount,
SUM(f.Tickets) AS Tickets,
SUM(f.Fare * f.Tickets) As Revenue,
AVG(f.Demand) AS Demand
FROM Flights
GROUP BY OriginAirportId
), AirportArrivalCount AS (
SELECT DestinationAirportId AS AirportId, COUNT(*) AS ArrivalCount
FROM Flights
GROUP BY DestinationAirportId
)
SELECT a.Id, a.IATA_Code,
COALESCE(depart.DepartCount,0) AS DepartCount,
COALESCE(arrival.ArrivalCount,0) AS ArrivalCount,
COALESCE(depart.Tickets,0) AS Tickets,
COALESCE(depart.Revenue,0) AS Revenue,
COALESCE(depart.Demand,0) AS Demand
FROM Airports a
LEFT JOIN AirportDepartureCount depart
ON a.Id = depart.AirportId
LEFT JOIN AirportArrivalCount arrival
ON a.Id = arrival.AirportId
ORDER BY COALESCE(depart.Tickets,0) DESC
Main difference here is that this code accounts for all airports (even those that did not have any flights). In your solution, you're ignoring any airports that did not have any departing flights. Perhaps that's by design but thought I'd throw this out there for completeness sake... ;)

Related

How can I use outer join with subquery and groupby?

Tool : MySQL Workbench 6.3
Version : MySQL 5.7
SELECT *
FROM cars as a, battery_log as b
WHERE a.user_seq = 226 AND a.seq = b.car_seq
AND b.created = ( SELECT MAX(created) FROM battery_log WHERE car_seq = a.seq )
GROUP BY car_type
ORDER BY a.created DESC;
I want to turn this query into an outer join.
By searching user_seq in the'cars' table
I need to get the latest value of the battery log in the one-to-many relationship of the corresponding car table.
Sometimes the battery log does not have a value that matches car seq, so it is truncated from the joining process of table a and table b. How can I fix this?
SELECT a.*, b.battery
FROM cars as a
LEFT OUTER JOIN battery_log as b ON a.seq = b.car_seq
LEFT OUTER JOIN ( SELECT MAX(created) FROM battery_log WHERE a.seq = b.car_seq) as c
ON b.created = c.MAX(created)
WHERE a.user_seq = 226
GROUP BY car_type
ORDER BY a.created DESC
I tried to fix it this way, but I got the following error:
Error Code: 1054, Unknown column'a.seq' in'where clause'
I solved this problem like this.
SELECT *
FROM cars as a
LEFT OUTER JOIN battery_log as b ON a.seq = b.car_seq
AND b.created = (SELECT MAX(created) FROM battery_log WHERE car_seq = b.car_seq)
WHERE a.user_seq = 226
GROUP BY car_type
ORDER BY a.created DESC;
After LEFT OUTER JOIN ... ON, an additional condition was given with AND, and the query was performed according to the condition.

SQL boolean EXISTS while using aggregate function

I have written a query that returns the list of all customers that have ever made a purchase with the company I work for. The person for whom I am getting the data would like to know if a specific criteria is true for any of these orders.
select L.ParentLocation,
[Number of Orders] = count(distinct(T.Order))
from Table1 L
join Table2 T
on L.Location = T.Location
group by L.ParentLocation
However, the issue is complicated because I am already grouping by ParentLocation, and each ParentLocation has many normal Locations. So I am counting the number of unique orders at the location level, then grouping them by the ParentLocation.
I want to return 'TRUE' in the query if a field 'OrderDesc' contains "Toys" in ANY of the orders by ANY of the Locations owned by a ParentLocation. Is there a way to do this?
NOTE: Table2 contains the OrderDesc column.
Thanks for reading!
select
L.ParentLocation,
[Number of Orders] = count(distinct(T.Order)),
has_toys = max(case when t.OrderDesc like '%toys%' then 'TRUE' else '' end)
from Table1 L
inner join Table2 T
on L.Location = T.Location
group by
L.ParentLocation

Using the results of WITH clause IN where STATEMENT of main query

I am relatively new at SQL so I apologise if this is obvious but I cannot work out how to use the results of the WITH clause query in the where statement of my main query.
My with query pulls the first record for each customer and gives the sale date for that record:
WITH summary AS(
SELECT ed2.customer,ed2.saledate,
ROW_NUMBER()OVER(PARTITION BY ed2.customer
ORDER BY ed2.saledate)AS rk
FROM Filteredxportdocument ed2)
SELECT s.*
FROM summary s
WHERE s.rk=1
I need to use the date in the above query as the starting point and pull all records for each customer for their first 12 months i.e. where the sale date is between ed2.saledate AND ed2.saledate+12 months.
My main query is:
SELECT ed.totalamountincvat, ed.saledate, ed.name AS SaleRef,
ed.customer, ed.customername, comp.numberofemployees,
comp.companyuid
FROM exportdocument AS ed INNER JOIN
FilteredAccount AS comp ON ed.customer = comp.accountid
WHERE (ed.statecode = 0) AND
ed.saledate BETWEEN ed2.saledate AND DATEADD(M,12,ed2.saledate)
I am sure that I need to add the main query into the WITH clause but I cant work out where. Is anyone able to help please
Does this help?
;WITH summary AS(
SELECT ed2.customer,ed2.saledate,
ROW_NUMBER()OVER(PARTITION BY ed2.customer
ORDER BY ed2.saledate)AS rk
FROM Filteredxportdocument ed2)
SELECT ed.totalamountincvat, ed.saledate, ed.name AS SaleRef,
ed.customer, ed.customername, comp.numberofemployees,
comp.companyuid
FROM exportdocument AS ed INNER JOIN
FilteredAccount AS comp ON ed.customer = comp.accountid
OUTER APPLY (SELECT s.* FROM summary s WHERE s.rk=1) ed2
WHERE ed.statecode = 0 AND
ed.saledate BETWEEN ed2.saledate AND DATEADD(M,12,ed2.saledate)
and ed.Customer = ed2.Customer
Results of CTE are not cached or stored, so you can't reuse it.
EDIT:
Based upon your requirement that all the records from CTE should be in final result, this is a new query:
;WITH summary AS(
SELECT ed2.customer,ed2.saledate,
ROW_NUMBER()OVER(PARTITION BY ed2.customer
ORDER BY ed2.saledate)AS rk
FROM Filteredxportdocument ed2)
SELECT
ed.totalamountincvat,
ed.saledate,
ed.name AS SaleRef,
ed.customer,
ed.customername,
comp.numberofemployees,
comp.companyuid
FROM
summary ed2
left join exportdocument ed
on ed.Customer = ed2.Customer
and ed.statecode = 0
AND ed.saledate BETWEEN ed2.saledate AND DATEADD(M,12,ed2.saledate)
INNER JOIN FilteredAccount comp
ON ed.customer = comp.accountid
WHERE
s.rk=1
summary you will be able to use only once. Alternate solution is store summary into temp table and use that as many times as u want.
Something like : Select * into #temp from Summary s where s.rk=1

SQL - How to only show the row with the greatest date value based on ID?

I have SQL statements in SQL Server 2008 R2 based on a few joined tables that utilizes all the information I need in my program:
SELECT
Laptops.Laptop_ID,
Laptops.Model_Name,
...
Users.Firstname + Users.Lastname AS Name,
Loans.Date_Loaned
FROM Users
INNER JOIN Loans ON Users.User_ID = Loans.User_ID
RIGHT OUTER JOIN Laptops ON Loans.Laptop_ID = Laptops.Laptop_ID
This brings up a table similar to:
ID Model_Name ... Name Date_Loaned
1 ... ... Kris 18-08-11
2 ... ... Jo 20-08-11
2 ... ... Bert 18-08-11
4 ... ... Sam 19-08-11
What I'm trying to do is where there would be repeated ID, I want to only show the row with the highest date, like this:
ID Model_Name ... Name Date_Loaned
1 ... ... Kris 18-08-11
2 ... ... Jo 20-08-11
4 ... ... Sam 19-08-11
I'm having problems figuring out how to do this with the SQL statement that I already have. Help!
Use windows functions:
SELECT * FROM(
SELECT Laptops.Laptop_ID,
Laptops.Model_Name,
...
Users.Firstname + Users.Lastname AS Name,
Loans.Date_Loaned,
row_number()
over(partition by Laptops.Laptop_ID
order by Loans.Date_Loaned desc) rn
FROM Users
INNER JOIN Loans ON Users.User_ID = Loans.User_ID
RIGHT OUTER JOIN Laptops ON Loans.Laptop_ID = Laptops.Laptop_ID) t
WHERE rn = 1
Try this
SELECT Laptops.Laptop_ID,
Laptops.Model_Name,
...
Users.Firstname + Users.Lastname AS Name,
MAX(Loans.Date_Loaned) AS date
FROM Users
INNER JOIN Loans ON Users.User_ID = Loans.User_ID
RIGHT OUTER JOIN Laptops ON Loans.Laptop_ID = Laptops.Laptop_ID
GROUP BY Laptops.Laptop_ID,
Laptops.Model_Name,
...
Users.Firstname + Users.Lastname AS Name
Glad you got your answer, but just wanted to mention you might get better performance selecting from Laptops and LEFT OUTER JOIN your Users/Loans sub query. It might be a little easier for the next person to decipher since RIGHT OUT JOIN is not used very often. Using aliases also helps eliminate some typing.
SELECT
l.Laptop_ID,
l.Model_Name,
ul.Name,
ul.Date_Loaned
FROM
Laptops l
LEFT JOIN (
SELECT l.Laptop_ID,
u.Firstname + u.Lastname AS Name,
l.Date_Loaned,
ROW_NUMBER() OVER(PARTITION BY l.Laptop_ID ORDER BY l.Date_Loaned desc) Rn
FROM Loans l
JOIN Users u ON l.User_ID = u.User_ID
) ul ON l.Laptop_ID = ul.Laptop_ID
AND ul.Rn = 1

How to SELECT DISTINCT Info with TOP 1 Info and an Order By FROM the Top 1 Info

I have 2 tables, that look like:
CustomerInfo(CustomterID, CustomerName)
CustomerReviews(ReviewID, CustomerID, Review, Score)
I want to search reviews for a string and return CustomerInfo.CustomerID and CustomerInfo.CustomerName. However, I only want to show distinct CustomerID and CustomerName along with just one of their CustomerReviews.Reviews and CustomerReviews.Score. I also want to order by the CustomerReviews.Score.
I can't figure out how to do this, since a customer can leave multiple reviews, but I only want a list of customers with their highest scored review.
Any ideas?
This is the greatest-n-per-group problem that has come up dozens of times on Stack Overflow.
Here's a solution that works with a window function:
WITH CustomerCTE (
SELECT i.*, r.*, ROW_NUMBER() OVER (PARTITION BY CustomerID ORDER BY Score DESC) AS RN
FROM CustomerInfo i
INNER JOIN CustomerReviews r ON i.CustomerID = r.CustomerID
WHERE CONTAINS(r.Review, '"search"')
)
SELECT * FROM CustomerCTE WHERE RN = 1
ORDER BY Score;
And here's a solution that works more broadly with RDBMS brands that don't support window functions:
SELECT i.*, r1.*
FROM CustomerInfo i
INNER JOIN CustomerReviews r1 ON i.CustomerID = r1.CustomerID
AND CONTAINS(r1.Review, '"search"')
LEFT OUTER JOIN CustomerReviews r2 ON i.CustomerID = r2.CustomerID
AND CONTAINS(r1.Review, '"search"')
AND (r1.Score < r2.Score OR r1.Score = r2.Score AND r1.ReviewID < r2.ReviewID)
WHERE r2.CustomerID IS NULL
ORDER BY Score;
I'm showing the CONTAINS() function because you should be using the fulltext search facility in SQL Server, not using LIKE with wildcards.
I voted for Bill Karwin's answer, but I thought I'd throw out another option.
It uses a correlated subquery, which can often incur performance problems with large data sets, so use with caution. I think the only upside is that the query is easier to immediately understand.
select *
from [CustomerReviews] r
where [ReviewID] =
(
select top 1 [ReviewID]
from [CustomerReviews] rInner
where rInner.CustomerID = r.CustomerID
order by Score desc
)
order by Score desc
I didn't add the string search filter, but that can be easily added.
I think this should do it
select ci.CustomterID, ci.CustomerName, cr.Review, cr.Score
from CustomerInfo ci inner join
(select top 1*
from CustomerReviews
where Review like '%search%'
order by Score desc) cr on ci.CustomterID = cr.CustomterID
order by cr.Score

Resources