MySQL Dynamic Pivot Table Select Most Recent Timestamp - mysql

I've been working with a dynamic pivot table in MySQL as referenced here, which works fine. In my situation, my table also has a timestamp column. I want to return the most recent values in the pivot table for the attributes and I can't figure out how to accomplish this. So consider the example table data:
+----+---------+---------------+--------+------------+
| id | item_id | property_name | value  | timestamp |
+----+---------+---------------+--------+------------+
|  1 |       1 | color         | blue   | 2018-01-01 |
|  2 |       1 | size          | large  | 2018-01-01 |
|  3 |       1 | weight        | 65     | 2018-01-01 |
<SNIP>
| 15 |       1 | color         | purple | 2018-02-01 |
| 16 |       1 | weight        | 69     | 2018-02-01 |
+----+---------+---------------+--------+------------|
For item_id '1' the result row should be:
+---------+--------+--------+--------+
| item_id | color  | size   | weight |
+---------+--------+--------+--------+
|       1 | purple | large  | 69     |
+---------+--------+--------+--------+
Thanks in advance!

Your question is slightly tricky. We can use a subquery to find the most recent record for each item and property. Then, join your table to this subquery and do a regular pivot query to generate the output you want.
SELECT
t1.item_id,
MAX(CASE WHEN t1.property_name = 'color' THEN t1.value END) AS color,
MAX(CASE WHEN t1.property_name = 'size' THEN t1.value END) AS size,
MAX(CASE WHEN t1.property_name = 'weight' THEN t1.value END) AS weight
FROM yourTable t1
INNER JOIN
(
SELECT item_id, property_name, MAX(timestamp) AS max_timestamp
FROM yourTable
GROUP BY item_id, property_name
) t2
ON t1.item_id = t2.item_id AND
t1.property_name = t2.property_name AND
t1.timestamp = t2.max_timestamp
WHERE
t1.item_id = 1
GROUP BY t1.item_id;
item_id | color | size | weight
1 | purple | large | 69
Demo

Related

How to track previous row status count

I want to calculate count of order status changes within different states.
My Orderstatus table:
| id |ordr_id| status |
|----|-------|------------|
| 1 | 1 | pending |
| 2 | 1 | processing |
| 3 | 1 | complete |
| 4 | 2 | pending |
| 5 | 2 | cancelled |
| 6 | 3 | processing |
| 7 | 3 | complete |
| 8 | 4 | pending |
| 9 | 4 | processing |
Output I want:
| state | count |
|----------------------|-------|
| pending->processing | 2 |
| processing->complete | 2 |
| pending->cancelled | 1 |
Currently I'm fetching the results by SELECT order_id,GROUP_CONCAT(status) as track FROM table group by order_id and then process the data in php to get the output. But is that possible in query itself ?
Use lag():
select prev_status, status, count(*)
from (select t.*,
lag(status) over (partition by order_id order by status) as prev_status
from t
) t
group by prev_status, status;
LAG() is available in MySQL starting with version 8.
Note that you can filter out the first status for each order by putting where prev_status is not null in the outer query.
Your version is not quite correct, because it does not enforce the ordering. It should be:
SELECT order_id,
GROUP_CONCAT(status ORDER BY id) as track
EDIT:
In earlier versions of MySQL, you can use a correlated subquery:
select prev_status, status, count(*)
from (select t.*,
(select t2.status
from t t2
where t2.order_id = t.order_id and t2.id < t.id
order by t2.id desc
limit 1
) as prev_status
from t
) t
group by prev_status, status;
If id column ensure the sequence of records, you can use self join to achieve your requirement as below-
SELECT A.Status +'>'+ B.Status, COUNT(*)
FROM OrderStatus A
INNER JOIN OrderStatus B
ON A.id = B.id -1
WHERE B.Status IS NOT NULL
GROUP BY A.Status +'>'+ B.Status
With a join of the 3 status change types to the grouping of the table that you already did:
select c.changetype, count(*) counter
from (
select 'pending->processing' changetype union all
select 'processing->complete' union all
select 'pending->cancelled'
) c inner join (
select
group_concat(status order by id separator '->') changestatus
from tablename
group by ordr_id
) t on concat('->', t.changestatus, '->') like concat('%->', changetype, '->%')
group by c.changetype
See the demo.
Results:
> changetype | counter
> :------------------- | ------:
> pending->cancelled | 1
> pending->processing | 2
> processing->complete | 2
...or just a simple join...
SELECT CONCAT(a.status,'->',b.status) action
, COUNT(*) total
FROM my_table a
JOIN my_table b
ON b.ordr_id = a.ordr_id
AND b.id = a.id + 1
GROUP
BY action;
+----------------------+-------+
| action | total |
+----------------------+-------+
| pending->cancelled | 1 |
| pending->processing | 2 |
| processing->complete | 2 |
+----------------------+-------+
Note that this relies on the fact that ids are contiguous.

MySQL queries, max & current rank for players

In the lists of players I need to find find the maximum rating and current rating
Petr | 1 | 2016-12-01 00:00:00
Petr | 2 | 2016-12-02 00:00:00
Petr | 3 | 2016-12-03 00:00:00
Oleg | 3 | 2016-12-01 00:00:00
Oleg | 2 | 2016-12-02 00:00:00
Oleg | 1 | 2016-12-03 00:00:00
I want to get a Output:
name | min | current
Petr | 1 | 3
Oleg | 1 | 1
For to find the maximum, I try
SELECT t1.rank as min
FROM table t1
LEFT JOIN table t2
ON t1.name = t2.name AND t1.rank > t2.rank
WHERE t2.name IS NULL
And other solve, for find the current
SELECT t1.rank as current
FROM table t1
WHERE t1.dt=(SELECT MAX(dt) FROM table t2 WHERE t1.name = t2.name)
I think you are looking for the minimum rating not maximum.
To get current, the rating with max date, use GROUP BY and then join the original table again, to get the rating value for this max date:
SELECT
t1.Name,
MAX(t2.MinRating) AS MinRating,
MAX(t1.Rating) AS Current
FROM yourTable AS t1
INNER JOIN
(
SELECT Name, MIN(rating) AS MinRating, MAX(rateDate) AS MaxRateDate
FROM yourTable
GROUP BY Name
) AS t2 ON t1.Name = t2.Name AND t1.rateDate = t2.MaxRateDate
GROUP BY t1.Name;
fiddle demo
| Name | MinRating | Current |
|------|-----------|---------|
| Oleg | 1 | 1 |
| Petr | 1 | 3 |

Get the duplicate entries from table mysql

I have the table structure as shown below. The database is MariaDB.
+-----------+----------+--------------+-----------------+
| id_object | name | value_double | value_timestamp |
+-----------+----------+--------------+-----------------+
| 1 | price | 1589 | null |
| 1 | payment | 1590 | null |
| 1 | date | null | 2012-04-17 |
| 2 | price | 1589 | null |
| 2 | payment | 1590 | null |
| 2 | date | null | 2012-04-17 |
| 3 | price | 1589 | null |
| 3 | payment | 1590 | null |
| 3 | date | null | 2012-09-25 |
| ... | ... | ... | .. |
+-----------+----------+--------------+-----------------+
1) I need to get the duplicates by three entries: price & payment & date;
For example: the record with id_object=2 is duplicate because price, payment and date are the same as values of the record with id_object=1. Record with id_object = 3 is not the duplicate because the date is different (2012-09-25 != 2012-04-17)
2) I should remove the duplicates except one copy of them.
I thought to do three select operations and join each select on id_object. I can get the duplicates by one entry (price | payment | date). I faced the problem doing the joins
SELECT `id_object`,`name`,{P.`value_double` | P.`value_timestamp`}
FROM record P
INNER JOIN(
SELECT {value_double | value_timestamp}
FROM record
WHERE name = {required_entry}
GROUP BY {value_double | value_timestamp}
HAVING COUNT(id_object) > 1
)temp ON {P.value_double = temp.value_double | P.value_timestamp = temp.value_timestamp}
WHERE name = {required_entry}
Can someone help and show the pure (better) solution?
Though less efficient than certain alternatives, I find an approach along these lines easier to read...
SELECT MIN(id_object) id_object
, price
, payment
, date
FROM
( SELECT id_object
, MAX(CASE WHEN name = 'price' THEN value_double END) price
, MAX(CASE WHEN name = 'payment' THEN value_double END) payment
, MAX(CASE WHEN name = 'date' THEN value_timestamp END) date
FROM eav
GROUP
BY id_object
) x
GROUP
BY price
, payment
, date;
I would just group_concat() the values together and do the test that way:
select t.*
from t join
(select min(id_object) id_object
from (select id_object,
group_concat(name, ':', coalesce(value_double, ''), ':', coalesce(value_timestamp, '') order by name) pairs
from t
where name in ('price', 'payment', 'date')
group by id_object
) tt
group by pairs
) tt
on t.id_object = tt.id_object;
To actually delete the ones that are not the minimum id for each group of related values:
delete t
from t left join
(select min(id) as id
from (select id, group_concat(name, ':', coalesce(value_double, ''), ':', coalesce(value_timestamp, '' order by name) as pairs,
from t
where name in ('price', 'payment', 'date')
group by id
) tt
group by pairs
) tt
on t.id = tt.id
where tt.id is null;

Combining Tables in MySQL to replace NULL values when rows match

I have an issue where different pieces of information on customers are found in completely different servers.
My solution so far is to take the necessary pieces from each server and combine them in MySQL.
My problem:
I'm looking to replace NULL values from one table in MySQL with information from another table when NULL in the first table, provided other columns match.
Information will only appear in the second table when the columns appear as NULL in the OrderName column and -1 in the Order_ID column from the original table. This second table will have the missing information and will align with other columns such as Cust_ID and DateTime.
E.g.
Table1:
Cust_ID | Order_ID | OrderName | DateTime | Other_Info
------------------------------------------------------------------------------
12345 | -1 | NULL | 2016/01/01 | Info1
12345 | 1254 | Orange | 2016/02/03 | Info2
54321 | 5412 | Apple | 2016/04/15 | Info3
45899 | -1 | NULL | 2016/06/08 | Info4
Table2:
Cust_ID | OrderID | OrderName | DateTime
-------------------------------------------------------------
12345 | 1549 | Banana | 2016/01/01
45899 | 5862 | Grape | 2016/06/08
Desired output:
Cust_ID | Order_ID | OrderName | DateTime | Other_Info
------------------------------------------------------------------------------
12345 | 1549 | Banana | 2016/01/01 | Info1
12345 | 1254 | Orange | 2016/02/03 | Info2
54321 | 5412 | Apple | 2016/04/15 | Info3
45899 | 5862 | Grape | 2016/06/08 | Info4
I have tried using CASE WHEN clauses combined with COALESCE function to achieve this with no success as of yet.
Is this possible to do in MySQL?
If so, any guidance on how to achieve this would be greatly appreciated.
Thanks for your help.
Use CASE EXPRESSION with a LEFT JOIN :
SELECT t1.cust_id,
CASE WHEN t1.order_id = -1 THEN t2.orderid else t1.order_id END as order_id,
CASE WHEN t1.orderName IS NULL THEN t2.orderName else t1.orderName END as order_Name,
t1.dateTime,
t1.other_info
FROM Table1 t
LEFT JOIN Table2 s
ON(t.cust_id = s.cust_id)
LEFT JOIN with CASE condition can get your required data.
SELECT T1.CUST_ID,
CASE WHEN T1.ORDER_NAME IS NULL THEN T2.ORDER_ID ELSE T1.ORDER_ID END,
CASE WHEN T1.ORDER_NAME IS NULL THEN T2.ORDER_NAME ELSE T1.ORDER_NAME END,
T1._DATE_TIME,
T1.OTHER_INFO
FROM
TABLE1 T1
LEFT JOIN TABLE2 T2
ON T1.CUST_ID = T2.CUST_ID;
You are looking for left join:
select t1.cust_id, coalesce(t2.orderid, t1.order_id) as order_id,
coalesce(t2.order_name, t1.order_name) as order_name,
t1.datetime, t1.other_info
from table1 t1 left join
table2 t2
on t1.cust_id = t2.cust_id and
t1.datetime = t2.datetime and
t1.order_id = -1;

cumulative average in MySQL

I have a table with id and values shown below. is it possible to get another column which takes the value divided by the cumulative average as we go down the row?
original table : t1
+----+----------------------+
| id | Val |
+----+---------------------+-
| 1 | NULL |
| 2 | 136 |
| 3 | 42 |
table i want to get
+----+---------------------+-----------------------------+
| id | Val | VAL/(AVG(VAL) ) |
+----+---------------------+-----------------------------+
| 1 | NULL | NULL |
| 2 | 136 | 136/((136+0)/2)=2.000 |
| 3 | 42 | 42/((42+136+0)/3)=0.708 |
here is my query:
SELECT t1.id, t1.Val, Val/AVG(t1.Val)
FROM followers t1
JOIN followers t2
ON t2.id <= t1.id
group by t1.id;
however i get this instead:
+----+---------------------+----------------------+
| id | Val | VAL/(AVG(VAL) ) |
+----+---------------------+----------------------+
| 1 | NULL | NULL |
| 2 | 136 | 1.0000 |
| 3 | 42 | 1.0000 |
seems like AVG(Val) returns the same value from the col Val.
I was hoping to do something similar to this link here but instead of sum i want average.
MySQL SELECT function to sum current data
I re-implemented the edits and took rows with NULL into account:
+----+---------------------+---------------------+
| id | Val | VAL/(AVG(VAL) ) |
+----+---------------------+----------------------+
| 1 | NULL | NULL |
| 2 | 136 | 1.0000 |<---need this to = 2.000
| 3 | 42 | 0.4719 |<---need this to = 0.708
SELECT t1.id, t1.Val, t1.Val/(SUM(t2.Val)/(t1.id)) AS C
FROM followers t1
JOIN followers t2
ON t2.id <= t1.id
group by t1.id;
I think you want t2.val in the avg():
SELECT t1.id, t1.Val, t1.Val/AVG(t2.Val)
FROM followers t1 JOIN
followers t2
ON t2.id <= t1.id
group by t1.id;
EDIT:
Mike Brand is correct that the above is a lousy way to do what you want. In MySQL, you can do the same using variables:
select t.id, t.val,
(case when (#n := #n + 1) is null then null
when (#cumval := #cumval + val) is null then null
else t.val / (#cumval / #n)
end)
from followers t cross join
(select #n := 0, #cumval := 0) vars
order by t.id;
This might misbehave with NULL values of val, but it gives the idea for a faster way to do the calculation in MySQL.