SQL / Postgres SELECT, ORDER BY smallest ratio within a GROUP - sql

I have a Products model, and each product has 1 or more Skus. Each sku has a shipping_price and a selling_price.
I'd like to sort the products by their skus' highest or lowest ratio of shipping_price/selling_price, but I'm having trouble getting the query right. For example, for the "minimum shipping ratio" right now I have:
SELECT
p.id,
p.title,
array_agg(s.shipping_price) as shipping_prices,
array_agg(s.baseline_price) as baseline_prices,
min(s.shipping_price/s.baseline_price) as shipping
FROM "Products" p
JOIN "Skus" s
ON p.id = s.product_id
WHERE p.category_id in (1,2,3)
GROUP BY p.id
ORDER BY shipping ASC
LIMIT 48
OFFSET 0;
But this gives a lot of zeros for shipping where they clearly aren't zeros. It looks like I need some combination of array operations and min, but I'm not sure how to combine the methods HERE.
Is there a straightforward way to do this?

Looks like the type is preserved by min, so dividing an integer by another integer rounds everything down to zero.
Solution was to cast to a double:
min(s.shipping_price*1.0/s.baseline_price) as shipping

Related

Get the product of two values from two different tables

If anyone can help me figure out where I am going wrong with this SQL that would be great. Please see my attempt to answer it below. I have answer how I think it should be answered but I am very confused by the exam advice below, which says I should use a SUM function? I have googled this and I do not see how a SUM function can help here when I need get the product of two values in this case. Or am I missing something major?
Question: TotalValue is a column in Order relation that contains derived data representing total value (amount) of each order. Write a SQL SELECT statement that computes a value for this column.
My answer:
SELECT Product.ProductPrice * OrderLine.QuantityOrdered AS Total_Value
FROM Product,
OrderLine
GROUP BY Product;
Advice from exam paper:
This is a straightforward question. Tip: you need to use the SUM function. Also, note that you can take the sum of various records set using the GROUP BY clause.
Ok your question became a lot clearer once I clicked on the the hyperlink (blue text).
Each order is going to be made up of a quantity of 1 or more products.
So there could be 3 Product A and 5 Product B etc.
So you have to get the total for each product which is your Price * Quantity, but then you need to add them all together which is where the SUM comes in.
Example:
3 * ProductA Price (e.g. €5) = 15
5 * ProductB Price (e.g. €4) = 20
Total Value = 35
So you need to use the Product, Order and OrderLine tables.
Something like (I haven't tested it):
SELECT SUM(Product.ProductPrice * OrderLine.QuantityOrdered) FROM Product, Order, OrderLine
WHERE Order.OrderID = OrderLine.OrderID
AND Product.ProductID = OrerLine.ProductID
GROUP BY Order.OrderID
This should return rows containing the totalValue for each order - the GROUP BY clause causes the SUM to SUM over each group - not the entire rows.
For a single order you would need add (before the GROUP BY) "AND Order.OrderID = XXXXX" where XXXXX is the actual orders OrderId.

Your Query does not include the specified expression, how to fix it?

I don't understand why my sql is not running,
it pop out a window say
"Your query does not include the specified expression ' SUM(SaleRecord.Number)*(product.Price' as part of an aggregate function"
SELECT SUM(SaleRecord.Number)*(Product.Price) AS TotalIncome
FROM Product, SaleRecord
WHERE Product.ProductID=SaleRecord.SaleProduct;
Product.Price is not part of the aggregate. Presumably, you intend:
SELECT SUM(SaleRecord.Number * Product.Price) AS TotalIncome
FROM Product INNER JOIN
SaleRecord
ON Product.ProductID=SaleRecord.SaleProduct;
Note that I also fixed the archaic join syntax.
You asked in my previous answer:
"thank you, I just make some mistake, now it is working. And sorry to
bother you more, I want to select the product who sell the most out,
how can I do it, I try to add MAX(xxx) on it, and it don't work"
Now, I am by no means an expert, but there are two processes going on. Your language is confusing so I'm going to assume you want to know which product sells the most in $$ terms (rather than count. For example, you might sell 1,000 $0.50 products, equallying $500 total sales, or 10 $500 products, totallying $5000. If you want the count or the dollar value, then the method changes slightly).
So the first process is to get the total sales of each product, which I outlined above. Then you want to nest that inside a second query, where you then select the max. I'll give you the code and then explain it:
SELECT ProductID, MAX(TotalSale)
FROM (
SELECT P.ProductID, SUM(S.Number)*P.Price AS TotalSale
FROM Products as P, SaleRecords as S
WHERE product.Productid = SaleRecord.SaleProduct
GROUP BY Product.ProductID
)
It's easiest to imagine this as querying a query. Your first query is in the FROM() statement. That will run and give you the output of total sale per product. Then the second query is ran (the top most SELECT line) that selects the productID and the sale amount that is the largest among all the products.
Your teacher may not like this since nesting queries is a little advanced (though completely intuitive IMO). Hopefully this helps!
You brackets are wrong - for each row you want to multiply the price by the number, and only then sum them:
SELECT SUM(SaleRecord.Number * Product.Price) AS TotalIncome
FROM Product, SaleRecord
WHERE Product.ProductID = SaleRecord.SaleProduct;
You have a bracket error:
SELECT SUM(SaleRecord.Number * Product.Price) AS TotalIncome
FROM Product INNER JOIN
SaleRecord ON Product.ProductID = SaleRecord.SaleProduct;
This is because you're not indicating which column to group by. The line you wrote is:
SUM(SaleRecord.Number) * Product.Price
Which sums all of the sale quantities (regardless of differences in product ID) and multiplies it by the price right? Well what if you have multiple products with different prices? Basically, you are doing a one to many match, where you have a total that is the sum of all the sales, multiplied by multiple prices. What you need is a group by command. I would modify your code to say:
SELECT Product.ProductID, SUM(SaleRecord.Number)*Product.Price AS TotalSales
FROM Product, SaleRecord
WHERE product.Productid = SaleRecord.SaleProduct
GROUP BY Product.ProductID
That should take care of it, telling the dbms to group each product together, sum the number of sales and then multiply by the price of that product.
You can nest that inside another query to get total Income:
SELECT SUM(TotalIncome)
FROM ( **the above code here)
EDIT: Or you can do it like the ways listed above where your query creates a TotalIncome for each ORDER, and then sums them all together. my way creates a total sale for each PRODUCT and then sums all the products

Multiple of same result even with group by

Alright so say I have a 'product_catalog', and 'orders' tables. Each order has the product_catalog_id as a foreign key. What I want to return as the query results is the product_code (name of the product associated with a specific product_catalog_id) + a count of how many of each product_code have been ordered. That's easy enough with something like this (Oracle SQL):
SELECT pc.product_code,
COUNT(*) as count
FROM orders o
join product_catalog pc on pc.product_catalog_id = o.product_catalog_id
GROUP BY pc.product_code
ORDER BY count DESC;
but I also want to print various pieces of information from the order table such as total of all monthly charges for that product_code. That would seem easy enough with something like this:
(o.monthly_base_charge*count(*)) as "Monthly Fee"
but the problem is that there have been various monthly fees for the same product_code over time. If I add the above line in and add 'o.monthly_base_charge' to the group by statement, then it will print out a unique row for every variation of pricing for that product_code. How do I get it to ignore those price variations and just add together every entry with that product code?
It is a little unclear what you are asking. My best guess is that you want the sum of the monthly base charge:
SELECT pc.product_code,
COUNT(*) as count,
sum(o.monthly_base_charge) as "Monthly Fee"
FROM orders o join
product_catalog pc
on pc.product_catalog_id = o.product_catalog_id
GROUP BY pc.product_code
ORDER BY count DESC;
I'm not sure if this is exactly what you want. What happens if you have two orders in the same month for the same product?
You may need to do something like this since SQL will not be able to know which monthly base charge to multiply by the count.
SELECT pc.product_code,
COUNT(*) as count,
(min(o.monthly_base_charge)*count(*)) as "Monthly Fee"
FROM orders o
join product_catalog pc on pc.product_catalog_id = o.product_catalog_id
GROUP BY pc.product_code
ORDER BY count DESC;
Or you will need to add o.monthly_base_charge to the group by in order for sql to know how to determine the count()
GROUP BY pc.product_code, o.monthly_base_charge

Using SUM() with multiple tables in sql

I am trying to write a query that will use the sum function to add up all values in 1 column then divide by the count of tuples in another table. For some reason when i run the sum query by itself i get the correct number back but when i use it in my query below the value is wrong.
this is what im trying to do but the numbers are coming out wrong.
select (sum(adonated) / count(p.pid)) as "Amount donated per Child"
from tsponsors s, player p;
I found out the issue is in the sum. below returns 650,000 when it should return 25000
select (sum(adonated)) as "Amount donated per Child"
from tsponsors s, player p;
if i remove the from player p it gets the correct amount. However i need the player table to get the number of players.
I have 3 tables that are related to this query.
player(pid, tid(fk))
team(tid)
tsponsors(tid(fk), adonated, sid(fk)) this is a joining table
what i want to get is the sum of all the amounts donated to each team sum(adonated) and divide this by the number of players in the database count(pid).
I guess your sponsors are giving amounts to teams. You then want to know the proportion of donations per child in the sponsored team.
You would then need something like this:
SELECT p.tid,(SUM(COALESCE(s.adonated,0)) / COUNT(p.pid)) AS "Amount donated per Child"
FROM player p
LEFT OUTER JOIN tsponsors s ON s.tid=p.tid
GROUP BY p.tid
I also used a LEFT OUTER JOIN in order to show 0$ if a team has no sponsors.
Try
select sum(s.adonated) / (SELECT count(p.pid) FROM player p)
as "Amount donated per Child"
from tsponsors s;
Your original query joins 2 tables without any condition, which results in cross join.
UPDATE
SELECT ts.tid, SUM(ts.adonated),num_plyr
FROM tsponsors ts
INNER JOIN
(
SELECT tid, COUNT(pid) as num_plyr
FROM player
GROUP BY tid
)a ON (a.tid = ts.tid)
GROUP BY ts.tid,num_plyr

SQL for price difference calculation

I've got two tables that I'm trying to grab data from. The first is a 'titles' table, which represents product information (name, unique ID, etc). The second is a 'prices' table which collects price information for various currencies (each product can have multiple historic entries in the prices table).
I've written a fairly long-winded SQL statement to grab the latest price changes across products, but there are some issues that hopefully more experienced users will be able to help me out with:
SELECT
t.id,
t.name,
t.type,
p.value,
(SELECT
value
FROM
prices
WHERE
prices.id = p.id AND
prices.country='US' AND
prices.timestamp < p.timestamp
ORDER BY
prices.timestamp DESC
LIMIT 1) AS last_value
FROM
prices AS p
INNER JOIN
titles AS t
ON
t.row_id = p.id
WHERE
p.country = 'US' AND
(SELECT
value
FROM
prices
WHERE
prices.id = p.id AND
prices.country='US' AND
prices.timestamp < p.timestamp
ORDER BY
prices.timestamp DESC
LIMIT 1) IS NOT NULL
GROUP BY
t.id
ORDER BY
p.timestamp DESC,
last_value DESC
LIMIT 0, 25"
The first issue I've run into is that, while this query works, titles appear multiple times in the same listing. While this is expected, I'd ideally like only the latest price change to be displayed for the title. To solve this, I tried GROUPING by the titles 'id' (note the: GROUP BY t.id above). Unfortunately, while I'd expect the GROUP to respect the ORDER BY (which orders the latest price changes in DESC order), the results seem to remove the latest changes and show the GROUP'd titles with earlier price values.
Secondly, is there any better way to grab the last price of each item (currently I grab the current value, and then run a subquery to grab the 'last_value' - which effectively represents the value before the current price change). At the moment I run two subqueries: one to grab the second to last known price, and again to ensure that a previous price exists in the database (otherwise there's no point in listing the title as having a price change).
Any help would be appreciated!
How about this:
SELECT titles.id, titles.name, titles.type, prices.value, MAX(prices.timestamp)
FROM titles, prices
WHERE prices.row_id = titles.id AND prices.country='US';
Mind you, I don't have MySQL installed so I couldn't try this query.
[Edit:] I think it won't work 'cause it'll always display the last price entered for all the items because it'll always choose the highest timestamp from the prices table, maybe a group by will do, I'm really sleepy now and I can't think straight;
[Edit2:] How about this:
(SELECT max(report_run_date) as maxdate, report_name
FROM report_history
GROUP BY report_name) maxresults
SELECT titles.id, titles.name, titles.type, prices.value,
(SELECT MAX(prices.timestamp) as maxtimestamp FROM prices GROUP BY prices.row_id)
FROM titles, prices
WHERE prices.row_id = titles.id AND prices.country='US';