Conditional Statement in SQL defining variables - sql

I have a query like this:
SELECT c.review, u.username, c.date, c.good, c.rate_id, r.rating
FROM comments AS c
LEFT JOIN users AS u
ON c.user_id = u.user_id
LEFT JOIN items as i
ON i.items_id = c.item_id
LEFT JOIN master_cat AS m
ON m.cat_id = i.cat_id
LEFT JOIN ratings as r
ON r.item_id = c.item_id
WHERE i.item = '{$item}' AND c.user_id = r.user_id
ORDER by {$sort};
But this is limiting. Because if that WHERE statement (c.user_id = r.user_id) matches, I JSON print out c.rating. If not, where it prints out in my application and it will skip data that I need.
For example, if it matches a comment to a rating from the same user, it will pair the data. But some comments don't have ratings (you can rate from 1 - 5) associated with them yet. So, instead of skipping the data with a inflexible WHERE clause, I want to make it conditional and print out 0 where later on I can say IF 0, print out "not rated". (this will not be done in SQL but JAVA. In SQL, I just need to store 0 in the MySQL DB.)
I am assuming I will need to drop the WHERE and make part of the SELECT?

LEFT JOIN ratings as r
ON r.item_id = c.item_id AND c.user_id = r.user_id
WHERE i.item = '{$item}'
That condition when placed in the where clause transforms the left join into an inner join

Related

Moodle sql query to get the report of completed as well as not completed list of enrolled users

I am using a Moodle ad-hoc query to fetch the report data I want to generate a completion report. The report is a list of all the enrolled users with course completion status.
I have tried the query below but it's only giving the list of the users who have completed the course. It's not showing the enrolled users who have not completed the course, which is needed in my report.
SELECT u.username, u.firstname,u.lastname,u.email,
DATE_FORMAT(FROM_UNIXTIME(p.timecompleted),'%Y-%m-%d') AS
completed,p.timecompleted
FROM prefix_course_completions AS p
JOIN prefix_course AS c ON p.course = c.id
JOIN prefix_user AS u ON p.userid = u.id
INNER JOIN prefix_cohort_members AS co ON co.cohortid=u.id
WHERE c.id=361 AND co.cohortid=142
ORDER BY u.username
I believe I have found at least part of your issue.
Within your query, you do INNER JOIN prefix_cohort_members AS co ON co.cohortid=u.id. This is joining the cohorts id to the users id, instead of user id to user id. I believe the correct line is INNER JOIN prefix_cohort_members AS co ON co.userid=u.id
Also within your query, you do both DATE_FORMAT(FROM_UNIXTIME(p.timecompleted),'%Y-%m-%d') AS completed and p.timecompleted, which will both return the time completed. I am not sure if this is what you wanted, but if you want to display the enrolled date this would add that for you
DATE_FORMAT(FROM_UNIXTIME(p.timeenrolled),'%Y-%m-%d') AS enrolled
With these two changes I was able to generate a report with a row containing a bool that shows if the user has completed the course or not using this block:
CASE
when p.timecompleted is null THEN 'False'
ELSE 'True'
END AS completed
NOTE: I also did not use an inner join, opting instead for just using JOIN. I am not sure if this made a difference, but if the changes stated above don't work for you, changing the inner join to just JOIN may help.
SELECT distinct c.id as courseid,c.fullname,u.id as user_id, u.email,cm.id as moduleid,cm.visible,CASE
WHEN cm.completion=0 THEN "none"
WHEN cm.completion=1 THEN "manual"
WHEN cm.completion=2 THEN "auto"
END as Completion_method,cmc.*
FROM prefix_course AS c
JOIN prefix_context AS ctx ON c.id = ctx.instanceid
JOIN prefix_role_assignments AS ra ON ra.contextid = ctx.id
JOIN prefix_user AS u ON u.id = ra.userid
JOIN prefix_course_modules cm ON cm.course = c.id
LEFT JOIN prefix_course_modules_completion cmc on cm.id=cmc.coursemoduleid and u.id=cmc.userid
where c.id=1234 and cm.visible<>0 and cm.completion<>0

SQL issue with RIGHT JOINS

The following SQL query does exactly what it should do, but I don't know how to change it so it does what I need it to do.
SELECT
a.id AS comment_id, a.parent_comment_id, a.user_id, a.pid, a.comment, a.blocked_at, a.blocked_by, a.created_at, a.updated_at,
b.name, b.is_moderator, COUNT(IF(d.type = 1, 1, NULL)) AS a_count, COUNT(IF(d.type = 2, 1, NULL)) AS b_count
FROM
comments AS a
RIGHT JOIN
users AS b ON b.id = a.user_id
RIGHT JOIN
node_user AS c ON c.user = b.id
RIGHT JOIN
nodes AS d ON d.id = c.node
WHERE
a.pid = 999
GROUP BY
comment_id
ORDER BY
a.created_at ASC
It gets all comments belonging to a specific pid, it then RIGHT JOINS additional user data like name and is_moderator, then RIGHT JOINS any (so called) nodes including additional data based on the user id and node id. As seen in the SELECT, I count the nodes based on their type.
This works great for users that have any nodes attached to their accounts. But users who don't have any, so whose id doesn't exist in the node_user and nodes tables, won't be shown in the query results.
So my question:
How can I make it so that even users who don't have any nodes, are still shown in the query results but with an a_count and b_count of 0 or NULL.
I'm pretty sure you want left joins not right joins. You also want to fix your table aliases so they are meaningful:
SELECT . . .
FROM comments c LEFT JOIN
users u
ON u.id = c.user_id LEFT JOIN
node_user nu
ON nu.user = u.id LEFT JOIN
nodes n
ON n.id = nu.node
WHERE c.pid = 999
GROUP BY c.id
ORDER BY c.created_at ASC;
This keeps everything in the first table, regardless of whether or not rows match in the subsequent tables. That appears to be what you want.

How to use outer query column inside inner query column

I am trying to run postgresql query below
SELECT *
FROM user_follow uf
INNER JOIN (SELECT uplr.post, COUNT(*)
FROM user_post_like_relation uplr
WHERE uplr.category = 'like' AND uf.recently_viewed < uplr.created_at . //CANNOT reference uf
AND clicked = true
GROUP BY uplr.post) uplr
ON uplr.post = uf.post
INNER JOIN post p ON uf.post = p.id
Unfortunately I am not able to reference uf inside the INNER JOIN query but it is essential that I join and also reference the recently_viewed column in side the JOIN query. Is there a way of overcoming this?
I think it is sufficient in your case to just JOIN to the table and GROUP the results to perform COUNT.
SELECT uf.post, p.id, count(*)
FROM user_follow uf
JOIN post p ON uf.post = p.id
JOIN user_post_like_relation uplr ON uplr.post = uf.post
WHERE uplr.category = 'like'
AND clicked = 'true'
AND uf.recently_viewed < uplr.created_at
GROUP BY uf.post, p.id
Play with SELECT and GROUP BY columns to get ones which you need.
You have to use INNER JOIN LATERAL if you want to reference columns from previous FROM list entries.
This is available from PostgreSQL 9.3 on.

Translating subquery to left join in sqlite

I have a query that is running against a SQLite database that uses a couple of subqueries. In order to accommodate some new requirements, I need to translate it to use joins instead. Below is the structure version of the original query:
SELECT c.id AS category_id, b.budget_year,
(
SELECT sum(actual)
FROM lines l1
WHERE status = 'complete'
AND category_id = c.id
AND billing_year = b.budget_year
) AS actual
(
SELECT sum(planned)
FROM lines l2
WHERE status IN ('forecasted', 'in-progress')
AND category_id = c.id
AND billing_year = b.budget_year
) AS rough_proposed
FROM categories AS c
LEFT OUTER JOIN budgets AS b ON (c.id = b.category_id)
GROUP BY c.id, b.budget_year;
The next query is my first attempt to convert it to use LEFT OUTER JOINs:
SELECT c.id AS category_id, b.budget_year, sum(l1.actual) AS actual, sum(l2.planned) AS planned
FROM categories AS c
LEFT OUTER JOIN budgets AS b ON (c.id = b.category_id)
LEFT OUTER JOIN lines AS l1 ON (l1.category_id = c.id
AND l1.billing_year = b.budget_year
AND l1.status = 'complete')
LEFT OUTER JOIN lines AS l2 ON (l2.category_id = c.id
AND l2.billing_year = b.budget_year
AND l2.status IN ('forecasted', 'in-progress'))
GROUP BY c.id, b.budget_year;
However, the actual and rough_proposed columns are much larger than expected. I am no SQL expert, and I am having a hard time understanding what is going on here. Is there a straightforward way to convert the subqueries to joins?
There is a problem with both your queries. However, the first query hides the problem, while the second query makes it visible.
Here is what's going on: you join lines twice - once as l1 and once more as l2. The query before grouping would have the same line multiple times when there are both actual lines and forecast-ed / in-progress lines. When this happens, each line would be counted multiple times, resulting in inflated values.
The first query hides this, because it does not apply aggregation to actual and rough_proposed columns. SQLite picks the first entry for each group, which has the correct value.
You can fix your query by joining to lines only once, and counting the amounts conditionally, like this:
SELECT
c.id AS category_id
, b.budget_year
, SUM(CASE WHEN l.status = 'complete' THEN l.actual END) AS actual
, SUM(CASE WHEN l.status IN ('forecasted', 'in-progress') THEN l.planned END) AS planned
FROM categories AS c
LEFT OUTER JOIN budgets AS b ON (c.id = b.category_id)
LEFT OUTER JOIN lines AS l ON (l.category_id = c.id AND l1.billing_year = b.budget_year)
GROUP BY c.id, b.budget_year
In this new query each row from lines is brought in only once; the decision to count it in one of the actual/planned columns is made inside the conditional expression embedded in the SUM aggregating function.

Query where foreign key column can be NULL

I want to get data if orgid = 2 or if there is no row at all for the uid. orgid is an integer. The closest thing I could think of is to do IS NULL but I'm not getting data for the uid that doesn't have an orgid row. Any idea?
select u.uid,u.fname,u.lname from u
inner join u_org on u.uid = u_org.uid
inner join login on u.uid = login.uid
where u_org.orgid=2 or u_org.orgid is NULL
and login.access != 4;
Basically the OR is if u_org.orgid row doesn't exist.
If there is "no row at all for the uid", and you JOIN like you do, you get no row as result. Use LEFT [OUTER] JOIN instead:
SELECT u.uid, u.fname, u.lname
FROM u
LEFT JOIN u_org o ON u.uid = o.uid
LEFT JOIN login l ON u.uid = l.uid
WHERE (o.orgid = 2 OR o.orgid IS NULL)
AND l.access IS DISTINCT FROM 4;
Also, you need the parenthesis I added because of operator precedence. (AND binds before OR).
I use IS DISTINCT FROM instead of != in the last WHERE condition because, again, login.access might be NULL, which would not qualify.
However, since you only seem to be interested in columns from table u to begin with, this alternative query would be more elegant:
SELECT u.uid, u.fname, u.lname
FROM u
WHERE (u.uid IS NULL OR EXISTS (
SELECT 1
FROM u_org o
WHERE o.uid = u.uid
AND o.orgid = 2
))
AND NOT EXISTS (
SELECT 1
FROM login l
WHERE l.uid = u.uid
AND l.access = 4
);
This alternative has the additional advantage, that you always get one row from u, even if there are multiple rows in u_org or login.