how to normalize / update a "order" column - sql

i have a table "mydata" with some data data :
id name position
===========================
4 foo -3
6 bar -2
1 baz -1
3 knork -1
5 lift 0
2 pitcher 0
i fetch the table ordered using order by position ASC;
the position column value may be non unique (for some reason not described here :-) and is used to provide a custom order during SELECT.
what i want to do :
i want to normalize the table column "position" by associating a unique position to each row which doesnt destroy the order. furthermore the highest position after normalising should be -1.
wished resulting table contents :
id name position
===========================
4 foo -6
6 bar -5
1 baz -4
3 knork -3
5 lift -2
2 pitcher -1
i tried several ways but failed to implement the correct update statement.
i guess that using
generate_series( -(select count(*) from mydata), -1)
is a good starting point to get the new values for the position column but i have no clue how to merge that generated column data into the update statement.
hope somebody can help me out :-)

Something like:
with renumber as (
select id,
-1 * row_number() over (order by position desc, id) as rn
from foo
)
update foo
set position = r.rn
from renumber r
where foo.id = r.id
and position <> r.rn;
SQLFiddle Demo

Try this one -
Query:
CREATE TABLE temp
(
id INT
, name VARCHAR(10)
, position INT
)
INSERT INTO temp (id, name, position)
VALUES
(4, 'foo', -3),
(6, 'bar', -2),
(1, 'baz', -1),
(3, 'knork', -1),
(5, 'lift', 0),
(2, 'pitcher', 0)
SELECT
id
, name
, position = -ROW_NUMBER() OVER (ORDER BY position DESC, id)
FROM temp
ORDER BY position
Update:
UPDATE temp
SET position = t.rn
FROM (
SELECT id, rn = - ROW_NUMBER() OVER (ORDER BY position DESC, id)
FROM temp
) t
WHERE temp.id = t.id
Output:
id name position
----------- ---------- --------------------
4 foo -6
6 bar -5
3 knork -4
1 baz -3
5 lift -2
2 pitcher -1

#a_horse_with_no_name is really near the truth - thank you !
UPDATE temp
SET position=t.rn
FROM (SELECT
id, name,
-((select count( *)
FROM temp)
+1-row_number() OVER (ORDER BY position ASC)) as rn
FROM temp) t
WHERE temp.id=t.id;
SELECT * FROM temp ORDER BY position ASC;
see http://sqlfiddle.com/#!1/d1770/6

update mydata temp1, (select a.*,#var:=#var-1 sno from mydata a, (select #var:=0) b
order by position desc, id asc) temp2
set temp1.position = temp2.sno
where temp1.id = temp2.id;

Related

SQL Query for sorting in particular order

I have this data:
parent_id
comment_id
comment_level
NULL
0xB2
1
NULL
0xB6
1
NULL
0xBA
1
NULL
0xBE
1
NULL
0xC110
1
NULL
0xC130
1
123
0xC13580
2
456
0xC135AC
3
123
0xC13680
2
I want the result in such a way that rows where comment_level=1 should be in descending order by comment_id and other rows(i.e. where comment_level!=1) should be in ascending order by comment_id but the order of comment level greater than 1 should be inserted according to to order of comment level 1 (what I mean is that rows with comment_level=1 should remain in descending order and then rows with comment_level!=1 should be inserted in increasing order but it should be inserted following rows where comment_id is less than it)
Result should look like this
NULL 0xC130 1
123 0xC13580 2
456 0xC135AC 3
123 0xC13680 2
NULL 0xC110 1
NULL 0xBE 1
NULL 0xBA 1
NULL 0xB6 1
NULL 0xB2 1
Note the bold rows in above sort by comment_id in ascending order, but they come after their "main" row (with comment_level = 1), where these main rows sort DESC by comment_id.
I tried creating 2 tables for different comment level and used sorting for union but it didn't work out because 2 different order by doesn't work maybe I tried from this Using different order by with union but it gave me an error and after all even if this worked it still might not have given me the whole answer.
I think I understand what you're going for, and a UNION will not be able to do it.
To accomplish this, each row needs to match with a specific "parent" row that does have 1 for the comment_level. If the comment_level is already 1, the row is its own parent. Then we can sort first by the comment_id from that parent record DESC, and then sort ascending by the local comment_id within the a given group of matching parent records.
You'll need something like this:
SELECT t0.*
FROM [MyTable] t0
CROSS APPLY (
SELECT TOP 1 comment_id
FROM [MyTable] t1
WHERE t1.comment_level = 1 AND t1.comment_id <= t0.comment_id
ORDER BY t1.comment_id DESC
) parents
ORDER BY parents.comment_id DESC,
case when t0.comment_level = 1 then 0 else 1 end,
t0.comment_id
See it work here:
https://dbfiddle.uk/qZBb3YjO
There's probably also a solution using a windowing function that will be more efficient.
And here it is:
SELECT parent_id, comment_id, comment_level
FROM (
SELECT t0.*, t1.comment_id as t1_comment_id
, row_number() OVER (PARTITION BY t0.comment_id ORDER BY t1.comment_id desc) rn
FROM [MyTable] t0
LEFT JOIN [MyTable] t1 ON t1.comment_level = 1 and t1.comment_id <= t0.comment_id
) d
WHERE rn = 1
ORDER BY t1_comment_id DESC,
case when comment_level = 1 then 0 else 1 end,
comment_id
See it here:
https://dbfiddle.uk/me1vGNdM
Those varbinary values aren't arbitrary, they're hierarchyid values. And just a guess, they're probably typed that way in the schema (it's too much to be a coincidence). We can use this fact to do what we're looking to do.
with d_raw as (
select * from (values
(NULL, 0xC130 , 1),
(123 , 0xC13580, 2),
(456 , 0xC135AC, 3),
(123 , 0xC13680, 2),
(NULL, 0xC110 , 1),
(NULL, 0xBE , 1),
(NULL, 0xBA , 1),
(NULL, 0xB6 , 1),
(NULL, 0xB2 , 1)
) as x(parent_id, comment_id, comment_level)
),
d as (
select parent_id, comment_id = cast(comment_id as hierarchyid), comment_level
from d_raw
)
select *,
comment_id.ToString(),
comment_id.GetAncestor(comment_id.GetLevel() - 1).ToString()
from d
order by comment_id.GetAncestor(comment_id.GetLevel() - 1) desc,
comment_id
Note - the CTEs that I'm using are just to get the data into the right format and the last SELECT adds additional columns just to show what's going on; you can omit them from your query without consequence. I think the only interesting part of this solution is using comment_id.GetAncestor(comment_id.GetLevel() - 1) to get the root-level node.
One way to do this and possibly the only one is create two separate queries and union them together. Something like:
(select * from table where comment_level = 1 order by comment_id desc)
union
(select * from table where not comment_level = 1 order by comment_id asc)

Select first occurrence of value after last occurrence of other value in SQL

I have a requirement to find the current operation of a part. The table I have to get this information from lists operation statuses of complete (1) or 0. So the table typically looks like:
ID Operation Status
1 100 1
2 200 1
3 250 1
4 300 0
5 350 0
So in this case Operation 300 is the current op which I get using MIN(Operation) WHERE Status = 0.
However, some cases have appeared where some operations are skipped which would look like:
ID Operation Status
1 100 1
2 200 0
3 250 1
4 300 0
5 350 0
So in this case the current operation is still Operation 300 but MIN(Operation) doesn't work. What I need is the first occurrence of the row where Status = 0 that follows the last occurrence of a Status = 1 row. How could I achieve this?
Edit: Also have to consider the case where all operations are Status 0, where the correct result would be the first row (Operation 100)
This will give you the entire row to work with:
DECLARE #MyTable TABLE (
ID INT,
Operation INT,
Status BIT
);
INSERT INTO #MyTable VALUES
(1, 100, 1)
,(2, 200, 0)
,(3, 250, 1)
,(4, 300, 0)
,(5, 350, 0)
;
WITH MaxOperation AS (
SELECT MAX(x.Operation) AS MaxOperation
FROM #MyTable x
WHERE x.Status = 1
)
SELECT TOP 1 t.*
FROM #MyTable t
CROSS APPLY (SELECT MaxOperation FROM MaxOperation) x
WHERE t.Operation > x.MaxOperation
OR x.MaxOperation IS NULL
ORDER BY t.Operation
This will result in:
ID Operation Status
----------- ----------- ------
4 300 0
It will also produce this if all the Status values are 0:
ID Operation Status
----------- ----------- ------
1 100 0
I'm sure there is a clever window function way to do it, but in vanilla sql this is the idea
SELECT MIN(Operation)
FROM SOME_TABLE
WHERE Operation >
( SELECT MAX(Operation)
FROM SOME_TABLE
WHERE status = 1
)
As indicated by user Error_2646, a good way would be something like
select
min(ID)
from
[YourTable]
where
ID > (select max(ID) from [YourTable] where Status = 1)
I hope this answer will give you the correct answer. If you can add the expected output in a image it is more easy to identify what you need. Please add schema and data when, so that it is easy for user to put their solutions.
Schema and data I used:
(
ID INT
,operation INT
,Status INT
)
insert into Occurances values(1,100,1)
insert into Occurances values(2,200,0)
insert into Occurances values(1,250,1)
insert into Occurances values(1,300,0)
insert into Occurances values(1,350,0)
SELECT *
FROM
(
SELECT
Rank() OVER ( ORDER BY operation) AS [rank]
,MIN([operation]) AS [min]
,id
,[status]
FROM Occurances
WHERE [Status]= 0
GROUP BY id
,[status]
,operation
UNION
SELECT
Rank() OVER ( ORDER BY operation DESC) AS [rank]
,MAX([operation]) AS [min]
,id
,[status]
FROM Occurances
WHERE [Status]= 1
GROUP BY id
,[status]
,operation
) AS A
WHERE A.[rank]= 1
This is the answer I am getting:
You can do this very efficiently with a window function:
SELECT TOP (1) *
FROM (
SELECT *, LEAD(Status) OVER (ORDER BY Operation DESC) AS PreviousStatus
FROM myTable
)
WHERE Status = 0 AND PreviousStatus = 1
ORDER BY Operation DESC
Try this:
DECLARE #Table TABLE
(
ID int
, Operation int
, [Status] bit
)
;
INSERT INTO #Table (ID, Operation, [Status])
VALUES
(1, 100, 1)
, (2, 200, 0)
, (3, 250, 1)
, (4, 300, 1)
, (5, 350, 0)
;
SELECT TOP 1 T.*
FROM #Table T
WHERE T.[Status] = 0
AND T.ID > (
SELECT TOP 1 T.ID
FROM #Table T
WHERE T.[Status] = 1
ORDER BY ID DESC
)
ORDER BY ID

How to get the result in below format in SQL Server?

I have a table called recipes with following data.
page_no title
-----------------
1 pancake
2 pizza
3 pasta
5 cookie
page_no 0 is always blank, and missing page_no are blank, I want output as below, for the blank page NULL values in the result.
left_title right_title
------------------------
NULL pancake
Pizza pasta
NULL cookie
I have tried this SQL statement, but it's not returning the desired output:
SELECT
CASE WHEN id % 2 = 0
THEN title
END AS left_title,
CASE WHEN id %2 != 0
THEN title
END AS right_title
FROM
recipes
You are quite close. You just need aggregation:
select max(case when id % 2 = 0 then title end) as left_title,
max(case when id % 2 = 1 then title end) as right_title
from recipes
group by id / 2
order by min(id);
SQL Server does integer division, so id / 2 is always an integer.
Using CTE.. this should be give you a good CTE overview
DECLARE #table TABLE (
pageno int,
title varchar(30)
)
INSERT INTO #table
VALUES (1, 'pancake')
, (2, 'pizza')
, (3, 'pasta')
, (5, 'cookie')
;
WITH cte_pages
AS ( -- generate page numbers
SELECT
0 n,
MAX(pageno) maxpgno
FROM #table
UNION ALL
SELECT
n + 1 n,
maxpgno
FROM cte_pages
WHERE n <= maxpgno),
cte_left
AS ( --- even
SELECT
n,
ROW_NUMBER() OVER (ORDER BY n) rn
FROM cte_pages
WHERE n % 2 = 0),
cte_right
AS ( --- odd
SELECT
n,
ROW_NUMBER() OVER (ORDER BY n) rn
FROM cte_pages
WHERE n % 2 <> 0)
SELECT
tl.title left_title,
tr.title right_title --- final output
FROM cte_left l
INNER JOIN cte_right r
ON l.rn = r.rn
LEFT OUTER JOIN #table tl
ON tl.pageno = l.n
LEFT OUTER JOIN #table tr
ON tr.pageno = r.n

Sql query solution needed

I have a table in the following format
ID class indicator
1 A Y
1 B N
1 C(recent) N
2 X N
2 K(recent) N
if a particular ID has its recent class indicator as N then search if it was Y somewhere before that recent entry if yes then answer should be that class if not then the recent entry is the answer.
Example: for ID 1 the recent entry was C but we got a Y at A also so we have to return A but in ID 2, K is the recent entry and we don't have any Y indicator before that so we should return Y.
One approach is to sort the entries for each ID according to this logic, assign a number to each with row_number() and take just the first:
SELECT id, class, indicator
FROM (SELECT id, class, indicator,
ROW_NUMBER() OVER (PARTITION BY id
ORDER BY indicator DESC,
CASE WHEN class LIKE '%(recent)' THEN 0
ELSE 1
END ASC) AS rn
FROM mytable) t
WHERE rn = 1
in your comment with Mureinik's answer you mention you want to use a datetime column in stead of recentin the class column.
One way to do this is like below, but this will not perform great if you have really many records
your table would look sometimes like this in that case :
ID Class Indicator IndicatorDate
-- ----- --------- -------------
1 A Y 31/03/2018 12:59:18
1 B N 1/04/2018 12:59:18
1 C N 3/04/2018 12:59:18
2 X N 3/04/2018 12:59:18
2 K N 2/04/2018 12:59:18
and a possible solution would be this
declare #table table (ID int, Class varchar(1), Indicator varchar(1), IndicatorDate datetime)
insert into #Table(ID, Class, Indicator, IndicatorDate)
values (1, 'A', 'Y', getdate() - 3), (1, 'B', 'N', getdate() - 2), (1, 'C', 'N', getdate()), (2, 'X', 'N', getdate()), (2, 'K', 'N', getdate() - 1)
select td.ID,
(select top 1 t2.Class from #table t2 where t2.ID = td.ID order by t2.Indicator desc, t2.IndicatorDate desc) as selectedClass,
(select top 1 t2.Indicator from #table t2 where t2.ID = td.ID order by t2.Indicator desc, t2.IndicatorDate desc) as selectedIndicator
from
( select distinct t.ID
from #table t
) td
which would return this
ID selectedClass selectedIndicator
-- ------------- -----------------
1 A Y
2 X N
HOWEVER:
This depends on if the endresult of this query is really what you are looking for, its still not entirely clear to me

i want to display data according to type in same query

i have following table "vehicle_data" :
ID ALERT_TYPE VALUE
58 2 1
58 1 1
104 1 1
104 2 1
Here alert_type = 2 is for GPS value and alert_type=1 is for engine_value .
so if alert_type=2 and its value is =1 then it means its value is correct.
when alert_type=2 and its value is =0 then it means its value is wrong.
same for alert_type=1
so now here i want the following output:
ID gps engine_value
58 1 1
104 1 1
how can i perform this query??
You can do it like this.
SELECT ID
,CASE WHEN [ALERT_TYPE]=2 and [value ]=1 THEN 1 ELSE 0 END as gps
,CASE WHEN [ALERT_TYPE]=1 and [value ]=1 THEN 1 ELSE 0 END as engine
FROM vehicle_data
SELECT ID, alert_type=2 AS gps, alert_type=1 AS [engine] FROM vehicle_data WHERE value=1;
EDITED to account for your explanation of VALUE.
Schema
CREATE TABLE table3 (id int, ALERT_TYPE int)
INSERT table3 VALUES (58, 1), (58, 2), (104, 1), (104, 2)
Query
SELECT *
FROM (
SELECT ID
,ROW_NUMBER() OVER (
PARTITION BY id ORDER BY id
) AS row_num
,gps = CASE
WHEN ALERT_TYPE = 2
THEN 1
ELSE 0
END
,engine = CASE
WHEN ALERT_TYPE = 1
THEN 1
ELSE 0
END
FROM table3
) a
WHERE a.row_num = 1
Output
ID gps engine
58 1 0
104 0 1
One possible way using subqueries :
select
Ids.ID
, gps.VALUE 'gps'
, engine_value.VALUE 'engine_value'
from (select distinct ID from vehicle_data) Ids
left join
(select ID, VALUE from vehicle_data where ALERT_TYPE = 2) gps
on gps.ID = Ids.ID
left join
(select ID, VALUE from vehicle_data where ALERT_TYPE = 1) engine_value
on engine_value.ID = Ids.ID
[SQL Fiddle demo]
I hope this should work for you,
Select ID,sum(gps) as gps ,sum(engine) as engine from
(SELECT ID
,CASE WHEN [ALERT_TYPE]=2 and [value ]=1 THEN 1 ELSE 0 END as gps
,CASE WHEN [ALERT_TYPE]=1 and [value ]=1 THEN 1 ELSE 0 END as engine
FROM vehicle_data
)a
group by id
select x.id,x.alert_type as GPS,x.value as engine_value from (
select ID,alert_type,value,ROW_NUMBER() over (partition by id order by alert_type ) as Rnk from mytable
)x
where Rnk=1
Please check this query in SQL :
create table mytable (id int, alert_type int, value int);
insert into mytable (id, alert_type, value)
values (58, 2, 1),
(58, 1, 1),
(104, 1, 1),
(104, 2, 1);
SELECT distinct ID
,(select count (id) from mytable mt where mt.id=mytable.id and mt.[ALERT_TYPE]=2 and mt.[value ]=1) as gps
,(select count (id) from mytable mt where mt.id=mytable.id and mt.[ALERT_TYPE]=1 and mt.[value ]=1) as engine
FROM mytable
BASED ON YOUR QUESTION I BELIEVE YOU WANT THE DATA IN COLUMN AND TO SUIT YOUR REQUIREMENT I HAVE MADE A SQL FIDDLE WORKING - CODE IS ALSO MENTIONED BELOW -
HERE YOU GO WITH THE WORKING FIDDLE -
WORKING DEMO
SQL CODE FOR REFERNECE -
CREATE TABLE ALERTS (ID INT, ALERT_TYPE INT, VALUE INT)
INSERT INTO ALERTS VALUES (58,2,1)
INSERT INTO ALERTS VALUES (58,1,0)
INSERT INTO ALERTS VALUES (104,1,1)
INSERT INTO ALERTS VALUES (104,2,0)
CREATE TABLE ALERTSVALUE (ID INT, gps INT,engine INT)
INSERT INTO ALERTSVALUE VALUES (58,1,0)
INSERT INTO ALERTSVALUE VALUES (104,0,1)
SELECT A.ID,
CASE A.ALERT_TYPE WHEN 2 THEN 1 ELSE 0 END AS GPS,
CASE A.ALERT_TYPE WHEN 1 THEN 1 ELSE 0 END AS ENGINE_VALUE,
A.VALUE FROM ALERTS A WHERE A.VALUE = 1
EDIT BASED ON COMMENT - TO MERGE THE ROWS FOR BOTH GPS AND ENGINE_VALUE:
SELECT X.ID,X.ALERT_TYPE as GPS,X.VALUE as ENGINE_VALUE
FROM (
SELECT ID,ALERT_TYPE ,VALUE ,ROW_NUMBER() OVER (PARTITION BY ID ORDER BY alert_type ) AS [Rank] FROM ALERTS
)X
WHERE [Rank]=1
SQL FIDDLE DEMO