there are two tables:
TABLE cars
id | date
----------
1 | 2012-01-04
2 | 2012-01-04
3 | 2012-01-05
TABLE versions
id_car | year | author
-------------------------
1 | 2005 | John
1 | 2001 | Carl
2 | 2003 | Carl
2 | 2001 | John
3 | 2004 | Carl
3 | 2003 | John
I need to get all the information about cars with yesterday's date (2012-01-04) and information about their latest version if author is Carl.
So in this example I need to get:
2 | 2012-01-04 | 2003 | Carl
You want an inner join:
select
c.id,
c.date,
v.year,
v.author
from
cars c
inner join versions v on
c.id = v.id_car
inner join (
select
id_car,
max(year) as latestYear
from
versions
group by
id_car
) vmax on
c.id = vmax.id_car
and v.year = vmax.latestYear
where
v.author = 'Carl'
and c.date = '2012-01-04'
In this query, you're saying, "Grab me everything from cars where the date is 2012-01-04 and then find everything in versions where the id_car column is equal to my id column in cars. Oh, and only give me anything from versions where the author is Carl, but only where the year of the car is equal to the greatest year of the car that's available."
Related
I'm in front of a "minor" problem taht looks easy but I didn't suceed to resolve it.
I have three tables in my Database :
Table gp
____________
id | name |
____________
1 | Le Mans|
2 | Toulon |
3 | Rennes |
Table player
____________
id | name |
____________
1 | Thibaut|
2 | Fred |
3 | Samir |
Table Records
_____________________________
id | gp_id | player_id | time
_____________________________
1 | 1 | 1 | 17860
2 | 2 | 1 | 11311
3 | 3 | 1 | 33133
4 | 3 | 2 | 11113
5 | 2 | 2 | 44444
6 | 1 | 2 | 13131
7 | 1 | 3 | 11111
8 | 3 | 3 | 21112
I want to get a sum of time for players that have a record on every gp ( so in my case, just players Thibaut and Fred have a record on the 3 gp ( Samir has just a record on two gp ) ).
I have no idea how I can get that, of course this SQL query is retrieving a sum but from this query I want to escape the guys that don't have a record on every GPs, but I'm blocked at that point ...
SELECT p.name, sum(time)
from records r
join gp g on r.gp_id = g.id
join player p on r.player_id = p.id
group by r.player_id
Thanks in advance guys !
You could use having count to exclude the records that don't have a record on every GPs.
Try:
select p.name,
sum(`time`) as tot_sum
from records r
inner join player p on r.player_id=p.id
inner join gp g on g.id=r.gp_id
group by p.name
having count(distinct gp_id) = (select count(distinct id) from gp)
https://dbfiddle.uk/t8QwSFDY
having count(distinct gp_id) = (select count(distinct id) from gp) will match only the records in the record table that have a record on every gp.
I have two tables below with the following information
project.analytics
| proj_id | list_date | state
| 1 | 03/05/10 | CA
| 2 | 04/05/10 | WA
| 3 | 03/05/10 | WA
| 4 | 04/05/10 | CA
| 5 | 03/05/10 | WA
| 6 | 04/05/10 | CA
employees.analytics
| employee_id | proj_id | worked_date
| 20 | 1 | 3/12/10
| 30 | 1 | 3/11/10
| 40 | 2 | 4/15/10
| 50 | 3 | 3/16/10
| 60 | 3 | 3/17/10
| 70 | 4 | 4/18/10
What query can I write to determine the average number of unique employees who have worked on the project in the first 7 days that it was listed by month and state?
Desired output:
| list_date | state | # Unique Employees of projects first 7 day list
| March | CA | 1
| April | WA | 2
| July | WA | 2
| August | CA | 1
My Attempt
select
month(list_date),
state_name,
count(*) as Projects,
from projects
group by
month(list_date),
state_name;
I understand the next steps are to subtract the worked_date - list_date and if value is <7 then average count of employees from the 2nd table but I'm not sure what query functions to use.
You could use a CASE with a DISTINCT to COUNT the unique employees that worked within the first 7 days of the list_date.
Once you have that total of employees per project, then you can calculate those averages per month & state.
SELECT
MONTHNAME(list_date) as `ListMonth`,
state,
AVG(TotalUniqEmp7Days) AS `Average Unique Employees of projects first 7 day list`
FROM
(
SELECT
proj.proj_id,
proj.list_date,
proj.state,
COUNT(DISTINCT CASE
WHEN emp.worked_date BETWEEN proj.list_date and DATE_ADD(proj.list_date, INTERVAL 6 DAY)
THEN emp.employee_id
END) AS TotalUniqEmp7Days
-- , COUNT(DISTINCT emp.employee_id) AS TotalUniqEmp
FROM project.analytics proj
LEFT JOIN employees.analytics emp ON emp.proj_id = proj.proj_id
GROUP BY proj.proj_id, proj.list_date, proj.state
) AS ProjectTotals
GROUP BY YEAR(list_date), MONTH(list_date), MONTHNAME(list_date), state;
A Sql Fiddle test can be found here
I think this is the code that you want
select
p.list_date, p.state,
emp.no_of_unique_emp
from project.analytics p
inner join (
select
t.project_id,
count(t.employee_id) as no_of_unique_emp
from (
select distinct employee_id, project_id
from employees.analytics
) t
group by t.project_id
) emp
on emp.project_id = p.project_id
where datediff (p.list_date, getdate()) <= 7
I do have the following three tables in a MySQL-DB (InnoDB)
UserTab
ID | Name | ---
------------------
1 | Tom |
2 | Dick |
3 | Harry |
EventTab
ID | Name | ---
------------------
1 | Easter |
2 | Holidays |
3 | ThxGiving |
4 | Christmas |
ParticipationTab
ID | UserID | EventID
---------------------
1 | 1 | 1
2 | 1 | 2
3 | 1 | 3
4 | 2 | 1
5 | 2 | 4
6 | 3 | 3
And I want to achieve the follwing result with my query:
QueryResultTab
UserTab.Name | EventTab.Name | NoPart | Names
-----------------------------------------------
Tom | Easter | 2 | Tom, Dick
Tom | Holidays | 1 | Tom
Tom | ThxGiving | 2 | Tom, Harry
Dick | Easter | 2 | Tom, Dick
Dick | Christmas | 1 | Dick
Harry | ThxGiving | 2 | Tom, Harry
I do know about Count() combined with GROUP to get the number of participants
I know about group-concat to get the "Names".
SELECT Event, GROUP_CONCAT(Name ORDER BY Name ASC SEPARATOR ', ') as Names
FROM
(SELECT ID as UserID, Name FROM X_Users WHERE ConditionA) AS UserTab
INNER JOIN
(SELECT EventID, UserID FROM X_Participation WHERE ConditionB) AS ParticipationTab
ON UserTab.UserID = ParticipationTab.UserID
INNER JOIN
(SELECT ID as EventID, Event FROM X_Events WHERE ConditionC) AS EventTab
ON ParticipationTab.EventID = EventTab.EventID
GROUP BY EventTab.EventID
This gives me:
ConcatTab
EventTab.Name | Names
---------------------------
Easter | Tom, Dick
Holidays | Tom
ThxGiving | Tom, Harry
Easter | Tom, Dick
Christmas | Dick
ThxGiving | Tom, Harry
I know about JOINs as you can see. Probably I could use LEFT or RIGHT JOINs as well for this.
For the other parts I use this query:
SELECT Name, Event, NoPart
FROM (SELECT ID as UserID, Name FROM X_Users WHERE ConditionA) AS UserTab
INNER JOIN (SELECT EventID, UserID FROM X_Participation WHERE ConditionB) AS PartTab
ON UserTab.UserID = PartTab.UserID
INNER JOIN (SELECT ID as EventID, Event FROM X_Events WHERE ConditionC) AS EvTab
ON PartTab.EventID = EvTab.EventID
INNER JOIN (SELECT EventID as CntID, COUNT(*) AS NoPart FROM X_Participation WHERE ConditionB) AS CntTab
ON EvTab.EventID = CntTab.CntID
ORDER BY UserTab.UserID
This gives me:
CountTab
UserTab.Name | EventTab.Name | NoPart
--------------------------------------
Tom | Easter | 2
Tom | Holidays | 1
Tom | ThxGiving | 2
Dick | Easter | 2
Dick | Christmas | 1
Harry | ThxGiving | 2
But how to combine/merge ConcatTab and CountTab into QueryResultTab? I want to retrieve the result table in PHP row by row with mysql_fetch_assco().
Please don't tell me about PDO, etc. I know about it.
The other option - what I try to avoid - is do it within a PHP-loop and use numerous tiny SQL-queries to achieve the result.
Based on your sample data, you want all the rows in the participation table, with information from the dimensions. Then you want a summary of that table.
Here is an approach that uses a subquery in the FROM clause:
SELECT u.name, e.name, p2.numPart, p2.Names
FROM X_Participation p INNER JOIN
X_Users u
ON u.UserID = p.UserID INNER JOIN
X_Events e
ON p.EventID = e.EventID INNER JOIN
(SELECT p2.EventId, COUNT(*) as numPart,
GROUP_CONCAT(u2.name SEPARATOR ', ') as names
FROM X_Participation p2 INNER JOIN
X_Users u2
ON u2.UserID = p2.UserID
GROUP BY p2.EventId
) p2
ON p2.EventId = p.EventId;
Notes:
If you are going to use table aliases (which you should), make them shorter, not longer than the table names.
Don't use subqueries unnecessarily. This is especially true in MySQL which materializes subqueries.
You can put additional conditions in a WHERE clause of the outer query.
Here I have 3 tables:
titles
id | title
1 | Cars
2 | Computers
entry
id | entry | title_id
1 | bmw | 1
2 | mercedes | 1
3 | ibm | 2
4 | hp | 2
5 | dell | 2
6 | acer | 2
7 | asus | 2
8 | toshiba | 2
9 | ferrari | 1
10 | toyota | 1
11 | honda | 1
entry_votes
id | entry_id | vote
1 | 1 | 1
2 | 1 | 1
3 | 1 | -1
4 | 2 | 1
5 | 2 | -1
6 | 9 | 1
7 | 9 | 1
8 | 9 | 1
9 | 10 | -1
10 | 10 | -1
I need to order the entries by the sum of votes they get.
I use this code to order cars by votes.
SET #title_id = 2;
select e.id, sum(v.vote) as score
from entry e join entry_votes v on (e.id = v.entry_id) and e.title_id=#title_id
group by e.id order by score desc
It works fine but the problem is missing the cars which does not have any votes.
This query orders this:
Ferrari (+3)
Bmw (1)
Mercedes (0)
Toyota (-2)
Expected result is:
Ferrari (+3)
Bmw (1)
Mercedes (0)
Honda (0)
Toyota (-2)
I got the result with COALESCE
correct query is:
SET #title_id = 2;
select e.*, COALESCE(sum(v.vote),0) as score
from entry e left join entry_votes v on (e.id = v.entry_id) where e.title_id=#title_id
group by e.id order by score desc
You can try with a LEFT JOIN, but I think you may have some typos or errors in your table/query:
In your entry table mercedes has a title = 2 (computers) instead of 1 (cars).
You have your '#title_id = 2'.
SET #title_id = 1;
select e.id, COALESCE(sum(v.vote),0) as score
from entry e left join entry_votes v
on (e.id = v.entry_id) WHERE e.title_id=#title_id
group by e.id order by score desc
EDIT:
Edited the query to add the COALESCE so it can be ordered correctly. What this function does is that if you get a NULL value, it will return (in this case) a 0.
You do not need to declare a variable in your sql. Just join the titles table to the entry table.
SQL: (Demo)
SELECT e.entry, SUM(COALESCE(v.vote, 0)) AS score
FROM entry e
JOIN titles t ON e.title_id=t.id
LEFT JOIN entry_votes v ON e.id=v.entry_id
WHERE t.title = 'Cars'
GROUP BY e.entry
ORDER BY score DESC
A left join will allow rows with no votes to appear in the result set. Coalesce null sums to 0 for consistently numeric vote counts. Use ALL CAPS when writing SQL keywords/functions for clarity.
Result set:
entry
score
ferrari
3
bmw
1
honda
0
mercedes
0
toyota
-2
I have the following tables:
members
This stores a list of members for our system.
---------------------
| member_id | name |
---------------------
| 1 | Bob |
---------------------
| 2 | Joe |
---------------------
| 3 | Tom |
---------------------
| 4 | Bill |
---------------------
| 5 | Will |
---------------------
categories
This stores the categories for our system. Categories are not visible to members by default. A member must have a valid licence to be able to access a category (see below).
----------------------
| cat_id | name |
----------------------
| 1 | Cat1 |
----------------------
| 2 | Cat2 |
----------------------
| 3 | Cat3 |
----------------------
licences
Stores the licences that a member has. One member can have many licences. Licences can have a life time and will expire. Once a licence expires, the member can no longer view the category.
------------------------------------------------------
| id | catid | subid | valid_from | valid_to |
------------------------------------------------------
| 1 | 1 | 1 | 2014-01-01 | 2020-12-01 |
------------------------------------------------------
| 2 | 1 | 2 | 1999-01-01 | 2001-01-02 |
------------------------------------------------------
| 3 | 1 | 3 | 2014-01-01 | 2020-12-01 |
------------------------------------------------------
| 4 | 1 | 4 | 1999-01-01 | 2000-01-01 |
------------------------------------------------------
| 5 | 1 | 5 | 2014-01-01 | 2020-12-01 |
------------------------------------------------------
| 6 | 2 | 1 | 2014-01-01 | 2020-12-01 |
------------------------------------------------------
| 7 | 2 | 2 | 1999-01-01 | 2001-01-02 |
------------------------------------------------------
| 8 | 2 | 3 | 2014-01-01 | 2020-12-01 |
------------------------------------------------------
| 9 | 2 | 4 | 1999-01-01 | 2000-01-01 |
------------------------------------------------------
| 10 | 2 | 5 | 2014-01-01 | 2020-12-01 |
------------------------------------------------------
| 11 | 3 | 1 | 2014-01-01 | 2020-12-01 |
------------------------------------------------------
| 12 | 3 | 2 | 2014-01-01 | 2020-12-01 |
------------------------------------------------------
preferences
The preferences table stores whether a member wishes to receive e-mails that are are sent relating to a category. The member can set a preference of '1' for 'wish to receive' or '0' for 'do not wish to receive'. A quirk is that if the member has no record (or a null value) we make an assumption that they wish to receive.
-----------------------------------
| id | catid | subid | pref |
-----------------------------------
| 1 | 1 | 1 | 0 |
-----------------------------------
| 2 | 2 | 1 | 1 |
-----------------------------------
| 3 | 3 | 1 | 1 |
-----------------------------------
| 4 | 1 | 2 | 0 |
-----------------------------------
| 5 | 1 | 3 | 1 |
-----------------------------------
| 6 | 2 | 3 | 0 |
-----------------------------------
recipients
When an email is sent out based on a category, the recipient is logged so we don't email them more than once.
-----------------------------
| id | emailid | subid |
-----------------------------
| 1 | 1 | 1 |
-----------------------------
| 2 | 1 | 2 |
-----------------------------
I'm trying to write a query the fetches all members, and their related licence for a range of category IDs, their preferences and also make sure that they don't have a record in the recipients table.
In pseudo query:
SELECT [all members, their licence info, and preference setting]
FROM [members table]
WHERE [member doesnt exist in the recipients table for a given emailID]
The issue is that I need to check against multiple categoryIDs, but return just one result and only if the preference is set to 1 (or null, or doesn't exist).
So with the example data, given we are searching for categoryIDs 1,2 and 3 (A member must have a licence for at least one of these categories) and checking against emailID of 1, the only result should be for member_id 3 (Tom) with preference ID of 6 (because it's set to 1) and licence ID of 3 (because it's valid and the preference ID of 6 corresponds to it and it is set to 1). The second result should be member_id 5 (Will) as he has a licence to catids 1 and 2, he hasn't received the email with ID of 1 and he has no specific preference set.
Reason being: Members 1 and 2 are in the recipient table for emailID 1, member 2's licence has also expired, member 4's licence has expired and member 5 has their preference set to 0.
The query I've written which isn't working quite right is:
SELECT
members.member_id,
members.name,
licence.catid as licencedToCat,
categories.cat_name as categoryName,
licence.valid_from as licenceStart,
licence.valid_to as licenceEnd,
preferences.pref
FROM (`members`)
JOIN `licence` ON `licence`.`subid`=`members`.`member_id`
JOIN `preferences` ON `preferences`.`subid`=`members`.`member_id`
JOIN `categories` ON `categories`.`cat_id`=`licence`.`catid`
WHERE `licence`.`catid` IN (1,2,3)
AND `start_date` <= '2014-12-16'
AND `end_date` >= '2014-12-16'
AND (pref='1' OR pref IS NULL)
AND `members`.`member_id` NOT IN (SELECT subid FROM `recipients` WHERE `recipients`.`emailid`='1')
GROUP BY `licence`.`subid`
The issue is that the query is returning results saying users have a preference set to 1 where they actually don't even have a record set for that category.
The desired output is any member(s) along with the licence they have for the category but only if their preference for that category is 1/null/doesn't exist AND only if they don't appear in the recipients table for a given emailID.
So, if a member has 2 licences
I appreciate this was a long read, so thanks if you're still here! Any ideas on how to tweak my query to solve this?
I think part of your problem here is that you're using all inner joins. Like you said, a user may not have a preference, so a row may not be returned in your query. That being said, it seems like you want to inner join most tables, as it appears you only want members who have licenses, but you want to see all licenses regardless of whether that user has a preference. So, I made preferences an outer joined table:
SELECT m.*, l.catid AS licenseCat, c.name AS categoryName,
l.valid_from AS licenseStart, l.valid_to AS licenseEnd, p.pref AS preference
FROM members m
JOIN licenses l ON l.subid = m.member_id
JOIN categories c ON c.cat_id = l.catid
LEFT JOIN preferences p ON p.catid = c.cat_id AND p.subid = l.subid;
Once I had done that, I wrote the subquery that pulled the member_id of all members who are in the recipients table with the specified email:
SELECT subid
FROM recipients
WHERE emailid = 1;
Now you can insert that into your original query, and add your other requirements:
SELECT m.*, l.catid AS licenseCat, c.name AS categoryName,
l.valid_from AS licenseStart, l.valid_to AS licenseEnd, IFNULL(p.pref, 0) AS preference
FROM members m
JOIN licenses l ON l.subid = m.member_id
JOIN categories c ON c.cat_id = l.catid
LEFT JOIN preferences p ON p.catid = c.cat_id AND p.subid = l.subid
WHERE c.cat_id IN (1, 2, 3) AND
l.valid_from <= '2014-12-06' AND l.valid_to >= '2014-12-06' AND
m.member_id NOT IN (SELECT subid FROM recipients WHERE emailid = 1)
AND (p.pref = 1 OR p.pref IS NULL);
You said in your question that this should return member_id 3 (which is Tom) but that does not match your results because member 5 has no preferences, so we should assume they want an email right? I'm also not sure how to group this for you. If a member has multiple subscriptions, which one do you want to keep?
I built an SQL Fiddle and tested what I have and it's really close. I hope this can at least push you in the right direction and I will edit the answer as needed.
EDIT
The following will give you what you want, but it is not always recommended. If you really don't care about the subscription dates (as long as it meets the criteria in the where clause) and you really don't care about the category for the user, just add GROUP BY m.member_id to get one row for each member.
So, the final query is like these, tested and working:
SELECT
m.member_id,
m.email,
l.catid as licencedToCat,
c.cat_name as categoryName,
l.valid_from as licenceStart,
l.valid_to as licenceEnd,
COALESCE(p.pref, 1) pref
FROM members m
JOIN licence l ON l.subid = m.member_id
JOIN categories c ON c.cat_id = l.catid
LEFT JOIN preferences p ON p.subid= m.member_id AND p.cat_id = l.cat_id
LEFT JOIN recipients r ON r.subid = m.member_id
WHERE l.catid IN (1,2,3)
AND start_date <= '2014-12-16' AND end_date >= '2014-12-16'
AND COALESCE(p.pref, 1) = 1
AND COALESCE(r.emailid, 0) = 0-- assuming with emailid = 0 it remains valid as recipient
GROUP BY m.member_id
However, for the purpose of the query it should only have DISTINCT m.* in the SELECT clause which would discard the GROUP BY