Over Partition for set column SQL - sql

I have this table, I need set ID column = 1 for the max value of column minutes, and the rest ID column = 0.
Initial table:
Register |minutes | ID
10 |5 | 0
10 |6 | 0
10 |0 | 0
12 |3 | 0
12 |0 | 0
12 |4 | 0
Final table:
Register |minutes | ID
10 |5 | 0
10 |6 | 1
10 |0 | 0
12 |3 | 0
12 |0 | 0
12 |4 | 1
Using Over Partition, any idea ?
UPDATE A
SET ID = 1
FROM
(
Select top 1 row_number() over (PARTITION BY minutes
order by minutes asc) AS column,*
from table
)A
WHERE A.column=1

You can use row_number() in an updatable CTE:
with m as (
select *,
row_number() over(partition by register order by minutes desc) rn
from t
)
update m set id=1 where rn=1

Does this do what you want?
DECLARE #max INT
SELECT TOP 1
#max = Minutes
FROM YourTable
ORDER BY Minutes DESC
UPDATE YourTable
SET ID = CASE
WHEN Minutes = #max
THEN 1
ELSE 0
END

If you don’t want to use CTE or variable tables:
UPDATE A
SET A.ID = CASE
WHEN B.RowNumber = 1
THEN 1
ELSE 0
END
FROM table A
JOIN (
SELECT *, row_number() over (PARTITION BY Register
order by minutes DESC) AS RowNumber
FROM table
) B ON A.Register = B.Register AND A.minutes = B.minutes

I have left my previous answer in place as it represents the answer to the question as it was at the time of answering. Given the new information added to the question, the update query would be as below:
;WITH MyCTE AS
(
SELECT Register,
Minutes,
ID,
ROW_NUMBER() OVER (PARTITION BY Register ORDER BY Minutes DESC) RowN
FROM YourTable
)
UPDATE MyCTE
SET ID = CASE
WHEN RowN = 1 THEN 1
ELSE 0
END

Related

Filling in missing data in Snowflake

I have a table in Snowflake like this:
TIME USER ITEM
1 frank 1
2 frank 0
3 frank 0
4 frank 0
5 frank 2
6 alf 5
7 alf 0
8 alf 6
9 alf 0
10 alf 9
I want to be able to replace all the zeroes with the next non-zero value, so in the end I have a table like this:
TIME USER ITEM
1 frank 1
2 frank 2
3 frank 2
4 frank 2
5 frank 2
6 alf 5
7 alf 6
8 alf 6
9 alf 9
10 alf 9
How would I write a query that does that in Snowflake?
You can use conditional_change_event function for this - documented here:
with base_table as (
select
t1.*,
conditional_change_event(item) over (order by time desc) event_num
from test_table t1
order by time desc
)
select
t1.time,
t1.user,
t1.item old_item,
coalesce(t2.item, t1.item) new_item
from base_table t1
left join base_table t2 on t1.event_num = t2.event_num + 1 and t1.item = 0
order by t1.time asc
Above SQL Results:
+----+-----+--------+--------+
|TIME|USER |OLD_ITEM|NEW_ITEM|
+----+-----+--------+--------+
|1 |frank|1 |1 |
|2 |frank|0 |2 |
|3 |frank|0 |2 |
|4 |frank|0 |2 |
|5 |alf |2 |2 |
|6 |alf |5 |5 |
|7 |alf |0 |6 |
|8 |alf |6 |6 |
|9 |alf |0 |9 |
|10 |alf |9 |9 |
+----+-----+--------+--------+
You can use lead(ignore nulls):
select t.*,
(case when item = 0
then lead(nullif(item, 0) ignore nulls) over (partition by user order by time)
else item
end) as imputed_item
from t;
You can also phrase this using first_value():
select t.*,
last_value(nullif(item, 0) ignore nulls) over (partition by user order by time desc)
from t;
If you want to use first_value() or last_value() in Snowflake, please keep in mind that Snowflake supports window frames differently from the ANSI standard as documented here. This means that if you want to use the default window frame RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW you have to include it explicitly in the statement, otherwise, the default would be ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING and that is why the LAST_VALUE example from the previous answer would not work correctly. Here is one example that would work:
select t.*,
last_value(nullif(item, 0) ignore nulls) over (partition by user order by time desc rows between unbounded preceding and current row)
from t;
Nothing wrong with above solutions ... but here's a different approach ... I think it's simpler.
select * from good
union all
select
bad.time
,bad.user
,min(good.item)
from bad
left outer join
good on good.user=bad.user and good.time>bad.time
group by
1,2
Full COPY|PASTE|RUN SQL:
with cte as (
select * from (
select 1 time, 'frank' user , 1 item union
select 2 time, 'frank' user , 0 item union
select 3 time, 'frank' user , 0 item union
select 4 time, 'frank' user , 0 item union
select 5 time, 'frank' user , 2 item union
select 6 time, 'alf' user , 5 item union
select 7 time, 'alf' user , 0 item union
select 8 time, 'alf' user , 6 item union
select 9 time, 'alf' user , 0 item union
select 10 time, 'alf' user , 9) )
, good as (select * from cte where item<> 0)
, bad as (select * from cte where item= 0)
select * from good
union all
select
bad.time
,bad.user
,min(good.item )
from bad
left outer join
good on good.user=bad.user and good.time>bad.time
group by
1,2

How to bind a row from one column to another in SQL Server based on no criteria

I have a #temp table that gets me the data in the following structure
Number |MobileNumber|FirstName|LastName|Voucher|MessageContent|MessageStatus
--------+------------+---------+--------+-------+--------------+-------------
340046 |1158963214 |trisha |govender|NULL |Hello | 0
354252 |1124589630 |Peter |Ngcobo |NULL |Hello | 0
385603 |2587465974 |Chris |Pat |NULL |Hello | 0
385674 |1256559878 |chris |pat |NULL |Hello | 0
385679 |4485484656 |john |doe |NULL |hello | 0
from the following query
IF OBJECT_ID('TempDB..#results') IS NOT NULL
BEGIN
DROP TABLE #results
END
SELECT DISTINCT
t.Number,
FA.MobileNumber, FA.FirstName, FA.LastName,
CAST(null AS NVARCHAR(25)) AS Voucher,
MessageContent,
CAST(0 AS BIT) AS MessageStatus
INTO
#results
FROM // has multiple join statements in here
I have an other permanent table structured in the database as follows
Voucher | Flag
---------+-------
ush54df | 0
th545th4 | 0
cb215gt | 0
dg84gd35g| 0
dfg15rg1 | 0
I am trying to assign a single voucher for each row in the #temp results table, however I don't have any criteria from the #results table to join onto the permanent voucher table.
I am trying to achieve that when I run the query for the #results table it should add a voucher into the voucher column as follows:
Number |MobileNumber |FirstName |LastName|Voucher |MessageContent|MessageStatus
-------+-------------+----------+--------+--------+--------------+--------------
340046 |1158963214 |trisha |govender|th545th4| Hello |0
354252 |1124589630 |Peter |Ngcobo |g54er8g4| Hello |0
385603 |2587465974 |Chris |Pat |ush54df | Hello |0
385674 |1256559878 |chris |pat |dfg15rg1| Hello |0
385679 |4485484656 |john |doe |cb215gt | Hello |0
Once I know how to achieve the above then I can then be able to set the voucher flag to a value of 1 so that I wont use the same voucher twice.for example:
Voucher | Flag
---------+-------
ush54df | 1
th545th4 | 1
cb215gt | 1
dg84gd35g| 0
dfg15rg1 | 1
dg5r4g | 0
we8r78e4 | 0
g54er8g4 1
So my question is how do I assign a voucher number to a Number from the #temp results table? I tried to use ROW_NUMBER() OVER (ORDER BY (SELECT 2)) but the #temp table doesn't match the voucher table numbers, your guidance will be highly appreciated
You can do something like this:
select r.*, v.voucher
from (select r.*, row_number() over (order by newid()) as seqnum
from #results r
) r left join
(select v.*, row_number() over (order by newid()) as seqnum
from vouchers v
) v
on r.seqnum = v.seqnum;
For every row in #results, this will assign one voucher, if it exists. If the number of rows in #results is more than the number of vouchers, then the excess will have NULL.
You can actually do the assignment using update:
update r
set voucher = v.voucher
from (select r.*, row_number() over (order by newid()) as seqnum
from #results r
) r join
(select v.*, row_number() over (order by newid()) as seqnum
from vouchers v
) v
on r.seqnum = v.seqnum;

SQL select top five most recent row and distinct by a specific column

Ok, So say I have a table as picture below name appModelFlat only with a few hundred more rows. It does not have a date field but I want to find out the five most recently created environments (EnvName). There is only 14 possible environments (EnvName). But I want to select the five most recently inserted rows that inserted different EnvName. That is to say I want to select distinct EnvName (Although distinct doesn't work this way) most recent 5 rows , and I know they are the most recent by their id. The higher the id the newer the row is. Any help on this query would be appreciated.
id|AppName|EnvName|ServerTypeName|ServerId|OS |OSVersion|CPU|Memory|ExtraStorage|MachineDesc |
----------------------------------------------------------------------------------------------------
1 |ASB |DEV |App |1 |Windows|7 |4 |4 |100 |ASB-DEV-App |
----------------------------------------------------------------------------------------------------
5 |AMS |DEV |APP |2 |RedHat |7.2 |4 |4 |50 |AMS-DEV-App |
----------------------------------------------------------------------------------------------------
6 |SPB |TST |App |1 |Windows|7 |2 |8 |50 |SPB-TST-App |
----------------------------------------------------------------------------------------------------
7 |SBI |TST |Oracle |1 |Solaris|11 |4 |8 |100 |SBI-TST-Oracle|
----------------------------------------------------------------------------------------------------
Here is my first attempt although I'm not sure if it is right. It does give me five results.
SELECT DISTINCT top 5 [ID] = ( SELECT TOP 1 [ID] FROM [AppModelFlat] Y WHERE Y.[EnvName] = X.[EnvName])
,[AppName]= ( SELECT TOP 1 [AppName] FROM [AppModelFlat] Y WHERE Y.[EnvName] = X.[EnvName])
,[EnvName]
,[ServerTypeName] = ( SELECT TOP 1 [ServerTypeName] FROM [AppModelFlat] Y WHERE Y.[EnvName] = X.[EnvName])
,[ServerId] = ( SELECT TOP 1 [ServerId] FROM [AppModelFlat] Y WHERE Y.[EnvName] = X.[EnvName])
,[OS] = ( SELECT TOP 1 [OS] FROM [AppModelFlat] Y WHERE Y.[EnvName] = X.[EnvName])
FROM [AppModelFlat] X order by id desc
edit:
For expected result. Lets say I only wanted to select the top 2 since I only gave 5 entries here. I would want to get back the following.
5 |AMS |DEV |APP |2 |RedHat |7.2 |4 |4 |50 |AMS-DEV-App |
----------------------------------------------------------------------------------------------------
7 |SBI |TST |Oracle |1 |Solaris|11 |4 |8 |100 |SBI-TST-Oracle|
Because I only have one of each EnvName and each row has the highest Id number for that row.
using row_number() to get the latest row for each EnvName, and only taking the top 5 from ordered Id desc
select top 5 *
from (
select *
, rn = row_number() over (partition by EnvName order by id desc)
from appModelFlat
) s
where rn = 1
order by id desc
top with ties version:
select top 5 *
from (
select top 1 with ties *
from appModelFlat
order by row_number() over (partition by EnvName order by id desc)
) s
order by id desc
A simple sub query would also do the trick:
SELECT TOP 5 Id, AppName, EnvName, ServerTypeName, ServerId, OS
FROM AppModelFlat Records
INNER JOIN (SELECT EnvName,
MAX(Id) as Id
FROM AppModelFlat) Latest ON Records.Id = Latest.Id

Removing duplicate results

I have a view with some records, many of them are duplicated. I need to filter records and get only one from each of them.
I've tried with
SELECT TOP 1 Item, Code, Desc, '1' AS Qty FROM vwTbl1 WHERE Code = '12' OR Code = '311'
Also tried with DISTINCT but still I get all records.
but in this case it shows me only one record. Grouping by Code doesn't work.
Is there any other way how to solve this?
Item | Code | Desc | QTY
a | 12 | 1 |1
a | 311 | 2 |1
b | 12 | 3 |1
b | 311 | 4 |1
c | 1 | 5 |1
Reult should be like:
Item | Code | Desc | QTY
a | 12 | 1 |1
b | 311 | 3 |1
So for each criteria get the first record.
The typical way of doing this uses row_number():
SELECT TOP 1 Item, Code, Desc, 1 AS Qty
FROM (SELECT v.*,
ROW_NUMBER() OVER (PARTITION BY Code ORDER BY (SELECT NULL)) as seqnum
FROM vwTbl1
WHERE Code IN ('12', '311') -- don't use single quotes if these are numbers
) v
WHERE seqnum = 1;
SELECT Top 1 *
FROM
(
SELECT Item, Code, Desc, '1' AS Qty
FROM vwTbl1 WHERE Code = '12' OR Code ='311'
)A
Edited Code based on your expected result:
Declare #YourTable table (Id INT IDENTITY(1,1),Item varchar(50),Code INT,
_Desc INT,Qty INT)
Insert into #YourTable
SELECT 'a',12,1,1 UNION ALL
SELECT 'a',311,2,1 UNION ALL
SELECT 'b',12,3,1 UNION ALL
SELECT 'b',311,4,1 UNION ALL
SELECT 'c',1 ,5 ,1
SELECT Item ,A.Code , _Desc ,Qty
FROM #YourTable T
JOIN
(
SELECT MAX(Id) Id, Code FROM #YourTable GROUP BY Code
)A ON A.Id = T.Id

Grouping SQL Results based on order

I have table with data something like this:
ID | RowNumber | Data
------------------------------
1 | 1 | Data
2 | 2 | Data
3 | 3 | Data
4 | 1 | Data
5 | 2 | Data
6 | 1 | Data
7 | 2 | Data
8 | 3 | Data
9 | 4 | Data
I want to group each set of RowNumbers So that my result is something like this:
ID | RowNumber | Group | Data
--------------------------------------
1 | 1 | a | Data
2 | 2 | a | Data
3 | 3 | a | Data
4 | 1 | b | Data
5 | 2 | b | Data
6 | 1 | c | Data
7 | 2 | c | Data
8 | 3 | c | Data
9 | 4 | c | Data
The only way I know where each group starts and stops is when the RowNumber starts over. How can I accomplish this? It also needs to be fairly efficient since the table I need to do this on has 52 Million Rows.
Additional Info
ID is truly sequential, but RowNumber may not be. I think RowNumber will always begin with 1 but for example the RowNumbers for group1 could be "1,1,2,2,3,4" and for group2 they could be "1,2,4,6", etc.
For the clarified requirements in the comments
The rownumbers for group1 could be "1,1,2,2,3,4" and for group2 they
could be "1,2,4,6" ... a higher number followed by a lower would be a
new group.
A SQL Server 2012 solution could be as follows.
Use LAG to access the previous row and set a flag to 1 if that row is the start of a new group or 0 otherwise.
Calculate a running sum of these flags to use as the grouping value.
Code
WITH T1 AS
(
SELECT *,
LAG(RowNumber) OVER (ORDER BY ID) AS PrevRowNumber
FROM YourTable
), T2 AS
(
SELECT *,
IIF(PrevRowNumber IS NULL OR PrevRowNumber > RowNumber, 1, 0) AS NewGroup
FROM T1
)
SELECT ID,
RowNumber,
Data,
SUM(NewGroup) OVER (ORDER BY ID
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS Grp
FROM T2
SQL Fiddle
Assuming ID is the clustered index the plan for this has one scan against YourTable and avoids any sort operations.
If the ids are truly sequential, you can do:
select t.*,
(id - rowNumber) as grp
from t
Also you can use recursive CTE
;WITH cte AS
(
SELECT ID, RowNumber, Data, 1 AS [Group]
FROM dbo.test1
WHERE ID = 1
UNION ALL
SELECT t.ID, t.RowNumber, t.Data,
CASE WHEN t.RowNumber != 1 THEN c.[Group] ELSE c.[Group] + 1 END
FROM dbo.test1 t JOIN cte c ON t.ID = c.ID + 1
)
SELECT *
FROM cte
Demo on SQLFiddle
How about:
select ID, RowNumber, Data, dense_rank() over (order by grp) as Grp
from (
select *, (select min(ID) from [Your Table] where ID > t.ID and RowNumber = 1) as grp
from [Your Table] t
) t
order by ID
This should work on SQL 2005. You could also use rank() instead if you don't care about consecutive numbers.