How to return the cheapest price? - sql

I have a table named sales and it has 2 columns: Sales_Id and Price. I need to retrieve the cheapest price from the table. The cheapest price is 100. However, it shows only for one Sales_ID whereas I want it to return all 3 entries with the cheapest price.
Create table sales
(
Sales_Id int Primary Key,
Price number(4)
);
insert into sales values( 1,100);
insert into sales values( 2,400);
insert into sales values( 3,100);
insert into sales values( 4,100);

select sales_id
from sales
where price = (select min(price) from sales)

One method uses rank()/dense_rank():
select s.*
from (select s.*, rank() over (order by price) as seqnum
from sales s
) s
where seqnum = 1;

select * from sales
order by price
fetch first row with ties;
(Requires Oracle 12.1 or later.)

Related

Top selling product by category - SQL

I am looking to write a query to pull the top selling product per category from a schema. The schema on a simplified view looks like this:
Category
Orderid
Revenue
Food
12as
234
Sport
421bb
3434
Steel
35366cd
12355
Food
3421ww
362
Sport
546421qw
436456
etc etc.
I am using amazon redshift. I want to find the distinct category, its top selling order ID and the sum of the revenue.
Select distinct category, orderid, sum(revenue) as rev from XXX
I've got the start of the query but not sure where to go from here.
You can try to use ROW_NUMBER window function, using PARTITION BY with your grouping column then get the top-selling values.
SELECT Category,Orderid,Revenue
FROM (
SELECT *,
ROW_NUMBER() OVER(PARTITION BY Category ORDER BY Revenue desc) rn
FROM XXX
) t1
WHERE rn = 1
See if the following works for you - using Window Function to find the OrderId with the highest revenue, then aggregating:
select Category, Sum(Revenue) TotalRevenue, Max(TopSelling) TopSelling
from (
select *,
First_Value(OrderId) over(partition by category order by Revenue desc) TopSelling
from t
)t
group by Category;
If I understand correctly what your result should look like then you can use Postgres's DISTINCT ON expression. This is not to be confused with DISTINCT. It "groups" by the specified columns and returns the first row. You can control which first row it returns by using ORDER BY.
create temporary table orders (
category varchar,
order_id varchar,
revenue int
);
insert into orders (category, order_id, revenue) values
('Food', '12as', '234'),
('Sport', '421bb', '3434'),
('Steel', '35366cd', '12355'),
('Food', '3421ww', '362'),
('Sport', '546421qw', '436456');
select distinct on (category)
category,
order_id,
revenue
from orders
order by category, revenue DESC;
/*
category | order_id | revenue
----------+----------+---------
Food | 3421ww | 362
Sport | 546421qw | 436456
Steel | 35366cd | 12355
*/

Unable to retrieve a row with highest value of a column using row number and group by

I am working on a query which returns one row which has highest price in it for each product.
For Example I have
Table T1
Product Price Tax Location
Pen 10 2.25 A
Pen 5 1.25 B
Pen 15 1.5 A
Board 25 5.26 A
Board 2 NULL B
Water 5 10 A
The result should be like
Product Price Tax Location
Pen 15 1.5 A
Board 25 5.26 A
Water 5 10 A
I am using row number() and group by to achieve this using the following
ALTER VIEW [dbo].[InferredBestBids]
AS
SELECT ROW_NUMBER() OVER ( ORDER BY ( SELECT NULL
) ) AS id ,
product ,
MAX(price) AS Price ,
MIN(tax) AS Tax ,
location
FROM [dbo].InferredBids_A
WHERE NOT ( proce IS NULL
AND tax IS NULL
)
GROUP BY market ,
term
GO
When I ran the above query, it threw me the error
Column 'dbo.InferredBids_A.Location' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause
When I tried to group the query results by location, it gave me incorrect results by returning multiple rows for a product depending on the location
No GROUP BY is needed if you get your row_number clauses properly engaged and then just select based on the rownumber. Feel free to add an extra row_number call to the front of the outer query if you require it for some other reason. See the example here.
SELECT Product, Price, Tax, Location
FROM (
SELECT Product, Price, Tax, Location, ROW_NUMBER()OVER(PARTITION BY Product ORDER BY Price DESC) as RowID
FROM InferredBids_A
) T
WHERE RowID = 1
If you select something that's aggregated you must GROUP BY anything else in the select list that is not also aggregated:
SELECT Product, Price ,Tax, Location
FROM (SELECT Product, Price ,Tax, Location,
RANK() OVER (PARTITION BY Product ORDER BY Price DESC) N
FROM InferredBids_A
WHERE Price IS NOT NULL AND Tax IS NOT NULL
) T WHERE N = 1
(RANK will give rows for ties, use ROW_NUMBER if you don't care about these)
Making some test data:
DECLARE #BestBids TABLE
(
Product VARCHAR(20),
Price INT,
Tax DECIMAL(10,2),
Location VARCHAR(10)
)
INSERT INTO #BestBids
VALUES
('Pen', 10, 2.25, 'A'),
('Pen', 5, 1.25, 'B'),
('Pen', 15, 1.5, 'A'),
('Board', 25, 5.26, 'A'),
('Board', 2, NULL, 'B'),
('Water', 5, 10, 'A');
We get our row number to be set to the highest price for each product.
SELECT * FROM
(
SELECT *,
ROW_NUMBER() OVER (PARTITION BY Product ORDER BY Price DESC) RN
FROM #BestBids
) a
WHERE RN=1
We wrap the sql and just pick the first row number. Here is the output:
Product Price Tax Location RN
Board 25 5.26 A 1
Pen 15 1.50 A 1
Water 5 10.00 A 1
You could use a common table expression:
WITH cte
AS ( SELECT product ,
MAX(price) AS price
FROM dbo.InferredBids_A
)
SELECT product ,
price ,
tax ,
location
FROM dbo.InferredBids_A tbl
INNER JOIN cte ON cte.product = tbl.product
AND cte.price = tbl.price

SQL Standard Value and Variations

Below is a sample of data
UnitID ITEM_Num Price
13446 71079 45.57
13447 71079 45.57
13448 71079 52.50
13449 71079 45.57
13450 71079 36.22
The actual dataset has roughly 100 unique UnitIDs and 700 unique Item_Num values. I am trying to determine the most common price for each Item_Num and then select any records that vary from that standard by more than a specified percent.
Ideally we would have a standard Price value for each item but we don't. What is the best way to find the most common value. Also is there a function that might be able to quickly rank the Items with the most variation is Price.
This is SQL Server 2012.
You can use GROUP BY statement:
SELECT Price, count(*) FROM my_table GROUP BY Price ORDER BY Price ASC
Hope this helps!
The following query should work in SQL Server. It should give back each ITEM_Num with a price 10% lower or higher than the most common price.
;WITH cte AS (
SELECT
RANK() OVER (PARTITION BY ITEM_Num ORDER BY COUNT(1) DESC) AS 'Rank'
, ITEM_Num
, Price
FROM Units
GROUP BY ITEM_Num, Price
)
SELECT u1.UnitID
, u1.ITEM_Num
, u1.Price
, u2.Price AS 'most common price'
FROM Units u1
INNER JOIN cte AS u2
ON u2.ITEM_Num = u1.ITEM_Num
AND u2.Rank = 1
WHERE ABS(u1.Price - u2.Price) >= (u2.Price * 0.1);
EDIT: I wrote the query not knowing your DBMS, could probably be more efficient using the ranking functions of SQL Server.
EDIT 2: http://sqlfiddle.com/#!6/74940/33
Create table #t(
UnitID int,
Item_Num int,
Price money
)
Insert into #t(Unitid, Item_Num, Price)
values(13446, 71079, 45.57 ),
(13447, 71079, 45.57),
(13448, 71079, 52.50),
(13449, 71079, 45.57),
(13450, 71079, 36.22)
;with cte as (
Select
Unitid, Item_Num, Price,
Row_Number() over ( partition by item_num order by price) rownum
from #t
)
Select
u.UnitID,
u.Item_Num,
u.Price,
U1.price as CommonPrice,
u.RowNum,
U.Price*0.1,
(u.price +(u.price*0.1)) as NewPrice
from cte as U
inner join #t u1 on u.item_num =u1.item_num
where u.rownum =1

Oracle SQL : table Joining

For Oracle Database, suppose I have two tables here (Similar structure, but much larger amount of data) Definition below:
create table payments(
record_no INTEGER;
cust_no INTEGER;
amount NUMBER;
date_entered DATE;
);
insert into payments values(1,3,34.5,sysdate-1);
insert into payments values(2,2,34.5,sysdate-2);
insert into payments values(3,3,34.5,sysdate-18/1440);
insert into payments values(4,1,34.5,sysdate-1);
insert into payments values(5,2,34.5,sysdate-2/24);
insert into payments values(6,3,34.5,sysdate-56/1440);
insert into payments values(7,4,34.5,sysdate-2);
insert into payments values(8,2,34.5,sysdate-1);
create table customer(
cust_no INTEGER;
name VARCHAR2;
zip VARCHAR2;
);
insert into customer values(1,'Tom',90001);
insert into customer values(2,'Bob',90001);
insert into customer values(3,'Jack',90001);
insert into customer values(4,'Jay',90001);
Now I want to generate a report with those columns (Get the first two payment amount and date for each customer order by paydate) :
Cust_no | pay_amount1 | pay_date1 |pay_amount2 | pay_date2
Sample report I want
CUST_NO PAYMENT1 PAYDATE1 PAYMENT2 PAYDATE2
1 34.5 October, 09 2013 0 null
2 34.5 October, 08 2013 34.5 October, 09 2013
3 34.5 October, 09 2013 34.5 October, 10 2013
4 34.5 October, 08 2013 0 null
Can anybody make a correct and efficient Query ? Thanks ahead.
First you need to get your creation script right. The ; terminates a statement not a line inside a statement. Secondly the varchar2 data types needs a length specification: name VARCHAR2(20) instead of name VARCHAR2. Also character literals need to be enclosed in single quotes. '90001' is a character literal, 90001 is a number. Those are two different things.
So this results in the following script:
create table payments(
record_no INTEGER,
cust_no INTEGER,
amount NUMBER,
date_entered DATE
);
insert into payments values(1,3,34.5,sysdate-1);
insert into payments values(2,2,34.5,sysdate-2);
insert into payments values(3,3,34.5,sysdate-18/1440);
insert into payments values(4,1,34.5,sysdate-1);
insert into payments values(5,2,34.5,sysdate-2/24);
insert into payments values(6,3,34.5,sysdate-56/1440);
insert into payments values(7,4,34.5,sysdate-2);
insert into payments values(8,2,34.5,sysdate-1);
create table customer(
cust_no INTEGER,
name VARCHAR2(20),
zip VARCHAR2(20)
);
insert into customer values(1,'Tom','90001');
insert into customer values(2,'Bob','90001');
insert into customer values(3,'Jack','90001');
insert into customer values(4,'Jay','90001');
Note that it's bad coding practice to not specify the columns in an INSERT statement. It should be insert into customer (cust_no, name, zip) values(1,'Tom','90001'); instead of insert into customer values(1,'Tom','90001');
Now for your query, the following should do you wnat you need:
with numbered_payments as (
select cust_no,
amount,
date_entered,
row_number() over (partition by cust_no order by date_entered) as rn
from payments
)
select c.cust_no,
c.name,
p1.amount as pay_amount1,
p1.date_entered as pay_date1,
p2.amount as pay_amount2,
p2.date_entered as pay_date2
from customer c
left join numbered_payments p1
on p1.cust_no = c.cust_no
and p1.rn = 1
left join numbered_payments p2
on p2.cust_no = c.cust_no
and p2.rn = 2;
Note that I used an outer join to ensure that every customer is returned even if there is no or only a single payment for it.
Here is an SQLFiddle with all corrections and the query: http://sqlfiddle.com/#!4/74349/3
SELECT * FROM (
SELECT
c.cust_no,
p.amount as payment,
p.date_entered as paydate,
ROW_NUMBER() OVER (PARTITION BY cust_no ORDER BY p.record_no ASC) AS rn
FROM customer c
JOIN payments p ON p.cust_no = c.cust_no
) t
WHERE
rn <= 2
ORDER BY cust_no, rn;
Will show the 2 records per client you need, in 2 separate lines. If you prefer having it in the same line, then use this query:
SELECT
cust_no,
payment1,
paydate1,
CASE WHEN nextcli <> cust_no THEN 0 ELSE payment2 END AS payment2,
CASE WHEN nextcli <> cust_no THEN SYSDATE ELSE paydate2 END AS paydate2
FROM (
SELECT
c.cust_no,
p.amount as payment1,
p.date_entered as paydate1,
ROW_NUMBER() OVER (PARTITION BY c.cust_no ORDER BY p.record_no ASC) AS rn,
LEAD(c.cust_no, 1, -1) OVER (ORDER BY c.cust_no ASC) as nextcli,
LEAD(p.amount, 1, 0) OVER (ORDER BY c.cust_no ASC) as payment2,
LEAD(p.date_entered, 1, NULL) OVER (ORDER BY c.cust_no ASC) as paydate2
FROM customer c
JOIN payments p ON p.cust_no = c.cust_no
) t
WHERE
rn <= 1
ORDER BY cust_no, rn;
The analytic function ROW_NUMBER can help you give a number to each payment :
select cust_no, amount, date_entered,
row_number() over(partition by cust_no order by date_entered ) rn
from payments ;
Using this I think we can get what you're looking for, something like :
With ordered_payments as (
select cust_no, amount, date_entered,
row_number() over(partition by cust_no order by date_entered ) rn
from payments)
select customer.cust_no, p1.amount, p1.date_entered, p2.amount, p2.date_entered
from customer left join ordered_payments p1
on customer.cust_no = p1.cust_no and p1.rn = 1
left join ordered_payments p2
on customer.cust_no = p2.cust_no and p2.rn = 2 ;

Finding the correct 'record' and using ONLY the data from that record

I have a list of products and suppliers.
I need to make sure that the Quantity is larger than zero.
If so, I need to find the product with the lowest price and list the supplier, the product (SKU), quantity and price.
My test data schema is:
create table products(merchant varchar(100), name varchar(150), quantity int, totalprice int);
insert into products values
('Supplier A', 'APC-SMT1000I', 10, 150),
('Supplier B', 'APC-SMT1000I', 15, 250),
('Supplier C', 'APC-SMT1000I', 15, 350),
('Supplier D', 'DEF-SMT1000I', 10, 500),
('Supplier E', 'DEF-SMT1000I', 35, 350),
('Supplier G', 'GHI-SMT1000I', 75, 70)
Logically, I would expect the result to read:
SUPPLIER SKU QTY PRICE
Supplier A APC-SMT1000I 10 150
Supplier D DEF-SMT1000I 35 350
Supplier G GHI-SMT1000I 75 70
My SQL Statement reads:
SELECT merchant AS Supplier, name AS sku,quantity AS Qty,
min(totalprice) AS Price FROM products where quantity > 0 group by name;
My results are:
SUPPLIER SKU QTY PRICE
Supplier A APC-SMT1000I 10 150
Supplier D DEF-SMT1000I 10 350
Supplier G GHI-SMT1000I 75 70
Obviously, the coding is finding the lowest price and displaying it, but not with the correct data.
My Question?
How can I group the data, find the record with the lowest price and make sure the programme uses ONLY the data from that record?
The easiest way to do this is using window/analytic functions. You don't specific the database you are using, but this is ANSI standard functionality available in most (but not all) databases.
Here is the syntax:
select merchant AS Supplier, name AS sku, quantity AS Qty,
totalprice AS Price
from (select p.*,
row_number() over (partition by name
order by totalprice
) as seqnum
from products p
where quantity > 0
) p
where seqnum = 1;
You could use the following query:
SELECT products.*
FROM
products INNER JOIN
(SELECT name, MIN(totalprice) min_price
FROM products
WHERE quantity>0
GROUP BY name) m
ON products.name=m.name AND products.totalprice=min_price
In the subquery I calculate the minimum total price for every name, then I'm joining this subquery with the products table, to return only the rows that have the minimum total price for that name. If there are more than one row with the minimum price, they all will be shown.
Please see fiddle here.
You haven't specified you RDBMS, so I'll provide a few queries.
This one should work in any database (but need 2 table scans):
select
p.merchant as Supplier,
p.name as sku,
p.quantity as Qty,
p.totalprice as Price
from products as p
where
p.totalprice in
(
select min(t.totalprice)
from products as t
where t.name = p.name
)
This one should work for any RDBMS which have row_number window function:
with cte as (
select *, row_number() over(partition by name order by totalprice) as rn
from products
)
select
p.merchant as Supplier,
p.name as sku,
p.quantity as Qty,
p.totalprice as Price
from cte as p
where rn = 1
This one is for PostgreSQL:
select distinct on (p.name)
p.merchant as Supplier,
p.name as sku,
p.quantity as Qty,
p.totalprice as Price
from products as p
order by p.name, p.totalprice
=> sql fiddle demo