Is it possible to find (in an ordered table) multiple rows in sequence? [duplicate] - sql

This question already has an answer here:
Compare Current Row with Previous/Next row in SQL Server
(1 answer)
Closed 4 years ago.
If I have a table ordered by ID like so:
|---------------------|------------------|
| ID | Key |
|---------------------|------------------|
| 1 | Foo |
|---------------------|------------------|
| 2 | Bar |
|---------------------|------------------|
| 3 | Test |
|---------------------|------------------|
| 4 | Test |
|---------------------|------------------|
Is there a way to detect two rows that match a where clause in sequence?
For example, in the table above, I would like to see if any two rows in succession have a Key of 'test'.
Is this possible in SQL?

Another option is a variation of Gaps-and-Islands
Example
Declare #YourTable Table ([ID] int,[Key] varchar(50))
Insert Into #YourTable Values
(1,'Foo')
,(2,'Bar')
,(3,'Test')
,(4,'Test')
Select ID_R1 = min(ID)
,ID_R2 = max(ID)
,[Key]
From (
Select *
,Grp = ID-Row_Number() over(Partition By [Key] Order by ID)
From #YourTable
) A
Group By [Key],Grp
Having count(*)>1
Returns
ID_R1 ID_R2 Key
3 4 Test
EDIT - Just in case the IDs are NOT Sequential
Select ID_R1 = min(ID)
,ID_R2 = max(ID)
,[Key]
From (
Select *
,Grp = Row_Number() over(Order by ID)
-Row_Number() over(Partition By [Key] Order by ID)
From #YourTable
) A
Group By [key],Grp
Having count(*)>1

You can try to use ROW_NUMBER window function check the gap.
SELECT [Key]
FROM (
SELECT *,ROW_NUMBER() OVER(ORDER BY ID) -
ROW_NUMBER() OVER(PARTITION BY [Key] ORDER BY ID) grp
FROM T
)t1
GROUP BY [Key]
HAVING COUNT(grp) = 2

You can do a self join as
CREATE TABLE T(
ID INT,
[Key] VARCHAR(45)
);
INSERT INTO T VALUES
(1, 'Foo'),
(2, 'Bar'),
(3, 'Test'),
(4, 'Test');
SELECT MIN(T1.ID) One,
MAX(T2.ID) Two,
T1.[Key] OnKey
FROM T T1 JOIN T T2
ON T1.[Key] = T2.[Key]
AND
T1.ID <> T2.ID
GROUP BY T1.[Key];
Or a CROSS JOIN as
SELECT MIN(T1.ID) One,
MAX(T2.ID) Two,
T1.[Key] OnKey
FROM T T1 CROSS JOIN T T2
WHERE T1.[Key] = T2.[Key]
AND
T1.ID <> T2.ID
GROUP BY T1.[Key]
Demo

You can use the LEAD() window function, as in:
with
x as (
select
id, [key],
lead(id) over(order by id) as next_id,
lead([key]) over(order by id) as next_key
from my_table
)
select id, next_id from x where [key] = 'test' and next_key = 'test'

Related

SQL count number of records where value remains constant

I need to find the count of tracker_id where position remains 1 through out the table.
tracker_id | position
---------------------
5 | 1
11 | 1
4 | 1
4 | 2
5 | 2
4 | 1
4 | 1
11 | 1
14 | 1
9 | 2
Here, the output should be 2 since, position of tracker_id:11 and 14 remains 1 through out the table.
You can use not exists
select count(*) from tbl a
where not exists(select 1
from tbl b
where a.tracker_id = b.tracker_id
and a.position <> b.position )
and a.position = 1
Output: 2
declare #table1 as table (tracker_id int,postion int)
insert into #table1 values (5,1)
insert into #table1 values (11,1)
insert into #table1 values (4,1)
insert into #table1 values (4,2)
insert into #table1 values (5,2)
insert into #table1 values (4,1)
insert into #table1 values (4,1)
insert into #table1 values (11,1)
insert into #table1 values (14,1)
insert into #table1 values (9,2)
select count(tracker_id),tracker_id,postion from #table1 group by tracker_id,postion
You can also do:
select ( count(distinct tracker_id) -
count(distinct tracker_id) filter (where position <> 1)
) as num_all_1s
from t;
Using uncorrelated subquery
select count(distinct tracker_id)
from t
where position=1
and tracker_id not in (select tracker_id from t where position<>1);
Using window function
select count(distinct tracker_id)
from (select *, avg(position) over (partition by tracker_id) as avg_pos from t) a
where avg_pos=1;
This one is just for giggles
select distinct count(*) over ()
from t
group by tracker_id
having count(*) = sum(position);
And if you really want to have fun
select count(distinct tracker_id)-count(distinct case when position<>1 then tracker_id end)
from t;
If position can only be 1, then you can use this, which gets all the tracker_ids with only a single position value, and then limits that to those records where position = 1:
WITH agg AS
(
SELECT
tracker_id
, p = MAX(position)
FROM table1
GROUP BY tracker_id
HAVING COUNT(DISTINCT position) = 1
)
SELECT COUNT(tracker_id)
FROM agg
WHERE p = 1

SQL: Order by date and distinct Number without losing fields

My knowledge about SQL is not the best but I need a quick solution for this. I have a table
number | date | text
1 | 2018-01-13 | A
2 | 2018-01-15 | B
1 | 2018-02-15 | C
Now I need to remove the duplicate value "number" in the output(select) based on the date. It should look like this:
number | date | text
2 | 2018-01-15 | B
1 | 2018-02-15 | C
I tried
SELECT DISTINCT number, date ORDER BY date DESC FROM table
The problem is that I now miss the field "text" in the output. I also tried
SELECT * DISTINCT(SELECT * number ORDER BY(date) DESC) FROM table
Any ideas?
One option uses ROW_NUMBER:
SELECT number, date, text
FROM
(
SELECT *, ROW_NUMBER() OVER (PARTITION BY number ORDER BY date DESC) rn
FROM yourTable
) t
WHERE rn = 1;
You can achieve it using below query:
SELECT t1.number, t1.date, t1.text
FROM yourTable t1
INNER JOIN
(
SELECT number, MAX(date) AS max_date
FROM yourTable
GROUP BY number
) t2
ON t1.number = t2.number AND
t1.date = t2.max_date;
use below query this will work for ur requirement
SELECT number, date, text
FROM
(
SELECT *, ROW_NUMBER() OVER (PARTITION BY number ORDER BY date DESC) rn
FROM test
) t
WHERE rn = 1;
It can be done just using window functions:
create table #TEST ([NUMBER] int, [DATE] date, [TEXT] char(1))
insert into #TEST values (1, '2018-01-13', 'A'), (2, '2018-01-15', 'B'), (1, '2018-02-15', 'C')
SELECT DISTINCT [NUMBER]
, FIRST_VALUE([DATE]) OVER (PARTITION BY [NUMBER] ORDER BY [DATE] DESC) AS [DATE]
, FIRST_VALUE([TEXT]) OVER (PARTITION BY [NUMBER] ORDER BY [DATE] DESC) AS [TEXT]
FROM #TEST

How can I select distinct by one column?

I have a table with the columns below, and I need to get the values if COD is duplicated, get the non NULL on VALUE column. If is not duplicated, it can get a NULL VALUE. Like the example:
I'm using SQL SERVER.
This is what I get:
COD ID VALUE
28 1 NULL
28 2 Supermarket
29 1 NULL
29 2 School
29 3 NULL
30 1 NULL
This is what I want:
COD ID VALUE
28 2 Supermarket
29 2 School
30 1 NULL
What I'm tryin' to do:
;with A as (
(select DISTINCT COD,ID,VALUE from CodId where ID = 2)
UNION
(select DISTINCT COD,ID,NULL from CodId where ID != 2)
)select * from A order by COD
You can try this.
DECLARE #T TABLE (COD INT, ID INT, VALUE VARCHAR(20))
INSERT INTO #T
VALUES(28, 1, NULL),
(28, 2 ,'Supermarket'),
(29, 1 ,NULL),
(29, 2 ,'School'),
(29, 3 ,NULL),
(30, 1 ,NULL)
;WITH CTE AS (
SELECT *, RN= ROW_NUMBER() OVER (PARTITION BY COD ORDER BY VALUE DESC) FROM #T
)
SELECT COD, ID ,VALUE FROM CTE
WHERE RN = 1
Result:
COD ID VALUE
----------- ----------- --------------------
28 2 Supermarket
29 2 School
30 1 NULL
Another option is to use the WITH TIES clause in concert with Row_Number()
Example
Select top 1 with ties *
from YourTable
Order By Row_Number() over (Partition By [COD] order by Value Desc)
Returns
COD ID VALUE
28 2 Supermarket
29 2 School
30 1 NULL
I would use GROUP BY and JOIN. If there is no NOT NULL value for a COD than it should be resolved using the OR in JOIN clause.
SELECT your_table.*
FROM your_table
JOIN (
SELECT COD, MAX(value) value
FROM your_table
GROUP BY COD
) gt ON your_table.COD = gt.COD and (your_table.value = gt.value OR gt.value IS NULL)
If you may have more than one non null value for a COD this will work
drop table MyTable
CREATE TABLE MyTable
(
COD INT,
ID INT,
VALUE VARCHAR(20)
)
INSERT INTO MyTable
VALUES (28,1, NULL),
(28,2,'Supermarket'),
(28,3,'School'),
(29,1,NULL),
(29,2,'School'),
(29,3,NULL),
(30,1,NULL);
WITH Dups AS
(SELECT COD FROM MyTable GROUP BY COD HAVING count (*) > 1 )
SELECT MyTable.COD,MyTable.ID,MyTable.VALUE FROM MyTable
INNER JOIN dups ON MyTable.COD = Dups.COD
WHERE value IS NOT NULL
UNION
SELECT MyTable.COD,MyTable.ID,MyTable.VALUE FROM MyTable
LEFT JOIN dups ON MyTable.COD = Dups.COD
WHERE dups.cod IS NULL

Merging data in a single SQL table without a Cursor

I have a table with an ID column and another column with a number. One ID can have multiple numbers. For example
ID | Number
1 | 25
1 | 26
1 | 30
1 | 24
2 | 4
2 | 8
2 | 5
Now based of this data, in a new table, I want to have this
ID | Low | High
1 | 24 | 26
1 | 30 | 30
2 | 4 | 5
2 | 8 | 8
As you can see, I want to merge any data where the numbers are consecutive, like 24, 25, 26. So now the low was 24, the high was 26, and then 30 is still a separate range. I am dealing with large amounts of data, so I would prefer to not use a cursor for performance sake (which is what I was previously doing, and was slowing things down quite a bit)...What is the best way to achieve this? I'm no SQL pro, so I'm not sure if there is a function available that could make this easier, or what the fastest way to accomplish this would be.
Thanks for the help.
The key observation is that a sequence of numbers minus another sequence is a constant. We can generate another sequence using row_number. This identifies all the groups:
select id, MIN(number) as low, MAX(number) as high
from (select t.*,
(number - ROW_NUMBER() over (partition by id order by number) ) as groupnum
from t
) t
group by id, groupnum
The rest is just aggregation.
Solution with CTE and recursion:
WITH CTE AS (
SELECT T.ID, T.NUMBER, T.NUMBER AS GRP
FROM T
LEFT OUTER JOIN T T2 ON T.ID = T2.ID AND T.NUMBER -1 = T2.NUMBER
WHERE T2.ID IS NULL
UNION ALL
SELECT T.ID, T.NUMBER, GRP
FROM CTE
INNER JOIN T
ON T.ID = CTE.ID AND T.NUMBER = CTE.NUMBER + 1
)
SELECT ID, MAX( NUMBER ), MIN(NUMBER)
FROM CTE
GROUP BY ID, GRP
Results at fiddlesql
I'd suggest using a WHILE loop structure with a table variable instead of the cursor.
For example,
DECLARE #TableVariable TABLE
(
MyID int IDENTITY (1, 1) PRIMARY KEY NOT NULL,
[ID] int,
[Number] int
)
DECLARE #Count int, #Max int
INSERT INTO #TableVariable (ID, Number)
SELECT ID, Number
FROM YourSourceTable
SELECT #Count = 1, #Max = MAX(MyID)
FROM #TableVariable
WHILE #Count <= #Max
BEGIN
...do your processing here...
SET #Count = #Count + 1
END
CREATE TABLE Table1
([ID] int, [Number] int)
;
INSERT INTO Table1
([ID], [Number])
VALUES
(1, 25),
(1, 26),
(1, 30),
(1, 24),
(2, 4),
(2, 8),
(2, 5)
;
select ID,
MIN(Number)
,(SELECT MIN(Number)
FROM (SELECT TOP 2 Number from Table1 WHERE ID =
T1.Id ORDER BY Number DESC) as DT)
from Table1 as T1
GROUP BY ID
UNION
SELECT ID, MAX(Number), MAX(Number)
FROM Table1 as T1
GROUP BY ID;
Live Example

Sql query to sort data on one column only but not change the others columns

I want a SQL query to display the following data
ID Name
1 AAA
2 BBB
3 CCC
4 DDD
as this:
ID Name
4 AAA
3 BBB
2 CCC
1 DDD
without changing the other columns.
Kindly suggest?
Thanks
You could use row_number to number the table in two directions, and zip those together:
declare #t table (id int, name varchar(4))
insert #t values (1, 'AAA'), (2, 'BBB'), (3, 'CCC'), (4, 'DDD')
; with numbered as
(
select row_number() over (order by id) as rn1
, row_number() over (order by id desc) as rn2
, *
from #t
)
select t2.id
, t1.name
from numbered t1
join numbered t2
on t1.rn1 = t2.rn2
This prints:
id name
4 AAA
3 BBB
2 CCC
1 DDD
I'm going with something like this :
SELECT t2.ID, t1.NAME
FROM
(SELECT ROW_NUMBER() OVER(ORDER BY ID DESC) AS rownumber,
Name
FROM MyTable) as t1
INNER JOIN
(SELECT ROW_NUMBER() OVER(ORDER BY ID ASC) AS rownumber,
ID
FROM MyTable) as t2
ON t1.rownumber = t2.rownumber
You have to set to each row a number for the Name field, and for the ID field, in different order, and then join between them to retrieve the datas in different order.
I would use a subselect with order by clause for the ID column.