Count Number of Consecutive Occurrence of values in Table - sql

I have below table
create table #t (Id int, Name char)
insert into #t values
(1, 'A'),
(2, 'A'),
(3, 'B'),
(4, 'B'),
(5, 'B'),
(6, 'B'),
(7, 'C'),
(8, 'B'),
(9, 'B')
I want to count consecutive values in name column
+------+------------+
| Name | Repetition |
+------+------------+
| A | 2 |
| B | 4 |
| C | 1 |
| B | 2 |
+------+------------+
The best thing I tried is:
select Name
, COUNT(*) over (partition by Name order by Id) AS Repetition
from #t
order by Id
but it doesn't give me expected result

One approach is the difference of row numbers:
select name, count(*)
from (select t.*,
(row_number() over (order by id) -
row_number() over (partition by name order by id)
) as grp
from t
) t
group by grp, name;
The logic is easiest to understand if you run the subquery and look at the values of each row number separately and then look at the difference.

You could use windowed functions like LAG and running total:
WITH cte AS (
SELECT Id, Name, grp = SUM(CASE WHEN Name = prev THEN 0 ELSE 1 END) OVER(ORDER BY id)
FROM (SELECT *, prev = LAG(Name) OVER(ORDER BY id) FROM t) s
)
SELECT name, cnt = COUNT(*)
FROM cte
GROUP BY grp,name
ORDER BY grp;
db<>fiddle demo
The first cte returns group number:
+-----+-------+-----+
| Id | Name | grp |
+-----+-------+-----+
| 1 | A | 1 |
| 2 | A | 1 |
| 3 | B | 2 |
| 4 | B | 2 |
| 5 | B | 2 |
| 6 | B | 2 |
| 7 | C | 3 |
| 8 | B | 4 |
| 9 | B | 4 |
+-----+-------+-----+
And main query groups it based on grp column calculated earlier:
+-------+-----+
| name | cnt |
+-------+-----+
| A | 2 |
| B | 4 |
| C | 1 |
| B | 2 |
+-------+-----+

I have use Recursive CTE and minimise the use of row_number,also avoid count(*).
I think it will perform better,but in real world it depend what else filter you put to minimise number of rows affected.
If ID is having discreet values then One extra CTE will be use to generate continuous id.
;With CTE2 as
(
select ROW_NUMBER()over(order by id) id, name,1 Repetition ,1 Marker from #t
)
, CTE as
(
select top 1 cast(id as int) id, name,1 Repetition ,1 Marker from CTE2 order by id
union all
select a.id, a.name
, case when a.name=c.name then Repetition +1 else 1 end
, case when a.name=c.name then c.Marker else Marker+1 end
from #t a
inner join CTE c on a.id=c.id+1
)
,CTE1 as
(select *,ROW_NUMBER()over(partition by marker order by id desc)rn from cte c
)
select Name,Repetition from cte1 where rn=1

Related

How to set Conditional Row Number by specific values from the table

I have a query that returns table and based on value (if that exists) I want to set row_number.
I have some solution but it looks long and I think could be easier and less code (best option) to handle it. Below sample with expected results:
If query returns Client with NULL:
----------------------
Process | Client|
A | NULL |
A | B |
A | B |
A | B |
A | C |
A | C |
A | C |
OutPut should be:
----------------------
Process | Client| RowNumber
A | NULL | 1
A | B | 2
A | B | 3
A | B | 4
A | C | 2
A | C | 3
A | C | 4
If query returns without NULL:
----------------------
Process | Client|
A | B |
A | B |
A | B |
A | C |
A | C |
A | C |
OutPut should be:
----------------------
Process | Client| RowNumber
A | B | 1
A | B | 2
A | B | 3
A | C | 1
A | C | 2
A | C | 3
I'm not sure if NULL should always be treated as 'B', but you need to handle that:
select t.* ,
row_number() over (partition by process, coalesce(client, 'B') order by (select null))
from t
where client is not null;
Oh, I see, you are not setting the NULL to 'B' but adding the number of NULLs to the other values. That is also quite simple:
select t.* ,
(row_number() over (partition by process, client order by (select null)) +
(case when client is null then 0
else sum(case when client is null then 1 else 0 end) over ()
end)
)
from t
where client is not null;
dbfiddle
DROP TABLE if exists mytable;
CREATE TABLE mytable(Process char(1), Client char(1));
INSERT INTO mytable values
('A',null),
('A','B'),
('A','B'),
('A','B'),
('A','C'),
('A','C'),
('A','C');
-- with a NULL value
select
Process,
Client,
ROW_NUMBER() OVER (partition by process,Client order by (select null))+CASE WHEN Client is null THEN 0 else 1 end R
from mytable;
-- without a NULL value
select
Process,
Client,
ROW_NUMBER() OVER (partition by process,Client order by (select null)) R
from mytable
where not client is null;
…
declare #t table(process varchar(10), client varchar(10));
insert into #t(process, client)
values
('A', null),
('A', 'B'),('A', 'B'),('A', 'B'),
('A', 'C'),('A', 'C'),
('A', ''), ('A', ''), ('A', ' '), ('A', ' '),
('A', 'ZXY'), ('A', 'ZXY'),
('X', 'B'),('X', 'B'),('X', 'B'),
('X', 'C'),('X', 'C');
select *,
row_number() over(partition by process,client order by client)
--if there is a null client per process then add 1 to every non null client
+ case when client is not null and min(case when client is null then 0 else 1 end) over(partition by process) = 0 then 1 else 0 end
--+ case when client is not null and min(isnull(ASCII(client+'.'), 0)) over(partition by process) = 0 then 1 else 0 end
as rownumber
from
(
select *
from #t
--where client is not null
) as t;

Select distinct one field other first non empty or null

I have table
| Id | val |
| --- | ---- |
| 1 | null |
| 1 | qwe1 |
| 1 | qwe2 |
| 2 | null |
| 2 | qwe4 |
| 3 | qwe5 |
| 4 | qew6 |
| 4 | qwe7 |
| 5 | null |
| 5 | null |
is there any easy way to select distinct 'id' values with first non null 'val' values. if not exist then null. for example
result should be
| Id | val |
| --- | ---- |
| 1 | qwe1 |
| 2 | qwe4 |
| 3 | qwe5 |
| 4 | qew6 |
| 5 | null |
In your case a simple GROUP BY should be the solution:
SELECT Id
,MIN(val)
FROM dbo.mytable
GROUP BY Id
Whenever using a GROUP BY, you have to use an aggregate function on all columns, which are not listed in the GROUP BY.
If an Id has a value (val) other than NULL, this value will be returned.
If there are just NULLs for the Id, NULL will be returned.
As far as i unterstood (regarding your comment), this is exactly what you're going to approach.
If you always want to have "the first" value <> NULL, you'll need another sort criteria (like a timestamp column) and might be able to solve it with a WINDOW-function.
If you want the first non-NULL value (where "first" is based on id), then MIN() doesn't quite do it. Window functions do:
select t.*
from (select t.*,
row_number() over (partition by id
order by (case when val is not null then 1 else 2 end),
id
) as seqnum
from t
) t
where seqnum = 1;
SQL Fiddle:
Create Table from SQL Fiddle:
CREATE TABLE tab1(pid integer, id integer, val varchar(25))
Insert dummy records :
insert into tab1
values (1, 1 , null),
(2, 1 , 'qwe1' ),
(3, 1 , 'qwe2'),
(4, 2 , null ),
(5, 2 , 'qwe4' ),
(6, 3 , 'qwe5' ),
(7, 4 , 'qew6' ),
(8, 4 , 'qwe7' ),
(9, 5 , null ),
(10, 5 , null );
fire below query:
SELECT Id ,MIN(val) as val FROM tab1 GROUP BY Id;

Hide the same cells in SQL Server result

I have a result from tables similar to this:
id | name | type
---+------+------
1 | John | 1
1 | John | 34
2 | Jane | 2
1 | John | 12
2 | Jane | 168
I need to hide repeated values and let only unique values. I need to get something like this
id | name | type
---+------+------
1 | John | 1
| | 34
| | 12
2 | Jane | 2
| | 168
How can I do that in SQL Server 2012?
This is something that your presentation layer ought to handle generally but...
WITH T AS
(
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY ID ORDER BY Name, Type) AS RN1,
ROW_NUMBER() OVER (PARTITION BY ID, Name ORDER BY Type) AS RN2
FROM YourTable
)
SELECT
CASE WHEN RN1 = 1 THEN ID END AS ID,
CASE WHEN RN2 = 1 THEN Name END AS Name,
Type
FROM T
ORDER BY ID, Name, Type
If you need nullify marked rows, you can use following query:
WITH Src AS
(
SELECT * FROM (VALUES
(1, 'John', 1 ),
(1, 'John', 34 ),
(2, 'Jane', 2 ),
(1, 'John', 12 ),
(2, 'Jane', 168)
)T(id, name, [type])
)
SELECT
CASE WHEN RN=1 THEN id END id,
CASE WHEN RN=1 THEN name END name,
[Type]
FROM (SELECT *, ROW_NUMBER() OVER (PARTITION BY id, name ORDER BY [Type]) RN FROM Src) T
It returns:
id name Type
----------- ---- -----------
1 John 1
NULL NULL 12
NULL NULL 34
2 Jane 2
NULL NULL 168

How to merge two tables data in one using SQL Server 2008

I have two tables
Table #1: tbl_test1
id | product1 | price1|
---+----------+-------+
1 | A | 200 |
2 | B | 250 |
3 | C | 300 |
Table #2 : tbl_test2
id | product2 | price2|
---+----------+-------+
40 | P | 200 |
20 | Q | 250 |
and I want to result in my given format
id | product1 | price1|id | product2 | price2|
---+----------+-------+---+----------+-------+
1 | A | 200 |50 | P | 200 |
2 | B | 250 |40 | Q | 250 |
3 | C | 300 | | | |
Please help...
I don't know what's your end game but if you just want to display your data side by side you need to use FULL JOIN. Additionally, you have to add a ROW_NUMBER for each of your tables:
WITH CteTest1 AS(
SELECT *,
rn = ROW_NUMBER() OVER(ORDER BY id)
FROM #tbl_test1
),
CteTest2 AS(
SELECT *,
rn = ROW_NUMBER() OVER(ORDER BY id)
FROM #tbl_test2
)
SELECT
t1.id, t1.product1, t1.price1,
t2.id, t2.product2, t2.price2
FROM CteTest1 t1
FULL JOIN CteTest2 t2
ON t2.rn = t1.rn
ONLINE DEMO
declare #FirstTable table (StudentId int, SubjectId int)
declare #SecondTable table (MarksId int, RankId int, LastRank int)
insert into #FirstTable values (1, 1)
insert into #FirstTable values (1, 2)
insert into #FirstTable values (1, 3)
insert into #SecondTable values (1, 4, 10)
insert into #SecondTable values (1, 5, 11)
;with XmlTable (RowNumber, StudentId, SubjectId) as
(
select row_number() over(order by StudentId) as RowNumber, * from #FirstTable
)
,TechnicalIdsTable (RowNumber, MarksId, RankId, LastRank) as
(
select row_number() over(order by MarksId) as RowNumber, * from #SecondTable
)
select x.StudentId, x.SubjectId, t.MarksId, t.RankId, t.LastRank from XmlTable x
left join TechnicalIdsTable t
on x.RowNumber = t.RowNumber

Select rows with maximum value where values in two columns are same

I have a simple table like this
....................................
| hotelNo | roomType | totalBooking |
....................................
| 1 | single | 2 |
| 1 | family | 4 |
| 2 | single | 3 |
| 2 | family | 2 |
| 3 | single | 1 |
.....................................
Now I want to get the most commonly booked roomType for each hotels, i.e the following result
......................
| hotelNo | roomType |
......................
| 1 | family |
| 2 | single |
| 3 | single |
......................
P.S I use sub-query to get the first table
If you want the maximum, you can use window functions:
select hotelNo, roomType
from (select t.*, row_number() over (partition by hotelNo order by totalBooking desc) as seqnum
from table t
) t
where seqnum = 1;
Sample table
SELECT * INTO #TEMP
FROM
(
SELECT 1 HOTELNO ,'SINGLE' ROOMTYPE ,2 TOTALBOOKING
UNION ALL
SELECT 1,'FAMILY',4
UNION ALL
SELECT 2,'SINGLE',3
UNION ALL
SELECT 2,'FAMILY',2
UNION ALL
SELECT 3,'SINGLE',1
)TAB
Result query
;WITH CTE1 AS
(
SELECT HOTELNO,ROOMTYPE,TOTALBOOKING,
MAX(TOTALBOOKING) OVER (PARTITION BY HOTELNO) MAXX
FROM #TEMP
)
SELECT DISTINCT HOTELNO,ROOMTYPE,MAXX
FROM CTE1
WHERE TOTALBOOKING=MAXX
Another way of doing is by using Aggregate Function Max
SELECT A.hotelNo,
A.roomType,
A.totalBooking
FROM tablename A
JOIN (SELECT Max (totalBooking) totalBooking,
hotelNo
FROM tablename
group by hotelNo) B
ON a.totalBooking = b.totalBooking
AND a.hotelNo = b.hotelNo
SQL FIDDLE DEMO