Select entire row on group by aggregation - sql

I'm having some struggle with something that should be a simple SQL query.
This is my initial database schema:
Also prepared the example in SQLFiddle
The query I've ended up with is:
select
b.ad_id,
b.auction_id,
max(b.amount) as max,
max(b.created_at) created_at
from bid b
where b.user_id = '601'
group by b.ad_id, b.auction_id
But in the result, I need the whole row from the bid table:
select
b.id,
b.ad_id,
b.auction_id,
max(b.amount) as max,
max(b.created_at) created_at
from bid b
where b.user_id = '601'
group by b.ad_id, b.auction_id
Which fails with: [42803] ERROR: column "b.id" must appear in the GROUP BY clause or be used in an aggregate function Position: 16. Cannot add the id field in the GROUP BY clause, because it will add some extra rows I don't need.
What I need is to select from the bid table the highest record (amount field) grouped by auction_id and ad_id.
I think I need to make some self inner join or subselect but right now I'm not able to write the SQL.

What I need is to select from the bid table the highest record (amount field) grouped by auction_id and ad_id
Take a look at DISTINCT ON in the docs. Your desired result would be obtained by the following query.
select DISTINCT ON (b.ad_id, b.auction_id)
b.id,
b.ad_id,
b.auction_id,
b.amount
b.created_at
from bid b
where b.user_id = '601'
ORDER BY b.ad_id, b.auction_id, b.amount DESC

If you want the most recent row for each auction for the given user, then you can use a correlated subquery to filter:
select b.*
from bid b
where b.user_id = '601' and
b.created_at = (select max(b2.created_at)
from bid b2
where b2.auction_id = b.auction_id and
b2.user_id = b.user_id
);
This seems like a sensible interpretation of what you want. I don't know if ad_id is needed.

Related

How to count occurrences in sql

I would like to count how many times an id is present in a table, then print the title associated with this id from another table (next to the amount of occurrences). I also want to only return the top 10 in descending order.
I could only manage to return the total number of occurrences
You are missing GROUP BY:
SELECT b.title, b.book_id,
COUNT(*)
FROM books b INNER JOIN
students_books sb
ON b.book_id = sb.book_id
GROUP BY b.title, b.book_id
ORDER BY COUNT(*) DESC
LIMIT 10;
I also added table aliases. These generally make the query easier to write and to read.

SQL - extract column from subquery

I'm working on a database meant for auctions and I would like get the id of all the winning bids. The hard part is extracting it from a subquery that returns 2 columns: the id and the amount.
It looks something like this:
SELECT id FROM Bid WHERE id IN (Select ID,max(amount) FROM Bid group by bid.idAuction)
Can I somehow extract just one column from the subquery? Any other sugestions to do this task are helpfull too.
Thank you!
Your query is close, but you need a correlated subquery to make this work:
SELECT b.id
FROM Bid b
WHERE b.amount = (SELECT max(amount)
FROM Bid b2
WHERE b2.idAuction = b.idAuction
);
SELECT id, maxBid.MAmount, Bid.Amount
FROM Bid
INNER JOIN (Select ID,max(amount) mamount FROM Bid group by bid.idAuction) MaxBid
on MaxBid.ID = Bid.ID
RDBMS and SQL operate most effectively in SET based operations. So in this case we generate a set based on ID and max bid. We then join it back to the base set so that only the max bids are treturned.

Filteration of Rows based on a column value

I have a table called AssetTable which stores AssetId
I have another table called AssetBookTable which stores two fields BookId and CurrentOperationsTax
Another table AssetBook that stores AssetId and BookId as foriegn keys.
Now, I have a situation in which I have to filter records based on
AssetBookTable.CurrentOperationsTax
This, is what I am getting now,
And this, is what I want:
**
The logic is that I want only one BookId per AssetId that has either Current/Operation/Tax for CurrentOperationsTax field.
**
Here's the SQL fiddle written so far:
SQLFiddle
Any help is greatly appreciated.
You can simple remove the GROUP BY BookID. But of course you then need to aggregate the BookID some how. Here using the MIN() function:
SELECT
ab.AssetId
,MIN(ab.BookId) as BookID
,abt.CuurentOperationsTax
FROM
AssetBook ab
JOIN AssetTable at ON at.AssetId = ab.AssetId
JOIN AssetBookTable abt ON abt.BookId = ab.BookId
GROUP BY
ab.AssetId
,abt.CuurentOperationsTax
http://sqlfiddle.com/#!6/e3477/42
I prefer using the ROW_NUMBER approach. It does require the use of a subquery. In summary it is grouping the records by the CuurentOperationsTax number and ordering by book id and giving the row number for each grouping. Then in the outer select I filter out how many I want for each grouping. In this example just 1.
SELECT AssetId
,BookId
,CuurentOperationsTax
,RowNum
FROM (
SELECT
ab.AssetId
,ab.BookId
,abt.CuurentOperationsTax
,ROW_NUMBER() OVER(PARTITION BY abt.CuurentOperationsTax ORDER BY ab.BookId) AS RowNum
FROM
AssetBook ab
JOIN AssetTable AT ON AT.AssetId = ab.AssetId
JOIN AssetBookTable abt ON abt.BookId = ab.BookId
) AS b
WHERE b.RowNum = 1

Rewrite SQL and use of group by

I have written below sql for one of the requirement and is fetching my results. But, I am wondering if there is any better way of writing this query rather than using alias table as A.
SELECT A.*,B.OPRDEFNDESC FROM
( select OPRID_ENTERED_BY ,COUNT(*)
from ps_req_hdr
where entered_dt > '01-JUL-2012'
GROUP BY OPRID_ENTERED_BY
ORDER BY COUNT(*) DESC) A, PSOPRDEFN B
WHERE A.OPRID_ENTERED_BY=B.OPRID
You may be able to use a simple INNER JOIN to do the same thing...
SELECT A.OPRID_ENTERED_BY, COUNT(*), B.OPRDEFNDESC
FROM ps_req_hdr A
JOIN PSOPRDEFN B ON A.OPRID_ENTERED_BY = B.OPRID
WHERE A.entered_dt > '01-JUL-2012'
GROUP BY A.OPRID_ENTERED_BY, B.OPRDEFNDESC
ORDER BY COUNT(*) DESC
NOTE
As per the comments below, the COUNT(*) result for this query will NOT include records that don't have corresponding matches in table B, and it will inflate for non-unique matches in table B. What this means is: if B.OPRID is not a unique field or if A.OPRID_ENTERED_BY is not a foreign key for B.OPRID then this answer will not yield the same results as the original query.

Problem With DISTINCT!

Here is my query:
SELECT
DISTINCT `c`.`user_id`,
`c`.`created_at`,
`c`.`body`,
(SELECT COUNT(*) FROM profiles_comments c2 WHERE c2.user_id = c.user_id AND c2.profile_id = 1) AS `comments_count`,
`u`.`username`,
`u`.`avatar_path`
FROM `profiles_comments` AS `c` INNER JOIN `users` AS `u` ON u.id = c.user_id
WHERE (c.profile_id = 1) ORDER BY `u`.`id` DESC;
It works. The problem though is with the DISTINCT word. As I understand it, it should select only one row per c.user_id.
But what I get is even 4-5 rows with the same c.user_id column. Where is the problem?
actually, DISTINCT does not limit itself to 1 column, basically when you say:
SELECT DISTINCT a, b
What you're saying is, "give me the distinct value of a and b combined" .. just like a multi-column UNIQUE index
distinct will ensure that ALL values in your select clause are unique, not just user_id. If you want to limit the results to individual user_ids, you should group by user_id.
Perhaps what you want is:
SELECT
`c`.`user_id`,
`u`.`username`,
`u`.`avatar_path`,
(SELECT COUNT(*) FROM profiles_comments c2 WHERE c2.user_id = c.user_id AND c2.profile_id = 1) AS `comments_count`
FROM `profiles_comments` AS `c` INNER JOIN `users` AS `u` ON u.id = c.user_id
WHERE (c.profile_id = 1)
GROUP BY `c`.`user_id`,
`u`.`username`,
`u`.`avatar_path`
ORDER BY `u`.`id` DESC;
DISTINCT works at a row level, not just a column level
If you want the DISTiNCT of only one column then you will have to aggregate the rest of the columns returned (MIN, MAX, SUM, AVG, etc)
SELECT DISTINCT (Name), Min (ID)
From MyTable
Distinct will try to return only unique rows, it will not return only 1 row per user id in your example.
http://dev.mysql.com/doc/refman/5.0/en/distinct-optimization.html
You misunderstand. The DISTINCT modifier applies to the entire row — it states that no two identical ROWS will be returned in the result set.
Looking at your SQL, what value of the several available do you expect to see returned in the created_at column (for instance)? It would be impossible to predict the results of the query as written.
Also, you're using profile_comments twice in your SELECT. It appears that you're trying to obtain a count of how many times each user has commented. If so, what you want to do is use an AGGREGATE query, grouped on user_id and including only those columns that uniquely identify a user along with a COUNT of the comments:
SELECT user_id, COUNT(*) FROM profile_comments WHERE profile_id = 1 GROUP BY user_id
You can add the join to users to get the user name if you want but, logically, your result set cannot include other columns from profile_comments and still produce only a single row per user_id unless those columns are also aggregated in some way:
SELECT user_id, MIN(created_at) AS Earliest, MAX(created_at) AS Latest, COUNT(*) FROM profile_comments WHERE profile_id = 1 GROUP BY user_id