Create Sequence only for flag N ' - sql

I want to create a row number when flag='n' only Or I am okay to sort the data by flags and rank them.
For example:
+-----+-------+-------+
| ID | Flag1 | Flag2 |
+-----+-------+-------+
| 100 | N | N |
| 100 | N | N |
| 100 | Y | N |
| 100 | N | Y |
| 101 | N | N |
| 101 | N | Y |
+-----+-------+-------+
Output:
+---------+-----+-------+-------+
| Seq_num | ID | flag1 | flag2 |
+---------+-----+-------+-------+
| 1 | 100 | N | N |
| 2 | 100 | N | N |
| 3 | 100 | Y | N |
| 4 | 100 | N | Y |
| 1 | 101 | N | N |
| 2 | 101 | N | Y |
+---------+-----+-------+-------+
I have written a query using row_number() and partition by , but this does not check for flags.
Basically, I need to first sort the data by flags. and if either of the flags or both are Y then sort them last.
how can I do this ?

You are on the right track with row_number() and partition by; the following query should work:
declare #tmp table(ID int, Flag1 char(1), Flag2 char(1))
insert into #tmp values
(100, 'N','N')
,(100, 'N','N')
,(100, 'Y','N')
,(100, 'N','Y')
,(101, 'N','N')
,(101, 'N','Y')
select row_number() over(partition by ID order by id, flag2, flag1) as Seq_num,
ID,
flag1,
flag2
from #tmp
Results:

Related

SQL Select random rows partitioned by a column

I have a dataset looks like this
| Country | id |
-------------------
| a | 5 |
| a | 1 |
| a | 2 |
| b | 1 |
| b | 5 |
| b | 4 |
| b | 7 |
| c | 5 |
| c | 1 |
| c | 2 |
and i need a query which returns 2 random values from where country in ('a', 'c'):
| Country | id |
------------------
| a | 2 | -- Two random rows from Country = 'a'
| a | 1 |
| c | 1 |
| c | 5 | --Two random rows from Country = 'c'
This should work:
select Country, id from
(select Country,
id,
row_number() over(partition by Country order by rand()) as rn
from table_name
) t
where Country in ('a', 'c') and rn <= 2
Replace rand() with random() if you're using Postgres or newid() in SQL Server.

SQL group by value depending on an interval given by the min and max of a date field

I have following table given:
----------------------------
| x | y | date |
----------------------------
| 1 | 1 | 01.01.2000 |
| 1 | 1 | 02.01.2000 |
| 1 | 1 | 03.01.2000 |
| 1 | 2 | 04.01.2000 |
| 1 | 2 | 05.01.2000 |
| 1 | 2 | 06.01.2000 |
| 1 | 1 | 07.01.2000 |
| 1 | 1 | 08.01.2000 |
| 1 | 1 | 09.01.2000 |
----------------------------
Now i need to group the table depending on both y and x values, depending on the resulting interval given by the date column:
-----------------------------------------
| x | y | min | max |
-----------------------------------------
| 1 | 1 | 01.01.2000 | 03.01.2000 |
| 1 | 2 | 04.01.2000 | 06.01.2000 |
| 1 | 1 | 07.01.2000 | 09.01.2000 |
-----------------------------------------
Just grouping y will result in a wrong result, since there is the possibility that the y value switches back to a previous state as stated in the example.
Try
select x,y, min(dat), max(dat)
from (
select x,y, dat, row_number() over(order by dat) - row_number() over(partition by x, y order by dat) as grp
from mytable
)
group by x,y, grp
order by min(dat), x,y
This is an old trick, row_number( ..)-row_number(partition..) keeps the same value till partitioned data do not change and changes the value when x,y change. So with x,y this computed grp identifies every group of the same x,y.

Get Multiple rows from multiple values (columns) in SQL Server 2008 R2

I have this table
+-----+------+--------+--------+
| ID | Name | Start | End |
+-----+------+--------+--------+
| 20 | Mike | 1 | 3 |
| 21 | Luke | 4 | 7 |
+-----+------+--------+--------+
And I want to generate all rows based on the range (start / end) of each person.
The outcome should be this
+-----+------+-----------------+
| ID | Name | Start_End |
+-----+------+-----------------+
| 20 | Mike | 1 |
| 20 | Mike | 2 |
| 20 | Mike | 3 |
| 21 | Luke | 4 |
| 21 | Luke | 5 |
| 21 | Luke | 6 |
| 21 | Luke | 7 |
+-----+------+--------+--------+
To get unique values based on Start and End column, I have this function
CREATE FUNCTION [dbo].[ufn_SplitRange] (#Start INT, #End INT)
RETURNS TABLE
AS
RETURN
(
SELECT TOP (#End - #Start+1) ROW_NUMBER() OVER (ORDER BY S.[object_id])+(#Start - 1) [Start_End]
FROM sys.all_objects S WITH (NOLOCK)
);
The above function returns the output of (based on Mike range of 1-3):
1
2
3
I have been trying several approaches and, I can't find the right solution, it seems a very common task, but a tricky one.
Any input is highly appreciated
using cross apply():
select t.Id, t.Name, x.Start_End
from t
cross apply dbo.ufn_SplitRange(t.Start,t.[End]) as x
rextester demo: http://rextester.com/FVA48693
returns:
+----+------+-----------+
| Id | Name | Start_End |
+----+------+-----------+
| 20 | Mike | 1 |
| 20 | Mike | 2 |
| 20 | Mike | 3 |
| 21 | Luke | 4 |
| 21 | Luke | 5 |
| 21 | Luke | 6 |
| 21 | Luke | 7 |
+----+------+-----------+
You can use tally table as below:
Select Id, Name, Start_end from #Values
cross apply (
Select top ([end] - [start] +1) Start_end = [start] + Row_number() over (order by (Select NULL))-1
from master..spt_values s1, master..spt_values s2
) a
Output :
+----+------+----+
| Id | Name | RN |
+----+------+----+
| 20 | Mike | 1 |
| 20 | Mike | 2 |
| 20 | Mike | 3 |
| 21 | Luke | 4 |
| 21 | Luke | 5 |
| 21 | Luke | 6 |
| 21 | Luke | 7 |
+----+------+----+
You could use recursive cte like this
DECLARE #SampleData AS TABLE
(
Id int,
Name varchar(10),
Start int,
[End] int
)
INSERT INTO #SampleData
(
Id,
Name,
Start,
[End]
)
VALUES
(1,'Mike',1,3),
(2,'Luke',4,7)
;WITH temp AS
(
SELECT Id, sd.Name, sd.Start , sd.[End]
FROM #SampleData sd
UNION ALL
SELECT t.Id, t.Name, t.Start + 1, t.[End]
FROM temp t
WHERE t.Start < t.[End]
)
SELECT t.Id, t.Name, t.Start AS [Start_End]
FROM temp t
ORDER BY t.Id
OPTION (MAXRECURSION 0)
Demo link: http://rextester.com/AFNYFW81782

Aggregation by positive/negative values v.2

I've posted several topics and every query had some problems :( Changed table and examples for better understanding
I have a table called PROD_COST with 5 fields
(ID,Duration,Cost,COST_NEXT,COST_CHANGE).
I need extra field called "groups" for aggregation.
Duration = number of days the price is valid (1 day=1row).
Cost = product price in this day.
-Cost_next = lead(cost,1,0).
Cost_change = Cost_next - Cost.
example:
+----+---------+------+-------------+-------+
|ID |Duration | Cost | Cost_change | Groups|
+----+---------+------+-------------+-------+
| 1 | 1 | 10 | -1,5 | 1 |
| 2 | 1 | 8,5 | 3,7 | 2 |
| 3 | 1 | 12.2 | 0 | 2 |
| 4 | 1 | 12.2 | -2,2 | 3 |
| 5 | 1 | 10 | 0 | 3 |
| 6 | 1 | 10 | 3.2 | 4 |
| 7 | 1 | 13.2 | -2,7 | 5 |
| 8 | 1 | 10.5 | -1,5 | 5 |
| 9 | 1 | 9 | 0 | 5 |
| 10 | 1 | 9 | 0 | 5 |
| 11 | 1 | 9 | -1 | 5 |
| 12 | 1 | 8 | 1.5 | 6 |
+----+---------+------+-------------+-------+
Now i need to group("Groups" field) by Cost_change. It can be positive,negative or 0 values.
Some kind guy advised me this query:
select id, COST_CHANGE, sum(GRP) over (order by id asc) +1
from
(
select *, case when sign(COST_CHANGE) != sign(isnull(lag(COST_CHANGE)
over (order by id asc),COST_CHANGE)) and Cost_change!=0 then 1 else 0 end as GRP
from PROD_COST
) X
But there is a problem: If there are 0 values between two positive or negative values than it groups it separately, for example:
+-------------+--------+
| Cost_change | Groups |
+-------------+--------+
| 9.262 | 5777 |
| -9.262 | 5778 |
| 9.262 | 5779 |
| 0.000 | 5779 |
| 9.608 | 5780 |
| -11.231 | 5781 |
| 10.000 | 5782 |
+-------------+--------+
I need to have:
+-------------+--------+
| Cost_change | Groups |
+-------------+--------+
| 9.262 | 5777 |
| -9.262 | 5778 |
| 9.262 | 5779 |
| 0.000 | 5779 |
| 9.608 | 5779 | -- Here
| -11.231 | 5780 |
| 10.000 | 5781 |
+-------------+--------+
In other words, if there's 0 values between two positive ot two negative values than they should be in one group, because Sequence: MINUS-0-0-MINUS - no rotation. But if i had MINUS-0-0-PLUS, than GROUPS should be 1-1-1-2, because positive valus is rotating with negative value.
Thank you for attention!
I'm Using Sql Server 2012
I think the best approach is to remove the zeros, do the calculation, and then re-insert them. So:
with pcg as (
select pc.*, min(id) over (partition by grp) as grpid
from (select pc.*,
(row_number() over (order by id) -
row_number() over (partition by sign(cost_change)
order by id
) as grp
from prod_cost pc
where cost_change <> 0
) pc
)
select pc.*, max(groups) over (order by id)
from prod_cost pc left join
(select pcg.*, dense_rank() over (order by grpid) as groups
from pcg
) pc
on pc.id = pcg.id;
The CTE assigns a group identifier based on the lowest id in the group, where the groups are bounded by actual sign changes. The subquery turns this into a number. The outer query then accumulates the maximum value, to give a value to the 0 records.

group by top two results based on order

I have been trying to get this to work with some row_number, group by, top, sort of things, but I am missing some fundamental concept. I have a table like so:
+-------+-------+-------+
| name | ord | f_id |
+-------+-------+-------+
| a | 1 | 2 |
| b | 5 | 2 |
| c | 6 | 2 |
| d | 2 | 1 |
| e | 4 | 1 |
| a | 2 | 3 |
| c | 50 | 4 |
+-------+-------+-------+
And my desired output would be:
+-------+---------+--------+-------+
| f_id | ord_n | ord | name |
+-------+---------+--------+-------+
| 2 | 1 | 1 | a |
| 2 | 2 | 5 | b |
| 1 | 1 | 2 | d |
| 1 | 2 | 4 | e |
| 3 | 1 | 2 | a |
| 4 | 1 | 50 | c |
+-------+---------+--------+-------+
Where data is ordered by the ord value, and only up to two results per f_id. Should I be working on a Stored Procedure for this or can I just do it with SQL? I have experimented with some select TOP subqueries, but nothing has even come close..
Here are some statements to create the test table:
create table help(name varchar(255),ord tinyint,f_id tinyint);
insert into help values
('a',1,2),
('b',5,2),
('c',6,2),
('d',2,1),
('e',4,1),
('a',2,3),
('c',50,4);
You may use Rank or DENSE_RANK functions.
select A.name, A.ord_n, A.ord , A.f_id from
(
select
RANK() OVER (partition by f_id ORDER BY ord asc) AS "Rank",
ROW_NUMBER() OVER (partition by f_id ORDER BY ord asc) AS "ord_n",
help.*
from help
) A where A.rank <= 2
Sqlfiddle demo