Duplicated data count as per baseline date - mysql

I’d like to create a query that checks if there is any duplicated ID that has been used as per date.
To this end, 60 days prior to, not the subsequent to, the baseline date, should be reviewed for any duplication.
A table example is as below.
CREATE TABLE SampleTable (
pKey INT PRIMARY KEY,
personalID INT NOT NULL,
createDate DATETIME NOT NULL,
value INT NULL
);
The baseline date corresponds to DATE(createDate) whereas ID to be checked for duplication is personallD.
Information needed herein can boil down to the number of today’s data and that of duplicated data.
The number of today’s data can be easily queried as follows.
SELECT
COUNT(*)
FROM SampleTable
WHERE
DATE(createDate) = DATE(NOW())
Out of today’s data, in addition, the number of duplicated data can be checked as follows.
SELECT
COUNT(*)
FROM (
SELECT
personalID,
COUNT(*)
FROM SampleTable
WHERE
DATEDIFF(NOW(), trDate) <= 60
GROUP BY personalID HAVING COUNT(*) > 1
) AS T
In conclusion, all I need to do is get the number of total data by date and the number of data that have the same personalID during the previous days.
[Sample Data]
pKey personalID createDate value
1 1 2018-01-01 100
2 2 2018-01-01 300
3 3 2018-01-01 500
7 1 2018-01-02 100
8 2 2018-01-02 200
9 3 2018-01-02 200
10 4 2018-01-02 100
11 5 2018-01-02 100
12 3 2018-01-03 200
13 4 2018-01-03 100
14 5 2018-01-03 100
15 6 2018-01-03 50
[Desired result]
date totalCount duplicated
2018-01-01 3 0
2018-01-02 5 3
2018-01-03 4 3

If you want the ids that have multiple rows in the past 60 days:
select personid
from sampledata
where trdate >= curdate() - interval 60 day
group by personid
having count(*) >= 2;
If you want to also insist that the personid appears on the most recent date:
select personid
from sampledata
where trdate >= curdate() - interval 60 day
group by personid
having count(*) >= 2 and date(max(trdate)) = curdate();
EDIT:
This seems to be what you want, assuming no duplicates on a given day
select trdate, count(*) as num_persons,
sum(num_dups > 0) as num_dups
from (select sd.*,
(select count(*)
from sampledata sd2
where sd2.personid = sd.personid and
sd2.trdate < sd.trdate and
sd2.trdate >= sd.trdate - interval 60 day
) as num_dups
from sampledata
) sd
group by trdate;

You can use the self join approach as well to find out this type of data. If you need to find out what ID repeated by comparing with previous dates , you can also use this approach.
Create table Testtbl (pkey int, personalID int, createddate date, value int);
insert into Testtbl values
(1 , 1, '2018-01-01' , 100) ,
(2 , 2, '2018-01-01' , 300) ,
(3 , 3, '2018-01-01' , 500) ,
(4 , 1, '2018-01-02' , 100) ,
(5 , 2, '2018-01-02' , 200) ,
(6 , 3, '2018-01-02' , 200) ,
(7 , 4, '2018-01-02' , 100) ,
(8 , 5, '2018-01-02' , 100) ,
(9 , 3, '2018-01-03' , 200) ,
(14 , 3, '2018-01-03' , 500) ,
(10 , 4, '2018-01-03' , 100) ,
(11 , 5, '2018-01-03' , 100) ,
(12 , 6, '2018-01-03' , 50),
(13 , 6, '2018-01-03' , 100)
Query: Left join will help to find out duplicated data without loss of total count. Distinct is to make sure that the same ID is not counted twice .
select t.createddate, count(Distinct t.pkey) TotalCount,
case when t.Createddate > t1.createddate
then Count(distinct t1.PersonalID) + case when t.Createddate =
t1.createddate and
t.personalID = t1.personalID and t.pkey != t1.pkey then Count(distinct
t1.PersonalID)
else 0 end else 0
end Duplicated from Testtbl t
left join Testtbl t1 on t.personalID = t1.personalID
and t.Createddate >= t1.Createddate and t.pkey != t1.pkey
and DATEDIFF(t1.Createddate, t.Createddate) <= 60
Group by t.createddate
Output:
createddate TotalCount Duplicated
2018-01-01 3 0
2018-01-02 5 3
2018-01-03 6 5

Related

How do I create multiple views in the same query in SQL and then join them?

This is the query that I am using.
I need to join the three views to calculate the monthly total revenue.
How should I proceed?
With Txn as(
Select DATE_FORMAT(DATE_ADD(createdAt, interval 330 MINUTE), '%y-%m') as Month, Sum(netPrice/100) as TransactionRevenue from transactions
group by Month)
With Leaves as(
Select DATE_FORMAT(DATE_ADD(createdAt, interval -1 MONTH), '%y-%m') as Month, sum(amount/100) as LeaveRevenue from driverPaymentTransactions
group by Month)
With Sxn as(
Select DATE_FORMAT(DATE_ADD(createdAt, interval 330 MINUTE ), '%y-%m') as Month, sum(amount/100) as SubscribedRevenue from subscribedDriversDailyRevenues
group by MONTH)
Select * from Txn t
join Leaves l on t.Month = l.month
join Sxn s on t.month = s.month
With Txn as(
Select DATE_FORMAT(DATE_ADD(createdAt, interval 330 MINUTE), '%y-%m') as Month, Sum(netPrice/100) as TransactionRevenue from transactions
group by Month),
Leaves as(
Select DATE_FORMAT(DATE_ADD(createdAt, interval -1 MONTH), '%y-%m') as Month, sum(amount/100) as LeaveRevenue from driverPaymentTransactions
group by Month),
Sxn as(
Select DATE_FORMAT(DATE_ADD(createdAt, interval 330 MINUTE ), '%y-%m') as Month, sum(amount/100) as SubscribedRevenue from subscribedDriversDailyRevenues
group by MONTH)
Select * from Txn t
join Leaves l on t.Month = l.month
join Sxn s on t.month = s.month
You need to "join" the subqueries
CREATE VIEw myview
AS (With Txn as(
Select DATE_FORMAT(DATE_ADD(createdAt, interval 330 MINUTE), '%y-%m') as Month, Sum(netPrice/100) as TransactionRevenue from transactions
group by Month)
, Leaves as(
Select DATE_FORMAT(DATE_ADD(createdAt, interval -1 MONTH), '%y-%m') as Month, sum(amount/100) as LeaveRevenue from driverPaymentTransactions
group by Month)
, Sxn as(
Select DATE_FORMAT(DATE_ADD(createdAt, interval 330 MINUTE ), '%y-%m') as Month, sum(amount/100) as SubscribedRevenue from subscribedDriversDailyRevenues
group by MONTH)
Select * from Txn t
join Leaves l on t.Month = l.month
join Sxn s on t.month = s.month)
Do not join when you need to "pull" some different measures to the common attribute. Use union all, where you do not need to care about the most complete source of group values:
create table t1 as
select 1 as id, 10 as val union all
select 1, 20 union all
select 2, 30 union all
select 3, 49
create table t2 as
select 1 as id, 10 as val union all
select 3, 20 union all
select 3, 30 union all
select 5, 49
create table t3 as
select 4 as id, 10 as val union all
select 6, 20 union all
select 2, 30 union all
select 3, 49
with u as (
select
id
, val as t1_val
, cast(null as decimal) as t2_val
, cast(null as decimal) as t3_val
from t1
union all
select
id
, null as t1_val
, val as t2_val
, null as t3_val
from t2
union all
select
id
, null as t1_val
, null as t2_val
, val as t3_val
from t3
)
select
id
, sum(t1_val) as t1_val
, sum(t2_val) as t2_val
, sum(t3_val) as t3_val
from u
group by id
id | t1_val | t2_val | t3_val
-: | -----: | -----: | -----:
1 | 30 | 10 | null
2 | 30 | null | 30
3 | 49 | 50 | 49
5 | null | 49 | null
4 | null | null | 10
6 | null | null | 20
db<>fiddle here

Get active users by month

Using MySQL, I'm trying to get the number of active users I have in any given month. I have a table with ActivationDate and TerminationDate columns, and if the month being counted is after the ActivationDate and TerminationDate is null, then the user is active and should be counted. I would like to summarize these amounts by month. I'm thinking I could just sum each side and calculate the total but breaking that down won't give me a running total. I've tried with window functions, but I don't have enough experience with them to know exactly what I'm doing wrong and I'm not certain how to ask the right question.
So for instance, if I have the following data...
UserId ActivationDate TerminationDate
1 2020-01-01 null
2 2020-01-15 null
3 2020-01-20 2020-01-30
4 2020-02-01 null
5 2020-02-14 2020-02-27
6 2020-02-15 2020-02-28
7 2020-03-02 null
8 2020-03-05 null
9 2020-03-20 2020-03-21
I would like my results to be similar to:
2020-01 2 (there are 2 active users, since one signed up but cancelled before the end of the month)
2020-02 3 (2 from the previous month, plus 1 that signed up this month and is still active)
2020-03 5 (3 from previous, 2 new, 1 cancellation)
You can unpivot, then aggregate and sum. In MySQL 8.0.14 or higher, you can use a lateral join:
select date_format(x.dt, '%Y-%m-01') as dt_month,
sum(sum(cnt)) over(order by date_format(x.dt, '%Y-%m-01')) as cnt_active_users
from mytable t
cross join lateral (
select t.activationdate as dt, 1 as cnt
union all select t.terminationdate, -1
) x
where x.dt is not null
group by dt_month
order by dt_month
In earlier 8.x versions:
select date_format(x.dt, '%Y-%m-01') as dt_month,
sum(sum(cnt)) over(order by date_format(x.dt, '%Y-%m-01')) as cnt_active_users
from (
select activationdate as dt, 1 as cnt from from mytable
union all select terminationdate, -1 from mytable
) x
where x.dt is not null
group by dt_month
order by dt_month
You don't say what version of MySQL. If you're using 8.0, this should work:
create table userdates (
UserId int not null,
ActivationDate date not null,
TerminationDate date null
);
insert into userdates (UserId, ActivationDate, TerminationDate)
values
(1, cast("2020-01-01" as date), null )
, (2, cast("2020-01-15" as date), null )
, (3, cast("2020-01-20" as date), cast("2020-01-30" as date))
, (4, cast("2020-02-01" as date), null )
, (5, cast("2020-02-14" as date), cast("2020-02-27" as date))
, (6, cast("2020-02-15" as date), cast("2020-02-28" as date))
, (7, cast("2020-03-02" as date), null )
, (8, cast("2020-03-05" as date), null )
, (9, cast("2020-03-20" as date), cast("2020-03-21" as date))
, (10, cast("2020-07-20" as date), null)
, (11, cast("2019-09-12" as date), cast("2019-09-14" as date));
WITH RECURSIVE d (dt)
AS (
SELECT cast("2019-01-01" as date)
UNION ALL
SELECT date_add(dt, interval 1 month)
FROM d
WHERE dt < cast("2020-12-01" as date)
)
select d.dt
, count(distinct ud.UserId) as UserCount
from userdates ud
right outer join d on d.dt >= date_format(ud.ActivationDate, '%Y-%m-01')
and (d.dt <= ud.TerminationDate or ud.TerminationDate is null)
group by d.dt;

Combining data from multiple rows into one

First question on here, so i will try my best to be clear.
I have 2 tables:
"TABLE1" which contains a record for each stock code and a list of attributes.
In TABLE 1 there is just one record for each stock_code
"TABLE2" which contains a log of changes to attributes of products, over time.
"TABLE2" contains the following fields:.
stock_code
stock_attribute
old_value
new_value
change_date
change_time
TABLE 2 has multiple entries ofr each stock_code.
Every time a stock item is change, another entry is made in Table2, with the attribute that has changed, the change date, time, old value and new value.
I want to create a query which will result in a table that has one record for each stock_code (from TABLE 1), and a column for each week over past year, with the value in each field being the last recorded "new_val" for that week (From TABLE 2)
I have tried
SELECT a.`stcode`, b.`week1`, b.`week2`, b.`week3`, b.`week4` etc. etc.
from (SELECT stcode, )as a
LEFT JOIN (SELECT stcode,
(CASE WHEN chngdate BETWEEN DATE_SUB(CURDATE(),INTERVAL 363 DAY) AND DATE_SUB(CURDATE(),INTERVAL 357 DAY) THEN newval END)week1,
(CASE WHEN chngdate BETWEEN DATE_SUB(CURDATE(),INTERVAL 356 DAY) AND DATE_SUB(CURDATE(),INTERVAL 350 DAY) THEN newval END)week2,
(CASE WHEN chngdate BETWEEN DATE_SUB(CURDATE(),INTERVAL 349 DAY) AND DATE_SUB(CURDATE(),INTERVAL 343 DAY) THEN newval END)week3,
(CASE WHEN chngdate BETWEEN DATE_SUB(CURDATE(),INTERVAL 342 DAY) AND DATE_SUB(CURDATE(),INTERVAL 336 DAY) THEN newval END)week4,
(etc
etc
etc
FROM (SELECT * from TABLE 2 ORDER BY "chngdate" DESC, "chngtime" DESC )as sub) as b ON b.stcode = s.stcode
ORDER BY stcode ASC
The trouble is with this, i get multiple lines for a stock_code which has mutliple entries....
for example, for stock_code abc123 the result i get is
STCODE WEEK1 WEEK2 WEEK3 WEEK4 WEEK5 WEEK6
abc123 null null 4 null null null
abc123 2 null null null null null
abc123 null null null null 3 null
what i WANT is this:
STCODE WEEK1 WEEK2 WEEK3 WEEK4 WEEK5 WEEK6
abc123 2 null 4 null 3 null
I have also tried the following, but teh query took so long, it never finished (there were 52 derived tables!)
SELECT a.`stcode`, w1.`new_value`, w2.`new_value`, w3.`new_value`, w4.`new_value` etc. etc.
from (SELECT stcode, )as a
LEFT JOIN (SELECT stcode,
LEFT JOIN (SELECT stcode, depot, fieldname, chngdate, chngtime, newval from STDepotAmendmentsLog WHERE chngdate BETWEEN DATE_SUB(CURDATE(),INTERVAL 363 DAY) AND DATE_SUB(CURDATE(),INTERVAL 357 DAY) ORDER BY "chngdate" DESC, "chngtime" DESC)as w1 on s.stcode = w1.stcode
etc for week 2, 3, 4 etc etc
You could do the following:
Find the greatest date for each "week"
Find the rows corresponding to those dates
Use conditional aggregation to convert rows into columns
Here is a rough outline of the code. It assumes that e.g. if today is 2020-03-03 then week 52 is from 2020-02-26 to 2020-03-03. Adjust if necessary:
SELECT t.stock_code
, MAX(CASE WHEN weeknum = 51 THEN new_value END) AS week01
, MAX(CASE WHEN weeknum = 50 THEN new_value END) AS week02
, MAX(CASE WHEN weeknum = 1 THEN new_value END) AS week51
, MAX(CASE WHEN weeknum = 0 THEN new_value END) AS week52
FROM table2 AS t
JOIN (
SELECT stock_code
, DATEDIFF(CURRENT_DATE, change_date) div 7 AS weeknum -- count multiples of 7
, MAX(change_date) AS greatest_date
GROUP BY stock_code, weeknum
FROM table2
) AS a ON t.stock_code = a.stock_code AND t.change_date = a.greatest_date
GROUP BY t.stock_code

how to sum of multiple count on different table and different filter

i want to get box_id, date , hour and sum of multiple count id in different table with different status in each table but having same box_id,
example
table 1
(filter by status = finished)
id box_id date status
i 20 2019-01-01 01:00:00.000 UTC finished
2 21 2019-01-01 02:00:00.000 UTC finished
3 21 2019-01-01 01:00:00.000 UTC unfinished
table 2
(filter by status = start)
id box_id date status
i 21 2019-01-01 01:00:00.000 UTC start
2 22 2019-01-01 02:00:00.000 UTC end
3 23 2019-01-01 01:00:00.000 UTC start
4 24 2019-01-01 01:00:00.000 UTC start
table 3
(filter by status = close)
id box_id date status
i 21 2019-01-01 03:00:00.000 UTC close
2 22 2019-01-01 02:00:00.000 UTC end
3 24 2019-01-01 01:00:00.000 UTC close
result that i want:
box_id date hour count
20 2019-01-01 1 1
21 2019-01-01 1 1
21 2019-01-01 2 1
21 2019-01-01 3 1
23 2019-01-01 1 1
24 2019-01-01 1 2
this is my query that works for table 1:
how i get for all in 1 table ?
select box_id,
date(date_update),
EXTRACT(hour FROM date_update) as hourly,
count(id)
from table1
where status = "finished"
group by box_id, date(date_update), EXTRACT(hour FROM date_update)
format hour = 0 - 23
Assuming your date field is of TIMESTAMP data type - below is for BigQuery Standard SQL
#standardSQL
SELECT box_id, date, hour, COUNT(1) cnt
FROM (
SELECT box_id, DATE(date) date, EXTRACT(HOUR FROM date) hour
FROM `project.dataset.table1` WHERE status = 'finished' UNION ALL
SELECT box_id, DATE(date) date, EXTRACT(HOUR FROM date) hour
FROM `project.dataset.table2` WHERE status = 'start' UNION ALL
SELECT box_id, DATE(date) date, EXTRACT(HOUR FROM date) hour
FROM `project.dataset.table3` WHERE status = 'close'
)
GROUP BY box_id, date, hour
You can test, play with above using sample/dummy data from your question as in below example
#standardSQL
WITH `project.dataset.table1` AS (
SELECT 1 id, 20 box_id, TIMESTAMP '2019-01-01 01:00:00.000 UTC'date, 'finished' status UNION ALL
SELECT 2, 21, '2019-01-01 02:00:00.000 UTC', 'finished' UNION ALL
SELECT 3, 21, '2019-01-01 01:00:00.000 UTC', 'unfinished'
), `project.dataset.table2` AS (
SELECT 1 id, 21 box_id, TIMESTAMP '2019-01-01 01:00:00.000 UTC' date, 'start' status UNION ALL
SELECT 2, 22, '2019-01-01 02:00:00.000 UTC', 'end' UNION ALL
SELECT 3, 23, '2019-01-01 01:00:00.000 UTC', 'start' UNION ALL
SELECT 4, 24, '2019-01-01 01:00:00.000 UTC', 'start'
), `project.dataset.table3` AS (
SELECT 1 id, 21 box_id, TIMESTAMP '2019-01-01 03:00:00.000 UTC' date, 'close' status UNION ALL
SELECT 2, 22, '2019-01-01 02:00:00.000 UTC', 'end' UNION ALL
SELECT 3, 24, '2019-01-01 01:00:00.000 UTC', 'close'
)
SELECT box_id, date, hour, COUNT(1) cnt
FROM (
SELECT box_id, DATE(date) date, EXTRACT(HOUR FROM date) hour
FROM `project.dataset.table1` WHERE status = 'finished' UNION ALL
SELECT box_id, DATE(date) date, EXTRACT(HOUR FROM date) hour
FROM `project.dataset.table2` WHERE status = 'start' UNION ALL
SELECT box_id, DATE(date) date, EXTRACT(HOUR FROM date) hour
FROM `project.dataset.table3` WHERE status = 'close'
)
GROUP BY box_id, date, hour
-- ORDER BY box_id, date, hour
with result
Row box_id date hour cnt
1 20 2019-01-01 1 1
2 21 2019-01-01 1 1
3 21 2019-01-01 2 1
4 21 2019-01-01 3 1
5 23 2019-01-01 1 1
6 24 2019-01-01 1 2
Below are slightly refactored versions of the same (with same output obviously)
#standardSQL
SELECT box_id, DATE(date) date, EXTRACT(HOUR FROM date) hour,
COUNTIF(
(t = 1 AND status = 'finished') OR
(t = 2 AND status = 'start') OR
(t = 3 AND status = 'close')
) cnt
FROM (
SELECT 1 t, * FROM `project.dataset.table1` UNION ALL
SELECT 2, * FROM `project.dataset.table2` UNION ALL
SELECT 3, * FROM `project.dataset.table3`
)
GROUP BY box_id, date, hour
HAVING cnt > 0
OR
#standardSQL
SELECT box_id, DATE(date) date, EXTRACT(HOUR FROM date) hour, COUNT(1) cnt
FROM (
SELECT * FROM `project.dataset.table1` WHERE status = 'finished' UNION ALL
SELECT * FROM `project.dataset.table2` WHERE status = 'start' UNION ALL
SELECT * FROM `project.dataset.table3` WHERE status = 'close'
)
GROUP BY box_id, date, hour
As discussed in the comments since you want to add fields from multiple tables I recommend you use a JOIN. There are multiple JOIN types, in case all the table have the same amount and values for box_id, you can use INNER JOIN. However, if that is not the case and you still want to see the count for each box_id, even though this box_id might not be present in all the three tables I suggest you to use FULL JOIN.
Below I have written a simplified example where I used FULL JOIN, together with other built in functions in BigQuery.
SELECT DISTINCT
coalesce(t1.box_id, t2.box_id, t3.box_id) AS id,
(ifnull(t1.count,0)+ifnull(t2.count,0)+ifnull(t3.count,0)) AS count
FROM (
SELECT
box_id,
count(box_id) AS count
FROM
`source_table1`
WHERE status = 'finished'
GROUP BY
box_id) t1
FULL JOIN (
SELECT
box_id,
count (box_id) AS count
FROM
`source_table2`
WHERE status = 'finished'
GROUP BY
box_id ) t2
ON
t1.box_id=t2.box_id
FULL JOIN (
SELECT
box_id,
count (box_id) AS count
FROM
`source_table3`
WHERE status = 'finished'
GROUP BY
box_id) AS t3
ON
t1.box_id=t3.box_id
WHERE
t1.box_id IS NOT NULL
OR t2.box_id IS NOT NULL
OR t3.box_id IS NOT NULL
ORDER BY
id
Notice that I used COALESCE to select the box_id, in case this field does not exist in table 1 it will move to table 2 and so on. Subsequently, I have used IFNULL to sum the result of the count encountered in each table, this method was used to make sure the count is set to zero when box_id is not in the table. Finally, I used the WHERE clause, so the count would respect the condition you imposed.
I used the sample data beneath to reproduce your case:
Table 1:
Table 2 and Table 3:
Hence, the output was:
I hope it helps.

Mysql : Finding empty time blocks between two dates and times?

I wanted to find out user's availability from database table:
primary id | UserId | startdate | enddate
1 | 42 | 2014-05-18 09:00 | 2014-05-18 10:00
2 | 42 | 2014-05-18 11:00 | 2014-05-18 12:00
3 | 42 | 2014-05-18 14:00 | 2014-05-18 16:00
4 | 42 | 2014-05-18 18:00 | 2014-05-18 19:00
Let's consider above inserted data is user's busy time, I want to find out free time gap blocks from table between start time and end time.
BETWEEN 2014-05-18 11:00 AND 2014-05-18 19:00;
Let me add here schema of table for avoiding confusion:
Create Table availability (
pid int not null,
userId int not null,
StartDate datetime,
EndDate datetime
);
Insert Into availability values
(1, 42, '2013-10-18 09:00', '2013-10-18 10:00'),
(2, 42, '2013-10-18 11:00', '2013-10-18 12:00'),
(3, 42, '2013-10-18 14:00', '2013-11-18 16:00'),
(4, 42, '2013-10-18 18:00', '2013-11-18 19:00');
REQUIREMENT:
I wanted to find out free gap records like:
'2013-10-27 10:00' to '2013-10-28 11:00' - User is available for 1 hours and
'2013-10-27 12:00' to '2013-10-28 14:00' - User is available for 2 hours and
available start time is '2013-10-27 10:00' and '2013-10-27 12:00' respectively.
Here you go
SELECT t1.userId,
t1.enddate, MIN(t2.startdate),
MIN(TIMESTAMPDIFF(HOUR, t1.enddate, t2.startdate))
FROM user t1
JOIN user t2 ON t1.UserId=t2.UserId
AND t2.startdate > t1.enddate AND t2.pid > t1.pid
WHERE
t1.endDate >= '2013-10-18 09:00'
AND t2.startDate <= '2013-11-18 19:00'
GROUP BY t1.UserId, t1.endDate
http://sqlfiddle.com/#!2/50d693/1
Using your data, the easiest way is to list the hours when someone is free. The following gets a list of hours when someone is available:
select (StartTime + interval n.n hour) as FreeHour
from (select cast('2014-05-18 11:00' as datetime) as StartTime,
cast('2014-05-18 19:00' as datetime) as EndTime
) var join
(select 0 as n union all select 1 union all select 2 union all select 3 union all select 4 union all
select 5 union all select 6 union all select 7 union all select 8 union all select 9
) n
on StartTime + interval n.n hour <= EndTime
where not exists (select 1
from availability a
where StartTime + interval n.n hour < a.EndDate and
StartTime + interval n.n hour >= a.StartDate
);
EDIT:
The general solution to your problem requires denormalizing the data. The basic query is:
select thedate, sum(isstart) as isstart, #cum := #cum + sum(isstart) as cum
from ((select StartDate as thedate, 1 as isstart
from availability a
where userid = 42
) union all
(select EndDate as thedate, -1 as isstart
from availability a
where userid = 42
) union all
(select cast('2014-05-18 11:00' as datetime), 0 as isstart
) union all
(select cast('2014-05-18 19:00' as datetime), 0 as isstart
)
) t
group by thedate cross join
(select #cum := 0) var
order by thedate
You then just choose the values where cum = 0. The challenge is getting the next date from this list. In MySQL that is a real pain, because you cannot use a CTE or view or window function, so you have to repeat the query. This is why I think the first approach is probably better for your situation.
The core query can be this. You can dress it up as you like, but I'd handle all that stuff in the presentation layer...
SELECT a.enddate 'Available From'
, MIN(b.startdate) 'To'
FROM user a
JOIN user b
ON b.userid = a.user
AND b.startdate > a.enddate
GROUP
BY a.enddate
HAVING a.enddate < MIN(b.startdate)
For times outside the 'booked' range, you have to extend this a little with a UNION, or again handle the logic at the application level