I have a single MySQL table with the name 'checkins' and 4 columns.
id | userIDFK | checkin_datetime | shopId
------------------------------------------------
1 | 1 | 2018-01-18 09:44:00 | 3
2 | 2 | 2018-01-18 10:32:00 | 3
3 | 3 | 2018-01-18 11:19:00 | 3
4 | 1 | 2018-01-18 17:57:00 | 3
5 | 1 | 2018-01-18 16:31:00 | 1
6 | 1 | 2018-01-19 08:31:00 | 3
Basically I want to find rows where users have checked-in more than once (>=2) on the same day and the same shop. So for instance if a user checks-in as in rows with ids 1 and 4 (same user, same day, same shop), the query should return a hit with the the entire rows (id, userIDFK, checkin_datetime, shopId). Hope this makes sense.
I already tried using
SELECT id, userIDFK, checkin_datetime, shopId
FROM (
SELECT * FROM 'checkins' WHERE COUNT(userIDFK)>=2 AND COUNT(shopId)>=2
)
The same day part I have no clew how to do it, and I know this query is way off, but this is the best I could.
You can try grouping by userId checkin_date and shopID
SELECT userIDFK, checkin_datetime, shopId,COUNT(SHOPiD)
FROM checkins
GROUP BY userIDFK, DATE(checkin_datetime), shopId
HAVING COUNT(SHOPID)>1
EDIT
You can include a subquery to get all lines:
select b.id,b.userIDFK, b.checkin_datetime, b.shopId
from checkins b
where (SELECT COUNT(SHOPiD)
FROM checkins a
where a.userIDFK=b.userIDFK and date(a.checkin_datetime)=date(b.checkin_datetime) and a.shopId=b.a.shopId
GROUP BY userIDFK, DATE(checkin_datetime), shopId)>1
GROUPBY can be used to get the multiple occurrences.
SELECT id, userIDFK, checkin_datetime, shopId
FROM checkins
GROUP BY userIDFK, DATE(checkin_datetime), shopId
HAVING count(id) > 1;
Hope it helps!
EDIT:
Using inner join you can achieve it. Here is the query:
SELECT c1.* FROM checkins c1 INNER JOIN checkins c2
ON c1.userIDFK = c2.userIDFK
AND date(c1.checkin_datetime) = date(c2.checkin_datetime)
AND c1.shopId = c2.shopId
AND c1.id != c2.id
Cheers!!
Related
A more generic title for this post would be
MySql Sum different columns in same table based on value of another row, group by yet another row
I have a table of employee expenses:
id | employee_id | expense_cat_id | expense_amount |
1 | 11 | 1 | 100 |
2 | 11 | 1 | 200 |
3 | 12 | 1 | 120 |
4 | 12 | 1 | 140 |
5 | 11 | 2 | 5 |
6 | 12 | 2 | 8 |`
and I want to produce a report like this:
Employee Id | Expense Cat 1 Total Amount | Expense Cat 2 Total Amount
11 | 300 | 5
12 | 260 | 8
So initially I thought I could use 2 table aliases for the same table like this:
SELECT
employee_id,
sum(expense_cat_1.expense_amount) as expense_1_total,
sum(expense_cat_2.expense_amount) as expense_2_total
FROM
expenses as expense_cat_1 where expense_cat_1.expense_cat_id=1 ,
expenses as expense_cat_2 where expense_cat_2.expense_cat_id=2
group by employee_id
but this was not correct Sql Syntax, which makes sense to me.
So I thought I could do two joins on between employee table and the expenses table:
SELECT
employees.id as employee_id,
sum(expenses_cat_1.expense_amount) as expense_1_total,
sum(expenses_cat_2.expense_amount) as expense_2_total
FROM employees
join expenses as expenses_cat_1 on employees.id = expenses_cat_1.employee_id and expenses_cat_1.expense_cat_id=1
join expenses as expenses_cat_2 on employees.id = expenses_cat_2.employee_id and expenses_cat_2.expense_cat_id=2
group by employees.id
Which comes close, but is wrong:
employee_id | expense_1_total | expense_2_total
11 | 300 | 10
12 | 260 | 16
as the expense 2 total is doubled! I think this is because the join on shows up two rows for each of the two expenses with category 1, and sums them.
I also tried a sub-query approach:
SELECT (SELECT sum(expense_amount)
FROM expenses
WHERE expense_cat_id = 1) AS sum1 ,
(SELECT sum(expense_amount)
FROM expenses
WHERE expense_cat_id = 2) AS sum2,
employee_id
FROM expenses group by employee_id
but this has the same problem as the join approach - totals for cat 2 are doubled.
How do I make the second join only include the expense_2_total once ???
I have a personal dislike of sql case statements as they seem more of a procedural language construct (and sql is declarative), but am happy to consider their use in this case - but I put the challenge out there for sql experts to solve this elegantly.
You are looking for conditional aggregation:
SELECT employee_id,
sum(case when expense_cat_id = 1 then expense_amount else 0 end) as expense_1_total,
sum(case when expense_cat_id = 2 then expense_amount else 0 end) as expense_2_total
FROM expenses e
GROUP BY employee_id;
I am unable to map the record as my expectation.
Doc Table
-------+-------------------
doc_id | doc_title
-------+-------------------
1 | My book
-------+-------------------
2 | My sec Book
--------------------------
Doc details Table
-----------+--------------+-----------------------
fk_doc_id | doc_version | submit_date
-----------+--------------+-----------------------
1 | 1 | 2015-10-25 14:32:01
-----------+--------------+-----------------------
1 | 2 | 2015-10-26 13:00:01
-----------+--------------+-----------------------
1 | 3 | 2015-10-27 09:00:00
--------------------------+-----------------------
2 | 1 | 2015-10-25 11:15:01
-----------+--------------+-----------------------
2 | 2 | 2015-10-26 10:00:00
--------------------------+-----------------------
Question: How do I join this two tables to get each documents with the latest version doc info? even though I get the latest version but the row info which is not correct.
So far I have tried this query
SELECT *, max(doc_version) AS latest_version
FROM d_doc
JOIN d_doc_dtl ON d_doc.doc_id = d_doc_dtl.fk_doc_id
GROUP BY d_doc.doc_id;
My expected result is
--------+--------------+----------------+--------------------
doc_id | doc_title | latest_version | submit_date
--------+--------------+----------------+--------------------
1 | My book | 3 | 2015-10-27 09:00:00
--------+--------------+----------------+--------------------
2 | My sec book | 2 | 2015-10-26 10:00:00
----------------------------------------+--------------------
but my result is
--------+--------------+----------------+--------------------
doc_id | doc_title | latest_version | submit_date
--------+--------------+----------------+--------------------
1 | My book | 3 | 2015-10-25 14:32:01
--------+--------------+----------------+--------------------
2 | My sec book | 2 | 2015-10-25 11:15:01
----------------------------------------+--------------------
NOTE: the submit_date which is no correct.
SELECT d_doc.doc_id, d_doc.doc_title, max_table.latest_version
FROM d_doc JOIN (
select fk_doc_id, max(doc_version) as latest_version from d_doc_dtl group by fk_doc_id
) as max_table ON d_doc.doc_id = max_table.fk_doc_id
This query should work as you expect. It selects latest document versions in inner subquery and than joins it with documents.
SELECT d.doc_id,
d.doc_title,
dtl.doc_version latest_version,
dtl.submit_date
FROM d_doc d
INNER JOIN (SELECT dt.*
FROM d_doc_dtl dt
INNER JOIN (SELECT fk_doc_id, MAX(doc_version) doc_version
FROM d_doc_dtl
GROUP BY fk_doc_id) dm
ON dt.fk_doc_id = dm.fk_doc_id
AND dt.doc_version = dm.doc_version) dtl
ON d.doc_id = dtl.fk_doc_id
You get wrong results because you selected only max(version), but date as it is not in group by clause can contain any value. First you need to get records containing latest version as shown above.
Easy, instead of
SELECT *, max(doc_version) AS latest_version
Use this
SELECT d_doc.*, max(doc_version) AS latest_version
What you were doing by selecting * is getting all the results after the table is joined and you only wanted the original table results.
select * from doc_table , doc_version where exists( select
max(version_id)
from
doc_version vert
where
(doc_table .DOC_ID = vert.VERSION_DOC_ID) ) group by doc_id;
You can try something like this.
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.
I have the following table:
user_id | Membership_type | start_Date
1 | 1 | 1
1 | 1 | 2
1 | 2 | 3
1 | 3 | 4
with several users, and i need to find out for each user when the membership type changes and what the change is, in the following format (start date is datetime, put it here in int for ease of understanding)
user_id |Membership_change| change_Date
1 | 1 to 2 | 3
1 | 2 to 3 | 4
I have tried
select m1.user_id, concat(m1.Membership_type, ' to ',m2.Membership_type), m2.start_date
from table_membership m1
join table_membership m2
on m1.user_id=m2.user_id and m1.start_date<m2.start_date and m1.membership_type<>m2.membership_type
but this will return
user_id |Membership_change| change_Date
1 | 1 to 2 | 3
1 | 1 to 2 | 3
1 | 1 to 3 | 4
1 | 2 to 3 | 4
The duplicate 1 to 2 is not a problem to remove through a grouping, but I cannot seem to be able to think of a way to avoid having the 1 to 3 result. I basically just need to join chronologically from one membership to the next
Any ideas would be appreciated!
Edit: Had an idea to add the column m1.start_date and group by account_id and m1.start_date, so I would only get the first row where each entry is joined. Also a pre-sort by date before the joins, to make sure they are all in order. Will test.
You are missing GROUP BY
select
m1.user_id,
concat(m1.Membership_type, ' to ',m2.Membership_type),
m2.start_date
from table_membership m1
join table_membership m2
on m1.user_id = m2.user_id
and m1.start_date < m2.start_date
and m1.membership_type <> m2.membership_type
GROUP BY user_id, Membership_change, change_Date
Had an idea to add the column m1.start_date and group by account_id and m1.start_date, so I would only get the first row where each entry is joined. Also a pre-sort by date before the joins, to make sure they are all in order.
select m.user_id, m.membership_change, m.change_date from
(
select
m1.user_id,
concat(m1.Membership_type, ' to ',m2.Membership_type) as membership_change,
m2.start_date as change_date,
m1.start_date
from (select * from table_membership order by start_date asc)m1
join (select * from table_membership order by start_date asc)m2
on m1.user_id = m2.user_id
and m1.start_date < m2.start_date
and m1.membership_type <> m2.membership_type
GROUP BY m1.user_id, m1.start_Date
)m group by 1,2,3
Given the following (simplified) tables:
People p
id name registered
-----------------------------------
1 Geoff 2011-03-29 12:09:08
2 Phil 2011-04-29 09:03:54
3 Tony 2011-05-29 21:22:23
4 Gary 2011-06-21 22:56:08
...
Items i
date p1id p2id
----------------------------------------
2011-06-29 20:09:44 1 2
2011-06-26 10:45:00 1 3
2011-06-23 12:22:43 2 3
2011-06-22 13:07:12 2 4
...
I'd like:
The earliest single i.date that each p.id appears in either column p1id or p2id; or p.registered if they feature in neither.
So far, I've tried:
CREATE TEMPORARY TABLE temp (id INT);
INSERT INTO temp (id)
SELECT DISTINCT u FROM (
SELECT p1id AS u FROM Items UNION ALL
SELECT p2id AS u FROM Items
)tt;
SELECT registered,id FROM People
WHERE id NOT IN (SELECT id FROM temp);
Which gets me as far as the second part, albeit in a fairly clumsy way; and I'm stuck on the first part beyond some sort of external, scripted iteration through all the values of p.id (ugh).
Can anyone help?
I'm on MySQL 5.1 and there's ~20k people and ~100k items.
One more solution:
SELECT id, name, IF(min_date1 IS NULL AND min_date2 IS NULL, registered, LEAST(COALESCE(min_date1, min_date2), COALESCE(min_date2, min_date1))) date FROM (
SELECT p.id, p.name, p.registered, MIN(i1.date) min_date1, MIN(i2.date) min_date2 FROM people p
LEFT JOIN items i1
ON p.id = i1.p1id
LEFT JOIN items i2
ON p.id = i2.p2id
GROUP BY id
) t;
OR this:
SELECT p.id, p.name, COALESCE(MIN(i.date), p.registered) FROM people p
LEFT JOIN (
SELECT p1id id, date FROM items
UNION ALL
SELECT p2id id, date FROM items
) i
ON p.id = i.id
GROUP BY id;
Result:
+------+-------+---------------------+
| id | name | date |
+------+-------+---------------------+
| 1 | Geoff | 2011-06-26 10:45:00 |
| 2 | Phil | 2011-06-22 13:07:12 |
| 3 | Tony | 2011-06-23 12:22:43 |
| 4 | Gary | 2011-06-22 13:07:12 |
+------+-------+---------------------+
This is tested in Postgres, but I think it ought to work in MySQL with few or no changes:
SELECT p.id,COALESCE(MIN(x.date),p.registered) AS date
FROM p
JOIN (
SELECT p.id,MIN(i.date) AS date
FROM p
JOIN i ON (p.id=i.p1id)
GROUP BY p.id
UNION
SELECT p.id,MIN(i.date) AS date
FROM p
JOIN i ON (p.id=i.p2id)
GROUP BY p.id
) AS x ON x.id = p.id
GROUP BY p.id,p.registered;
Output (given your sample data):
id | date
----+---------------------
3 | 2011-06-23 12:22:43
1 | 2011-06-26 10:45:00
2 | 2011-06-22 13:07:12
4 | 2011-06-22 13:07:12
(4 rows)