Getting highest results in a JOIN - sql

I've got three tables; Auctions, Auction Bids and Users. The table structure looks something like this:
Auctions:
id title
-- -----
1 Auction 1
2 Auction 2
Auction Bids:
id user_id auction_id bid_amt
-- ------- ---------- -------
1 1 1 200.00
2 2 1 202.00
3 1 2 100.00
Users is just a standard table, with id and user name.
My aim is to join these tables so I can get the highest values of these bids, as well as get the usernames related to those bids; so I have a result set like so:
auction_id auction_title auctionbid_amt user_username
---------- ------------- -------------- -------------
1 Auction 1 202.00 Bidder2
2 Auction 2 100.00 Bidder1
So far my query is as follows:
SELECT a.id, a.title, ab.bid_amt, u.display_name FROM auction a
LEFT JOIN auctionbid ab ON a.id = ab.auction_id
LEFT JOIN users u ON u.id = ab.user_id
GROUP BY a.id
This gets the single rows I am after, but it seems to display the lowest bid_amt, not the highest.

You can use the MAX-Function and a sub-select to get the maximum bid for each auction. If you join this subselect with your other tables and set the where clause as follows you should get what you are looking for.
SELECT a.id, a.title, ab.bid_points, u.display_name
FROM Auction AS a
INNER JOIN (SELECT auction_id, MAX(bid_points) AS maxAmount FROM auction_bids GROUP BY auction_id) AS maxBids ON maxBids.auction_id = a.id
INNER JOIN auction_bids AS ab ON a.id = ab.auction_id
INNER JOIN users AS u ON u.id = ab.user_id
WHERE ab.auction_id = maxBids.auction_id AND ab.bid_amount = maxBids.maxAmount
Hope that helps.

This is a typical within-group aggregate problem. You can solve it using a so called left self exclusion join
Try the following:
SELECT a.id, a.title, ab.bid_points, u.displayname
FROM auction a
INNER JOIN auction_bids ab ON ab.auction_id = a.id
LEFT JOIN auction_bids b1 ON ab.auction_id = b1.auction_id
AND ab.bid_points < b1.bid_points
LEFT JOIN users u ON u.id = ab.user_id
WHERE b1.auction_id IS NULL
It basically builds a join between the left and right side, until it doesn't find one for the left side anymore, and thats the highest element then.
Another solution would be using multiple querys (of course) or a temporary aggregate table.

Try this:
SELECT a.id, a.title, ab.bid_points, u.display_name FROM auction a
LEFT JOIN auctionbid ab ON a.id = ab.auction_id
LEFT JOIN users u ON u.id = ab.user_id
GROUP BY a.id
ORDER BY ab.bid_points DESC
If that doesn't work, try using a subselect on auctionbids containing something like
SELECT id, user_id, auction_id, MAX(bid_amt) FROM action_bids GROUP BY auction_id

Try adding the following clause; not sure about performance.
WHERE NOT EXISTS
(SELECT * FROM auctionbid abhigher
WHERE abhigher.auction_id = ab.auction_id
AND abhigher.auctionbid_amt > ab.auctionbid_amt)
Excludes auction bids from the query that have a higher bid for the same auction.
The only problem is that if you have 2 equal bids and both will list. One way to get rid of them - but it is a relatively arbitrary choice of winner, is to use the bid id:
WHERE NOT EXISTS
(SELECT * FROM auctionbid abhigher
WHERE abhigher.auction_id = ab.auction_id
AND abhigher.auctionbid_amt >= ab.auctionbid_amt
AND abhigher.id > ab.id)

Here is what you can try..like old school..nothing new..no need to go for left join or anything else..rest depends on your exact requirement
select A.id,A.title,max(AB.bid_amt),name
from Auction A,AuctionBids AB,Users U
where U.ID=AB.USER_ID AND A.ID=AB.ID
group by A.ID,A.title,name

Related

Left join vs. inner join not returning expected records

I am trying to find all of the most recent records (attempt_date) for a given "course" for each user. The query below returns the correct date for each user, unless the user doesn't have an attempt_date. In that case, the query does not return a row with the user at all.
If I change the inner joins on gradebook_grade and attempt to left join, it returns all enrolled users, but the query then returns null values if a null value exists for the any submission in the "course" rather than just the most recent attempt_date. Query here (forgive the weird naming conventions for term in the where clause, I did not choose those):
select distinct on (cu.pk1)
cu.pk1
,cm.course_id
,a.attempt_date
from course_users cu
inner join course_main cm on cm.pk1 = cu.crsmain_pk1
inner join course_term ct on ct.crsmain_pk1 = cm.pk1 /* through table */
inner join term t on t.pk1 = ct.term_pk1
inner join gradebook_grade gg on gg.course_users_pk1 = cu.pk1
inner join attempt a on a.gradebook_grade_pk1 = gg.pk1
where t.name like '%Fall 2021%'
and cu.role = 'S'
and cu.row_status = '0'
order by cu.pk1, course_id, attempt_date desc
How can I sidestep this behavior? If a student only has null values in a course for submission dates, I want the null value. If a student has anything other than a null value for submission dates in a course, I want the most recent last attempt date.
*Edited to give minimal reproducible example.
If you can, please write the structure of tables (create table DDL commands) and sample data. I will help you. But, I understood that: For example, we have 5 records in course_users table, and in the gradebook_grade may be have 7-8-10 records for only linked user3 and user4. Which users are not data in gradebook_grade table, we must select these users as null values, but which users have data in the gradebook_grade table, these users must be selected as one by one with the last attempt_date field.
For Example:
course_users.pk1
course_main.course_id
attempt_date
1
30
2
30
3
30
2021-03-10
4
20
2021-08-01
5
20
In this example, user3 and user4 have courses, but other users don't have.
Firstly we write a query that will select only users, which has courses and group these users by the last attaempt_date
select g1.course_users_pk1, max(a1.attempt_date) as attempt_date from attempt a1
inner join gradebook_grade g1 on a1.gradebook_grade_pk1 = g1.pk1
group by g1.course_users_pk1
Result:
course_users_pk1
attempt_date
3
2021-03-10
4
2021-08-01
Then completing our full query, with left joining this query to our main tables. Our final query:
select distinct on (cu.pk1)
cu.pk1,
cm.course_id,
gg.attempt_date
from course_users cu
left join course_main cm on cm.pk1 = cu.crsmain_pk1
left join course_term ct on ct.crsmain_pk1 = cm.pk1 /* through table */
left join term t on t.pk1 = ct.term_pk1 and t.name like '%2021%'
left join (
select g1.course_users_pk1, max(a1.attempt_date) as attempt_date from attempt a1
inner join gradebook_grade g1 on a1.gradebook_grade_pk1 = g1.pk1
group by g1.course_users_pk1
) gg on cu.pk1 = gg.course_users_pk1
where
cu.role = 'S'
and cu.row_status = '0'
order by cu.pk1, course_id, attempt_date desc
Maybe I misunderstood your question or your table structure, I am sorry, but if you can write create tables and some samples data I'm trying to help you.

Left outer join with count, on 3 tables not returning all rows from left table

I have these 3 tables:
Areas - id, name
Persons - id, area_id
Special_Persons - id_person, date
I'd like to produce a list of all Areas, followed by a count of Special Persons in each area, including Areas with no Special Persons.
If I do a left join of Areas and Persons, like this:
select a.id as idArea, count(p.id) as count
from areas a
left join persons p on p.area_id = a.id
group by a.id;
This works just fine; Areas that have no Persons show up, and have a count of 0.
What I am not clear on is how to do the same thing with the special_persons table, which currently only has 2 entries, both in the same Area.
I have tried the following:
select a.id as idArea, count(sp.id_person) as count
from special_persons sp, areas a
left join persons p on p.area_id = a.id
where p.area_id = a.id
and sp.id_person = p.id
group by a.id;
And it only returns 1 row, with the Area that happens to have 2 Special Persons in it, and a count of 2.
To continue getting a list of all areas, do I need to use a sub-query? Another join? I'm not sure how to go about it.
You can add another left join to the Special_Persons table:
select a.id as idArea, count(p.id), count(sp.id_person)
from areas a
left join persons p on p.area_id = a.id
left join special_persons sp on sp.id_person = p.id
group by a.id;

SQL select with join are returning double results

I am trying to select some data from different tables using join.
First, here is my SQL (MS) query:
SELECT Polls.pollID,
Members.membername,
Polls.polltitle, (SELECT COUNT(*) FROM PollChoices WHERE pollID=Polls.pollID) AS 'choices',
(SELECT COUNT(*) FROM PollVotes WHERE PollVotes.pollChoiceID = PollChoices.pollChoicesID) AS 'votes'
FROM Polls
INNER JOIN Members
ON Polls.memberID = Members.memberID
INNER JOIN PollChoices
ON PollChoices.pollID = Polls.pollID;
And the tables involved in this query is here:
The query returns this result:
pollID | membername | polltitle | choices | votes
---------+------------+-----------+---------+-------
10000036 | TestName | Test Title| 2 | 0
10000036 | TestName | Test Title| 2 | 1
Any help will be greatly appreciated.
Your INNER JOIN with PollChoices is bringing in more than 1 row for a given poll as there are 2 choices for the poll 10000036 as indicated by choices column.
You can change the query to use GROUP BY and get the counts.
In case you don't have entries for each member in the PollVotes or Polls table, you need to use LEFT JOIN
SELECT Polls.pollID,
Members.membername,
Polls.polltitle,
COUNT(PollChoices.pollID) as 'choices',
COUNT(PollVotes.pollvoteId) as 'votes'
FROM Polls
INNER JOIN Members
ON Polls.memberID = Members.memberID
INNER JOIN PollChoices
ON PollChoices.pollID = Polls.pollID
INNER JOIN PollVotes
ON PollVotes.pollChoiceID = PollChoices.pollChoicesID
AND PollVotes.memberID = Members.memberID
GROUP BY Polls.pollID,
Members.membername,
Polls.polltitle
You are getting 1 row for each PollChoices record since there are multiple choices per Polls INNER JOIN Members. You may be expecting the SELECT COUNT(*) sub-queries to act as a GROUP BY clause, but they don't.
If that doesn't make sense, add a bare minimum of sample data and the expected result and we can help more.
This query result is telling you the number of votes per choice in each poll.
In your example, this voter named TestName answered the poll (with ID 10000036) and gave one choice 1 vote, and the second choice 0 votes. This is why you are getting two rows in your result.
I'm not sure if you are expecting just one row because you didn't specify what data, exactly, you are trying to select. However if you are trying to see the number of votes that TestName has submitted, for each choice where the vote was greater than 1, then you will have to modify your query like this:
select * from
(SELECT Polls.pollID,
Members.membername,
Polls.polltitle, (SELECT COUNT(*) FROM PollChoices WHERE pollID=Polls.pollID) AS 'choices',
(SELECT COUNT(*) FROM PollVotes WHERE PollVotes.pollChoiceID = PollChoices.pollChoicesID) AS 'votes'
FROM Polls
INNER JOIN Members
ON Polls.memberID = Members.memberID
INNER JOIN PollChoices
ON PollChoices.pollID = Polls.pollID) as mysubquery where votes <> 0;

Mixing INs and Cross Table queries in Postgres

I'm new to Postgres. I have a query that involves 4 tables. My tables look like the following:
User Account Transaction Action
---- ------- ----------- ------
ID ID ID ID
Name UserID AccountID AccountID
Description Description
For each user, I'm trying to figure out: How many accounts they have, and how many total transactions and actions have been taken across all accounts. In other words, I'm trying to generate a query whose results will look like the following:
User Accounts Transactions Actions
---- -------- ------------ -------
Bill 2 27 7
Jack 1 7 0
Joe 0 0 0
How do I write a query like this? Currently, I'm trying the following:
SELECT
u.Name,
(SELECT COUNT(ID) FROM Account a WHERE a.UserID = u.ID) as Accounts
FROM
User u
Now, I'm stuck though.
untested, I would go for something like this.
select
u.Name,
count(distinct a.ID) as Accounts,
count(distinct t.ID) as Transactions,
count(distinct ac.ID) as Actions
from User u
left join Account a on u.ID = a.UserID
left join Transaction t on t.AccountID = a.ID
left join Action ac on ac.AccountId = a.Id
group by u.Name
As probably intended:
SELECT u.name, u.id
,COALESCE(x.accounts, 0) AS accounts
,COALESCE(x.transactions, 0) AS transactions
,COALESCE(x.actions, 0) AS actions
FROM users u
LEFT JOIN (
SELECT a.userid AS id
,count(*) AS accounts
,sum(t.ct) AS transactions
,sum(c.ct) AS actions
FROM account a
LEFT JOIN (SELECT accountid AS id, count(*) AS ct FROM transaction GROUP BY 1) t USING (id)
LEFT JOIN (SELECT accountid AS id, count(*) AS ct FROM action GROUP BY 1) c USING (id)
GROUP BY 1
) x USING (id);
Group first, join later. That's fastest and cleanest by far if you want the whole table.
SQL Fiddle (building on the one provided by #Raphaƫl, prettified).
Aside: I tripped over your naming convention in my first version.

Inner join query

Please go thourgh Attached Image where i descirbed my scenario:
I want SQL Join query.
Have a look at something like
SELECT *
FROM Orders o
WHERE EXISTS (
SELECT 1
FROM OrderBooks ob INNER JOIN
Books b ON ob.BookID = b.BookID
WHERE o.OrderID = ob.OrderID
AND b.IsBook = #IsBook
)
The query will return all orders based on the given criteria.
So, what it does is, when #IsBook = 1 it will return all Orders where there exists 1 or more entries linked to this order that are Books. And if #IsBook = 0 it will return all Orders where there exists 1 or more entries linked to this order that are not Books.
Inner join is a method that is used to combine two or more tables together on base of common field from both tables. the both keys must be of same type and of length in regardless of name.
here is an example,
Table1
id Name Sex
1 Akash Male
2 Kedar Male
similarly another table
Table2
id Address Number
1 Nadipur 18281794
2 Pokhara 54689712
Now we can perform inner join operation using the following Sql statements
select A.id, A.Name, B.Address, B.Number from Table1 A
INNER JOIN Table2 B
ON A.id = B.id
Now the above query gives one to one relation details.