I'm trying to get this piece of SQL query to work. The problem is that I can't access core_user.id in the stuff in its parameters. I don't really know why.
SELECT
core_user.id AS target, (
SELECT
COUNT(permission_id) AS permissions
FROM (
SELECT
DISTINCT permission_id
FROM (
SELECT
id,
permission_id
FROM
core_user_user_permissions
WHERE
core_user_user_permissions.user_id = core_user.id
UNION
SELECT
id,
permission_id
FROM
auth_group_permissions
WHERE
auth_group_permissions.group_id IN (
SELECT
group_id
FROM
core_user_groups
WHERE
core_user_groups.user_id = core_user.id)) AS `permissions`) AS `derived`) AS `perms`
FROM
`core_user`
WHERE
`core_user`.`is_active` = TRUE
GROUP BY
`core_user`.`id`
ORDER BY
`perms` ASC
If i try target.id with core_user AS target it does not work either: Unknown column 'target.id' in 'where clause'
Expected Results:
+----------------------------------+-------+
| id | perms |
+----------------------------------+-------+
| ab7ec54bf9124dffb807fb89f9ea8036 | 0 |
| b54d4d3f97134dfcbc36ac193c0c1250 | 81 |
| c69ffa4d162b49129ff6a316da3caaa3 | 64 |
| f8ac73eee80044359c246f3b173aa631 | 0 |
+----------------------------------+-------+
Any idea how to fix this?
It's not easy to understand what one wants, if there is only an (invalid) query and no description of what it should retrieve. So this is just a best guess:
SELECT cu.id target,
count(x.permission_id) perms
FROM core_user cu
LEFT JOIN (SELECT cuup.permission_id,
cuup.user_id
FROM core_user_user_permissions cuup
WHERE cuup.permission_id IS NOT NULL
UNION
SELECT agp.permission_id,
cug.user_id
FROM auth_group_permissions agp
INNER JOIN core_user_groups cug
ON cug.group_id = agp.group_id
WHERE agp.permission_id IS NOT NULL) x
ON x.user_id = cu.id
WHERE cu.is_active = TRUE
GROUP BY cu.id
ORDER BY perms ASC;
Instead of getting the count in a subselect it left joins the permission IDs for each user. Grouping by the user ID than gets the (distinct, because the union already removed any duplicates and id is also unique in core_user (I assume)) count of the non null permission IDs of a user, which is what I believe you want.
(You may remove the WHERE <alias>.permission_id IS NOT NULL in the inner query, if there are not null constraints on the columns. The idea here is, that nulls aren't counted in the end anyway, so we want to discard them as early as possible.)
(Untested, as neither schema nor sample data was provided. May contain typos.)
I would rewrite it as:
SELECT core_user.id AS target,
(SELECT COUNT(permission_id) AS permissions
FROM (SELECT permission_id
FROM core_user_user_permissions
WHERE core_user_user_permissions.user_id = `core_user`.id
UNION
SELECT permission_id
FROM auth_group_permissions
WHEREauth_group_permissions.group_id IN (
SELECT group_id
FROM core_user_groups
WHERE core_user_groups.user_id = `core_user`.id)
) AS `derived`
) AS `perms`
FROM `core_user`
WHERE `core_user`.`is_active` = TRUE
GROUP BY `core_user`.`id`
ORDER BY `perms`;
I've removed one level of nested subquery in SELECT list.
Well I hope I could get your expected result by excluding group premissons:
SELECT core_user.id AS target,
(
SELECT COUNT(DISTINCT(permission_id)) AS permissions
FROM
(
SELECT cuup.id as id, cuup.permission_id as permission_id
FROM core_user_user_permissions cuup
-- WHERE cuup.user_id = cu.id
--VV-- this should be a join
JOIN core_user cu ON cu.id = cuup.user_id
)
) AS `perms`
FROM `core_user`
WHERE `core_user`.`is_active` = TRUE
GROUP BY `core_user`.`id`
ORDER BY `perms` ASC
Related
I have two tables. My task is to choose the last person comment.
db_user (db_user_id, name, balance)
db_comment (db_comment_id, db_user_id, text)
My query:
SELECT db_user.name,db_comment.text
FROM db_user INNER JOIN db_comment ON db_user.db_user_id = db_comment.db_user_id
ORDER BY db_comment.db_user_id DESC
Tried to use LIMIT but failed.
A table with values has already been created here: http://sqlfiddle.com/#!9/badaf/14
My data sampling should receive the last comment (db comment.text) from each person (db_user.name).
Сondition, you cannot add new fields.
You can use the following solution using a additional JOIN:
SELECT dbu.name, dbc.text
FROM db_user dbu INNER JOIN (
SELECT MAX(db_comment_id) AS db_comment_id, db_user_id
FROM db_comment
GROUP BY db_user_id
) dbc_max ON dbu.db_user_id = dbc_max.db_user_id
INNER JOIN db_comment dbc ON dbu.db_user_id = dbc_max.db_user_id
AND dbc.db_comment_id = dbc_max.db_comment_id
ORDER BY dbu.db_user_id DESC
... or using a sub-select directly on the SELECT:
SELECT dbu.name, (
SELECT `text`
FROM db_comment dbc
WHERE dbu.db_user_id = dbc.db_user_id
ORDER BY dbc.db_comment_id DESC
LIMIT 1
) AS `text`
FROM db_user dbu
ORDER BY dbu.db_user_id DESC
demo on dbfiddle.uk
I would use a correlated subquery for filtering. In many cases, this is approach that has the best performance, especially with an index on db_comment(db_user_id, db_comment_id):
select u.name, c.text
from
db_user u
inner join db_comment c on c.db_user_id = u.db_user_id
where c.db_comment_id = (
select max(c1.db_comment_id)
from db_comment c1
where c1.db_user_id = c.db_user_id
)
This assumes that the last comment is the one that has the highest db_comment_id.
Updated demo on DB Fiddle
name text
Ivan Message3 ivan
Petr Message3 Petr
Artur Message2 Artur
John Message2 John
The last comment is the one with the highest ID. So, make sure there doesn't exist a higher one for the user:
SELECT
u.name,
c.text
FROM db_user u
JOIN db_comment c ON c.db_user_id = u.db_user_id
AND NOT EXISTS
(
SELECT *
FROM db_comment c2
WHERE c2.db_user_id = c.db_user_id
AND c2.db_comment_id > c.db_comment_id
);
Or work with a list of highest comment IDs per user:
SELECT
u.name,
c.text
FROM db_user u
JOIN db_comment c ON c.db_user_id = u.db_user_id
AND (c.db_user_id, c.db_comment_id) IN
(
SELECT db_user_id, MAX(db_comment_id)
FROM db_comment
GROUP BY db_user_id
);
As of MySQL 8 you can also use window function. E.g.:
SELECT
u.name,
c.text
FROM db_user u
JOIN
(
SELECT
db_user_id,
text,
ROW_NUMBER() OVER (PARTITION BY db_user_id ORDER BY db_comment_id DESC) AS rn
FROM db_comment
) c ON c.db_user_id = u.db_user_id AND c.rn = 1;
Demo: https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=2c2c67cdba80a992b593e4c74201fa61
Try it -
SELECT db_user.name,db_comment.text
FROM db_user
INNER JOIN db_comment
ON db_user.db_user_id = db_comment.db_user_id
ORDER BY db_user.db_user_id DESC, db_comment.db_comment_id desc
limit 1
OR
SELECT db_user.name,db_comment.text
FROM db_user
JOIN (
select db_comment_id as maxId, db_user_id, text
from db_comment
order by db_comment_id desc
limit 1
) as db_comment ON db_comment.db_user_id = db_user.db_user_id
ORDER BY db_user.db_user_id DESC`
if the limit is not working then you can use the MAX function and make join self table
Other approach without the need to use MAX(db_comment_id) or GROUP BY db_user_id and complety works by filtering
Query
SELECT
db_user.name
, db_comment1.text
FROM
db_comment db_comment1
LEFT JOIN
db_comment db_comment2
ON
db_comment1.db_user_id = db_comment2.db_user_id
AND
db_comment1.db_comment_id < db_comment2.db_comment_id
INNER JOIN
db_user
ON
db_comment1.db_user_id = db_user.db_user_id
WHERE
db_comment2.db_user_id IS NULL
Note: Using db_comment1.db_comment_id > db_comment2.db_comment_id instead would do MIN(db_comment_id) yes the operator direction can feel pretty bit counterintuitive and it is very easy to get wrong and write the wrong one (that's why i needed to edit mine answer...) , see demo..
Result
| name | text |
|-------|----------------|
| Ivan | Message3 ivan |
| Petr | Message3 Petr |
| Artur | Message2 Artur |
| John | Message2 John |
Performance note: it needs to have the INDEX(db_user_id, db_comment_id) on the db_comment table otherwise it will not be very fast. If you have that index, MySQL should be able to handle (very) large tables when running this query..
see demo
I have the following tables:
Users:
ID LastPaymentDate
1 2017-01-01
2 2018-02-05
3 2018-04-06
5 NULL
ActivityLog:
ID ActivityDate
1 2017-01-01
1 2017-05-17
3 2018-05-20
I need to find out the number of users that have LastPaymentDate but doesn't have matched ActivityDate
The output result for the above data is: 2 (UserID 3 and 2).
How can I do this?
We can try using a left join approach here:
SELECT u.ID, u.LastPaymentDate
FROM Users u
LEFT JOIN ActivityLog a
ON u.ID = a.ID AND u.LastPaymentDate = a.ActivityDate
WHERE
a.ID IS NULL AND u.LastPaymentDate IS NOT NULL;
Demo
Use NOT EXISTS:
SELECT COUNT(*)
FROM Users u
WHERE
u.LastPaymentDate IS NOT NULL
AND NOT EXISTS (
SELECT 1
FROM ActivityLog a
WHERE u.ID = a.ID AND u.ActivityDate = a.ActivityDate
)
The good thing about this approach is that it will not count several times the same record in Users, even if it has several matching record in the ActivityLog.
I had a problem similar to this. My resolution was to create views to display the fields and the count as columns. Then I did a join between the views to display the net results:
CREATE ALGORITHM=UNDEFINED DEFINER=MySQL CURRENT_USER() SQL SECURITY DEFINER VIEW `subcr_count_x` AS SELECT `x_subscriptions`.`user_id` AS `user_id`, count(0) AS `cnt` FROM `x_subscriptions` WHERE (`x_subscriptions`.`user_id` > 0) GROUP BY `x_subscriptions`.`user_id` ;
CREATE ALGORITHM=UNDEFINED DEFINER=MySQL CURRENT_USER() SQL SECURITY DEFINER VIEW `subcr_count_y` AS SELECT `y_subscriptions`.`user_id` AS `user_id`, count(0) AS `cnt` FROM `y_subscriptions` WHERE (`y_subscriptions`.`user_id` > 0) GROUP BY `y_subscriptions`.`user_id`;
To select the records where there isn't a match, it does this.
SELECT * FROM
`subcr_count_x` x INNER JOIN
`subcr_count_y` y ON x.user_id = y.user_id
WHERE x.cnt != y.cnt
I'm doing a pretty complicated reporting function with many conditions, many panels,
I have a record in tbl_my_report
id param_filter
101 FIND_IN_SET(t.owner_department,'0620510200,0621510200,0623510200')
Query:
SELECT *
FROM tbl_abc t WHERE t.id = '1' AND
(SELECT mr.param_filter
FROM tbl_my_report mr WHERE mr.id = '101'
)
How to use it as a valid condition string?
First, you should normalize your data, and get rid of FIND_IN_SET. So, there should be some department table looking like this:
id | owner
'101' | '0620510200'
'101' | '0621510200'
'101' | '0623510200'
With this table in place, you may refactor your query to:
SELECT *
FROM tbl_abc t
WHERE
t.id = '1' AND
EXISTS (SELECT 1 FROM department WHERE id = '101' AND owner = t.owner_department);
In general, you should avoid storing CSV or other unnormalized data in your database tables, for the very reason that it can make querying difficult.
Use this:
SELECT * FROM tbl_abc t WHERE t.id = '1' IN (SELECT mr.param_filter
FROM tbl_my_report mr WHERE mr.id = '101' );
Query with OR which outputs wrong
SELECT DISTINCT
sm___employees.id,
sm___employees.employee_code,
sm___employees.leaving_date,
sm___employees.name_of_employee,
sm___employees.position,
sm___employees.rating,
sm___employees.entry_date
FROM
sm___employees
JOIN
sm___employee_skills
ON
sm___employees.id=sm___employee_skills.employee_id
WHERE
((sm___employee_skills.skill_id=1 AND sm___employee_skills.ans LIKE '%MBA%')
**OR**
(sm___employee_skills.skill_id=5 AND sm___employee_skills.ans IN (3)))
AND
sm___employees.rating IN (1)
ORDER BY
sm___employee_skills.date DESC
But I want it by And
SELECT DISTINCT
sm___employees.id,
sm___employees.employee_code,
sm___employees.leaving_date,
sm___employees.name_of_employee,
sm___employees.position,
sm___employees.rating,
sm___employees.entry_date
FROM
sm___employees
JOIN
sm___employee_skills
ON
sm___employees.id=sm___employee_skills.employee_id
WHERE
((sm___employee_skills.skill_id=1 AND sm___employee_skills.ans LIKE '%MBA%')
**AND**
(sm___employee_skills.skill_id=5 AND sm___employee_skills.ans IN (3)))
AND
sm___employees.rating IN (1)
ORDER BY
sm___employee_skills.date DESC
When am using first query with OR of MBA or 3, It gives me result for both which is correct as per OR operation
I want only those records which are having MBA AND 3 which gives me blank records when there are records available with this comparison
So please help me to resolve this.
Thank you in advance
To start with: DISTINCT often indicates a badly written query. This is the case here. You are joining records only to dismiss them later. If you want employee records, then select from the employee table. If you have criteria on the skills table check this in the WHERE clause. Don't join.
Then the WHERE clause looks at one row at a time. So neither skill_id = ... AND skill_id = ... nor skill_id = ... OR skill_id = ... can work for you. You must look up the skills table twice:
SELECT
id,
employee_code,
leaving_date,
name_of_employee,
position,
rating,
entry_date
FROM sm___employees
WHERE rating IN (1)
AND id IN
(
SELECT employee_id
FROM sm___employee_skills
WHERE skill_id = 1 AND ans LIKE '%MBA%'
)
AND id IN
(
SELECT employee_id
FROM sm___employee_skills
WHERE skill_id = 5 AND ans IN (3)
);
And here is a way to look up skills just once:
SELECT
id,
employee_code,
leaving_date,
name_of_employee,
position,
rating,
entry_date
FROM sm___employees
WHERE rating IN (1)
AND id IN
(
SELECT employee_id
FROM sm___employee_skills
WHERE (skill_id = 1 AND ans LIKE '%MBA%')
OR (skill_id = 5 AND ans IN (3))
GROUP BY employee_id
HAVING COUNT(DISTINCT skill_id) = 2 -- both skills
);
It seems strange though that you consider ans to be a string in one place (ans LIKE '%MBA%') and a number in another (ans IN (3)).
UPDATE: If you want to sort by skill date, you should consider by which skill's date. For this to happen, you would join, but not join the skills table, but the skills aggregate result. E.g.:
SELECT
e.id,
e.employee_code,
e.leaving_date,
e.name_of_employee,
e.position,
e.rating,
e.entry_date
FROM sm___employees e
JOIN
(
SELECT employee_id, MAX(date) AS max_date
FROM sm___employee_skills
WHERE (skill_id = 1 AND ans LIKE '%MBA%')
OR (skill_id = 5 AND ans = 3)
GROUP BY employee_id
HAVING COUNT(DISTINCT skill_id) = 2 -- both skills
) s ON s.employee_id = e.id
WHERE e.rating = 1
ORDER BY s.max_date;
Please try this :
SELECT DISTINCT
sm1.id,
sm1.employee_code,
sm1.leaving_date,
sm1.name_of_employee,
sm1.position,
sm1.rating,
sm1.entry_date
FROM sm___employees sm1
LEFT JOIN sm___employee_skills sm2 ON sm1.id = sm2.employee_id
WHERE ((sm2.skill_id=1 AND sm2.ans LIKE '%MBA%')
AND (sm2.skill_id=1 AND sm2.ans=3))
AND sm1.rating IN (1)
ORDER BY sm2.date DESC;
I am having some trouble putting together a SQL statement properly because I don't have much experience SQL, especially aggregate functions. Safe to say I don't really know what I'm doing outside of the basic SQL structure. I can do regular joins, but not complex ones.
I have some tables: 'Survey', 'Questions', 'Session', 'ParentSurvey', and 'ParentSurveyQuestion'. Structurally, a survey can have questions, it can have users that started the survey (a session), and it can have a parent survey whose questions get imported into the current survey.
What I want to do is get information for a each survey in the Survey table; total questions it has, how many sessions have been started (conditionally, ones that have not finished), and the number of questions in the parents survey. The three joined tables can but do not have to contain any values, and if they don't then 0 should be returned by COUNT. The common field in three of the tables is a variation of 'survey_id'
Here is my SQL so far, I put the table structure below it.
SELECT
`kp_survey_id`,
COALESCE( q.cnt, 0 ) AS questionsAmount,
COALESCE( s.cnt, 0 ) AS sessionsAmount
COALESCE( p.cnt, 0 ) AS parentQAmount,
FROM `Survey`
LEFT JOIN <-- I'd like the count of questions for this survey
( SELECT COUNT(*) AS cnt
FROM Questions
GROUP BY kf_survey_id ) q
ON Survey.kp_survey_id = Questions.kf_survey_id
LEFT JOIN
( SELECT COUNT(*) AS cnt <-- I'd like the count of started sessions for this survey
FROM Session
WHERE session_status = 'started' <-- should this be Session.session_status?
GROUP BY kf_survey_id ) s
ON Survey.kp_survey_id = Session.kf_survey_id
LEFT JOIN
( SELECT COUNT(*) AS cnt <-- I'd like the count of questions in the parent survey with this survey id
FROM ParentSurvey
GROUP BY kp_parent_survey_id ) p
ON Survey.kf_parent_survey_id = ParentSurveyQuestion.kf_parent_survey_id
'kp' prefix means primary key, while 'kf' prefix means foreign key
Structure:
Survey: 'kp_survey_id' | 'kf_parent_survey_id'
Question: 'kp_question_id' | 'kf_survey_id'
Session: 'kp_session_id' | 'kf_survey_id' | 'session_status'
ParentSurvey: 'kp_parent_survey_id' | 'survey_name'
ParentSurveyQuestion: 'kp_parent_question_id' | 'kf_parent_survey_id'
There are also other columns in each table like 'name' or 'account_id', but i don't think they matter in this case
I'd like to know if I'm doing this correctly or if I'm missing something. I'm repurposing some code I found here on stackoverflow and modifying it to meet my needs, as I haven't seen conditional aggregation for more than three tables on this site.
My expected output is something like:
kp_survey_id | questionsAmount | sessionsAmount | parentQAmount
1 | 3 | 0 | 3
2 | 0 | 5 | 3
I think you were pretty close -- just need to fix your joins and include the survey id in the subqueries to use in those joins:
SELECT
`kp_survey_id`,
COALESCE( q.cnt, 0 ) AS questionsAmount,
COALESCE( s.cnt, 0 ) AS sessionsAmount
COALESCE( p.cnt, 0 ) AS parentQAmount,
FROM `Survey`
LEFT JOIN
( SELECT COUNT(*) cnt, kf_survey_id AS cnt
FROM Questions
GROUP BY kf_survey_id ) q
ON Survey.kp_survey_id = q.kf_survey_id
LEFT JOIN
( SELECT COUNT(*) cnt, kf_survey_id
FROM Session
WHERE session_status = 'started'
GROUP BY kf_survey_id ) s
ON Survey.kp_survey_id = s.kf_survey_id
LEFT JOIN
( SELECT COUNT(*) cnt, kp_parent_survey_id
FROM ParentSurvey
GROUP BY kp_parent_survey_id ) p
ON Survey.kf_parent_survey_id = p.kp_parent_survey_id
One thing you need to do is correct your joins. When you are joining to a subquery, you need to use the alias of the subquery. In your case you are using the alias of the table being used in the subquery.
Another thing you need to change is to include the field you wish to use in your JOIN in the subquery.
Make these changes and try running. Do you get an error or the desired results?
SELECT
`kp_survey_id`,
COALESCE( q.cnt, 0 ) AS questionsAmount,
COALESCE( s.cnt, 0 ) AS sessionsAmount
COALESCE( p.cnt, 0 ) AS parentQAmount,
FROM `Survey`
LEFT JOIN <-- I'd like the count of questions for this survey
( SELECT kf_survey_id, COUNT(*) AS cnt
FROM Questions
GROUP BY kf_survey_id ) q
ON Survey.kp_survey_id = q.kf_survey_id
LEFT JOIN
( SELECT kf_survey_id, COUNT(*) AS cnt <-- I'd like the count of started sessions for this survey
FROM Session
WHERE session_status = 'started' <-- should this be Session.session_status?
GROUP BY kf_survey_id ) s
ON Survey.kp_survey_id = s.kf_survey_id
LEFT JOIN
( SELECT kp_parent_survey_id, COUNT(*) AS cnt <-- I'd like the count of questions in the parent survey with this survey id
FROM ParentSurvey
GROUP BY kp_parent_survey_id ) p
ON Survey.kf_parent_survey_id = p.kf_parent_survey_id