Partition by fixed number of records - sql

I like to know how I can partition a window by a fixed number of records.
Example (http://sqlfiddle.com/#!1/7df86).
CREATE TABLE Games
(
id serial primary key,
game_no integer not null,
points integer,
constraint game_no unique (game_no)
);
INSERT INTO Games (game_no, points)
VALUES (3123, 5), (3126, 5), (3135, 8), (3128, null), (3130, 1), (3121, 11),
(3132, 0), (3133, 4), (3110, 7), (3112, null), (3113, 12), (3125, 3),(3134, 8);
I want the sum of the points of three games combined, starting with the highest game number, descending ordered by the game number. Like this.
| GAME_NO | POINTS | SUM_THREE |
|---------|--------|-----------|
| 3135 | 8 | 20 |
| 3134 | 8 | 20 |
| 3133 | 4 | 20 |
| 3132 | 0 | 1 |
| 3130 | 1 | 1 |
| 3128 | (null) | 1 |
| 3126 | 5 | 13 |
| 3125 | 3 | 13 |
| 3123 | 5 | 13 |
| 3121 | 11 | 23 |
| 3113 | 12 | 23 |
| 3112 | (null) | 23 |
| 3110 | 7 | 7 |
How to accomplish this with a window function without using a subquery? I also can't use for example a with statement. It has to be one single query because of the external parser that will execute it (and I have no control over). It seems so simple and I'm breaking my head over it the last couple of days:)

You can use row_number function divided by 3 to assign unique number to each group of 3 consecutive rows. Then use sum as analytical function on each group.
SQL Fiddle
with x(game_no, points, grp) as (
select game_no, points,
ceil(cast(row_number() over (order by game_no desc) as decimal)/ 3)
from games
)
select game_no, points,
sum(points) over (partition by grp)
from x
order by game_no desc;
You can use inline view instead of with construct.
select game_no, points,
sum(points) over (partition by grp)
from (
select game_no, points,
ceil(cast(row_number() over
(order by game_no desc) as decimal)/ 3) as grp
from games
) as x
order by game_no desc;
Results:
| GAME_NO | POINTS | SUM |
|---------|--------|-----|
| 3135 | 8 | 20 |
| 3134 | 8 | 20 |
| 3133 | 4 | 20 |
| 3132 | 0 | 1 |
| 3130 | 1 | 1 |
| 3128 | (null) | 1 |
| 3126 | 5 | 13 |
| 3125 | 3 | 13 |
| 3123 | 5 | 13 |
| 3121 | 11 | 23 |
| 3113 | 12 | 23 |
| 3112 | (null) | 23 |
| 3110 | 7 | 7 |

Related

Count Since Last Max Within Window

I have been working on this query for most of the night, and just cannot get it to work. This is an addendum to this question. The query should find the "Seqnum" of the last Maximum over the last 10 records. I am unable to limit the last Maximum to just the window.
Below is my best effort at getting there although I have tried many other queries to no avail:
SELECT [id], high, running_max, seqnum,
MAX(CASE WHEN ([high]) = running_max THEN seqnum END) OVER (ORDER BY [id]) AS [lastmax]
FROM (
SELECT [id], [high],
MAX([high]) OVER (ORDER BY [id] ROWS BETWEEN 9 PRECEDING AND CURRENT ROW) AS running_max,
ROW_NUMBER() OVER (ORDER BY [id]) as seqnum
FROM PY t
) x
When the above query is run, the below results.
id | high | running_max | seqnum | lastmax |
+----+--------+-------------+--------+---------+
| 1 | 28.12 | 28.12 | 1 | 1 |
| 2 | 27.45 | 28.12 | 2 | 1 |
| 3 | 27.68 | 28.12 | 3 | 1 |
| 4 | 27.4 | 28.12 | 4 | 1 |
| 5 | 28.09 | 28.12 | 5 | 1 |
| 6 | 28.07 | 28.12 | 6 | 1 |
| 7 | 28.2 | 28.2 | 7 | 7 |
| 8 | 28.7 | 28.7 | 8 | 8 |
| 9 | 28.05 | 28.7 | 9 | 8 |
| 10 | 28.195 | 28.7 | 10 | 8 |
| 11 | 27.77 | 28.7 | 11 | 8 |
| 12 | 28.27 | 28.7 | 12 | 8 |
| 13 | 28.185 | 28.7 | 13 | 8 |
| 14 | 28.51 | 28.7 | 14 | 8 |
| 15 | 28.5 | 28.7 | 15 | 8 |
| 16 | 28.23 | 28.7 | 16 | 8 |
| 17 | 27.59 | 28.7 | 17 | 8 |
| 18 | 27.6 | 28.51 | 18 | 8 |
| 19 | 27.31 | 28.51 | 19 | 8 |
| 20 | 27.11 | 28.51 | 20 | 8 |
| 21 | 26.87 | 28.51 | 21 | 8 |
| 22 | 27.12 | 28.51 | 22 | 8 |
| 23 | 27.22 | 28.51 | 23 | 8 |
| 24 | 27.3 | 28.5 | 24 | 8 |
| 25 | 27.66 | 28.23 | 25 | 8 |
| 26 | 27.405 | 27.66 | 26 | 8 |
| 27 | 27.54 | 27.66 | 27 | 8 |
| 28 | 27.65 | 27.66 | 28 | 8 |
+----+--------+-------------+--------+---------+
Unfortunately the lastmax column is taking the last max of all the previous records and not the max of the last 10 records only. The way it should result is below:
It is important to note that their can be duplicates in the "High" column, so this will need to be taken into account.
Any help would be greatly appreciated.
This isn't a bug. The issue is that high and lastmax have to come from the same row. This is a confusing aspect when using window functions.
Your logic in the outer query is looking for a row where the lastmax on that row matches the high on that row. That last occurred on row 8. The subsequent maxima are "local", in the sense that there was a higher value on that particular row.
For instance, on row 25, the value is 26.660. That is the maximum value that you want from row 26 onward. But on row 25 itself, then maximum is 28.230. That is clearly not equal to high on that row. So, it doesn't match in the outer query.
I don't think you can easily do what you want using window functions. There may be some tricky way.
A version using cross apply works. I've used id for the lastmax. I'm not sure if you really need seqnum:
select py.[id], py.high, t.high as running_max, t.id as lastmax
from py cross apply
(select top (1) t.*
from (SELECT top (10) t.*
from PY t
where t.id <= py.id
order by t.id desc
) t
order by t.high desc
) t;
Here is a db<>fiddle.

Obtain MIN() and MAX() over not correlative values in PostgreSQL

I have a problem that I can't found a solution. This is my scenario:
parent_id | transaction_code | way_to_pay | type_of_receipt | unit_price | period | series | number_from | number_to | total_numbers
10 | 2444 | cash | local | 15.000 | 2018 | A | 19988 | 26010 | 10
This result's when a grouping parent_id, transaccion_code, way_to_pay, type_of_receipt, unit_price, periodo, series, MIN(number), MAX(number) and COUNT(number). But the grouping hides that the number is not correlative, because this is my childs situation:
parent_id | child_id | number
10 | 1 | 19988
10 | 2 | 19989
10 | 3 | 19990
10 | 4 | 19991
10 | 5 | 22001
10 | 6 | 22002
10 | 7 | 26007
10 | 8 | 26008
10 | 9 | 26009
10 | 10 | 26010
What is the magic SQL to achieve the following?
parent_id | transaction_code | way_to_pay | type_of_receipt | unit_price | period | series | number_from | number_to | total_numbers
10 | 2444 | cash | local | 15.000 | 2018 | A | 19988 | 19991 | 4
10 | 2444 | cash | local | 15.000 | 2018 | A | 22001 | 22002 | 2
10 | 2444 | cash | local | 15.000 | 2018 | A | 26007 | 26010 | 4
You can identify adjacent numbers by subtracting a sequence. It would help if you showed your query, but the idea is this:
select parent_id, transaccion_code, way_to_pay, type_of_receipt, unit_price, periodo, series,
min(number), max(number), count(*)
from (select t.*,
row_number() over
(partition by parent_id, transaccion_code, way_to_pay, type_of_receipt, unit_price, periodo, series
order by number
) as seqnum
from t
) t
group by parent_id, transaccion_code, way_to_pay, type_of_receipt, unit_price, periodo, series,
(number - seqnum);

Can we use rank function over random and another db field?

Code :
select name,rank() over (order by name asc) as rank,
ABS(CAST(CAST(NEWID() AS VARBINARY) AS INT)) AS [RandomNumber]
from Student
I would like to combine random number and name to get rank
I do not know why you want to use a random number with rank() instead of just using row_number(), but it goes something like this:
rextester: http://rextester.com/OLKI98516
create table student(
id int not null identity(1,1) primary key
, name varchar(255) not null
);
insert into student values
('Santosh'),('Kumar'),('Reddy'),('Badugula'),('SqlZim')
,('Emma'),('Xandra'),('Naida'),('Daria'),('Colby'),('Yetta')
,('Zena'),('Deacon'),('Francis'),('Lilah'),('Risa'),('Lee')
,('Vanna'),('Molly'),('Destiny'),('Tallulah'),('Meghan')
,('Deacon'),('Francis'),('Daria'),('Colby');
select
name
, RandomNumber = abs(cast(cast(newid() as varbinary) as int))
, Name_w_RandomNumber = concat(name, '_', abs(cast(cast(newid() as varbinary) as int)))
, rank = rank() over (order by name asc)
, row_number = row_number() over (order by name asc)
, rank_w_Rand = rank() over (order by name,abs(cast(cast(newid() as varbinary) as int)) asc)
from student
results:
+----------+--------------+---------------------+------+------------+-------------+
| name | RandomNumber | Name_w_RandomNumber | rank | row_number | rank_w_Rand |
+----------+--------------+---------------------+------+------------+-------------+
| Badugula | 1105357025 | Badugula_1036749632 | 1 | 1 | 1 |
| Colby | 1125329440 | Colby_1442709274 | 2 | 2 | 2 |
| Colby | 1891932149 | Colby_1045919975 | 2 | 3 | 3 |
| Daria | 1494409363 | Daria_112566484 | 4 | 4 | 4 |
| Daria | 666341314 | Daria_262264162 | 4 | 5 | 5 |
| Deacon | 1530588472 | Deacon_1783529467 | 6 | 6 | 6 |
| Deacon | 350443065 | Deacon_1150932866 | 6 | 7 | 7 |
| Destiny | 2007923301 | Destiny_793747374 | 8 | 8 | 8 |
| Emma | 435476101 | Emma_659930976 | 9 | 9 | 9 |
| Francis | 1638790395 | Francis_2132056162 | 10 | 10 | 10 |
| Francis | 793873129 | Francis_756254272 | 10 | 11 | 11 |
| Kumar | 20071275 | Kumar_2007808448 | 12 | 12 | 12 |
| Lee | 2069120264 | Lee_837143565 | 13 | 13 | 13 |
| Lilah | 1319087807 | Lilah_605243166 | 14 | 14 | 14 |
| Meghan | 487733175 | Meghan_1884481541 | 15 | 15 | 15 |
| Molly | 2086860257 | Molly_1914281986 | 16 | 16 | 16 |
| Naida | 169335218 | Naida_719205571 | 17 | 17 | 17 |
| Reddy | 528578158 | Reddy_1297094295 | 18 | 18 | 18 |
| Risa | 1826403411 | Risa_1530611023 | 19 | 19 | 19 |
| Santosh | 723134579 | Santosh_487617337 | 20 | 20 | 20 |
| SqlZim | 937324776 | SqlZim_738072767 | 21 | 21 | 21 |
| Tallulah | 521881065 | Tallulah_1717653898 | 22 | 22 | 22 |
| Vanna | 1508284361 | Vanna_1620612208 | 23 | 23 | 23 |
| Xandra | 532483290 | Xandra_493053714 | 24 | 24 | 24 |
| Yetta | 1735945301 | Yetta_1548495144 | 25 | 25 | 25 |
| Zena | 311372084 | Zena_1429570716 | 26 | 26 | 26 |
+----------+--------------+---------------------+------+------------+-------------+
Here is the query I was referring to in my comment, not pretty but functional. You asked so here it is, if I am correct I still would never use a random value myself as each time you run it you will get different results.
select name,
rank() over (order by name,
ABS(CAST(CAST(NEWID() AS VARBINARY) AS INT))) asc) as rank
from Student
EDIT: With cte to show random number, NEWID() is guaranteed unique but not sure if it will still be when using ABS, you will need to look into that.
with cteQry As
( select name, ABS(CAST(CAST(NEWID() AS VARBINARY) AS INT))) NewIdVal
from Student
)
select name, NewIdVal,
rank() over (order by name, NewIdVal asc) as rank
from cteQry

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.

Oracle rank function issue

Iam experiencing an issue in oracle analytic functions
I want the rank in oracle to be displayed sequentialy but require a cyclic fashion.But this ranking should happen within a group.
Say I have 10 groups
In 10 groups each group must be ranked in till 9. If greater than 9 the rank value must start again from 1 and then end till howmuch so ever
emp id date1 date 2 Rank
123 13/6/2012 13/8/2021 1
123 14/2/2012 12/8/2014 2
.
.
123 9/10/2013 12/12/2015 9
123 16/10/2013 15/10/2013 1
123 16/3/2014 15/9/2015 2
In the above example the for the group of rows of the empid 123 i have split the rank in two subgroup fashion.Sequentially from 1 to 9 is one group and for the rest of the rows the rank again starts from 1.How to achieve this in oracle rank functions.
as per suggestion from Egor Skriptunoff above:
select
empid, date1, date2
, row_number() over(order by date1, date2) as "rank"
, mod(row_number() over(order by date1, date2)-1, 9)+1 as "cycle_9"
from yourtable
example result
| empid | date1 | date2 | rn | ranked |
|-------|----------------------|----------------------|----|--------|
| 72232 | 2016-10-26T00:00:00Z | 2017-03-07T00:00:00Z | 1 | 1 |
| 04365 | 2016-11-03T00:00:00Z | 2017-07-29T00:00:00Z | 2 | 2 |
| 79203 | 2016-12-15T00:00:00Z | 2017-05-16T00:00:00Z | 3 | 3 |
| 68638 | 2016-12-18T00:00:00Z | 2017-02-08T00:00:00Z | 4 | 4 |
| 75784 | 2016-12-24T00:00:00Z | 2017-11-18T00:00:00Z | 5 | 5 |
| 72836 | 2016-12-24T00:00:00Z | 2018-09-10T00:00:00Z | 6 | 6 |
| 03679 | 2017-01-24T00:00:00Z | 2017-10-14T00:00:00Z | 7 | 7 |
| 43527 | 2017-02-12T00:00:00Z | 2017-01-15T00:00:00Z | 8 | 8 |
| 03138 | 2017-02-26T00:00:00Z | 2017-01-30T00:00:00Z | 9 | 9 |
| 89758 | 2017-03-29T00:00:00Z | 2018-04-12T00:00:00Z | 10 | 1 |
| 86377 | 2017-04-14T00:00:00Z | 2018-10-07T00:00:00Z | 11 | 2 |
| 49169 | 2017-04-28T00:00:00Z | 2017-04-21T00:00:00Z | 12 | 3 |
| 45523 | 2017-05-03T00:00:00Z | 2017-05-07T00:00:00Z | 13 | 4 |
SQL Fiddle