How to retrieve specific columns in a GROUP BY sql statement? - sql

Table airports:
id | from | to | price | photo | notes
_______________________________________
1 | LON | JFK| 1000 | | test
2 | LON | JFK| 2000 | | test2
I want to retrieve the bestprice entry of all from-to combinations inside the database.
I want to fetch the whole record that is minprice found, or at least specific tables.
The following works, BUT only gives me the 3 columns from, to, price. Not the whole entity.
SELECT from, to, min(price) FROM airports GROUP BY from, to
How would I have to adapt this?

This is typically done using window functions:
select id, "from", "to", price, photo, notes
from (
select id, "from", "to", price, photo, notes
min(price) over (partition by "from", "to") as min_price
from the_table
) t
where price = min_price
order by id;
from is a reserved word and it's a bad idea to use that as a column name (not entirely sure about to)
To deal with "ties" (same values in from, to and price), you can use the dense_rank() function instead:
select id, "from", "to", price, photo, notes
from (
select id, "from", "to", price, photo, notes
dense_rank() over (partition by "from", "to" order by price) as price_rank
from the_table
) t
where price_rank = 1
order by id;

You can order the results and use distinct on to take the first result from each grouping
select distinct on (from,to) * from airports order by from,to,price asc;
the above query should work

A very simple solution would be this. SQLFiddle here
SELECT *
FROM airports
WHERE (from_place, to_place, price) =
(SELECT from_place, to_place, min(price)
FROM airports
GROUP BY from_place, to_place);
Use SELECT * FROM ... since you want the whole entity.

There isn't going to be a way to get a "whole entity" there could be many rows in your table that could contain the matching from + to + min price
For example if your table contains
id | from | to | price | photo | notes
_______________________________________
1 | LON | JFK| 1000 | | test
2 | LON | JFK| 2000 | | test2
3 | LON | JFK| 5000 | | test3
4 | LON | JFK| 2000 | | test4
5 | LON | JFK| 1000 | | test5
Then both rows 1 and 5 meet your criteria of from + to + min price.
You could write the query
SELECT id, from, to, price, photo, notes
FROM airports a
INNER JOIN (
SELECT from, to, min(price) [price]
FROM airports
GROUP BY from, to) sub
ON sub.from = a.from
AND sub.to = a.to
AND sub.price = a.price
Which would get you the matching records.

If you want to get the entire data, here is a query that solve your problem:
SELECT A.*
FROM airports A
INNER JOIN (SELECT A2.fromhere
,A2.tohere
,MIN(A2.price) AS minprice
FROM airports A2
GROUP BY A2.fromhere, A2.tohere) T ON T.fromhere = A.fromhere
AND T.tohere = A.tohere
AND T.minprice = A.price
The jointure is used to get only the best prices for each couple fromhere/tohere.
Hope this will help you.

Related

Postgres query - get all records of lowest price per ID [duplicate]

This question already has answers here:
Select first row in each GROUP BY group?
(20 answers)
Closed 10 months ago.
I have items table where I store information about items and their prices.
It looks like this:
id | title | item_code | price | site_id | store_id
I want to select all item rows with the lowest price per item_code. It means the query should return ONE row per item_code in my table, which contains the lowest price.
I'm using PostgreSQL.
Not sure where to start. Example DB data:
id | title | item_code | price | site_id | store_id
1 | Shampoo | TEST1 | 10 | 1 | 1
2 | Shampoo | TEST1 | 5 | 2 | 1
3 | Shampoo | TEST1 | 12 | 2 | 1
Use DISTINCT ON:
SELECT DISTINCT ON (item_code) *
FROM items
ORDER BY item_code, price;
See the demo.
Group your result set and use the MIN aggregate function:
SELECT item_code
, MIN(price) min_price
FROM items
GROUP BY item_code
;
Join the result of this query with the original table if you need the the complete item record:
SELECT it.*
FROM items it
JOIN (
SELECT item_code
, MIN(price) min_price
FROM items
GROUP BY item_code
) gi ON ( gi.item_code = it.item_code )
WHERE it.price = gi.min_price
;
See a live demo here on dbfiddle.co.uk
You can also use ROW_NUMBER().
SELECT a.id,
a.title,
a.item_code,
a.price,
a.site_id,
a.store_id
FROM
(
SELECT *, row_number() over(partition by item_code order by price) rn
FROM items
) a WHERE a.rn=1;

Select row with smallest number on multiple groups of same ids

I have the following table as an output from a sql statement
user | product | price
…
123 | 12 | 451.29
373 | 12 | 637.28
623 | 12 | 650.84
672 | 16 | 356.87
123 | 16 | 263.90
…
Now I want to get only the row with the smallest price for each product_id
THE SQL is fairly easy
SELECT user, product, price
FROM t
WHERE product IN (
SELECT product_id
FROM p
WHERE typ LIKE 'producttyp1'
)
)
but adding MIN(price) does not work how it usually do. I think its because there are several groups of the same product_ids in the same table. Is there an easy to use solution or do I have to rewrite the whole query?
Edit: when I delete user from the query I can get the product and the smallest price:
12 | 451.29
16 | 263.90
But now I would have to join the user, which I am trying to avoid.
You can use row_number():
select p.*
from (select p.*,
row_number() over (partition by product order by price asc) as seqnum
from p
) p
where seqnum = 1;

SQL Access QUERY to show duplicate

I have the below table in SQL Access and I want to find out which products have 2 distinct categories.
For example the product abc has only one category, so I don't want it to show up in my query, but product def has both categories, so I want it to show up.
+----+---------+----------+
| ID | Product | Category |
+----+---------+----------+
| 1 | abc | A |
| 2 | abc | A |
| 3 | def | B |
| 4 | def | A |
| 5 | abc | A |
+----+---------+----------+
The answer ultimately depends on whether you are looking for products which are assigned to more than one category, or exactly 2 categories as you state in your question:
I want to find out which products have 2 distinct categories.
For the latter, you might use something like the following:
select t.product
from (select distinct product, category from YourTable) t
group by t.product
having count(*) = 2
For the former, there are many possible options - you can simply change the equality operator = in the above query to a greater than or equal to operator >= yielding:
select t.product
from (select distinct product, category from YourTable) t
group by t.product
having count(*) >= 2
Or you could use a where exists clause to test whether there exists at least one other record for the same product assigned to a different category:
select distinct t.product
from YourTable t
where exists
(select 1 from YourTable u where u.product = t.product and u.category <> t.category)
Or you could use aggregation with a min/max test within the having clause, as per query suggested by #forpas.
In all of the above examples, change YourTable to the name of your table.
Access does not support COUNT(DISTINCT ...) so for your sample data a HAVING clause where you set the condition that the minimum Category is different than the maximum Category will do:
select Product
from tablename
group by Product
having min(Category) <> max(Category)

SELECT only rows when count=1 - without additional SELECT or/ and having

I wonder if there is a way to build a query without joins or/and having clause that would return the same result as the query below? I already found similar question (select and count rows) but didn't find the answer.
SELECT ID, CATEGORY, PRODUCT, DESC
FROM SALES s
JOIN (SELECT ID, COUNT(CATEGORY)
FROM SALES
GROUP by ID
HAVING count(CATEGORY)=1) S2 ON S.ID=S2.ID;
So the table looks like
ID | Country | Product | DESC
1 | USA | Cream | Super cream
1 | Canada | Toothpaste| Great Toothpaste
2 | Germany | Beer | Tasty Beer
and the result I would like to get is
ID | Country | Product | DESC
2 | Germany | Beer | Tasty Beer
because id=1 has 2 different countries assigned
I'm using SQL Server
In general I'm interested in the 'fastest' solution. The table is huge and I just wonder if there is a way to do it smarter.
you may want to consider this query.
select t2.id, t2.category, t2.product, t2.desc from (
select id, category, product,
case when (select count(1) from sales where id=t1.id group by id) as ct
,desc
from sales t1) t2 where t2.ct = 1
You can try this Query:
SELECT ID, CATEGORY, PRODUCT, DESC
FROM SALES s
WHERE 1 = (
SELECT COUNT(*)
FROM SALES x
WHERE x.ID = s.ID
);
One method uses window functions:
SELECT ID, CATEGORY, PRODUCT, DESC
FROM (SELECT s.*, COUNT(*) OVER (PARTITION BY ID) as cnt
FROM SALES s
) s
WHERE cnt = 1;
However, the fastest solution would require a unique id and an index. That would be:
select s.*
from sales s
where not exists (select 1
from sales s2
where s2.id = s.id and
s2.<unique key> <> s.<unique key>
);
This can take advantage of an index on (id, <unique key>).
Note: This particular formulation assumes that category is never null.

SQL Query find users with only one product type

I solemnly swear I did my best to find an existing question, may I'm not sure how to phrase it correctly.
I would like to return records for users that have quota for only one product type.
| user_id | product |
| 1 | A |
| 1 | B |
| 1 | C |
| 2 | B |
| 3 | B |
| 3 | C |
| 3 | D |
In the example above I'd like a query that only returns users who carry quota for only one product type - doesn't really matter which product at this point.
I tried using select user_id, product from table group by 1,2 having count(user) < 2 but this does not work, nor does select user_id, product from table group by 1,2 having count(*) < 2
Any help is appreciated.
Your having clause is good; the issue's with your group by. Try this:
select user_id
, count(distinct product) NumberOfProducts
from table
group by user_id
having count(distinct product) = 1
Or you could do this; which is closer to your original:
select user_id
from table
group by user_id
having count(*) < 2
The group by clause can't take ordinal arguments (like, e.g., the order by clause can). When grouping by a value like 1, you're in fact grouping by the literal value 1, which would just be the same for any row in the table, and thus will group all the rows in the table to one group. Since there are more than one product in the entire table, no rows will be returned.
Instead, you should group by the user_id:
SELECT user_id
FROM mytable
GROUP BY user_id
HAVING COUNT(*) = 1
If you want the product, then do:
select user_id, max(product) as product
from table
group by user_id
having min(product) = max(product);
The having clause could also be:
having count(distinct product) = 1