I'm trying to build a kind of peddy paper. For that I have the following tables:
teams
CREATE TABLE `teams` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`creator_id` int(11) NOT NULL,
`friend_id` int(11) DEFAULT NULL,
`team_name` varchar(128) NOT NULL,
PRIMARY KEY (`id`)
);
team_log
CREATE TABLE IF NOT EXISTS `progress_tracker` (
`id` int(8) NOT NULL AUTO_INCREMENT,
`user_id` int(8) NOT NULL,
`team_id` int(11) NOT NULL,
`date` date NOT NULL,
`clues_found` int(11) NOT NULL,
`clues_to_find` int(11) NOT NULL,
PRIMARY KEY (`id`)
);
Each team is composed by two users;
Each user starts out with a variable number of clues found;
clues_found can either increase or decrease. No guarantee that the highest number is the latest;
I need to get a rank of the teams (in percentage) based on the average of the number of clues the user found since they joined (for both users in a team) - clues_found on the row with biggest date minus clues_found on the record with lowest date).
For instance if I have the following data for each table:
teams table data
+--------+------------+------------+---------------+
| id | creator_id | friend_id | team_name |
+--------+------------+------------+---------------+
| 1 | 25 | 28 | Test1 |
| 2 | 31 | 5 | Test2 |
+--------+------------+------------+---------------+
team_log table data
+--------+---------+---------+------------+-------------+---------------+
| id | user_id | team_id | date | clues_found | clues_to_find |
+--------+---------+---------+------------+-------------+---------------+
| 1 | 25 | 1 | 2013-01-6 | 3 | 24 |
| 2 | 25 | 1 | 2013-01-8 | 7 | 24 |
| 3 | 25 | 1 | 2013-01-10 | 10 | 24 |
| 4 | 28 | 1 | 2013-01-8 | 5 | 30 |
| 5 | 28 | 1 | 2013-01-14 | 20 | 30 |
| 6 | 31 | 2 | 2013-01-11 | 6 | 14 |
| 7 | 5 | 2 | 2013-01-9 | 2 | 20 |
| 8 | 5 | 2 | 2013-01-10 | 10 | 20 |
| 9 | 5 | 2 | 2013-01-12 | 14 | 20 |
+--------+---------+---------+------------+-------------+---------------+
Desired Result
+-------------+---------------------+
| team_id | team_percentage |
+-------------+---------------------+
| 1 | 39,58333333 |
| 2 | 30 |
+-------------+---------------------+
As a reference this is an intermediate representation which might help to understand:
+-------------+---------+---------------------+
| user_id | team_id | precentage_per_user |
+-------------+---------+---------------------+
| 25 | 1 | 29,16666667 |
| 28 | 1 | 50 |
| 31 | 2 | 0 |
| 5 | 2 | 60 |
+-------------+---------+---------------------+
So far I have the following sql:
SELECT STRAIGHT_JOIN
tl2.team_id, (tl2.weight - tl1.weight)*100/tl2.clues_to_find
from
( select
team_id,user_id,clues_found
FROM
`team_log`
where 1
group by
team_id, user_id
order by
`date` ) base
join (select team_id, user_id, clues_found, clues_to_find from `team_log` where user_id = base.user_id and team_id = base.team_id group by team_id, user_id order by `date` desc) tl2
But this returns an error as I'm not allowed to use base.user_id inside the second query. I'm also not very sure I'm heading in the right direction.
Can anyone help please?
Here's another query that will produce the correct result:
SELECT calc.team_id, AVG((calc.end_clues - calc.start_clues)/calc.total_clues*100) as team_percentage
FROM
(SELECT log1.user_id, log1.team_id, log1.clues_found as start_clues, log2.clues_found as end_clues, log2.clues_to_find as total_clues FROM team_log log1
JOIN
(SELECT MIN(id) as start_id, MAX(id) as end_id FROM team_log GROUP BY user_id) ids
ON ids.start_id = log1.id
JOIN team_log log2 ON ids.end_id = log2.id) calc
GROUP BY team_id
ORDER BY team_id;
And the SQL Fiddle-link...
Please take a look at this and comment:
SQLFIDDLE DEMO
Team pct:
select z.team_id, avg(z.pct) as teampct
from (
select x.user_id, y.team_id, x.mndate,
y.mxdate, x.mnclues_found,
y.mxclues_found,
(((y.mxclues_found - x.mnclues_found)*100)
/y.mxclues_tofind) pct
from
(select user_id, team_id, min(date) mndate,
min(clues_found) as mnclues_found
from team_log
group by user_id, team_id) x
left join
(select user_id, team_id, max(date) mxdate,
max(clues_found) as mxclues_found,
max(clues_to_find) as mxclues_tofind
from team_log
group by user_id, team_id) y
on x.user_id = y.user_id and
x.team_id = y.team_id) z
group by z.team_id
;
Results 1:
| USER_ID | TEAM_ID | MNDATE | MXDATE | MNCLUES_FOUND | MXCLUES_FOUND | PCT |
-------------------------------------------------------------------------------------
| 5 | 2 | 13-01-09 | 13-01-12 | 2 | 14 | 60 |
| 25 | 1 | 13-01-06 | 13-01-10 | 3 | 10 | 29.1667 |
| 28 | 1 | 13-01-08 | 13-01-14 | 5 | 20 | 50 |
| 31 | 2 | 13-01-11 | 13-01-11 | 6 | 6 | 0 |
Results final:
| TEAM_ID | TEAMPCT |
----------------------
| 1 | 39.58335 |
| 2 | 30 |
This is a bit ugly, but should work:
select
team_id,
AVG(percentage_per_user) as team_percentage
from (select
team_id,
user_id,
((select clues_found from progress_tracker as x
where x.user_id = m.user_id order by x.date desc limit 0, 1)
- (select clues_found from progress_tracker as y
where y.user_id = m.user_id order by y.date asc limit 0, 1))
/ MAX(clues_to_find)
as percentage_per_user
from progress_tracker as m
group by team_id, user_id
) as userScore
group by team_id
order by team_percentage desc;
Note the inner query run by itself will yield your intermediate "per-user" result.
SQLFiddle
SELECT `team_id`,
(SUM(CASE WHEN b.`date` IS NULL THEN 0 ELSE `clues_found` * 100 / `clues_to_find` END) -
SUM(CASE WHEN c.`date` IS NULL THEN 0 ELSE `clues_found` * 100 / `clues_to_find` END)) / 2
FROM `team_log` a
LEFT JOIN (
SELECT `team_id`, `user_id`, MAX(date) AS `date`
FROM `team_log`
GROUP BY `team_id`, `user_id`) b
USING (`team_id`, `user_id`, `date`)
LEFT JOIN (
SELECT `team_id`, `user_id`, MIN(date) AS `date`
FROM `team_log`
GROUP BY `team_id`, `user_id`) c
USING (`team_id`, `user_id`, `date`)
GROUP BY `team_id`
Since you say there are always two team members, I've used /2. It would be slightly more complex for variable-sized teams.
Related
I have 2 tables, one with price overrides and one with minute overrides, each per user and product. How can I select them so that I have a record for each user and product?
Table 1: Override Prices
+--------+-----------+---------------+
| userID | productID | overridePrice |
+--------+-----------+---------------+
| 4 | 53 | 99.99 |
| 4 | 13 | 99.99 |
| 4 | 55 | 99.99 |
+--------+-----------+---------------+
Table 2: Override Minutes
+--------+-----------+---------------+
| userID | productID | overrideMin |
+--------+-----------+---------------+
| 4 | 18 | 23 |
| 4 | 55 | 4 |
| 50 | 55 | 2 |
+--------+-----------+---------------+
The table I want it to produce:
Table 2: All overrides
+--------+-----------+-------------+---------------+
| userID | productID | overrideMin | overridePrice |
+--------+-----------+-------------+---------------+
| 4 | 13 | null | 99.99 |
| 4 | 18 | 23 | null |
| 4 | 53 | null | 99.99 |
| 4 | 55 | 4 | 99.99 |
| 50 | 55 | 2 | null |
+--------+-----------+-------------+---------------+
I've attempted to GROUP BY userID, productID, but because product IDs may exist in table 1 that don't exist in table 2, I get different results depending on which productID I group by.
Use UNION:
SELECT userID, productID,
MAX(overrideMin) AS overrideMin,
MAX(overridePrice) AS overridePrice
FROM
(
SELECT userID, productID, null AS overrideMin, overridePrice
FROM OverridePrices
UNION
SELECT userID, productID, overrideMin, null AS overridePrice
FROM OverrideMinutes
) AS t
GROUP BY userID, productID;
This will give you the exact results you are looking for:
| userID | productID | overrideMin | overridePrice |
|--------|-----------|-------------|---------------|
| 4 | 13 | (null) | 99.99 |
| 4 | 18 | 23 | (null) |
| 4 | 53 | (null) | 99.99 |
| 4 | 55 | 4 | 99.99 |
| 50 | 55 | 2 | (null) |
You have to use IF statement:
SELECT
t1.userID,
t1.productID,
if (t2.overrideMin IS NULL, NULL, t2.overrideMin) AS overrideMin,
if (t1.overridePrice IS NULL, NULL, t1.overridePrice) AS overridePrice
FROM OverridePrices AS t1
LEFT JOIN OverrideMinutes AS t2 ON t1.userID = t2.userID AND t1.productID = t2.productID;
But in case you can have different products in table1 and table2 you have to join to table which contains all products, like:
SELECT
t1.userID,
t1.productID,
if (t2.overrideMin IS NULL, NULL, t2.overrideMin) AS overrideMin,
if (t1.overridePrice IS NULL, NULL, t1.overridePrice) AS overridePrice
FROM (
SELECT userID, productID FROM OverridePrices
UNION
SELECT userID, productID FROM OverrideMinutes
) AS t0
LEFT JOIN OverridePrices AS t1 ON t0.userID = t1.userID AND t0.productID = t1.productID
LEFT JOIN OverrideMinutes AS t2 ON t0.userID = t2.userID AND t0.productID = t2.productID;
Also, now you can have GROUP, HAVING etc.
SELECT table1.userID,table1.productID,table2.overrideMin,table1.overridePrice FROM table1
LEFT JOIN table2 ON table1.userID=table2.userID AND table1.productID = table2.productID
UNION SELECT table2.userID,table2.productID,table2.overrideMin,table1.overridePrice FROM table1
RIGHT JOIN table2 ON table1.userID=table2.userID AND table1.productID = table2.productID
ORDER BY userID, productID
This is the OUTPUT
I have asked a question on How to get temporal sequence by mysql before. There I want to get a simplified sequence with the latest date.
Now I want to get the start date and end date of each sequence. Suppose that the table is still like this:
ID DATE STATUS
1 0106 A
1 0107 A
1 0112 A
1 0130 B
1 0201 A
2 0102 C
2 0107 C
and I want to get the result like this:
ID START_DATE END_DATE STATUS
1 0106 0112 A
1 0130 0130 B
1 0201 0201 A
2 0102 0107 C
I tried to adapt the answer of former question to it though but failed. I am wondering how I can realize it.
Given this
SELECT * FROM T;
+------+------+--------+
| ID | DATE | STATUS |
+------+------+--------+
| 1 | 106 | A |
| 1 | 107 | A |
| 1 | 112 | A |
| 1 | 130 | B |
| 1 | 201 | A |
| 2 | 102 | C |
| 2 | 107 | C |
+------+------+--------+
It's quite straightforward to allocate a block and seqno using this
SELECT T.ID,T.DATE,T.STATUS,
IF(STATUS <> #PREVS,#RN:=#RN+1,#RN:=#RN) RNBLOCK ,
IF(STATUS = #PREVS,#RN2:=#RN2+1,#RN2:=1) RNSEQ ,
#PREVS:=STATUS PSTATUS
FROM (SELECT #RN:=1) RNBLOCK, (SELECT #RN2:=0) RNSEQ,(SELECT #PREVS:=NULL) P, T
To give this
+------+------+--------+---------+-------+---------+
| ID | DATE | STATUS | RNBLOCK | RNSEQ | PSTATUS |
+------+------+--------+---------+-------+---------+
| 1 | 106 | A | 1 | 1 | A |
| 1 | 107 | A | 1 | 2 | A |
| 1 | 112 | A | 1 | 3 | A |
| 1 | 130 | B | 2 | 1 | B |
| 1 | 201 | A | 3 | 1 | A |
| 2 | 102 | C | 4 | 1 | C |
| 2 | 107 | C | 4 | 2 | C |
+------+------+--------+---------+-------+---------+
So now we have isolated the blocks and know the min seq no (1) and the max seqno and we can push them into a table
drop table t1;
CREATE TABLE `t1` (
`ID` INT(11) NULL DEFAULT NULL,
`DATE` INT(11) NULL DEFAULT NULL,
`STATUS` VARCHAR(1) NULL DEFAULT NULL,
`rnblock` int null default null,
`rnseq` int null default null,
`pstatus` VARCHAR(1) NULL DEFAULT NULL
)
COLLATE='latin1_swedish_ci'
ENGINE=InnoDB
;
and create a simple min max join
SELECT T2.ID,T2.DATE,T3.DATE,T2.STATUS FROM
(
SELECT T1.RNBLOCK,MAX(T1.RNSEQ) MAXSEQ
FROM T1
GROUP BY RNBLOCK
) S
JOIN T1 T2 ON T2.RNBLOCK = S.RNBLOCK AND T2.RNSEQ = 1
JOIN T1 T3 ON T3.RNBLOCK = S.RNBLOCK AND T3.RNSEQ = S.MAXSEQ
to get this
+------+------+------+--------+
| ID | DATE | DATE | STATUS |
+------+------+------+--------+
| 1 | 106 | 112 | A |
| 1 | 130 | 130 | B |
| 1 | 201 | 201 | A |
| 2 | 102 | 107 | C |
+------+------+------+--------+
The downside is that you have to create a table to make it work.
Or you could use this rather ungainly code which does not use an intermediate table
select u.id,u.date,v.date,u.status from
(
select s.rnblock,s.status,min(s.rnseq) minseq,max(s.rnseq) maxseq
from
(
SELECT T.ID,T.DATE,T.STATUS,
IF(STATUS <> #PREVS,#RN:=#RN+1,#RN:=#RN) RNBLOCK ,
IF(STATUS = #PREVS,#RN2:=#RN2+1,#RN2:=1) RNSEQ ,
#PREVS:=STATUS PSTATUS
FROM (SELECT #RN:=1) RNBLOCK, (SELECT #RN2:=0) RNSEQ,(SELECT #PREVS:=NULL) P, T
) s
group by s.rnblock,s.status
) T
join
(SELECT T.ID,T.DATE,T.STATUS,
IF(STATUS <> #PREVS2,#RN3:=#RN3+1,#RN3:=#RN3) RNBLOCK ,
IF(STATUS = #PREVS2,#RN4:=#RN4+1,#RN4:=1) RNSEQ ,
#PREVS2:=STATUS PSTATUS
FROM (SELECT #RN3:=1) RNBLOCK, (SELECT #RN4:=0) RNSEQ,(SELECT #PREVS2:=NULL) P, T
) u on u.rnblock = t.rnblock and u.rnseq = minseq
join
(SELECT T.ID,T.DATE,T.STATUS,
IF(STATUS <> #PREVS3,#RN5:=#RN5+1,#RN5:=#RN5) RNBLOCK ,
IF(STATUS = #PREVS3,#RN6:=#RN6+1,#RN6:=1) RNSEQ ,
#PREVS3:=STATUS PSTATUS
FROM (SELECT #RN5:=1) RNBLOCK, (SELECT #RN6:=0) RNSEQ,(SELECT #PREVS3:=NULL) P, T
) v on v.rnblock = t.rnblock and v.rnseq = maxseq
Oh I have just thought out a method though it seems very stupid. The method is just executing the code from How to get temporal sequence by mysql twice(by different order) and join these two tables.
I store transactions in a table, and I want to delete all transactions (grouped by a user_id) except the one with the biggest amount, here is an example table:
+----+---------+--------+
| id | user_id | amount |
+----+---------+--------+
| 1 | 1 | 10 |
+----+---------+--------+
| 2 | 1 | 20 |
+----+---------+--------+
| 3 | 1 | 30 |
+----+---------+--------+
| 4 | 2 | 50 |
+----+---------+--------+
| 5 | 2 | 100 |
+----+---------+--------+
| 6 | 3 | 2 |
+----+---------+--------+
| 7 | 3 | 4 |
+----+---------+--------+
I want the following result
+----+---------+--------+
| id | user_id | amount |
+----+---------+--------+
| 3 | 1 | 30 |
+----+---------+--------+
| 5 | 2 | 100 |
+----+---------+--------+
| 7 | 3 | 4 |
+----+---------+--------+
I tried
DELETE FROM `transactions`
WHERE `user_id` NOT IN (
SELECT `user_id`
FROM (
SELECT MAX(`amount`) AS ts
FROM `transactions` e
WHERE `user_id` = `user_id`
) s
WHERE ts = `transactions`.`amount`
)
ORDER BY `transactions`.`user_id` ASC
DELETE FROM `transactions`
WHERE id NOT IN
(
SELECT MAX(id)
FROM `transactions`
group by user_id
)
The inner query groups by each user and select only the highest ID for each. Delete all records except the IDs from the inner select.
Wasn't sure what did you mean by except the latest one so I considered except last record inserted hence ORDER BY id DESC was used
DELETE FROM `transactions`
WHERE `id` NOT IN (
SELECT `id`
FROM `transactions`
GROUP BY `user_id`
ORDER BY `id` DESC
)
I have two tables:
mysql> select * from orders;
+------+---------------------+------------+---------+
| id | created_at | foreign_id | data |
+------+---------------------+------------+---------+
| 1 | 2010-10-10 10:10:10 | 3 | order 1 |
| 4 | 2010-10-10 00:00:00 | 6 | order 4 |
| 5 | 2010-10-10 00:00:00 | 7 | order 5 |
+------+---------------------+------------+---------+
mysql> select * from activities;
+------+---------------------+------------+------+
| id | created_at | foreign_id | verb |
+------+---------------------+------------+------+
| 1 | 2010-10-10 10:10:10 | 3 | get |
| 2 | 2010-10-10 10:10:15 | 3 | set |
| 3 | 2010-10-10 10:10:20 | 3 | put |
| 4 | 2010-10-10 00:00:00 | 6 | get |
| 5 | 2010-10-11 00:00:00 | 6 | set |
| 6 | 2010-10-12 00:00:00 | 6 | put |
+------+---------------------+------------+------+
Now I need to join activities with orders on foreign_id column: select only one activity (if exists) for every order such that ABS(TIMESTAMPDIFF(SECOND, orders.created_at, activities.created_at)) is minimal. E.g. the order and the activity were created approximately at the same time.
+----------+---------+---------------------+-------------+------+---------------------+
| order_id | data | order_created_at | activity_id | verb | activity_created_at |
+----------+---------+---------------------+-------------+------+---------------------+
| 1 | order 1 | 2010-10-10 10:10:10 | 1 | get | 2010-10-10 10:10:10 |
| 4 | order 4 | 2010-10-10 00:00:00 | 4 | get | 2010-10-10 00:00:00 |
| 5 | order 5 | 2010-10-10 00:00:00 | NULL | NULL | NULL |
+----------+---------+---------------------+-------------+------+---------------------+
The following query produces set of rows that includes the desired rows. If GROUP BY statement is included then it's not possible to control which row from activities is joined.
SELECT o.id AS order_id
, o.data AS data
, o.created_at AS order_created_at
, a.id AS activity_id
, a.verb AS verb
, a.created_at AS activity_created_at
FROM orders AS o
LEFT JOIN activities AS a ON a.foreign_id = o.foreign_id;
Is it possible to write such a query? Ideally I'd like to avoid using group by because this section is a part of larger reporting querty.
Because both tables reference some mysterious foreign key there's potential for errors with the query below, but it may give you a principle which you can adapt for your purposes...
DROP TABLE IF EXISTS orders;
CREATE TABLE orders
(id INT NOT NULL PRIMARY KEY
,created_at DATETIME NOT NULL
,foreign_id INT NOT NULL
,data VARCHAR(20) NOT NULL
);
INSERT INTO orders VALUES
(1 ,'2010-10-10 10:10:10',3 ,'order 1'),
(4 ,'2010-10-10 00:00:00',6 ,'order 4'),
(5 ,'2010-10-10 00:00:00',7 ,'order 5');
DROP TABLE IF EXISTS activities;
CREATE TABLE activities
(id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,created_at DATETIME NOT NULL
,foreign_id INT NOT NULL
,verb VARCHAR(20) NOT NULL
);
INSERT INTO activities VALUES
(1,'2010-10-10 10:10:10',3,'get'),
(2,'2010-10-10 10:10:15',3,'set'),
(3,'2010-10-10 10:10:20',3,'put'),
(4,'2010-10-10 00:00:00',6,'get'),
(5,'2010-10-11 00:00:00',6,'set'),
(6,'2010-10-12 00:00:00',6,'put');
SELECT o.id order_id
, o.data
, o.created_at order_created_at
, a.id activity_id
, a.verb
, a.created_at activity_created_at
FROM activities a
JOIN orders o
ON o.foreign_id = a.foreign_id
JOIN
( SELECT a.foreign_id
, MIN(ABS(TIMEDIFF(a.created_at,o.created_at))) x
FROM activities a
JOIN orders o
ON o.foreign_id = a.foreign_id
GROUP
BY a.foreign_id
) m
ON m.foreign_id = a.foreign_id
AND m.x = ABS(TIMEDIFF(a.created_at,o.created_at))
UNION DISTINCT
SELECT o.id
, o.data
, o.created_at
, a.id
, a.verb
, a.created_at
FROM orders o
LEFT
JOIN activities a
ON a.foreign_id = o.foreign_id
WHERE a.foreign_id IS NULL;
;
+----------+---------+---------------------+-------------+------+---------------------+
| order_id | data | order_created_at | activity_id | verb | activity_created_at |
+----------+---------+---------------------+-------------+------+---------------------+
| 1 | order 1 | 2010-10-10 10:10:10 | 1 | get | 2010-10-10 10:10:10 |
| 4 | order 4 | 2010-10-10 00:00:00 | 4 | get | 2010-10-10 00:00:00 |
| 5 | order 5 | 2010-10-10 00:00:00 | NULL | NULL | NULL |
+----------+---------+---------------------+-------------+------+---------------------+
I want to get the customer info where customer id should be 2 and the log_id should be maximum value
i tried below query but it is fetching first record found.
What will be the simple query
mysql> select * from log_customer where customer_id =2 group by customer_id having max(log_id);
+--------+------------+-------------+---------------------+-----------+----------+
| log_id | visitor_id | customer_id | login_at | logout_at | store_id |
+--------+------------+-------------+---------------------+-----------+----------+
| 2 | 56 | 2 | 2010-02-19 19:34:45 | NULL | 1 |
+--------+------------+-------------+---------------------+-----------+----------+
1 row in set (0.00 sec)
mysql> select * from log_customer where customer_id =2 limit 5;
+--------+------------+-------------+---------------------+---------------------+----------+
| log_id | visitor_id | customer_id | login_at | logout_at | store_id |
+--------+------------+-------------+---------------------+---------------------+----------+
| 2 | 56 | 2 | 2010-02-19 19:34:45 | NULL | 1 |
| 3 | 114 | 2 | 2010-02-23 17:31:55 | NULL | 1 |
| 31 | 1854 | 2 | 2010-03-08 18:31:28 | 2010-03-08 18:56:49 | 1 |
| 32 | 1992 | 2 | 2010-03-09 01:12:43 | NULL | 1 |
| 33 | 2304 | 2 | 2010-03-09 14:42:39 | NULL | 1 |
+--------+------------+-------------+---------------------+---------------------+----------+
Please do not suggest order by log_id desc I don't want to get in this way
SELECT *
FROM log_customer
WHERE customer_id = 2
AND log_id = (Select Max(Log_id)
FROM log_Customer
WHERE customer_id = 2)
That should do the trick
Edit without the Where:
SELECT *
FROM log_customer
WHERE log_id = (Select Max(Log_id)
FROM log_Customer
WHERE customer_id = 2)
Maybe this way:
SELECT *
FROM Log_Customer
WHERE Customer_Id = 2
AND Log_Id = (SELECT Max(Log_Id)
FROM Log_Customer
WHERE Customer_Id = 2)
A sub-select usually isn't bad as far as the execution plan is concerned.
select * from log_customer
left join
(select max(log_id) as max_id from log_customer where customer_id=2)
as log_customer2
on log_customer.log_id=log_customer2.max_id
where log_customer.customer_id=2;
oh damn...
select * from log_customer where customer_id=2 order by log_id desc limit 1;