I am stuck in a problem explained below:
id | user_id | admin
1 | 1 | 2
2 | 1 | 5
3 | 2 | 5
4 | 2 | 5
5 | 3 | 5
6 | 4 | 5
I need to write a query to get all the users who are associated with an admin id passed in WHERE clause AND has more than one transactions(can be with another admin).
If user_id is 5 then result should come
user_id
1
2
Have tried
SELECT
user_id ,
COUNT(*) count
FROM
table
WHERE admin = 5
GROUP BY
user_id
Having
count > 1
but this above-mentioned query skips the user_id that has only one transaction with admin = 5 and has another row with another admin.
Suggestions?
Aggregate on the user_id and assert that:
The admin of interest (e.g. 5) appears,
Two or more admins of any type appear
SELECT user_id
FROM yourTable
GROUP BY user_id
HAVING SUM(CASE WHEN admin = 5 THEN 1 ELSE 0 END) > 0 AND
COUNT(*) > 1;
Demo
Try this
SELECT user_id from `tablename` WHERE admin IN( SELECT admin FROM `tablename` GROUP BY admin HAVING count(*) > 1)
In your query filter on admin=5 and with a separate subquery on those users, who have more than 1 transactions. You need to have the count in a subquery because the admin=5 criterion does not apply to the count.
SELECT DISTINCT user_id
FROM yourTable
WHERE admin_id=5
AND user_id IN (SELECT user_id FROM yourTable GROUP BY user_id HAVING COUNT(*)>1)
SELECT user_id
FROM test_table1
GROUP BY user_id
HAVING SUM(CASE WHEN admin = &n THEN 1 ELSE 0 END ) > 0
AND
COUNT(user_id) > 1;
If I understand your question, you want to ask:
Show me all users who have more than one admin associated
Also allow me to identify which admin specifically is in question (in your example 5)
This would give you a list of every user (user id only)
SELECT DISTINCT table.user_id
FROM table
JOIN (
SELECT user_id
,COUNT(DISTINCT admin_id) num_admins
FROM table
GROUP BY user_id
HAVING COUNT(DISTINCT admin_id) > 1
) multi_admin
ON table.user_id = multi_admin.user_id
WHERE admin = 5 -- if you want to look only at user_ids somehow associated with
Related
Two tables are used in this query, and all that matters in the result is the number of users which have or haven't posted any comments so far. The table user of course has the column id, which is the foreign key in the table comment, identified by the column user_id.
The first super-simple query groups users by whether or not they have any comments so far. It outputs two rows (a row with the user count who have comments, and a row with the user count who have no comments), with two columns (number of users, and whether or not they have posted any comments).
SELECT
COUNT(id) AS user_count,
IF( id IN ( SELECT user_id FROM `comment` ), 1, 0) AS has_comment
FROM `user`
GROUP BY has_comment
An example of how the output would look like here:
+------------+-------------+
| user_count | has_comment |
+------------+-------------+
| 150 | 0 |
| 140 | 1 |
+------------+-------------+
Now here comes my question. I want slightly more information here, by grouping these users into 3 groups instead:
Users that have posted no comments
Users that have posted fewer than 10 comments
Users that have posted 10 or more comments
And the best query that I know how to write for this purpose is as follows, which works, but unfortunately runs 4 subqueries and has 2 derived tables:
SELECT
COUNT(id) AS user_count,
CASE
WHEN id IN ( SELECT user_id FROM ( SELECT COUNT(user_id) AS comment_count, user_id FROM `comment` GROUP BY user_id HAVING comment_count >= 10 ) AS a) THEN '10 or more'
WHEN id IN ( SELECT user_id FROM ( SELECT COUNT(user_id) AS comment_count, user_id FROM `comment` GROUP BY user_id HAVING comment_count < 10 ) AS b) THEN 'less than 10'
ELSE 'none'
END AS has_comment
FROM `user`
GROUP BY has_comment
An example of the output here would be something like:
+------------+-------------+
| user_count | has_comment |
+------------+-------------+
| 150 | none |
| 130 | less than 10|
| 100 | 10 or more |
+------------+-------------+
This second query; can it be written more simply and efficiently, and still produce the same kind of result? (potentially maybe even be expanded into more of these kinds of "groups")
You can use two levels of aggregation:
select
count(*) no_users,
case
when no_comments = 0 then 'none'
when no_comments < 10 then 'less than 10'
else '10 or more'
end has_comment
from (
select
u.id,
(select count(*) from comments c where c.user_id = u.id) no_comments
from users u
) t
group by has_comment
order by no_comments
The subquery counts how many comments each user has (you could also express this with a left join and aggregation); then, the outer query classifies and count the users per number of comments.
I have a typical user table in addition to the following feature table
features:
-----------------------
| userId | feature |
-----------------------
| 1 | account |
| 1 | hardware |
| 2 | account |
| 3 | account |
| 3 | hardware |
| 3 | extra |
-----------------------
Basically I am trying to get some counts for reporting purposes. In particular, I am trying to find the number of users with accounts and hardware along with the total number of accounts.
I know I can do the following to get the total number of accounts
SELECT
COUNT(DISTINCT userId) as totalAccounts
FROM features
WHERE feature = "account";
I am unsure as to how to get the number of users with both accounts and hardware though. In this example dataset, the number I am looking for is 2. Users 1 and 3 have both accounts and hardware.
I would prefer to do this in a single query. Possibly using CASE (example for totalAccounts below):
SELECT
COUNT(DISTINCT(CASE WHEN feature = "account" THEN userId END)) as totalAccounts,
COUNT( ? ) as accountsWithHardware
FROM features;
These are two queries - one for the all user count, one for the two-features user count - that you can combine with a cross join:
select
count_all_users.cnt as all_user_count,
count_users_having_both.cnt as two_features_user_count
from
(
select count(distinct userid) as cnt
from features
) count_all_users
cross join
(
select count(*) as cnt
from
(
select userid
from features
where feature in ('account', 'hardware')
group by userid
having count(*) = 2
) users_having_both
) count_users_having_both;
UPDATE: With some thinking, there is a much easier way. Group by user and detect whether feature 1 and feature 2 exists. Then count.
select
count(*) as all_user_count,
count(case when has_account = 1 and has_hardware = 1 then 1 end)
as two_features_user_count
from
(
select
userid,
max(case when feature = 'account' then 1 else 0 end) as has_account,
max(case when feature = 'hardware' then 1 else 0 end) as has_hardware
from features
group by userid
) users;
I have a table called team_members with this structure and contents:
+---------+---------+
| team_id | user_id |
+---------+---------+
| 1 | 18 |
+---------+---------+
| 1 | 7 |
+---------+---------+
| 3 | 18 |
+---------+---------+
What i am trying to do is to find a team that only contains 2 users and this users are supplied by me (in this case users with id 7 and 18). Unfortunately, i am having no ideas about how to make this query properly. I have tried something like
SELECT a.team_uid
FROM team_members a
INNER JOIN (
SELECT team_uid, user_id, COUNT(*) cnt_team
FROM team_members
GROUP BY team_uid
HAVING COUNT(*) = 2
) b ON a.user_id = b.user_id
Use Case statement in Having clause and Count only the required user_id's. Try this.
select teamid from yourtable
group by teamid
having count(case when userid=7 then 1 end)=1
and count(case when userid=18 then 1 end)=1
and count(1)=2
Something to think about (and assuming a PK on team_id,user_id)...
SELECT x.*, COUNT(*),SUM(user_id IN(7,18)) FROM my_table x GROUP BY team_id;
A couple more ways to do this (where $id1 and $id2 are the users in question):
SELECT team_id
FROM team_members
GROUP BY team_id
HAVING COUNT(*) = 2
AND MIN(user_id) = LEAST($id1,$id2)
AND MAX(user_id) = GREATEST($id1,$id2)
See SQL Fiddle Demo here with values of 7 and 18 for $id1 and $id2. I am using LEAST() and GREATEST() in case it's not known which is the higher and which is the lower (for example, if they're coming from user input).
SELECT team_id
FROM team_members
GROUP BY team_id
HAVING GROUP_CONCAT(user_id ORDER BY user_id) = ('7,18')
See SQL Fiddle Demo here. Again, if it isn't known which is the higher and which is the lower, then this might be written as (the ORDER BY in GROUP_CONCAT() would be unnecessary):
SELECT team_id
FROM team_members
GROUP BY team_id
HAVING GROUP_CONCAT(user_id) IN ('$id1,$id2','$id2,$id1')
You can also use:
select team_id
from team_members
group by team_id
having sum(user_id not in(7, 18)) = 0
Example above assumes you want teams with only users 7 or 18 (no others, but not necessarily both).
If you want teams with BOTH users 7 and 18, and no others, you can use:
select team_id
from team_members
group by team_id
having sum(user_id not in(7, 18)) = 0
and sum(user_id in(7, 18)) = 2
I am trying to select jobs that are not currently assigned to a user.
Users table: id | name
Jobs: id | name
Assigned: id | user_id | job_id | date_assigned
I want to select all the jobs that are not currently taken. Example:
Users:
id | name
--------------
1 | Chris
2 | Steve
Jobs
id | name
---------------
1 | Sweep
2 | Skids
3 | Mop
Assigned
id | user_id | job_id | date_assigned
-------------------------------------------------
1 | 1 | 1 | 2012-01-01
2 | 1 | 2 | 2012-01-02
3 | 2 | 3 | 2012-01-05
No two people can be assigned the same job. So the query would return
[1, Sweep]
Since no one is working on it since Chris got moved to Skids a day later.
So far:
SELECT
*
FROM
jobs
WHERE
id
NOT IN
(
SELECT
DISTINCT(job_id)
FROM
assigned
ORDER BY
date_assigned
DESC
)
However, this query returns NULL on the same data set. Not addressing that the sweep job is now open because it is not currently being worked on.
SELECT a.*
FROM jobs a
LEFT JOIN
(
SELECT a.job_id
FROM assigned a
INNER JOIN
(
SELECT MAX(id) AS maxid
FROM assigned
GROUP BY user_id
) b ON a.id = b.maxid
) b ON a.id = b.job_id
WHERE b.job_id IS NULL
This gets the most recent job per user. Once we have a list of those jobs, we select all jobs that aren't on that list.
You can try this variant:
select * from jobs
where id not in (
select job_id from (
select user_id, job_id, max(date_assigned)
from assigned
group by user_id, job_id));
I think you might want:
SELECT *
FROM jobs
WHERE id NOT IN (SELECT job_id
from assigned
where user_id is not null
)
This assumes that re-assigning someone changes the user id on the original assignment. Does this happen? By the way, I also simplified the subquery.
First you need to be looking at a list of only current job assignments. Ordering isn't enough. The way you have it set up, you need a distinct subset of job assignments from Assigned that are the most recent assignments.
So you want a grouping subquery something like
select job_id, user_id, max(date_assigned) last_assigned from assigned group by job_id, user_id
Put it all together and you get
select id, name from jobs
where id not in (
select job_id as id from (
select job_id, user_id, max(date_assigned) last_assigned from assigned
group by job_id, user_id
)
)
As an extra feature, you could pass up the value of "last_assigned" and it would tell you how long a job has been idle for.
I have a table called user_scores as below:
id | af_id | uid | level | record_date
----------------------------------------
1 | 1.1 | 1 | 3 | 2012-01-01
2 | 1.1 | 1 | 4 | 2012-02-01
3 | 1.2 | 1 | 3 | 2012-01-01
4 | 1.2 | 1 | 5 | 2012-03-01
...
I have another table call user_info as below:
uid | forename | surname | gender
-----------------------------------
1 | Homer | Simpson | M
2 | Marge | Simpson | F
3 | Bart | Simpson | M
4 | Lisa | Simpson | F
...
In user scores uid is the user id of a registered user on the system, af_id identifies a particular test a user submits. A user scores a level between 1 - 5 for each test, which can be submitted every month.
My problem is I need to produce an analysis at the end of the year to COUNT the number of users that have achieved each level for a particular test. The analysis is to show a gender split for male and female.
So for example an administrator would select test 1.1 and the system would generate stats based that would COUNT of the total MAX level achieved by each user in the year, with a gender split.
Any help is much appreciated. Thank you in advance.
-
I think I need to clarify myself a bit. Because a user can complete the test multiple times throughout the year, there will be multiple scores for the same test. The query should take the highest level achieved and include this in the count. An example result would be:
Male Results:
level1 | level2 | level3 | level4 | level5
------------------------------------------
2 | 5 | 10 | 8 | 1
I am not certain I get exactly what you mean, but as always I'll have a go. As I understand it you want to know how many people from each gender reached each level in a certain year.
SELECT MaxLevel,
COUNT(CASE WHEN ui.Gender = 'M' THEN 1 END) AS Males,
COUNT(CASE WHEN ui.Gender = 'F' THEN 1 END) AS Females
FROM User_Info ui
INNER JOIN
( SELECT MAX(Level) AS MaxLevel,
UID
FROM User_Scores us
WHERE af_ID = '1.1'
AND YEAR(Record_Date) = 2012
GROUP BY UID
) AS MaxUs
ON MaxUs.uid = ui.UID
GROUP BY MaxLevel
I've put some sample data on SQL Fiddle so you see if it is what you were after.
EDIT
To transpose the data so levels are along the top and Gender in the rows the following will work:
SELECT Gender,
COUNT(CASE WHEN MaxLevel = 1 THEN 1 END) AS Level1,
COUNT(CASE WHEN MaxLevel = 2 THEN 1 END) AS Level2,
COUNT(CASE WHEN MaxLevel = 3 THEN 1 END) AS Level3,
COUNT(CASE WHEN MaxLevel = 4 THEN 1 END) AS Level4,
COUNT(CASE WHEN MaxLevel = 5 THEN 1 END) AS Level5
FROM User_Info ui
INNER JOIN
( SELECT MAX(Level) AS MaxLevel,
UID
FROM User_Scores us
WHERE af_ID = '1.1'
AND YEAR(Record_Date) = 2012
GROUP BY UID
) AS MaxUs
ON MaxUs.uid = ui.UID
GROUP BY Gender
Note, that if there are ever more than 5 levels you will need to add more to the select statement, or start building dynamic SQL.
Assuming record_date holds only dates (without time parts):
SELECT
s.maxlevel,
COUNT(NULLIF(gender, 'F')) AS M,
COUNT(NULLIF(gender, 'M')) AS F
FROM user_info u
INNER JOIN (
SELECT
uid,
MAX(level) AS maxlevel
FROM user_scores
WHERE record_date > DATE_SUB(CURDATE(), INTERVAL DAYOFYEAR(CURDATE()) DAY)
AND af_id = '1.1'
GROUP BY
uid
) s ON s.uid = u.uid
GROUP BY
s.maxlevel
That will show you only the maximum levels found in the user_scores table. If you have a Levels table where all possible levels (1 to 5) are listed, you could use that table to get a complete list of levels. If some levels are not present in the requested subset of data, the corresponding rows will show 0s in both columns.
Here's the above script with minor changes to show the complete chart of levels:
SELECT
l.level AS maxlevel,
COUNT(NULLIF(gender, 'F')) AS M,
COUNT(NULLIF(gender, 'M')) AS F
FROM user_info u
INNER JOIN (
SELECT
uid, MAX(level) AS maxlevel
FROM user_scores
WHERE record_date > DATE_SUB(CURDATE(), INTERVAL DAYOFYEAR(CURDATE()) DAY)
AND af_id = '1.1'
GROUP BY
uid
) s ON s.uid = u.uid
RIGHT JOIN Levels l ON s.maxlevel = l.level
GROUP BY
l.level
Hope this is what your looking for!
Show number of records group by userid and gender of the max score for af_id '1.1'.
select count(*), info.uid, info.gender, max(score.level)
from user_info as info
join user_scores as score
on info.uid = score.uid
where score.af_id = '1.1'
group by info.uid, info.gender;
EDITED based on your edit.
select sum(if(a.gender="M",1,0)) Male_users, sum(if(a.gender="F",1,0)) Female_users
from myTable a where
a.level = (select max(b.level) from myTable b where a.uid=b.uid)
group by af_id.
I typed this in a rush. But it should work or at least get you where you need to go. E.G. if you need to specify time frame, add that.
You need something like
SELECT
uid,
MAX(level)
WHERE
record_date BETWEEN '2012-01-01' AND '2012-12-31'
AND af_id='1.1'
GROUP BY uid
If you need the gender splits then depending on what stat you need per gender you can either add a JOIN on the user_info table into this query (to get the MAX per gender) to wrap this as a sub-query and JOIN on the whole thing.