Summarize data in a new table - mysql

The problem is as follows during the day data is put into status_table (this can reach up to 4000 entries a day
During the night i want to put the data in a more organized way in a storage table
(status_table_storage) and remove it from the status_table.
The idea is to group all the entries that have the same status until the status/error changes. from that point on a new group should start an be placed into the table. an example can be found in the fiddle
what is the best way to do this.
an example of the table structure can be found here: http://sqlfiddle.com/#!9/488524/1

Step by step explanation:
First you order the table by name and timestamp and initialize three user-defined variables.
SELECT s.* FROM status_table s
, (SELECT #group_number := 0, #prevName := NULL, #prevStatus := NULL) var_init_subquery
ORDER BY name, timestamp
As you can see, we can use a subquery for that. The ORDER BY is important, because there's no order in a relational database, unless you specify it.
Now, MySQL evaluates the SELECT clause in the specified order, therefore don't change the order here.
SELECT
s.*,
#prevName,
#prevStatus,
#prevName := s.name,
#prevStatus := s.status
FROM status_table s
, (SELECT #group_number := 0, #prevName := NULL, #prevStatus := NULL) var_init_subquery
ORDER BY name, timestamp
When you execute this statement, you can see, that when we simply select the variables they hold the value of the previous row or NULL when it's the first row, that was read. Then the value of the current row is assigned to the variables. So we can compare now the current row with the previous row. If something changed, we simply increment the third variable, which is a number for each "group" that we are building.
SELECT
s.*,
#group_number := IF(#prevName != s.name OR #prevStatus != s.status, #group_number + 1, #group_number) AS group_number,
#prevName := s.name,
#prevStatus := s.status
FROM status_table s
, (SELECT #group_number := 0, #prevName := NULL, #prevStatus := NULL) var_init_subquery
ORDER BY name, timestamp
So we incremented the #group_number when something changed and assigned the variable to itself if not, so that it doesn't change.
Now we can simply use this query as subquery and do a simple grouping.
SELECT
group_number AS id,
name,
status,
MIN(error) AS error,
MIN(timestamp) AS firstEntry,
MAX(timestamp) AS lastEntry,
COUNT(*) AS entries
FROM (
SELECT
s.*,
#group_number := IF(#prevName != s.name OR #prevStatus != s.status, #group_number + 1, #group_number) AS group_number,
#prevName := s.name,
#prevStatus := s.status
FROM status_table s
, (SELECT #group_number := 0, #prevName := NULL, #prevStatus := NULL) var_init_subquery
ORDER BY name, timestamp
) sq
GROUP BY
group_number,
name,
status
see it working in this sqlfiddle

Related

MySQL update Query using Select

Can someone explain how to write an update query to update a column "newBalance" in table "abcd" with the O/P of balance in the following query ?
I would like to update newBalance column with the balance calculated for each row using the below query.
SELECT id
, inAmt
, outAmt
, #prev := if(id = 1, #prev := #curBalance , #prev + (#prevOut-#prevIn)) as balance
, #prevIn :=inAmt
, #prevOut := outAmt
from (select #prev := 0) as i
, abcd
order
by id
Any help is highly appreciated.
You can use update with a join:
update abcd join
(select id, inAmt, outAmt,
#prev := if(id = 1, #prev := #curBalance , #prev + (#prevOut-#prevIn)) as balance,
#prevIn := inAmt,
#prevOut := outAmt
from (select #prev := 0) as i cross join
abcd
order by id
) x
on abcd.id = x.id
set abcd.newbalance = x.balance;
All that said, your query is definitely not guaranteed to work and can fail at any time. You are using variables in one expression and assigning them in another. MySQL does not guarantee the order of evaluation of expressions.
However, that was not the question that you asked. If you want the query fixed, ask a new question with sample data and desired results.

It is possible to merge rows chronologically? [duplicate]

The problem is as follows during the day data is put into status_table (this can reach up to 4000 entries a day
During the night i want to put the data in a more organized way in a storage table
(status_table_storage) and remove it from the status_table.
The idea is to group all the entries that have the same status until the status/error changes. from that point on a new group should start an be placed into the table. an example can be found in the fiddle
what is the best way to do this.
an example of the table structure can be found here: http://sqlfiddle.com/#!9/488524/1
Step by step explanation:
First you order the table by name and timestamp and initialize three user-defined variables.
SELECT s.* FROM status_table s
, (SELECT #group_number := 0, #prevName := NULL, #prevStatus := NULL) var_init_subquery
ORDER BY name, timestamp
As you can see, we can use a subquery for that. The ORDER BY is important, because there's no order in a relational database, unless you specify it.
Now, MySQL evaluates the SELECT clause in the specified order, therefore don't change the order here.
SELECT
s.*,
#prevName,
#prevStatus,
#prevName := s.name,
#prevStatus := s.status
FROM status_table s
, (SELECT #group_number := 0, #prevName := NULL, #prevStatus := NULL) var_init_subquery
ORDER BY name, timestamp
When you execute this statement, you can see, that when we simply select the variables they hold the value of the previous row or NULL when it's the first row, that was read. Then the value of the current row is assigned to the variables. So we can compare now the current row with the previous row. If something changed, we simply increment the third variable, which is a number for each "group" that we are building.
SELECT
s.*,
#group_number := IF(#prevName != s.name OR #prevStatus != s.status, #group_number + 1, #group_number) AS group_number,
#prevName := s.name,
#prevStatus := s.status
FROM status_table s
, (SELECT #group_number := 0, #prevName := NULL, #prevStatus := NULL) var_init_subquery
ORDER BY name, timestamp
So we incremented the #group_number when something changed and assigned the variable to itself if not, so that it doesn't change.
Now we can simply use this query as subquery and do a simple grouping.
SELECT
group_number AS id,
name,
status,
MIN(error) AS error,
MIN(timestamp) AS firstEntry,
MAX(timestamp) AS lastEntry,
COUNT(*) AS entries
FROM (
SELECT
s.*,
#group_number := IF(#prevName != s.name OR #prevStatus != s.status, #group_number + 1, #group_number) AS group_number,
#prevName := s.name,
#prevStatus := s.status
FROM status_table s
, (SELECT #group_number := 0, #prevName := NULL, #prevStatus := NULL) var_init_subquery
ORDER BY name, timestamp
) sq
GROUP BY
group_number,
name,
status
see it working in this sqlfiddle

MariaDB/MySQL RANK() implementation

I am working on migration from MS SQL Server to MariaDB 10.0. I have query which use RANK() PARTITION BY. I already created some kind of RANK() implementation on my table but it's not working properly.
The original query was:
RANK() OVER (PARTITION BY visits.id_partner ORDER BY visits.updated_at DESC) AS rank
My implementation for MariaDB/MySQL:
SELECT
...,
(
CASE visits.id_partner
WHEN #currId THEN
#curRow := #curRow + 1
ELSE
#curRow := 1 AND #currId := visits.id_partner
END
) AS rank
FROM
records rec
JOIN visits ON visits.id = rec.id_visit,
(
SELECT
#curRank := 0,
#currId := NULL
) r
WHERE
...
ORDER BY visits.id_partner ASC, visits.updated_at DESC
I want to select row ranked by id_partner order by updated_at field. When id_partner is same as on row before RANK should increase by 1. When is different than before, it should reset to 1.
But my query is not working at all. I have still rank 1 on all rows. Can you help me find mistake?
Thank you for help!
It is tricky to use variables in MySQL/MariaDB. A variable should only be used and assigned in one statement (as you do correctly). However, AND can short-circuit variable assignment.
I use a construct like this for ROW_NUMBER(). RANK() is actually a bit of a pain . . . DENSE_RANK() and ROW_NUMBER() are simpler. However, this seems to be the code that you are aiming for:
SELECT ...,
(#rn := if(#currId = visits.id_partner, #rn + 1,
if(#currId := visits.id_partner, 1, 1)
)
) as rank
FROM records rec JOIN
visits
ON visits.id = rec.id_visit CROSS JOIN
(SELECT #rn := 0, #currId := NULL) params
WHERE
...
ORDER BY visits.id_partner ASC, visits.updated_at DESC;
EDIT:
In MySQL (and presumably in MariaDB), sometimes variables don't work quite right unless you use a subquery. So, try this:
SELECT . . .,
(#rn := if(#currId = visits.id_partner, #rn + 1,
if(#currId := visits.id_partner, 1, 1)
)
) as rank
FROM (SELECT ...
FROM records rec JOIN
visits
ON visits.id = rec.id_visit
WHERE
...
ORDER BY visits.id_partner ASC, visits.updated_at DESC
) t CROSS JOIN
(SELECT #rn := 0, #currId := NULL) params;

Mysql count per name reset per name change

I have a table with entries names,date etc.
select name from names
order by names
for example.
I need an output like
1|paul
2|paul
1|morne
1|marone
2|marone
3|marone
i have no clue where to start to do a count like this for when i grou i cant get this right.
In MySQL, the easiest way to do this is with variables:
select n.name,
(#rn := if(#name = name, #rn + 1, 1)) as seqnum,
#name := name
from names n cross join
(select #name := '', #rn := 0) var
order by names;
EDIT:
In case anyone comes across this answer, the above sort-of works, but MySQL does not guarantee the order of evaluation of expressions. So, I now write the above as:
select n.name,
(#rn := if(#name = name, #rn + 1,
if(#name := name, 1, 1)
)
) as seqnum
from names n cross join
(select #name := '', #rn := 0) var
order by names;
Because all the variable assignments are in one statement, there is no problem with the order of evaluation of expressions in the select statement.

Write results of SQL sub query back to database

I've been playing with this SQL code:
SELECT
id,
#prev := #curr as prev,
#curr := measure as curr,
#rank := IF(#prev > #curr, #rank+#ties, #rank) AS rank,
#ties := IF(#prev = #curr, #ties+1, 1) AS ties,
(1-#rank/#total) as percentrank
FROM
mytable,
(SELECT
#curr := null,
#prev := null,
#rank := 0,
#ties := 1,
#total := count(*) from mytable where measure is not null
) b
WHERE
measure is not null
ORDER BY
measure DESC
I'd like to write the calculated 'percentrank' back to each corresponding row of mytable in a column named "percentile," but I can't recall how to work in my update statement.
I appreciate the help.
Credit to http://code.openark.org/blog/mysql/sql-ranking-without-self-join for the SQL.
To update from a subquery, give the subquery an alias so that it's a derived table. Then use this syntax:
update YourTable
set SomeField = DerivedTable.something
, etc
from YourTable join
(subquery goes here) DerivedTable on YourTable.Whatever = DerivedTable.Whatever
etc