Subqueries involving multiple columns - mysql

I want a query to return values not present in another table. I currently run two queries and do the intersection in code. I am stuck with the syntax for multiple columns and presence of statements after where
First query:
SELECT sid, cid
FROM Table2
where used = 0
group by sid, cid
Main query:
SELECT sid, cid, count(1) as cnt
FROM Table1
WHERE ##not any pair of (sid, cid) returned from first query##
GROUP BY sid, cid
HAVING cnt < 20
LIMIT 50
What is a complete main query?

Try:
SELECT t1.sid, t1.cid, count(1) as cnt
FROM Table1 t1
LEFT JOIN Table2 t2
ON t1.sid = t2.sid AND t1.cid = t2.cid AND t2.used = 0
WHERE t2.sid IS NULL AND t2.cid IS NULL
GROUP BY sid, cid
HAVING cnt < 20
LIMIT 50

Related

SQL count number of common matches using several WHERE clauses

I have a table having columns like: membership_id | user_id | group_id
I'm looking for a SQL query to get the number of common groups between 2 different users. I could do that in several queries and using some PHP but I'd like to know if there is a way to use only SQL for that.
Like with the user ids 1 and 3, there are 3 common groups (1, 5 and 6) so the result returned would be 3.
I've made several tests but so far no result...Thank you.
You don't need "multiple WHERE clauses" or even a self JOIN:
SELECT group_id
FROM theTable AS t
WHERE t.user_id IN (1, 3)
GROUP BY group_id
HAVING COUNT(DISTINCT user_id) = 2;
more generically
SELECT group_id
FROM theTable AS t
WHERE t.user_id IN ([user id list])
GROUP BY group_id
HAVING COUNT(DISTINCT user_id) = [# of user ids in list];
Edit: Oh, you wanted the number of groups....
SELECT COUNT(1) FROM (
SELECT group_id
FROM theTable AS t
WHERE t.user_id IN (1, 3)
GROUP BY group_id
HAVING COUNT(DISTINCT user_id) = 2
);
You can achieve this with join.
Try this:
select t1.user_id, t2.user_id, group_concat(distinct t1.group_id)
from your_table t1
join your_table t2
on t1.user_id < t2.user_id
and t1.group_id = t2.group_id
group by t1.user_id, t2.user_id;
If you don't want a concatenated output:
select distinct t1.user_id, t2.user_id, t1.group_id
from your_table t1
join your_table t2
on t1.user_id < t2.user_id
and t1.group_id = t2.group_id;
Try to join two instances of the same table (for each of them you select only the records relative to one of the users) using group_id as join attribute, and count the result:
SELECT COUNT(*)
FROM table AS t1
JOIN table AS t2 ON t1.group_id=t2.group_id
WHERE t1.user_id=1 AND t2.user_id=3;
SELECT COUNT(*)
FROM TABLE_NAME USER_ONE_INFO
TABLE_NAME USER_TWO_INFO
WHERE USER_ONE_INFO.ID = USER_ONE_ID
AND USER_TWO_INFO.ID = USER_TWO_ID
AND USER_ONE_INFO.GROUP_ID = USER_TWO_INFO.GROUP_ID;

select the last record in each group along with count

We are having a following sqlite3 table named 'atable'
id student assignment grade
-----------------------------------
1 A 123 9
2 A 456 9
3 A 234 8
4 B 534 7
5 B 654 9
6 C 322 7
id is unique and incremented for each records. We are fetching latest assignment for each user by running query
SELECT student, assignment, grade from atable where id in
(select max(id) from atable group by student) order by id desc
This is working fine. However, we also need to fetch number of assignments for each user where user received a particular grade in the same query, say 9.
Any idea suggestion how to enhance or rewrite above query to return count as well. As mentioned, we are using sqlite3.
Thanks
You can use this correlated query:
SELECT t.student, t.assignment, t.grade,
(SELECT COUNT(*) FROM atable s
WHERE s.student = t.student and s.grade >= 9) as total_above_9
from atable t
where t.id in
(select max(id) from atable group by student)
order by t.id desc
It would be better to join to a derived table that contains an aggregated version of the original table:
select t1.student, t1.assignment, t1.grade, t2.cnt
from mytable as t1
join (
select student, max(id) as id,
count(case when grade = 9 then 1 end) as cnt
from mytable
group by student
) as t2 on t1.id = t2.id
Try this;)
select t1.student, t1.assignment, t1.grade, t2.count
from atable t1
inner join (select max(id) as id, count(if(grade=9, 1, null)) as count from atable group by student) t2
on t1.id = t2.id
order by t1.id desc

Joining three tables such that extra matches are discarded?

How can I write a query to give the results of three tables such that there's only one result per "line"?
The tables are:
T1 (ID, name, IP)
T2 (ID, date_joined)
T3 (ID, address, date_modified)
The relations are:
T1-T2 1:1, T1-T3 1:M - there can be many address rows per ID in T3.
What I want is a listing of all users with the fields above, but IF they have an address, I only want to record ONE (bonus would be if it is the latest one based on T3.date_modified).
So I should end up with exactly the number of records in T1 (happens to be equal to T2 in this case) and no more.
I tried:
select t.ID, t.name, t.IP, tt.ID, tt.date_joined, ttt.ID, ttt.address
from T1 t JOIN T2 tt ON (t.ID = tt.ID) JOIN T3 ttt ON (t.ID = ttt.ID)
And every sensible combination of LEFT, RIGHT, INNER, etc joins I could think of! I keep getting multiple duplicate because of T3
This query should work:
select
t1.ID, t1.name, t1.IP, t2.date_joined, t3x.address
from t1
join t2 on t1.ID = t2.id
left join (
select t3.*
from t3
join (
select id, max(date_modified) max_date
from t3
group by id
) max_t3 on t3.id = max_t3.id and t3.date_modified = max_t3.max_date
) t3x on t1.ID = t3x.id
First you do the normal join between t1 and t2 and then you left join with a derived table (t3x) that is the set of t3 rows having the latest date.
So T2 is actually not relevant here. You just need a way to join from T1 to T3 in a way that gets you at most one T3 row per T1 row.
One way of doing this would be:
select
T1.*,
(select address from T3 where T3.ID=T1.ID order by date_modified desc limit 1)
from T1;
This won't likely be very efficient, being a correlated subquery, but you may not care depending on the size of your dataset.
It's also only good for getting one column from T3, so if you had Address, City, and State, you'd have to figure out something else.
You can use sub query with Top 1 so that u get only one result from T3
here is a sample sql
select * into #T1 from(
select 1 ID
union select 2
union select 3) A
select * into #T2 from(
select 1 ID
union select 2
union select 3) A
select * into #T3 from(
select 1 ID, 'ABC' Address, getDate() dateModified
union select 1, 'DEF', getDate()
union select 3, 'GHI', getDate()) A
select *, (select top 1 Address from #T3 T3 where T3.ID= T1.ID order by datemodified desc) from #T1 T1
inner join #T2 T2 on T1.ID = T2.ID
Bonus :- you can also add order by dateModified desc to get the latest address

using outer alias in mysql subquery

I am writing a mysql query and I have a question. Can I / How do I do something like this:
select rating, user_id, (
-- in here I want to write a subquery to get the number of times the user_id in the outter query has rated this teacher
) as user_rated_frequency from teachers_rating where teacher_id = id
Essentially I am trying to get data and the frequency in which that user rated that teacher. Is it possible to use an alias from one of the items I want to select in a subquery that is still in the select and not in the where clause?
Check out this ...
SELECT rating,
user_id,
(SELECT COUNT(*)
FROM teachers_rating t1
WHERE teacher_id = 3
AND t1.user_id = t2.user_id) AS user_rated_frequency
FROM teachers_rating t2
WHERE teacher_id = 3;
or that one:
SELECT AVG (rating) AS average_rating,
user_id,
(SELECT Count(*)
FROM teachers_rating t1
WHERE teacher_id = 3
AND t1.user_id = t2.user_id) AS user_rated_frequency
FROM teachers_rating t2
WHERE teacher_id = 3
GROUP BY user_rated_frequency;
Links above show a SQL Fiddle example assuming that id is 3.
Alternatively you could have a sub query in the FROM clause:
SELECT AVG (t1.rating),
t1.user_id,
t2.user_rated_frequency
FROM teachers_rating t1,
(SELECT tr.teacher_id,
tr.user_id,
COUNT(*) AS user_rated_frequency
FROM teachers_rating tr
GROUP BY tr.teacher_id) t2
WHERE t1.teacher_id = t2.teacher_id
AND t1.user_id = t2.user_id
GROUP BY user_id, user_rated_frequency
Hat a look at this Fiddle.
You need to move your subquery (technically called a derived table) into your from clause.
Something like so:
select
rating,
user_id,
from teachers_rating,
(in here I want to write a subquery to get the number of times the user_id in the outter query has rated this teacher) as user_rated_frequency f
where teacher_id = f.id

MySQL select count based on two rows data

Table column headers: n,t1,t2
entries :
1 A B
2 A C
3 B C
4 D E
5 B A
How do I count total number of rows each letter appears in t1 MINUS the number of rows they appear in t2 ? I need to do something like following 2 lines in 1 query :
select count(*) as val,t1 from table group by t1
select count(*) as val,t2 from table group by t2
Thanks,
Martin
Here is one way:
select t1, max(t1cnt) - max(t2cnt) as diff
from ((select t1, count(*) as t1cnt, 0 as t2cnt
from t
group by t1
) union all
(select t2, 0 as t1cnt, count(*) as t2cnt
from t
group by t2
)
) t
group by t1
Using the union all ensures that you get all possible values from both columns, even values that only appear in one column.
You can use the following query to get the result. This query first gets a list of all the distinct t1 and t2 values (this is the UNION query). Once you have the list of these values, then you can use a LEFT JOIN to the original queries that you posted:
select d.col, coalesce(totT1, 0) - coalesce(totT2, 0) Total
from
(
select t1 col
from entries
union
select t2 col
from entries
) d
left join
(
select count(*) totT1, t1
from entries
group by t1
) d1
on d.col = d1.t1
left join
(
select count(*) totT2, t2
from entries
group by t2
) d2
on d.col = d2.t2;
See SQL Fiddle with Demo