Select alternate rows from SQL Server table - sql-server

I am working with SQL Server 2008. I have a table which does not contain any unique columns; how to get alternate rows from it?
SQL Server table:
+-----+--------+
| id | name |
|-----+--------|
| 1 | abc |
| 2 | pqr |
| 2 | pqr |
| 3 | xyz |
| 4 | lmn |
| 5 | efg |
| 5 | efg |
+-----+--------+
As we've to come with at least one working suggestion with the question, I've tried below code; which is not so proper technique when fetching from a huge amount of data.
Trial:
create table #tmp
(
id int, name varchar(10), srNo int
)
insert into #tmp
select
id, name,
ROW_NUMBER() OVER (ORDER BY id) % 2 as srNo --,alternate rows
from
Employee
select *
from #tmp
where srNo = 1 --or srNo = 0
Above query gives out alternate rows i.e. 1st, 3rd, 5th OR 2nd, 4th, 6th etc.
Please help me out with proper way without #tmp to achieve the goal!

You can just use your select statement as an in-line view. You don't need the #tmp table.
select t.id, name
from (select id, name, ROW_NUMBER() over (order by id) as srNo from Employee) t
where (t.srNo % 2) = 1
SqlFiddle

--To fetch ALTERNATE records from a table (EVEN NUMBERED)
Select * from TableName where ColumnName % 2 = 0
For Eg : select * from HumanResources.Employee where BusinessEntityID % 2 = 0
--To fetch ALTERNATE records from a table (ODD NUMBERED)
Select * from TableName where ColumnName % 2 = 1
For Eg : select * from HumanResources.Employee where BusinessEntityID % 2 = 1

I'm taking student as a table name.
Here is my answer ->
For Even Row Number -
> SELECT id from (SELECT rowno, id from student) where mod(rowno,2)=0
For Odd Row Number -
> SELECT id from (SELECT rowno, id from student) where mod(rowno,2)=1

Same also can be achieved using having clause; but it adds group by task:
SELECT id, name
FROM (SELECT id, name, ROW_NUMBER()over(order by id) AS srNo FROM Employee) x
GROUP BY srNo, id, name
HAVING (srNo % 2) = 0

You can just use your select statement as an in-line view. You don't need the #tblCities table.
select tbl1.CityID,tbl1.CityName from (select ROW_NUMBER() over(order by CityID asc) as row_no,CityID,CityName from tblCities) as tbl1 where tbl1.row_no%2=1

declare #t table
(
id int,
name nvarchar(20)
)
insert into #t
Select 1, 'abc'
union all
Select 2, 'pqr'
union all
Select 2, 'pqr'
union all
Select 3, 'xyz'
union all
Select 4, 'lmn'
union all
Select 5, 'efg'
union all
Select 2, 'efg'
Select * from(
Select *, row_number() over(order by id) as rnum from #t ) t where rnum % 2 <> 0

create table t (id bigint NOT NULL, input_1 boolean not null, data_gps timestamp(0) not null);
insert into t (id, input_1,data_gps) values
(1, false , '2022-01-01 15:42:07'),
(2, true , '2022-01-02 15:42:07'),
(3, true , '2022-01-03 15:42:07'),
(4, false , '2022-01-04 15:42:07'),
(5, true , '2022-01-05 15:42:07'),
(6, true , '2022-01-06 15:42:07'),
(7, true , '2022-01-07 15:42:07'),
(8, true , '2022-01-08 15:42:07'),
(9, false , '2022-01-09 15:42:07'),
(10 ,true , '2022-01-10 15:42:07'),
(11, true , '2022-01-11 15:42:07'),
(12, true , '2022-01-12 15:42:07'),
(13, false , '2022-01-13 15:42:07'),
(14, true , '2022-01-14 15:42:07');
you will have
Here is the query that will group by value change
select input_1, min(data_gps) as mind, max(data_gps) as maxd
from (
select input_1, data_gps,
row_number() over (order by data_gps)
- row_number() over (partition by input_1 order by data_gps) as grp
from t
) as tmp
group by input_1, grp
order by min(data_gps);
The results
DEMO
https://dbfiddle.uk/6Ajy3H5O

Related

Select ID for corresponding max date using GROUP BY

My table structure as below
Category Sex Last Modified Date Id
7 2 2015-01-16 87603
7 1 2014-11-27 87729
7 2 2018-09-06 87135
7 1 2017-12-27 87568
My sql query as below
SELECT
MAX(Id) AS Id
FROM
Table
GROUP BY
Category, Sex
Result as below
87603
87729
But I would like to get Id as Max Last Modified Date. Correct result should be as below
87135
87568
You can use ROW_NUMBER() to find most recent row per group:
SELECT Id, LastModifiedDate
FROM (
SELECT Id, LastModifiedDate, ROW_NUMBER() OVER (PARTITION BY Category, Sex ORDER BY LastModifiedDate DESC) AS rnk
FROM t
) AS cte
WHERE rnk = 1
Use RANK() if you're interested in finding all rows with ties for LastModifiedDate.
You can also get it as
SELECT T.*
FROM
(
SELECT Sex,
MAX([Last Modified Date]) [Last Modified Date],
Category
FROM T
GROUP BY Sex,
Category
) TT INNER JOIN T ON T.[Last Modified Date] = TT.[Last Modified Date]
WHERE T.Sex = TT.Sex
AND
T.Category = TT.Category;
Returns:
+----------+-----+---------------------+-------+
| Category | Sex | Last Modified Date | Id |
+----------+-----+---------------------+-------+
| 7 | 2 | 06/09/2018 00:00:00 | 87135 |
| 7 | 1 | 27/12/2017 00:00:00 | 87568 |
+----------+-----+---------------------+-------+
We can get the solution by joining the same table with its grouped set:
SELECT MIN(T.Id)
FROM Table T
INNER JOIN (SELECT Category,
Sex,
MAX(LastModifiedDate) AS LastModifiedDate
FROM Table
GROUP BY Category, Sex) GT
ON GT.Category = T.Category
AND GT.Sex = T.Sex
AND GT.LastModifiedDate = T.LastModifiedDate
GROUP BY T.Category, T.Sex
Other option is to use correlated subquery :
select t.*
from table t
where t.LastModifiedDate = (select max(t1.LastModifiedDate)
from table t1
where t1.Category = t.Category and t1.Sex = t.Sex
);
Here are a few different approaches... (in no particular order)
IF OBJECT_ID('tempdb..#TestData', 'U') IS NOT NULL
DROP TABLE #TestData;
GO
CREATE TABLE #TestData (
Category TINYINT NOT NULL,
Sex TINYINT NOT NULL,
LastModifiedDate DATE NOT NULL,
Id INT NOT NULL
);
GO
INSERT #TestData(Category, Sex, LastModifiedDate, Id) VALUES
(7, 2, '2015-01-16', 87603),
(7, 1, '2014-11-27', 87729),
(7, 2, '2018-09-06', 87135),
(7, 1, '2017-12-27', 87568);
GO
/* nonclustered index to support the query. */
CREATE UNIQUE NONCLUSTERED INDEX ix_TestData_Category_Sex_LastModifiedDate
ON #TestData (Category ASC, Sex ASC, LastModifiedDate DESC)
INCLUDE (Id);
GO
--====================================================
-- option 1: TOP(n) WITH TIES...
SELECT TOP (1) WITH TIES
td.Id
FROM
#TestData td
ORDER BY
ROW_NUMBER() OVER (PARTITION BY td.Category, td.Sex ORDER BY td.LastModifiedDate DESC);
GO
-----------------------------------------------------
-- option 2: Filter on ROW_NUMBER()...
WITH
cte_AddRN AS (
SELECT
td.Id,
rn = ROW_NUMBER() OVER (PARTITION BY td.Category, td.Sex ORDER BY td.LastModifiedDate DESC)
FROM
#TestData td
)
SELECT
arn.Id
FROM
cte_AddRN arn
WHERE
arn.rn = 1;
GO
-----------------------------------------------------
-- option 3: binary concatination...
SELECT
Id = CONVERT(INT, SUBSTRING(MAX(bv.bin_val), 4, 4))
FROM
#TestData td
CROSS APPLY ( VALUES (CONVERT(BINARY(3), td.LastModifiedDate) + CONVERT(BINARY(4), td.Id)) ) bv (bin_val)
GROUP BY
td.Category,
td.Sex;
GO
--====================================================

Select all second highest values only from temp table

Using T-SQL (SQL Server 2008 R2), I'm trying to list only the rows with the second highest value in a particular column from a temp table and then place the results into a new temp table. The PK is the ID, which can have increasing version numbers and then unique codes.
Example:
ID | Name| Version | Code
------------------------
1 | A | 1 | 10
1 | A | 2 | 20
1 | A | 3 | NULL
2 | B | 1 | 40
2 | B | 2 | 50
2 | C | 1 | 60
The desired outcome of the query is
ID | Version | Code
------------------------
1 | 2 | 20
2 | 1 | 40
To achieve this I need the below query to be adapted to pull the second highest value as long as the result gives a version number greater than 1. These results come from a temp table and will then be placed into a final results temp table. EDIT: Please note this will be applied over 33000 rows of data so I would prefer something neater than INSERT VALUES. Thanks.
Current query:
SELECT
ID
,Version
,Code
INTO
#table2
FROM
#table1
SELECT *
FROM #table2
WHERE Version > 1
ORDER BY ID asc
DROP TABLE #table1
DROP TABLE #table2
I have tried running the where clause WHERE Version < (SELECT MAX(VERSION) FROM #TABLE 2) but this has no effect, presumably due to the unique code values and in any case wouldn't work where I have more than 3 Versions.
Ideas would be gratefully received.
Thanks in advance.
i HAVE TEST THE BELOW CODE AND IT IS GIVING OUTPUT AS PER The YOUR desired outcome of the query is
SELECT ID,Name,[Version],Code
FROM (
SELECT ROW_NUMBER() OVER (PARTITION BY NAME ORDER BY [Version] DESC) AS RNK,*
FROM
(
SELECT 1 ID, 'A' Name ,1 [Version] ,10 Code
UNION ALL
SELECT 1, 'A', 2 ,20
UNION ALL
SELECT 1, 'A', 3 ,30
UNION ALL
SELECT 1, 'A', 4 ,NULL
UNION ALL
SELECT 2, 'B', 1 ,40
UNION ALL
SELECT 2, 'B', 2 ,50
UNION ALL
SELECT 2, 'C', 1 ,60
)B
)BASE
WHERE RNK =2
If your primary key is only ID, you have duplicate rows. So I assume your primary key is something else, for example ID, Version, Name. You have two rows with the same ID and same Version, what kind of rule do you want to apply on this ? Lowest number ?
I made an example that does kind of what you want:
First declare the necessary tables:
declare #table1 table (
Id int,
Name nvarchar(20),
[Version] int,
Code int
)
insert into #table1 values (1,'A',1,10),(1,'A',2,20),(1,'A',3,30),(1,'A',4,NULL)
,(2,'B',1,40),(2,'B',2,50),(2,'C',1,60);
And then the query to get the results:
with HighestVersions (Id, MaxVersion) As
(
select Id, max(version) from #table1 group by Id
)
select
t1.Id,
t1.[Version],
min(t1.Code) as Code
from
#table1 t1
inner join
HighestVersions hv
on
hv.Id = t1.Id
and (hv.MaxVersion-1) = t1.[Version]
group by
t1.Id
,t1.[Version]
I had to do a little dirty trick with the outermost select, this is because of the duplicate 'Id' and 'Version'. Else you would have gotten two rows with ID = 2, Version = 1
If you want to remove the NULL value you can change the WITH part (according to your last edit):
with HighestVersions (Id, MaxVersion) As
(
select Id, max(version) from #table1 where Code is not null group by Id
)
Try this:
DECLARE #List TABLE (ID int, Name char(1), Version int, Code int NULL)
INSERT INTO #List
VALUES
(1, 'A', 1, 10),
(1, 'A', 2, 20),
(1, 'A', 3, 30),
(1, 'A', 4, NULL),
(2, 'B', 1, 40),
(2, 'B', 2, 50),
(2, 'C', 1, 60)
SELECT
ID, Name, Version, Code
FROM
(
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY ID, Name ORDER BY Version DESC) Rn
FROM #List
) a
WHERE
a.Rn = 2

Retrieve most frequent values from each column within groups

For each group, grouped using field GRP, I would like to retrieve the most frequently occurring value in column A and the most frequently occurring value in column B, and potentially do this for many other columns.
Sample Data:
GRP | A | B
-----------
Cat | 1 | 1
Cat | 2 | 1
Cat | 3 | 2
Cat | 3 | 3
Dog | 5 | 6
Dog | 5 | 7
Dog | 6 | 7
Expected Output:
GRP | A | B
-----------
Cat | 3 | 1
Dog | 5 | 7
This query achieves that result:
SELECT
freq1.GRP,
freq1.A,
freq2.B
FROM (
SELECT
GRP,
A,
ROW_NUMBER() OVER(PARTITION BY GRP ORDER BY COUNT(*) DESC) AS F_RANK
FROM MyTable
GROUP BY GRP, A
) AS freq1
INNER JOIN (
SELECT
GRP,
B,
ROW_NUMBER() OVER(PARTITION BY GRP ORDER BY COUNT(*) DESC) AS F_RANK
FROM MyTable
GROUP BY GRP, B
) AS freq2 ON freq2.GRP = freq1.GRP
WHERE freq1.F_RANK = 1 AND freq2.F_RANK = 1
It just doesn't look very efficient, and even less so if I were to add a column C, D, etc...
Is there a better way?
I wouldn't say this approach is "better" because it will generate the exact same execution plan. However, I find this type of approach a lot more maintainable as the number of columns might grow. For me this is a lot easier to read.
with GroupA as
(
select Grp
, A
, ROW_NUMBER() over(partition by grp order by count(*) desc) as RowNum
from MyTable
group by Grp, A
)
, GroupB as
(
select Grp
, B
, ROW_NUMBER() over(partition by grp order by count(*) desc) as RowNum
from MyTable
group by Grp, B
)
select a.Grp
, a.A
, b.B
from GroupA a
inner join GroupB b on a.Grp = b.Grp and b.RowNum = 1
where a.RowNum = 1;
An alternative using results ranked in a temp table:
SELECT GRP, A, B,
ROW_NUMBER() OVER (PARTITION BY A ORDER BY GRP, A) ARank,
ROW_NUMBER() OVER (PARTITION BY B ORDER BY GRP, B) BRank
INTO #TMP
FROM MyTable
SELECT t1.GRP,
(SELECT TOP 1 A FROM #TMP WHERE GRP = t1.Grp ORDER BY ARank DESC) A,
(SELECT TOP 1 B FROM #TMP WHERE GRP = t1.Grp ORDER BY BRank DESC) B
FROM MyTable t1
GROUP BY T1.GRP
DROP TABLE #TMP
Full Solution on SQL Fiddle
Schema Setup:
CREATE TABLE MyTable
([GRP] varchar(3), [A] int, [B] int)
;
INSERT INTO MyTable
([GRP], [A], [B])
VALUES
('Cat', 1, 1),
('Cat', 2, 1),
('Cat', 3, 2),
('Cat', 3, 3),
('Dog', 5, 6),
('Dog', 5, 7),
('Dog', 6, 7)
;
Query 1:
SELECT GRP, A, B,
ROW_NUMBER() OVER (PARTITION BY A ORDER BY GRP, A) ARank,
ROW_NUMBER() OVER (PARTITION BY B ORDER BY GRP, B) BRank
INTO #TMP
FROM MyTable
SELECT t1.GRP,
(SELECT TOP 1 A FROM #TMP WHERE GRP = t1.Grp ORDER BY ARank DESC) A,
(SELECT TOP 1 B FROM #TMP WHERE GRP = t1.Grp ORDER BY BRank DESC) B
FROM MyTable t1
GROUP BY T1.GRP
DROP TABLE #TMP
Results:
| GRP | A | B |
|-----|---|---|
| Cat | 3 | 1 |
| Dog | 5 | 7 |
I'll start out this answer by saying this is NOT going to be more efficient to run - it should just be easier to add/subtract columns. To do this you just add them into the code in two places.
You can use dynamic SQL to build your result set like this:
CREATE TABLE ##fields (id INT IDENTITY(1,1),fieldname VARCHAR(255))
INSERT INTO ##fields
( fieldname )
VALUES ('A'),('B') --Add field names here
DECLARE #maxid INT
SELECT #maxid = MAX(id) FROM ##fields
CREATE TABLE ##Output (GRP VARCHAR(3), A INT, B INT) --Add field names here
INSERT INTO ##Output
( GRP )
SELECT DISTINCT GRP FROM MyTable
DECLARE #SQL NVARCHAR(MAX)
DECLARE #i INT = 1
WHILE #i <=#maxid
BEGIN
SELECT #SQL = 'with cte as (SELECT GRP , ' + fieldname + ' ,
ROW_NUMBER() OVER ( PARTITION BY GRP ORDER BY COUNT(*) DESC ) AS F_RANK
FROM MyTable
GROUP BY GRP , ' + fieldname + ')
UPDATE O
SET O.' + fieldname + ' = cte.' + fieldname + '
FROM ##Output O
INNER JOIN cte ON O.GRP = cte.GRP AND cte.F_Rank = 1' FROM ##fields WHERE id = #i
EXEC sp_executesql #sql
SET #i = #i + 1
END
SELECT *
FROM ##Output
DROP TABLE ##fields
DROP TABLE ##Output
Using your simple example above, I received the following performance stats:
Dynamic SQL
CPU = 31
Reads = 504
Duration = 39
Your SQL
CPU = 0
Reads = 6
Duration = 1
Clearly, this way is not a more efficient way of doing this. I did want to throw it out there anyway as an alternative to your current method.
First we create the test data:
DECLARE #MyTable TABLE
(
GRP varchar(10),
A int,
B int
)
INSERT INTO #MyTable
( GRP, A, B)
VALUES
('Cat', 1, 1),
('Cat', 2, 1),
('Cat', 3, 2),
('Cat', 3, 3),
('Dog', 5, 6),
('Dog', 5, 7),
('Dog', 6, 7);
Now we use first_value from a subselect (or a cte if you wanted) and grab the top cat and dog columns
SELECT DISTINCT
GRP,
FIRST_VALUE(A) OVER(PARTITION BY GRP ORDER BY d.A_CNT DESC) AS A_RANK,
FIRST_VALUE(B) OVER(PARTITION BY GRP ORDER BY d.B_CNT DESC) AS B_RANK
FROM
(
SELECT
GRP,
A,
ROW_NUMBER() OVER (PARTITION BY A ORDER BY GRP, A) AS A_CNT,
B,
ROW_NUMBER() OVER (PARTITION BY B ORDER BY GRP, B) AS B_CNT
FROM #MyTable
) d
Output:
GRP A_RANK B_RANK
Cat 3 1
Dog 5 7

How can I recursively calculate a value

I have this table.
Bundles
id | parent_id | quantity
1 | 0 | 1
2 | 1 | 4
3 | 2 | 5
I want to get the total quantity of a bundle with id 3, which is 1 * 4 * 5 = 20 items
Can this be done with a single query?
Here's a solution using CTE:
Setup:
CREATE TABLE Table1
(id int, parent_id int, quantity int)
;
INSERT INTO Table1
(id, parent_id, quantity)
VALUES
(1, 0, 1),
(2, 1, 4),
(3, 2, 5),
(4, 0, 7),
(5, 4, 10)
;
CTE to return total of id=3 and it's parent items:
;WITH myCTE AS
(
SELECT id, parent_id, quantity
FROM Table1
WHERE id = 3
UNION ALL
SELECT T.id, T.parent_id, T.quantity
FROM Table1 T
JOIN myCTE C ON T.id = C.parent_id
)
SELECT EXP(sum(log(quantity)))
FROM myCTE
Demo SQL Fiddle
Multiplication method for values in a column, SELECT EXP(sum(log(quantity))), taken from here.

COUNT number of rows in a GROUP on higher aggregate level

I am trying to find out how many rows of a certain item exist in the table, e.g. in the following example for itemID 1 I need the result 5 (not 3, which is what I currently get). I am tempted to add TransactionID into the PARTITION BY clause, but that results in Msg 8120 since the query does not GROUP by TransactionID. Well, if it did then getting that count would be easy, but I do not want to group on Transaction Level. What can I do to get that ItemCount right? It must be so easy but I am banging my head.
DECLARE #t TABLE (TransactionID INT PRIMARY KEY IDENTITY, CustomerID INT, ItemID INT);
INSERT INTO #t (CustomerID, ItemID)
VALUES
(1, 1),
(2, 1),
(3, 1),
(4, 2),
(1, 1),
(2, 2),
(3, 3),
(4, 4),
(1, 1);
SELECT
CustomerID,
ItemID,
Rows = COUNT(*),
ItemRowCount = COUNT(*) OVER (PARTITION BY ItemID)
FROM
#t
GROUP BY
CustomerID,
ItemID
ORDER BY
ItemID,
CustomerID;
EDIT: I was overaggregating, I guess. Sebastian Meine got me on the track and his answer is right so I accepted it. However, this subquery works for my:
SELECT
CustomerID,
ItemID,
Rows = COUNT(*),
ItemRowCount = (SELECT COUNT(*) FROM #t x WHERE t.ItemID = x.ItemID)
FROM
#t t
GROUP BY
CustomerID,
ItemID
ORDER BY
ItemID,
CustomerID;
You need to pull your outer group count out of the actual group by query. The easiest way to do that is like this:
SQL Fiddle
MS SQL Server 2008 Schema Setup:
CREATE TABLE dbo.tbl (TransactionID INT PRIMARY KEY IDENTITY, CustomerID INT, ItemID INT);
INSERT INTO dbo.tbl (CustomerID, ItemID)
VALUES
(1, 1),
(2, 1),
(3, 1),
(4, 2),
(1, 1),
(2, 2),
(3, 3),
(4, 4),
(1, 1);
Query 1:
SELECT *,SUM(Rows)OVER(PARTITION BY ItemId) ItemCnt
FROM(
SELECT
CustomerID,
ItemID,
Rows = COUNT(*)
FROM
dbo.tbl
GROUP BY
CustomerID,
ItemID
)X
ORDER BY
ItemID,
CustomerID
Results:
| CUSTOMERID | ITEMID | ROWS | ITEMCNT |
----------------------------------------
| 1 | 1 | 3 | 5 |
| 2 | 1 | 1 | 5 |
| 3 | 1 | 1 | 5 |
| 2 | 2 | 1 | 2 |
| 4 | 2 | 1 | 2 |
| 3 | 3 | 1 | 1 |
| 4 | 4 | 1 | 1 |
Notice that I add the inner counts together instead of recounting from scratch.
You can use simple count and group by:
SELECT
ItemID,
ItemRowCount = COUNT(1)
FROM
#t
GROUP BY
ItemID
ORDER BY
ItemID
or if you need attach total rows count and item row count to every row:
SELECT
CustomerID,
ItemID,
Rows = COUNT(1) over (),
ItemRowCount = COUNT(1) OVER (PARTITION BY ItemID)
FROM
#t
ORDER BY
ItemID,
CustomerID;
To find out how many rows of a certain item exist in the table, we may not need CustomerId. Use following query -
DECLARE #t TABLE (TransactionID INT PRIMARY KEY IDENTITY, CustomerID INT, ItemID INT);
INSERT INTO #t (CustomerID, ItemID)
VALUES
(1, 1),
(2, 1),
(3, 1),
(4, 2),
(1, 1),
(2, 2),
(3, 3),
(4, 4),
(1, 1);
;WITH cte AS(
SELECT itemId, ROW_NUMBER() OVER (PARTITION BY itemId ORDER BY itemId DESC) AS row_cnt FROM #t
)
SELECT itemId, MAX(row_cnt) row_count FROM cte GROUP BY itemId
It will return -
itemId row_count
1 5
2 2
3 1
4 1
And if is case you need customerId, the use -
;WITH cte AS(
SELECT customerId, itemId, ROW_NUMBER() OVER (PARTITION BY itemId ORDER BY itemId DESC) AS row_cnt FROM #t
)
SELECT customerId, itemId, MAX(row_cnt) item_count FROM cte GROUP BY CustomerID, itemId
It will return -
customerId itemId item_count
1 1 5
2 1 2
3 1 3
2 2 1
4 2 2
3 3 1
4 4 1
You can use a CTE or join to a sub-table (like this)
SELECT
tbl.CustomerID,
tbl.ItemID,
Rows = COUNT(*),
ItemRowCount
FROM tbl
JOIN (SELECT ItemID, Count(*) as ItemRowCount
FROM tbl
GROUP BY ItemID) t ON tbl.ItemID = t.ItemID
GROUP BY
tbl.CustomerID,
tbl.ItemID,
ItemRowCount
ORDER BY
tbl.ItemID,
CustomerID;
or this
SELECT
tbl.CustomerID,
tbl.ItemID,
Rows = COUNT(*),
MAX(ItemRowCount)
FROM tbl
JOIN (SELECT ItemID, Count(*) as ItemRowCount
FROM tbl
GROUP BY ItemID) t ON tbl.ItemID = t.ItemID
GROUP BY
tbl.CustomerID,
tbl.ItemID
ORDER BY
tbl.ItemID,
CustomerID;

Resources