Trouble with my SQL join/group by/cursor question - sql-server

I've made database design for a small CRM system. It comprises of Companies and Meetings (amongst others).
Companies has the fields:
ID (primary, auto_inc)
Name (text)
Meetings has the fields:
ID (primary, auto_inc)
CompanyId (link to Companies.ID)
WhenTime (datetime, to store when the meeting was)
Notes (text about the meeting)
What I want to accomplish is a query that gives me a list of all Companies (all fields in the table), AND the WhenTime and Notes of the latest meeting with that company (latest is max(WhenTime), and if there is none, a NULL will do fine).
I think I can solve this with cursors, but I'm afraid of speed.
I've tried several Group By formulations, but I fear I lack the finesse required.
My last attempt was this:
select Companies.ID, Companies.name, mts.whentime, mts.notes
from Companies
left outer join (
select top(1) *
from Meetings
order by [whentime] desc
) mts
on Companies.ID = mts.companyID
order by Companies.name asc
but this code only takes one tuple from Meetings, not one per company in the join, so it's no good.
Any ideas?

Try:
select Companies.ID, Companies.name, mts.whentime, mts.notes
from Companies
cross apply
(
select top(1) *
from Meetings
where Companies.ID = Meetings.companyID
order by [whentime] desc
) mts
order by Companies.name asc;

I would start by creating a view of the latest meetings as I find creating views makes complex queries easier to read and maintain and can introduce an element of reusability (if done right).
CREATE VIEW [dbo].[LatestCompanyNotes]
AS
SELECT [CompanyId], [WhenTime], [Notes]
FROM [Meetings] AS M1
INNER JOIN
(
SELECT [CompanyId], MAX([Id]) AS [MaxId]
FROM [Meetings]
GROUP BY [CompanyId]
) AS M2 ON M2.[CompanyId] = M1.[CompanyId] AND M2.[MaxId] = M1.[Id]
Now you should be able to join to this view in your query as you've previously done.
SELECT Companies.[ID], Companies.[Name], mts.[WhenTime], mts.[Notes]
FROM [Companies]
LEFT OUTER JOIN [dbo].[LatestCompanyNotes] AS mts ON mts.[CompanyId] = Companies.[ID]
ORDER BY Companies.[Name] ASC
Please note that I've not tested the code (I don't even have SQL Server installed) and it may require a few small changes to work.

You don't need a cross-apply here, just a correlated sub-query to find the most recent meeting date:
SELECT Companies.ID, Companies.name, mts.whentime, mts.notes
FROM Companies
LEFT OUTER JOIN Meetings mts
ON Companies.ID = mts.companyID
AND mts.WhenTime =
(SELECT MAX(WhenTime) FROM Meetings mtshist WHERE mtshist.companyID = mts.companyID)
ORDER BY Companies.name
Note that this will retrieve all companies, including those which have never had a meeting:
1 Alexander and co. 2010-01-04 some more notes
2 Barnard Partnership 2010-01-03 NULL
3 Collingwood Ltd. 2010-01-07 recent meeting
4 Dimitri and sons NULL NULL

Related

List all vendors which do not have active contracts in SQL

Assuming the following stucture
table contract: contract_id, status_id, vendor_id
table status: active (status_id = 1), inactive (status_id = 2), deleted (status_id=3)
table vendor: vendor_id, vendor_name
How can I list only vendors with their contracts, all of them either inactive or deleted?
so if there is at least one contract for specific vendor which is active, this vendor and all of his contracts should be omitted
EDIT:
sample schema
http://sqlfiddle.com/#!18/01f4c/5
I was thinking something like this might fit the bill. Qry1 is the set of all contracts with the relevant vendor and status columns. Qry2 returns the set of all vendors with no active contracts. The final query returns everything from Qry1 where the vendor is not in Qry2.
With Qry1 As (
Select Vendor.VendorId,
Vendor.VendorName,
Contract.ContractId,
Contract.ContractName,
Status.StatusName
From Vendor
Inner Join Contract
On Vendor.VendorId = Contract.VendorId
Inner Join Status
On Contract.StatusId = Status.StatusId
),
Qry2 As (
Select Distinct VendorId
From Qry1
Where StatusName = 'Active'
)
Select Qry1.*
From Qry1
Where Not Exists (
Select Qry2.VendorId
From Qry2
Where Qry2.VendorId = Qry1.VendorId
)
First, I'd like to point out that in your question, you state "...inactive or deleted", which would mean two statuses(2,3), yet in your last comment, you specify where status = 1, which is active. This is very confusing. However, based on your last comment, which I will treat as the most up to date requirements, I believe the below query will get what you want.
EDIT: I believe I just figured out what you meant. There's some ambiguity in the way you wrote your last comment. Adjusted the query.
SELECT *
FROM vendors v
LEFT JOIN contract c ON v.vendor_id = c.vendor_id
WHERE c.STATUS in (2,3)
AND c.vendor_id IS NULL

How do I query three related tables?

I'm new to databases and I'm having a hard time figuring this out. Any assistance would be greatly appreciated
Deliveries Table
-- ID (PK)
-- DriverID (FK to Drivers Table)
Drivers Table
-- ID (PK)
-- LocationID (FK to Locations Table)
Locations Table
-- ID (PK)
-- RestaurantID (FK to Restaurants Table)
Restaurant Table
--ID (PK)
A Restaurant can have multiple locations (1 to many). A location can have multiple drivers (1 to many). a Driver can have multiple deliveries (1 to many). This design is supposed to break things out in 3rd normal form. So if I want to go to the deliveries table and get all of the deliveries associated with a particular restaurant, how would I query or do a join for that? Would I have to add a second foreign key to Deliveries that directly references the Restaurant table? I think after I see the query I can figure out what is going on. Thx
You can use left or right outer join to make a combined table and then you can easily query it, or else you can use a query with multiple sub-queries inside it to attain the required result without using join. Here is an example on how to use sub-query for your use-case.
SELECT ID FROM Deliveries De
WHERE De."DriverID" IN (SELECT ID FROM Drivers Dr
WHERE Dr. "LocationID" IN (SELECT ID FROM Locations L
WHERE L. "RestaurantID" IN (SELECT ID FROM Restaurant)))
I hope this solves your issue without using join statement.
You can use inner join or union depending on what you want to achieve. Example:
SELECT a."articleId" AS id, a.title, a."articleImage" AS "articleImage/url", c.category AS "category/public_id", a."createdOn", concat("firstName", ' ', "lastName") AS author
FROM articles a
INNER JOIN users u ON a."userId" = u."userId"
INNER JOIN categories c ON a."categoryId" = c."categoryId"
UNION
SELECT g."gifId" AS id, g.title, g."imageUrl" AS "articleImage/url", g.public_id AS "category/public_id", g."createdOn", concat("firstName", ' ', "lastName") AS author
FROM gifs g
INNER JOIN users u ON g."userId" = u."userId"
ORDER BY "createdOn" DESC
You can say how you want to get the results for more detailed query.
If I understand what you want to do then it maybe like this,
1st you have to join all those table to get corresponding result you want,
the join condition will be
select <your desire column name>
from Restaurant A,Locations B,Drivers C,Deliveries D
where A.ID = B.RestaurantID
and B.ID = C.LocationID
and C.ID = D.DriverID
Hope this is helpful, fell free to say anything.

Select multiple columns from one table based on MAX of one field in that table

I've been searching for ages on this one and getting lots of answers which seem to answer the questions asked but maybe I'm just not 'getting' it. I can't seem to apply the answers to my particular problem.
I have a query which lists all of our learners and pulls all of their history records (of 4 types) or shows NULL if they don't have any:
select LEARNERS.Learner_ID
, LEARNERS.Firstname
, LEARNERS.Surname
, HISTORY.DateStart
, HISTORY.Notes
from LEARNERS left outer join
HISTORY on
LEARNERS.Learner_ID = HISTORY.Learner_ID
and
HISTORY.Category_ID in (479,480,481,482)
order by LEARNERS.Learner_ID
This works great but I only want to see the latest history for each learner (or NULL if none) so, following some searching on here, I have tried to do this:
select LEARNERS.Learner_ID
, LEARNERS.Firstname
, LEARNERS.Surname
, HISTORY.DateStart
, HISTORY.Notes
from LEARNERS left outer join
(select MAX(DateStart) as LatestHistory, Learner_ID
from HISTORY
Where Category_ID in (479,480,481,482)
group by Learner_ID) as LatestHistoryTable on
LEARNERS.Learner_ID = LatestHistoryTable.Learner_ID
Which works brilliantly and I only get one history per learner (their latest one). However; what I really need, is more info from the history table (e.g. History.Notes, History.Officer, History.DateStart).
I tried to simply add further columns into the subquery:
...
(select MAX(DateStart) as LatestHistory, Learner_ID, Notes, Officer
from HISTORY
Where Category_ID in (479,480,481,482)
group by Learner_ID, Notes, Officer) as LatestHistoryTable
...
But that gave me an error about a text field in the group by clause :o(
I decided that I would just add the HistoryID field and then INNER JOIN that to the HISTORY table outside of the subquery to pull the other fields that I need:
...
(select MAX(DateStart) as LatestHistory, Learner_ID, HistoryID
from HISTORY
Where Category_ID in (479,480,481,482)
group by Learner_ID, HistoryID) as LatestHistoryTable
...
but all that did was to produce multiple lines per learner (similar to my original query) so now I'm stumped.
I'm sure it's a simple thing for an experienced SQL coder as it must come up loads of times but I'm still learning so I'm a bit stuck I'm afraid.
Thanks,
Alan
You can use row_number and desc as below:
select LEARNERS.Learner_ID
, LEARNERS.Firstname
, LEARNERS.Surname
, LatestHistoryTable.DateStart
, LatestHistoryTable.Notes
from LEARNERS left outer join
( select RowN = Row_Number() over(partition by Learner_id order by DateStart Desc), DateStart as LatestHistory,
Learner_ID, Notes, Officer
from HISTORY
Where Category_ID in (479,480,481,482)
) as LatestHistoryTable on
LEARNERS.Learner_ID = LatestHistoryTable.Learner_ID
AND LatestHistoryTable.RowN = 1

MS Access finding customers that have previous year orders and don't have this year orders

This problem is taking too long to solve on my own, so I hope someone can help me.
I have a simple MS Access database: two tables (Customers and Orders) and I need to get those customers, that have placed an order last year and haven't placed one this year.
This database is used to send CD's with information to clients. They subscribe once a year and then they receive a CD each month. What I want is to find those, who have forgotten to subscribe this year.
The table "Customers" is very simple: has fields like "Customer name","address", "e-mail" and so on. It is linked with table "Orders", where all the orders for each customers are stored. It is also pretty basic, has fields like: "Order Status", "Order type","Order year" (here, the year for which the order has been made is stored), "Quantity" and comments.
I tried to create 2 queries, each would gather orders for this and for previous year (by using that "Order year" field), but I cannot figure out what to do next, how to pull out the customers that have order in say 2015 and don't have one in 2016?
SELECT a.*,b.orderyear
FROM (select t1.* from customers t1 inner join orders o on t1.ID=o.CustomerID where o.orderyear=2015) a
LEFT join (select * from orders where orderyear=2016) b
ON a.ID=b.CustomerID
WHERE b.orderyear is null
pulls out the customers that have order in say 2015 and don't have one in 2016
Something like this should work:
SELECT C.* FROM Customers C
INNER JOIN Orders O ON C.Id = O.CustomerID AND O.OrderYear = 2015
LEFT JOIN Orders O2 ON C.Id = O2.CustomerID AND O.OrderYear = 2016
WHERE O2.Id IS NULL
It's going to get the customers that have an order record from 2015, and then it's going to set up an outer join on orders for 2016, which will return NULL for the 2016 orders fields if there is no match. The WHERE clause is then filtering out everything that does have a matching 2016 order.
If you're doing it in Access, I would do it in stages:-
(1) set up a query (Query1) joining customers to Orders and selecting keyID where subscriptYear=2015. Might want to set Unique Values or use GROUP BY to avoid duplicates.
(2) Set up a similar query (Query2) for subscriptYear=2016.
(3) Set up a third query with Query1 left joined to Query 2 on keyID and specify Query2.keyID=NULL

SQL: Summing columns with a similar column in common

I'm extremely new to SQL Sever and so I apologize if the question is worded strange. I am doing a homework assignment, and this is the question:
"A manager wants to know the email address, number or orders, and the total amount of purchases made by each customer. Create a summary query that returns these three items for each customer that has orders."
I have all of the data queried, the problem is when I pull data from each customer, it will show the quantity of items per order, and I need the items to be pooled together into one column. This is my query thus far (again, total noob, please excuse any poor syntax, etc.)
SELECT EmailAddress,
ItemPrice - DiscountAmount * Quantity AS TotalPurchaseAmount,
COUNT(*) AS OrderQty
FROM Customers
JOIN Orders ON Customers.CustomerID = Orders.CustomerID
JOIN OrderItems ON Orders.OrderID = OrderItems.OrderID
GROUP BY Orders.CustomerID,
OrderItems.ItemPrice, OrderItems.DiscountAmount,
OrderItems.Quantity,
Customers.EmailAddress;
The following is a small bit of the result set that I get:
Email Address OrderTotal OrderQty
allan.sherwood#yahoo.com 253.15 2
allan.sherwood#yahoo.com 839.30 2
allan.sherwood#yahoo.com 1208.16 2
barryz#gmail.com 303.79 4
christineb#solarone.com 479.60 2
david.goldstein#hotmail.com 299.00 2
david.goldstein#hotmail.com 489.30 1
david.goldstein#hotmail.com 479.60 1
So as you can see, I have several orders I need to smoosh together into one single row per e-mail, I have looked and looked for an answer but the only thing I can find is how to find duplicates and ignore them, not combine their data. Any help is extremely appreciate, thanks so much for taking the time to read this :) If my question doesn't make sense please let me know so I can clear up any bad wording I may have used!
Just do GROUP BY CustomerID, EmailAddress:
SELECT
c.EmailAddress,
SUM((i.ItemPrice - i.DiscountAmount) * Quantity) AS TotalPurchaseAmount,
COUNT(*) AS OrderQty
FROM Customers c
INNER JOIN Orders o
ON c.CustomerID = o.CustomerID
INNER JOIN OrderItems i
ON o.OrderID = i.OrderID
GROUP BY
c.CustomerID, c.EmailAddress
Additional note: Use aliases for your tables
You need to change your formula and remove columns that you dont want to group by from select query..
for example your query should be something like this
SELECT EmailAddress,
--do your aggregation here
blah AS TotalPurchaseAmount,
COUNT(*) AS OrderQty
FROM Customers
JOIN Orders ON Customers.CustomerID = Orders.CustomerID
JOIN OrderItems ON Orders.OrderID = OrderItems.OrderID
GROUP BY Orders.CustomerID,
Customers.EmailAddress;

Resources