Difference with previous data [duplicate] - mysql

This question already has answers here:
MySQL difference between two rows of a SELECT Statement
(6 answers)
Closed 3 years ago.
I have this table:
CREATE TABLE datos (
id_estacion smallint(6) DEFAULT NULL,
id_sensor smallint(6) DEFAULT NULL,
tipo_sensor smallint(6) DEFAULT NULL,
valor float DEFAULT NULL,
fecha date DEFAULT NULL,
hora time DEFAULT NULL,
id int(11) NOT NULL AUTO_INCREMENT,
dato float DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=8556 DEFAULT CHARSET=latin1;
And this data:
id_estacion fecha hora valor
1 2019-03-15 00:00:00 1164.63
1 2019-03-15 00:15:00 1164.63
1 2019-03-15 00:30:00 1164.64
1 2019-03-15 00:45:00 1164.62
1 2019-03-15 01:00:00 1164.67
1 2019-03-15 01:15:00 1164.63
1 2019-03-15 01:30:00 1164.64
I need to calculate with mysql the difference between a data and the previous data. For example the value at '00:30' is 1164.64, the previus value, at '00:15', is 1164.63 the difference is 0.01.
id_estacion fecha hora valor diferencia
1 3/15/2019 0:00:00 1164.63 0
1 3/15/2019 0:15:00 1164.63 0
1 3/15/2019 0:30:00 1164.64 0.01
1 3/15/2019 0:45:00 1164.62 -0.02
1 3/15/2019 1:00:00 1164.67 0.05
1 3/15/2019 1:15:00 1164.63 -0.04
1 3/15/2019 1:30:00 1164.64 0.01
Is that possible? Hope you understand me.
Best regards

Here is a solution that should work on all versions of MySQL.
The principle is to self-JOIN the table, using a NOT EXISTS condition to bring in the previous record of the same id_estacion.
SELECT
t.id_estacion,
t.fetcha,
t.hora,
t.valor,
COALESCE(t.valor - tprev.valor, 0) diferencia
FROM mytable t
LEFT JOIN mytable tprev
ON tprev.id_estacion = t.id_estacion
AND CONCAT(tprev.fecha, ' ', tprev.hora) < CONCAT(t.fecha, ' ', t.hora)
NOT EXISTS (
SELECT 1 FROM mytable t1
WHERE
t1.id_estacion = t.id_estacion
AND CONCAT(t1.fecha, ' ', t1.hora) < CONCAT(t.fecha, ' ', t.hora)
AND CONCAT(t1.fecha, ' ', t1.hora) > CONCAT(tprev.fecha, ' ', tprev.hora)
)
NB: I would recommend not storing the date and time parts in separated columns, as it just makes things more complex; instead, you can use a unique column of datatype DATETIME, and use MySQL date and time functions when you need to extract parts of it.

Related

Get data in SELECT MySQL by a day in field type timestamp

I want select rows in where a day in specific i.e. "Monday", but my type column is a timestamp "AAAA-MM-DD HH:MM:SS". I've searched but I don't how to select this.
My table is this, and the field is forex_pair_price_time (timestamp):
mysql> describe forex_pair_price;
+-------------------------+----------------+------+-----+-------------------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------------------+----------------+------+-----+-------------------+----------------+
...
| forex_pair_price_time | timestamp | NO | | CURRENT_TIMESTAMP | |
...
+-------------------------+----------------+------+-----+-------------------+----------------+
To find all records where the forex timestamp happen to land on Monday, we can try using DAYNAME:
SELECT *
FROM forex_pair_price
WHERE DAYNAME(forex_pair_price_time) = 'Monday';
SELECT forex_pair_price_time,
CASE
WHEN DAYOFWEEK(orex_pair_price_time) = 1 THEN "Sunday"
WHEN DAYOFWEEK(orex_pair_price_time) = 2 THEN "Monday"
WHEN DAYOFWEEK(orex_pair_price_time) = 3 THEN "Tuesday"
WHEN DAYOFWEEK(orex_pair_price_time) = 4 THEN "Wednesday"
WHEN DAYOFWEEK(orex_pair_price_time) = 5 THEN "Thursday"
WHEN DAYOFWEEK(orex_pair_price_time) = 6 THEN "Friday"
WHEN DAYOFWEEK(orex_pair_price_time) = 7 THEN "Saturday"
END Weekday
FROM forex_pair_price
http://www.mysqltutorial.org/mysql-weekday
Use WEEKDAY(date).
The WEEKDAY function returns a weekday index for a date i.e., 0 for Monday, 1 for Tuesday, … 6 for Sunday.
Select * from forex_pair_price
where weekday(forex_pair_price_time)=0
Or
Dayname(forex_pair_price_time)='Monday'
I think it is saver to use weekday because it is an compare to an integer instead of string. This is saver against problems with typos in String.

MySQL calculating query

I have this table, only two columns, each record stores an interest rate for a given month:
id rate
===========
199502 3.63
199503 2.60
199504 4.26
199505 4.25
... ...
201704 0.79
201705 0.93
201706 0.81
201707 0.80
201708 0.14
Based on this rates, I need to create another table of accumulated rates which similar structure, whose data is calculated as function of a YYYYMM (month/year) parameter, this way (this formula is legally mandatory):
The month given as parameter has always rate of 0 (zero)
The month immediately previous has always a rate of 1 (one)
The previous months' rates will be (one) plus the sum of rates of months between that given month and the month given as parameter.
I'll clarify this rules with this example, given parameter 201708:
SOURCE CALCULATED
id rate id rate
=========== =============
199502 3.63 199502 360.97 (1 + sum(rate(199503) to rate(201707)))
199503 2.60 199503 358.37 (1 + sum(rate(199504) to rate(201707)))
199504 4.26 199504 354.11 (1 + sum(rate(199505) to rate(201707)))
199505 4.25 199505 349.86 (1 + sum(rate(199506) to rate(201707)))
... ... ... ...
201704 0.79 201704 3.54 (1 + rate(201705) + rate(201706) + rate(201707))
201705 0.93 201705 2.61 (1 + rate(201706) + rate(201707))
201706 0.81 201706 1.80 (1 + rate(201707))
201707 0.80 201707 1.00 (per definition)
201708 0.14 201708 0.00 (per definition)
Now I've already implemented a VB.NET function that reads the source table and generates the calculated table, but this is done in runtime at each client machine:
Public Function AccumRates(targetDate As Date) As DataTable
Dim dtTarget = Rates.Clone
Dim targetId = targetDate.ToString("yyyyMM")
Dim targetIdAnt = targetDate.AddMonths(-1).ToString("yyyyMM")
For Each dr In Rates.Select("id<=" & targetId & " and id>199412")
If dr("id") = targetId Then
dtTarget.Rows.Add(dr("id"), 0)
ElseIf dr("id") = targetIdAnt Then
dtTarget.Rows.Add(dr("id"), 1)
Else
Dim intermediates =
Rates.Select("id>" & dr("id") & " and id<" & targetId).Select(
Function(ldr) New With {
.id = ldr.Field(Of Integer)("id"),
.rate = ldr.Field(Of Decimal)("rate")}
).ToArray
dtTarget.Rows.Add(
dr("id"),
1 + intermediates.Sum(
Function(i) i.rate))
End If
Next
Return dtTarget
End Function
My question is how can I put this as a query in my database so it can be used dynamically by other queries which would use these accumulated rates to update debts to any given date.
Thank you very much!
EDIT
I managed to make a query that returns the data I want, now I just don't know how to encapsulate it so that it can be called from another query passing any id as argument (here I did it using a SET ... statement):
SET #targetId=201708;
SELECT
id AS id_acum,
COALESCE(1 + (SELECT
SUM(taxa)
FROM
tableSelic AS ts
WHERE
id > id_acum AND id < #targetId
LIMIT 1),
IF(id >= #targetId, 0, 1)) AS acum
FROM
tableSelic
WHERE id>199412;
That's because I'm pretty new to MySQL, I'm used to MS-Access where parametrized queries are very straightfoward to create.
For example:
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(id INT NOT NULL PRIMARY KEY
,rate DECIMAL(5,2) NOT NULL
);
INSERT INTO my_table VALUES
(201704,0.79),
(201705,0.93),
(201706,0.81),
(201707,0.80),
(201708,0.14);
SELECT *
, CASE WHEN #flag IS NULL THEN #i:=1 ELSE #i:=#i+rate END i
, #flag:=1 flag
FROM my_table
, (SELECT #flag:=null,#i:=0) vars
ORDER
BY id DESC;
+--------+------+-------------+-------+------+------+
| id | rate | #flag:=null | #i:=0 | i | flag |
+--------+------+-------------+-------+------+------+
| 201708 | 0.14 | NULL | 0 | 1 | 1 |
| 201707 | 0.80 | NULL | 0 | 1.80 | 1 |
| 201706 | 0.81 | NULL | 0 | 2.61 | 1 |
| 201705 | 0.93 | NULL | 0 | 3.54 | 1 |
| 201704 | 0.79 | NULL | 0 | 4.33 | 1 |
+--------+------+-------------+-------+------+------+
5 rows in set (0.00 sec)
Ok, I made it with a function:
CREATE FUNCTION `AccumulatedRates`(start_id integer, target_id integer) RETURNS decimal(6,2)
BEGIN
DECLARE select_var decimal(6,2);
SET select_var = (
SELECT COALESCE(1 + (
SELECT SUM(rate)
FROM tableRates
WHERE id > start_id AND id < target_id LIMIT 1
), IF(id >= unto, 0, 1)) AS acum
FROM tableRates
WHERE id=start_id);
RETURN select_var;
END
And them a simple query:
SELECT *, AccumulatedRates(id,#present_id) as acum FROM tableRates;
where #present_id is passed as parameter.
Thanks to all, anyway!

Adding values from 2 rows based on conditions

I have a table as Below....
ROW gvkey datadate CQTR CYEARQ Value
1 6066 3/31/2015 0:00 1 2015 3610
2 6066 12/31/2014 0:00 4 2014 16868
3 6066 9/30/2014 0:00 3 2014 10809
4 6066 6/30/2014 0:00 2 2014 6905
5 6066 3/31/2014 0:00 1 2014 3326
I want to get the sum of Value of 3/31/2015 and 12/31/2014. Please suggest how Can I do it in MS Sql.
Are you looking for this :-
Set Nocount On;
If Object_Id('tempdb.dbo.#table') Is Not Null
Begin
Drop Table #table;
End
Create Table #table
(
Id Int Primary Key
,Col1 Int
,RDate Datetime
,Col2 Int
,RYear Int
,Col3 Int
)
Insert Into #table Values
(1,6066,'03/31/2015 0:00',1,2015,3610)
,(2,6066,'12/31/2014 0:00',4,2014,16868)
,(3,6066,'09/30/2014 0:00',3,2014,10809)
,(4,6066,'06/30/2014 0:00',2,2014,6905)
,(5,6066,'03/31/2014 0:00',1,2014,3326)
Select t.Col1
,Sum(t.Col3) As ColSum
From #table As t With (Nolock)
Where t.RDate In ('03/31/2015','12/31/2014')
Group By t.Col1

Two methods of performing cohort analysis in MySQL using joins

I make a cohort analysis processor. Input parameters: time range and step, condition (initial event) to exctract cohorts, additional condition (retention event) to check after each N hours/days/months. Output parameters: cohort analysis grid, like this:
0h | 16h | 32h | 48h | 64h | 80h | 96h |
cohort #00 15 | 6 | 4 | 1 | 1 | 2 | 2 |
cohort #01 1 | 35 | 8 | 0 | 2 | 0 | 1 |
cohort #02 0 | 3 | 31 | 11 | 5 | 3 | 0 |
cohort #03 0 | 0 | 4 | 27 | 7 | 6 | 2 |
cohort #04 0 | 1 | 1 | 4 | 29 | 4 | 3 |
Basically:
fetch cohorts: unique users who did something 1 in every period from time_begin every time_step.
find how many of them (in each cohort) did something 2 after N seconds, N*2 seconds, N*3, and so on until now.
In short - I have 2 solutions. One works too slow and includes a heavy select with joins for each data step: 1 day, 2 day, 3 day, etc. I want to optimize it by joining result for every data step to cohorts - and it's the second solution. It looks like it works but I'm not sure it's the best way and that it will give the same result even if cohorts will intersect. Please check it out.
Here's the whole story.
I have a table of > 100,000 events, something like this:
#user-id, timestamp, event_name
events_view (uid varchar(64), tm int(11), e varchar(64))
example input row:
"user_sampleid1", 1423836540, "level_end:001:win"
To make a cohort analisys first I extract cohorts: for example, users, who send special event '1st_launch' in 10 hour periods starting from 2015-02-13 and ending with 2015-02-16. All code in this post is simplified and shortened to see the idea.
DROP TABLE IF EXISTS tmp_c;
create temporary table tmp_c (uid varchar(64), tm int(11), c int(11) );
set beg = UNIX_TIMESTAMP('2015-02-13 00:00:00');
set en = UNIX_TIMESTAMP('2015-02-16 00:00:00');
select min(tm) into t_start from events_view ;
select max(tm) into t_end from events_view ;
if beg < t_start then
set beg = t_start;
end if;
if en > t_end then
set en = t_end;
end if;
set period = 3600 * 10;
set cnt_c = ceil((en - beg) / period) ;
/*works quick enough*/
WHILE i < cnt_c DO
insert into tmp_c (
select uid, min(tm), i from events_view where
locate("1st_launch", e) > 0 and tm > (beg + period * i)
AND tm <= (beg + period * (i+1)) group by uid );
SET i = i+1;
END WHILE;
Cohorts may consist the same user ids, though usually one user is exist only in one cohort. And in each cohort users are unique.
Now I have temp table like this:
user_id | 1st timestamp | cohort_no
uid1 1423836540 0
uid2 1423839540 0
uid3 1423841160 1
uid4 1423841460 2
...
uidN 1423843080 M
Then I need to again divide time range on periods and calculate for each period how many users from each cohort have sent event "level_end:001:win".
For each small period I select all unique users who have sent "level_end:001:win" event and left join them to tmp_c cohorts table. So I have something like this:
user_id | 1st timestamp | cohort_no | user_id | other fields...
uid1 1423836540 0 uid1
uid2 1423839540 0 null
uid3 1423841160 1 null
uid4 1423841460 2 uid4
...
uidN 1423843080 M null
This way I see how many users from my cohorts are in those who have sent "level_end:001:win", exclude not found by where clause: where t2.uid is not null.
Finally I perform grouping and have counts of users in each cohort, who have sent "level_end:001:win" in this particluar period.
Here's the code:
DROP TABLE IF EXISTS tmp_res;
create temporary table tmp_res (uid varchar(64) CHARACTER SET cp1251 NOT NULL, c int(11), cnt int(11) );
set i = 0;
set cnt_c = ceil((t_end - beg) / period) ;
WHILE i < cnt_c DO
insert into tmp_res
select concat(beg + period * i, "_", beg + period * (i+1)), c, count(distinct(uid)) from
(select t1.uid, t1.c from tmp_c t1 left join
(select uid, min(tm) from events_view where
locate("level_end:001:win", e) > 0 and
tm > (beg + period * i) AND tm <= (beg + period * (i+1)) group by uid ) t2
on t1.uid = t2.uid where t2.uid is not null) t3
group by c;
SET i = i+1;
END WHILE;
/*getting result of the first method: tooo slooooow!*/
select * from tmp_res;
The result I've got (it's ok that some cohorts are not appear on some periods):
"1423832400_1423890000","1","35"
"1423832400_1423890000","2","3"
"1423832400_1423890000","3","1"
"1423832400_1423890000","4","1"
"1423890000_1423947600","1","21"
"1423890000_1423947600","2","50"
"1423890000_1423947600","3","2"
"1423947600_1424005200","1","9"
"1423947600_1424005200","2","24"
"1423947600_1424005200","3","70"
"1423947600_1424005200","4","6"
"1424005200_1424062800","1","7"
"1424005200_1424062800","2","15"
"1424005200_1424062800","3","21"
"1424005200_1424062800","4","32"
"1424062800_1424120400","1","7"
"1424062800_1424120400","2","13"
"1424062800_1424120400","3","24"
"1424062800_1424120400","4","18"
"1424120400_1424178000","1","10"
"1424120400_1424178000","2","12"
"1424120400_1424178000","3","18"
"1424120400_1424178000","4","14"
"1424178000_1424235600","1","6"
"1424178000_1424235600","2","7"
"1424178000_1424235600","3","9"
"1424178000_1424235600","4","12"
"1424235600_1424293200","1","6"
"1424235600_1424293200","2","8"
"1424235600_1424293200","3","9"
"1424235600_1424293200","4","5"
"1424293200_1424350800","1","5"
"1424293200_1424350800","2","3"
"1424293200_1424350800","3","11"
"1424293200_1424350800","4","10"
"1424350800_1424408400","1","8"
"1424350800_1424408400","2","5"
"1424350800_1424408400","3","7"
"1424350800_1424408400","4","7"
"1424408400_1424466000","2","6"
"1424408400_1424466000","3","7"
"1424408400_1424466000","4","3"
"1424466000_1424523600","1","3"
"1424466000_1424523600","2","4"
"1424466000_1424523600","3","8"
"1424466000_1424523600","4","2"
"1424523600_1424581200","2","3"
"1424523600_1424581200","3","3"
It works but it takes too much time to process because there are many queries here instead of one, so I need to rewrite it.
I think it can be rewritten with joins, but I'm still not sure how.
I decided to make a temporary table and write period boundaries in it:
DROP TABLE IF EXISTS tmp_times;
create temporary table tmp_times (tm_start int(11), tm_end int(11));
set cnt_c = ceil((t_end - beg) / period) ;
set i = 0;
WHILE i < cnt_c DO
insert into tmp_times values( beg + period * i, beg + period * (i+1));
SET i = i+1;
END WHILE;
Then I get periods-to-events mapping (user_id + timestamp represent particular event) to temp table and left join it to cohorts table and group the result:
SELECT Concat(tm_start, "_", tm_end) per,
t1.c coh,
Count(DISTINCT( t2.uid ))
FROM tmp_c t1
LEFT JOIN (SELECT *
FROM tmp_times t3
LEFT JOIN (SELECT uid,
tm
FROM events_view
WHERE Locate("level_end:101:win", e) > 0)
t4
ON ( t4.tm > t3.tm_start
AND t4.tm <= t3.tm_end )
WHERE t4.uid IS NOT NULL
ORDER BY t3.tm_start) t2
ON t1.uid = t2.uid
WHERE t2.uid IS NOT NULL
GROUP BY per,
coh
ORDER BY per,
coh;
In my tests this returns the same result as method #1. I can't check the result manually, but I understand how method #1 work more and as far I can see it gives what I want. Method #2 is faster, but I'm not sure it's the best way and it will give the same result even if cohorts will intersect.
Maybe there are well-known common methods to perform a cohort analysis in SQL? Is method #1 I use more reliable than method #2? I work with joins not that often, that's why still do not fully understand joins magic yet.
Method #2 looks like pure magic, and I used to not believe in what I don't understand :)
Thanks for answers!

for loop statement to create rows in database

I am trying to use for loop statement as follows:
for(int i=1; i <= 48; i++) { insertdiary("", ""); }
in my MyDB file:
package com.cookbook.data;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.util.Log;
public class MyDB {
private SQLiteDatabase db;
private final Context context;
private final MyDBhelper dbhelper;
// Initializes MyDBHelper instance
public MyDB(Context c){
context = c;
dbhelper = new MyDBhelper(context, Constants.DATABASE_NAME, null,
Constants.DATABASE_VERSION);
}
// Closes the database connection
public void close()
{
db.close();
}
// Initializes a SQLiteDatabase instance using MyDBhelper
public void open() throws SQLiteException
{
try {
db = dbhelper.getWritableDatabase();
} catch(SQLiteException ex) {
Log.v("Open database exception caught", ex.getMessage());
db = dbhelper.getReadableDatabase();
}
}
// Saves a diary entry to the database as name-value pairs in ContentValues instance
// then passes the data to the SQLitedatabase instance to do an insert
public long insertdiary(String title, String content)
{
try{
ContentValues newTaskValue = new ContentValues();
newTaskValue.put(Constants.TITLE_NAME, title);
newTaskValue.put(Constants.CONTENT_NAME, content);
newTaskValue.put(Constants.DATE_NAME, java.lang.System.currentTimeMillis());
return db.insert(Constants.TABLE_NAME, null, newTaskValue);
} catch(SQLiteException ex) {
Log.v("Insert into database exception caught",
ex.getMessage());
return -1;
}
}
// updates a diary entry (existing row)
public boolean updateDiaryEntry(String title, long rowId)
{
ContentValues newValue = new ContentValues();
newValue.put(Constants.TITLE_NAME, title);
return db.update(Constants.TABLE_NAME, newValue, Constants.KEY_ID + "=" + rowId, null)>0;
}
// Reads the diary entries from database, saves them in a Cursor class and returns it from the method
public Cursor getdiaries()
{
Cursor c = db.query(Constants.TABLE_NAME, null, null,
null, null, null, null);
return c;
}
}
My aim is to create 48 empty rows upon database or table first creation so I can further update these rows instead of creating new entries. Unfortunately my attempts to utilize this code were unfortunate giving me errors or creating many more rows than 48.
Is there anyone who could help me with utilizing this code to create 48 rows upon database or table first time creation please?
I appreciate all help.
Paddy
Unless there is really some strict rule governing the requirement to create 48 empty rows, creating them is really the absolute wrong way to go about doing it. Create them as needed, when you need to plug data into them.
I did this in mysql originally. Had trouble creating an SQLFiddle so i created an SQLite version as well.
There is an SQLFiddle. Squeezing all the stuff that follows into 8K, the SQLFiddle limit, was 'interesting' ;-/
The SQLite version, which is exactly the same apart from the 'create table' statements, i will make available if required. It will be a download of the database file, that understand, is the same across all machines. I can also provide the creation scripts if required.
Purpose:
The idea, i understand, is to display 'appointments' where the day is split into 48, 30 minute periods.
The requirement is to only record the actual appointments.
I pictured it as a small number of departments, recording appointments during the day when events will happen. In my example data, people visiting.
Here is the query to show appointments:
SELECT *
FROM department_appointments_view dav
WHERE dav.the_date = '2014-04-11'
AND dav.department_id = 1
AND dav.time_slot_id BETWEEN 12 AND 20;
Here is the sample output:
appointment_id department_id department_code the_date time_slot_id start_time attendee reason duration
-------------- ------------- --------------- ------------------- ------------ ---------- ----------------- --------------------- ----------
0 1 dept_01 2014-04-11 00:00:00 12 05:30:00 30
0 1 dept_01 2014-04-11 00:00:00 13 06:00:00 30
1 1 dept_01 2014-04-11 00:00:00 14 06:30:00 Catherine Tramell to see you 30
0 1 dept_01 2014-04-11 00:00:00 15 07:00:00 30
2 1 dept_01 2014-04-11 00:00:00 16 07:30:00 Buddy Ackerman to see them 30
0 1 dept_01 2014-04-11 00:00:00 17 08:00:00 30
0 1 dept_01 2014-04-11 00:00:00 18 08:30:00 30
3 1 dept_01 2014-04-11 00:00:00 19 09:00:00 Ivan Drago to visit someone else 30
0 1 dept_01 2014-04-11 00:00:00 20 09:30:00 30
So, the main table, where appointments are entered, is:
CREATE TABLE `department_appointments` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`department_id` int(11) NOT NULL,
`the_date` date NOT NULL,
`time_slot_id` int(11) NOT NULL,
`attendee` varchar(128) COLLATE utf8_unicode_ci NOT NULL,
`reason` varchar(128) COLLATE utf8_unicode_ci NOT NULL,
`duration` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `dept_fk` (`department_id`),
CONSTRAINT `dept_fk` FOREIGN KEY (`department_id`) REFERENCES `departments` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
This is the only table where appointment information is entered.
Sample data:
id department_id the_date time_slot_id attendee reason duration
------ ------------- ---------- ------------ ----------------------- --------------------- ----------
1 1 2014-04-11 14 Catherine Tramell to see you 30
2 1 2014-04-11 16 Buddy Ackerman to see them 30
3 1 2014-04-11 19 Ivan Drago to visit someone else 30
We need some supporting tables:
The departments table:
CREATE TABLE `departments` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`department_code` varchar(64) COLLATE utf8_unicode_ci NOT NULL,
`title` varchar(128) COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
Sample data:
id department_code title
------ --------------- -----------------------------
1 dept_01 Dept 01 - The Widget Makers
2 dept_02 Dept 02 - For Bar Workers
The calendar: This is just a table with dates in it. My test data was for april.
CREATE TABLE `the_calendar` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`the_date` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=31 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
Sample data:
id the_date
------ ---------------------
1 2014-04-01 00:00:00
2 2014-04-02 00:00:00
3 2014-04-03 00:00:00
4 2014-04-04 00:00:00
The read_only_time_slots table. This has 48 rows in it with start times. This table is read only and never updated or copied or anything.
CREATE TABLE `read_only_time_slots` (
`time_slot_id` int(11) NOT NULL,
`start_time` time NOT NULL,
`duration` int(11) NOT NULL,
PRIMARY KEY (`time_slot_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
Sample data:
time_slot_id start_time duration
------------ ---------- ----------
1 00:00:00 30
2 00:30:00 30
3 01:00:00 30
----------------------
You now need some queries to run this lot. Please be aware that we take advantage of the database engine to do cartesian products whenever it can. It will generate all the needed rows for us from just the above tables.
Now, to simplify the use of the information, i have used 'views'. Less confusion for me that way.
The views
The first is: time_slot_view
CREATE VIEW `time_slot_view` AS (
SELECT ts.time_slot_id AS time_slot_id,
ts.start_time AS start_time,
ts.duration AS duration
FROM read_only_time_slots ts
ORDER BY ts.time_slot_id ASC)
The next is: department_calendar_view
This returns empty timeslots for each department, for each day.
CREATE VIEW `department_calendar_view` AS (
SELECT
`d`.`id` AS `department_id`,
`d`.`department_code` AS `department_code`,
`c`.`the_date` AS `the_date`,
`tsv`.`time_slot_id` AS `time_slot_id`,
`tsv`.`start_time` AS `start_time`,
`tsv`.`duration` AS `duration`
FROM ((`the_calendar` `c`
JOIN `time_slot_view` `tsv`)
JOIN `departments` `d`)
ORDER BY `d`.`department_code`,`c`.`the_date`,`tsv`.`time_slot_id`)
Finally: there is the view that uses all the above:
The: department_appointments_view
This probably could be done as an outer join. I just used two queries and a union.
CREATE VIEW `department_appointments_view` AS
SELECT da.id AS appointment_id,
dcv.`department_id` AS department_id,
dcv.`department_code` AS department_code,
da.`the_date` AS the_date,
da.`time_slot_id` AS time_slot_id,
dcv.start_time AS start_time,
da.`attendee` AS attendee,
da.`reason` AS reason,
da.`duration` AS duration
FROM
`department_appointments` AS da
INNER JOIN department_calendar_view AS dcv
ON da.department_id = dcv.department_id
AND da.the_date = dcv.the_date
AND da.time_slot_id = dcv.time_slot_id
UNION
SELECT 0,
dcv.department_id,
dcv.`department_code` ,
dcv.the_date,
dcv.time_slot_id,
dcv.start_time,
'' AS attendee,
'' AS reason,
dcv.`duration`
FROM department_calendar_view AS dcv
WHERE NOT EXISTS (SELECT 1
FROM `department_appointments` AS da
WHERE da.department_id = dcv.department_id
AND da.the_date = dcv.the_date
AND da.time_slot_id = dcv.time_slot_id)
ORDER BY department_code, the_date, time_slot_id;