delete nth row from table (postgresql) - sql

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 |
+----+-----+

Related

How to delete the rows with three same data columns and one different data column

I have a table "MARK_TABLE" as below.
How can I delete the rows with same "STUDENT", "COURSE" and "SCORE" values?
| ID | STUDENT | COURSE | SCORE |
|----|---------|--------|-------|
| 1 | 1 | 1 | 60 |
| 3 | 1 | 2 | 81 |
| 4 | 1 | 3 | 81 |
| 9 | 2 | 1 | 80 |
| 10 | 1 | 1 | 60 |
| 11 | 2 | 1 | 80 |
Now I already filtered the data I want to KEEP, but without the "ID"...
SELECT student, course, score FROM mark_table
INTERSECT
SELECT student, course, score FROM mark_table
The output:
| STUDENT | COURSE | SCORE |
|---------|--------|-------|
| 1 | 1 | 60 |
| 1 | 2 | 81 |
| 1 | 3 | 81 |
| 2 | 1 | 80 |
Use the following query to delete the desired rows:
DELETE FROM MARK_TABLE M
WHERE
EXISTS (
SELECT
1
FROM
MARK_TABLE M_IN
WHERE
M.STUDENT = M_IN.STUDENT
AND M.COURSE = M_IN.COURSE
AND M.SCORE = M_IN.SCORE
AND M.ID < M_IN.ID
)
OUTPUT
db<>fiddle demo
Cheers!!
use distinct
SELECT distinct student, course, score FROM mark_table
Assuming you don't just want to select the unique data you want to keep (you mention you've already done this), you can proceed as follows:
Create a temporary table to hold the data you want to keep
Insert the data you want to keep into the temporary table
Empty the source table
Re-Insert the data you want to keep into the source table.
select * from
(
select row_number() over (partition by student,course,score order by score)
rn,student,course,score from mark_table
) t
where rn=1
Use CTE with RowNumber
create table #MARK_TABLE (ID int, STUDENT int, COURSE int, SCORE int)
insert into #MARK_TABLE
values
(1,1,1,60),
(3,1,2,81),
(4,1,3,81),
(9,2,1,80),
(10,1,1,60),
(11,2,1,80)
;with cteDeleteID as(
Select id, row_number() over (partition by student,course,score order by score) [row_number] from #MARK_TABLE
)
delete from #MARK_TABLE where id in
(
select id from cteDeleteID where [row_number] != 1
)
select * from #MARK_TABLE
drop table #MARK_TABLE

SQL query to create ascending values within groups

I have the following table:
+----+--------+-----+
| id | fk_did | pos |
+----+--------+-----+
This table contains hundreds of rows, each of them referencing another table with fk_did. The value in pos is currently always zero which I want to change.
Basically, for each group of fk_did, the pos-column should start at zero and be ascending. It doesn't matter how the rows are ordered.
Example output (select * from table order by fk_did, pos) that I wanna get:
+----+--------+-----+
| id | fk_did | pos |
+----+--------+-----+
| xx | 0 | 0 |
| xx | 0 | 1 |
| xx | 0 | 2 |
| xx | 1 | 0 |
| xx | 1 | 1 |
| xx | 1 | 2 |
| xx | 4 | 0 |
| xx | 8 | 0 |
| xx | 8 | 1 |
| xx | 8 | 2 |
+----+--------+-----+
There must be no two rows that have the same combination of fk_did and pos
pos must be ascending for each fk_did
If there is a row with pos > 0, there must also be a row with the same fk_did and a lower pos.
Can this be done with a single update query?
You can do this using a window function:
update the_table
set pos = t.rn - 1
from (
select id,
row_number() over (partition by fk_id) as rn
from the_table
) t
where t.id = the_table.id;
The ordering of pos will be more or less random, as there is no order by, but you said that doesn't matter.
This assumes that id is unique, if not, you can use the internal column ctid instead.
If id is the PK of your table, then you can use the following query to update your table:
UPDATE mytable
SET pos = t.rn
FROM (
SELECT id, fk_did, pos,
ROW_NUMBER() OVER (PARTITION BY fk_did ORDER BY id) - 1 AS rn
FROM mytable) AS t
WHERE mytable.id = t.id
ROW_NUMBER window function, used with a PARTITION BY clause, generates sequence numbers starting from 1 for each fk_did slice.
Demo here
I'd suggest creating a temporary table if id column is not unique):
create temp table tmp_table as
select id, fk_did, row_number() over (partition by fk_did) - 1 pos
from table_name
And then truncate current table and insert records from the temp table

SQL remove duplicates from GROUP BY results

I have a table with the following structure
sys_id(identity) | id | group_id | fld_id | val
-----------------------------------------------
I have a query
SELECT id,group_id,fld_id,val,COUNT(*)
FROM [DB_ALERT].[dbo].[DATATABLE]
GROUP BY id,group_id,fld_id,val
HAVING COUNT(*)>1
The resul set is like this
ID | group_id | fld_id | val| count(*)
__________________________________________
1000001| 1 | 1 | 23 | 2
1000003| 1 | 1 | 24 | 5
1000008| 1 | 1 | 14 | 4
Now in the result set I want to take only top 1 sys_id for each record and delete the others with same ID,Group,Fld and val (remove its dublicates). I know how to do this with cursors, but is there any way to do such operation in a single query?
Please try:
;with c as
(
select *, row_number() over(partition by ID, Group, Fld, val order by ID, Group, Fld, val) as n
from YouTable
)
delete from c
where n > 1

Function to detect change in an ordered list

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...

Make a field monotonic across all rows

I have table in my sql server database which I want to convert to PK column
To do that I want to change value of each row in this column to 1,2,3 ...
Could You write T-Sql query for that task ?
Thanks for help
begin state:
Id | Name |
----------
1 | One |
2 | Two |
2 | Three|
x | xxx |
result:
Id | Name |
----------
1 | One |
2 | Two |
3 | Three|
4 | xxx |
;with cte as
(
SELECT Id, ROW_NUMBER() over (order by Id) as rn
from YourTable
)
UPDATE cte SET Id = rn
you can also do it with name if you dont have the id!
;with cte as
(
SELECT Id, ROW_NUMBER() over (order by name) as rn
from YourTable
)
UPDATE cte SET Id = rn