Mixed sorting of dates in MySQL - php

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 |
+----+-------------+

Related

Show data of today and one week earlier in one row. Is it possible in mysql?

Table example
id | name | value | date
--------------------------------------------
1 | abc | 20 | 2018-01-26
1 | abc | 24 | 2018-01-27
1 | abc | 25 | 2018-01-28
1 | abc | 30 | 2018-01-29
I know how to fetch data from 28th Jan or today. But I need some way to show values of two dates in two columns. Is it possible in one mysql query?
Like this
name | value_today | value_pre
---------------------------------------
abc | 30 | 25
You can achieve this using the case. This query returns the today's value and previous date value:
SELECT
id, name,
Sum(Case When date = CURDATE()
Then value Else 0 End) TodaySum,
Sum(Case When (date = CURDATE()-1)
Then value Else 0 End) PreviousSum
FROM tbl1
group by id, name
Here's a hypothetical query that would do it.
SELECT t1.value AS value_today,(SELECT t2.value FROM table AS t2 WHERE t2.date=2018-01-29 ) AS value_pre
FROM table AS t1 WHERE t1.date=2018-01-28

SQL count Query retrieving 0

i want to list the amount of views per object_id, between the current date and the date year ago.
So, i have this Query:
SELECT count(*) FROM event_logs WHERE object_id=252 AND object_type='product' AND event_type='view' AND event_date BETWEEN 2014-02-14 AND 2015-02-14 GROUP BY month(event_date)
And the following table:
event_id | user_id | objectd_id | object_type | event_type | event_date
1 | 1 | 252 | product | view | 2014-02-25 00:00:00
2 | 1 | 252 | product | view | 2015-02-12 19:36:05
3 | 1 | 252 | product | view | 2015-01-05 19:36:05
The problem is when i execute the query, show an amount of results of 0 (zero),
Could have i been doing wrong?
Please help and thank you all for your attention!
One problem is the event_date constants:
SELECT count(*)
FROM event_logs
WHERE object_id = 252 AND
object_type = 'product' AND
event_type = 'view' AND
event_date BETWEEN 2014-02-14 AND 2015-02-14
------------------------^ ---------^---^----------^ missing quotes
GROUP BY month(event_date);
Date constants should be in single quotes. Otherwise, these are treated as arithmetic -- 2014 - 2 - 14 = 1998, which is not really a valid date.
Also, if you want the total value, it is unclear why you are grouping by the month. You can use CURRENT_DATE and date arithmetic and not have to hardcode the dates:
SELECT count(*)
FROM event_logs
WHERE object_id = 252 AND
object_type = 'product' AND
event_type = 'view' AND
event_date <= CURRENT_DATE and
event_date > date_sub(CURRENT_DATE, interval -1 year);
Depending on the exact logic, you might want to change the <= to < or whatever -- depending on whether you want to include the end dates. Note: if event_date has a time component, then you might want to use date(event_date) to strip it off.
Change to this
BETWEEN '2014-02-14 00:00:00' AND '2015-02-14 23:59:59'

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)

Mysql Query SUM adding each to eachother result?

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;

Obtain the next times (in VARCHAR) from the DB in PHP, according to time now

i have this mysql table with the timetables the train with php,
Type (INT) | time_start | time_stop
1 | 09:31:00 | 09:34:00
1 | 09:43:00 | 09:47:00
1 | 09:55:00 | 09:58:00
1 | 10:07:00 | 10:10:00
1 | 10:33:00 | 10:36:00
1 | 10:45:00 | 10:47:00
1 | 10:57:00 | 11:00:00
1 | 11:12:00 | 11:15:00
1 | 11:35:00 | 11:38:00
(and it goes on..)
- "type" is the timetable type, cus it changes in the winter, summer, etc.
- "type" is INT, and "time_start" and "time_stop" are VARCHAR(8)
I would like to know the most efective way to get the 6 next "train times", acording to the time now.
Imagine, it's now 09:33:10, what I want to obtain is this ones:
1 | 09:43:00 | 09:47:00
1 | 09:55:00 | 09:58:00
1 | 10:07:00 | 10:10:00
1 | 10:33:00 | 10:36:00
1 | 10:45:00 | 10:47:00
1 | 10:57:00 | 11:00:00
If theres any change change i should do in the mysql table, i'm also open to your ideias.
Thanks in advance ;)
Miguel.
You simply could change the VARCHAR type to TIME type, and do a SQL request like
SELECT * FROM <yourtable> WHERE time_start > NOW()
The basic approach is this:
select *
from timetables tt
where tt.time_start > current time
order by tt.time_start
limit 6
There are two challenges with this. The first is midnight. Presumably, if the time is late in the evening, then you want trains in the early morning as well. The second is converting the times to the right format.
select *
from timetable tt
order by (t.time_start > time(now()) desc,
tt.time_start
limit 6
The trick is to move the where condition into the ordering clause. In effect, this starts the ordering at the current time and continues it after midnight. This allows you to select the six with wrapping.
The time() function should be doing the necessary conversion for the comparison.
Just convert your searched time to an int:
$char_time = '09:33:10';
$int_time = (int) str_replace(':','', $char_time);
and then construct your sql like this:
$sql = "SELECT *, CAST(REPLACE(time_start, ',', '') AS INT) as mytime
FROM yourtable WHERE mytime > $int_time
ORDER BY mytime LIMIT 6";
Basically what we do above is just casting your varchar time field to an int type, and using that for comparing, this is a good solution if you can't change your database fields to be of TIME type.
The query will be
SELECT * FROM table_name
WHERE time_start >=time(now())
LIMIT 6

Categories