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
I have a system that is used to log kids' their behavior. If a child is naughty it is logged as negative and if it has a well behaviour it is logged as positive.
For instance - if a child is rude it gets a 'Rude' negative and this is logged in the system with minus x points.
My structure can be seen in this sqlfiddle - http://sqlfiddle.com/#!9/46904
In the users_rewards_logged table, the reward_id column is a foreign key linked to either the deductions OR achievements table depending on the type of column.
If type is 1 is a deduction reward, if the type value is 2 is a achievement reward.
I basically want a query to list out something like this:
+------------------------------+
| reward | points | count |
+------------------------------+
| Good Work | 100 | 1 |
| Rude | -50 | 2 |
+------------------------------+
So it tallys up the figures and matches the reward depending on type (1 is a deduction, 2 is a achievement)
What is a good way to do this, based on the sqlfiddle?
Here's a query that gets the above desired results:
SELECT COALESCE(ua.name, ud.name) AS reward,
SUM(url.points) AS points, COUNT(url.logged_id) AS count
FROM users_rewards_logged url
LEFT JOIN users_deductions ud
ON ud.deduction_id = url.reward_id
AND url.type = 1
LEFT JOIN users_achievements ua
ON ua.achievement_id = url.reward_id
AND url.type = 2
GROUP BY url.reward_id, url.type
Your SQLFiddle had the order of points and type in the wrong order for the table users_rewards_logged.
Here's the fixed SQLFiddle with the result:
reward points count
Good Work 100 1
Rude -50 2
Although eggyal is correct--this is rather bad design for your data--what you ask can be done, but requires a UNION clause:
SELECT users_achievements.name, users_rewards_logged.points, COUNT(*)
FROM users_rewards_logged
INNER JOIN users_achievements ON users_achievements.achievement_id = users_rewards_logged.reward_id
WHERE users_rewards_logged.type = 2
UNION
SELECT users_deductions.name, users_rewards_logged.points, COUNT(*)
FROM users_rewards_logged
INNER JOIN users_deductions ON users_deductions.deduction_id = users_rewards_logged.reward_id
WHERE users_rewards_logged.type = 1
GROUP BY 1, 2
There's no reason NOT to combine the achievements and deductions tables and just use non-conflicting codes. If you combined the tables, then you would no longer need the UNION clause--your query would be MUCH simpler.
I noticed that you have two tables (users_deductions and users_achievements) that defines the type of reward. As #eggyal stated, you are violating the principle of orthogonal design, which causes the lack of normalization of your schema.
So, I have combined the tables users_deductions and users_achievements in one table called reward_type.
The result is in this fiddle: http://sqlfiddle.com/#!9/813d5/6
I'm trying to do a JOIN like you see below. I only want records that have at least X email addresses in the property_res table. When I change the acount value from 10 to 20 for example the returned results stay at 949 records. This should decrease dramatically as there should be alot less matches where r.EmailAddress is found 20 times. Is there a limitation to using COUNT on a varchar data type? What is the best way to achieve this?
SELECT
r.FirstName AS ag_fname,
r.LastName AS ag_lname,
r.EmailAddress AS ag_email,
COUNT(r.EmailAddress) AS `acount`
FROM property_res e
LEFT JOIN ActiveAgent_Matrix r
ON e.ListAgentMLSID=r.MemberNumber
WHERE e.ListPrice >= 50000
GROUP BY r.EmailAddress
HAVING acount >=20
A sample output of the data shows a weird value for acount as I'd think it would be the count of the email address but they all are the same?
ag_fname | ag_lname | ag_email | acount
Jane | Doe1 | jdoe1#doe.com | 3390
Jane | Doe3 | jdoe3#doe.com | 3390
Jane | Doe4 | jdoe4#doe.com | 3390
Jane | Doe5 | jdoe5#doe.com | 3390
What's happening is your join condition is not specific enough (or in fact multiple emails can be associated with the same id, or vice versa, in which case you GROUP BY is not specific enough). I suspect it is the former and that your result set is exploding. Not quite a Cartesian join, but similar.
Try to troubleshoot with the following two queries:
SELECT
r.EmailAddress,
COUNT(*)
FROM property_res e
LEFT JOIN ActiveAgent_Matrix r
ON e.ListAgentMLSID=r.MemberNumber
GROUP BY r.EmailAddress
HAVING COUNT(*) > 1;
SELECT
e.ListAgentMLSID,
COUNT(*)
FROM property_res e
LEFT JOIN ActiveAgent_Matrix r
ON e.ListAgentMLSID=r.MemberNumber
GROUP BY e.ListAgentMLSID
HAVING COUNT(*) > 1;
One (or both) of these result sets will be non empty. That's important because it means that this join condition: ON e.ListAgentMLSID=r.MemberNumber is not specific enough. Either there are multiple emails per ListAgentMLSID or there are multiple ListAgentMLSID's per email address... or both.
To trouble shoot this, I'd start by trying to identify where the "multiple X's per Y's" are. The queries above should actually help you do that. The first one will identify emails associated with multiple IDs. The second will help you identify IDs associated with multiple emails. The question you need to ask yourself is, should multiple emails be associated with any given id? Or should multiple ids be associated with any given email? If that's permissible, change your GROUP BY. If it's not, change your join condition.
It may be as simple as joining on id and email.... but if it is not, then you either need to group by the email as well (as suggested above in the comments... this is fine if indeed multiple emails should be permitted to have an association with an id, or vice versa) or you need to add an additional join condition that's specific enough to prevent data that shouldn't be joined, joined.
Hope this helps.
These are my tables:
portal_users(id, first_name, last_name, email, ...,)
courses(id, name, location, capacity, ...,)
feedback_questions(id, question, ...,)
feedback_answers(id, answers, IDquestion, IDuser, IDcourse)
I want to do a view with this:
course | first_name | last_name | IDuser | IDcourse | question_1 | answer_1 | question_2 | answer_2
So far
CREATE VIEW feedback_answers_vw
as
SELECT
fa.id,
fa.answer,
fq.question,
pu.first_name,
pu.last_name,
fa.IDuser,
fa.IDcourse
FROM feedback_answers fa
INNER JOIN feedback_questions fq
ON fa.IDquestion = fq.id
INNER JOIN portal_users pu
ON fa.IDuser = pu.id
INNER JOIN courses cu
on fa.IDcourse = cu.id
GROUP BY
fa.IDcourse, fa.IDuser
This just display one question and its answers, but not all the questions that belong to the same course and user.
I could hardcode this with something like this in the SELECT statement
SELECT
fa.id,
(select question
from feedback_questions
where id = 1) as question_1,
(select question
from feedback_questions
where id = 2) as question_2,
(select question
from feedback_questions
where id = 3) as question_3,
pu.first_name,
pu.last_name,
fa.IDuser,
fa.IDcourse
But I want to do it in the right way, so I won't be changing the code everytime that a question is added.
Edit:
This is data example of my tables:
**Portal users:**
1, tom, hanks, tom_hanks#example.com, ...,
2, steven, spielberg, steven#example.com, ...,
**Courses:**
1, quality, california, 30
2, information technologies, texas, 24
**Questions:**
1, How did you find the course?, ...,
2, Do you want purchase order?, ...,
**Answers:**
1, Internet, 1, 1, 1
2, yes, 2, 1, 1
3, TV, 1, 2, 1,
4, no, 2, 2, 1,
5, Internet, 1, 1, 2
6, yes, 1, 1, 2
This are data example I want to display in the view:
course|first_name|last_name|IDuser|IDcourse|Question_1|Answer_1|Question_2|Answer_2
----------------------------------------------------------------------------------
quality | tom | hanks | 1 | 1 | How did you find the course? | Internet | Do you want purchase order? | yes
quality | steven | spielberg | 2 | 1 | How did you find the course? | TV | Do you want purchase order? | no
Information technologies | tom | hanks | 1 | 2 How did you find the course? | Internet | Do you want purchase order? | yes
I dont know if you would actually need it as a view, and here is a sample query for it.
If you look at the first part of the where clause, it is based on a minimum of question ID = 1. I then join that to the course table and portal_user table to get those fields. Since the feedback answers is the first table, we can immediately get that answer, but also join to the questions table to get the question.
So now, how to simply expand this query for future questions? Notice the LEFT-JOINs right after that for the feedback answers AGAIN, but this time as an alias "fa2" (feedback_answers2), and the JOIN clause is based on the same user, same course, but only for question ID = 2. This then joins to the questions table on the fa2.questionID to the questions table (alias fq2). And then another exact same setup but for question 3. So here, I am doing nothing but using the same tables, but just different aliases. They all start with question 1, and if there is a question 2, get it... if a question 3, get it too, expand as needed, no group by, etc.
So now if you only cared about a single course you were interested, just add that to the outermost WHERE clause, nothing else needs to change. Get 10 questions, copy/paste the LEFT-JOIN components for the corresponding question IDs.
SELECT
c.name as course,
pu.first_name,
pu.last_name,
fa1.IDUser,
fa1.IDCourse,
fq1.question as question1,
fa1.answers as answer1,
fq2.question as question2,
fa2.answers as answer2,
fq3.question as question3,
fa3.answers as answer3
from
feedback_answers fa1
JOIN courses c
ON fa1.IDCourse = c.id
JOIN portal_users pu
ON fa1.IDUser = pu.id
JOIN feedback_questions fq1
ON fa1.IDquestion = fq1.id
LEFT JOIN feedback_answers fa2
ON fa1.IDUser = fa2.IDUser
AND fa1.IDCourse = fa2.IDCourse
AND fa2.id = 2
LEFT JOIN feedback_questions fq2
ON fa2.IDquestion = fq2.id
LEFT JOIN feedback_answers fa3
ON fa1.IDUser = fa3.IDUser
AND fa1.IDCourse = fa3.IDCourse
AND fa2.id = 3
LEFT JOIN feedback_questions fq3
ON fa3.IDquestion = fq3.id
where
fa1.id = 1
After some sample data was posted, it is clear a pivot table (in access/excel known as a 'crosstab') is needed here. We could hard code a select with some case statements to cover a fixed number of questions, but that solution would have to be revisited each time a question was added or removed. #drapp has such a solution below.
However, what we really want is the number of columns returned by the view to grow and shrink with the number of questions. Unfortunately mysql doesn't have any built in functions to help us build one. The best solution I found online is http://www.artfulsoftware.com/infotree/qrytip.php?id=523. The author proposes building the sql select statement dynamically then executing it. It is actually a pretty elegant solution.
Faced with this requirement, I wouldn't do this work in SQL. The problem I foresee is that as the numbers of questions rises, more and more columns will be returned; your view would quickly slow down to a crawl, and eventually stop working altogether. I would try to transform the data outside of sql, perhaps in an etl tool, application server, or BI tool.
Or, if I had the ability to (and I never do), I would switch database engines. Here are three solutions from other engines that provide tools for creating pivot tables:
Oracle: http://www.oracle.com/technetwork/articles/sql/11g-pivot-097235.html
Postgres: http://www.postgresql.org/docs/9.1/static/tablefunc.html
SQL Server: http://blogs.msdn.com/b/spike/archive/2009/03/03/pivot-tables-in-sql-server-a-simple-sample.aspx
We just talked about views in my DB Design class and my Professor said that you can't use a join in a VIEW statement. I can't recall why not but your issues looks familiar.
Apologies for the rubbish question title, but it's a bit tricky to summarise my requirement into a single line. I usually don't have an issue with MySQL JOINs but this one is throwing me.
I'm building a training feedback system and for one feature would like to display a list of all available workshops in the database, which workshops a given delegate has been assigned to and whether any feedback has been submitted by that delegate for those assigned workshops.
I could do this in a couple of queries, but I'm trying to do something a bit more elegant with a single query.
The pertinent details of my database structure:
WORKSHOPS table
id: INT
name: TINYTEXT
DELEGATES table
id: INT
name: TINYTEXT
FEEDBACK table
delegate_id: INT
workshop_id: INT
feedback: TEXT
DELEGATES_X_WORKSHOPS table
delegate_id: INT
workshop_id: INT
delegate_id and workshop_id in the tables are Foreign Keys to the DELEGATES and WORKSHOPS tables.
As any given delegate can be assigned to multiple workshops, I'm using the DELEGATES_X_WORKSHOPS table as a cross-referencing table so I can quickly search for who is assigned to any given workshop or which workshops any given delegate is assigned to.
However, I've tried LEFT JOINing a couple of different ways and I can't get a full list of workshops on the left and matches (if they exist) on the right for a given delegate_id.
Example data
Delegate Ross has delegate_id = 1
Registered workshops are
C++
PHP
ASP.NET
HTML5
JavaScript
Ross is assigned to PHP, HTML5 and JavaScript
Question 1 is this: how do I return the following for delegate_id=1:
[workshop] | [assigned]
C++ | null
PHP | TRUE
ASP.NET | null
HTML5 | TRUE
JavaScript | TRUE
(it doesn't matter right now what goes into column B, I just want a null if a particular delegate_id hasn't been assigned to a workshop).
I've used this:
SELECT
workshops.name,
delegates_x_workshops.delegate_id
FROM
workshops
LEFT JOIN
delegates_x_workshops
ON
workshops.id=delegates_x_workshops.workshop_id
WHERE
delegates_x_workshops.delegate_id=1
However I'm only returning the 3 rows where delegate_id=1, not 5 rows for all workshops.
Question 2 is a bit more involved:
Taking question 1 as a base, how would I work column C to display if feedback has been left for a workshop that Ross has been assigned to?
[workshop] | [assigned] | [givenfeedback]
C++ | null | null
PHP | TRUE | TRUE
ASP.NET | null | null
HTML5 | TRUE | null
JavaScript | TRUE | TRUE
Thanks in advance to anybody who makes it this far and has a clue what I'm blithering about. As I said, I could rattle through this with a few different queries, but I'm trying to keep things elegant.
No doubt half of this will need clarification, so ask any questions.
Thanks
For question 1, you need to move the where condition into the on clause. It is turning the left outer join into an inner join because non-matching rows have NULL values:
SELECT w.name, dxw.delegate_id
FROM workshops w LEFT JOIN
delegates_x_workshops dxw
ON w.id = dxw.workshop_id and
dxw.delegate_id = 1;
For the second question, I think this is what you want:
SELECT w.name,
(case when max(w.name = 'Ross') > 0 then 'True' end) as Assigned,
(case when count(f.workshop_id) > 0 then 'True' end) as Feedback
FROM workshops w LEFT JOIN
delegates_x_workshops dxw
ON w.id = dxw.workshop_id and
dxw.delegate_id = 1 LEFT JOIN
delegates d
on d.id = dxw.delegate_id LEFT JOIN
feedback f
on f.workshop_id = w.id
GROUP BY w.name;
For reference, here's my final query:
SELECT DISTINCT
workshops.id AS wid,
workshops.name AS workshop,
(delegates_x_workshops.delegate_id IS NOT NULL) AS assigned,
(initial_feedback.delegate_id IS NOT NULL
OR
ongoing_feedback.delegate_id IS NOT NULL) AS hasfeedback
FROM
workshops
LEFT JOIN
delegates_x_workshops
ON
workshops.id = delegates_x_workshops.workshop_id
AND
delegates_x_workshops.delegate_id = 1
LEFT JOIN
initial_feedback
ON
workshops.id = initial_feedback.workshop_id
AND
initial_feedback.delegate_id = 1
LEFT JOIN
ongoing_feedback
ON
workshops.id = ongoing_feedback.workshop_id
AND
ongoing_feedback.delegate_id = 1
ORDER BY
workshop ASC
For every workshop in the WORKSHOPS table, I'll get the id and name of the workshop, 1 or 0 if a given delegate_id is assigned and 1 or 0 if feedback of either type (I have 2 kinds) has been left for that workshop.
Scary to think that all I was missing was an AND condition on my LEFT JOIN.
Thanks again Gordon!