MySQL Relational Select Issue - mysql

I'm trying to select all jobs that a user has been added to, but exclude jobs that they have declined. I've been looking into NOT EXISTS, but I haven't been able to exclude declined jobs with that.
Table: declined
+---------+---------+---------+----------+
| id | job_id | user_id | declined |
+---------+---------+---------+----------+
| 15 | 223 | 25 | 1 |
| 100 | 156 | 50 | 1 |
| 125 | 651 | 60 | 1 |
+---------+---------+---------+----------+
Current query. Selects all jobs they've been added to, as well as jobs they have declined.
SELECT
job.*, applicants.*, declined.*
FROM job
JOIN applicants ON job.job_id = applicants.job_id
LEFT OUTER JOIN declined ON job.job_id = declined.job_id
WHERE applicants.user_id = '" . $userId . "' AND applicants.recruited = 1
Failed attempt. This obviously does not select any records.
SELECT
job.*, applicants.*, declined.*
FROM job
JOIN applicants ON job.job_id = applicants.job_id
LEFT OUTER JOIN declined ON job.job_id = declined.job_id
WHERE applicants.user_id = '" . $userId . "' AND applicants.recruited = 1 AND declined.declined = 0

you can use NOT IN to exclude the declined jobs:
SELECT
job.*, applicants.*
FROM job
JOIN applicants ON job.job_id = applicants.job_id
WHERE applicants.user_id = '" . $userId . "' AND applicants.recruited = 1
AND job.job_id not in (
SELECT DISTINCT job_id from declined where user_id = '" . $userId . "' and declined = 1
)
One more thing - not sure what language you're using, but you shouldn't set $userId like that, use a parameterized query, it's much safer than building the query like that.

If you want to use NOT EXISTS
SELECT
job.*, applicants.*
FROM job, applicants
WHERE job.job_id = applicants.job_id
AND NOT EXISTS
(SELECT 1 FROM declined WHERE job.job_id = declined.job_id AND declined.user_id = applicants.user_id AND declined.declined = 1)
AND applicants.user_id = '" . $userId . "'
AND applicants.recruited = 1

Related

How to SUM Duplicate COUNT values in Mysql

I have this code:
Const SQLExpression As String = "SELECT site.siteid AS 'SITE ID', site_name AS 'SITE', COUNT(DISTINCT date) AS 'Attendance', 75 * (SELECT COUNT(DISTINCT date) AS 'Attendance') AS 'Total' FROM record LEFT JOIN learner on record.idNumber = learner.idNumber LEFT JOIN site ON learner.siteid=site.siteid WHERE MONTH(date) = MONTH(CURRENT_DATE()) AND YEAR(date) = YEAR(CURRENT_DATE()) GROUP BY record.idNumber "
And it returns the following data: Table
I want to to get the following:
+----------+--------------------------------+
| SITE ID | SITE | Attendance |
+-------------------------------------------+
| 314 | Broad Market1 | 34 |
| 254 | Catherine Booth.. | 36 |
| 289 | Ceter for Entrep..| 27 |
| 330 | Climamark Morem.. | 7 |
+-------------------------------------------+
I have tried the following code:
"SELECT siteid AS 'SITE ID', site_name AS 'SITE', SUM(DISTINCT Attendance) AS 'Attendance' FROM (SELECT COUNT(DISTINCT record.date)AS 'Attendance' FROM record LEFT JOIN learner on record.idNumber = learner.idNumber LEFT JOIN site ON learner.siteid=site.siteid WHERE MONTH(date) = MONTH(CURRENT_DATE()) AND YEAR(date) = YEAR(CURRENT_DATE()) GROUP BY record.idNumber) As Attendance, site GROUP BY siteid "
Am getting the following data: TableResult
Assuming that in the table site you have id for primary key you should use a inner join INNER JOIN site Attendance.siteid = site.id (and not a cross join (Attendance, site without where) )
"SELECT siteid AS 'SITE ID'
, site_name AS 'SITE'
, SUM(DISTINCT Attendance) AS 'Attendance'
FROM (
SELECT COUNT(DISTINCT record.date) AS 'Attendance'
FROM record
LEFT JOIN learner on record.idNumber = learner.idNumber
LEFT JOIN site ON learner.siteid=site.siteid
WHERE MONTH(date) = MONTH(CURRENT_DATE())
AND YEAR(date) = YEAR(CURRENT_DATE())
GROUP BY record.idNumber ) As T_Attendance
INNER JOIN site T_Attendance.siteid = site.id
GROUP BY siteid "

Decrease the number of joins to the same parent table in mysql

I have an user table containing user info and a signature table which maps to user table with userid, as shown below
users
userid | firstname | lastname
1 | John | P
2 | Pete | C
3 | Tim | D
signs
doneBy | CheckedBy | VerifiedBy
1 | 2 | 3
Is there a better way to do this instead of multiple joins as below,
considering I would have 6 such joins
select
concat(usr1.firstname, ' ', usr1.lastname) as doneby,
concat(usr2.firstname, ' ', usr2.lastname) as checkedby,
concat(usr3.firstname, ' ', usr3.lastname) as verifiedby
from signs sgn
join users usr1 on ( usr1.userid = sgn.doneBy)
join users usr2 on ( usr2.userid = sgn.checkedBy)
join users usr3 on ( usr3.userid = sgn.verifiedBy)
You can do this with conditional aggregation using CASE EXPRESSION like this:
SELECT MAX(CASE WHEN t.doneBy = t.userId THEN t.full_name END) AS doneBy,
MAX(CASE WHEN t.CheckedBy = t.userId THEN t.full_name END) AS doneBy,
MAX(CASE WHEN t.VerifiedBy = t.userId THEN t.full_name END) AS doneBy
FROM (
SELECT u.userid,concat(u.firstname, ' ', u.lastname) as full_name,u.*
FROM signs s
INNER JOIN users u
ON(s.userid IN(s.doneBy,s.CheckedBy,s.VerifiedBy))) t
GROUP BY t.doneBy,t.checkBy,t.VerifiedBy

slow count query - explain shows temp and filesort

have following query which runs slow,
It produces a list of ACTION records with "action.typeid=1"
and also counts if an ACTION record with "typeid=2" exists.
It is this count which is slowing things - it uses temporary and filesort!!!
can you help me find out how to improve.
EXPLAIN
SELECT
action.actionid
FROM
ACTION
INNER JOIN EVENT
ON action.eventid = event.eventid
LEFT JOIN
(SELECT
COUNT(1),
action.eventid
FROM
ACTION
WHERE (action.typeid = '2')
GROUP BY action.eventid) AS act
ON act.eventid = event.eventid
WHERE actiondate2 BETWEEN 20130601
AND 20131031
AND event.siteid = 1
AND action.typeid = 1
The following indexes exist
CREATE INDEX idx_cusid ON `event` (cusid);
CREATE INDEX idx_actiontypeid ON `action` (typeid);
CREATE INDEX idx_actioneventid ON `action` (eventid);
CREATE INDEX idx_actiondate ON `action` (actiondate2);
CREATE INDEX idx_eventsiteid ON `event` (siteid);
Sir, the requirements in the question are still unclear to me.
I am going to explain my confusion using examples.
Please take a look at a simple demo: http://sqlfiddle.com/#!2/19f52c/6
This demo contains a simplified (for a sake of clarity) database structure and queries.
The query from the question (the first query in the demo) returns the following results:
SELECT action.actionid
FROM ACTION
INNER JOIN EVENT
ON action.eventid = event.eventid
LEFT JOIN
(SELECT
COUNT(1),
action.eventid
FROM
ACTION
WHERE (action.typeid = '2')
GROUP BY action.eventid) AS act
ON act.eventid = event.eventid
WHERE -- actiondate2 BETWEEN 20130601 AND 20131031 AND
event.siteid = 1
AND action.typeid = 1
;
+ ------------- +
| actionid |
+ ------------- +
| 1 |
| 3 |
| 5 |
+ ------------- +
Hovever, in the above query the subquery with alias ACT is simply ... useless.
The query executes this subquery (consuming time and server resources), then ... ignores its results, just throws them away.
The above query is equivalent to the below query (the second query in the demo) - it returns identical results as the query from the question, but without using the subquery (saving time and resources, therefore will perform much better):
SELECT action.actionid
FROM
ACTION
INNER JOIN EVENT
ON action.eventid = event.eventid
WHERE -- actiondate2 BETWEEN 20130601 AND 20131031 AND
event.siteid = 1
AND action.typeid = 1
;
+ ------------- +
| actionid |
+ ------------- +
| 1 |
| 3 |
| 5 |
+ ------------- +
If your intent is to optimize the query show in your question - then please simply use the query shown above, this is the answer to your question.
However, looking at your comments about expected results, it appears that the query in the question is probably wrong - it doesn't give expected results.
Well, but it's still unclear what the query should give ? There are many possibilities, I'll show some of them below.
If you need to list all action.actionid with typeid = 1, but only such records, for which it exists any record with the same eventid and typeid = 2 ... then use the below query (the 3rd query in the demo):
SELECT a.actionid
FROM
ACTION a
INNER JOIN EVENT e
ON a.eventid = e.eventid
WHERE -- actiondate2 BETWEEN 20130601 AND 20131031 AND
EXISTS ( SELECT 1 FROM action a1
WHERE a1.eventid = a.eventid
AND a1.typeid = 2
)
AND e.siteid = 1
AND a.typeid = 1
;
+ ------------- +
| actionid |
+ ------------- +
| 1 |
| 3 |
+ ------------- +
This query uses the EXISTS operator, instead of COUNT() - if we want an information that same record exist, we don't need to count all of them! The count must read all of records, the EXISTS stops reading the table if it finds the first record that meets conditions - therefore EXISTS is usually faster than COUNT().
If you need to list all action.actionid with typeid = 1,and also display an information that some corresponding records exist with typeid = 2 - then use the below query (the fourth query in the demo):
SELECT
a.actionid ,
CASE WHEN EXISTS ( SELECT 1 FROM action a1
WHERE a1.eventid = a.eventid
AND a1.typeid = 2
)
THEN 'typeid=2 EXISTS'
ELSE 'typeid=2 DOESN''T EXIST'
END typeid2_exist
FROM
ACTION a
INNER JOIN EVENT e
ON a.eventid = e.eventid
WHERE -- actiondate2 BETWEEN 20130601 AND 20131031 AND
e.siteid = 1
AND a.typeid = 1
;
+ ------------- + ---------------------- +
| actionid | typeid2_exist |
+ ------------- + ---------------------- +
| 1 | typeid=2 EXISTS |
| 3 | typeid=2 EXISTS |
| 5 | typeid=2 DOESN'T EXIST |
+ ------------- + ---------------------- +
But if you really need to count corresponding records with typeid = 2 - then this query can help (the fifth query in the demo):
SELECT
a.actionid ,
( SELECT count(*) FROM action a1
WHERE a1.eventid = a.eventid
AND a1.typeid = 2
) typeid2_count
FROM
ACTION a
INNER JOIN EVENT e
ON a.eventid = e.eventid
WHERE -- actiondate2 BETWEEN 20130601 AND 20131031 AND
e.siteid = 1
AND a.typeid = 1
;
+ ------------- + ------------------ +
| actionid | typeid2_count |
+ ------------- + ------------------ +
| 1 | 1 |
| 3 | 1 |
| 5 | 0 |
+ ------------- + ------------------ +
If none of queries shown above meet your requirements, please show (basing on sample data in the demo) the results that the query should return - this helps someone in this forum to build a proper query that meets all your requirements.
Then, when we recognize the right query that meet all expectations, we can start to optimize it's performance.

$wpdb MySQL select from main table and join each row with two rows of another table

$referrer is the only defined variable.
main_table holds the relationship between users and referrers. a referrer can have many users.
user_id | referrer
1 | seller
2 | abother seller
3 | another seller
4 | seller
secondary_table holds user_id, meta_key and meta_value with last_name and first_name being meta_keys.
user_id | meta_key | meta_value
1 | first_name | John
1 | last_name | Doe
4 | first_name | Betty
4 | last_name | Boo
I need to merge the following 3 queries
SELECT user_id FROM main_table WHERE referrer = $referrer
SELECT meta_value FROM secondary_table WHERE user_id = $user_id AND meta_key = first_name
SELECT meta_value FROM secondary_table WHERE user_id = $user_id AND meta_key = last_name
into one query so I can use it with $results = $wpdb->get_results, then asort() the $results by last_name to have an alphabetically ordered output and echo $results with a foreach like
foreach ($results as $result) {
echo $result->user_id.' '.$result->first_name.' '.$result->last_name;
}
If $referrer == "seller" the output should look like this:
4 Betty Boo
1 John Doe
How should that single query look like?
Thanks for your help.
You could use this:
select
main_table.user_id,
concat(s1.meta_value, ' ', s2.meta_value) as name
from
main_table left join secondary_table s1
on main_table.user_id = s1.user_id and s1.meta_key = 'first_name'
left join secondary_table s2
on main_table.user_id = s2.user_id and s2.meta_key = 'last_name'
where
referrer = $referrer
I'm joining main_table with secondary_table twice, the first time filtered on the first name, the second time filtered on the last name. Then to get the name you just have to concat the first meta_value with the second.
Its little bit tricky. You can use following query
select m.user_id as new_user_id, s1.meta_value as new_first_name, s2.meta_value as new_last_name from main_table m, secondary_table s1, secondary_table s2 where m.user_id=s1.user_id and m.user_id=s2.user_id and s1.meta_key='first_name' and s2.meta_key='last_name' and m.referrer='seller' order by new_last_name
and then u can do
$results = $wpdb->get_results(...)
foreach ($results as $result) {
echo $result->new_user_id.' '.$result->new_first_name.' '.$result->new_last_name;
}

Return data from several tables in database

I need some help getting data from several tables.
This is the tables I have:
_______________ ______________ ___________ _______________ _____________
|_tblUsers____| |_tblAnswers__| |_tblAlt__| |_tblQuestion_| |_survey_____|
| userID | | answerAltID | | altID | | questID | | surveyID |
| username | | userID | | altText | | questText | | surveyName |
|_____________| |_____________| |_questID_| |_surveyID____| |____________|
TblUsers have a list of users in the system, tblAnswers have all answers that has been given from users, tlbAlt holds alternatives for a question, and tblQuestion has the questions. There is one more table called tblSurveys, but that's not needed here as the ID is mentioned in the tblQuestion.
This is what I have so far:
SELECT
tblQuestion.questText,
tblAlt.altText,
Count(tblAnswers.answerID) as answers_count,
(SELECT COUNT(answerID) FROM tblAnswers, tblAlt
WHERE tblAnswers.answerAltID = tblAlt.altID
AND tblAlt.questID = " & CInt(questionID) & ") as total_count
FROM tblAlt, tblQuestion
LEFT JOIN tblAnswers ON (tblAlt.altId = tblAnswers.altID)
WHERE tblAlt.questID = " & CInt(questionID) & "
GROUP BY tblAlt.altText;
This returns rows like this:
| What is blablabla? | The answer is... | 2 (has answered) | 10 (total
answers) |
This unfortunately only returns all rows for one question. Is there a way to get all rows that is a part of same survey (based on surveyID)?
If want the output to be like this:
| What is blablabla? | The answer is... | 2 (has answered) | 10 (total
answers) | Name of Survey |
I want to return ALL alternatives (with how many answered, total answers, related question and survey).
Update:
This my input:
SELECT tblalternativ.altTekst, tblalternativ.altID, Count(tblsvar.svarAltID) as antSvar,
(SELECT COUNT(*) FROM tblsvar, tblalternativ
WHERE tblsvar.svarAltID = tblalternativ.altID
AND tblalternativ.altSpmID = " & CInt(lblQuestion.Tag) & ") as antTotal,
(SELECT Count(*) FROM tblalternativ WHERE altSpmID = " & CInt(lblQuestion.Tag) & ") as spmTotal
FROM(tblalternativ) LEFT JOIN tblsvar ON (tblalternativ.altId = tblsvar.svarAltID)
WHERE(tblalternativ.altSpmID = " & CInt(lblQuestion.Tag) & ")
GROUP BY tblalternativ.altTekst ORDER BY tblalternativ.altID ASC
My output:
altTekst altID antSvar antTotal spmTotal
Black 83 1 3 5
Green 84 1 3 5
Yellow 85 1 3 5
White 86 0 3 5
Pink 87 0 3 5
But this only show statistics for one question. I want to show for all questions in one survey. So I need to get all altTekst for that survey, question name, and ID of survey.
I want:
spmTekst altTekst altID antSvar antTotal spmTotal evalID
What is... Black 83 1 3 5 1
What is... Green 84 1 3 5 1
What is... Yellow 85 1 3 5 1
What is... White 86 0 3 5 1
What is... Pink 87 0 3 5 1
Who is.... The king 88 2 3 3 1
Who is.... The pope 89 0 3 3 1
Who is.... The president 90 1 3 3 1
Which.... Shoe 91 2 3 2 1
Which.... Hat 92 1 3 2 1
In other words, I want the statistics from all questions in same survey (based on evalID).
To return all questions, answer text, count ofusers with that answer, and total answers provided; per survey.
Select TQ.QuestText, tAlt.altText, count(*) as Answers_count, suM(mTemp.Cnt) as total_count
FROM tblQuestion tq
LEFT JOIN tblAlt talt on Talt.QuestID = TQ.QuestID
LEFT JOIN tblAnswers ta on ta.AnswerAltID = talt.AltID
LEFT JOIN tblUsers tu ON Ta.UserID = TU.UserID
LEFT join tblAnswers ta2 on ta2.answeraltId = talt.altID
LEFT JOIN
(SELECT COUNT(*) cnt, questID
FROM tblAnswers
INNER JOIN tblAlt on AltID = AnswerAltID
group by questID) mTemp
on mTemp.QuestID = talt.QuestID
WHERE tQ.SurveyID = 123 --Change this to your value
Group by TQ.QuestText, TAlt.AltText
It's just a bunch of left joins vs inner joins; and i abstracted out the counts for each table once so it should be faster instead of doing a sub select on each row. This way it does it once for all rows and is done.
Try this(Not at all optimized, just added the survey part to it):
SELECT tblQuestion.questText, tblAlt.altText,
Count(tblAnswers.answerAltID) AS answers_count,
(SELECT COUNT(answerAltID) FROM tblAnswers, tblAlt
WHERE tblAnswers.answerAltID = tblAlt.altID AND
tblAlt.questID = " & CInt(questionID) & ") as total_count,
survey.surveyName
FROM survey, tblQuestion, tblAlt
LEFT JOIN tblAnswers ON (tblAlt.altId = tblAnswers.answerAltID)
WHERE tblAlt.questID = " & CInt(questionID) & " AND
tblQuestion.surveyID = survey.surveyID
GROUP BY tblAlt.altText;
Edit: Try this then:
SELECT tblQuestion.questText AS spmTekst, tblAlt.altText AS altTekst,
tblAlt.altID,
Count(tblAnswers.answerAltID) AS antSvar,
COUNT(tblAlt.altID) AS antTotal,
COUNT(tblQuestion.questID) AS spmTotal,
survey.surveyID AS evalID
FROM tblQuestion
JOIN survey ON (survey.surveyID = tblQuestion.surveyID)
JOIN tblAlt ON (tblAlt.questID = tblQuestion.questID)
LEFT JOIN tblAnswers ON (tblAnswers.answerAltID = tblAlt.altID)
WHERE tblAlt.questID = " & CInt(questionID) & " AND -- what really is this? review this
survey.surveyID = '123' -- the value u want
GROUP BY tblAlt.altText
ORDER BY tblAnswers.answerAltID;