SELECT region, person, sum(dollars) as thousands
FROM sales
GROUP BY region, person
ORDER BY region, sum(dollars) desc
The SQL above produces a complete list of sales people per region like this
region person thousands
canada mike smith $114
canada joe blog $76
canada pete dodd $45
usa john doe $253
usa jane smyth $120
europe pieter tsu $546
europ mike lee $520
If I'm only interested in showing the top salesperson per region (as below), how can I best do that?
region person thousands
canada mike smith $114
usa john doe $253
europe pieter tsu $546
I've done something like burnall suggested. I wasn't getting much love with the "top 1 with ties" part, so I made the whole thing a subquery and chose rows where ranking = 1.
select *
from
(
select region,
person,
rank() over(partition by region order by sum(dollars) desc) as ranking
from sales
group by region,
person
) temp
where ranking = 1
Note that this also works for ties since rank() seems to place the same ranking on sums that are equal.
Using Sql Server 2005+ you could do this using a ROW_NUMBER()
Have a look at this full example.
DECLARE #sales TABLE(
region VARCHAR(50),
person VARCHAR(50),
Sales FLOAT
)
INSERT INTO #sales SELECT 'canada','mike smith',1
INSERT INTO #sales SELECT 'canada','mike smith',1
INSERT INTO #sales SELECT 'canada','mike smith',1
INSERT INTO #sales SELECT 'canada','mike smith',1
INSERT INTO #sales SELECT 'canada','joe blog',1
INSERT INTO #sales SELECT 'canada','joe blog',1
INSERT INTO #sales SELECT 'canada','pete dodd',1
INSERT INTO #sales SELECT 'usa','john doe',1
INSERT INTO #sales SELECT 'usa','john doe',1
INSERT INTO #sales SELECT 'usa','jane smyth',1
INSERT INTO #sales SELECT 'europe','pieter tsu',1
INSERT INTO #sales SELECT 'europe','pieter tsu',1
INSERT INTO #sales SELECT 'europe','mike lee',1
;WITH Counts AS(
SELECT region,
person,
count(*) as thousands
FROM #sales
GROUP BY region,
person
), CountVals AS(
SELECT *,
ROW_NUMBER() OVER(PARTITION BY region ORDER BY thousands DESC) ROWID
FROM Counts
)
SELECT *
FROM CountVals
WHERE ROWID = 1
In SQL Server 2005 and above use ROW_NUMBER with PARTITION BY. Following should work (not tested, and probably can be shortened):
WITH total_sales
AS (SELECT region, person, count(*) as thousands
FROM sales
GROUP BY region, person
ORDER BY region, count(*) desc
)
, ranked_sales
AS (SELECT region, person, thousands,
ROW_NUMBER() OVER (PARTITION BY region ORDER BY thousands DESC, person) AS region_rank
FROM total_sales
)
SELECT region, person, thousands
FROM ranked_sales
WHERE region_rank = 1
First of all I do not understand why count(*) is in $.
My solution is similar to existing, but shorter and I believe faster
select top 1 with ties region, person, rank() over(partition by region order by count(*) desc)
from sales
group by region, person
order by 3
This isn't too difficult. This query will do exactly what you want.
select distinct region,
(select top 1 person
from Sales s2 where s2.region = s1.region
group by person
order by SUM(dollars) desc) as person,
(select top 1 SUM(dollars)
from Sales s2 where s2.region = s1.region
group by person
order by SUM(dollars) desc) as thousands
from sales s1
You can use the max() aggregate. It's probably less efficient than the other alternatives because you'll be doing group by twice
SELECT region,person,max(thousands) FROM
(SELECT region, person, count(*) as thousands
FROM sales
GROUP BY region, person) tmp
GROUP BY region, person
ORDER BY region, max(thousands) desc
to find top 5 salesperson by sales for each region
select *
from
(
select region,
[Customer Name],
rank() over(partition by region order by sum(sales) desc) as ranking
from Orders
group by region, [Customer Name]
) temp
where ranking between 1 and 5
Related
I have two table with columns as mentioned below:
CUSTOMER table as DIM_CUSTOMER:
ID_CUSTOMER, CUSTOMER_NAME
TRANSACTION table as FACT_TRANSACTION:
ID_CUSTOMER, DATE, TOTAL_PRICE, QUANTITY
Problem statement is to
Find top 100 customers and their average spend, average quantity by each year. Also find the percentage of change in their spend.
My approach:
SELECT TOP 100
YEAR(FT.DATE) AS [YEAR],
FT.ID_CUSTOMER AS [CUSTOMER NAME],
FT.TOTAL_PRICE AS [TOTAL AMT],
AVG(FT.TOTAL_PRICE) AS [AVG SPEND],
AVG(FT.QUANTITY) AS [AVG QUANTITY]
FROM
FACT_TRANSACTIONS FT
INNER JOIN
DIM_CUSTOMER DC ON FT.ID_CUSTOMER = DC.ID_CUSTOMER
GROUP BY
FT.DATE, FT.ID_CUSTOMER, FT.TOTAL_PRICE
ORDER BY
3 DESC
This is resulting in the top 100 customers based on their usage.
Now I need to determine the percentage change in their spend YEAR wise.
How can I do that? Probably using PIVOT option herein will help, but I'm unsure.
You can try using LAG in order to access the previous [AVG SPEND] for the current row. The idea is to group the data for each [CUSTOMER NAME] using PARTITION BY and then to order the data by the [YEAR]. The function will give us the previous result and we can calculated easily the difference.
Try something like this:
SELECT TOP 100
YEAR(FT.DATE) AS [YEAR],
FT.ID_CUSTOMER AS [CUSTOMER NAME],
FT.TOTAL_PRICE AS [TOTAL AMT],
AVG(FT.TOTAL_PRICE) AS [AVG SPEND],
AVG(FT.QUANTITY) AS [AVG QUANTITY]
INTO #DataSource
FROM
FACT_TRANSACTIONS FT
INNER JOIN
DIM_CUSTOMER DC ON FT.ID_CUSTOMER = DC.ID_CUSTOMER
GROUP BY
YEAR(FT.DATE), FT.ID_CUSTOMER, FT.TOTAL_PRICE
ORDER BY
[AVG SPEND] DESC
SELECT *
,[AVG SPEND] - LAG([AVG SPEND], 1, 0) OVER (PARTITION BY [CUSTOMER NAME] ORDER BY [YEAR])
FROM #DataSource
Note, that:
the function requires SQL Server 2012+
you can change the partitioning and ordering as you like in order to satisfy your real goal (for example you can use ORDER BY [YEAR] DESC
you can use the LEAD function in order to access the next value within the group if you want to calculated difference in advace
I materialized the data in temporary table, but you can use table variable or whatever you are using
Can you please try following SQL CTE query ?
;with topcustomers as(
SELECT distinct top 100
ID_CUSTOMER,
SUM(TOTAL_PRICE) over (partition by ID_CUSTOMER) as TotalSPEND
FROM FACT_TRANSACTION
order by TotalSPEND desc
), cte as (
SELECT
distinct
t.ID_CUSTOMER, YEAR(t.DATE) [YEAR], TotalSPEND,
AVG(t.QUANTITY * 1.0) over (partition by t.ID_CUSTOMER, YEAR(t.DATE)) as AverageQUANTITY,
AVG(t.TOTAL_PRICE * 1.0) over (partition by t.ID_CUSTOMER, YEAR(t.DATE)) as AverageSPEND
FROM FACT_TRANSACTION t
INNER JOIN topcustomers c on c.ID_CUSTOMER = t.ID_CUSTOMER
)
select
*,
( AverageSPEND - lag(AverageSPEND,1) over (partition by ID_CUSTOMER order by [YEAR]) ) * 100.0 / AverageSPEND as [%Change]
from cte
I have two tables:
Customer which has an Id column representing the customer Id.
CustomerDonation that contains CustomerId (FK), Amount and DatePayed
I'd like have all the customers together with their latest donation and the amount of that donation.
I am receiving duplicate values on my query so I will not paste it here.
You could also use the WITH TIES option
Select Top 1 With Ties *
From YourTable
Order By Row_Number() over (Partition By CustomerId Order By DatePayed Desc)
WITH
SortedDonation AS
(
SELECT
ROW_NUMBER() OVER (PARTITION BY CustomerId ORDER BY DatePayed DESC) AS SeqID,
*
FROM
CustomerDonation
)
SELECT
*
FROM
Customer
LEFT JOIN
SortedDonation
ON SortedDonation.CustomerId = Customer.Id
AND SortedDonation.SeqId = 1
If the same customer can make multiple donations with the same DatePayed, then this will arbitrarily pick just one of them.
If you add additional fields to the ORDER BY you can deterministically pick which one you want.
Or, if you want all of them use DENSE_RANK() instead of ROW_NUMBER()
Use Row_Number() Analytic function .
Select * from (
Select customerId,Amount,DatePayed, row_number() over (partition by CustomerId order by DatePayed desc) as rowN)
as tab where rowN = 1
You only need the CustomerDonation table for this. You can join with the Customer table if you want other information of the customer.
WITH cte AS (
SELECT
CustomerId
, MAX(DatePayed) AS LastDate
FROM
CustomerDonation
)
SELECT
cd.CustomerId
, cd.Amount
, cd.DatePayed
FROM
CustomerDonation cd
JOIN cte ON cd.CustomerId = cte.CustomerId
AND cd.DatePayed = cte.LastDate
I have 36 Sales tables each referred to one store:
st1.dbo.Sales
st2.dbo.Sales
...
st35.dbo.Sales
st36.dbo.Sales
Each record has the following key columns:
UserName, PostalCode, Location, Country, InvoiceAmount, ItemsCount, StoreID
Here is SQLFiddle
I need to copy into Customers table all Username (and their details) that are not already present into Customers
in case of duplicated it is required to use the fields of record where InvoiceAmount is MAX
I tried to build a query but looks too complicated and it is also wrong because in CROSS APPLY should consider the full list of Sales Tables
INSERT INTO Customers (.....)
SELECT distinct
d.UserName,
w.postalCode,
w.location,
W.country,
max(w.invoiceamount) invoiceamount,
max(w.itemscount) itemscount,
w.storeID
FROM
(SELECT * FROM st1.dbo.Sales
UNION
SELECT * FROM st2.dbo.Sales
UNION
...
SELECT * FROM st36.dbo.Sales) d
LEFT JOIN
G.dbo.Customers s ON d.Username = s.UserName
CROSS APPLY
(SELECT TOP (1) *
FROM s.dbo.[Sales]
WHERE d.Username=w.Username
ORDER BY InvoiceAmount DESC) w
WHERE
s.UserName IS NULL
AND d.username IS NOT NULL
GROUP BY
d.UserName, w.postalCode, w.location,
w.country, w.storeID
Can somebody please give some hints?
As a basic SQL query, I'd create a row_number in the inner subquery and then join to customers and then isolated the max invoice number for each customer not in the customer table.
INSERT INTO Customers (.....)
SELECT w.UserName,
w.postalCode,
w.location,
w.country,
w.invoiceamount,
w.itemscount,
w.storeID
FROM (select d.*,
row_number() over(partition by d.Username order by d.invoiceamount desc) rownumber
from (SELECT *
FROM st1.dbo.Sales
UNION
SELECT *
FROM st2.dbo.Sales
UNION
...
SELECT *
FROM st36.dbo.Sales
) d
LEFT JOIN G.dbo.Customers s
ON d.Username = s.UserName
WHERE s.UserName IS NULL
AND d.username IS NOT NULL
) w
where w.rownumber = 1
Using your fiddle this will select distinct usernames rows with max invoiceamount
with d as(
SELECT * FROM Sales
UNION
SELECT * FROM Sales2
)
select *
from ( select *,
rn = row_number() over(partition by Username order by invoiceamount desc)
from d) dd
where rn=1;
step 1 - use cte .
select username , invoiceamount ,itemscount from Sales
UNION all
select user name , invoiceamount ,itemscount from Sales
.....
...
step 2
next cte use group by and get max invoiceamount ,itemscount for user of last result set.
,cte2 as (
select user name , max (invoiceamount) as invoiceamount ,max(itemscount) as itemscount from cte)
step3
use left join with user table and find missing record and itemscount invoiceamount
I have a fact database from which I want to make a trendline based on top 10 items based on sum quantity for each item per year.
I've done the following, but it does for example select more than 10 entities for my year 2007:
select TOP 10 sum(Quantity) as Quantity,DIM_Time.Year, DIM_Item.Name as Name
from Fact_Purchase
join DIM_Item on DIM_Item.BKey_ItemId = Fact_Purchase.DIM_Item
join DIM_Time on DIM_Time.ID = Fact_Purchase.DIM_Time_DeliveryDate
where Fact_Purchase.DIM_Company = 2 and DIM_Time.ID = FACT_Purchase.DIM_Time_DeliveryDate
Group by dim_item.Name, DIM_Time.Year
Order by Quantity DESC
How do I select top 10 items with the highest quantity through all my years, with only 10 top entities for each year?
As you can guess, the company is individual, and Is going to be a parameter in my report
I think this is what you're going for. My apologies if I messed up on translating your tables across.
select *
from (
select DIM_Time.[Year], dim_item.Name, SUM(Quantity) Quantity, RANK() OVER (PARTITION BY DIM_Time.[Year] ORDER BY SUM(Quantity) DESC) salesrank
from Fact_Purchase
join DIM_Item on DIM_Item.BKey_ItemId = Fact_Purchase.DIM_Item
join DIM_Time on DIM_Time.ID = Fact_Purchase.DIM_Time_DeliveryDate
where Fact_Purchase.DIM_Company = 2 and DIM_Time.ID = FACT_Purchase.DIM_Time_DeliveryDate
group by dim_item.Name, DIM_Time.[Year]
) tbl
where salesrank <= 10
order by [Year], salesrank
The subquery groups by name/year, and the RANK() OVER part sets up a sort of row index that increments by SUM(Quantity) and restarts for each Year. From there you just have to filter out anything with a salesrank (index) that's over 10.
SELECT
_year,
Name,
_SUM,
RANK_iD
FROM
(
SELECT
_year,
Name,
_SUM,
DENSE_RANK()OVER(PARTITION BY _year,_Month ORDER BY _SUM DESC) AS RANK_iD
FROM(
Select
DIM_Time AS _year,
DIM_Item as Name,
sum(Quantity) AS _SUM
from
#ABC
GROUP BY
_year,
Name
)A
)B
WHERE RANK_iD<=10
A table consists of employee name, address, phone, department, salary:
How to get the highest paid employee row from each department?
I tried with
select dept, max(salary) from employee group by dept
but it gives only two columns. But I want to select an entire row. How to do it?
Alternatively how to add more columns to the result?
(I am using SQL Server 2008)
You simply need to join the query you currently have back to the employee table to get the full employee information.
select e.*
from employee e
inner join (select dept, max(salary) ms from employee group by dept) m
on e.dept = m.dept and e.salary = m.ms
SELECT name,
address,
phone,
department,
salary,
dept
FROM (SELECT name,
address,
phone,
department,
salary,
dept,
row_number() OVER(PARTITION BY dept ORDER BY salary DESC) AS rn
FROM employee) AS e
WHERE e.rn = 1
Using row_number() will give you one row if there is a tie for the highest salary. If you want all the highest salaries for each department you should use rank() instead.
SELECT name,
address,
phone,
department,
salary,
dept
FROM (SELECT name,
address,
phone,
department,
salary,
dept,
rank() OVER(PARTITION BY dept ORDER BY salary DESC) AS rn
FROM employee) AS e
WHERE e.rn = 1
Something like this?
select * from employee where salary = (select max(salary) from employee)
with cte as (
select *, rank() over (partition by dept order by salary desc) as [r]
from employees
)
select * from cte where [r] = 1;
select * from employee
where salary in
(select max(salary) from employee group by dept);
If situation like you have both employee details and department in same table and you have to find the highest paid employee from each department like given below
EmployeeId EmployeeName Department Salary
1 Neeraj Dot Net 45000
2 Ankit Java 5000
3 Akshay Java 6000
4 Ramesh Dot Net 7600
5 Vikas Java 4000
7 Neha Php 8500
8 Shivika Php 4500
9 Tarun Dot Net 9500
Then you can solve it by using below solutions
Solution - 1
SELECT t.EmployeeName,t.Department,t.Salary
FROM(SELECT MAX(Salary) AS TotalSalary,Department FROM Employee GROUP BY Department)
AS TempNew Inner Join Employee t ON TempNew.Department=t.Department
and TempNew.TotalSalary=t.Salary
ORDER BY t.Department ASC
Solution -2
;WITH EmployeeDetails AS (
SELECT EmployeeName, Department, DENSE_RANK() OVER(PARTITION BY Department
ORDER BY Salary DESC) AS SalaryRank, Salary FROM Employee )
SELECT EmployeeName, Department, Salary FROM EmployeeDetails WHERE SalaryRank=1
OUTPUT
EmployeeName Department Salary
Neeraj Dot Net 45000
Akshay Java 6000
Neha Php 8500