Update based on group by, top 1 row and where case - sql

I have a table as follows:
This is a result of this select:
SELECT ParentID, ID, [Default], IsOnTop, OrderBy
FROM [table]
WHERE ParentID IN (SELECT ParentID
FROM [table]
GROUP BY ParentID
HAVING SUM([Default]) <> 1)
ORDER BY ParentID
Now, what I want to do is to: for each ParentID group, set one of the rows as a Default ([Default] = 1), where the row is chosen using this logic:
if group has a row with IsOnTop = 1 then take this row, otherwise take top 1 row ordered by OrderBy.
I'm completly clueless as on how to do that in SQL and I have over 40 of such groups, thus I'd like to ask you for help, preferably with some explanation of your query.

Just slightly modify your current query by assigning a row number, across each ParentID group. The ordering logic for the row number assignment is that records with IsOnTop values of 1 come first, and after that the OrderBy column determines position. I update the CTE under the condition that only the first record in each ParentID group gets assigned a Default value of 1.
WITH cte AS (
SELECT ParentID, ID, [Default], IsOnTop, OrderBy,
ROW_NUMBER() OVER (PARTITION BY ParentID
ORDER BY IsOnTop DESC, OrderBy) rn
FROM [table]
WHERE ParentID IN (SELECT ParentID FROM [table]
GROUP BY ParentID HAVING SUM([Default]) <> 1)
)
UPDATE cte
SET [Default] = 1
WHERE rn = 1;

There might be a quicker way but this is how I would do it.
First create a CTE
First we create a CTE in which we add a row_number over the ParentID's based on if IsOnTop = 1. Else it picks the 1st row based on the OrderBy column.
Then we update the rows with the rownumber 1.
WITH FindSoonToBeDefault AS (
SELECT ParentID, ID, [Default], IsOnTop, OrderBy, row_number() OVER(PARTITION BY ParentID ORDER BY IsOnTop DESC, [OrderBy] ASC) AS [rn]
FROM [table]
WHERE ParentID IN (SELECT ParentID
FROM [table]
GROUP BY ParentID
HAVING SUM([Default]) <> 1)
ORDER BY ParentID
)
UPDATE FindSoonToBeDefault
SET [Default] = 1
WHERE [rn] = 1
In your screenshot row 12 will be default.
Row 13 will be not.

(1-IsOnTop)*OrderBy combines IsOnTop and OrderBy into a single result that can be ranked so that the lowest value is the one you want. Use a derived table to identify the lowest result for each ParentID, thenJOIN to that to identify your defaults.
UPDATE [table]
SET [Default] = 1
FROM [table]
INNER JOIN
(
SELECT ParentID, MIN((1-IsOnTop)*OrderBy) DefaultRank
FROM [table]
GROUP BY ParentID
) AS rankForDefault
ON rankForDefault.ParentID=[table].ParentID
AND rankForDefault.DefaultRank=(1-[table].IsOnTop)*[table].OrderBy

Related

SQL sort by, order by case when, item to specific position

How to put item in a specific position?
I'd like to put item on a 5th position:
`id`='randomId243'
So far my sorting looks like that:
ORDER BY
CASE
WHEN `id` = 'randomId123' THEN 0
WHEN `id` = 'randomId098' THEN 1
ELSE 2
END, `name` ASC
I don't know yet which id will be in position 2,3,4. I'd prefer to avoid run another query/subquery to get ids of items from position 2-4
So final order should be like this:
randomId123
randomId098
just item alphabetical
just item alphabetical
just item alphabetical
randomId243
just item alphabetical
Use ROW_NUMBER() window function for your current ordering to rank the rows initially.
Then create 3 groups of rows: the rows on top, the row with 'item5' and the rows below 'item5' which will be assembled with UNION ALL and sorted by group and row number:
WITH cte AS (
SELECT *,
ROW_NUMBER() OVER (ORDER BY CASE id WHEN 'item0' THEN 0 WHEN 'item1' THEN 1 ELSE 2 END, name) AS rn
FROM tablename
)
SELECT id, name
FROM (
SELECT * FROM (SELECT *, 1 grp FROM cte WHERE id <> 'item5' ORDER BY rn LIMIT 5)
UNION ALL
SELECT *, 2 FROM cte WHERE id = 'item5'
UNION ALL
SELECT * FROM (SELECT *, 3 FROM cte WHERE id <> 'item5' ORDER BY rn LIMIT -1 OFFSET 5) -- negative limit means there is no limit
)
ORDER BY grp, rn;
Note that instead of:
ORDER BY CASE id WHEN 'item0' THEN 0 WHEN 'item1' THEN 1 ELSE 2 END, name
you could use:
ORDER BY id = 'item0' DESC, id = 'item1' DESC, name
See a simplified demo.

How to group and pick only certain values based on a field using select query SQL

I have a table as follow
ID
ORDERNO
1
123
1
123
2
456
2
456
During every select query done via application using JDBC, only the grouped records based on ORDERNO should be picked.
That means, for example, during first select query only details related to ID = 1, but we cannot specify the ID number in where clause because we do not know how many number of IDs will be there in future. So the query should yield only one set of records; application will delete those records after picking, hence next select query will result in picking other set of records. How to achieve it?
You can use TOP WITH TIES for this
SELECT TOP (1) WITH TIES
t.ID,
t.ORDERNO
FROM YourTable t
ORDER BY
t.ID;
If you want to select and delete at the same time you could delete using an OUTPUT clause
WITH cte AS (
SELECT TOP (1) WITH TIES
t.ID,
t.ORDERNO
FROM YourTable t
ORDER BY
t.ID
)
DELETE cte
OUTPUT deleted.*;
As one option you could select on the MIN(ID) like:
SELECT *
FROM yourtable
WHERE ID = (SELECT MIN(ID) FROM yourtable);
You could also use window functions to do this:
SELECT ID, ORDERNO
FROM
(
SELECT ID, ORDERNO
DENSE_RANK() OVER (ORDER BY ID ASC) AS dr
FROM yourtable
)dt
WHERE dr = 1;
order your rows and select top n number of rows that you want :
select top (1) with ties ID, ORDERNO
from tablename
order by ID asc

Update a column to the row number in a group by SQL

The current database I am working with has a table where the defined positions have gone out of order.
I have the following query which can allow me to update the position to the Row Number (RN) but only for a single group of objects with the ParentId
SELECT id, ParentId, Position, Title, -1+ ROW_NUMBER() OVER (ORDER BY [Position]) as RN
FROM Objects
where ParentId = 4390
However there are multiple ParentId Groups.
My question is how can this query be applied to the entire Object table while keeping these groupings and row number increments correct?
I know I could use a cursor to loop through a list of the ParentIds but this feels very inefficient.
You need to add a PARTITION BY
SELECT id
,ParentId
,Position
,Title
,-1+ ROW_NUMBER() OVER (PARTITION BY ParentId ORDER BY [Position]) as RN
FROM Objects
to update i would use a CTE
WITH CTE AS (
SELECT id
,ParentId
,Position
,Title
,-1+ ROW_NUMBER() OVER (PARTITION BY ParentId ORDER BY [Position]) as NewPosition
FROM Objects
)
UPDATE CTE
SET Position = NewPosition
WHERE Position <> NewPosition

SQL query null value replaced based on another

So I have query to return data and a row number using ROW_NUMBER() OVER(PARTITION BY) and I place it into a temp table. The initial output looks the screenshot:
.
From here I need to, in the bt_newlabel column, replace the nulls respectively. So Rownumber 1-4 would be in progress, 5-9 would be underwriting, 10-13 would be implementation, and so forth.
I am hitting a wall trying to determine how to do this. Thanks for any help or input of how I would go about this.
One method is to assign groups, and then the value. Such as:
select t.*, max(bt_newlabel) over (partition by grp) as new_newlabel
from (select t.*, count(bt_newlabel) over (order by bt_stamp) as grp
from t
) t;
The group is simply the number of known values previously seen in the data.
You can update the field with:
with toupdate as (
select t.*, max(bt_newlabel) over (partition by grp) as new_newlabel
from (select t.*, count(bt_newlabel) over (order by bt_stamp) as grp
from t
) t
)
update toupdate
set bt_newlabel = new_newlabel
where bt_newlabel is null;
If I understood what you are trying to do, this is the type of update you need to do on your temp table:
--This will update rows 1-4 to 'Pre-Underwritting'
UPDATE temp_table SET bt_newlabel = 'Pre-Underwritting'
WHERE rownumber between
1 AND (SELECT TOP 1 rownumber FROM temp_table WHERE bt_oldlabel = 'Pre-Underwritting');
--This will update rows 5-9 to 'Underwritting'
UPDATE temp_table SET bt_newlabel = 'Underwritting'
WHERE rownumber between
(SELECT TOP 1 rownumber FROM temp_table WHERE bt_oldlabel = 'Pre-Underwritting')
AND
(SELECT TOP 1 rownumber FROM temp_table WHERE bt_oldlabel = 'Underwritting');
--This will update rows 10-13 to 'Implementation'
UPDATE temp_table SET bt_newlabel = 'Implementation'
WHERE rownumber between
(SELECT TOP 1 rownumber FROM temp_table WHERE bt_oldlabel = 'Underwritting')
AND
(SELECT TOP 1 rownumber FROM temp_table WHERE bt_oldlabel = 'Implementation');
I made a working Fiddle to check out the results: http://sqlfiddle.com/#!18/1cae2/1/3

How to update distinct column in SQL Server 2005?

In my table I have a column Segment_No which has duplicates. Now I want to update those duplicates.
For example: the value 249X5601 is present in two rows. I want to change the second value to 249X5601R
The general form would be as follows:
;with AllRows as (
select Donation_ID,Registration_No,Segment_No,
ROW_NUMBER() OVER (
PARTITION BY Segment_No
order by <Suitable_Column>
) as rn
from UnnamedTable
)
update AllRows set Segment_no = <New_Value>
where rn > 1
Where <Suitable_Column> gives the column(s) the define which row is "first" and which is second. <New_Value> defines how the new Segment_no value should be computed, and rn gives the row numbers - so the where clause is ignoring the "first" row.
So if there are only ever a max of two rows sharing a single Segment_no value, and the "first" is the one with the lowest Donation_ID value, then it would be:
;with AllRows as (
select Donation_ID,Registration_No,Segment_No,
ROW_NUMBER() OVER (
PARTITION BY Segment_No
order by Donation_ID
) as rn
from UnnamedTable
)
update AllRows set Segment_no = Segment_no + 'R'
where rn > 1