SQl - Select best price based on quantity - sql

I have a table that contains prices for a particular item based upon the quantity being ordered and the type of client placing the order ...
ID Name Quantity ClientType Price/Unit ($)
========================================================
1 Cheese 10 Consumer 20
2 Cheese 20 Consumer 15
3 Cheese 30 Consumer 12
4 Cheese 10 Restaurant 18
5 Cheese 20 Restaurant 13
6 Cheese 30 Restaurant 10
I have having trouble with WHERE clause in the SQL to select the row where the customer gets the best price based upon the quantity that is ordered. The rule is they must at least meet the quantity in order to get the price for that pricing tier. If their order is below the minimum quantity then they get the Price for the first quantity (10 in this case) and if they order more than the largest quantity (30 in this example) they get that price.
For example ... If a Restaurant orders 26 units of cheese the row with ID = 5 should be chosen. If a Consumer ordered 9 units of cheese then the row returned should be ID = 1. If the Consumer orders 50 units of cheese then they should get ID = 3.
declare #SelectedQuantity INT;
SELECT *
FROM PriceGuide
WHERE Name = 'Cheese'
AND ClientType = 'Consumer'
AND Quantity <= #SelectedQuantity
What am I missing in the WHERE clause?

Edit
The first solution didn't handle the special case correctly, as mentioned in the comments.
Next try:
SELECT TOP 1 ID, Name, Quantity, ClientType, [Price/Unit]
FROM PriceGuide
WHERE Name = 'Cheese'
AND ClientType = 'Consumer'
ORDER BY CASE WHEN Quantity <= #SelectedQuantity THEN Quantity ELSE -Quantity END DESC
Assuming that Quantity is positive, the ORDER BY will return rows that meet Quantity <= #SelectedQuantity condition first, in a descending order.
For rows that do not match this condition, it uses -Quantity for ordering. So if no rows match the condition, the one with smallest quantity will be returned.

This is a little tricky because you need to deal with the quantities less than 10.
I think the best approach is:
SELECT TOP 1 *
FROM PriceGuide
WHERE Name = 'Cheese' AND ClientType = 'Consumer'
ORDER BY (CASE WHEN #SelectedQuantity >= Quantity THEN 1 ELSE 0 END) DESC,
(CASE WHEN #SelectedQuantity >= Quantity THEN PriceUnit END) ASC,
Quantity ASC;
This version handles the minimum quantity by keeping all the rows for a given Name/ClientType, using the ORDER BY for prioritization.

Your query will return all matching rows less than the #SelectedQuantity. You only want to return the row with highest quantity less than the selected quantity, so a subquery is needed to get this result:
SELECT *
FROM PriceGuide a
WHERE a.Name = 'Cheese'
AND a.ClientType = 'Consumer'
AND a.Quantity = (SELECT MAX(Quantity) FROM PriceGuide
WHERE ClientType = a.ClientType
AND Name = a.Name
AND Quantity <= #SelectedQuantity)

In your example of 26 unit, your query will return row 4 and 5 whereas you need it to return only row 5.
declare #SelectedQuantity INT;
SELECT *
FROM PriceGuide
WHERE Name = 'Cheese'
AND ClientType = 'Consumer'
AND Quantity <= #SelectedQuantity
ORDER BY Quantity DESC
LIMIT 1

Related

Limit result depending on sum of value

I´ve been trying to automize a list of addresses in SQL. I have multiple addresses and quantities and i need only the addresses that will fulfill the quantity i need
For example:
I have a table with
Item A qty 20
Item B qty 5
Item C qty 23
And a table with addresses and units
Address 1 item A 15units
Address 2 item A 10units
Address 3 item A 10units
Address 4 item A 13units
The result should show only
Address 2 item A 10units
Address 3 item A 10units
Assuming that your first table sales stores the sales made :
Item
qty
A
20
B
5
C
23
and your second table stocks indicates the addresses of the stored items :
Address
item
units
1
A
15
2
A
10
3
A
10
4
A
13
then you can select a subset of addresses so that the sum of the stored units is equal or greater than the quantity sold :
WITH list AS (
SELECT st.item
, sa.qty AS sales_qty
, array_agg(st.address) OVER w AS addresses
, array_agg(st.units) OVER w AS units
, sum(units) OVER w AS total_stored_units
FROM sales AS sa
INNER JOIN stocks AS st
ON st.item = sa.item
WINDOW w AS (PARTITION BY st.item ORDER BY st.address)
)
SELECT DISTINCT ON (item)
item, unnest(addresses) AS address, unnest(units) AS units
FROM list
WHERE total_stored_units >= sales_qty
ORDER BY item, total_stored_units ASC
Result
item
address
units
A
1
15
A
2
10
see dbfiddle
Thanks a lot #Edouard!
I ended up using something like
WITH r AS
(
SELECT
item,
address,
quantity,
sum(quantity)
OVER (
PARTITION BY item
ORDER BY quantity desc
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) as total_units
FROM st
GROUP BY 1,2,3
)
SELECT *
FROM r
GROUP BY 1,2,3,4
HAVING total_units <= qty_needed + avg(units)
ORDER BY units desc
I couldnt run your query in the bigquery console, but from it a re read some documentation of windows functions find this solution.
Again, thanks a lot!

Case in Sql group by query

I am working on a project in which I want to use Case to calculate price of product under specific Reference Number in SQL server. Below is my Sql query
SELECT
product AS Products,
refNum AS Refrence,
COUNT(id) AS Count
FROM ProductPriceList
GROUP BY
refNum, product
By Executing Above query I get:
Product Reference Count
Product1 Ref08 24
Product2 Ref08 7
Product3 Ref07 32
Product2 Ref12 1
Product3 Ref12 18
Product1 Ref07 76
Product1 Null 56
Can anyone guide me how to use Case statement in Sql query with group by statement to show price Below is the case:
if count < 10 then price 1
if count > 10 and < 100 then price 2
if count > 100 then price 3
I don't want to add a new table in my database. I hope you can understand my query.
Thanks in advance.
I think a basic CASE expression can handle your requirement:
SELECT
product AS Products,
refNum AS Refrence,
CASE WHEN COUNT(*) < 10 THEN 1
WHEN COUNT(*) >= 10 AND COUNT(*) < 100 THEN 2
ELSE 3 END AS price
FROM ProductPriceList
GROUP BY
product, refNum;
Not much to explain here, except that the 2 price case uses a bound which includes the count of 10 (since the 1 price case excludes it).
Here's alternative (doesn't differ much from exisiting one though):
You can use your query in subquery and use case outside:
select product,
--to get NULL values back
case Reference when 'RefNull' then NULL else Reference end [Reference],
case when [Count] < 10 then 1
when [Count] between 10 and 100 then 2
else 3 end [price]
from (
SELECT product AS Products,
--to allow also null values to be grouped
coalesce(refNum, 'RefNull') AS Refrence,
COUNT(id) AS Count
FROM ProductPriceList
GROUP BY coalesce(refNum, 'RefNull'), product
) [a]
Dataset:
Create Table ProductPriceList
(
Product varchar(10)
,RefNum CHAR(5)
,Records Int
);
Insert into ProductPriceList
Values
('Product1','Ref08',24)
,('Product2','Ref08',7)
,('Product3','Ref07',32)
,('Product2','Ref12',1)
,('Product3','Ref12',18)
,('Product1','Ref07',76)
,('Product1', NULL, 56);
With RCTE AS
(
Select Product
,RefNum
,Records
,1 RowNo
From ProductPriceList PPL
Union All
Select Product
,RefNum
,Records
,RowNo + 1
From RCTE R
Where RowNo + 1 < Records
)
Insert Into ProductPriceList (Product, RefNum, Records)
Select Product, RefNum, Records
From RCTE
where Records > 1
Query to fetch desired result:
Select Product
,RefNum
,Case When Count(*) < 10 Then 1
When Count(*) Between 10 and 99 then 2
Else 3 End Price
From ProductPriceList
Group By Product, RefNum
SQL Fiddle

Select if then case with first record

Can you do something like this in SQL Server?
I want to select from a table which has some records with the same product_id in one column and a Y or N in another (in stock), and take the first one which has a Y where the product_id is the same, while matching the product_id_set from another table.
... ,
SELECT
(SELECT TOP 1
(product_name),
CASE
WHEN in_stock = 'Y' THEN product_name
ELSE product_name
END
FROM
Products
WHERE
Products.product_set = Parent_Table.product_set) AS 'Product Name',
...
Sample data would be
product_set in_stock product_id product_name
---------------------------------------------------
1 N 12 Orange
1 Y 12 Pear
2 N 12 Apple
2 N 12 Lemon
Output from product_set = 1 would be 'Pear' for example.
So there's kind of two solutions depending on the answer to the following question. If there are no records for a product id with an in_stock value of 'Y', should anything return? Secondly, if there are multiple rows with in_stock 'Y', do you care which one it picks?
The first solution assumes you want the first row, whether or not there is ANY "Y" value.
select *
from (select RID = row_number() over (partition by product_set order by in_stock desc) -- i.e. sort Y before N
from Products) a
where a.RID = 1
The second will only return a value if there is at least one row with a 'Y' for in_stock. Note that the order by (select null) is essentially saying you don't care which one it picks if there are multiple in_stock items. If you DO care the order, replace it with the appropriate sort condition.
select *
from (select RID = row_number() over (partition by product_set order by (select null)) -- i.e. sort Y before N
from Products
where in_stock = 'Y') a
where a.RID = 1
I don't know what the structure of the "parent table" in your query is, so I've simplified it to assume you have what you need in Products alone.
SELECT ISNULL(
(
SELECT TOP 1 product_name
FROM Products
WHERE Products.product_set = Parent_Table.product_set
AND Products.in_stock = 'Y'
), 'Not in the stock') AS 'Product Name'

Calculate percentages of columns in Oracle SQL

I have three columns, all consisting of 1's and 0's. For each of these columns, how can I calculate the percentage of people (one person is one row/ id) who have a 1 in the first column and a 1 in the second or third column in oracle SQL?
For instance:
id marketing_campaign personal_campaign sales
1 1 0 0
2 1 1 0
1 0 1 1
4 0 0 1
So in this case, of all the people who were subjected to a marketing_campaign, 50 percent were subjected to a personal campaign as well, but zero percent is present in sales (no one bought anything).
Ultimately, I want to find out the order in which people get to the sales moment. Do they first go from marketing campaign to a personal campaign and then to sales, or do they buy anyway regardless of these channels.
This is a fictional example, so I realize that in this example there are many other ways to do this, but I hope anyone can help!
The outcome that I'm looking for is something like this:
percentage marketing_campaign/ personal campaign = 50 %
percentage marketing_campaign/sales = 0%
etc (for all the three column combinations)
Use count, sum and case expressions, together with basic arithmetic operators +,/,*
COUNT(*) gives a total count of people in the table
SUM(column) gives a sum of 1 in given column
case expressions make possible to implement more complex conditions
The common pattern is X / COUNT(*) * 100 which is used to calculate a percent of given value ( val / total * 100% )
An example:
SELECT
-- percentage of people that have 1 in marketing_campaign column
SUM( marketing_campaign ) / COUNT(*) * 100 As marketing_campaign_percent,
-- percentage of people that have 1 in sales column
SUM( sales ) / COUNT(*) * 100 As sales_percent,
-- complex condition:
-- percentage of people (one person is one row/ id) who have a 1
-- in the first column and a 1 in the second or third column
COUNT(
CASE WHEN marketing_campaign = 1
AND ( personal_campaign = 1 OR sales = 1 )
THEN 1 END
) / COUNT(*) * 100 As complex_condition_percent
FROM table;
You can get your percentages like this :
SELECT COUNT(*),
ROUND(100*(SUM(personal_campaign) / sum(count(*)) over ()),2) perc_personal_campaign,
ROUND(100*(SUM(sales) / sum(count(*)) over ()),2) perc_sales
FROM (
SELECT ID,
CASE
WHEN SUM(personal_campaign) > 0 THEN 1
ELSE 0
end AS personal_campaign,
CASE
WHEN SUM(sales) > 0 THEN 1
ELSE 0
end AS sales
FROM the_table
WHERE ID IN
(SELECT ID FROM the_table WHERE marketing_campaign = 1)
GROUP BY ID
)
I have a bit overcomplicated things because your data is still unclear to me. The subquery ensures that all duplicates are cleaned up and that you only have for each person a 1 or 0 in marketing_campaign and sales
About your second question :
Ultimately, I want to find out the order in which people get to the
sales moment. Do they first go from marketing campaign to a personal
campaign and then to sales, or do they buy anyway regardless of these
channels.
This is impossible to do in this state because you don't have in your table, either :
a unique row identifier that would keep the order in which the rows were inserted
a timestamp column that would tell when the rows were inserted.
Without this, the order of rows returned from your table will be unpredictable, or if you prefer, pure random.

Individual percentage for each item, throughput

With the following code I get how many items that is not "Out", but it returns percentage for all the items and not for each individual. I know it has to do with the count(date) that counts all the date of the all the unitids. Is there any way to count each item individual so it doesn't show the total percentage?
SELECT unitid, (COUNT(date)* 100 / (SELECT COUNT(*) FROM items)) AS Percentage
FROM items
WHERE date !='Out'
GROUP BY unitid
EDIT1, clarification: Lets say I have 2 of each product, product a, b, c, d and e, one of each item is 'Out'. The result I get is:
unitid Percentage
1. a 10
2. b 10
3. c 10
4. d 10
5. e 10
I'd like it to show this instead:
unitid Percentage
1. a 50
2. b 50
3. c 50
4. d 50
5. e 50
Thanks :)
You need a link between the count items and the item selected.
SELECT
unitid,
COUNT(date) * 100
/ (SELECT COUNT(*) FROM items B WHERE B.unidid = A.unitid) AS Percentage
FROM items A
WHERE date !='Out'
GROUP BY unitid
You query does not require a subquery, just a conditional aggregation:
SELECT i.unitid, 100*sum(case when date <> 'Out' then 1 else 0 end)/count(date) as Percentage
FROM items i
GROUP BY unitid
Assuming that [date] is never NULL, you express this more simply as:
select i.unitid, 100*avg(case when date<>'out' then 1.0 else 0 end) as Percentage
from items i
group by unitid
Let's see if I understand this correctly. If you have one a, two b, three c, and four d's, one of each is "Out", whatever that is, your result set should be:
unitid Percentage
1. a 100.00
2. b 50.00
3. c 33.33
4. d 25.00
To do that, you can try this:
Select counts.unitId, 100.0 *outcounts.count/ counts.count as Percentage
from (select unitid, count(*) as count
from items
where items.date ='Out'
group by unitid) as outcounts
inner join (select unitid, count(*) as count
from items
group by unitid) as counts
on outcounts.unitId = counts.unitId
here's an SQL Fiddle with the setup