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(*)
Related
I have 2 tables in sql server:
Employee (Job Title, Hire Date)
Person (FirstName, LastName)
And I need to return the E.JobTitle, E.HireDate,P.FirstName ,P.LastName along with the how many employees have the same job title.
so I used this query:
SELECT E.JobTitle,
E.HireDate,
P.FirstName,
P.LastName,
COUNT(E.JobTitle)
FROM AdventureWorks2019.HumanResources.Employee E
JOIN AdventureWorks2019.Person.Person P ON E.BusinessEntityID = P.BusinessEntityID
GROUP BY E.JobTitle,
E.HireDate,
P.FirstName,
P.LastName;
the problem is that the query returns 1 per the count, while I'd expect to get per each row the count of the num employees with that job title.
my question is how can I get the correct count?
As mentioned by #Larnu in the comments, your issue is that you're grouping by LastName also, so you are going to get a count of 1 per LastName.
You need a windowed count with OVER, not an aggregated one with GROUP BY
No extra joins or grouping needed.
SELECT E.JobTitle,
E.HireDate,
P.FirstName,
P.LastName,
COUNT(*) OVER (PARTITION BY E.JobTitle)
FROM AdventureWorks2019.HumanResources.Employee E
JOIN AdventureWorks2019.Person.Person P ON E.BusinessEntityID = P.BusinessEntityID;
You should count the number of employee for each job title first in a subquery or cte.
SELECT
JobTitle,
COUNT(*) AS Count
FROM HumanResources.Employee
GROUP BY JobTitle
Then you can join the subquery or the cte with your original query.
WITH cte
AS
(
SELECT
JobTitle,
COUNT(*) AS Count
FROM HumanResources.Employee
GROUP BY JobTitle
)
SELECT
E.JobTitle,
E.HireDate,
P.FirstName,
P.LastName,
cte.Count
FROM
HumanResources.Employee E
INNER JOIN Person.Person P ON P.BusinessEntityID = E.BusinessEntityID
INNER JOIN cte on cte.JobTitle = E.JobTitle
ORDER BY
E.JobTitle,
P.LastName,
P.FirstName
;
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.
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
I have three tables, Customers, Sales and Products.
Sales links a CustomerID with a ProductID and has a SalesPrice.
select Products.Category, AVG(SalePrice) from Sales
inner join Products on Products.ProductID = Sales.ProductID
group by Products.Category
This lets me see the average price for all sales by category. However, I only want to include customers that have more than 3 sales records or more in the DB.
I am not sure the best way, or any way, to go about this. Ideas?
You haven't mentioned the customer data anywhere so I'll assume it's in the Sales table
You need to filter and restrict the Sales table first to the customers with more the 3 sales, then join to get product category and get the average across categories
select
Products.Category, AVG(SalePrice)
from
(SELECT ProductID, SalePrice FROM Sales GROUP BY CustomerID HAVING COUNT(*) > 3) S
inner join
Products on Products.ProductID = S.ProductID
group by
Products.Category
I'd try the following:
select Products.Category, AVG(SalePrice) from Sales s
inner join Products on Products.ProductID = s.ProductID
where
(Select Count(*) From Sales Where CustomerID = s.CustomerID) > 3
group by Products.Category
I'd create a pseudo-table of "big customer IDs" with a select, and then join it to your query to limit the results:
SELECT Products.Category, AVG(SalePrice) FROM Sales
INNER JOIN Products ON Products.ProductID = Sales.ProductID
INNER JOIN (
SELECT CustomerID FROM Sales WHERE COUNT(CustomerID) >= 3 GROUP BY CustomerID
) BigCustomer ON Sales.CustomerID = BigCustomer.CustomerID
GROUP BY Products.Category
Too lazy to test this out though, so let me know if it works ;o)
Another way
;WITH FilteredSales AS
(
SELECT Products.Category, Sales.SalesPrice, COUNT(Sales.CustomerId) OVER(PARTITION BY Sales.CustomerId) AS SaleCount
FROM Sales
INNER JOIN Products ON Products.ProductID = Sales.ProductID
)
select Category, AVG(SalePrice)
from FilteredSales
WHERE SaleCount > 3
group by Category