Hi Programming Master,
I need help with this. This is an employee data, the NULL in TerminationDate refer to current date, it's mean that the employee are still working.
I need to find longest time (in days) where there is no one hired or terminated.
Table Name : Employee
Column Name : ID, HireDate, TerminationDate
Employee
ID HireDate TerminationDate
1 2009-06-20 2016-01-01
2 2010-02-12 NULL
3 2012-03-14 NULL
4 2013-09-10 2014-01-01
5 2013-09-10 NULL
6 2015-04-10 2015-05-01
7 2010-04-11 2016-01-01
8 2012-05-12 NULL
9 2011-04-13 2015-02-13
I have developed process of what need to do
Combine data in HireDate and TerminationDate (it should have 18 rows)
Order the date
Find the difference between each date from Row(n) and Row(n-1)
Get the max difference
However I don't know how to do it in MySQL or if it is even possible. I wonder if there is any other method? Please help me
This is rather complicated in MySQL, prior to version 8. But you can do:
select dte, next_dte,
datediff(coalesce(next_dte, curdate()), dte) as diff
from (select dte,
(select min(d2.dte)
from ((select hiredate as dte
from t
) union -- intentionally remove duplicates
(select terminationdate as dte
from t
where teminationdate is not null
)
) d2
where d2.dte > d.dte
) next_dte
from ((select hiredate as dte
from t
) union -- intentionally remove duplicates
(select terminationdate as dte
from t
where teminationdate is not null
)
) d
) d
order by diff desc
limit 1;
Note that this finds the the most recent period, based on the current date. You can adjust this by replacing curdate() with whatever cutoff date you have in mind. If you don't want the most recent period, add where next_dte is not null to the outer query.
Related
I have table as following:
hours | ... | task_assigned | task_deadline | task_completion
----------------------------------------------------------------
123 | ... | 2019-08-01 | - | -
234 | ... | - | 2018-08-01 | 2019-08-01
145 | ... | 2017-08-01 | 2017-08-01 | 2018-01-01
I want to calculate total hours for each year, i.e. grouping by year.
Currently I'm only taking into account task_completion field.
If there's no value in task_completion field, the record is not included in SUM calculation.
To elaborate further, say for year 2019, row 1 and 1 both should be considered. Hence the total hours should be 123 + 234 = 357.
And for year 2018, row 2 and 3.
Similarly, for year 2017, row 3.
SELECT YEAR(task_completion) as year, ROUND(SUM(total_hours), 2) as hours
FROM task
GROUP BY year
HAVING year BETWEEN '$year_from' AND '$year_to'
The resultset:
year | hours
--------------------
2017 | <somevalue>
2018 | <somevalue>
2019 | <somevalue>
How can I include other two date fields too?
You want to consider each row once for each of its years. Use UNION to get these years:
select year, round(sum(total_hours), 2) as hours
from
(
select year(task_assigned) as year, total_hours from task
union
select year(task_deadline) as year, total_hours from task
union
select year(task_completion) as year, total_hours from task
) years_and_hours
group by year
having year between $year_from and $year_to
order by year;
If you want to consider a row with one year twice or thrice also as often in the sum, then change UNION to UNION ALL.
Basically, you want to unpivot the data. I will assume that the - represents a NULL value and your dates are real dates.
select year(dte) as year, sum(total_hours) as hours
from ((select task_assigned as dte, total_hours
from task
) union all
(select task_deadline, total_hours
from task
) union all
(select task_completion, total_hours
from task
)
) d
where dte is not null
group by year(dte)
order by year(dte);
Based on your sample data, the round() is not necessary so I removed it.
If you want to filter for particular years, the filtering should be in a where clause -- so it filters the data before aggregation.
Change the where to:
where year(dte) >= ? and year(dte) <= ?
or:
where dte >= ? and dte <= ?
to pass in the dates.
The ? are for parameter placeholders. Learn how to use parameters rather than munging query strings.
This answer is no langer valid with the updated request.
If I understand correctly, you want to use task_assigned if the task_completion is still null. Use COALEASCE for this.
SELECT
YEAR(COALESCE(task_completion, task_assigned)) as year,
ROUND(SUM(total_hours), 2) as hours
FROM task
GROUP BY year
HAVING year BETWEEN $year_from AND $year_to
ORDER BY year;
(I don't think you actually want to use task_deadline, too, for how could a task get completed before getting assigned first? If such can occur, then include it in the COALESCE expression. Probably: COALESCE(task_completion, task_assigned, task_deadline)` then.)
I have the next structure in a MySQL database:
boats
id name
-------------
1 name1
2 name2
boat_prices
id boat_id date duration price is_default
---------------------------------------------------------------
1 1 '2018-01-01' 1 100
2 1 '2018-01-01' 2 200
3 1 null null 100 1
4 2 '2018-01-02' 2 400
5 2 '2018-01-02' 4 800
6 2 null null 200 1
7 3 '2018-01-03' 5 1500
8 3 null null 300 1
The boats have a price for a specific date and duration in days.
All boats have a default "from" price that is identified by date = null and duration = null.
But, not all boats have prices for all days.
When I search for boat prices for a specific date and duration, the query should return all rows with a price for that date and duration, and in case a boat hasnĀ“t got a price for that date return its "from" default price.
Example: For the date = '2018-01-01 and duration = 1, the result should be:
boat_prices
id boat_id date duration price is_default
----------------------------------------------------------------
1 1 '2018-01-01' 1 100
6 2 null null 200 1
8 3 null null 300 1
I did this query example just to simplify, but please take into account apart from this, the query has some other joins with other tables.
I need help with the query.
I believe Rick was on the right direction having left join, but you probably need TWO. One to get the boat prices that qualify the date interested in, another explicitly for the default.
select
b.id,
b.name,
DefPrice.price as DefaultPrice,
Specials.price as SpecialsPrice,
COALESCE( Specials.price, DefPrice.price ) as DiscountOrDefaultPrice
from
( select #parmDate = '2018-01-01' ) sqlvars,
boats b
JOIN boat_prices DefPrice
on b.id = DefPrice.boat_id
AND DefPrice.date IS NULL
AND DefPrice.Duration IS NULL
LEFT JOIN boat_prices Specials
on b.id = Specials.boat_id
AND Specials.date <= #parmDate
AND #parmDate <= Date_Add( Specials.Date, INTERVAL (Specials.duration -1 ) DAY )
Now, you could always return only the one price in question by doing a COALESCE() in case there is no Specials price, it gets the default via the DiscountOrDefaultPrice column.
Take your pick version of which column(s) you want to run with. This should get ALL boats, regardless of some special price based on durations. As you change whatever your parameter date in question is -- even if you do a current date, it will work. This is because you are testing the date in question against ALL possible special boat prices and its beginning to beginning + duration end date range. If you have multiple prices that overlap dates, that will just return those multiple rows that overlap.
My Adding of the duration is subtracting 1. For example, if your date is 2018-01-01 and its good for 1 day, does that mean it is only good for that one day? or up to and including 2018-01-02. The -1 forces the qualification to just the one day. So the price on 2018-01-01 good for 1 day is ONLY 2018-01-01.
Your other example for 2018-01-02 has two day duration. To me, indicating 2 days including 01-02 through 01-03. Two actual days.
CONFIRMATION from comment about dates and range
I guess my interpretation was wrong then on your data needs. Your sample of TWO dated boat price records apparently is not enough. You stated you want ALL boats regardless of qualification of a special price record. So you must start with the boat and the join to get all possible "Default" pricing no matter what. It is only the LEFT-JOIN component that needs to be adjusted.
That being said, lets simulate more data. Assume you have the following
Boad ID Date Duration Rate
1 2018-01-01 1 x
1 2018-01-02 4 y
2 2018-01-02 2 z
2 2018-01-04 4 a
3 2018-01-03 5 b
If I provide the date 2018-01-01, what rate records should I see?
If I provide date 2018-01-03, what records?
If I provide date 2018-01-05, what records?
For the particular date "2018-01-01" and duration of 1, i will use an UNION clause like this:
(Note: Edited for add is_default column)
-- Get prices for particular day and duration.
(SELECT
boat_id,
date,
duration,
price,
0 AS is_default
FROM
boat_prices
WHERE
date = "2018-01-01" AND duration = 1)
UNION
-- Add defaults prices for those don't have a price on the particular day and duration
(SELECT
boat_id,
date,
duration,
price,
is_default
FROM
boat_prices
WHERE
date IS NULL
AND
duration IS NULL
AND
boat_id NOT IN (SELECT boat_id
FROM boat_prices
WHERE date ="2018-01-01" AND duration = 1))
EXAMPLE WITH STORED PROCEDURE SOLUTION
DELIMITER //
CREATE PROCEDURE GetPricesByDateAndDuration(IN pDate DATE, IN pDuration INT)
BEGIN
-- Get prices for particular day and duration.
(SELECT
boat_id,
date,
duration,
price,
0 AS is_default
FROM
boat_prices
WHERE
date = pDate AND duration = pDuration)
UNION
-- Add defaults prices for those don't have a price on the particular day and duration
(SELECT
boat_id,
date,
duration,
price,
is_default
FROM
boat_prices
WHERE
date IS NULL
AND
duration IS NULL
AND
boat_id NOT IN (SELECT boat_id
FROM boat_prices
WHERE date = pDate AND duration = pDuration))
END //
DELIMITER ;
Then you can call the procedure like this:
CALL GetPricesByDateAndDuration('2018-01-01', 1);
Instead of that clunky output, consider:
boat_id price default
-----------------------------
1 100
2 300 (default)
Something like this should generate that:
SELECT boat_id,
IF(b.price IS NULL, dflt.price, b.price) AS price,
IF(b.price IS NULL, '(default)', '') AS default
FROM boat_prices AS dflt
LEFT JOIN boat_prices AS b USING(boat_id)
WHERE dflt.date IS NULL
AND dflt.duration IS NULL
AND '2018-01-01' >= b.date
AND '2018-01-01' < b.date + INTERVAL b.duration DAY
GROUP BY boat_id
I'd like to ask you, if there is any way in mysql to do this. I know it can be done by cycle, but feeling like there has to be a better way.
I have table vacation
id | date_from | date_to
1 2017-02-16 2017-02-19
2 2017-02-18 2017-02-21
3 2017-02-12 2017-02-19
4 2017-02-19 2017-02-21
The thing is, that user picks the date range he wants to reserve, like 2017-02-14 to 2017-02-24 and I have to check whether he can still request it, because only 4 people can request vacation simultaneously.
So I need query which will check whether in user selected range (for example 2017-02-14 to 2017-02-24) are for any day 4 or more rows.
Thanks in advance for your answers.
You can do this for any day:
select count(*)
from vacation v
where '2017-02-14' between date_from and date_to;
You can extend this to a range, by specifying each day:
select dte, count(v.id)
from (select date('2017-02-14') as dte union all
select date('2017-02-15') as dte union all
select date('2017-02-16') as dte union all
select date('2017-02-17') as dte union all
select date('2017-02-18') as dte union all
. . .
) d left join
vacation v
on d.dte between date_from and date_to
group by d.dte
having count(*) > 4;
It might be simpler to loop through on the application side. Or, to use a calendar table if you have one.
Select all rows in that date range and count the results:
SELECT count(*) FROM vacation WHERE :input_from BETWEEN date_from AND date_to OR :input_to BETWEEN date_from AND date_to
I have a table in my database that contains an ID and DATETIME column, here is some sample data:
ID | DATETIME
1 | 2014-05-06 01:12
1 | 2014-05-06 01:30
1 | 2014-05-06 01:45
1 | 2014-05-06 02:59
2 | 2014-05-06 01:17
2 | 2014-05-06 01:18
2 | 2014-05-06 01:19
2 | 2014-05-06 02:00
I need to produce a query that determines the ID belonging to the object that has the longest time between its DATETIME values, where the time between consecutive DATETIME values does not exceed 20 minutes.
For example, in the sample data, I would want to return 1 as it has DATETIME values from (01:12 - 01:45) without having a consecutive difference of 20 minutes between DATETIME values.
Thanks.
It looks like you will need a self-join. Because if you had 10 entries for an ID, your 20 minute gap might be between entries 3-6 vs 1-4 or even 4-9. So the second instance of the join would be on the same ID and have a date time higher than that of the primary entry, but less than 20 minutes. Then, it could be ordered by the time-gap and limit to the one you want. Something like:
select
YT.ID,
YT.DTColumn,
MAX( YT2.DTColumn ) as MaxDateWithin20Minutes
from
YourTable YT
JOIN YourTable YT2
ON YT.ID = YT2.ID
AND YT.DTColumn < YT2.DTColumn
AND YT2.DTColumn <= date_add( YT.DTColumn, INTERVAL 20 MINUTE )
group by
YT.ID,
YT.DTColumn
order by
timediff(MAX( YT2.DTColumn ), YT.DTColumn) DESC
limit
1
You need to get the next (or previous) value and get the time difference. I think the following does what you want:
select t.*
from (select t.*,
(select t2.datetime
from table t2
where t2.id = t.id and t2.datetime < t.datetime
order by t2.datetime desc
) prev_datetime
from table t
) t
where datetime <= prev_datetime + interval 20 minutes
order by timestampdiff(second, prev_datetime, datetime) desc
limit 1;
Let's say I have a table that says how many items of something are valid between two dates.
Additionally, there may be multiple such periods.
For example, given a table:
itemtype | count | start | end
A | 10 | 2014-01-01 | 2014-01-10
A | 10 | 2014-01-05 | 2014-01-08
This means that there are 10 items of type A valid 2014-01-01 - 2014-01-10 and additionally, there are 10 valid 2014-01-05 - 2014-01-08.
So for example, the sum of valid items at 2014-01-06 are 20.
How can I query the table to get the sum per day? I would like a result such as
2014-01-01 10
2014-01-02 10
2014-01-03 10
2014-01-04 10
2014-01-05 20
2014-01-06 20
2014-01-07 20
2014-01-08 20
2014-01-09 10
2014-01-10 10
Can this be done with SQL? Either Oracle or MySQL would be fine
The basic syntax you are looking for is as follows:
For my example below I've defined a new table called DateTimePeriods which has a column for StartDate and EndDate both of which are DATE columns.
SELECT
SUM(NumericColumnName)
, DateTimePeriods.StartDate
, DateTimePeriods.EndDate
FROM
TableName
INNER JOIN DateTimePeriods ON TableName.dateColumnName BETWEEN DateTimePeriods.StartDate and DateTimePeriods.EndDate
GROUP BY
DateTimePeriods.StartDate
, DateTimePeriods.EndDate
Obviously the above code won't work on your database but should give you a reasonable place to start. You should look into GROUP BY and Aggregate Functions. I'm also not certain of how universal BETWEEN is for each database type, but you could do it using other comparisons such as <= and >=.
There are several ways to go about this. First, you need a list of dense dates to query. Using a row generator statement can provide that:
select date '2014-01-01' + level -1 d
from dual
connect by level <= 15;
Then for each date, select the sum of inventory:
with
sample_data as
(select 'A' itemtype, 10 item_count, date '2014-01-01' start_date, date '2014-01-10' end_date from dual union all
select 'A', 10, date '2014-01-05', date '2014-01-08' from dual),
periods as (select date '2014-01-01' + level -1 d from dual connect by level <= 15)
select
periods.d,
(select sum(item_count) from sample_data where periods.d between start_date and end_date) available
from periods
where periods.d = date '2014-01-06';
You would need to dynamically set the number of date rows to generate.
If you only needed a single row, then a query like this would work:
with
sample_data as
(select 'A' itemtype, 10 item_count, date '2014-01-01' start_date, date '2014-01-10' end_date from dual union all
select 'A', 10, date '2014-01-05', date '2014-01-08' from dual)
select sum(item_count)
from sample_data
where date '2014-01-06' between start_date and end_date;