This is my input table
but i want to get this table
Explaination:
I want to subtract value of segmeted 14/10/22 - 7/10/22 that means (28930-28799)
how could i get this kindly help me to figure it out. I cant format it properly.
This is my table
and i want to subtract value column subtraction by SEGMENTED_DATE wise
like (14th october value - 7th october value) that means (28930-28799)
the segment table is created by bellow query
select segment ,count(distinct user_id)as value,SEGMENTED_DATE from weekly_customer_RFM_TABLE
where segment in('About to sleep','Promising','champion','Loyal_customer',
'Potential_Loyalist','At_Risk','Need_Attention','New_customer',
'Hibernating','Cant_loose')
and SEGMENTED_DATE between '2022-10-07' and '2022-10-28'
Group by segment,SEGMENTED_DATE
I want this table as output
This is only value difference only Segment_date wise
The sample data of results table is not correct.
You said that
"I want to subtract value of segmeted 14/10/22 - 7/10/22 that means (28930-28799) " but this gives 131 not 233.
You said that "while in you example and value 21/10/22 -14/10/22 that means(29137-28930)" but this gives 207 not 190.
How did you calculate the value 344 in the first row?
The following query will produce the format you want but without the first row as it is not clear to me how did you calculate it. I put xxx AS your table name. The query is based on using variables.
SET #Prev = 0;
SET #i = 0;
SELECT CONCAT('Week', C, '-', 'Week', C-1) AS Change_Time, Segment, Prev AS Value FROM (
SELECT `Value`- #Prev AS Prev, Segment, #Prev :=`Value` AS V, #i:=#i+1 AS C, Segmentd FROM xxx
) AS t WHERE C> 1;
The results will be :
This query is suitable for MySQL engine and will not run on SQL server.
Edit1:
Here is some explanation:
In inner query I used variables for two reasons:
I need a counter (#i) so I can know week index like (week1, week2, ...). This counter will increase with each record by (#i:=#i+1).
I need to know value of previous record so I used (#Prev :=Value) to save that value then I can subtract it from Value in current record (Value- #Prev) AS Prev.
I started with initial values (SET #Prev = 0;) Assuming no previous values and (SET #i = 0;) because #i will increased to (1) at first record.
In outer query I converted (#i named C) to (week(i)-week(i-1)) week1-week0, week2-week1, ....... and removed first record because it will display wrong data.
I can help improving the query if you show me some real data.
Edit2:
According to you last modification at 2022/10/07 the query will be :
SET #Prev = 0;
SET #S = 0;
SELECT Segment, Diffirence, SEGMENTED_DATE FROM (
SELECT
`Value`- #Prev AS Diffirence,
POSITION(#S IN Segment) AS NotFirst,
#Prev := IF(#S=Segment, `Value`, 0) AS `Value`,
#S := Segment AS Segment,
SEGMENTED_DATE
FROM test
) AS t WHERE NotFirst> 0;
You may perform a self join as the following:
SET #rn=1;
SELECT T.segment,
D.value-T.value AS Difference,
D.segmented_date,
FROM table_name T JOIN table_name D
ON D.segmented_date=T.segmented_date + INTERVAL 7 DAY
AND D.segment=T.segment
ORDER BY T.segment, D.segmented_date
See a demo.
Related
I am using MySQL 8.0 which already support windowed function.
Given current dataset like on image below
data set with 2 kind of item
I need to get stock position on each stock movement.
With query like below
> SELECT h.item, h.item_date, h.item_qty, h.item_flow,
> #stock := #stock + (h.item_qty*h.item_flow) AS running_stock FROM stock h CROSS JOIN (SELECT #stock := 0) r WHERE h.item = 1 ORDER
> BY h.item
I can get running stock for specific item
result with running stock for each date
My Question is :
How can I modify my query so I can get result for all kind of item within a single query?
I don't know how to reset #stock variable on each group.
Adding column is not a solution as I need to solve this within a query. Adding a column would require me to calculate each time new stock movement added in-between date (there will be a back-date transaction)
Expected result set
Use SUM() as an analytic function:
SELECT
item,
item_date,
item_qty,
item_flow,
SUM(item_qty*item_flow) OVER (PARTITION BY item ORDER BY item_date) AS running_stock
FROM stock
ORDER BY
item,
item_date;
Got working query.. referenced from here
> SELECT h.item, h.item_date, h.item_qty, h.item_flow,
> #stock :=CASE WHEN #item_id=h.item THEN #stock + (h.item_qty*h.item_flow) ELSE h.item_qty * h.item_flow END AS
> running_stock,
> #item_id:=h.item AS item_id FROM stock h CROSS JOIN (SELECT #stock := 0, #item_id := '') r ORDER BY h.item, h.item_date,
> h.item_flow DESC
give me expected results as I expected. The most crucial key is to reset #stock value to default (item_qty * item_flow) each time item column value change
I want to find a way to sum up all the increments in the value of a column.
We provide delivery services to our customers. A customer can pay as he go, but if he pays an upfront fee, he gets a better deal. There is a table that has the balance of the customer across the time. So I want to sum all the increments to the balance. I can't change the way the payment is recorded.
I have alredy coded an stored procedure that works, but is kind slow, so I'm looking for alternatives. I think that, maybe, an sql statement that can do this task, can outperform my stored procedure that has loops.
My stored procedure makes a select of the customer in a given date range, and insert the result in a temp table X. After that, it starts to pop rows from X table, comparing the balance value in that row against the previous row, and detects if there is an increment. If there is not increment, pops another row and do the same routine, if there is an increment, it calculates the difference between that row and the previous, and the result is inserted in another temp table Y.
When there are no rows left, the stored procedure performs a SUM in the temp table Y, and thus, you can know how much the customer has "refilled" its balance.
This is an example of the table X, and the expected result:
DATE BALANCE
---- -------
2019-02-01 200
2019-02-02 195 //from 200 to 195 there is a decrement, so it doesn't matter
2019-02-03 180
2019-02-04 150
2019-02-05 175 //there is an increment from 150 to 175, it's 25 that must be inserted in the temp table
2019-02-06 140
2019-02-07 180 //there is another increment, from 140 to 180, it's 40
So the resulting temp table Y must be something like this:
REFILL
------
25
40
The expected result is 65. My stored procedure returns this value, but as I said, is kind slow (it takes about 22 seconds to process 3900 rows, equivalent to 3 days, aprox), I think is because the loops. I would like to explore another alternatives. Because some details that I don't mention here, for a single costumer, I can have 1300 rows per day (the example is given in days, but I have rows by the minute). My tables are indexed, I think properly. I can't post my stored procedure, but it works as described (I know that "The devil is in the detail"). So any suggestion will be appreciated.
Use a user-defined variable to hold the balance from the previous row, and then subtract it from the current row's balance.
SELECT SUM(refill) AS total_refill
FROM (
SELECT GREATEST(0, balance - #prev_balance) AS refill, #prev_balance := balance
FROM (
SELECT balance
FROM tableX
ORDER BY date) AS t
CROSS JOIN (SELECT #prev_balance := NULL) AS ars
) AS t
There is a quite well-known mechanism to deal with these: Use a variable inside a field.
SELECT #result:=0;
SELECT #lastbalance:=9999999999; -- whatever value is sure to be highe than any real balance
SELECT SUM(increments) AS total FROM (
SELECT
IF(balance>#lastbalance, balance-#lastbalance, 0) AS increments,
#lastbalance:=balance AS ignore
FROM X -- insert real table name here
WHERE
-- insert selector here
ORDER BY
-- insert real chronological sorter here
) AS baseview;
Use lag() in MySQL 8+:
select sum(balance - prev_balance) as refills
from (select t.*, lag(balance) over (order by date) prev_balance
from t
) t
where balance > prev_balance;
In older versions of MySQL this is tricky. If the values are continuous dates, then a simple JOIN works:
select sum(t.balance - tprev.balance) as refills
from t join
t tprev
on tprev.date = t.date - 1
where t.balance > tprev.balance;
This may not be the case. Then the next best method is variables. But you have to be very careful. MySQL does not declare the order of evaluation of expressions in a SELECT. As the documentation explains:
The order of evaluation for expressions involving user variables is undefined. For example, there is no guarantee that SELECT #a, #a:=#a+1 evaluates #a first and then performs the assignment.
The variables need to be assigned and used in the same expression:
select sum(balance - prev_balance) as refills
from (select t.*,
(case when (#temp_prevb := #prevb) = NULL -- intentionally false
then -1
when (#prevb := balance)
then #temp_prevb
end) as prev_balance
from (select t.* from t order by date) t cross join
(select #prevb := NULL) params
) t
where balance > prev_balance;
And the final method is a correlated subquery:
select sum(balance - prev_balance) as refills
from (select t.*,
(select t2.balance
from t t2
where t2.date < t.date
order by t2.date desc
) as prev_balance
from t
) t
where balance > prev_balance;
I have following table
id vehicle_id timestamp distance_meters
1 1 12:00:01 1000
2 1 12:00:04 1000.75
3 1 15:00:06 1345.0(unusual as time and distance jumped)
4 1 15:00:09 1347
The table above is the log of the vehicle.Normally , vehicle sends the data at 3 seconds interval , but sometimes they can get offline and send the data only they are online. Only, way to find out that is find out unusual jump in distance . We can assume some normal jump as (500 meters)
What is the best way to do that?
If you cannot ensure that the ids increment with no gaps, then you need another method. One method uses variables and one uses correlated subqueries.
The variables is messy, but probably the fastest method:
select t.*,
(case when #tmp_prev_ts := #prev_ts and false then NULL -- never happens
when #prev_ts := timestamp and false then NULL -- never happens
else #tmp_prev_ts
end) as prev_timestamp,
(case when #tmp_prev_d := #prev_d and false then NULL -- never happens
when #prev_d := distance_meters and false then NULL -- never happens
else #tmp_prev_d
end) as prev_distance_meters
from t cross join
(select #prev_ts := '', #prev_d := 0) params
order by timestamp; -- assume this is the ordering
You can then use a subquery or other logic to get the large jumps.
Usually you can use windowing function for such task - LEAD and LAG are perfect for this. However since there are no windowing functions in mysql you would have to emulate them.
You need to get your data set with row number and then join it to itself by row number with offset by 1.
It would look something like this:
SELECT
*
FROM (SELECT
rownr,
vehicle_id,
timestamp,
distance_meters
FROM t) tcurrent
LEFT JOIN (SELECT
rownr,
vehicle_id,
timestamp,
distance_meters
FROM t) tprev
ON tcurrent.vehicle_id = tprev.vehicle_id
AND tprev.rownr = tcurrent.rownr - 1
If you can assume id is sequential (without gaps) per vehicle_id, then you could use it instead of rownr. Otherwise you would have to make you own rank/row number.
So you would have to combine ranking solution from this question:
MySQL - Get row number on select
Given the following table, I want to select the value of each ID at exactly 00:00:00. When there's an entry at this exact time, return it, otherwise calculate it with linear interpolation (an imaginary graph line between the nearest values before and after 00:00:00). If there's no value after the given time yet, return the last value, or use linear interpolation from the last two points.
ID|Timestamp|Value
1|2015-01-01 23:00:00|90
1|2015-01-02 01:00:00|110
2|2015-01-01 23:00:00|210
2|2015-01-02 01:00:00|190
3|2015-01-02 00:00:00|50
4|2015-01-01 23:00:00|100
5|2015-01-01 22:00:00|80
5|2015-01-01 23:00:00|90
Result:
ID|Value
1|100
2|200
3|50
4|100
5|100
Is this possible with MySQL only and how?
First, let's split it into single entries like #3 versus double entries (the rest):
( SELECT ID, value FROM tbl GROUP BY ID HAVING COUNT(*) = 1 )
UNION ALL
( ... ) -- This needs interpolation code, below
ORDER BY ID;
To separate the pair of rows is tricky since there is no good way to do "groupwise-max". So, instead, I will work with #variables and walk through the table in order.
To round to the nearest midnight might be ROUND(... / 86400) * 86400 . The potential problem is the time_zone you are in. I don't feel like fixing that.
SELECT ID, val FROM (
SELECT ID,
IF(ID != #prevID, '1st', '2nd') AS picker, -- See WHERE filter, below
#ts = timestamp, -- Need an INT here, not sure that is what I have
#dts = #ts - #prev_ts,
#dval = value - #prev_val,
#midnight := ROUND(#ts / 86400) * 86400, -- DST issues?
value + (#midnight - #ts) * (#dval / #dts) AS val, -- interpolate
#prev_id = ID,
#prev_ts = #ts,
#prev_val = value
FROM ( SELECT #prevID := 0 ) AS Initialize
JOIN tbl
ORDER BY ID, timestamp
) AS x
WHERE picker = '2nd'
ORDER BY ID
Put that monster as the second part of the UNION.
I have a MySQL query that generates a table for my vehicle tracking 'in' and 'out' times.
The problem is that the 'in' time is not the same as the 'out' time so seconds or minutes are lost in between.
Is there a way to set the 'in' time equal to the 'out time' from the previous row, even if I need to embed my current select inside a new select?
you will see on the image below that the first rows out time is 15:45:14 and the in time for the next row is 15:46:14. so in this case a minute is lost
in reality if the vehicles has left one point, it is immediately on the road to the next point so I can set the in time equal to the out time of the previous row. This way, time is never lost
the sql for my query is:
select vehicle,InTime,OutTime from (select
PreQuery.callingname as vehicle,
PreQuery.geofence,
PreQuery.GroupSeq,
MIN( PreQuery.`updatetime` ) as InTime,
UNIX_TIMESTAMP(MIN( PreQuery.`updatetime`))as InSeconds,
MAX( PreQuery.`updatetime` ) as OutTime,
UNIX_TIMESTAMP(MAX( PreQuery.`updatetime`))as OutSeconds,
TIME_FORMAT(SEC_TO_TIME((UNIX_TIMESTAMP(MAX( PreQuery.`updatetime` )) - UNIX_TIMESTAMP(MIN( PreQuery.`updatetime`)))),'%H:%i:%s') as Duration,
(UNIX_TIMESTAMP(MAX( PreQuery.`updatetime` )) - UNIX_TIMESTAMP(MIN( PreQuery.`updatetime`))) as DurationSeconds
from
( select
v_starting.callingname,
v_starting.geofence,
v_starting.`updatetime`,
#lastGroup := #lastGroup + if( #lastAddress = v_starting.geofence
AND #lastVehicle = v_starting.callingname, 0, 1 ) as GroupSeq,
#lastVehicle := v_starting.callingname as justVarVehicleChange,
#lastAddress := v_starting.geofence as justVarAddressChange
from
v_starting,
( select #lastVehicle := '',
#lastAddress := '',
#lastGroup := 0 ) SQLVars
order by
v_starting.`updatetime` ) PreQuery
Group By
PreQuery.callingname,
PreQuery.geofence,
PreQuery.GroupSeq) parent
where (InTime> DATE_SUB('2013-03-23 15:00', INTERVAL 24 HOUR) or OutTime> '2013-03-23 15:00' ) and vehicle='TT08' order by InTime asc
The MySQL syntax is in depth so quite large but could be done on a much simpler query as well. like
select vehicle, intime,outtime from vehicletimes
My desired result is something like:
select vehicle, intime(outtime of row above),outtime from vehicletimes
The first rows in time can be as is and the last rows outtime can be as is. I just need to account for every second between the smallest in time and the largest out time.
Any help appreciated as always.
Thanks in advance
I think this will give you the latest in-time prior to each current out-time, for your existing records:
select
vt.vehicle, max(qGetMaxOut.outtime) as intime , vt.outtime
from
vehicle_times vt
inner join
(
select vehicle, outtime
from vehicle_times
) qGetMaxOut
on qGetMaxOut.vehicle = vt.vehicle
and qGetMaxOut.outtime <= vt.intime
group by
vt.vehicle, vt.outtime
The above query will also help you if you want to insert a new record, but need to find the previous in-time for a particular time (ie if you need to insert a new record who's in/out times are prior to the latest time - eg inserting a record that was somehow previously missed and where newer time entries have been added since). If you need this scenario, let me know and I'll elaborate if you can't work it out from the above.
The join basically joins the table "back on itself" to provide another "copy", but limits the results in the "copy" to only those rows for the current vehicle in the main table, and excludes those rows from the copy where the vehicle's out-time is more recent than the current in-time from the main table. This way you can do a MAX() over the copy, to find what the previous out time was.
I don't know your specific requirements, but I would recommend storing the most accurate information you can. So if "sythensising" a value is just for cosmetic purposes on a few reports, I would leave the data alone, and tidy up the report, rather than loosing data that might come in handy down the track. eg what happens if in the future, you suddenly have a requirement to tell your boss "how long are our vehicles 'in' and sitting idle for?"
But if you do just want to insert a new record with the actual out-time ignored, and replaced by the in-time from the most recent record, then this following query will find that value for you:
select
vt.vehicle, max(vt.outtime) as intime
from
vehicle_times vt
group by
vt.vehicle
Have I missed your requirement?