Calculating a simple rate for "windows" within data - postgresql

Battery % time charging
90 t1 yes
91 t2 yes
95 t3 no
89 t4 no
87 t5 no
80 t6 no
78 t7 yes
85 t8 yes
50 t9 no
40 t10 no
38 t11 no
20 t12 yes
I want to calculate battery depletion rate as :
change in battery / time taken
This should be calculated for ALL the windows when charging is 'no' (sandwiched in between 2 "yes"), and then the average of those rates should be taken.
So, for this dataset it should be:
95 - 80 / t6 - t3 = rate 1
50 - 38 / t11 - t9 = rate 2
average rate = ( rate 1 + rate 2 ) / 2
Please note there can be more than 2 windows of no's in the data
Here is my current code -
select ((max(battery_Percentage) - min (battery_Percentage)) / NULLIF(Extract(epoch FROM (max(time) - min(time))/3600),0)) as rate_of_battery_decline
from table
where
table.charging = 'no'
but this is not taking into account windows of no's in between the yes's as I want. Please help.

You have to separate the runs between the charging = 'yes' blocks:
with discharge_intervals as (
select battery_pct, tstamp,
sum((charging = 'yes')::int) over (order by tstamp) as ival_number,
charging = 'no' as keep
from cstats
), interval_rates as (
select ival_number,
(max(battery_pct) - min(battery_pct))
/ extract(epoch from max(tstamp) - min(tstamp)) as ival_rate
from discharge_intervals
where keep
group by ival_number
)
select avg(ival_rate)
from interval_rates;
Fiddle Here

Related

How do I graph of change from previous value in grafana amazon-timestream?

My current query is
SELECT bin(time, 5m) AS bin, sum(measure_value::double) as bytesdropped FROM $__database.$__table
where measure_name='messurename' and $__timeFilter
group by bin(time, 5m) order by bin
unfortunately, the bytesdropped value is cumulative, for example a table of the data:
bin
bytesdropped cum
t0
0
t1
100
t2
100
t3
110
t4
110
t5
115
t6
115
when no bytes dropped, the the bytes dropped value does not report as zero, but instead reports as the previous value, IE t2 after t1 means no bytes dropped at t2 (100 bytes had dropped at t1)
how do I modify (or re-write completely) my query such that I only show the change between the current time and the last time. IE:
bin
bytesdropped
t0
0
t1
100
t2
0
t3
10
t4
0
t5
15
t6
0

"partition by" giving incorrect value

battery_pct tstamp charging phone_id
90 t1 yes 12
91 t2 yes 22
95 t3 no 22
89 t4 no 22
87 t5 no 22
80 t6 no 22
78 t7 yes 22
85 t8 yes 4
50 t9 no 4
40 t10 no 4
38 t11 no 4
20 t12 yes 4
I want to calculate battery depletion rate as : change in battery / time taken
This should be calculated for ALL the windows when charging is 'no' (sandwiched in between 2 "yes"), and then the average of those rates should be taken.
So, for this dataset it should be:
95 - 80 / t6 - t3 = rate for phone_id 22
50 - 38 / t11 - t9 = rate for phone_id 4
average rate = ( rate 1 + rate 2 ) / 2
Please note there can be more than one windows of no's for each phone_id in the data.
I have to find average rate across ALL phone id's. i.e. one value for average rate which encompasses all phones.
Here is my current code, it does not give any error, but is returning a value that is NOT plausible -
with discharge_intervals as (
select battery_pct, tstamp,
sum((charging = 'yes')::int) over (partition by phone_id order by tstamp) as ival_number,
charging = 'no' as keep
from dataset
), interval_rates as (
select ival_number,
(max(battery_pct) - min(battery_pct))
/ extract(epoch from max(tstamp) - min(tstamp)) as ival_rate
from discharge_intervals
where keep
group by ival_number
)
select avg(ival_rate)
from interval_rates;
Your interval_rates are calculated without grouping by phone, but should be. The ival_numbers are partitioned by phone_id, but that just means multiple phones will create rows with the same ival_number. You'll want to use
with discharge_intervals as (
select battery_pct, tstamp, phone_id,
-- ^^^^^^^^^
sum((charging = 'yes')::int) over (partition by phone_id order by tstamp) as ival_number,
charging = 'no' as keep
from dataset
), interval_rates as (
select (max(battery_pct) - min(battery_pct))
/ extract(epoch from max(tstamp) - min(tstamp)) as ival_rate
from discharge_intervals
where keep
group by phone_id, ival_number
-- ^^^^^^^^^
)
select avg(ival_rate)
from interval_rates;

DB2/SQL : Iterate and update columns one by one of one table with values from other table

I have a table structure for example,
Table T1,
ID | PART | PRCODE |PRVENDOR | PREX1 | PREX2 | PREX3
10 - A1 - A11 - AV1 - 1 - 0 - 0
20 - A2 - A22 - AV2 - 1 - 0 - 0
30 - A3 - A33 - AV3 - 1 - 0 - 0
40 - A4 - A44 - AV4 - 1 - 0 - 0
50 - A5 - A55 - AV5 - 1 - 0 - 0
60 - A4 - A66 - AV6 - 1 - 0 - 0
Table T2,
ID | PART | PRCODE |PRVENDOR | PDEX1 | PDEX2 | PDEX3
10 - A1 - A11 - AV1 - 10 - 20 - 30
20 - A2 - A22 - AV2 - 100 - 200 - 300
30 - A3 - A33 - AV3 - 11 - 22 - 33
40 - A4 - A44 - AV4 - 1 - 2 - 3
Now, I want to write a update query to update the PREX1, PREX2, PREX3 columns of Table T1 with PDEX1,PDEX2,PDEX3 columns of Table T2 by checking the
(ID, PART, PRCODE) matching (Common) records in T1 and T2.
If update is fine, as a result I want to get Table T1 as below,
Table T1,
ID | PART | PRCODE |PRVENDOR | PREX1 | PREX2 | PREX3
10 - A1 - A11 - AV1 - 10 - 20 - 30
20 - A2 - A22 - AV2 - 100 - 200 - 300
30 - A3 - A33 - AV3 - 11 - 22 - 33
40 - A4 - A44 - AV4 - 1 - 2 - 3
50 - A5 - A55 - AV5 - 1 - 0 - 0
60 - A4 - A66 - AV6 - 1 - 0 - 0
Tried below queries, but not working as expected.
update T1 a
inner join T2 b on (
(a.ID=b.ID) AND (a.PART=b.PART) (a.PRCODE=b.PRCODE)
)
)
set a.PREX1= b.PDEX1,
a.PREX2 = b.PDEX2,
a.PREX3 = b.PDEX3
where ( (a.ID=b.ID) AND (a.PART=b.PART) (a.PRCODE=b.PRCODE) )
Error message: Keyword Inner not expected. valid tokens SET.
Also tried updating individual columns by below query,
update T1
set T1.PREX1 = (select T2.PDEX1
FROM T2, T1
( (T1.ID=T2.ID) AND
(T1.PART =T2.PART) AND
(T1.PRCODE=T2.PRCODE)
) where T1.ID=10;
wrong result: This query updates all the PREX1 Column records to same value which is not expected.
Let me know if i am missing any conditions.
Thanks in Advance!
Better way is by using merge statement
MERGE INTO T1
USING T2
ON T1.ID = T2.ID and T1.PART = T2.PART and T1.PRCODE = T2.PRCODE
WHEN MATCHED THEN UPDATE SET T1.PREX1=T2.PDEX1,
T1.PREX2=T2.PDEX2,
T1.PREX3=T2.PDEX3;
or use can update with subquery
Update T1
Set PREX1 =(Select MAX(PDEX1)
from T2
Where T1.ID = T2.ID
and T1.PART = T2.PART
and T1.PRCODE = T2.PRCODE
),
PREX2 =(Select MAX(PDEX2)
from T2
Where T1.ID = T2.ID
and T1.PART = T2.PART
and T1.PRCODE = T2.PRCODE
),
PREX3 =(Select MAX(PDEX3)
from T2
Where T1.ID = T2.ID
and T1.PART = T2.PART
and T1.PRCODE = T2.PRCODE
);
You should be able to run:
Update T1
Set PREX1 =
(Select PDEX1
from T2
Where T1.ID = T2.ID
and T1.PART = T2.PART
and T1.PRCODE = T2.PRCODE
);

PostgreSQL non-overlapping ranges

I use PostgreSQL database and have a cards table.
Each record(card) in this table have card_drop_rate integer value.
For example:
id | card_name |card_drop_rate
-------------------------------
1 |card1 |34
2 |card2 |16
3 |card3 |54
max drop rate is 34 + 16 + 54 = 104.
In accordance to my application logic I need to find a random value between 0 and 104 and then retrieve card according to this number, for example:
random value: 71
card1 range: 0 - 34(0 + 34)
card2 range: 34 - 50(34 + 16)
card3 range: 50 - 104(50 + 54)
So, my card is card3 because 71 is placed in the range 50 - 104
What is the proper way to reflect this structure in PostgreSQL ? I'll need to query this data often under so the performance is a criterion number one for this solution.
Following query works fine:
SELECT
b.id,
b.card_drop_rate
FROM (SELECT a.id, sum(a.card_drop_rate) OVER(ORDER BY id) - a.card_drop_rate as rate, card_drop_rate FROM cards as a) b
WHERE b.rate < 299 ORDER BY id DESC LIMIT 1
You can do this using cumulative sums and random. The "+ 1"s might be throwing me off, but it is something like this:
with c as (
select c.*,
sum(card_drop_rate + 1) - card_drop_rate as threshhold
from cards c
),
r as (
select random() * (sum(card_drop_rate) + count(*) - 1) as which_card
from cards c
)
select c.*
from c cross join
r
where which_card >= threshhold
order by threshhold
limit 1;
For performance, I would simply take the cards and generate a new table with 106 slots. Assign the card value to the slots and build an index on the slot number. Then get a value using:
select s.*
from slots s
where s.slotid = floor(random() * 107);

Complicated AVG within date range

I've got a table with a tracking of a plant's equipment installation.
Here is a sample:
ID Name Date Percentage
1 GT-001 2011-01-08 30
2 GT-002 2011-01-11 40
3 GT-003 2011-02-02 30
4 GT-001 2011-02-03 50
5 GT-003 2011-02-15 50
6 GT-004 2011-02-15 30
7 GT-002 2011-02-15 60
8 GT-001 2011-02-20 60
9 GT-003 2011-03-01 60
10 GT-004 2011-03-05 50
11 GT-001 2011-03-10 70
12 GT-004 2011-03-15 60
And the corresponding script:
CREATE TABLE [dbo].[SampleTable](
[ID] [int] NOT NULL,
[Name] [nvarchar](50) NULL,
[Date] [date] NULL,
[Percentage] [int] NULL) ON [PRIMARY]
GO
--Populate the table with values
INSERT INTO [dbo].[SampleTable] VALUES
('1', 'GT-001', '2011-01-08', '30'),
('2', 'GT-002', '2011-01-11', '40'),
('3', 'GT-003', '2011-02-02', '30'),
('4', 'GT-001', '2011-02-03', '50'),
('5', 'GT-003', '2011-02-15', '50'),
('6', 'GT-004', '2011-02-15', '30'),
('7', 'GT-002', '2011-02-15', '60'),
('8', 'GT-001', '2011-02-20', '60'),
('9', 'GT-003', '2011-03-01', '60'),
('10', 'GT-004', '2011-03-05', '50'),
('11', 'GT-001', '2011-03-10', '70'),
('12', 'GT-004', '2011-03-15', '60');
GO
What i need is to create a chart with Date on the X and Average Percentage on the Y. Average Percentage is an average percentage of all equipment by that particular date starting from the beggining of the installation process (MIN(Fields!Date.Value, "EquipmentDataset"))
Having no luck in implementing this using SSRS only, i decided to create a more complicated dataset for it using T-SQL.
I guess that it is nessesary to add a calculated column named 'AveragePercentage' that should store an average percentage on that date, calculating only the most latest equipment percentage values in a range between the beggining of the installation process (MIN(Date)) and the current row's date. Smells like a recursion, but i'm newbie to T-SQL....))
Here is the desired output
ID Name Date Percentage Average
1 GT-001 2011-01-08 30 30
2 GT-002 2011-01-11 40 35
3 GT-003 2011-02-02 30 33
4 GT-001 2011-02-03 50 40
5 GT-003 2011-02-15 50 48
6 GT-004 2011-02-15 30 48
7 GT-002 2011-02-15 60 48
8 GT-001 2011-02-20 60 50
9 GT-003 2011-03-01 60 53
10 GT-004 2011-03-05 50 58
11 GT-001 2011-03-10 70 60
12 GT-004 2011-03-15 60 63
What do you think?
I'll be very appreciated for any help.
You could use cross apply with row_number to find the latest value for each machine. An additional subquery is required because you cannot use row_number in the where clause directly. Here's the query:
select t1.id
, t1.Name
, t1.Date
, t1.Percentage
, avg(1.0*last_per_machine.percentage)
from SampleTable t1
outer apply
(
select *
from (
select row_number() over (partition by Name order by id desc)
as rn
, *
from SampleTable t2
where t2.date <= t1.date
) as numbered
where rn = 1
) as last_per_machine
group by
t1.id
, t1.Name
, t1.Date
, t1.Percentage
Working example on SE Data.