Change Position of Serial Number in SQL - sql

I have a table named students. the structure is given below
______________________________
AdmissionNo RollNo Name
______________________________
1001 1 A
1003 2 B
1005 3 C
1006 4 D
1008 5 E
Now i want to change rollno 4 to 2 and increment forthcoming numbers
so the result should be like below
-------------------------------
AdmissionNo RollNo Name
-------------------------------
1001 1 A
1006 2 D
1003 3 B
1005 4 C
1008 5 E
--------------------------------
How to attain this using sql Query.
Note: Question Edited as per 'The Impaler' said.Admission number is not changing.only Roll no change. The values in table are examples actual values are hundreds of records.

With the omission of a dialect, I have answered this in T-SQL, as I wanted a stab at this.
This isn't pretty, however, I use a couple of updatable CTE's to find the offset for the specific rows, and then update the needed rows accordingly:
USE Sandbox;
GO
CREATE TABLE dbo.YourTable (AdmissionNo int, Rollno tinyint, [Name] char(1));
INSERT INTO dbo.YourTable
VALUES(1001,1,'A'),
(1003,2,'B'),
(1005,3,'C'),
(1006,4,'D'),
(1008,5,'E');
GO
DECLARE #NewPosition tinyint = 2,
#MovingName char(1) = 'D';
WITH Offsetting AS(
SELECT *,
COUNT(CASE Rollno WHEN #NewPosition THEN 1 END) OVER (ORDER BY RollNo ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING) -
COUNT(CASE [Name] WHEN #MovingName THEN 1 END) OVER (ORDER BY RollNo ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING) AS LagOffset
FROM dbo.YourTable),
NewNames AS(
SELECT *,
CASE RollNo WHEN #NewPosition THEN #MovingName
ELSE LAG([Name],LagOffset) OVER (ORDER BY RollNo)
END AS NewName
FROM Offsetting)
UPDATE NewNames
SET [Name] = NewName;
GO
SELECT *
FROM dbo.YourTable;
GO
DROP TABLE dbo.YourTable;

Not pretty but you could use some sub queries
DROP TABLE IF EXISTS T;
create table t
(AdmissionNo int, RollNo int, Name varchar(1));
insert into t values
(1001 , 1 , 'A'),
(1003 , 2 , 'B'),
(1005 , 3 , 'C'),
(1006 , 4 , 'D'),
(1008 , 5 , 'E');
select t.*,
case when rollno = 2 then (select name from t where rollno = 4)
when rollno > 2 and
rollno <> (select max(rollno) from t) then (select name from t t1 where t1.rollno < t.rollno order by t1.rollno desc limit 1)
else name
end
from t;
+-------------+--------+------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| 1001 | 1 | A | A |
| 1003 | 2 | B | D |
| 1005 | 3 | C | B |
| 1006 | 4 | D | C |
| 1008 | 5 | E | E |
+-------------+--------+------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
5 rows in set (0.001 sec)

DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(admission_no INT NOT NULL PRIMARY KEY
,roll_no INT NOT NULL
,name CHAR(1) NOT NULL
);
INSERT INTO my_table VALUES
(1001,1,'A'),
(1003,2,'B'),
(1005,3,'C'),
(1006,4,'D'),
(1008,5,'E');
SELECT *
, CASE WHEN roll_no = 4 THEN 2
WHEN roll_no >= 2 AND roll_no < 4 THEN roll_no + 1
ELSE roll_no END x FROM my_table;
+--------------+---------+------+---+
| admission_no | roll_no | name | x |
+--------------+---------+------+---+
| 1001 | 1 | A | 1 |
| 1003 | 2 | B | 3 |
| 1005 | 3 | C | 4 |
| 1006 | 4 | D | 2 |
| 1008 | 5 | E | 5 |
+--------------+---------+------+---+
5 rows in set (0.00 sec)
...or, as an update...
UPDATE my_table x
JOIN
( SELECT *
, CASE WHEN roll_no = 4 THEN 2
WHEN roll_no >= 2 AND roll_no < 4 THEN roll_no + 1
ELSE roll_no END n
FROM my_table
) y
ON y.admission_no = x.admission_no
SET x.admission_no = y.n;
You'd probably want to extend this idea to deal with the fact that rows can be dragged up and down the list, so something like this...
SET #source = 1, #target = 5;
SELECT *
, CASE WHEN roll_no = GREATEST(#source,#target) THEN LEAST(#source,#target)
WHEN roll_no >= LEAST(#source,#target) AND roll_no < GREATEST(#source,#target) THEN roll_no + 1
ELSE roll_no END x
FROM my_table;

Try this below query
; with cte as (select a.AdmissionNo, a.RollNo, b.Name from student a
join student b on a.RollNo=b.RollNo+1
where a.RollNo between 3 and 4
union all
select a.AdmissionNo, a.RollNo, b.Name from student a
left join student b on a.RollNo+2=b.RollNo
where a.RollNo=2)
update a set a.Name = b.name
from student a
join cte b on a.rollno=b.rollno

Related

Update table in Postgresql by grouping rows

I want to update a table by grouping (or combining) some rows together based on a certain criteria. I basically have this table currently (I want to group by 'id_number' and 'date' and sum 'count'):
Table: foo
---------------------------------------
| id_number | date | count |
---------------------------------------
| 1 | 2001 | 1 |
| 1 | 2001 | 2 |
| 1 | 2002 | 1 |
| 2 | 2001 | 6 |
| 2 | 2003 | 12 |
| 2 | 2003 | 2 |
---------------------------------------
And I want to get this:
Table: foo
---------------------------------------
| id_number | date | count |
---------------------------------------
| 1 | 2001 | 3 |
| 1 | 2002 | 1 |
| 2 | 2001 | 6 |
| 2 | 2003 | 14 |
---------------------------------------
I know that I can easily create a new table with the pertinent info. But how can I modify an existing table like this without making a "temp" table? (Note: I have nothing against using a temporary table, I'm just interested in seeing if I can do it this way)
If you want to delete rows you can add a primary key (for distinguish rows) and use two sentences, an UPDATE for the sum and a DELETE for obtain less rows.
You can do something like this:
create table foo (
id integer primary key,
id_number integer,
date integer,
count integer
);
insert into foo values
(1, 1 , 2001 , 1 ),
(2, 1 , 2001 , 2 ),
(3, 1 , 2002 , 1 ),
(4, 2 , 2001 , 6 ),
(5, 2 , 2003 , 12 ),
(6, 2 , 2003 , 2 );
select * from foo;
update foo
set count = count_sum
from (
select id, id_number, date,
sum(count) over (partition by id_number, date) as count_sum
from foo
) foo_added
where foo.id_number = foo_added.id_number
and foo.date = foo_added.date;
delete from foo
using (
select id, id_number, date,
row_number() over (partition by id_number, date order by id) as inner_order
from foo
) foo_ranked
where foo.id = foo_ranked.id
and foo_ranked.inner_order <> 1;
select * from foo;
You can try it here: http://rextester.com/PIL12447
With only one UPDATE
(but with a trigger) you can set a NULL value in count and trigger a DELETE in that case.
create table foo (
id integer primary key,
id_number integer,
date integer,
count integer
);
create function delete_if_count_is_null() returns trigger
language plpgsql as
$BODY$
begin
if new.count is null then
delete from foo
where id = new.id;
end if;
return new;
end;
$BODY$;
create trigger delete_if_count_is_null
after update on foo
for each row
execute procedure delete_if_count_is_null();
insert into foo values
(1, 1 , 2001 , 1 ),
(2, 1 , 2001 , 2 ),
(3, 1 , 2002 , 1 ),
(4, 2 , 2001 , 6 ),
(5, 2 , 2003 , 12 ),
(6, 2 , 2003 , 2 );
select * from foo;
update foo
set count = case when inner_order = 1 then count_sum else null end
from (
select id, id_number, date,
sum(count) over (partition by id_number, date) as count_sum,
row_number() over (partition by id_number, date order by id) as inner_order
from foo
) foo_added
where foo.id_number = foo_added.id_number
and foo.date = foo_added.date
and foo.id = foo_added.id;
select * from foo;
You can try it in: http://rextester.com/MWPRG10961

SQL: Pick highest and lowest value (int) from one row

I am looking for a way to pick the highest and lowest value (integer) from a single row in table. There are 4 columns that i need to compare together and get highest and lowest number there is.
The table looks something like this...
id | name | col_to_compare1 | col_to_compare2 | col_to_compare3 | col_to_compare4
1 | John | 5 | 5 | 2 | 1
2 | Peter | 3 | 2 | 4 | 1
3 | Josh | 3 | 5 | 1 | 3
Can you help me, please? Thanks!
You can do this using CROSS APPLY and the VALUES clause. Use VALUES to group all your compared columns and then select the max.
SELECT
MAX(d.data1) as MaxOfColumns
,MIN(d.data1) as MinOfColumns
,a.id
,a.name
FROM YOURTABLE as a
CROSS APPLY (
VALUES(a.col_to_compare1)
,(a.col_to_compare2)
,(a. col_to_compare3)
,(a.col_to_compare4)
,(a. col_to_compare5)
) as d(data1) --Name the Column
GROUP BY a.id
,a.name
Assuming you are looking for min/max per row
Declare #YourTable table (id int,name varchar(50),col_to_compare1 int,col_to_compare2 int,col_to_compare3 int,col_to_compare4 int)
Insert Into #YourTable values
(1,'John',5,5,2,1),
(2,'Peter',3,2,4,1),
(3,'Josh',3,5,1,3)
Select A.ID
,A.Name
,MinVal = min(B.N)
,MaxVal = max(B.N)
From #YourTable A
Cross Apply (Select N From (values(a.col_to_compare1),(a.col_to_compare2),(a.col_to_compare3),(a.col_to_compare4)) N(N) ) B
Group By A.ID,A.Name
Returns
ID Name MinVal MaxVal
1 John 1 5
3 Josh 1 5
2 Peter 1 4
These solutions keep the current rows and add additional columns of min/max.
select *
from t cross apply
(select min(col) as min_col
,max(col) as max_col
from (
values
(t.col_to_compare1)
,(t.col_to_compare2)
,(t.col_to_compare3)
,(t.col_to_compare4)
) c(col)
) c
OR
select *
,cast ('' as xml).value ('min ((sql:column("t.col_to_compare1"),sql:column("t.col_to_compare2"),sql:column("t.col_to_compare3"),sql:column("t.col_to_compare4")))','int') as min_col
,cast ('' as xml).value ('max ((sql:column("t.col_to_compare1"),sql:column("t.col_to_compare2"),sql:column("t.col_to_compare3"),sql:column("t.col_to_compare4")))','int') as max_col
from t
+----+-------+-----------------+-----------------+-----------------+-----------------+---------+---------+
| id | name | col_to_compare1 | col_to_compare2 | col_to_compare3 | col_to_compare4 | min_col | max_col |
+----+-------+-----------------+-----------------+-----------------+-----------------+---------+---------+
| 1 | John | 5 | 5 | 2 | 1 | 1 | 5 |
+----+-------+-----------------+-----------------+-----------------+-----------------+---------+---------+
| 2 | Peter | 3 | 2 | 4 | 1 | 1 | 4 |
+----+-------+-----------------+-----------------+-----------------+-----------------+---------+---------+
| 3 | Josh | 3 | 5 | 1 | 3 | 1 | 5 |
+----+-------+-----------------+-----------------+-----------------+-----------------+---------+---------+
A way to do this is to "break" apart the data
declare #table table (id int, name varchar(10), col1 int, col2 int, col3 int, col4 int)
insert into #table values (1 , 'John' , 5 , 5 , 2 , 1)
insert into #table values (2 , 'Peter' , 3 , 2 , 4 , 1)
insert into #table values (3 , 'Josh' , 3 , 5 , 1 , 3)
;with stretch as
(
select id, col1 as col from #table
union all
select id, col2 as col from #table
union all
select id, col3 as col from #table
union all
select id, col4 as col from #table
)
select
t.id,
t.name,
agg.MinCol,
agg.MaxCol
from #table t
inner join
(
select
id, min(col) as MinCol, max(col) as MaxCol
from stretch
group by id
) agg
on t.id = agg.id
Seems simple enough
SELECT min(col1), max(col1), min(col2), max(col2), min(col3), max(col3), min(col4), max(col4) FROM table
Gives you the Min and Max for each column.
Following OP's comment, I believe he may be looking for a min/max grouped by the person being queried against.
So that would be:
SELECT name, min(col1), max(col1), min(col2), max(col2), min(col3), max(col3), min(col4), max(col4) FROM table GROUP BY name

SQL find missing language entries in table

I have a table which is missing some entries for a certain language. How can I get a list of all language text in english (lang 1 in table) which is missing the foreign translation counterpart (lang 2)
My table is as follows
PageName | LanguageNo | TranslationName | TranslationText |
main | 1 | SomeName | some english text |
main | 2 | SomeName | some foreign text |
main | 1 | SomeName2 | some english 2 |
other | 1 | SomeName3 | some english 3 |
other | 2 | SomeName3 | some foreign 3 |
For example, using the above table data, only the following should be returned:
main | 1 | SomeName2 | some english 2 |
How can I write a SQL statement to achieve this?
Thanks
You can try the following:
-- Create demo data
CREATE TABLE #translation(pageName nvarchar(10), LanguageNo int, TranslationName nvarchar(25), TranslationText nvarchar(50))
INSERT INTO #translation(pageName, LanguageNo, TranslationName, TranslationText)
VALUES ('main',1,'SomeName','some english text'),
('main',2,'SomeName','some foreign text'),
('main',1,'SomeName2','some english 2'),
('other',1,'SomeName3','some english 3'),
('other',2,'SomeName3','some foreign 3')
--,('other',3,'SomeName3','some foreign 3') -- uncomment for language3 demo
-- your work:
SELECT availTrans.*
FROM #translation t
-- get all needed combinations
RIGHT JOIN(
SELECT DISTINCT t.pageName, t.TranslationName, langs.LanguageNo
FROM #translation as t
CROSS JOIN (SELECT DISTINCT LanguageNo FROM #translation) as langs
) as availTrans
ON t.pageName = availTrans.pageName
AND t.TranslationName = availTrans.TranslationName
AND t.LanguageNo = availTrans.LanguageNo
WHERE t.pageName IS NULL
-- Cleanup
DROP TABLE #translation
Given input:
pageName LanguageNo TranslationName TranslationText
---------- ----------- ------------------------- ---------------------
main 1 SomeName some english text
main 2 SomeName some foreign text
main 1 SomeName2 some english 2
other 1 SomeName3 some english 3
other 2 SomeName3 some foreign 3
Which produces this result:
pageName TranslationName LanguageNo
---------- ------------------------- -----------
main SomeName2 2
There are several methods, but here is one that uses not exists:
select t.*
from mytable t
where t.LanguageNo = 1 and
not exists (select 1
from mytable t2
where t2.pagename = t.pagename and
t2.translationname = t.translationname and
t2.LanguageNo = 2
);
Here is a SQL Fiddle.
You can also use SUM with OVER() like this.
Query
;WITH CTE AS
(
SELECT pageName, LanguageNo, TranslationName, TranslationText,
SUM(CASE WHEN LanguageNo = 1 THEN 1 ELSE 0 END) OVER(PARTITION BY TranslationName) L1,
SUM(CASE WHEN LanguageNo = 2 THEN 1 ELSE 0 END) OVER(PARTITION BY TranslationName) L2
FROM #translation t
)
SELECT pageName, LanguageNo, TranslationName, TranslationText FROM CTE
WHERE L1 >=1 AND L2 = 0
Output
pageName LanguageNo TranslationName TranslationText
main 1 SomeName2 some english 2
SELECT t1.*
FROM tblTranslation t1
LEFT JOIN tblTranslation t2
ON t2.TranslationName = t1.TranslationName
AND t2.LanguageNo = 2
WHERE t2.TranslationName IS NULL
AND t1.LanguageNo = 1
SELECT t1.*
FROM tblTranslation t1
LEFT JOIN tblTranslation t2 ON t2.PageName = t1.PageName
AND t2.TranslationName = t1.TranslationName
AND t2.LanguageNo = 2
WHERE t1.LanguageNo = 1
AND t2.LanguageNo IS NULL

Select items by column value

I have a table in SQL used to store result like so
Result
---------------
ID | DateCreated
1 | 2014-10-10
The Items under the result above
ResultItems
---------------
ResultID | StudentID | SubjID | Test1 | Test2 | Exam
1 | 1 | 1 | 7 | 7 | 30
1 | 2 | 1 | 8 | 8 | 35
1 | 1 | 2 | 5 | 5 | 45
1 | 2 | 2 | 6 | 6 | 40
I need to select from this tables so that each subject is in its own column, with the score of each subject summed under it
Result items
Result Output
---------------
StudentID| SubjID-1 | SubjID-2
1 | 44 | 55
2 | 51 | 52
I did try quiet some queries, such as this one below, which didn't give the result I needed
SELECT r.*,
ri.StudentID,
ri.Test1,
ri.Test2,
ri.Exam,
( ri.Test1+ ri.Test2 + ri.Exam ) Total
FROM Result r
LEFT JOIN ResultItems ri
ON ri.ResultID = r.id
WHERE ri.Test1 <> '-'
AND
ri.Test2 <> '-'
AND
ri.exam <> '-';
How can I amend this query?
Edit
I read about Pivot and saw this question SQL - columns for different categories, in which case the names/id of the subject has to be know before hand, which would not work for my case
Solution for SQL Server
All you need is called Pivot.
CREATE TABLE #ResultItems
(
ResultID INT,
StudentID INT,
SubjID INT,
Test1 INT,
Test2 INT,
Exam INT
)
INSERT INTO #ResultItems (ResultID, StudentID, SubjID, Test1, Test2, Exam)
VALUES(1, 1 , 1 , 7, 7, 30),
(1, 2 , 1 , 8, 8, 35),
(1, 1 , 2 , 5, 5, 45),
(1, 2 , 2 , 6, 6, 40)
SELECT StudentId, [1], [2]
FROM (
SELECT StudentId, SubjID, Test1 + Test2 + Exam AS TmpSum
FROM #ResultItems
) AS DT
PIVOT(SUM(TmpSum) FOR SubjID IN ([1], [2])) AS PT
DROP TABLE #ResultItems
SQLite solution
Use CASE
SELECT StudentId, SUM(Subj1) AS Subj1, SUM(Subj2) As Subj2
FROM (
SELECT t1.StudentId, CASE WHEN SubjID = 1 THEN Test1 + Test2 + Exam ELSE 0 END AS Subj1,
CASE WHEN SubjID = 2 THEN Test1 + Test2 + Exam ELSE 0 END AS Subj2
FROM #ResultItems AS t1
) AS T
GROUP BY T.StudentID
or subqueries:
SELECT t1.StudentId, (SELECT Test1 + Test2 + Exam FROM #ResultItems WHERE StudentID = t1.StudentID AND SubjID = 1) AS Subj1,
(SELECT Test1 + Test2 + Exam FROM #ResultItems WHERE StudentID = t1.StudentID AND SubjID = 2) AS Subj2
FROM #ResultItems AS t1
GROUP BY t1.StudentID

Alternate of lead lag function in SQL Server 2008

I want to compare the current row with a value in the next row. SQL has LEAD and LAG functions to get the next and previous values but I can not use them because I am using SQL Server 2008.
So how do I get this?
I have table with output
+----+-------+-----------+-------------------------+
| Id | ActId | StatusId | MinStartTime |
+----+-------+-----------+-------------------------+
| 1 | 42 | 1 | 2014-02-14 11:17:21.203 |
| 2 | 42 | 1 | 2014-02-14 11:50:19.367 |
| 3 | 42 | 1 | 2014-02-14 11:50:19.380 |
| 4 | 42 | 6 | 2014-02-17 05:25:57.280 |
| 5 | 42 | 6 | 2014-02-19 06:09:33.150 |
| 6 | 42 | 1 | 2014-02-19 06:11:24.393 |
| 7 | 42 | 6 | 2014-02-19 06:11:24.410 |
| 8 | 42 | 8 | 2014-02-19 06:44:47.070 |
+----+-------+-----------+-------------------------+
What I want to do is if the current row status is 1 and the next row status is 6 and both times are the same (up to minutes) then I want to get the row where the status is 1.
Eg: Id 6 row has status 1 and Id 7 row has status 6 but both times are the same ie. 2014-02-19 06:11
So I want to get this row or id for status 1 ie. id 6
In your case, the ids appear to be numeric, you can just do a self-join:
select t.*
from table t join
table tnext
on t.id = tnext.id - 1 and
t.StatusId = 1 and
tnext.StatusId = 6 and
datediff(second, t.MinStartTime, tnext.MinStartTime) < 60;
This isn't quite the same minute. It is within 60 seconds. Do you actually need the same calendar time minute? If so, you can do:
select t.*
from table t join
table tnext
on t.id = tnext.id - 1 and
t.StatusId = 1 and
tnext.StatusId = 6 and
datediff(second, t.MinStartTime, tnext.MinStartTime) < 60 and
datepart(minute, t.MinStartTime) = datepart(minute, tnext.MinStartTime);
Just posting a more complex join using two different tables created with Gordon's foundation. Excuse the specific object names, but you'll get the gist. Gets the percentage change in samples from one to the next.
SELECT
fm0.SAMPLE curFMSample
, fm1.SAMPLE nextFMSample
, fm0.TEMPERATURE curFMTemp
, fm1.TEMPERATURE nextFMTemp
, ABS(CAST((fm0.Temperature - fm1.Temperature) AS DECIMAL(4, 0)) / CAST(fm0.TEMPERATURE AS DECIMAL(4, 0))) AS fmTempChange
, fm0.GAUGE curFMGauge
, fm1.GAUGE nextFMGauge
, ABS(CAST((fm0.GAUGE - fm1.GAUGE) AS DECIMAL(4, 4)) / CAST(fm0.GAUGE AS DECIMAL(4, 4))) AS fmGaugeChange
, fm0.WIDTH curFMWidth
, fm1.WIDTH nextFMWidth
, ABS(CAST((fm0.Width - fm1.Width) AS DECIMAL(4, 2)) / CAST(fm0.Width AS DECIMAL(4, 2))) AS fmWidthChange
, cl0.TEMPERATURE curClrTemp
, cl1.TEMPERATURE nextClrTemp
, ABS(CAST((cl0.Temperature - cl1.Temperature) AS DECIMAL(4, 0)) / CAST(cl0.TEMPERATURE AS DECIMAL(4, 0))) AS clrTempChange
FROM
dbo.COIL_FINISHING_MILL_EXIT_STR02 fm0
INNER JOIN dbo.COIL_FINISHING_MILL_EXIT_STR02 fm1 ON (fm0.SAMPLE = fm1.SAMPLE - 1 AND fm1.coil = fm0.coil)
INNER JOIN dbo.COIL_COILER_STR02 cl0 ON fm0.coil = cl0.coil AND fm0.SAMPLE = cl0.SAMPLE
INNER JOIN dbo.COIL_COILER_STR02 cl1 ON (cl0.SAMPLE = cl1.SAMPLE - 1 AND cl1.coil = cl0.coil)
WHERE
fm0.coil = 2015515872
Well, I would suggest a very simple solution if you do not have a sequential row id but a different step (if some records were deleted for example..):
declare #t table(id int, obj_name varchar(5))
insert #t select 1,'a'
insert #t select 5,'b'
insert #t select 22,'c'
insert #t select 543,'d'
---------------------------------
select *from #t
Example Source Table #t:
---------------------------------
id obj_name
1 a
5 b
22 c
543 d
---------------------------------
Select with self join
select obj_name_prev=tt.obj_name, obj_name_next=min(t.obj_name)
from #t t
join #t tt on tt.id < t.id
group by tt.obj_name
Result:
---------------------------------
obj_name_prev obj_name_next
a b
b c
c d
---------------------------------