Mysql Query SUM adding each to eachother result? - php

Sorry if my question makes no sense. Not sure if we can do this with mysql only. Lets say I have this query:
SELECT SUM(win) * 100 as win_profit, date, uid FROM `tips` WHERE uid = 60 AND placed = 1 GROUP by date
This would obviously get the sum of the win column each day that is in the database.
Lets say the database had:
|___win___|____date____|
| 10 | 2014-04-16 |
| 10 | 2014-04-16 |
| 10 | 2014-04-17 |
| 10 | 2014-04-18 |
| 10 | 2014-04-18 |
| 10 | 2014-04-18 |
| 10 | 2014-04-19 |
| 10 | 2014-04-19 |
| 10 | 2014-04-19 |
This would result:
20
10
30
30
How can I get it to result so each adds up, mysql query only. So the result would be:
20
30
60
90

You could get all distinct dates, and LEFT JOIN to find the sum of all values up to that date; I kept the 100 multiplier from your sample query, but you need to remove it to get a result matching your desired result.
SELECT 100 * SUM(b.win), a.date
FROM (SELECT DISTINCT date FROM `tips`) a
LEFT JOIN tips b ON a.date >= b.date
GROUP BY a.date
ORDER BY a.date
An SQLfiddle to test with.

This could be another way to do it...
SET #full_sum=0;
SELECT #full_sum+SUM(win) as win_profit, date as this_date, uid,
#full_sum:=(SELECT #full_sum+SUM(win)
FROM `testing` WHERE uid = 60
GROUP by date HAVING date=this_date)
FROM `testing` WHERE uid = 60 GROUP by date;

Related

Get rows above and below (neighbouring rows) a certain row, based on two criteria SQL

Say I have a table like so:
+---+-------+------+---------------------+
|id | level |score | timestamp |
+---+-------+------+---------------------+
| 4 | 1 | 70 | 2021-01-14 21:50:38 |
| 3 | 1 | 90 | 2021-01-12 15:38:0 |
| 1 | 1 | 20 | 2021-01-14 13:10:12 |
| 5 | 1 | 50 | 2021-01-13 12:32:11 |
| 7 | 1 | 50 | 2021-01-14 17:15:20 |
| 8 | 1 | 55 | 2021-01-14 09:20:00 |
| 10| 2 | 99 | 2021-01-15 10:50:38 |
| 2 | 1 | 45 | 2021-01-15 10:50:38 |
+---+-------+------+---------------------+
What I want to do is show 5 of these rows in a table (in html), with a certain row (e.g. where id=5) in the middle and have the two rows above and below it (in the correct order). Also where level=1. This will be like a score board but only showing the user's score with the two above and two below.
So because scores can be the same, the timestamp column will also need to be used - so if two scores are equal, then the first person to get the score is shown above the other person.
E.g. say the user is id=5, I want to show
+---+-------+------+---------------------+
|id | level |score | timestamp |
+---+-------+------+---------------------+
| 4 | 1 | 70 | 2021-01-14 21:50:38 |
| 8 | 1 | 55 | 2021-01-14 09:20:00 |
| 5 | 1 | 50 | 2021-01-13 12:32:11 |
| 7 | 1 | 50 | 2021-01-14 17:15:20 |
| 2 | 1 | 45 | 2021-01-15 10:50:38 |
| 1 | 1 | 20 | 2021-01-14 13:10:12 |
+---+-------+------+---------------------+
Note that id=7 is below id=5
I am wondering does anyone know a way of doing this?
I have tried this below but it is not outputting what I need (it is outputting where level_id=2 and id=5, and the other rows are not in order)
((SELECT b.* FROM table a JOIN table b ON b.score > a.score OR (b.score = a.score AND b.timestamp < a.timestamp)
WHERE a.level_id = 1 AND a.id = 5 ORDER BY score ASC, timestamp DESC LIMIT 3)
UNION ALL
(SELECT b.* FROM table a JOIN table b ON b.score < a.score OR (b.score = a.score AND b.timestamp > a.timestamp)
WHERE a.level_id = 1 AND a.id = 5 ORDER BY score DESC, timestamp ASC LIMIT 2))
order by score
If it is easier to output all rows in the table, say where level = 1, so it is a full score board.. and then do the getting a certain row and two above and below it using PHP I'd also like to know please :) ! (possibly thinking this may keep the SQL simpler)?
You can use cte and inner join as follows:
With cte as
(select t.*,
dense_rank() over (order by score) as dr
from your_table t)
Select c.*
From cte c join cte cu on c.dr between cu.dr - 2 and cu.dr + 2
Where cu.id = 5
Ordwr by c.dr, c.timestamp
I would suggest window functions:
select t.*
from (select t.*,
max(case when id = 7 then score_rank end) over () as id_rank
from (select t.*,
dense_rank() over (order by score) as score_rank
from t
where level = 1
) t
) t
where score_rank between id_rank - 2 and id_rank + 2;
Note: This returns 5 distinct score values, which may result in more rows depending on duplicates.
Here is a db<>fiddle.
EDIT:
If you want exactly 5 rows using the timestamp, then:
select t.*
from (select t.*,
max(case when id = 7 then score_rank end) over () as id_rank
from (select t.*,
dense_rank() over (order by score, timestamp) as score_rank
from t
where level = 1
) t
) t
where score_rank between id_rank - 2 and id_rank + 2
order by score;
Note: This still treats equivalent timestamps as the same, but they seem to be unique in your data.

How to get the latest data of a specific day? [MYSQL]

How can I get the latest data of a specific day in MySQL?
Let's assume that I have a column of dates recorded on my database
dates | time | value
---------------------------------------------
2015-08-05 | 11:03:02 | 200
2015-08-05 | 23:04:22 | 2400
2015-08-07 | 8:00:22 | 500
2015-08-08 | 13:00:11 | 400
2015-08-08 | 13:23:11 | 200
2015-08-09 | 17:00:23 | 2200
2015-08-09 | 17:06:00 | 1290
2015-08-09 | 19:22:00 | 900
2015-08-13 | 01:01:22 | 1010
I want to get the latest data or transaction of a specific date, my desired result would be like this
dates | time | value
---------------------------------------------
2015-08-05 | 23:04:22 | 2400
2015-08-07 | 8:00:22 | 500
2015-08-08 | 13:23:11 | 200
2015-08-09 | 19:22:00 | 900
2015-08-13 | 01:01:22 | 1010
Only the latest data of a spefic or distinct date is chosen, what is the possible query with this?
You need to do this way to get that for each dates
select t1.dates,t1.time,t1.value from table as t1 inner join
(
select dates,max(time) as time from table group by dates
) as t2 on t1.dates=t2.dates and t1.time=t2.time
Use
SELECT * FROM TABLE_NAME GROUP BY dates ORDER BY time desc LIMIT 1;
Or
SELECT * FROM TABLE_NAME GROUP BY dates HAVING time <= '23:59:59' LIMIT 1;
Try this:
SELECT `dates`, MAX(`time`) AS `time`, MAX(`value`) AS `value`
FROM `tbl_name`
GROUP BY `dates`
ORDER BY `dates` ASC
Try this query:
SELECT * FROM `table` GROUP BY dates ORDER BY time DESC;

Mixed sorting of dates in MySQL

I have a date column which holds either a date value or null. I use that to hold a product's expiration date. I want to write a query to fetch them in this order: Not yet expired, no expiration (null), and expired.
For example, assuming today is May 15:
prd_id prd_name Expiry Date
-----------------
1 name1 May 16
2 name2 May 17
3 name3 May 18
4 name4 May 21
5 namex null
6 namex null
7 namex null
8 namex May 14
9 namex May 12
(A null value denotes no expiration)
How would I do this?
You can try follwing Syntax:-
SELECT *
FROM YOUR_TABLE
ORDER BY CASE WHEN expire_date > CURDATE() THEN 1 END,
WHEN expire_date IS NULL THEN 2 Desc END,
WHEN expire_date < CURDATE() THEN 3 Desc END;
Essentially, you'll be joining three separate queries together:
SELECT * FROM (SELECT * FROM `product_entries` WHERE expires_on IS NOT NULL AND expires_on > CURDATE()
ORDER BY expires_on ASC) a
UNION ALL
SELECT * FROM (SELECT * FROM `product_entries` WHERE expires_on IS NULL) b
UNION ALL
SELECT * FROM (SELECT * FROM `product_entries` WHERE expires_on IS NOT NULL AND expires_on <= CURDATE()
ORDER BY expires_on DESC) c
The first one returns those that haven't expired, the second returns those without expiration, and the third one returns expired entries.
Keep in mind that you'd want the expires_on column to be indexed. Also, as you can see in the third query, I'm counting the current date as being expired. If you want the current day to count as not being expired, then change the <= to < in the third query, and > to >= in the first query.
Another alternative would to be to use a CASE clause (if you're not concerned about the order of each entry, so long as the non-expired are at the top, the perpetual products are in the middle, and the expired entries are at the bottom)
mysql> SELECT * FROM product_entries ORDER BY CASE
-> WHEN expires_on >= CURDATE() THEN 3
-> WHEN expires_on IS NULL THEN 2
-> WHEN expires_on < CURDATE() THEN 1
-> END DESC;
+----+-------------+
| id | expire_date |
+----+-------------+
| 9 | 2015-05-11 |
| 8 | 2015-05-06 |
| 7 | 2015-05-01 |
| 10 | NULL |
| 6 | 2015-04-26 |
| 5 | 2015-04-21 |
| 4 | 2015-04-16 |
| 3 | 2015-04-11 |
| 2 | 2015-04-06 |
| 1 | 2015-04-01 |
+----+-------------+

How to select data from multiple tables based on different time ranges?

I have two tables in MySQL:
visits
points
visits table looks like the following:
+-------+-------------------+-----------+
| id | date | user_id |
+-------+-------------------+-----------+
| 1 |2014-08-01 05:23:00| 43 |
| 2 |2014-08-01 14:41:00| 21 |
| 3 |2014-08-02 23:54:00| 43 |
| 4 |2014-08-03 03:21:00| 43 |
| 5 |2014-08-03 04:19:00| 34 |
| 6 |2014-08-03 11:33:00| 43 |
| 7 |2014-08-04 12:21:00| 43 |
| 8 |2014-08-05 01:55:00| 43 |
| 9 |2014-08-06 06:13:00| 43 |
| 10 |2014-08-07 19:47:00| 43 |
+-------+-------------------+-----------+
points table looks like the following:
+-------+-------------------+-----------+-------+----------+
| id | date | user_id | points| status |
+-------+-------------------+-----------+-------+----------+
| 1 |2014-08-01 04:33:00| 43 | 10 | 0 |
| 2 |2014-08-02 05:21:00| 21 | 23 | 0 |
| 3 |2014-08-02 09:01:00| 43 | 32 | 1 |
| 4 |2014-08-02 01:21:00| 43 | 21 | 0 |
| 5 |2014-08-03 23:23:00| 34 | 54 | 0 |
| 6 |2014-08-04 20:34:00| 43 | 11 | 0 |
| 7 |2014-08-04 17:54:00| 43 | 9 | 0 |
| 8 |2014-08-04 03:45:00| 43 | 34 | 0 |
| 9 |2014-08-06 08:23:00| 43 | 76 | 0 |
| 10 |2014-08-07 11:43:00| 43 | 52 | 0 |
+-------+-------------------+-----------+-------+----------+
I want execute only 1 query and achieve the following.
I'd like to count the rows in the visits table where the user_id = 43 and the date is between 2014-08-01 and 2014-08-03.
I also want to count the rows, sum the points in the points table where user_id = 43 and the date is between 2014-08-01 and 2014-08-03 and the status is 0.
After that, in the same query, I'd like to select the same as above, but in a different timeframe, like: 2014-08-04 and 2014-08-07.
Is there any query out there which can solve this problem for me?
(I'm actually doing this because I'd like to get data for one of my jQuery chart which is called: Morris.js. I'd like to get 12 datasets if the visitor selects a time range and divide it based on the days, the visitor selected. For example: if he selects: 2014-08-01 till 2014-08-01, I want to display him 12 datasets of that day. But if he selects for example: 2014-08-01 till 2014-08-06, then I'd want to display him the data for the 6 day divided by 12.)
If you don't understand something here, just let me know and I'll explain it better. The point is that I'd like to collet the datasets and draw the chart based on the time range to the visitor. Is that above MySQL logic a good solution for my issue?
EDIT:
As per request I'm showing the desired result in here:
+---------------------+-----------+-------+-------+-------------+
| date | user_id | points| visits| points_count|
+---------------------+-----------+-------+-------+-------------+
|2014-08-01-2014-08-03| 43 | 31 | 4 | 2 |
|2014-08-04-2014-08-07| 43 | 182 | 4 | 5 |
+---------------------+-----------+-------+-------+-------------+
I hope I've calculated everything correctly.
ok so this took a little work because you have to join the two tables independantly of eachother.. the reason being is because one can't have the status of 1 and the other can.. so with that in mind this query will return exactly what you want.
QUERY:
SELECT
t.join_date as 'Time Frame',
t1.user_id,
t.num_visits,
t1.num_points,
t1.total_points
FROM
( SELECT
CASE
WHEN DATE(date) >= '2014-06-01' AND DATE(date) <= '2014-07-10' THEN 1
WHEN DATE(date) >= '2014-08-05' AND DATE(date) <= '2014-08-07' THEN 2
ELSE 3
END AS grouping_col,
CONCAT(MIN(DATE(date)), ' - ', MAX(DATE(date))) as join_date,
COUNT(id) as num_visits
FROM visits
WHERE user_id = 43
GROUP BY grouping_col
)t
LEFT JOIN
( SELECT
CASE
WHEN DATE(date) >= '2014-06-01' AND DATE(date) <= '2014-07-10' THEN 1
WHEN DATE(date) >= '2014-08-05' AND DATE(date) <= '2014-08-07' THEN 2
ELSE 3
END AS grouping_col,
CONCAT(MIN(DATE(date)), ' - ', MAX(DATE(date))) as join_date,
user_id,
COUNT(id) as num_points,
SUM(points) as total_points
FROM points
WHERE user_id = 43
AND status = 0
GROUP BY grouping_col
)t1 ON t1.grouping_col = t.grouping_col
WHERE t.grouping_col IN(1, 2) OR t1.grouping_col IN(1, 2)
NOTE:
you can add as many timeframes to this by just adding more rows to the CASE statement...
SEE DEMO:
FIDDLE
A better option may be to actually use a stored procedure and set a beginning date param and an end date param so you can pick any dates you want
The only approach which comes to mind is to do two distinct query and use UNION ALL
SELECT *
FROM
(SELECT
count(v.id) AS visit_count,
count(p.id) AS point_count,
sum(p.points) AS points
FROM visits v1
JOIN points p1
ON v1.user_id = p1.user_id
WHERE v1.user_id = 43
AND DATE(v1.date) BETWEEN '2014-08-01' AND '2014-08-03'
AND p1.status = 0
UNION ALL
SELECT
count(v.id) AS visit_count,
count(p.id) AS point_count,
sum(p.points) AS points
FROM visits v2
JOIN points p2
ON v2.user_id = p2.user_id
WHERE v2.user_id = 43
AND DATE(v2.date) BETWEEN '2014-08-04' AND '2014-08-07'
AND p2.status = 0
) temp
You could try:
select count(id)
from visits
join points on points.user_id = visits.user_id
where date between 2014-08-04 00:00:00 and 2014-08-07 00:00:00
and visits.user_id = 43

MySQL get the sum of all rows without retrieving all of them

This may be a little confusing but please bear with me. Here's the thing:
I have a database that contains ~1000 records, as the following table illustrates:
+------+----------+----------+
| id | date | amount |
+------+----------+----------+
| 0001 | 14/01/15 | 100 |
+------+----------+----------+
| 0002 | 14/02/04 | 358 |
+------+----------+----------+
| 0003 | 14/05/08 | 1125 |
+------+----------+----------+
What I want to do is this:
Retrieve all the records beginning at 2014 and until yesterday:
WHERE `date` > '14-01-01' AND `date` < CURDATE()
But also get the sum of amount up to the current date, this is:
WHERE `date` < CURDATE()
I've already got this working by just selecting all the records based on the second condition, getting the sum, and then excluding those which don't match the first condition. Something like this:
SELECT `id`, `date`, `amount` FROM `table`
WHERE `date` < CURDATE()
And then:
$rows = fetchAll($PDOStatement);
foreach($rows as $row) {
$sum += $row->amount;
if (
strtotime($row->date) > strtotime('14-01-01') &&
strtotime($row->date) < strtotime(date('Y-m-d'))
) {
$valid_rows[] = $row;
}
}
unset $rows;
Is there a way to achieve this in a single query, efficiently? Would a transaction be more efficient than sorting out the records in PHP? This has to be SQL-standard compliant (I'll be doing this on MySQL and SQLite).
Update:
It doesn't matter if the result ends up being something like this:
+------+----------+----------+-----+
| id | date | amount | sum |
+------+----------+----------+-----+
| 0001 | 14/01/15 | 100 | 458 |
+------+----------+----------+-----+
| 0002 | 14/02/04 | 358 | 458 |
+------+----------+----------+-----+
| 0003 | 14/05/08 | 1125 | 458 |
+------+----------+----------+-----+
The worst case would be when the resulting set ends up being the same as the set that gives the sum (in this case appending the sum would be irrelevant and would cause an overhead), but for any other regular cases the bandwith save would be huge.
You can create a special record with your sum and add it at the end of your first query
SELECT * FROM `table` WHERE `date` > '14-01-01' AND `date` < CURDATE()
UNION
SELECT 9999, CURDATE(), SUM(`amount`) FROM `table` WHERE `date` < CURDATE()
Then you will have all your desired record and the record with id 9999 or whatever is your sum
This could be achieved by correlated subquery, something like below:
SELECT *, (SELECT SUM(amount) FROM t WHERE t.date < t1.date) AS PrevAmount
FROM t AS t1
WHERE `date` > '14-01-01' AND `date` < CURDATE()
However it is very unefficient if the number of records is large.
It's hackish, but:
> select * from foo;
+------+------+
| id | val |
+------+------+
| 1 | 1 |
| 2 | 2 |
| 3 | 3 |
| 4 | 4 |
| 5 | 5 |
+------+------+
5 rows in set (0.02 sec)
> select * from foo
left join (
select sum(val)
from foo
where id < 3
) AS bar ON 1=1
where id < 4;
+------+------+----------+
| id | val | sum(val) |
+------+------+----------+
| 1 | 1 | 3 |
| 2 | 2 | 3 |
| 3 | 3 | 3 |
+------+------+----------+
Basically, do your summing in a joined subquery. That'll attach the sum result to every row in the outer table's results. You'll waste a bit of bandwidth sending that duplicated value out with every row, but it does get you the results in a "single" query.
EDIT:
You can get the SUM using a LEFT OUTER JOIN.
SELECT t1.`id`, t1.`date`, t2.sum_amount
FROM
`table` t1
LEFT OUTER JOIN
(
SELECT SUM(`amount`) sum_amount
FROM `table`
WHERE `date` < CURDATE()
) t2
ON 1 = 1
WHERE t1.`date` > STR_TO_DATE('01,1,2014','%d,%m,%Y') AND t1.`date` < CURDATE();
This will do what you want it to do...optimizing the subquery is the real challenge:
SELECT id,date,amount,(SELECT SUM(amount) FROM table) AS total_amount
FROM table
WHERE date BETWEEN '14-01-01' AND DATE_ADD(CURDATE(), INTERVAL -1 DAY)

Categories