Count and Row_Number - sql-server

I want to get the top 5 Zipcodes for each Store with the highest Customers in them (zipcodes).
Please find below my query:
SELECT T.[Store], T.[ZipCode], Count(T.[Customer])
FROM ( SELECT T.[Store], T.[ZipCode],
Count(T.[Customer]) row_number() over (Partition By T.[StoreGitanjali] Order By Count (T.[Customer]) desc) as RN
FROM [Marketing].[dbo].[Poscus] as T
Group By T.[StoreGitanjali], T.[ZipCode]) as T
where T.RN <=5
Group By T.[StoreGitanjali], T.[ZipCode]
Please let me know how to use Count here in this scenario.
Thank you!

Example
CREATE TABLE #t
(
ID INT IDENTITY(1,1),
Customer NVARCHAR(3),
Store NVARCHAR(5),
ZIP INT
)
INSERT INTO #t VALUES('a', 'XYZ', 1234)
,('b', 'XYZ', 1234)
,('c', 'PQR', 1231)
,('d', 'PQR', 1231)
,('e', 'PQR', 1231)
,('f', 'XYZ', 1232)
,('g', 'XYZ', 1232)
,('h', 'XYZ', 1232)
,('i', 'PQR', 1236)
,('j', 'PQR', 1236)
,('k', 'LMN', 1237)
SELECT * FROM #t
The solution is, Set WHERE part < 2 according to your requirement.
SELECT TotalCustomer, Store, ZIP, Part FROM (
SELECT
COUNT(1) AS TotalCustomer,
Store,
ZIP,
ROW_NUMBER() OVER (PARTITION BY Store ORDER BY Store) AS Part
FROM #t
GROUP BY Store, ZIP
) t
WHERE Part < 2
ORDER BY Part

;WITH CTE
AS(
SELECT Store
,Zip
,COUNT(DISTINCT Customer) AS CustCount
FROM #t
GROUP BY Store,Zip
--ORDER BY Store,Zip
)
SELECT A.*
FROM(
SELECT *
--,DENSE_RANK() OVER(PARTITION BY Store ORDER BY CustCount DESC) AS DenRank
,ROW_NUMBER() OVER(PARTITION BY Store ORDER BY CustCount DESC) AS DenRank
FROM CTE
--ORDER BY Store,Zip
) AS A
WHERE A.DenRank <= 2

Related

TSQL: Group by one column, count all rows and keep value on second column based on row_number

I have a query that returns an Id, a Name and the Row_Number() based on some rules.
The query looks like that
SELECT
tm.id AS Id,
pn.Name AS Name,
ROW_NUMBER() OVER(PARTITION BY tm.id ORDER BY tm.CreatedDate ASC) AS Row
FROM
#tempTable AS tm
LEFT JOIN
names pn WITH (NOLOCK) ON tm.nameId = pn.NameId
WHERE ....
The output of the above query looks like the table below with the dummy data
CREATE TABLE people
(
id int,
name varchar(55),
row int
);
INSERT INTO people
VALUES (1, 'John', 1), (1, 'John', 2), (2, 'Mary', 1),
(3, 'Jeff', 1), (4, 'Bill', 1), (4, 'Bill', 2),
(4, 'Bill', 3), (4, 'Billy', 4), (5, 'Bobby', 1),
(5, 'Bob', 2), (5, 'Bob' , 3), (5, 'Bob' , 4);
What I try to do, is group by the id field, count all rows, but for the name, use the one with row = 1
My attempt is like this, but, obviously, I get different rows since I include the x.name in the group by.
SELECT
x.id,
x.name,
COUNT(*) AS Value
FROM
(SELECT
tm.id AS Id,
pn.Name AS Name,
ROW_NUMBER() OVER(PARTITION BY tm.id ORDER BY tm.CreatedDate ASC) AS Row
FROM
#tempTable AS tm
LEFT JOIN
names pn WITH (NOLOCK) ON tm.nameId = pn.NameId
WHERE ....
) x
GROUP BY
x.id, x.name
ORDER BY
COUNT(*) DESC
The desired results from the dummy data are:
id name count
------------------
1 John 2
2 Mary 1
3 Jeff 1
4 Bill 4
5 Bobby 4
You can use FIRST_VALUE() window function to get the name of the row with row number = 1 and with the keyword DISTINCT there is no need to GROUP BY:
SELECT DISTINCT tm.id AS Id
, FIRST_VALUE(pn.Name) OVER (PARTITION BY tm.id ORDER BY tm.CreatedDate ASC) AS Name
, COUNT(*) OVER (PARTITION BY tm.id) AS counter
FROM #tempTable AS tm
LEFT JOIN names pn WITH (NOLOCK) ON tm.nameId = pn.NameId
WHERE ....
If you can't use FIRST_VALUE() then you can do it with conditional aggregation:
SELECT id,
MAX(CASE WHEN Row = 1 THEN Name END) AS NAME,
COUNT(*) AS Counter
FROM (
SELECT tm.id AS Id
, pn.Name AS Name
, ROW_NUMBER() OVER(PARTITION BY tm.id ORDER BY tm.CreatedDate ASC) AS Row
FROM #tempTable AS tm
LEFT JOIN names pn WITH (NOLOCK) ON tm.nameId = pn.NameId
WHERE ....
) t
GROUP BY id
This could be one solution to your problem: group on both id and the target name (case when p.row = 1 then p.name end) for the counting. Adding a with rollup to the grouping will "roll up" the count aggregations. Another aggregation on just id can then be use to merge the row values from the intermediate data set (visible in fiddle).
with cte as
(
select p.id,
case when p.row = 1 then p.name end as name,
count(1) as cnt
from people p
group by p.id, case when p.row = 1 then p.name end with rollup
having grouping(p.id) = 0
)
select cte.id,
max(cte.name) as name,
max(cte.cnt) as [count]
from cte
group by cte.id;
Fiddle
This would be another solution: do a regular count query with grouping on id and fetch the required name afterwards with a cross apply.
with cte as
(
select p.id,
count(1) as cnt
from people p
group by p.id
)
select cte.id,
n.name,
cte.cnt as [count]
from cte
cross apply ( select p.name
from people p
where p.id = cte.id
and p.row = 1 ) n;
Fiddle

Get top column base on maximum other column using group by?

How to get top column base on maximum other column using group by?
My raw data:
DECLARE #TB TABLE (ID INT, APP VARCHAR(25), PRICE MONEY)
INSERT INTO #TB
VALUES
(1, 'Apple', 10),
(1, 'Banana', 30),
(1, 'Orange', 20),
(2, 'Apple', 20),
(2, 'Banana', 30),
(2, 'Orange', 40)
This what I want:
Explain:
TOP_APP = Banana because MAX(PRICE) GROUP BY ID,
TOTAL = 60 because SUM(PRICE) GROUP BY ID.
You can use ROW_NUMBER and aggregation to achieve your required output-
DEMO HERE
SELECT A.ID,A.App,A.SUM
FROM
(
select *,
SUM(Price) OVER(PARTITION BY ID) SUM,
ROW_NUMBER() OVER (PARTITION BY ID ORDER BY Price DESC) RN
from #tb
)A
WHERE RN = 1
Demo on db<>fiddle
;WITH cte_TempTable as
(
SELECT Id, app, price,
SUM(Price) OVER(PARTITION BY ID) Total,
ROW_NUMBER() OVER (PARTITION BY ID ORDER BY Price DESC) Row_Number
FROM #TB
)
SELECT Id, app as TOP_APP, Total
FROM cte_TempTable
WHERE Row_Number = 1
Output
Id TOP_APP Total
1 Banana 60.0000
2 Orange 90.0000
As my assumption you want the result is
SELECT identity (int,1,1) as ID,
APP AS TOP_APP
,SUM(PRICE) AS TOTAL INTO #T
FROM #TB
GROUP BY APP
order by SUM(PRICE)
select * from #t

sql query that gets the difference between 2 recent rows for every row item that occurs more than once in a table

Sql query that gets the difference between 2 recent rows for every value that occurs more than once in a table.
for example
book value date
A 4 2017-07-17 09:16:44.480
A 2 2017-08-15 10:05:58.273
B 3 2017-04-15 10:05:58.273
C 2 2017-08-15 10:05:58.273
B 3 2017-04-13 10:05:58.273
B 3 2017-04-12 10:05:58.273
should return
A 2
B 0
Here is a solution:
SELECT book, MAX(value) - MIN(value) AS difference FROM (
SELECT book, value, ROW_NUMBER() OVER (PARTITION BY book ORDER BY date DESC) AS rownum FROM t
) AS a WHERE rownum <= 2 GROUP BY book HAVING MAX(rownum) >= 2
And here it is in SQLFiddle
SELECT id_pk FROM [table] GROUP BY [fields you whant to compare by] HAVING COUNT(*) > 1)
this select returns you the list of pk from element that are repited
so, in other select you migth get another Select like
Select * from [table] where id_pk in(
SELECT id_pk FROM [table] GROUP BY [fields you whant to compare by] HAVING COUNT(*) > 1)) limit 2
this is functional, still not good as i'm not analising complexity.
Add a rownumber before calculating:
create table #test ([book] char(1), [value] int, [date] datetime)
insert into #test values ('A', 4, '2017-07-17 09:16:44.480')
insert into #test values ('A', 2, '2017-08-15 10:05:58.273')
insert into #test values ('B', 3, '2017-04-15 10:05:58.273')
insert into #test values ('C', 2, '2017-08-15 10:05:58.273')
insert into #test values ('B', 3, '2017-04-13 10:05:58.273')
insert into #test values ('B', 3, '2017-04-12 10:05:58.273')
;with cte as(
Select ROW_NUMBER () OVER (order by [book], [date] ) as rownumber, *
from #test)
select distinct [1].book, abs(first_value([1].[Value]) over (partition by [1].book order by [1].rownumber desc) - [2].val2) as [Difference]
from cte [1]
inner join
(select rownumber, book, first_value([Value]) over (partition by book order by rownumber desc) as val2
from cte) [2] on [1].book = [2].book and [1].rownumber < [2].rownumber
I would use analytic functions:
;with CTE as (
SELECT book
,value
,LAG(value) OVER (PARTITION BY book ORDER BY date) last_value
,ROW_NUMBER() OVER (PARTITION BY book ORDER BY date DESC) rn
FROM MyTable
)
SELECT book
,value - last_value as value_change
FROM CTE
WHERE rn = 1
AND last_value IS NOT NULL
LAG() was added in SQL Server 2012, but even if you're on a higher version, your database must have the compatibility version set to 110 or higher for them to be available. Here's an alternative that should work on SQL Server 2005 or higher, or a database compatibility 90 or higher.
;with CTE as (
SELECT book
,value
,ROW_NUMBER() OVER (PARTITION BY book ORDER BY date DESC) rn
FROM MyTable
)
SELECT c1.book
c1.value - c2.value as value_change
FROM CTE c1
INNER JOIN CTE c2
ON c1.book = c2.book
WHERE c1.rn = 1
AND c2.rn = 2

Error when using insert with CTE and ranking function

I have a table temp.Results which contain Employee info. This table contains info used by HR. All changes to the employee records are in the table.i.e.
select * from temp.Results where ID=1
1,'2 main st','salem','2009-01-01','2000-01-01'
1,'34 elm st','acton','2013-03-09','2000-01-01'
Datevalidated is when we entered latest info.DateProcessed is the first time we entered employee info.
WITH ordered as(
select ID, name, address,city, DateValidated, DateProcessed
,ROW_NUMBER() over (partition by DateValidated
order by DateValidated desc) as rn from
aa.temp.Results (nolock) where id=31
)
insert into tempResults2(ID, name, address,city, DateValidated, DateProcessed)
select ID, name, address,city, DateValidated, DateProcessed from ordered where rn = 1 ;
I tried getting the above query to get the latest info of each employee into a teable but get this error.
Invalid object name 'tempResults2'
How to resolve this?
Thanks
MR
You can use where clause with select * into .
WITH ordered as
(
select
ID, name, address,city, DateValidated, DateProcessed,
ROW_NUMBER() over (partition by DateValidated order by DateValidated desc) as rn
from aa.temp.Results where id=31
)
select * into tempResults2 from ordered where rn = 1 ;
WITH ordered as(
select ID, name, address,city, DateValidated, DateProcessed
,ROW_NUMBER() over (partition by DateValidated
order by DateValidated desc) as rn from
aa.temp.Results (nolock) where id=31
)
select ID, name, address,city, DateValidated, DateProcessed
into tempResults2
from ordered
where rn = 1 ;
You query is wrong. When you can use nolock like this
WITH ordered as(
select ID, name, address,city, DateValidated, DateProcessed
,ROW_NUMBER() over (partition by DateValidated
order by DateValidated desc) as rn from
aa.temp.Results with(nolock) where id=31
)

How do I form a query with a running count retaining the order

I have a trace table which looks like this
I'd like to get a running total which looks like the following output - its very important that I retain the order - as this is the execution order of the stored porcedures - It will help me analyze bottle necks in the system
I have tried
select max(RowNumber),objectname, count(1) from rob
where eventclass = 42
group by objectname
But that mucks up the order
Is this even possible in SQL?
UPDATE:
I tried this
select RowNumber,objectname, count(1) from rob
where eventclass = 42
group by objectname,RowNumber
order by RowNumber
But this (as the query quite rightly says groups by rownumber (have to have that to have it in the order by) )
select objectname,
count(*)
from (
select RowNumber,
objectname,
row_number() over(order by RowNumber) - row_number() over(order by objectname, RowNumber) as grp
from rob
where eventclass = 42
) as T
group by grp, objectname
order by min(RowNumber)
Working sample using a table variable.
declare #T table
(
RowNumber int,
objectname varchar(50)
)
insert into #T values
(8, 'f_system_log_init'),
(10, 'f_purge_system_log'),
(25, 'f_system_log_msg'),
(65, 'f_system_log_msg'),
(104, 'f_system_log_msg'),
(143, 'f_system_log_msg'),
(182, 'f_system_log_msg'),
(221, 'f_system_log_msg'),
(5015, 'f_get_system_logs_parent_log_id_for_dataloader'),
(5055, 'f_system_log_msg'),
(5096, 'f_system_log_msg')
select objectname,
count(*)
from (
select RowNumber,
objectname,
row_number() over(order by RowNumber) - row_number() over(order by objectname, RowNumber) as grp
from #T
) as T
group by grp, objectname
order by min(RowNumber)
Result:
objectname
-------------------------------------------------- -----------
f_system_log_init 1
f_purge_system_log 1
f_system_log_msg 6
f_get_system_logs_parent_log_id_for_dataloader 1
f_system_log_msg 2
Try this:
;WITH CTE as (select *,ROW_NUMBER() over(order by rownumber,objectname) rn from test101)
,CTE1 as(
select *,1 as incr from CTE where rn=1
union all
select t.*,
CASE WHEN t.objectname=c.objectname then incr else incr+1 end as incr
from CTE t inner join CTE1 c
on t.rn=c.rn+1
)
select max(objectname),count(incr) from CTE1
group by incr

Resources