MySQL Update with same table subquery - mysql

I have a table (EMP_ID, START_DATE, END_DATE) that contains a series of date ranges. What I want to do is ensure that they are all contiguous, so the END_DATE should be one less than the next START_DATE for any given EMP_ID.
I have the following query that lets me identify the records that are not contiguous:
SELECT H.EMP_ID,
H.START_DATE,
H.END_DATE,
DATE(
( SELECT MIN(START_DATE)
FROM TSRHierarchy I
WHERE I.START_DATE > H.START_DATE
AND I.EMP_ID = H.EMP_ID
)
) AS NEXT_DATE
FROM TSRHierarchy H
HAVING END_DATE <> DATE_ADD(NEXT_DATE, INTERVAL -1 DAY)
ORDER BY H.EMP_ID, H.START_DATE;
What I can't do is figure out how to turn this into an UPDATE statement? The MySQL documentation states 'Currently, you cannot update a table and select from the same table in a subquery.' which may be part of my problem.
Any suggestions for a work-around?

Try this UPDATE query using JOIN
UPDATE TSRHierarchy t
JOIN
( SELECT H.EMP_ID,
H.START_DATE,
H.END_DATE,
DATE((SELECT MIN(START_DATE)
FROM TSRHierarchy I
WHERE I.START_DATE > H.START_DATE
AND I.EMP_ID = H.EMP_ID
)) AS NEXT_DATE
FROM TSRHierarchy H
HAVING END_DATE <> DATE_ADD(NEXT_DATE, INTERVAL -1 DAY)
) AS t2
ON t.EMP_ID = t2.EMP_ID
SET t.END_DATE = t2.NEXT_DATE

Related

MySQL - Using COALESCE with DATE_ADD and DATE_SUB to get next/previous record

I am trying to query MySQL to select the previous and next record. I need help in using COALESCE and DATE_ADD/DATE_SUB together.
SELECT * from `Historical` where `DeltaH` = 'ALTF' and `Date`=
COALESCE(DATE_SUB('2019-01-21', INTERVAL 1 DAY),
DATE_SUB('2019-01-21',INTERVAL 2 DAY),
DATE_SUB('2019-01-21', INTERVAL 3 DAY));
I cannot use the primary key because rows in the table are/will be deleted. The date column also does not necessarily have fixed dates, what I want to find is the next earlier/later date.
SELECT * from `Historical` where `DeltaH` = 'ALTF' and `Date`=
DATE_SUB('2019-01-21', INTERVAL 3 DAY);
The above query seems to work, however I need to query for INTERVAL 1 DAY, in case the date does not exist move to INTERVAL 2 DAY....
select * from `Historical` where `DeltaH` = 'ALTF' and `Date`=
DATE_SUB('2019-01-21', INTERVAL COALESCE(1,2,3,4,5) DAY);
This one does not work either. I understand that the COALESCE() function returns the first non-null value, however I am not able to get it to work using the above query. I have confirmed that data exists for 2019-01-18 but is not being selected. Can you please advise?
I am OK with using an alternate solution.
You can use a subquery to find the most recent date in the table that is less than 2019-01-21 e.g.
SELECT *
FROM `Historical`
WHERE `DeltaH` = 'ALTF' AND `Date`= (SELECT MAX(`Date`)
FROM `Historical`
WHERE `DeltaH` = 'ALTF' AND `Date` < '2019-01-21')
To find the closest date that is later, we just adapt the query slightly, using MIN and >:
SELECT *
FROM `Historical`
WHERE `DeltaH` = 'ALTF' AND `Date`= (SELECT MIN(`Date`)
FROM `Historical`
WHERE `DeltaH` = 'ALTF' AND `Date` > '2019-01-21')
FWIW, I'd write this differently...
SELECT x.*
FROM Historical
JOIN
( SELECT deltah
, MAX(date) date
FROM Historical
WHERE date < '2019-01-21'
GROUP
BY deltah
) y
ON y.deltah = x.deltah
AND y.date = x.date
WHERE x.deltah = 'ALTF';
This seems like the simplest method:
select h.*
from historical h
where h.DeltaH = 'ALTF' and
h2.Date < '2019-01-21'
order by h.Date DESC
limit 1
For best performance, you want an index on (DeltaH, Date).
If you want both the date before and after:
(select h.*
from historical h
where h.DeltaH = 'ALTF' and
h2.Date < '2019-01-21'
order by h.Date desc
limit 1
) union all
(select h.*
from historical h
where h.DeltaH = 'ALTF' and
h2.Date > '2019-01-21'
order by h.Date asc
limit 1
);
I'm not sure if one or both comparisons should be have =, so you can get results on that date.

How can I optimise this sql query?

Here are 4 tables....
tbl_std_working_hour
tbl_attendance
tbl_holiday
tbl_leave
I want to find out the employees absentee reports by this query....but it takes times when I have applied this for many employees...is there any way to simplify this query?
SELECT date
FROM tbl_std_working_hour
WHERE date NOT IN (SELECT date FROM tbl_attendance WHERE emp_id = '$emp_id')
AND date NOT IN (SELECT date FROM tbl_holiday)
AND date NOT IN (SELECT date FROM tbl_leave WHERE emp_id = '$emp_id')
AND total_hour <> '00:00:00'
AND date >= '$start'
AND date <= '$end'
AND emp_id = '$emp_id'
First, I would rewrite using NOT EXISTS:
SELECT wh.date
FROM tbl_std_working_hour wh
WHERE NOT EXISTS (SELECT 1
FROM tbl_attendance a
WHERE a.date = wh.date AND a.emp_id = wh.emp_id
) AND
NOT EXISTS (SELECT 1
FROM tbl_holiday h
WHERE h.date = wh.date
) AND
NOT EXISTS (SELECT 1
FROM tbl_leave l
WHERE l.emp_id = wh.emp_id and l.date = wh.date
)
WHERE wh.total_hour <> '00:00:00' AND
wh.date >= '$start' AND
wh.date <= '$end' AND
wh.emp_id = '$emp_id';
Then add the following composite (multi-column) indexes:
tbl_std_working_hour(emp_id, date, total_hour)
tbl_attendance(emp_id, date)
tbl_holiday(date) (might already exist if date is the primary key or unique)
tbl_leave(emp_id) (might already exist if emp_id is the primary key or unique)
Note that I changed the subqueries to refer to the emp_id in the outer query. This makes it easier to change the emp_id. In addition, your query should be using parameters for the values in the WHERE clause.
This is also a better way that can work better using UNION ALL
SELECT date
FROM tbl_std_working_hour AS tswh
WHERE NOT EXISTS(
SELECT date FROM tbl_attendance ta WHERE ta.date = tswh.date AND ta.emp_id = tswh.emp_id
UNION ALL
SELECT date FROM tbl_holiday th WHERE th.date = tswh.date
UNION ALL
SELECT date FROM tbl_leave tl WHERE tl.date = tswh.date AND tl.emp_id = tswh.emp_id)
AND total_hour <> '00:00:00'
AND date >= '$start'
AND date <= '$end'
AND emp_id = '$emp_id'

how to find duplicate records in a table within a predefined time period in sql

For example, i have following table
Mobile number Timestamp
123456 17-09-2015 11:30
455677 17-09-2015 12:15
123456 17-09-2015 12:25
453377 17-09-2015 13:15
If now is 11:30, I want to scan my table and find rows with the same numbers within the past 1 hour.
That's my SQL statement:
select a.number, a.time
from mytable a inner join
(select number, time
from mytable b
where time>=now()-Interval 1 hour and time<=now ()
group by number
Having count(*) > 1
) b
on a.number = b.number and a.time = b.time
I want to find duplicate rown with the same numbers happening within 1 hour. I should output the number and timestamp.
How about just using exists?
select t.*
from mytable t
where t.time >= now() - Interval 1 hour and
t.time <= now() and
exists (select 1
from mytable t2
where t2.number = t.number and
t2.time >= now() - Interval 1 hour and
t2.time <= now () and
t2.time <> t.time
);
However, I suspect that the problem with your query is the join to time. Just remove the time from the subquery and the on clause and you will get all numbers. Alternatively, use group by:
select t.number, group_concat(time)
from mytable t
where t.time >= now() - Interval 1 hour and
t.time <= now()
group by t.number
having count(*) > 1;

Return results of query based on todays date in SQL (MySQL)

I have a query which I got help with but I am stuck on another bit.
The code I have is
SELECT a.name, COUNT(*) AS num FROM table2 b
INNER JOIN table1 a
ON b.status_id=a.id
GROUP BY status_id
What I would like to do now is only show the results if they have been entered on the current date? The date column is in table2. The format for the date column is date and time (eg 1341241153) which is automatically set by the CRM in this way. I am not sure what format this is in!
I only need to check if the date matches the current day. I hope that is clear.
This is a MySQL database.
Any help will be gratefully received!
Use this solution:
SELECT a.name, COUNT(*) AS num
FROM table2 b
INNER JOIN table1 a ON b.status_id = a.id
WHERE b.datecol >= UNIX_TIMESTAMP(CURDATE()) AND
b.datecol < UNIX_TIMESTAMP(CURDATE() + INTERVAL 1 DAY)
GROUP BY b.status_id
This will avoid wrapping your date column inside a function which would make the query non-sargable (i.e. not able to use indexes). By keeping the comparison on the bare date column, MySQL will still able to utilize an index(if you have it set up on the date column), and it should be quite efficient.
You could simplify it even further if you ABSOLUTELY KNOW that the date entered can never be sometime in the future (i.e. tomorrow or the next day):
SELECT a.name, COUNT(*) AS num
FROM table2 b
INNER JOIN table1 a ON b.status_id = a.id
WHERE b.datecol >= UNIX_TIMESTAMP(CURDATE())
GROUP BY b.status_id
The second conditional check is removed as it doesn't need to check if it's in a future day.
You should use from_unixtime() function on date column that holds values like 1341241153.
Because these values seem stored in unix timestamp format.
Example:
mysql> select
-> from_unixtime( 1341241153 ) as 'my_datetime_1341241153',
-> date( from_unixtime( 1341241153 ) ) as 'my_date_1341241153',
-> curdate(),
-> curdate() > date( from_unixtime( 1341241153 ) ) 'is_today_later?',
-> curdate() = date( from_unixtime( 1341241153 ) ) 'is_today_equal?',
-> curdate() < date( from_unixtime( 1341241153 ) ) 'is_today_before?'
-> from
-> dual
-> \G
*************************** 1. row ***************************
my_datetime_1341241153: 2012-07-02 20:29:13
my_date_1341241153: 2012-07-02
curdate(): 2012-07-15
is_today_later?: 1
is_today_equal?: 0
is_today_before?: 0
1 row in set (0.00 sec)
Your query should be:
SELECT a.name, COUNT(*) AS num FROM table2 b
INNER JOIN table1 a
ON ( b.status_id=a.id and curdate() = date( from_unixtime( b.crm_date_time_column ) ) )
GROUP BY status_id
Try this::
SELECT a.name, COUNT(*) AS num FROM table2 b
INNER JOIN table1 a
ON b.status_id=a.id
where DATE(column_date) = DATE(now())
GROUP BY status_id

Select the first element in each day of the month

How to select the first element of each day in a month with mysql query ?
I have table with offers - startdate, so i can check for each day,month,year i'm getting the element but, i'm wondering how to get only the first element in each day of some month ?
Assume the following
Table is called mytable
Table has id as primary key
Table has dt as datatime
You want the first id of everyday in February 2012
Try this:
SELECT B.id FROM
(
SELECT DATE(dt) date_dt,MIN(dt) dt
FROM mytable
WHERE dt >= '2012-02-01 00:00:00'
AND dt < '2012-03-01 00:00:00'
GROUP BY DATE(dt)
) A
LEFT JOIN mytable B USING (dt);
If any dt has multiple B.id values try this:
SELECT dt,MIN(id) id
(
SELECT B.id,B.dt FROM
(
SELECT DATE(dt) date_dt,MIN(dt) dt
FROM mytable
WHERE dt >= '2012-02-01 00:00:00'
AND dt < '2012-03-01 00:00:00'
GROUP BY DATE(dt)
) A
LEFT JOIN mytable B USING (dt)
) AA GROUP BY dt;
Assuming startdate is a DATETIME type, and the earliest entry is the one with the earliest DATETIME value, for March, 2012:
SELECT DISTINCT *
FROM tbl t1
LEFT JOIN tbl t2
ON (t2.startdate BETWEEN '2012-02-01 00:00:00' AND '2012-02-29 23:59:59')
AND t2.startdate < t1.startdate
WHERE (t1.startdate BETWEEN '2012-02-01 00:00:00' AND '2012-02-29 23:59:59')
AND t2.startdate IS NULL
If there are no duplicate dates, then you don't need the DISTINCT.
This query works by joining with any earlier record for the same month, so if nothing was joined, it's the earliest, through process of elimination.
This technique is explained in detail in the book SQL Antipatterns.
This could also be solved with subqueries, but this type of JOIN is supposed to be easier to optimize by MySQL than subqueries, which often negate the use of indexes.
without knowing the exact structure of your table something like this should work:
SELECT MIN(offerId) FROM offers WHERE startdate <= '2012-03-06' AND startdate >= '2012-02-06' GROUP BY date(startdate)
It sounds like you are trying to do something like the following:
SELECT col_1, date_col, col_3 FROM tbl
WHERE
date_col = ( SELECT min(date_col) FROM tbl
WHERE
year(date_col) = 2006 AND
month(date_col) = 02
);
This can also be used to find the max( date_col ) . Hope this helps.
Just to offer a different way to skin this cat (much easier in SQL Server for once actually)
SELECT
t0.offerId
FROM
offers AS t0 LEFT JOIN
offers AS t1 ON t0.offerId = t1.offerId AND t1.startDate > t0.startDate AND
(t0.startDate BETWEEN '2012-02-01' AND '2012-03-01') AND
(t1.startDate BETWEEN '2012-02-01' AND '2012-03-01')
WHERE
t1.col1 IS NULL;
If you have multiple rows with the same exact time you will get multiple values returned, which you can weed out in your application logic or with a sub-query. BTW this is called a groupwise minimum/maximum.