I have the following table:
Mails
id (int 11 auto increment)
user_id (int 11)
subject (varchar 255)
body (text)
deleted (datetime)
By default the deleted column holds the value 0000-00-00 00:00:00 When something gets deleted the deleted column will be set to the delete date. e.g.: 2017-08-14 09:37:13
Now I want to query this table and get either the user_id record where deleted = 0000-00-00 00:00:00 or the most recent deleted value if there is no user record where deleted = 0000-00-00 00:00:00
You can sort a specific value on top with a case:
select user_id
from YourTable
order by
case
when deleted = '0000-00-00 00:00:00' then '9999-12-31 23:59:59'
else deleted
end desc
limit 1
Where '9999-12-31 23:59:59' is the maximum value of a datetime.
For multiple users, you could look up the maximum with a join:
select *
from Mails m1
join (
select user_id
, max(
case
when deleted = '0000-00-00 00:00:00' then '9999-12-31 23:59:59'
else deleted
end) max_deleted
from Mails
group by
user_ud
) m2
on m1.user_id = m2.user_id
and case
when m1.deleted = '0000-00-00 00:00:00' then '9999-12-31 23:59:59'
else m1.deleted
end = m2.max_deleted
Making a slight amendment to Andomar's answer to remove the limit you could try something like:
SELECT user_id
FROM YourTable
WHERE
case
when deleted = '0000-00-00 00:00:00' then 'true'
else deleted then 'true'
end desc = 'true'
As Case statements run sequentially this would try and return true for the filter in the WHERE clause if the deleted date is '0000-00-00 00:00:00' and if this holds false it will move to the next case which always holds try and passes the filter.
Not the prettiest though.
Related
I am implementing the cashback functionality with expiry feature. I am trying to redeem the partial amount based on early expiry date. I've already ordered the rows based on expiry date with the following mysql command.
SELECT * FROM `cashback` WHERE `user_id` = 1 and `used`= 'NO' AND IF(CONCAT(`point_expiry`) !='0000-00-00 00:00:00', `point_expiry` >= NOW(), NOW()) ORDER BY (case when CONCAT(`point_expiry`) = '0000-00-00 00:00:00' then 9999
else 1
end) ASC, `point_expiry` ASC
And the output for the following will be
id
amount
point_expiry
used
user_id
3
30
2023-02-24 00:00:00
NO
1
1
20
2023-02-25 00:00:00
NO
1
2
50
0000-00-00 00:00:00
NO
1
Now i want to redeem the value based on the above query result
Let say i want to redeem 35$ for the above result and the expected result will be
id
amount
point_expiry
used
used_amount
3
30
2023-02-24 00:00:00
NO
30
1
20
2023-02-25 00:00:00
NO
5
Here used_amount column represent the specific redeem value($35) redeemed based on amount column
Much appreciate your help!
This uses SUM(amount) OVER(ORDER BY ...) to calculate a running total and compares it to the balance -
SELECT *
FROM (
SELECT
`id`,
`amount`,
`point_expiry`,
`used`,
`amount` - GREATEST(SUM(`amount`) OVER (ORDER BY IF(`point_expiry` = '0000-00-00 00:00:00', 1, 0) ASC, `point_expiry` ASC, id ASC) - /* Here is your amount --> */ 35, 0) AS `used_amount`
FROM `cashback`
WHERE (`point_expiry` >= NOW() OR `point_expiry` = '0000-00-00 00:00:00')
AND `used` = 'NO'
AND `user_id` = 1
) t
WHERE `used_amount` > 0;
Consider I have the following table and current date is 2022-09-01:
Requirement: I want to get all users that have no event_name like cbt care in the past 14 days and onwards into the future.
I have this query:
SELECT * FROM test_table
WHERE event_name LIKE "%cbt care%"
AND start_date <= DATE_SUB(NOW(), INTERVAL 14 DAY)
;
Which returns:
The issue is that user_id = x does have a cbt care event in 2022-09-10 which is 9 days ahead of current date (2022-09-01).
How to return only users satisfy requirement posted above?
SELECT user_id,
COUNT(CASE WHEN event_name LIKE '%cbt care%' AND start_date
> CURDATE() - INTERVAL 14 day THEN 1 END) AS count_recent
FROM test_table
GROUP BY user_id
HAVING count_recent = 0;
https://www.db-fiddle.com/f/64j7L1VZsVdLYqmcQ2NrvV/0
The CASE expression returns 1 for each row with the conditions you described (a specific event name and a start date after the date 14 days ago, which includes all of the future dates too). For rows that don't match that condition, the CASE returns NULL. There's an implicit ELSE NULL in any CASE expression.
COUNT(<expr>), like many set functions, ignores NULLs. It will only count the occurrences of non-NULL values. So if the count returns 0, then the CASE returned only NULLs, which means there are no recent or future 'cbt care' events for that user.
select id
,user_id
,event_name
,start_date
from (
select *
,count(case when abs(datediff(curdate(),start_date)) <= 14 and event_name like "%cbt care%" then 1 end) over (partition by user_id) as cw
from t
) t
where cw = 0
id
user_id
event_name
start_date
0
a
cbt care
2022-06-01 20:00:00
Fiddle
I want to grab all users that have last logged in between two specific days. How can this be done?
Note: the reason why I did sub.updated_at is because:
the login session may last for more than a day
same users may log in on different pc and start a secondary session
I need to get the very last session started by the user and then check when it was last updated.
Example build: http://sqlfiddle.com/#!9/39849/11
So from the example on sqlfiddle I would expect the query to select Bill, because updated_at, for his last record, is between 2019-09-15 00:00:00 and 2019-09-15 23:59:59. One of John's entries was also updated between those dates but his latest record was updated on 2019-09-18 12:00:00 hence why John should not be selected.
This is a possible solution, but I have concerns about performance, indexing update_at should have a positive effect, but what if we have 1mil+ users and an even greater number of user logins:
SELECT *
FROM
(
SELECT *
FROM (
SELECT *
FROM `user_logins`
WHERE `updated_at` >= '2019-09-15 00:00:00'
ORDER BY `user_logins`.`id` DESC
) as sub
GROUP BY `sub`.user_id
) as sub
WHERE `sub`.updated_at BETWEEN '2019-09-15 00:00:00' and '2019-09-15 23:59:59'
SELECT *
FROM (
SELECT *
FROM `user_logins`
WHERE `updated_at` BETWEEN '2019-09-15 00:00:00' and '2019-09-15 23:59:59'
ORDER BY `user_logins`.`id` DESC
) as sub
WHERE NOT EXISTS (
SELECT 1
FROM user_logins
WHERE user_id = sub.user_id
and updated_at > '2019-09-15 23:59:59'
);
You query may be simplified to -
SELECT U.*, UL.*
FROM users U
JOIN user_logins UL ON U.id = UL.user_id
JOIN (SELECT user_id, MAX(updated_at) updated_at
FROM user_logins
GROUP BY user_id) UL2 ON UL2.updated_at = UL.updated_at
AND UL2.user_id = UL.user_id
WHERE DATE(UL2.updated_at) BETWEEN DATE('2019-09-15 00:00:00') AND DATE('2019-09-15 23:59:59')
ORDER BY UL.id DESC
Here is the fiddle.
I got a strange condition in Mysql coalesce function:
select start_date, end_date, COALESCE(end_date, start_date)
from log where log_id = 8999134
returns: 2011-02-09 23:00:38, NULL, NULL
Anyone has any idea why coalsce is not working as it should ?
Edit:
After some research I decided to try the query in MYSQL workbench and saw that the tool I was using to access the database was incorrectly returning null when the field had end_date = 0000-00-00 00:00:00.
Yet the problem got even more strange. The result above is already a debug. The first query was:
select id, start_date, COALESCE(end_date, start_date)
from log where end_date is null
from where I debugged:
select id, start_date, end_date, end_date is null
from log where end_date is null
and the result was: 2011-02-09 23:00:38, 0000-00-00 00:00:00, 0
This is result's first line, but got equal results to the other lines. Why the end_date is NULL for where and not to coalesce ?
the datatype for both fields is datetime
This would occur if the value NULL were really a string 'NULL'. This would suggest that fields that should be dates are really being stored as strings.
Check the data types. If they are varchar() or char(), then this is probably the problem. Then, fix the problem by fixing the data structure. Date/times should be stored using native database formats.
EDIT:
A date value of 0 is not the same as NULL.
if you have your dates stored like that 0000-00-00 00:00:00 then Coalesce will not work because its not NULL . try this with CASE :
select id, start_date, CASE WHEN end_date = '0000-00-00 00:00:00'
THEN start_date
ELSE end_date END as coalesced_date
from log where log_id = 8999134
Or this:
SELECT id, start_date,
CASE WHEN end_date ='0000-00-00 00:00:00' AND start_date != '0000-00-00 00:00:00'
THEN start_date
WHEN start_date ='0000-00-00 00:00:00' AND end_date != '0000-00-00 00:00:00'
THEN end_date
WHEN start_date ='0000-00-00 00:00:00' AND end_date = '0000-00-00 00:00:00'
THEN 'They are nulled'
ELSE end_date END as coalesced_date
FROM log
WHERE log_id = 8999134
EDIT2:
you dont need to use NULL in your where clause since you dont have null values , you have '0000-00-00 00:00:00' and this is not null.
so instead of
where end_date is null
use
where end_date != '0000-00-00 00:00:00'
I have a union query to get a Max date record from two tables
SELECT
id,
MAX(mx) AS max_date,
`assigned_user_id`
FROM (SELECT
id,
MAX(date_due) AS mx,
`assigned_user_id`
FROM tasks
WHERE `deleted` = 0
AND `parent_id` = 'aa6f0330-7a17-572f-94b5-4a5db1a1201f'
AND `parent_type` = 'Accounts'
AND date_due >= CURDATE() UNION SELECT
id,
MAX(date_start) AS mx,
`assigned_user_id`
FROM calls
WHERE `deleted` = 0
AND `account_id` = 'aa6f0330-7a17-572f-94b5-4a5db1a1201f'
AND date_start >= CURDATE()) s
this would look into both calls and Tasks and get me the Futuristic Call or a Task, but when i run this and i have a Futuristic Call and task is older, i get NULL in id and assigned_user_id columns. How do i fetch the whole info for the MAX date record.
When i Run individual Queries, i get null for first one while a record in the second one.
Perhaps I'm misreading the problem, or I'm missing something - in which case, write me a comment to let me know, and I can delete this post altogether.
But, the way I understand what you've written, you want to:
Get the single record in tasks with the given parent_id and parent_type and where date_due >= CURDATE() which has the latest date_due value.
Get that same type of record from the calls table (using date_start instead of date_due).
Of those two records, find the one record with the later date value. Select the id, the date (as max_date), and the assigned_user_id from that record.
Your query (and sub-queries) could just order by descending date and fetch only the first record.
I'm thinking something like this...
SELECT id,
mx AS max_date,
`assigned_user_id`
FROM (
SELECT id,
date_due as mx,
`assigned_user_id`
FROM tasks
WHERE `deleted` = 0
AND `parent_id` = 'aa6f0330-7a17-572f-94b5-4a5db1a1201f'
AND `parent_type` = 'Accounts'
AND date_due >= CURDATE()
ORDER BY date_due DESC
LIMIT 1
UNION
SELECT id,
date_start as mx,
`assigned_user_id`
FROM calls
WHERE `deleted` = 0
AND `account_id` = 'aa6f0330-7a17-572f-94b5-4a5db1a1201f'
AND date_start >= CURDATE()
ORDER BY date_start DESC
LIMIT 1
)
ORDER BY max_date DESC
LIMIT 1
Use UNION ALL. To allow duplicate values, use the ALL keyword with UNION.
Select Max(max_date) from
(SELECT
id,
MAX(mx) AS max_date,
assigned_user_id
FROM
(SELECT
id,
MAX(date_due) AS mx,
assigned_user_id
FROM tasks
WHERE deleted = 0
AND parent_id = 'aa6f0330-7a17-572f-94b5-4a5db1a1201f'
AND parent_type = 'Accounts'
AND date_due >= CURDATE() group by id UNION SELECT
id,
MAX(date_start) AS mx,
assigned_user_id
FROM calls
WHERE deleted = 0
AND account_id = 'aa6f0330-7a17-572f-94b5-4a5db1a1201f'
AND date_start >= CURDATE()) s group by id)
as temp