SQL select inside select - mysql

I have 3 tables.
person{personid,name, etc}
bid{bidid,personid,etc}
rating{ratingid,bidid,rating}
A person gets the rating after the bid is accepted by customers. So 1 bid = 1 rating.
And then the person bids another order, but the rating won't show up.
I already tried:
SELECT a.namausaha,ROUND(AVG(c.rating)) AS rating,a.kota,a.kontak,b.bidprice,a.mitraid
FROM tb_mitra a
JOIN tb_bid b ON b.mitraid=a.mitraid
LEFT JOIN tb_rating c ON c.bidid=b.bidid
WHERE b.orderid='OD004' AND b.statusbidid='1'
GROUP BY a.mitraid
but it doesn't work.
How to do it? I want to show the rating for every person.

Some rating return null due to left join
Try this
SELECT a.name,
ROUND(AVG(CASE WHEN c.rating IS NULL THEN 1 ELSE c.rating END )) AS rating,
a.etc,b.etc,a.personid
FROM person a
JOIN bid b ON b.personid=a.personid
LEFT JOIN rating c ON c.bidid=b.bidid
GROUP BY a.personid
SQL Fiddle

Related

How to execute group by in another group by in mysql?

I have 2 tables: cars and lands:
cars (id, land_id)
lands (id, district_id, location)
I need to get count of cars per each district. What I have now:
SELECT district_id, COUNT(*)
FROM lands
GROUP BY district_id
But of course it returns only count of lands per each district. How to get count of cars?
Expected result:
district_id | cars_count
1 | 30
3 | 10
...
The problem that you select only from lands table, when you also need to join cars table.
SELECT l.id, count(c.land_id)
FROM lands as l LEFT JOIN cars as c ON l.id = c.land_id
GROUP BY l.id
left join these 2 tables based on land_id(cars) and id(lands)
select a.id,
count(b.id)
from lands a
left join cars b on a.id=b.land_id
group by 1;

Remove duplicates based on rank after join in SQL request

I am using MySQL 5.6.
I have a SQL table with a list of users:
id name
1 Alice
2 Bob
3 John
and a SQL table with the list of gifts for each user (numbered in order of preference):
id gift rank
1 balloon 2
1 shoes 1
1 seeds 3
1  video-game 1
2 computer 2
3 shoes 2
3 hat 1
And I would like a list of the preferred gift for each user (the highest rank - if two gifts have the same rank, pick only one randomly) (bonus: if the list could be randomized, that would be perfect!):
id name gift rank
2 Bob computer 2
1 Alice shoes 1
3 John hat 1
I tried to use the clause GROUP BY but without any success.
Considering rank as a part of your data; Without using window functions or complex sub queries
SELECT u.id, u.name, g.gift
FROM users u
JOIN gifts g ON g.id = u.id
LEFT JOIN gifts g2 ON g2.id = g.id AND g2.rank > g.rank
WHERE g2.id IS NULL;
Added link http://sqlfiddle.com/#!9/62f59e/15/0
You can use row_number to get one row for each User.(Mysql 8.0+)
SELECT A.ID,NAME,GIFT,`RANK` FROM USERS A
LEFT JOIN (
SELECT ID,GIFT,`RANK` FROM
(SELECT *,ROW_NUMBER() OVER(PARTITION BY ID ORDER BY `RANK` ASC) AS RN FROM X) X
WHERE RN =1
) B
ON A.ID= B.ID
I do not know DB what you use. And I'm not an expert in SQL(I can have some mistake in next). But I think it is not difficult.
So I can give you just advice that you have to think gradually. Let me write.
First All I need is the highest rank. So I have to get this.
SELECT MAX(RANK)
FROM GIFT
GROUP BY ID
And then I think that I need get gifts from this rank.
SELECT GIFT.*
FROM GIFT
INNER JOIN(
SELECT ID, MAX(RANK)
FROM GIFT
GROUP BY ID
) filter ON GIFT.ID = filter.ID AND GIFT.RANK = filter.RANK
I think this is the table what you want!
So If below code works, That's what you really want.
SELECT *
FROM USER
LEFT OUTER JOIN(
above table
) GIFT ON USER.ID = GIFT.ID
But Remember this, I said I'm not an expert in SQL. There can be better way.
Checkout the query
SELECT tbluser.id,name,gift,rank into tblrslt
FROM tbluser
LEFT JOIN tblgifts
ON tbluser.id = tblgifts.id order by id,rank;
SELECT tt.*
FROM tblrslt tt
INNER JOIN
(SELECT id, min(rank) AS rank
FROM tblrslt
GROUP BY id) groupedtt
ON tt.id = groupedtt.id
AND tt.rank = groupedtt.rank order by id
In MySQL versions older than 8 you have no ranking functions available. You'll select the minimum rank per user instead and use these ranks to select the gift rows. This means you access the gifts table twice.
I suggest this:
select *
fron users u
join gifts g
on g.id = u.id
and (g.id, g.rank) in (select id, min(rank) from gifts group by id)
order by u.id;
If you also want to show users without gifts, simply change the inner join to a left outer join.

SQL finding most popular books

I have Books table
BookID BookName
1 BookA
2 BookB
3 BookC
Member table
MemberID MemberName
1 MemberA
2 MemberB
Borrow Table
MemberID BookID
1 1
1 2
2 1
2 2
I want to find out five popular book by Memeber A
I tried the following query
SELECT TOP (5) Book.BookTitle, COUNT(*) AS Count, Member_1.MemberName
FROM Book INNER JOIN
Borrow ON Book.BookID = Borrow.BookID INNER JOIN
Member ON Borrow.MemberID = Member.MemberID INNER JOIN
Member AS Member_1 ON Borrow.MemberID = Member_1.MemberID
where Member.MemberName='A'
GROUP BY Book.BookTitle, Member_1.MemberName
ORDER BY Count DESC
But this is not giving me the actual result.
Any suggestion would be appreciated.
I think you have too many joins:
SELECT TOP (5) b.BookTitle, COUNT(*) AS Count, m.MemberName
FROM Book b INNER JOIN
Borrow bo
ON bo.BookID = b.BookID INNER JOIN
Member m
ON bo.MemberID = m.MemberID
WHERE m.MemberName = 'A'
GROUP BY b.BookTitle, m.MemberName
ORDER BY Count DESC;
Note: This syntax is usually associated with SQL Server and does not work in MySQL. In MySQL, you would use LIMIT 5 rather then SELECT TOP (5).

how to get result of join also for records that dont have a joined record with another table?

I have this query:
SELECT suppliers.id, count(*)
FROM suppliers
INNER JOIN supplier_addresses
ON suppliers.id = supplier_addresses.supplier_id
GROUP BY suppliers.id;
this gives my a table of supplierId and count of its addresses in the supplier_addresses table. But it only shows me suppliers that have at least 1 address.
I want to see in the result also count of 0 addresses...for example:
supplier.id | count(*)
1 3
2 0
3 1
4 9
in my query I dont see the second record.
Use LEFT JOIN
SELECT suppliers.id, count(supplier_addresses.supplier_id )
FROM suppliers
LEFT JOIN supplier_addresses
ON suppliers.id = supplier_addresses.supplier_id
GROUP BY suppliers.id;

SQL subquery to return MIN of a column and corresponding values from another column

I'm trying to query
number of courses passed,
the earliest course passed
time taken to pass first course, for each student who is not currently expelled.
The tricky part here is 2). I constructed a sub-query by mapping the course table onto itself but restricting matches only to datepassed=min(datepassed). The query appears to work for a very sample, but when I try to apply it to my full data set (which would return ~1 million records) the query takes impossibly long to execute (left it for >2 hours and still wouldn't complete).
Is there a more efficient way to do this? Appreciate all your help!
Query:
SELECT
S.id,
COUNT(C.course) as course_count,
C2.course as first_course,
DATEDIFF(MIN(C.datepassed),S.dateenrolled) as days_to_first
FROM student S
LEFT JOIN course C
ON C.studentid = S.id
LEFT JOIN (SELECT * FROM course GROUP BY studentid HAVING datepassed IN (MIN(datepassed))) C2
ON C2.studentid = C.studentid
WHERE YEAR(S.dateenrolled)=2013
AND U.id NOT IN (SELECT id FROM expelled)
GROUP BY S.id
ORDER BY S.id
Student table
id status dateenrolled
1 graduated 1/1/2013
3 graduated 1/1/2013
Expelled table
id dateexpelled
2 5/1/2013
Course table
studentid course datepassed
1 courseA 5/1/2014
1 courseB 1/1/2014
1 courseC 2/1/2014
1 courseD 3/1/2014
3 courseA 1/1/2014
3 couseB 2/1/2014
3 courseC 3/1/2014
3 courseD 4/1/2014
3 courseE 5/1/2014
SELECT id, course_count, days_to_first, C2.course first_course
FROM (
SELECT S.id, COUNT(C.course) course_count,
DATEDIFF(MIN(datepassed),S.dateenrolled) as days_to_first,
MIN(datepassed) min_datepassed
FROM student S
LEFT JOIN course C ON C.studentid = S.id
WHERE S.dateenrolled BETWEEN '2013-01-01' AND '2013-12-31'
AND S.id NOT IN (SELECT id FROM expelled)
GROUP BY S.id
) t1 LEFT JOIN course C2
ON C2.studentid = t1.id
AND C2.datepassed = t1.min_datepassed
ORDER BY id
I would try something like:
SELECT s.id, f.course,
COALESCE( DATEDIFF( c.first_pass,s.dateenrolled), 0 ) AS days_to_pass,
COALESCE( c.num_courses, 0 ) AS courses
FROM student s
LEFT JOIN
( SELECT studentid, MIN(datepassed) AS first_pass, COUNT(*) AS num_courses
FROM course
GROUP BY studentid ) c
ON s.id = c.studentid
JOIN course f
ON c.studentid = f.studentid AND c.first_pass = f.datepassed
LEFT JOIN expelled e
ON s.id = e.id
WHERE s.dateenrolled BETWEEN '2013-01-01' AND '2013-12-31'
AND e.id IS NULL
This query assumes a student can pass only one course on a given day, otherwise you can get more than one row for a student as its possible to have many first courses.
For performance it would help to have an index on dateenrolled in student table and a composite index on (studentid,datepassed) in courses table.