please help me i have no idea for this...
I have table like this (create_at YYYY-MM-DD). ID is auto increment
-----------------------------------------------------------------
| ID | id_user | activity | create_at |
-----------------------------------------------------------------
| 1 | 10 | A | 2017-10-11 |
| 2 | 52 | A | 2017-10-11 |
| 3 | 41 | A | 2017-10-12 |
| 4 | 52 | A | 2017-10-12 |
| 5 | 41 | B | 2017-10-12 |
| 6 | 52 | B | 2017-10-13 |
| 7 | 10 | B | 2017-10-14 |
-----------------------------------------------------------------
How to get count (mysql) user who doing activity "B" after activity "A" in sameday create_at.. In this case, the result is 1 (IDUser 41).. How can i do this in mysql? thankyou
We could use a semi-join or a correlated subquery.
we start like this, users that are doing activity B
SELECT t.id_user
FROM table_like_this t
WHERE t.activity = 'B'
we can match those rows to users that are doing activity A on the "same day" with JOIN operation back to the same table...
SELECT t.id_user
FROM table_like_this t
JOIN table_like_this r
ON r.id_user = t.id_user
AND r.create_at = t.create_at
AND r.activity = 'A'
WHERE t.activity = 'B'
As far as whether activity B is occurring "after" activity A, I don't see any information in the table that can tell us that (we can't tell what time each activity A and B occurred, and can't determine which one was "after" the other.)
For testing, we can include other columns in the SELECT list, to verify which rows from t and r are being returned, if the matching is being done properly.
Once we are satisfied, we can replace the SELECT list, to get a count of distinct id_user
SELECT COUNT(DISTINCT t.id_user)
FROM ...
Note that this will collapse occurrences of id_user that performed activity A and B on several different days so that the id_user will be counted only once.
If we want to count the number of days for each id_user, and include each of those days in the count, the query would need to be changed.
Related
I need to retrieve rows from a mysql database as follows: I have a contract table, a contract line item table, and another table called udac. I need all contracts which DO NOT have a line item record with criteria based on a relationship between contract line item and udac. If there is a better way to state this question, let me know.
Table Structures
----contract--------------------- ---contractlineitem-----------
| id | customer_id | entry_date | | id | contract_id | udac_id |
--------------------------------- ------------------------------
| 1 | 1234 | 2010-01-01 | | 1 | 1 | 5 |
| 2 | 2345 | 2016-01-31 | | 2 | 1 | 2 |
--------------------------------- | 3 | 1 | 1 |
| 4 | 2 | 4 |
| 5 | 2 | 2 |
------------------------------
---udac----------
| id | udaccode |
-----------------
| 1 | SWBL/R |
| 2 | SWBL |
| 3 | ABL/R |
| 4 | ABL |
| 5 | XRS/F |
-----------------
Given the above data, contract 2 would show up but contract 1 would not, because it has contractlineitems that point to udacs that end in /F or /R.
Here's what i have so far, but it's not correct.
SELECT c.*
FROM contract c
JOIN contractlineitem cli
ON c.id = cli.contract_id
WHERE c.entry_timestamp > '2016-01-01 00:00:00'
AND NOT EXISTS (
SELECT cli.id
FROM contractlineitem cli_i
JOIN udac u
ON cli_i.udac_id = u.id
WHERE u.udaccode LIKE '%/F' OR u.udaccode LIKE '%/R'
AND cli_i.contract_id = cli.contract_id);
Tom's comment that your WHERE clause is wrong may be the problem you are chasing. Plus, using a correlated subquery may be problematic for performance if the optimizer can't figure out a better way to do it.
Here is the better way to do it using an OUTER JOIN:
SELECT c.*
FROM contract c
JOIN contractlineitem cli
ON c.id = cli.contract_id
LEFT OUTER JOIN udac u
ON ( u.id = cli.udac_id
AND ( u.udaccode LIKE '%/F' OR u.udaccode LIKE '%/R' ) )
WHERE c.entry_timestamp > '2016-01-01 00:00:00'
AND u.id IS NULL
Try that out and see if it does what you want. The query essentially does what you stated: It tries to join to udac where the code ends in '/F' or '/R', but then it only accepts the ones where it can't find a match (u.id IS NULL).
If the same row is returned multiple times incorrectly, throw a distinct on the front.
I have a basic table:
+-----+--------+------+------+
| id, | name, | cat, | time |
+-----+--------+------+------+
| 1 | jamie | 1 | 100 |
| 2 | jamie | 2 | 100 |
| 3 | jamie | 1 | 50 |
| 4 | jamie | 2 | 150 |
| 5 | bob | 1 | 100 |
| 6 | tim | 1 | 300 |
| 7 | alice | 4 | 100 |
+-----+--------+------+------+
I tried using the "Left Joining with self, tweaking join conditions and filters" part of this answer: SQL Select only rows with Max Value on a Column but some reason when there are records with a value of 0 it breaks, and it also doesn't return every unique answer for some reason.
When doing the query on this table I'd like to receive the following values:
+-----+--------+------+------+
| id, | name, | cat, | time |
+-----+--------+------+------+
| 1 | jamie | 1 | 100 |
| 4 | jamie | 2 | 150 |
| 5 | bob | 1 | 100 |
| 6 | tim | 1 | 300 |
| 7 | alice | 4 | 100 |
+-----+--------+------+------+
Because they are unique on name and cat and have the highest time value.
The query I adapted from the answer above is:
SELECT a.name, a.cat, a.id, a.time
FROM data A
INNER JOIN (
SELECT name, cat, id, MAX(time) as time
FROM data
WHERE extra_column = 1
GROUP BY name, cat
) b ON a.id = b.id AND a.time = b.time
The issue here is that ID is unique per row you can't get the unique value when getting the max; you have to join on the grouped values instead.
SELECT a.name, a.cat, a.id, a.time
FROM data A
INNER JOIN (
SELECT name, cat, MAX(time) as time
FROM data
WHERE extra_column = 1
GROUP BY name, cat
) b ON A.Cat = B.cat and A.Name = B.Name AND a.time = b.time
Think about it... So what ID is mySQL returning form the Inline view? It could be 1 or 3 and 2 or 4 for jamie. Hows does the engine know to pick the one with the max ID? it is "free to choose any value from each group, so unless they are the same, the values chosen are indeterminate. " it could pick the wrong one resulting in incorrect results. So you can't use it to join on.
https://dev.mysql.com/doc/refman/5.0/en/group-by-handling.html
If you want to use a self join, you could use this query:
SELECT
d1.*
FROM
date d1 LEFT JOIN date d2
ON d1.name=d2.name
AND d1.cat=d2.cat
AND d1.time<d2.time
WHERE
d2.time IS NULL
It is very simple
SELECT MAX(TIME),name,cat FROM table name group by cat
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
My head is spinning. I have been struggling for the past two days to come up with a MySQL query that joins two tables. I've run into several complications.
The first table, we'll call it log, has a log of people which have "signed in". Each entry is timestamped with datetime. It consists of a student_id and timestamp. It looks something like this
-------------------------------------
| student_id | timestamp |
| 1234 | 2014-02-26 21:50:27 |
| 2345 | 2014-02-26 21:54:54 |
| 1234 | 2014-03-03 19:18:18 |
| .....etc. |
-------------------------------------
My second table, we'll call it students has the information on each student.
--------------------------------------------------------
| student_id | name | homeroom |
| 1234 | Charles Reinmuth | Swatosh |
| 2345 | Kathryn Mo | Green |
| 6789 | Emily Salt | Clayborne |
| .....etc. |
--------------------------------------------------------
I want to return students, within a certain datetime range, that have fewer than X entries in the log. INCLUDING students with no entries within that datetime range. My current query works well. But only returns students with >0 entries in the log. I've tried LEFT OUTER and RIGHT OUTER but to no avail. Here is my current query:
SELECT students.name, students.homeroom, COUNT(1) AS cnt
FROM students
INNER JOIN log ON log.student_id = students.student_id
WHERE log.`datetime` between '2014-02-25 00:00:00' and '2014-03-04 00:00:00'
GROUP BY log.student_id HAVING COUNT(1) < 3
This will return, in the case of the example above:
--------------------------------------------------
| Name | Homeroom | Count |
| Charles Reinmuth | Swatosh | 2 |
| Kathryn Mo | Green | 1 |
| .....etc. |
--------------------------------------------------
This is what I am trying to accomplish:
--------------------------------------------------
| Name | Homeroom | Count |
| Charles Reinmuth | Swatosh | 2 |
| Kathryn Mo | Green | 1 |
| Emily Salt | Clayborne | 0 |
| .....etc. |
--------------------------------------------------
To get rows with no matches in the second table, you need to use LEFT JOIN.
SELECT students.name, students.homeroom, COUNT(log.student_id) AS cnt
FROM students
LEFT JOIN log
ON log.student_id = students.student_id
AND log.`datetime` between '2014-02-25 00:00:00' and '2014-03-04 00:00:00'
GROUP BY students.student_id
HAVING cnt < 3
Note the other changes I made:
COUNT(log.student_id) - Otherwise, you'll count the row from the students table, even though there's no log row. COUNT(column) doesn't count null values, which is what you get when there's no match.
The log.datetime test must be moved into the ON clause, because it will be NULL for students with no rows in log.
I changed the HAVING clause to use the cnt alias, to avoid having to repeat COUNT(log.student_id).
I'd like a little help here.
I'm building a database in MySQL where I will have a bunch of different activities. Each activity is part of a list.
So, I have the following tables on my database.
List
id
name
Activity
id
name
idList (FK to List)
I also want to know when each activity is finished (you can finish the same activity many times). To accomplish that, I have another table:
History
date
idActivity (FK to activity)
When the user finishes an activity, I add the id of this activity and the current time the activity was finished, to the History table.
I want to get the entire list with the date it was finished. When an activity has not been finished, I want it to show the date as null.
But, getting the list just once is easy. A simple Left Outer Join will do the trick. My issue here is that I want to get the ENTIRE list everytime a date appears on the history table.
This is what I'm looking for:
List:
id | name
1 | list1
Activity:
id | name | idList
1 | Activity1 | 1
2 | Activity2 | 1
3 | Activity3 | 1
4 | Activity4 | 1
5 | Activity5 | 1
6 | Activity6 | 1
History:
date | idActivity
17/07/14 | 1
17/07/14 | 3
17/07/14 | 4
17/07/14 | 6
16/07/14 | 2
16/07/14 | 3
16/07/14 | 5
Expected Result:
idActivity | idList | activityName | date
1 | 1 | Activity1 | 17/07/14
2 | 1 | Activity2 | NULL
3 | 1 | Activity3 | 17/07/14
4 | 1 | Activity4 | 17/07/14
5 | 1 | Activity5 | NULL
6 | 1 | Activity6 | 17/07/14
1 | 1 | Activity1 | NULL
2 | 1 | Activity2 | 16/07/14
3 | 1 | Activity3 | 16/07/14
4 | 1 | Activity4 | NULL
5 | 1 | Activity5 | 16/07/14
6 | 1 | Activity6 | NULL
The "trick" is to use a CROSS JOIN (or semi-cross join) operation with a distinct list of dates from the history table, to produce the set of rows you want to return.
Then a LEFT JOIN (outer join) to the history table to find the matching history rows.
Something like this:
SELECT a.id AS idActivity
, a.idList AS idList
, a.name AS activityName
, h.date AS `date`
FROM activity a
CROSS
JOIN ( SELECT s.date
FROM history s
GROUP BY s.date
) r
LEFT
JOIN history h
ON h.idActivity = a.id
AND h.date = r.date
ORDER
BY r.date
, a.id
That query gets the six rows from activity, and two rows (distinct values of date) from history (inline view aliased as r). The CROSS JOIN operation matches each of the six rows with each of the two rows, to produce a Cartesian product of 12 rows.
To get the rows returned in the specified order, we order by date, and then by activity.id.