How to add interval to subscribed date in join table - mysql

I have 2 tables transaction,plan so I am getting subscribed users data by joining these two table and at the same time I want to add plan_validity column as interval with subscription date to check whether validity is completed or not ,but while adding interval it is showing error unexpected date_interval near '))' so how to add interval to subscription date of user.
transaction table
id user id plan_id subscription_date
1 1 1 2019-06-08
2 2 3 2019-07-05
plan table
id plan_validity
1 3 month
2 6 month
3 9 month
mysql query:
select tr.*,t.subscription_date,DATE_ADD(t.subscription_date, INTERVAL p.plan_validity)
from transaction t inner join plan p on t.plan_id=plan.id where t.user_id=1

Try with a CASE expression that checks the type of interval that you want to add:
select t.*,
case
when p.plan_validity like '%month'
then date_add(t.subscription_date, interval p.plan_validity month)
when p.plan_validity like '%year'
then date_add(t.subscription_date, interval p.plan_validity year)
end result
from transaction t inner join plan p
on t.plan_id = p.id
where t.user_id = 1
See the demo.
Results:
| id | user_id | plan_id | subscription_date | result |
| --- | ------- | ------- | ------------------- | -----------|
| 1 | 1 | 1 | 2019-06-08 | 2019-09-08 |

Related

Why is table1.Date = table2.Date - 1 different from table1.date = table2.date - Interval 1 day in my sql

Problem
The table contains information about the temperature on a certain day.
id is the primary key for this table.
Columns (id, record date, temperature)
Write an SQL query to find all dates' Id with higher temperatures compared to its previous dates (yesterday).
Return the result table in any order.
My Query :
SELECT w1.id AS Id
FROM weather AS w
INNER JOIN weather AS w1 ON w.recorddate = w1.recorddate - 1
WHERE w1.temperature > w.temperature;
Alternate Query:
SELECT w1.id AS Id
FROM weather AS w
INNER JOIN weather AS w1 ON w.recorddate = w1.recorddate - Interval 1 day
WHERE w1.temperature > w.temperature;
My Query is not giving same results as the alternate query
The 2 expressions return the new date in different formats. In your queries there is an implicit conversion and the result should be the same, as in the following example.
If this is causing errors you would do better to stick to recorddate - Interval 1 day. The following query shows the difference between the 2 formats.
select
id,
temperature,
recorddate,
recorddate - 1,
recorddate - Interval 1 day
from weather;
id | temperature | recorddate | recorddate - 1 | recorddate - Interval 1 day
-: | ----------: | :--------- | -------------: | :--------------------------
1 | 15 | 2022-04-27 | 20220426 | 2022-04-26
2 | 18 | 2022-04-28 | 20220427 | 2022-04-27
db<>fiddle here

MYSQL Select row when first part of condition is valid (with a group by)

I really don't know how to find an answer for my question, so I'm asking you.
Here is the table I have :
+----+------------+------------+-------------+
| id | start_date | end_date | id_person |
+----+------------+------------+-------------+
| 1 | 2017-10-01 | 2017-12-01 | 1 |
| 2 | 2017-07-01 | 2017-09-01 | 1 |
| 3 | 2016-01-01 | 2016-02-01 | 1 |
| 4 | 2016-05-01 | 2016-06-01 | 2 |
| 5 | 2016-01-01 | 2016-02-01 | 2 |
+----+------------+------------+-------------+
And here is the query I tried to use :
SELECT * FROM table
WHERE ((start_date < NOW() AND end_date > NOW())
OR start_date > NOW()
OR end_date < NOW())
GROUP BY `id_person`
The result I was expecting was this one :
+----+------------+------------+-------------+
| id | start_date | end_date | id_person |
+----+------------+------------+-------------+
| 2 | 2017-07-01 | 2017-09-01 | 1 | // matches first condition
| 4 | 2016-05-01 | 2016-06-01 | 2 | // matches 3rd condition and has the most recent start_date
+----+------------+------------+-------------+
If you didn't get what I did wrong yet, I'm going to tell you.
Here, I was trying to show a single row per person but I wanted this row to match the first condition it finds and not the others, I don't want the row to just be ordered by start_date. It is like a custom order where I want the first row for each person.
The problem is that this query doesn't work since the GROUP BY statement doesn't apply conditions first. (even if it did, I'm not sure the condition would only select one row)
I really don't know how I can achieve that and I don't even know if it is possible, I hope someone can lead me towards any solution.
Thanks for reading this, I'll answer as fast as I can to give you more informations.
Here's one idea...
SELECT m.*
FROM my_table m
JOIN
( SELECT x.id_person
, MAX(x.start_date) start_date
FROM my_table x
JOIN
( SELECT id_person
, MIN(CASE WHEN NOW() BETWEEN start_date AND end_date THEN 'A' WHEN start_date > NOW() THEN 'B' WHEN end_date < NOW() THEN 'C' END) rule
FROM my_table
GROUP
BY id_person
) y
ON y.id_person = x.id_person
AND y.rule = CASE WHEN NOW() BETWEEN start_date AND end_date THEN 'A' WHEN start_date > NOW() THEN 'B' WHEN end_date < NOW() THEN 'C' END
GROUP
BY id_person
) n
ON n.id_person = m.id_person
AND n.start_date = m.start_date;
+----+------------+------------+-----------+
| id | start_date | end_date | id_person |
+----+------------+------------+-----------+
| 2 | 2017-07-01 | 2017-09-01 | 1 |
| 4 | 2016-05-01 | 2016-06-01 | 2 |
+----+------------+------------+-----------+
If are happy to write the rules directly in sql rather than as where conditions you can ask the database for what yuo want more directly.
This means taking a step back to see what the rules you want are. It looks like you want to prioritise the entries by closest date, showing first current, then future, then historical. It also looks like end_date >= start_date, which means you only need to look at end_date to find what you are looking for.
Mysql can answer the question abusing it's group by functionality (until recent versions).
SELECT t.* FROM
(
SELECT t.*
FROM table t
ORDER BY SIGN(t.end_date - NOW()),ABS(t.end_date-NOW())
)
GROUP BY t.id_person
A standard sql method that will also play better with indexes would be to look for end dates before and after today separately.
SELECT t.*
FROM table t
JOIN (
SELECT t.person_id
,COALESCE(first_not_ended.end_date,t.last_ended.end_date) AS end_date
FROM table t
LEFT JOIN (
SELECT t.*,MIN(end_date) AS end_date
FROM table t
WHERE t.end_date > NOW()
GROUP by t.person_id
) first_not_ended
ON t.person_id=first_not_ended.person_id
AND t.end_date=first_not_ended.end_ate
LEFT JOIN (
SELECT t.person_id,MAX(end_date) AS end_date
FROM table t
WHERE t.end_date < NOW()
GROUP by t.person_id
) last_ended
ON t.person_id=last_ended.person_id
AND t.end_date=last_ended.end_date
) closest
ON t.person_id=closest.person_id
AND t.end_date=closest.end_date

Finding total active hours by calculating difference between TimeDate records

I have a table to register users logs every one minute and other activities using DateTime for each user_id
This is a sample data of my table
id | user_id | log_datetime
------------------------------------------
1 | 1 | 2016-09-25 13:01:08
2 | 1 | 2016-09-25 13:04:08
3 | 1 | 2016-09-25 13:07:08
4 | 1 | 2016-09-25 13:10:08
5 | 2 | 2016-09-25 13:11:08
6 | 1 | 2016-09-25 13:13:08
7 | 2 | 2016-09-25 13:13:09
8 | 2 | 2016-09-25 13:14:10
I would like to calculate the total active time on the system
UPDATE: Expected Output
For Example user_id 1 his total available time should be 00:12:00
Since his hours and seconds are same so I'll just subtract last log from previous then previous from next previous and so on then I'll sum all subtracted values
this a simple for
Simply I want to loop through the data from last record to first record with in my range
this is a simple formula I hope that make my question clear
SUM((T< n > - T< n-1 >) + (T< n-1 > - T< n-2 >) ... + (T< n-x > - T< n-first >))
Since user_id 1 his hours and seconds are the same then I'll calculate the minutes only.
(13-10)+(10-7)+(7-4)+(4-1) = 12
user_id | total_hours
---------------------------------
1 | 00:12:00
2 | 00:03:02
I did this code
SET #start_date = '2016-09-25';
SET #start_time = '13:00:00';
SET #end_date = '2016-09-25';
SET #end_time = '13:15:00';
SELECT
`ul1`.`user_id`, SEC_TO_TIME(SUM(TIME_TO_SEC(`dl1`.`log_datetime`))) AS total_hours
FROM
`users_logs` AS `ul1`
JOIN `users_logs` AS `ul2`
ON `ul1`.`id` = `ul2`.`id`
WHERE
`ul1`.`log_datetime` >= CONCAT(#start_date, ' ', #start_time)
AND
`ul2`.`log_datetime` <= CONCAT(#end_date, ' ', #end_time)
GROUP BY `ul1`.`user_id`
But this code Sum all Time not getting the difference. This is the output of the code
user_id | total_hours
---------------------------------
1 | 65:35:40
2 | 39:38:25
How can I calculate the Sum of all difference datetime, then I want to display his active hours every 12 hours (00:00:00 - 11:59:59) and (12:00:00 - 23:59:59) with in selected DateTime Period at the beginning of the code
So the output would look like this (just an dummy example not from given data)
user_id | total_hours | 00_12_am | 12_00_pm |
-------------------------------------------------------
1 | 10:10:40 | 02:05:20 | 08:05:20 |
2 | 04:10:20 | 01:05:10 | 03:05:30 |
Thank you
So you log every minute and if a user is available there is a log entry.
Then count the logs per user, so you have the number of total minutes.
select user_id, count(*) as total_minutes
from user_logs
group by user_id;
If you want them displayed as time use sec_to_time:
select user_id, sec_to_time(count(*) * 60) as total_hours
from user_logs
group by user_id;
As to conditional aggregation:
select
user_id,
count(*) as total_minutes,
count(case when hour(log_datetime) < 12 then 1 end) as total_minutes_am,
count(case when hour(log_datetime) >= 12 then 1 end) as total_minutes_pm
from user_logs
group by user_id;
UPDATE: In order to count each minute just once count distinct minutes, i.e. DATE_FORMAT(log_datetime, '%Y-%m-%d %H:%i'). This can be done with COUNT(DISTINCT ...) or with a subquery getting distinct values.
The complete query:
select
user_id,
count(*) as total_minutes,
count(case when log_hour < 12 then 1 end) as total_minutes_am,
count(case when log_hour >= 12 then 1 end) as total_minutes_pm
from
(
select distinct
user_id,
date_format(log_datetime, '%y-%m-%d %h:%i') as log_moment,
hour(log_datetime) as log_hour
from.user_logs
) log
group by user_id;

MySQL set OFFSET depending on subquery

I'm trying to delete all records older than one week while keeping at least one for each user.
Example:
| ID | user | date | other columns...
| 1 | 1234 | -2 days | ...
| 2 | 1234 | -3 days | ...
| 3 | 1234 | -8 days | ...
| 4 | 5678 | -9 days | ...
| 5 | 5678 | -10 days | ...
Should become
| ID | user | date | other columns...
| 1 | 1234 | -2 days | ...
| 2 | 1234 | -3 days | ...
| 4 | 5678 | -9 days | ... // Keeping the most recent record for this user
So far I've made this, but it uses CASE to set OFFSET, so it doesn't work:
DELETE FROM transactions WHERE ID < (
SELECT ID FROM (
SELECT ID FROM transactions t WHERE
DATE(date) <= DATE_SUB(CURDATE(), INTERVAL 7 DAY) AND
user = transactions.user
ORDER BY ID DESC
LIMIT 1 OFFSET CASE WHEN EXISTS (
SELECT ID FROM transactions x WHERE
DATE(date) > DATE_SUB(CURDATE(), INTERVAL 7 DAY) AND
user = transactions.user
) THEN 0 ELSE 1 END
)
)
So the question is: how to fix the code above?
P.S.: I'm relatively new to anything except most basic operations in SQL
By grouping the transactions by user, you can determine those that you wish to preserve:
SELECT user, MAX(date) date
FROM transactions
GROUP BY user
You can then make an outer join between these results and your original table using the multiple-table DELETE syntax in order to delete only the desired records:
DELETE transactions
FROM transactions NATURAL LEFT JOIN (
SELECT user, MAX(date) date
FROM transactions
GROUP BY user
) t
WHERE date < CURRENT_DATE - INTERVAL 7 DAY
AND t.date IS NULL
try
DELETE FROM transactions tt WHERE tt.id NOT IN (
SELECT ID FROM transactions t WHERE
DATE(t.date) <= DATE_SUB(CURDATE(), INTERVAL 7 DAY) AND
t.user = tt.transactions.user
ORDER BY t.ID DESC limit 1
)

MySQL how to write SQL to find excessive transactions in 15 minute windows?

MySQL
Lets say there is a credit card processing company. Every time a credit card is used a row gets inserted into a table.
create table tran(
id int,
tran_dt datetime,
card_id int,
merchant_id int,
amount int
);
One wants to know what cards have been used 3+ times in any 15 minute window at the same merchant.
My attempt:
select card_id, date(tran_dt), hour(tran_dt), merchant_id, count(*)
from tran
group by card_id, date(tran_dt), hour(tran_dt), merchant_id
having count(*)>=3
The first problem is that would give excessive transactions per hour, not per a 15 minute window. The second problem is that would not catch transactions that cross the hour mark ie at 1:59pm and 2:01pm.
To make this simpler, it would ok to split up the hour into 5 minute increments. So we would not have to check 1:00-1:15pm, 1:01-1:16pm, etc. It would be ok to check 1:00-1:15pm, 1:05-1:20pm, etc., if that is easier.
Any ideas how to fix the sql? I have a feeling maybe I need sql window functions, that are not yet available in MySQL. Or write a stored procedure that can look at each 15 block.
http://sqlfiddle.com/#!9/f2d74/1
You can convert the date/time to seconds and do arithmetic on the seconds to get the value within a 15 minute clock interval:
select card_id, min(date(tran_dt)) as first_charge_time, merchant_id, count(*)
from tran
group by card_id, floor(to_seconds(tran_dt) / (60 * 15)), merchant_id
having count(*) >= 3;
The above uses to_seconds(). In earlier versions of MySQL, you can use unix_timestamp().
Getting any 15 minute interval is more challenging. You can express the query as:
select t1.*, count(*) as numTransactions
from tran t1 join
tran t2
on t1.merchant_id = t2.merchanti_d and
t1.card_id = t2.card_id and
t2.tran_dt >= t1.tran_dt and
t2.tran_dt < t1.tran_dt + interval 15 minute
group by t1.id
having numTransactions >= 3;
Performance of this query might be problematic. An index on trans(card_id, merchant_id, tran_dt) should help a lot.
An option might be adding a trigger to the tran table on insert that checks the card_id inserted against the previous 15 minutes. If the count is greater than 3 then insert it into an "audit" table that you can query at your leisure.
-- create table to store audited cards
create table audit_cards(
card_id int,
tran_dt datetime
);
-- create trigger on tran table to catch the cards used 3 times in 15 min
CREATE TRIGGER audit_card AFTER INSERT ON tran
BEGIN
if (select count(new.card_id)
from tran
where tran_dt >= (new.tran_dt - INTERVAL 15 MINUTE)) >= 3
THEN
INSERT new.card_id, new.tran_dt into audit_cards;
END;
Then you can run a report on these cards...
select * from audit_cards;
http://dev.mysql.com/doc/refman/5.6/en/trigger-syntax.html
SELECT t1.card_id,t1.merchant_id,count(distinct t1.id)+1 as ChargeCount
FROM tran t1
INNER JOIN tran t2
on t2.card_id=t1.card_id
and t2.merchant_id=t1.merchant_id
and t2.tran_dt <= DATE_ADD(t1.tran_dt, INTERVAL 15 MINUTE)
and t2.id>t1.id
GROUP BY t1.card_id,t1.merchant_id
HAVING ChargeCount>2;
I was able to group all rows belonging to the same 15 minute window without duplicate records in the result, using in a single query.
Say your table has:
| id | tran_dt | card_id | merchant_id | amount |
|----|---------------------|---------|-------------|--------|
| 13 | 2015-07-23 16:40:00 | 1 | 1 | 10 |
| 14 | 2015-07-23 16:59:00 | 1 | 1 | 10 | <-- these should
| 15 | 2015-07-23 17:00:00 | 1 | 1 | 10 | <-- be identified
| 16 | 2015-07-23 17:01:00 | 1 | 1 | 10 | <-- in the
| 17 | 2015-07-23 17:02:00 | 1 | 1 | 10 | <-- first group
| 18 | 2015-07-23 17:03:00 | 2 | 2 | 10 |
...
| 50 | 2015-07-24 17:58:00 | 1 | 1 | 10 | <-- and these
| 51 | 2015-07-24 17:59:00 | 1 | 1 | 10 | <-- in the
| 52 | 2015-07-24 18:00:00 | 1 | 1 | 10 | <-- second
The result will be:
| id | card_id | merchant_id | numTrans | amount | dateTimeFirstTrans | dateTimeLastTrans
|----|---------|-------------|----------|--------|---------------------|---------------------
| 14 | 1 | 1 | 4 | 40 | 2015-07-23 16:59:00 | 2015-07-23 17:02:00
| 50 | 1 | 1 | 3 | 30 | 2015-07-24 17:58:00 | 2015-07-24 18:00:00
The query (SQL Fiddle):
select output.* from
(
select
min(subquery.main_id) as id,
subquery.main_card_id as card_id,
subquery.main_merchant_id as merchant_id,
count(subquery.main_id) as numTrans,
sum(subquery.main_amount) as amount,
min(subquery.x_timeFrameStart) as dateTimeFirstTrans,
max(subquery.x_timeFrameStart) as dateTimeLastTrans
from
(
select
main.id as main_id,
main.card_id as main_card_id,
main.merchant_id as main_merchant_id,
main.tran_dt as main_timeFrameStart,
main.amount as main_amount,
main.tran_dt + INTERVAL 15 MINUTE as main_timeFrameEnd,
xList.tran_dt as x_timeFrameStart,
xList.tran_dt + INTERVAL 15 MINUTE as x_timeFrameEnd
from tran as main
inner join tran as xList on /* cross list */
main.card_id = xList.card_id and
main.merchant_id = xList.merchant_id
where
xList.tran_dt between main.tran_dt and main.tran_dt + INTERVAL 15 MINUTE
) as subquery
group by subquery.main_id, subquery.main_card_id, subquery.main_merchant_id, subquery.main_timeFrameStart, subquery.main_timeFrameEnd
having count(subquery.main_id) >= 3
) as output
left join (
select
xList.id as x_id
from tran as main
inner join tran as xList on /* cross list */
main.card_id = xList.card_id and
main.merchant_id = xList.merchant_id and
main.id <> xList.id /* keep only first of the list */
where
xList.tran_dt between main.tran_dt and main.tran_dt + INTERVAL 15 MINUTE
) as exclude on output.id = exclude.x_id
where exclude.x_id is null;
The query is a bit long, and it repeats one subquery just to filter duplicates, so do your testing and tuning to make sure you don't incur in performance problems.