SQL Query to Join tables between dates - mysql

I have a MySQL database which records pager messages which has two tables, one is a table with messages and the other is a table with recipients, where the link between the tables is pagermessages.pcapcode = capcodes.bcapcode (a numerical address each pager message is sent to).
This works semi-well, and I typically use an inner or left join to show the various pager messages and who the messages are for.
The issue I have, is that the system is dynamic, and the pagers can be re-programmed so that address 1, which might have been for recipient EXAMPLE1 may eventually be changed to be for EXAMPLE 2.
This leads to the issue, that I want to store the messages in a historical form, but I need a way to be able to specify between a date range. My thought was to have in the Capcodes table a date & time (bStartDateTime) and a finish date & time (bFinishDateTime) and then in my query be able to use this to work out how to join the pager messages.
This is where I am stuck though, I would normally do something like SELECT * from PagerMessages INNER JOIN Capcodes ON PagerMessages.pCapcode = Capcodes.bCapcode however what I want to now be able to do is to do the same thing, but where the pager message date (pDateTime) is between bStartDateTime and bFinishDateTime (which could be blank) to link, but if it is outside of that then to not link.
So take this example
Pager Message Table
ID|pDateTime |pCapcode|pMessage
1 |2014-06-24 14:00|1 |This is a test message
2 |2014-06-24 15:00|1 |This is a test message
3 |2014-06-24 16:00|2 |This is a test message
Capcode Table
CapcodeID|pCapcode|Name |bStartDateTime |bFinishDateTime
1 |1 |Example 1|2014-06-24 14:00|2014-06-24 14:30
2 |1 |Example 2|2014-06-24 14:31|
3 |2 |Example 3|2014-06-24 14:31|
In the above examples, what I would like to be able to do is to join the tables so that I can get a table like the below:
ID|pDateTime |pCapcode|Name |pMessage
1 |2014-06-24 14:00|1 |Example 1|This is a test message
2 |2014-06-24 15:00|1 |Example 2|This is a test message
3 |2014-06-24 16:00|2 |Example 3|This is a test message
So you can see, basically because the capcode exists twice in the pCapcode table, it has taken the entry in which the date & time falls between, this way I can then have multiple entries of the same bCapcode in the capcode table, and then if they get changed just add the date it was changed in the Capcodes table, and add a new entry rather than having historical entries that are no longer accurate as I can't select them properly.
Sorry if it does not make too much sense, I can clarify if needed but I think i have covered most of what I am asking, which is really what kind of an SQL statement would allow me to do the above, as it is not a simple INNER / LEFT JOIN from what I can see.
Thanks!

You can still join them. Just make sure only use the finish time if it exists.
select a.id, a.pdatetime, b.name, a.pmessage
from PageMessage a
join Capcode b on a.pcapcode = b.pcapcode and b.bstartdatetime <= a.pdatetime
and (b.bfinishdatetime is null or a.pdatetime <= b.bfinishdatetime);

select ID,pdatetime,pcapcode,0 as 'Name',pmessage from Pager_Message
union
select ID,0,pcapcode,name,0 from Capcode

Related

Joining and selecting multiple tables and creating new column names

I have very limited experience with MySQL past standard queries, but when it comes to joins and relations between multiple tables I have a bit of an issue.
I've been tasked with creating a job that will pull a few values from a mysql database every 15 minutes but the info it needs to display is pulled from multiple tables.
I have worked with it for a while to figure out the relationships between everything for the phone system and I have discovered how I need to pull everything out but I'm trying to find the right way to create the job to do the joins.
I'm thinking of creating a new table for the info I need, with columns named as:
Extension | Total Talk Time | Total Calls | Outbound Calls | Inbound Calls | Missed Calls
I know that I need to start with the extension ID from my 'user' table and match it with 'extensionID' in my 'callSession'. There may be multiple instances of each extensionID but each instance creates a new 'UniqueCallID'.
The 'UniqueCallID' field then matches to 'UniqueCallID' in my 'CallSum' table. At that point, I just need to be able to say "For each 'uniqueCallID' that is associated with the same 'extensionID', get the sum of all instances in each column or a count of those instances".
Here is an example of what I need it to do:
callSession Table
UniqueCallID | extensionID |
----------------------------
A 123
B 123
C 123
callSum table
UniqueCallID | Duration | Answered |
------------------------------------
A 10 1
B 5 1
C 15 0
newReport table
Extension | Total Talk Time | Total Calls | Missed Calls
--------------------------------------------------------
123 30 3 1
Hopefully that conveys my idea properly.
If I create a table to hold these values, I need to know how I would select, join and insert those things based on that diagram but I'm unable to construct the right query/statement.
You simply JOIN the two tables, and do a group by on the extensionID. Also, add formulas to summarize and gather the info.
SELECT
`extensionID` AS `Extension`,
SUM(`Duration`) AS `Total Talk Time`,
COUNT(DISTINCT `UniqueCallID`) as `Total Calls`,
SUM(IF(`Answered` = 1,0,1)) AS `Missed Calls`
FROM `callSession` a
JOIN `callSum` b
ON a.`UniqueCallID` = b.`UniqueCallID`
GROUP BY a.`extensionID`
ORDER BY a.`extensionID`
You can use a join and group by
select
a.extensionID
, sum(b.Duration) as Total_Talk_Time
, count(b.Answered) as Total_Calls
, count(b.Answered) -sum(b.Answered) as Missed_calls
from callSession as a
inner join callSum as b on a.UniqueCallID = b.UniqueCallID
group by a.extensionID
This should do the trick. What you are being asked to do is to aggregate the number of and duration of calls. Unless explicitly requested, you do not need to create a new table to do this. The right combination of JOINs and AGGREGATEs will get the information you need. This should be pretty straightforward... the only semi-interesting part is calculating the number of missed calls, which is accomplished here using a "CASE" statement as a conditional check on whether each call was answered or not.
Pardon my syntax... My experience is with SQL Server.
SELECT CS.Extension, SUM(CA.Duration) [Total Talk Time], COUNT(CS.UniqueCallID) [Total Calls], SUM(CASE CS.Answered WHEN '0' THEN SELECT 1 ELSE SELECT 0 END CASE) [Missed Calls]
FROM callSession CS
INNER JOIN callSum CA ON CA.UniqueCallID = CS.UniqueCallID
GROUP BY CS.Extension

Joins are coming back with no rows selected

i'm having some trouble with trying to extract some data from several MySQL tables in a join statement.
My tables and attributes are:
appointment_end_time (table)
appointment_end_time_id (int)(pk)(ai)
appointment_end_date (datetime)
appointment_start_time (table)
appointment_date_id (int)(pk)(ai)
appointment_start_date (datetime)
instructor(table)
instructor_id (int)(pk)(ai)
firstname varchar(45)
lastname varchar(45)
appointment_timetable
appointment_timetable_id int(11) AI PK
instructor_id int(11) FK
appointment_date_id int(11) FK
appointment_end_time_id int(11) FK
SELECT a.appointment_timetable_id, i.instructor_id, ad.appointment_start_date, aet.appointment_end_date
FROM db12405956.appointment_timetable a
JOIN instructor i on i.instructor_id = a.instructor_id
JOIN appointment_start_time ad on ad.appointment_date_id = a.appointment_date_id
JOIN appointment_end_time aet on aet.appointment_end_time_id = a.appointment_end_time_id
ORDER BY a.appointment_timetable_id;
However, this code brings back no rows selected when executed so i'm wondering what i'm doing wrong, any help will be much appreciated
Sample rows:
(appointment_end_time)
appointment_end_time_id appointment_end_date
1 2016-12-26 14:00:00
2 2016-12-24 13:00:00
3 2016-12-26 13:00:00
(appointment_start_time)
appointment_date_id appointment_start_date
1 2016-12-26 15:00:00
2 2016-12-24 16:00:00
3 2016-12-26 15:30:00
instructor_id firstname lastname
1 Sasha Thompson
2 Laura Robinson
3 John Walters
appointment_timetable
appointment_timetable_id instructor_id appointment_date_id appointment_end_time_
1 Blank Blank Blank
2 Blank Blank Blank
3 Blank Blank Blank
What you need is to learn how to diagnose the problem yourself. It is a common problem that a query doesn't return the expected results and you should understand how to break things down to find the issue.
Let's start with your query:
SELECT a.appointment_timetable_id, i.instructor_id, ad.appointment_start_date, aet.appointment_end_date
FROM db12405956.appointment_timetable a
JOIN instructor i on i.instructor_id = a.instructor_id
JOIN appointment_start_time ad on ad.appointment_date_id = a.appointment_date_id
JOIN appointment_end_time aet on aet.appointment_end_time_id = a.appointment_end_time_id
ORDER BY a.appointment_timetable_id;
What you do to break it down is start with the first table and then add the joins (and where conditions although you don't have any here), one at a time until the data problem appears. I find this easiest to do by using select * or select top 1 * (Or top 10 as I usually prefer to see more than one record) instead of the field list because then you don't have to look for the fields that are associated with joins you haven't added in yet.
So start with
SELECT top 10 *
FROM db12405956.appointment_timetable a
Then try
SELECT top 10 *
FROM db12405956.appointment_timetable a
JOIN instructor i on i.instructor_id = a.instructor_id
Then
SELECT top 10 *
FROM db12405956.appointment_timetable a
JOIN instructor i on i.instructor_id = a.instructor_id
JOIN appointment_start_time ad on ad.appointment_date_id = a.appointment_date_id
Finally
SELECT top 10 *
FROM db12405956.appointment_timetable a
JOIN instructor i on i.instructor_id = a.instructor_id
JOIN appointment_start_time ad on ad.appointment_date_id = a.appointment_date_id
JOIN appointment_end_time aet on aet.appointment_end_time_id = a.appointment_end_time_id
ORDER BY a.appointment_timetable_id;
At some point you will see where the records fell out and that is the location of the problem. Then you might need to look at the fields you are joining on and the data in them in your data sets to see why they are not returning any matches. For instance, if you are joining on dates, they may be stored as dates in one table and as varchar in another and date "01/01/2016' is not equal to 'Jan 1, 2016' or sometimes the column has some sort of prefix or suffix not in the other table. Something like PR2345 in one table and 2345 in the other. Sometimes the query is correct and no rows genuinely meet the conditions. This could be because the data is not fully populated yet (think writing a report for a system that is not live yet, no data on completed actions because none have completed yet.) or because the requirement was wrong in some of its assumptions or because there should be no matching records. It could even be a bug in the data entry.
Depending on the nature of the problem, you might need to return all the records or only use select top 1 (since all records are disappearing). Using SELECT * this way will help when you are returning too many or duplicate records as well as sometimes is is the fields not being returning that affect the results set. Note that I am not saying to use SELECT * in your final result set, it is only being used as a diagnostic tool here.
In your case, the problem looks as if it is in the first table. There are blanks for instructor ID and the other fields in your sample, so there is nothing to join on. (You only gave a sample so the rest of the table may not be like this.) If this is a case where the data is not there yet due to the feature that would add it not yet being live, then you can test your query only by adding test data to the table. Be sure to delete this data after you have finished unit testing. If the data should have been there, then you need to look at the insert from the application for a bug.

MS-Access 2010 DELETE Query LEFT JOIN

There's a lot of these issues floating around the net with many solutions, but I'm really struggling with this one.
I have a table [BaseHrs] which looks a little like this -
p_ID b_Person WeekNos HrsRequired
1 A 2016-39 10
1 A 2016-40 10
1 A 2016-41 10
1 A 2016-42 10
1 B 2016-39 11
1 B 2016-40 11
1 B 2016-41 12
1 B 2016-42 09
The table continues with different p_ID, people & week numbers. There is no Primary Key and no indexing. This table also has no relationship with any other table.
It is populated from a Query connected to another table as well as a form for the [HrsRequired] field.
Scenario -
Project 1 (p_ID=1) has now been brought forward by two weeks and BaseHrs table no longer needs row for [WeekNos] 2016-41 & 2016-42.
I initially use a query to show which weeks the project is now running on (qry_SelectNewDates).
I have started my delete query by first creating a Select query which looks like this -
SELECT BaseHrs.*
FROM BaseHrs
LEFT JOIN qry_SelectNewDates
ON BaseHrs.WeekNos = qry_SelectNewDates.WeekNos
WHERE (((BaseHrs.p_ID)=[Forms]![frm_Projects]![p_ID])
AND ((BaseHrs.WeekNos) Not In ([qry_SelectNewDates].[WeekNos])));
This works as intended.
Converting that into a delete query produces an error though. Delete Query -
DELETE BaseHrs.*, BaseHrs.p_ID, BaseHrs.WeekNos
FROM BaseHrs
LEFT JOIN qry_SelectNewDates
ON BaseHrs.WeekNos = qry_SelectNewDates.WeekNos
WHERE (((BaseHrs.p_ID)=[Forms]![frm_Projects]![p_ID])
AND ((BaseHrs.WeekNos) Not In ([qry_SelectNewDates].[WeekNos])));
Error message -
Could not delete from specified tables.
I realise that there is often an issue when trying to delete records in this way. I've tried using it with just 'DELETE.*' in the first line without luck.
I have also made an attempt at a nested Query, but I just can't figure out how to construct it. Any guidance?
**********EDIT**********
With advice from #SunKnight0 I have added a primary key to my BaseHrs table and got this query -
DELETE *
FROM BaseHrs
WHERE b_pKey IN
(SELECT BaseHrs.b_pKey
FROM BaseHrs
LEFT JOIN qry_SelectNewDates
ON (BaseHrs.WeekNos = qry_SelectNewDates.WeekNos)
WHERE (((BaseHrs.p_ID)=[Forms]![frm_Projects]![p_ID])
AND ((BaseHrs.WeekNos) Not In ([qry_SelectNewDates].[WeekNos]))));
This query appears to work but takes a huge amount of time to run. Is that as good as it gets?

How can I find days between different paired rows?

I've been racking my brain about how to do this in one query without PHP code.
In a nutshell, I have a table that records email activity. For the sake of this example, here is the data:
recipient_id activity date
1 delivered 2011-08-30
1 open 2011-08-31
2 delivered 2011-08-30
3 delivered 2011-08-24
3 open 2011-08-30
3 open 2011-08-31
The goal: I want to display to users a single number that tells how many recipients open their email within 24 hours.
E.G. "Users that open their email within 24 hours: 13 Readers"
In the case of the sample data, above, the value would be "1". (Recipient one was delivered an email and opened it the next day. Recipient 2 never opened it and recipient 3 waited 5 days.)
Can anyone think of a way to express the goal in a single query?
Reminder: In order to count, the person must have a 'delivered' tag and at least one 'open' tag. Each 'open' tag only counts once per recipient.
** EDIT ** Sorry, I'm using MySQL
Here is a version in mysql.
select count(distinct recipient_id)
from email e1
where e1.activity = 'delivered'
and exists
(select * from email e2
where e1.recipient_id = e2.recipient_id
and e2.activity = 'open'
and datediff(e2.action_date,e1.action_date) <= 1)
The basic principle is that you want to find a delivered row for a recipient that also has an open within 24 hours.
The datediff() is a good way to do the date arithmetic in mysql -- other dbs will vary on exact methods for this step. The rest of the sql will work anywhere.
SQLFiddle here: http://sqlfiddle.com/#!2/c9116/4
Untested, but should work ;) Don't know which SQL dialect you use, so I've used TSQL DATEDIFF function.
select distinct opened.recipient_id -- or count(distinct opened.recipient_id) if you want to know number
from actions as opened
inner join actions as delivered
on opened.recipient_id = delivered.recipient_id and delivered.activity = 'delivered'
where opened.activity = 'open' and DATEDIFF(day, delivered.date, opened.date) <= 1
Edit: I'd confused opened with delivered - now replaced.
Assumptions: MySql, table is called "TABLE"
Ok, I am not 100% on this, because I don't have a copy of the table to run it against, but I think that you could do something like this:
SELECT COUNT(DISTINCT t1.recipient_id) FROM TABLE t1
INNER JOIN TABLE t2 ON t1.recipient_id = t2.recipient_id AND t1.activity != t2.activity
WHERE t1.activity in ('delivered', 'open') AND t2.activity in ('delivered', 'open')
AND ABS(DATEDIFF(t1.date, t2.date)) = 1
Basically, you are joining a table onto itself, where the activities don't match, but recipient_ids do, and the status is either 'delivered' or 'open'. What you would end up getting, is a result that looks like this:
1 delivered 2011-08-30 1 open 2011-08-31
You are then doing a diff between the two dates (with an absolute value, because we don't know which order they will be in) and making sure that it is equal to 1 (or 24 hours).

Very complex Group By / Unique / Limit by SQL-command

I actually don't even know how to call this :P, but...
I have one table, let's call it "uploads"
id owner date
-----------------------------
0 foo 20100101120000
1 bar 20100101120300
2 foo 20100101120400
3 bar 20100101120600
.. .. ..
6 foo 20100101120800
Now, when I'ld do something like:
SELECT id FROM uploads ORDER BY date DESC
This would result in:
id owner date
-----------------------------
6 foo 20100101120800
.. .. ..
3 bar 20100101120600
2 foo 20100101120400
1 bar 20100101120300
0 foo 20100101120000
Question: Nice, but, I want to go even further. Because now, when you would build a timeline (and I did :P), you are 'spammed' by messages saying foo and bar uploaded something. I'ld like to group them and return the first result with a time-limit of '500' at the date-field.
What kind of SQL-command do I need that would result in:
id owner date
-----------------------------
6 foo 20100101120800
3 bar 20100101120600
0 foo 20100101120000
Then, after that, I can perform a call for each record to get the associative records in a timeframe of 5 minutes (this is an exmaple for id=6):
SELECT id FROM uploads WHERE date>=20100101120800-500 ORDER BY date DESC
Does anyone now how I should do the first step? (so limiting/grouping the results)
(btw. I know that when I want to use this, I should convert every date (YmdHis=60) to Unix-time (=100), but I don't need the 5 minutes to be exactly 5 minutes, they may be a minute less sometimes...)
I'm not quite clear on the result you are trying to get, even with your examples. Perhaps something with rounding and group by.
SELECT max(id) max_id,owner, (ROUND(date/500)*500) date_interval, max(date) date
FROM uploads GROUP BY date_interval,owner
You may want to use FLOOR or CEILING instead of ROUND, depending on what you want.
Standard SQL doesn't deal with intervals very well.
You are going to need to do a self-join of the table to compare dates of different tuples.
That way, you can easily find all pairs of tuples of which the dates are no more than 500 apart.
However, you really want to cluster the dates in sets no more than 500 apart - and that can't be expressed in SQL at all, as far as I know.
What you can do is something quite similar: split the total time interval into fixed 500-unit ranges, and then cluster all tuples in the table based on the interval they're in. For that, you first need a table or query result with the start times of the intervals; this can be created using a SQL query on your table and a function that either "rounds off" a timestamp to the starting time in its interval, or computes its interval sequence number. Then as a second step you can join the table with that result to group its timestamps according to their corresponding start time. I can't give the SQL because it's DBMS-dependent, and I certainly can't tell you if this is the best way of accomplishing what you want in your situation.
Use an inline view? e.g. something like
SELECT u1.*
FROM uploads u1,
(SELECT date
FROM uploads u2
WHERE u2.owner='foo') datum_points
WHERE u1.date BETWEEN datum_points.date
AND DATE_ADD(datum_points.date INTERVAL 5 MINUTES)
should return all the posts made within 5 minutes of 'foo' making a post.