Selecting two conditions simultaneously - mysql

Say i've got the next to tables: Doctors, and Workdays:
DocNumbers | idNum
118 | 11
119 | 12
120 | 13
121 | 14
122 | 15
Notice: a doctor can work in several different workdays.
DocNum | Workday |AmountOfHours |
118 | 1 | 8 |
118 | 3 | 9 |
120 | 1 | 6 |
121 | 3 | 5 |
122 | 4 | 7 |
I want to create a new table containing all id's of the doctors that work in day 1 and day 3 - That means that i will get a table containing only 118.
So far i've got:
SELECT distinct Doctors.doctorNumber, idNum
FROM Doctors, Workdays
WHERE Workdays.dayInWeek in (1,3)
AND Workdays.doctorNumber=Doctors.doctorNumber
But it seems like a i get irrelevant results like 120 and 121.
So 'IN' is more like a 'OR'. Can't seem to find the equivalence for 'and'?

This is easy to do if you join the Workdays table twice, once for each day you want to check:
select Doctors.DocNumbers, Doctors.idNum
from Doctors
inner join Workdays as Workdays1 on Workdays1.DocNum = Doctors.DocNumbers and Workdays1.Workday = 1
inner join Workdays as Workdays3 on Workdays3.DocNum = Doctors.DocNumbers and Workdays3.Workday = 3;
http://www.sqlfiddle.com/#!2/4c530/3

Try this with simple join
SELECT DISTINCT w.`DocNum`, d.idNum
FROM doctors d
LEFT JOIN Workdays w ON(d.`DocNumbers`=w.`DocNum`)
LEFT JOIN Workdays ww ON(d.`DocNumbers`=ww.`DocNum`)
WHERE w.`Workday` = 1 AND ww.`Workday` =3
See fiddle here
Here is another way of doing same
SELECT w.`DocNum`, d.idNum
FROM doctors d
LEFT JOIN Workdays w ON(d.`DocNumbers`=w.`DocNum`)
GROUP BY d.`DocNumbers`
HAVING GROUP_CONCAT(w.`Workday` SEPARATOR ',')= '1,3'
See fiddle here

Related

How do I get results of a MySQL JOIN where records meet a value criteria in joined table?

This may be simple but I can't figure it out...
I have two tables:
tbl_results:
runID | balance |
1 | 3432
2 | 5348
3 | 384
tbl_phases:
runID_fk | pc |
1 | 34
1 | 2
1 | 18
2 | 15
2 | 18
2 | 20
3 | -20
3 | 10
3 | 60
I want to get a recordset of: runID, balance, min(pc), max(pc) only where pc>10 and pc<50 for each runID as a group, excluding runIDs where any associated pc value is outside of value range.
I would want the following results from what's described above:
runID | balance | min_pc | max_pc
2 | 5348 | 15 | 20
... because runID=1&3 have pc values that fall outside the numeric range for pc noted above.
Thanks in advance!
You may apply filters based on your requirements in your having clause. You may try the following.
Query #1
SELECT
r.runID,
MAX(r.balance) as balance,
MIN(p.pc) as min_pc,
MAX(p.pc) as max_pc
FROM
tbl_results r
INNER JOIN
tbl_phases p ON p.runID_fk = r.runID
GROUP BY
r.runID
HAVING
MIN(p.pc)>10 AND MAX(p.pc) < 50;
runID
balance
min_pc
max_pc
2
5348
15
20
Query #2
SELECT
r.runID,
MAX(r.balance) as balance,
MIN(p.pc) as min_pc,
MAX(p.pc) as max_pc
FROM
tbl_results r
INNER JOIN
tbl_phases p ON p.runID_fk = r.runID
GROUP BY
r.runID
HAVING
COUNT(CASE WHEN p.pc <= 10 or p.pc >= 50 THEN 1 END) =0;
runID
balance
min_pc
max_pc
2
5348
15
20
View working demo on DB Fiddle
Updated with comments from Rahul Biswas

MySQL - Retrieve the max value of an associated column within a LEFT JOIN with a different perimeter than the WHERE clause of the main query

I'm using MySql 5.6 and have a select query with a LEFT JOIN but i need to retrieve the max of a associated column email_nb) but with a different "perimeter" of constraints.
Let's take an example: let me state that it is a mere example with only 5 rows but it should work also when I have thousands... (I'm stating this since there is a LIMIT clause in my query)
Table 'query_results'
+-----------------------------+------------+--------------+
| query_result_id | query_id | author |
+-----------------------------+------------+--------------+
| 2 | 1 | john |
| 3 | 1 | eric |
| 7 | 3 | martha |
| 9 | 4 | john |
| 10 | 1 | john |
+-----------------------------+------------+--------------+
Table 'customers_emails'
+-------------------+-----------------+--------------+-----------+-------------+------------------------
| customer_email_id | query_result_id | customer_id | author | email_nb | days_since_sending
+-------------------+-----------------+--------------+-----------+-------------+------------------------
| 5 | 2 | 12 | john | 2 | 150
| 12 | 3 | 7 | eric | 4 | 90
| 27 | 3 | 12 | eric | 2 | 86
| 40 | 9 | 15 | john | 9 | 87
| 42 | 2 | 12 | john | 7 | 23
| 51 | 10 | 12 | john | 3 | 89
+-------------------+-----------------+--------------+-----------+-------------+-----------------------
Notes:
you can have a query_result where the author appears in NO row at all in any of the customers_emails, hence the LEFT JOIN I'm using.
You can see author is by design kind of duplicated as it's both on the first table and the second table each time associated with a query_result_id. It's important to note.
email_nb is an integer between 0 and 10
there is a LIMIT clause as I need to retrieve a set number of records
Today my query aims at retrieving query_results with a certain number of conditions on The specificity is that I make sure to retrieve query_results with an author who does not appear in any customer_email_id where the days_since_sending would be less than 60 days: it means i check these days_since_sending not only within the records for this query, but across all customers_emails thanks to the subquery NOT IN (see below).
This is my current query for customer_id = 12 and query_id = 1
SELECT
qr.query_result_id,
qr.author,
FROM
query_results qr
LEFT JOIN
customers_emails ce
ON
qr.author = ce.author
WHERE
qr.query_id = 1 AND
qr.author IS NOT NULL
AND qr.author NOT IN (
SELECT recipient
FROM customers_emails
WHERE
(
customer_id = 12 AND
( days_since_sending >= 60) )
)
)
# we don't take by coincidence/bad luck 2 query results with the same author
GROUP BY
qr.author
ORDER BY
qr.query_result_id ASC
LIMIT
20
This is the expected output:
+-----------------------------+------------+--------------+
| query_result_id | author | email_nb |
+-----------------------------+------------+--------------+
| 10 | john | 7 |
| 3 | eric | 2 |
+-----------------------------+------------+--------------+
My challenge/difficulty today:
Notice on the 2nd line Eric is tied to email_nb 2 and not the max of all Eric's emails which could have been 4 if we had taken the max of email_nb across ALL messages to author=eric. but we stay within the limit of customer_id = 12 so there's only one left with email_nb = 2
Also notice that on the first line, the email_nb associated with query_result = 10 is 7, and not 3, which could have been the case as 3 is what appears in table customers_emails on the last line.
Indeed for emails to 'john' i had the choice between email_nb 2, 7 and 3 but I take highest so it's 7 (even if this email is from more than 60 days ago !! This is very important and part of what I don't know how to do: the perimeters are different: today I retrieve all the query_results where the author has NOT been sent a email for the past 60 days (see the NOT IN subquery) BUT I need to have in the column the max email_nb sent to john by customer_id=12 and query_id=1 EVEN if it was sent more than 60 days ago so these are different perimeters...Don't really know how to do this...
It means in other words I don't want to find the max (email_nb) within the same WHERE clauses such as days_since_sending >= 60 or within the same LIMIT and GROUP BY...as my current query: what I neeed is to retrieve the maximum value of email_nb for customer_id=12 AND query_id=1 and sent to john across ALL records on the customers_emails table!
If there is no associated row on customers_emails at all (it means no email have been ever sent by this customer for this query in the past) then the email_nb should be sth like NULL..
This means I do NOT want this output:
+-----------------------------+------------+--------------+
| query_result_id | author | email_nb |
+-----------------------------+------------+--------------+
| 10 | john | 3 |
| 3 | eric | 2 |
+-----------------------------+------------+--------------+
How to achieve this in MySQL 5.6 ?
Since you were confusing a bit, I came up on this.
select
max(q.query_result_id) as query_result_id,q.author,max(email_nb) as email_nb
from query_results q
left join customers_emails c on q.author=c.author
where customer_id=12 and query_id=1
group by q.author;
I think the best thing to do in a situation like this is break it down into smaller queries and then combine them together.
The first thing you want to do is this:
The specificity is that I make sure to retrieve query_results with an author who does not appear in any customer_email_id where the days_since_sending would be less than 60 days
This might look something like this:
-- Query A
SELECT DISTINCT q.author FROM query_results q
WHERE q.author NOT IN (
SELECT c.author FROM customers_emails c
WHERE c.days_since_sending < 60
)
AND q.query_id = 1
This will get you the list of authors (with duplicates removed) that haven't had an email in the last 60 days that appear for the given query ID. Your next requirement is the following:
I need to have in the column the max email_nb sent to john by customer_id=12 and query_id=1 EVEN if it was sent more than 60 days ago
This query could look like this:
-- Query B
SELECT c.query_result_id, c.author, MAX(c.email_nb) as max_email_nb
FROM customers_emails c
LEFT JOIN query_results q ON c.author = q.author
WHERE c.customer_id = 12
AND q.query_id = 1
GROUP BY c.query_result_id, c.author
That gets you the maximum email_nb for each author/query_result combination, not taking into consideration the date at all.
The only thing left to do is reduce the set of results from the second query down to only the authors that appear in the first query. There are a few different methods for doing that. For example, you could INNER JOIN the two queries by author:
SELECT b.* FROM (
-- Query B
SELECT c.query_result_id, c.author, MAX(c.email_nb) as max_email_nb
FROM customers_emails c
LEFT JOIN query_results q ON c.author = q.author
WHERE c.customer_id = 12
AND q.query_id = 1
GROUP BY c.query_result_id, c.author
) b INNER JOIN (
-- Query A
SELECT DISTINCT q.author FROM query_results q
WHERE q.author NOT IN (
SELECT c.author FROM customers_emails c
WHERE c.days_since_sending < 60
)
AND q.query_id = 1
) a ON a.author = b.author
You could use another NOT IN clause:
SELECT b.* FROM (
-- Query B
SELECT c.query_result_id, c.author, MAX(c.email_nb) as max_email_nb
FROM customers_emails c
LEFT JOIN query_results q ON c.author = q.author
WHERE c.customer_id = 12
AND q.query_id = 1
GROUP BY c.query_result_id, c.author
) b
WHERE b.author NOT IN (
-- Query A
SELECT DISTINCT q.author FROM query_results q
WHERE q.author NOT IN (
SELECT c.author FROM customers_emails c
WHERE c.days_since_sending < 60
)
AND q.query_id = 1
) a
There are most likely ways to improve the speed or reduce down the lines of code for this query, but if you need to do that you now have a query that works at least that you can compare the results to.

Get total count of records with a mysql join and 2 tables

I have 2 tables that I am trying to join but I am not sure how to make it the most time efficient.
Tasks Table:
nid | created_by | claimed_by | urgent
1 | 11 | 22 | 1
2 | 22 | 33 | 1
3 | 33 | 11 | 1
1 | 11 | 43 | 0
1 | 11 | 44 | 1
Employee Table:
userid | name
11 | EmployeeA
22 | EmployeeB
33 | EmployeeC
Result I am trying to get:
userid | created_count | claimed_count | urgent_count
11 | 3 | 1 | 3
22 | 1 | 1 | 2
33 | 1 | 1 | 2
created_account column will show total # of tasks created by that user.
claimed_count column will show total # of tasks claimed by that user.
urgent_count column will show total # of urgent tasks (created or claimed) by that user.
Thanks in advance!
I would start by breaking this up into pieces and then putting them back together. You can get the created_count and claimed_count using simple aggregation like this:
SELECT created_by, COUNT(*) AS created_count
FROM myTable
GROUP BY created_by;
SELECT claimed_by, COUNT(*) AS claimed_count
FROM myTable
GROUP BY claimed_by;
To get the urgent count for each employee, I would join the two tables on the condition that the employee is either the created_by or claimed_by column, and group by employee. Instead of counting, however, I would use SUM(). I am doing this because it appears each row will be either 0 or 1, so SUM() will effectively count all non-zero rows:
SELECT e.userid, SUM(t.urgent)
FROM employee e
JOIN task t ON e.userid IN (t.created_by, t.claimed_by)
GROUP BY e.userid;
Now that you have all the bits of data you need, you can use an outer join to join all of those subqueries to the employees table to get their counts. You can use the COALESCE() function to replace any null counts with 0:
SELECT e.userid, COALESCE(u.urgent_count, 0) AS urgent_count, COALESCE(crt.created_count, 0) AS created_count, COALESCE(clm.claimed_count, 0) AS claimed_count
FROM employee e
LEFT JOIN(
SELECT e.userid, SUM(t.urgent) AS urgent_count
FROM employee e
JOIN task t ON e.userid IN (t.created_by, t.claimed_by)
GROUP BY e.userid) u ON u.userid = e.userid
LEFT JOIN(
SELECT claimed_by, COUNT(*) AS claimed_count
FROM task
GROUP BY claimed_by) clm ON clm.claimed_by = e.userid
LEFT JOIN(
SELECT created_by, COUNT(*) AS created_count
FROM task
GROUP BY created_by) crt ON crt.created_by = e.userid;
Here is an SQL Fiddle example.

mysql inner join condition to ignore duplicate ids

Given below the list of tables.
Order the shop ids in the descending order based on the sum of number of clicks for a product in the locations [23,24,25,26,27].
Given product_id = 8
link_shops_locations
shop_id | location_id
---------------------
1 | 23
2 | 24
3 | 25
3 | 26
3 | 27
products_clicks
shop_id | product_id | clicks
-----------------------------
1 | 8 | 1
2 | 7 | 3
2 | 8 | 87
3 | 8 | 21
3 | 8 | 9
link_products_shops
product_id | shop_id
---------------------
7 | 1
8 | 1
8 | 1
8 | 2
8 | 2
8 | 1
7 | 3
8 | 3
This is what I have tried,
SELECT SUM(c.clicks) as no,
s.shop_id
FROM link_products_shops l
INNER JOIN products_clicks c
ON c.product_id = l.product_id
INNER JOIN link_shops_locations s
ON s.shop_id = c.shop_id // duplicate shop_ids gives wrong SUM
WHERE s.location_id IN (23,24,25,26,27)
GROUP BY s.shop_id
ORDER BY no DESC;
My problem is, since the link_shops_locations table has 3 shop_ids, the resulting expected SUM is multiplied by 3. How do I solve this? The INNER JOIN condition of link_shops_locations has something to do with this?. A little help will be very useful.
[1]You missed one more condition in INNER JOIN with products_clicks.
[2]Also you ignored product_id. Have a look at this query.
[3]You had inserted product_id=8 and shop_id=1 three times in link_products_shops table.
select shop_id, SUM(NoOfClicks) FROM(SELECT distinct c.shop_id, c.clicks AS NoOfClicks
FROM products_clicks c
INNER JOIN link_products_shops l
ON c.product_id = l.product_id
AND c.shop_id = l.shop_id
INNER JOIN link_shops_locations s
ON s.shop_id = c.shop_id and s.shop_id = l.shop_id
WHERE s.location_id IN (23,24,25,26,27)
AND c.product_id = 8) AS TabShopCounters
GROUP BY shop_id
ORDER BY NoOfClicks DESC;
SQL Fiddle - http://www.sqlfiddle.com/#!2/b22fe/8

Filter results by number of rows in mysql

I have three tables:
houses
id | square_feet
------------------
1 | 2000
2 | 1600
3 | 1000
energies
id | house_id | kwh
-------------------------
6 | 1 | 10
7 | 2 | 100
8 | 3 | 200
cars
id | energy_id | gallons
--------------------------
11 | 6 | 20
12 | 6 | 40
13 | 7 | 200
14 | 8 | 77
15 | 8 | 88
I need to get the average square feet and average kwh for either all houses or just the houses with a certain number of cars.
So for two cars, I want values for houses 1 and 3. For one car, I want values only for house 2. For all houses, I want 1, 2 and 3.
For any all houses I am using:
SELECT AVG(houses.square_feet) AS avg_sf, AVG(energies.kwh) AS avg_kwh
FROM (houses LEFT JOIN energies ON houses.id = energies.house_id)
How do I find the average square feet and average kwh for houses that have two cars?
Use:
SELECT AVG(h.square_feet),
AVG(e.kwh)
FROM ENERGIES e
JOIN HOUSES h ON h.id = e.house_id
JOIN (SELECT c.energy_id
FROM CARS s
GROUP BY e.energy_id
HAVING COUNT(*) = 2) x ON x.energy_id = e.id
Because of the foreign key relationship, you can use a JOIN rather than LEFT JOIN between the ENERGIES and HOUSES table.
Update to add that you can change:
HAVING COUNT(*) = 2
...to whatever number you like depending on the number of cars you want to know about.
I'd use a WHERE ... IN construct. A JOIN risks changing the averages by duplicating energy rows.
SELECT
AVG(houses.square_feet) AS avg_sf,
AVG(energies.kwh) AS avg_kwh
FROM houses
LEFT JOIN energies ON houses.id = energies.house_id
WHERE houses.house_id in (
SELECT energies.house_id
FROM energies
JOIN cars ON cars.energy_id = energies.id
GROUP BY energies.house_id
HAVING count(distinct cars.id) = 2
)
GROUP BY houses.id
This even works if one house has multiple links to a single car through the energies table, the COUNT DISTINCT should make sure each car is only counted once.