I have 2 tables.
Contacts
ContactID pk
EmailAddress
FirstName
LastName
Address
Orders
OrderID pk
ContactID fk
I want to get the number or orders for each email address in Contacts like below
select
Contacts.EmailAddress,
count(distinct Orders.OrderID) AS NumOrders
from
Contacts inner join Orders on Contacts.ContactID = Orders.ContactID
group by
Contacts.EmailAddress
Problem is, I also want the first name, last name, address. But I can't group by those because each email address in Contacts could have a different first name, lastname or address associated with it.
ie:
myname#email.com, Fred, Jackson, 123 Main St
myname#email.com, Bob, Smith, 456 Spruce St.
How can I change my query so that I can get the first name, last name and address for the most recent entry made in Contacts for that email address?
Thanks in advance!
My first thought would be to use windowed functions.
SELECT EmailAddress,
FirstName,
Lastname,
[Address],
EmailOrderCount
FROM (SELECT c.EmailAddress,
c.FirstName,
c.LastName,
c.[Address],
COUNT(o.OrderID) OVER (PARTITION BY c.EmailAddress) EmailOrderCount,
ROW_NUMBER() OVER (PARTITION BY c.EmailAddress ORDER BY c.ContactID DESC) Rn
FROM Contacts c
JOIN Orders o ON c.ContactID = o.ContactID
) t
WHERE Rn = 1
Demo
another way would be to use CROSS APPLY to append the top 1 contact record to the summary rows.
SELECT c.EmailAddress,
COUNT(o.OrderID) NumOrders,
ca.FirstName,
ca.LastName,
ca.[Address]
FROM Contacts c
INNER JOIN Orders ON c.ContactId = o.ContactID
CROSS APPLY (
SELECT TOP 1
FirstName,
Lastname,
[Address]
FROM Contacts c2
WHERE c2.EmailAddress = c.EmailAddress
ORDER BY c2.ContactID DESC) ca
GROUP BY c.EmailAddress,
ca.FirstName,
ca.LastName,
ca.[Address]
Try this:
select
Contacts.Name,
Contacts.FirstName,
Contacts.LastName
Contacts.EmailAddress,
count(distinct Orders.OrderID) AS NumOrders
from
(
select max(ContactID) as ContactID,
EmailAddress
from Contacts
group by EmailAddress
) MinContactForEachEMailAddress
inner join
Contacts
on MinContactForEachEMailAddress.ContactID = Contacts.ContactID
inner join
Orders
on Contacts.ContactID = Orders.ContactID
group by
Contacts.EmailAddress
Another way to get what you want is using a CTE and taking the "maximum" row by using ROW_NUMBER.
;WITH CTE AS (
SELECT C.ContactId, C.Name, C.FirstName, C.LastName, C.EmailAddress,
ROW_NUMBER() OVER (PARTITION BY EmailAddress ORDER BY ContactId DESC) RowNo
FROM Contact C
)
SELECT CTE.*, COUNT(o.OrderID) OVER (PARTITION BY CTE.EmailAddress) Cnt
FROM CTE
JOIN Orders O on CTE.ContactID = O.ContactID
-- select the "maximum" row
WHERE CTE.RowNo = 1
An easy way to do this is to make your original query a subquery and select from it. I'm making a slight change, because it's a better practice to group by your primary key than your email address. (Is it a safe bet that each contact has just one email address, and that the basic intent is to group by person?) If so, try this:
SELECT DISTINCT c.EmailAddress, c.FirstName, c.LastName, c.Address, sub.NumOrders
FROM
(
select
Contacts.ContactID
count(distinct Orders.OrderID) AS NumOrders
from
Contacts inner join Orders on Contacts.ContactID = Orders.ContactID
group by
Contacts.ContactID
) sub
JOIN Contacts c
ON sub.ContactID = c.ContactID
If you really need to group by email address instead, then change the above subquery to your original query and change c.EmailAddress to sub.EmailAddress. Of course you may order the SELECT fields however best suits you.
Edit follows:
The ContactID must be a sequence number and you can continually put the same person in the table. So if you add the DISTINCT keyword in the outer query I believe that will give you what you need.
Related
I have 3 tables in SQL Server:
Sales (customerId)
Customer (customerId, personId)
Person (personId, firstName, lastName)
and I need to return the top 10 customers.
I used this query:
SELECT TOP 10
CustomerID, COUNT(CustomerID)
FROM
Sales
GROUP BY
(CustomerID)
ORDER BY
COUNT(CustomerID) DESC
The query currently returns only the customerId and count, but I also need to return the firstName and lastName of these customers from the Person table.
I know I need to reach the firstName and lastName by correlating between Sales.customerId and Customer.customerId, and from Customer.personId to get the Person.personId.
My question is whether I need to use an inner join or union, and how to use either of them to get the firstName and lastName of these customers
Union is mostly used for disjoint sets. To achieve your target, u can go with inner-join.
If you want to use joins, then here is the query which works similarly to your requirement.
SELECT TOP 10 S.CustomerID, P.FirstName,P.LastName, count(*)
FROM Sales S
INNER JOIN Customer C on S.CustomerId=C.CustomerId
INNER JOIN Person P on C.PersonId = P.PersonId
GROUP BY (S.CustomerID, P.FirstName,P.LastName)
ORDER BY count(*) DESC
You need use inner join like this :
SELECT TOP 10 S.CustomerID
, P.FirstName
, P.LastName
, COUNT (1) AS CountOfCustomer -- this is equal count(*)
FROM Sales S
INNER JOIN Customer C ON S.CustomerId = C.CustomerId
INNER JOIN Person P ON C.PersonId = P.PersonId
GROUP BY S.CustomerID, P.FirstName, P.LastName
ORDER BY 4 DESC; -- this is equal order by count(*)
My Sales.Customer table includes the following columns:
CustomerID (integer)
FirstName (nvarchar(50))
LastName (nvarchar(50))
My Sales.SalesOrder table includes the following columns:
SalesOrderNumber (integer)
OrderDate (date)
CustomerID (integer)
Amount (money)
Some customers have placed multiple orders over a period of years. I've written the following query to retrieve the last date on which each customer placed an order:
SELECT c.CustomerID, c.FirstName, c.LastName,
-- correlated subquery goes here
AS LastOrderDate
FROM Sales.Customer AS c;
Why does my subquery not complete my actual query? Am I missing something or should it be something different?
(SELECT MAX(o.OrderDate)
FROM Sales.SalesOrder AS o
WHERE o.CustomerID = c.CustomerID)
I considered whether I might be over-complicating my solution so maybe this should work instead?
(SELECT MAX(c.OrderDate)
FROM Sales.Customer AS c)
You should be able to rewrite it as a CTE like this.
;WITH LastOrderDate AS
(
SELECT CustomerID,MAX(OrderDate) AS LastOrderDate
FROM Sales.SalesOrder
GROUP BY CustomerID
)
SELECT c.CustomerID, c.FirstName, c.LastName, LastOrderDate
AS LastOrderDate
FROM Sales.Customer AS c
LEFT JOIN LastOrderDate l
ON c.CustomerID = l.CustomerID;
I have an Employee table like this
And a second table for EmployeeComments with the EmployeeID as foreign key:
I would like to query the employees with their comments in the following format:
select Name, Comment
from Employee emp
left join EmployeeComments empC on empC.EmployeeID = emp.ID
I would like the results to be like:
I have already looked at Pivot, but it doesn't resolve my issue
Use window function:
select case when row_number() over(partition by emp.name order by empC.ID) = 1
then Name
else '' end as Name,
Comment
from Employee emp
left join EmployeeComments empC On empC.EmployeeID = emp.ID
This might give you some kind of order in your result window at least
WITH cte AS(
SELECT emp.Name ,
empC.Comment,
RANK() OVER (ORDER BY emp.Name) NameOrder,
ROW_NUMBER() OVER (PARTITION BY emp.Name ORDER BY empC.ID) RN
FROM Employee emp
LEFT JOIN EmployeeComments empC ON empC.EmployeeID = emp.ID
)
SELECT
Name = (CASE WHEN RN = 1 THEN Name ELSE '' END),
Comment
FROM
cte
ORDER BY
NameOrder,
RN
"use Cross Join:"
Query:
select case t.cnt
when 1 then
coalesce(t.Name,' ')
end as Name,t.comment
from
(
select t1.Name,t2.comment,row_number()
over(partition by t1.name order by t1.Name)
as cnt
from
EmployeeComments t1
cross join
Employee t2
where t1.ID=t2.Employeeid
)t
I need to query the addresses of top 500 customers (best buyers). Many companies have multiple addresses.
The tables with data:
CustomerInfo
CustomerAddress
TransactionInfo
TransactionElements
My query looks this way:
select Customername, CustomerStreet --etc
from CustomerInfo
join CustomerAddress on CustomerID = Add_CustID
join TransactionInfo on Trn_CustID = CustomerID
JOIN TransactionElements ON Trn_CustID = TrE_CustID
GROUP BY CustomerName, CustomerStreet --etc
ORDER BY SUM (TrE_TranValue) DESC
It returns multiple addresses of a single company, I need just one.
One approach would be to use a CTE (Common Table Expression) if you're on SQL Server 2005 and newer (you aren't specific enough in that regard).
With this CTE, you can partition your data by some criteria - i.e. your CustomerId - and have SQL Server number all your rows starting at 1 for each of those "partitions", ordered by some criteria.
So try something like this:
;WITH CustomerAndAddress AS
(
SELECT
c.Customername, ca.CustomerStreet ,
ROW_NUMBER() OVER(PARTITION BY c.CustomerId ORDER BY ca.AddressID DESC) AS 'RowNum'
FROM
dbo.CustomerInfo c
INNER JOIN
dbo.CustomerAddress ca ON c.CustomerID = ca.Add_CustID
WHERE
......
)
SELECT
Customername, CustomerStreet
FROM
CustomerAndAddress
WHERE
RowNum = 1
Here, I am selecting only the "first" entry for each "partition" (i.e. for each CustomerId) - ordered by some criteria (I just arbitrarily picked AddressID from the address - adapt as needed) you need to define in your CTE.
Does that approach what you're looking for??
Something like this will work from sqlserver 2005+. I also suggest adding some aliasses to your tables and refer to those.
select CI.Customername, CA.CustomerStreet --etc
from CustomerInfo CI
cross apply
(select top 1 Customername, CustomerStreet --etc
from CustomerAddress where CustomerID = CI.Add_CustID) CA
join TransactionInfo TI on TI.Trn_CustID = CI.CustomerID
JOIN TransactionElements ON CI.CustomerID = TE.TrE_CustID
GROUP BY CustomerName, CustomerStreet --etc
ORDER BY SUM (TrE_TranValue) DESC
If CustomerInfo has many CustomerAddress, this query will make CustomerInfo returned for each CustomerAddress (a cartesian product) :
join CustomerAddress on CustomerID = Add_CustID
So if you need to get only one address you have to add conditions needed to choose single CustomerAddress :
join CustomerAddress on CustomerID = Add_CustID where <conditions>
In my DB I have Customers, who make Purchases, and those purchases are then associated with an employee who assisted with that purchase. I have written a query below that will provide me with a list of customers with how many total purchases they have made, their first purchase and the last purchase. I also want the employee name associated with the last purchase?
Customer
-cstId
-cstName
Purchase
-cstId
-soldDate
-empId
Employee
-FirstName
-LastName
-empId
SELECT customer.cstName, MAX(purchase.soldDate) AS [Last Purchase], MIN(purchase.soldDate) AS [First Purhcase], COUNT(invTruck.invId)
AS [Total Purchases]
FROM customer INNER JOIN
purchase ON customer.cstId = purchase.cstId
INNER JOIN
employee ON purchase.empId = employee.empId
GROUP BY customer.cstName
Can you use a stored procedure? I usually fall back to memory tables for situations like this.
Declare #tblCust TABLE (
cstid int null,
cstName varchar(50) null,
lastpurchase datetime null,
firstpurchase datetime null,
empid varchar(50) null
)
Insert into #tblCust(cstid, cstname, lastpurchase, firstpurchase)
SELECT purchase.cstid, customer.cstName, MAX(purchase.soldDate) AS [Last Purchase],
MIN(purchase.soldDate) AS [First Purhcase]
FROM customer INNER JOIN
purchase ON customer.cstId = purchase.cstId
GROUP BY purchase.cstId, customer.cstName
Update t set EmpId = p.EmpId
From #tblCust t
INNER JOIN Purchase p ON t.cstId = p.cstid and t.LastPurchase = p.soldDate
You now have the employee id on a temp table that you can return your data from, or join to any other tables you may need.
I'm assuming you have some primary key on purchase table. I named it "purchaseID":
SELECT customer.cstName,
MAX(purchase.soldDate) AS [Last Purchase],
MIN(purchase.soldDate) AS [First Purhcase],
COUNT(invTruck.invId) AS [Total Purchases],
LastPurchase.empID AS [Last Purchase Employee]
FROM customer INNER JOIN
purchase ON customer.cstId = purchase.cstId
INNER JOIN
employee ON purchase.empId = employee.empId
CROSS APPLY (
SELECT TOP 1 *
FROM purchase px
WHERE px.purchaseID = purchase.purchaseID
ORDER BY px.soldDate DESC) AS LastPurchase
GROUP BY customer.cstName,
LastPurchase.empID
What the CROSS APPLY does is runs the enclosed select statement on every record, utilizing the WHERE criteria also inside. It behaves similarly to an INNER JOIN relative to an OUTER APPLY which behaves similarly a LEFT JOIN.
you could use APPLY: http://msdn.microsoft.com/library/ms175156(v=sql.105).aspx
use cross apply to get the list of purchases and the respective employee, with TOP 1 and sort by soldDate desc
example:
CROSS APPLY (
select top 1 p.empId
from purchase p
where p.cstId = customer.cstId
order by soldDate desc
) o (emp)
and add o.emp to your select
i'm not 100% sure that the syntax is 100% perfect but the idea is there :P
You can just extend it with a simple SCALAR SUBQUERY
SELECT
customer.cstName,
MAX(purchase.soldDate) AS [Last Purchase],
MIN(purchase.soldDate) AS [First Purhcase],
COUNT(invTruck.invId) AS [Total Purchases],
(SELECT TOP(1) e.lastname
FROM purchase p
INNER JOIN employee e ON p.empId = e.empId
WHERE customer.cstId = p.cstId
ORDER BY p.soldDate DESC) lastPurchaseEmployee
FROM customer
INNER JOIN purchase
ON customer.cstId = purchase.cstId
INNER JOIN employee
ON purchase.empId = employee.empId
GROUP BY
customer.cstId, customer.cstName