Function to detect change in an ordered list - sql

I who like to find a solution in T-SQL that could find a a way to detect a change in a given list or records.
The physical table is like this:
| id |val |
|----|----|
| 1 | A |
|----|----|
| 2 | A |
|----|----|
| 3 | B |
|----|----|
| 4 | B |
|----|----|
| 5 | A |
|----|----|
| 6 | A |
|----|----|
id is a sequencial integer
val is an arbitrary value
I would like to add an calculated field that could somehow denote a change of val
Desired result:
| id |val | segment |
|----|----|---------|
| 1 | A | 1 |
|----|----|---------|
| 2 | A | 1 |
|----|----|---------|
| 3 | B | 2 |
|----|----|---------|
| 4 | B | 2 |
|----|----|---------|
| 5 | A | 3 |
|----|----|---------|
| 6 | A | 3 |
|----|----|---------|
What I'm trying to do is the possibility to group by "segments" like this:
| from_id | to_id | val |
|---------|-------|-----|
| 1 | 2 | A |
| 3 | 4 | B |
| 5 | 6 | A |
|---------|-------|-----|

Assuming SQL Server 2005+
DECLARE #T TABLE (
id INT PRIMARY KEY,
val CHAR(1))
INSERT INTO #T
SELECT 1,'A' UNION ALL SELECT 2,'A' UNION ALL
SELECT 3,'B' UNION ALL SELECT 4,'B' UNION ALL
SELECT 5,'A' UNION ALL SELECT 6,'A'
;WITH cte1 AS(
SELECT
id,
val,
ROW_NUMBER() OVER (ORDER BY id) - ROW_NUMBER() OVER (PARTITION BY val ORDER BY id) AS Grp
FROM #T
),
cte2 AS(
SELECT
id,
val,
MIN(id) OVER (PARTITION BY Grp, val) AS GrpStart
FROM cte1
)
SELECT
id,
val,
DENSE_RANK() OVER (ORDER BY GrpStart) AS segment
FROM cte2
Or the updated requirement is a bit simpler
;WITH cte AS(
SELECT
id,
val,
ROW_NUMBER() OVER (ORDER BY id) - ROW_NUMBER() OVER (PARTITION BY val ORDER BY id) AS Grp
FROM #T
)
SELECT
val,
MIN(id) AS from_id,
MAX(id) AS to_id
FROM cte
GROUP BY Grp, val
ORDER BY from_id

This might have to be modified a bit to work. Also, i never use loops, do this might not be the most efficient way to do it.
Declare #counter int
Set #counter = 1
Declare #seg int
Set #seg = 1
Declare #cur varchar(50)
Set # cur = select val from table where id = 1
While #counter <= select max id from table
Begin
if(#cur == select val from table where id = #counter)
update table set segment = #seg where id = #counter
else
{
set #cur = select val from table where id = #counter
set # seg = #seg + 1
update table set segment = #seg where id = #counter
}
Set #counter = #counter + 1
End
Well thats the general idea anyways...

Related

Select Last Record Based on few criteria

Before
+--------+--------+---------+-------+------+
| RowNum | Status | Remarks | SetNo | |
+--------+--------+---------+-------+------+
| 1 | Q | | Set 1 | Want |
| 2 | Q | | Set 1 | Want |
| 3 | Q | | Set 1 | Want |
| 4 | Q | | Set 1 | Want |
| 5 | W | | Set 1 | Want |
| 1 | W | abc | Set 2 | |
| 2 | W | abc | Set 2 | |
| 3 | W | abc | Set 2 | |
| 4 | W | abc | Set 2 | Want |
| 1 | Q | | Set 3 | Want |
| 2 | w | abc | Set 3 | |
| 3 | w | abc | Set 3 | Want |
+--------+--------+---------+-------+------+
How to select Status=Q and Status=W based on Rownum=lastnumber and setno? Expectation result is the row with "want" is what i need. Those empty, will be remove
Tried:
select *
from mytable
where (RowNum != (select max(RowNum) from mytable) and status = 'W')
I understand that for each setno, you want all "Q"s and the latest "W". If so, you can use window functions like that:
select *
from (
select t.*,
row_number() over(partition by setno, status order by rownum desc) rn
from mytable t
) t
where rn = 1 or status = 'Q'
You might want to look into Window Functions. I don't fully understand what you need to do but I would suggest something like:
with rowNumberedData as
(
SELECT *,
ROW_NUMBER() OVER (PARTITION BY SetNo ORDER BY RowNum DESC) as RowOrder
FROM mytable
)
SELECT *
FROM rowNumberedData
WHERE (Status = 'Q' OR Status = 'W') AND RowOrder = 1
What this will do is add RowOrder column to your data and its value will be 1, for the max RowNum in every set. You can read more here and here to check what the with syntax is if you are unfamiliar.
This query should return the correct rows
with t_cte as (
select t.*,
row_number() over(partition by setno order by rownum desc) rn
from testTable t)
select *
from t_cte
where [status] = 'Q'
or ([status] = 'W'
and rn = 1);
I understand the question and wanting the last row for each set and then all rows with q. One method uses row_number():
select t.*
from (select t.*,
row_number() over (partition by setno order by rownum desc) as seqnum
from mytable t
) t
where seqnum = 1 or status = 'Q';
There are other ways to express this:
select t.*
from mytable t
where t.status = 'Q' or
t.rownum = (select max(t2.rownum)
from mytable t2
where t2.setno = t.setno
);
This is similar to the approach you are trying.

create sql function ( Column typeX)

create sql function ( Column typeX)
hi
i have this Table:
autoID | id | name | age | Tel
------------------------------------------
1 | 1 | Frank | 40 | null
2 | 1 | null | 50 | 7834xx
3 | 1 | Alex | null | null
4 | 1 | null | 20 | null
5 | 2 | James | null | 4100xx
6 | 3 | jan | 24 | null
7 | 3 | null | null | 4100xx
my query for select :
SELECT TOP 10
(SELECT top(1) name FROM test1 where id=1 and name is not null order by autoID desc) as name ,(SELECT top(1) age FROM test1 where id=1 and age is not null order by autoID desc) as Age ,(SELECT top(1) Tel
FROM test1
where id=1 and Tel
is not null order by autoID desc) as Telephon FROM [dbo].[test1] group by id
Result:
autoID | id | name | age | Tel
------------------------------------------
1 | 1 | Alex | 20 | 7834xx
I need create function like this:
CREATE FUNCTION TestSchema.MyfunctinX(#ColumnX #ColumnX.type)
RETURNS #ColumnX.type
AS
BEGIN
DECLARE #value #ColumnX.type
SELECT top 1 #value from #ColumnX where #value is not null order by #autoID desc
RETURN #value
END;
GO
for my select query get Short like:
Select Id, MyfunctinX(name) as [Name], MyfunctinX(age) as Age, MyfunctinX(Tel)
as Tel from yourtable Group by Id
Is there a way to do this?
Your structure is not very efficient. You may be better served with an EAV Structure (Entity Attribute Value)
Example
Select A.ID
,Name = [dbo].[MyFunction] (A.ID,'Name')
,Age = [dbo].[MyFunction] (A.ID,'Age')
,Tel = [dbo].[MyFunction] (A.ID,'Tel')
From (Select Distinct ID From YourTable ) A
Returns
ID Name Age Tel
1 Alex 20 7834xx
2 James NULL 4100xx
3 jan 24 4100xx
The UDF
CREATE FUNCTION [dbo].[MyFunction] (#ID int,#Field varchar(100))
Returns varchar(max)
As
Begin
Return (
Select Top 1 with ties Value
From (Select XMLData = cast((Select * from YourTable where ID=#ID For XML RAW) as xml)) A
Cross Apply (
Select autoID = r.value('#autoID','int')
,Item = attr.value('local-name(.)','varchar(100)')
,Value = attr.value('.','varchar(max)')
From A.XMLData.nodes('/row') as A(r)
Cross Apply A.r.nodes('./#*') AS B(attr)
Where attr.value('local-name(.)','varchar(100)') not in ('autoID')
and attr.value('local-name(.)','varchar(100)')= #Field
) B
Order By Row_Number() over (Partition By Item Order By autoID Desc)
)
End

delete nth row from table (postgresql)

I know its possible to select nth row.
For example
select from table limit 1 offset 3;
Is it possible to delete nth row?
If you have an id and something to order by and/or partition by you can delete using row_number() like so:
drop table if exists t;
create table t (id int, val int);
insert into t values (1,9),(2,8),(3,7),(4,6),(5,5);
delete
from t
where id in (
select id
from (
select id, row_number() over (order by val asc) as rn
from t
) s
where s.rn = 3);
select * from t;
rextester demo: http://rextester.com/XJHB50704
returns:
+----+-----+
| id | val |
+----+-----+
| 1 | 9 |
| 2 | 8 |
| 4 | 6 |
| 5 | 5 |
+----+-----+

Conditional Group By in SQL

I have the following table that I want to group by type. When there are multiple rows with the same type (e.g., A & B type), I want to preserve the 'value' from the row with the highest rank (i.e., primary > secondary > tertiary..)
rowid | type | rank | value
1 | A | primary | 1
2 | A | secondary | 2
3 | B | secondary | 3
4 | B | tertiary | 4
5 | C | primary | 5
So the resulting table should look like
rowid | type | rank | value
1 | A | primary | 1
3 | B | secondary | 3
5 | C | primary | 5
Any suggestions will be highly appreciated!
p.s., I'm working in MS SQL Server.
You can use row_number(). Here is a simple'ish method:
select t.*
from (select t.*,
row_number() over (partition by type
order by charindex(rank, 'primary,secondary,tertiary')
) as seqnum
from t
) t
where seqnum = 1;
This uses charindex() as a simple method of ordering the ranks.
try this,
;WITH CTE
AS (
SELECT *
,row_number() OVER (
PARTITION BY [type] ORDER BY value
) rn
FROM #t
)
SELECT *
FROM cte
WHERE rn = 1
Another way of doing is with Row_Number and an Order By specifying your rule with CASE.
Schema:
CREATE TABLE #TAB(rowid INT, [type] VARCHAR(1), rankS VARCHAR(50) , value INT)
INSERT INTO #TAB
SELECT 1 , 'A' , 'primary' , 1
UNION ALL
SELECT 2 , 'A' , 'secondary', 2
UNION ALL
SELECT 3 , 'B' , 'secondary' , 3
UNION ALL
SELECT 4 , 'B' , 'tertiary' , 4
UNION ALL
SELECT 5 , 'C' , 'primary' , 5
Now apply rank rule with Row_Number
SELECT * FROM (
SELECT ROW_NUMBER() OVER(PARTITION BY [type] ORDER BY (CASE rankS
WHEN 'primary' THEN 1
WHEN 'secondary' THEN 2
WHEN 'tertiary' THEN 3 END )) AS SNO, * FROM #TAB
)A
WHERE SNO =1
Result:
+-----+-------+------+-----------+-------+
| SNO | rowid | type | rankS | value |
+-----+-------+------+-----------+-------+
| 1 | 1 | A | primary | 1 |
| 1 | 3 | B | secondary | 3 |
| 1 | 5 | C | primary | 5 |
+-----+-------+------+-----------+-------+

SELECT only latest record of an ID from given rows

I have this table shown below...How do I select only the latest data of the id based on changeno?
+----+--------------+------------+--------+
| id | data | changeno | |
+----+--------------+------------+--------+
| 1 | Yes | 1 | |
| 2 | Yes | 2 | |
| 2 | Maybe | 3 | |
| 3 | Yes | 4 | |
| 3 | Yes | 5 | |
| 3 | No | 6 | |
| 4 | No | 7 | |
| 5 | Maybe | 8 | |
| 5 | Yes | 9 | |
+----+---------+------------+-------------+
I would want this result...
+----+--------------+------------+--------+
| id | data | changeno | |
+----+--------------+------------+--------+
| 1 | Yes | 1 | |
| 2 | Maybe | 3 | |
| 3 | No | 6 | |
| 4 | No | 7 | |
| 5 | Yes | 9 | |
+----+---------+------------+-------------+
I currently have this SQL statement...
SELECT id, data, MAX(changeno) as changeno FROM Table1 GROUP BY id;
and clearly it doesn't return what I want. This should return an error because of the aggrerate function. If I added fields under the GROUP BY clause it works but it doesn't return what I want. The SQL statement is by far the closest I could think of. I'd appreciate it if anybody could help me on this. Thank you in advance :)
This is typically referred to as the "greatest-n-per-group" problem. One way to solve this in SQL Server 2005 and higher is to use a CTE with a calculated ROW_NUMBER() based on the grouping of the id column, and sorting those by largest changeno first:
;WITH cte AS
(
SELECT id, data, changeno,
rn = ROW_NUMBER() OVER (PARTITION BY id ORDER BY changeno DESC)
FROM dbo.Table1
)
SELECT id, data, changeno
FROM cte
WHERE rn = 1
ORDER BY id;
You want to use row_number() for this:
select id, data, changeno
from (SELECT t.*,
row_number() over (partition by id order by changeno desc) as seqnum
FROM Table1 t
) t
where seqnum = 1;
Not a well formed or performance optimized query but for small tasks it works fine.
SELECT * FROM TEST
WHERE changeno IN (SELECT MAX(changeno)
FROM TEST
GROUP BY id)
for other alternatives :
DECLARE #Table1 TABLE
(
id INT, data VARCHAR(5), changeno INT
);
INSERT INTO #Table1
SELECT 1,'Yes',1
UNION ALL
SELECT 2,'Yes',2
UNION ALL
SELECT 2,'Maybe',3
UNION ALL
SELECT 3,'Yes',4
UNION ALL
SELECT 3,'Yes',5
UNION ALL
SELECT 3,'No',6
UNION ALL
SELECT 4,'No',7
UNION ALL
SELECT 5,'Maybe',8
UNION ALL
SELECT 5,'Yes',9
SELECT Y.id, Y.data, Y.changeno
FROM #Table1 Y
INNER JOIN (
SELECT id, changeno = MAX(changeno)
FROM #Table1
GROUP BY id
) X ON X.id = Y.id
WHERE X.changeno = Y.changeno
ORDER BY Y.id