select from two tables and conditionally collapse one column - sql

I have a list of Products
upc | name | price | qty
----------------------------
1 | apple | 1.00 | 3
2 | peach | 2.00 | 7
3 | melon | 1.75 | 2
and SaleProducts
upc | price
------------
2 | 1.90
I want to select from Products but also sale price
from SaleProducts (if product is on sale). This is what I came up with:
SELECT t1.upc, t1.name, MIN(t1.price) AS 'price', t1.qty
FROM (
SELECT p.upc, p.name, p.price, p.qty
FROM products p
UNION
SELECT sp.upc, NULL, sp.price, NULL
FROM saleproducts sp
) t1
GROUP BY t1.upc;
Output:
upc | name | price | qty
----------------------------
1 | apple | 1.00 | 3
2 | peach | 1.90 | 7
3 | melon | 1.75 | 2
Can anyone suggest a more elegant way to accomplish this? Im aware of similar question but my goal is to grab whichever price is lower, so COALESCE wouldn't work.
The only restriction is, I must use vanilla SQL, no stored procs or IF's.

Try this instead using CASE:
SELECT p.upc, p.name,
CASE WHEN sp.price IS NOT NULL
THEN CASE WHEN p.price > sp.price
THEN sp.price
ELSE p.price
END
ELSE p.price
END price, p.qty
FROM products p LEFT JOIN saleproducts sp ON p.upc = sp.upc;
It will prefer to use the price from saleproducts when it's available. If there is not a sale price for that product it will instead use the price from products.
EDIT - I've updated my answer so that it always gets the lowest price. FWIW I can't imagine why you'd bother having a sale price which is actually higher than your list price.

This seems more like the job for a left outer join:
SELECT p.upc, p.name,
(case when sp.price is null or p.price < sp.upc then p.price
else sp.price
end) as price,
p.qty
FROM products p left outer join
salesproducts sp
on p.upc = sp.upc;

Related

SQL - select values from two different tables

I have got a problem with selecting values from two different tables. Tables below:
Material:
MaterialID | MaterialName
-------------------------
1111111 | Material1
2222222 | Material2
3333333 | Material3
Stock:
MaterialID | Location | Quantity
---------------------------------
1111111 | LocA | 10
1111111 | LocB | 20
2222222 | LocC | 15
2222222 | LocD | 10
My SQL query below:
SELECT
[Material].[MaterialName] as 'Material Name',
custom.quantity as 'Total Quantity',
FROM
[Material]
inner join (
SELECT
[MaterialId] as 'Materialcode',
SUM([Quantity]) as 'Quantity'
from
[Stock]
group by
[MaterialId]
) custom
on
custom.Materialcode = [Material].[MaterialId]
The result is:
Material Name | Total Quantity
------------------------------
Material1 | 30
Material2 | 25
The problem is that in the result there is no information about Material3 (I know that the quantity is equal to 0 as it`s not in Stock table, but I need a result that will show all of the materials - like below:
Material Name | Total Quantity
------------------------------
Material1 | 30
Material2 | 25
Material3 | 0
Is it possible?
You can left join and aggregate:
select m.materialName, coalesce(sum(s.quantity), 0) total_quantity
from material m
left join stock s on s.materialID = m.materialID
group by m.materialID, m.materialName
You may also aggregate, then left join (that was your original attempt, you just need to change the join type).
Actually, you might as well use a correlated subquery - with an index on stock(materialID, quantity), this may be an efficient solution (and you don't need coalesce() here):
select
m.materialName,
(select sum(quantity) from stock s where s.materialID = m.materialID) total_quantity
from material m
Another way to express this is to use a lateral join:
select m.materialName, s.total_quantity
from material m
outer apply (select sum(quantity) total_quantity from stock s where s.materialID = m.materialID) s

PostgreSQL Referencing Outer Query in Subquery

I have two Postgres tables (really, more than that, but simplified for the purpose of the question) - one a record of products that have been ordered by customers, and another a historical record of prices per customer and a date they went into effect. Something like this:
'orders' table
customer_id | timestamp | quantity
------------+---------------------+---------
1 | 2015-09-29 16:01:01 | 5
1 | 2015-10-23 14:33:36 | 3
2 | 2015-10-19 09:43:02 | 7
1 | 2015-11-16 15:08:32 | 2
'prices' table
customer_id | effective_time | price
------------+---------------------+-------
1 | 2015-01-01 00:00:00 | 15.00
1 | 2015-10-01 00:00:00 | 12.00
2 | 2015-01-01 00:00:00 | 14.00
I'm trying to create a query that will return every order and its unit price for that customer at the time of the order, like this:
desired result
customer_id | quantity | price
------------+----------+------
1 | 5 | 15.00
1 | 3 | 12.00
2 | 7 | 14.00
1 | 2 | 12.00
This is essentially what I want, but I know that you can't reference an outer query inside an inner query, and I'm having trouble figuring out how to re-factor:
SELECT
o.customer_id,
o.quantity,
p.price
FROM orders o
INNER JOIN (
SELECT price
FROM prices x
WHERE x.customer_id = o.customer_id
AND x.effective_time <= o.timestamp
ORDER BY x.effective_time DESC
LIMIT 1
) p
;
Can anyone suggest the best way to make this work?
Instead of joining an inline view based on the prices table, you can perform a subquery in the SELECT list:
SELECT customer_id, quantity, (
SELECT price
FROM prices p
WHERE
p.customer_id = o.customer_id
AND p.effective_time <= o.timestamp
ORDER BY p.effective_time DESC
LIMIT 1
) AS price
FROM orders o
That does rely on a correlated subquery, which could be bad for performance, but with the way your data are structured I doubt there's a substantially better alternative.
You dont need the subquery, just a plain inner join will do (this assumes there are no duplicate effective_times per customer):
SELECT o.customer_id, o.quantity
,p.price
FROM orders o
JOIN prices p ON p.customer_id = o.customer_id
AND p.effective_time <= o.timestamp
AND NOT EXISTS ( SELECT * FROM prices nx
WHERE nx.customer_id = o.customer_id
AND nx.effective_time <= o.timestamp
AND nx.effective_time > p.effective_time
)
;

SUM in multi-currency

I am trying to do SUM() in a multi-currency setup. The following will demonstrate the problem that I am facing:-
Customer
-------------------------
Id | Name
1 | Mr. A
2 | Mr. B
3 | Mr. C
4 | Mr. D
-------------------------
Item
-------------------------
Id | Name | Cost | Currency
1 | Item 1 | 5 | USD
2 | Item 2 | 2 | EUR
3 | Item 3 | 10 | GBP
4 | Item 4 | 5 | GBP
5 | Item 5 | 50 | AUD
6 | Item 6 | 20 | USD
7 | Item 3 | 10 | EUR
-------------------------
Order
-------------------------
User_Id | Product_Id
1 | 1
2 | 1
1 | 2
3 | 3
1 | 5
1 | 7
1 | 5
2 | 6
3 | 4
4 | 2
-------------------------
Now, I want the output of a SELECT query that lists the Customer Name and the total amount worth of products purchased as:-
Customer Name | Amount
Mr. A | Multiple-currencies
Mr. B | 25 USD
Mr. C | 15 GBP
Mr. D | 2 EUR
So basically, I am looking for a way to add the cost of multiple products under the same customer, if all of them have the same currency, else simply show 'multiple-currencies'. Running the following query will not help:-
SELECT Customer.Name, SUM(Item.Amount) FROM Customer
INNER JOIN Order ON Order.User_Id = Customer.Id
INNER JOIN Item ON Item.Id = Order.Product_Id
GROUP BY Customer.Name
What should my query be? I am using Sqlite
I would suggest two output columns, one for the currency and one for the amount:
SELECT c.Name,
(case when max(currency) = min(currency) then sum(amount)
end) as amount,
(case when max(currency) = min(currency) then max(currency)
else 'Multiple Currencies'
end) as currency
FROM Customer c INNER JOIN
Order o
ON o.User_Id = c.Id INNER JOIN
Item
ON i.Id = o.Product_Id
GROUP BY c.Name
If you want, you can concatenate these into a single string column. I just prefer to have the information in two different columns for something like this.
The above is standard SQL.
I think your query should looks like this
SELECT
Data.Name AS [Customer Name],
CASE WHEN Data.Count > 1 THEN "Multiple-currencies" ELSE CAST(Data.Amount AS NVARCHAR) END AS Amount
FROM
(SELECT
Customer.Name,
COUNT(Item.Currency) AS Count,
SUM(Item.Amount) AS Amount
FROM
Customer
INNER JOIN Order ON Order.User_Id = Customer.Id
INNER JOIN Item ON Item.Id = Order.Product_Id
GROUP BY
Customer.Name) AS Data
A subquery to get the count of currencies and then ask for them in the main query to show the total or the text "Multiple-currencies".
Sorry if there is any mistake or mistype but I don't have a database server to test it
Hope this helps.
IMO I would start by standardizing variable names. Why call ID in customer table USER_ID in order table? Just a pet peeve. Anyway, you should learn how to build queries.
start with joining the customer table to the order table on then join the result to the item table. The first join is on CUSTOMER_ID and the second join is on PRODUCT_ID. Once you have that working use SUM and GROUP BY
Ok, I managed to solve the problem this way:-
SELECT innerQuery.Name AS Name, (CASE WHEN innerQuery.Currencies=1 THEN (innerQuery.Amount || innerQuery.Currency) ELSE 'Mutliple-Currencies' END) AS Amount, FROM
(SELECT Customer.Name, SUM(Item.Amount), COUNT(DISTINCT Item.Currency) AS Currencies, Item.Currency AS Currency FROM Customer
INNER JOIN Order ON Order.User_Id = Customer.Id
INNER JOIN Item ON Item.Id = Order.Product_Id
GROUP BY Customer.Name)innerQuery

Working SQL Server 2005 Query Optimization

I have two tables Sell and Purchase. My query is giving me the desired result, I am carefull about it performance so plz guide me if it can be better. My tables are:
Table Sell
UserId | ProductId | ProductName | ProductPrice
1 | p_101 | Cycle | 500
1 | p_121 | Car | 500000
2 | p_111 | Cycle | 5000
Table Purchase
UserId | ProductId | ProductName | ProductPrice
1 | p_109 | CellPhone | 150
2 | p_121 | Car | 500000
3 | p_111 | Book | 15
Desired OutPut Table
Type | ProductId | ProductName | ProductPrice
Sell | p_101 | Cycle | 500
Sell | p_121 | Car | 500000
Purchase| p_109 | CellPhone | 150
Working Query:
SELECT type, P1.ProductId, P1.ProductName, P1.ProductPrice
FROM
(
SELECT s.UserId, 'Sell' as type, s.ProductId, s.ProductName, s.ProductPrice FROM [Sell] s
UNION
SELECT p.userid, 'Purchase' as type, p.ProductId, p.ProductName, p.ProductPrice FROM [Purchase] p
) as P1
WHERE userid=1
Better design is to combine both tables and have a transaction_type column which will either have "Purchase" or "Sell" as values. If you do that you won't have to do UNION or UNION ALL.
With current design here is a simple and faster way to get records. Note that I have used UNION ALL which is faster than UNION as UNION uses DISTINCT to unique records which I think in your case doesn't apply. If you provide details about the index and execution plan I can see if there is a better way.
SELECT s.userid,
'Sell' as type,
s.ProductId,
s.ProductName,
s.ProductPrice
FROM Sell s
WHERE UserId = 1
UNION ALL
SELECT p.userid,
'Purchase' as type,
p.ProductId,
p.ProductName,
p.ProductPrice
FROM Purchase P
WHERE UserId = 1
Its better to use joins rather than subqueries. This way, there will be no overhead on your queries specially on dealing with large volumes of data.

Join with alternating possibilities

There is a Select statement
Select i.ItemID, s.Price, s.Qty, s.Company From Item i
Inner Join Sku s ON s.ItemID = i.ItemID
Which returns this:
ItemID | Price | Qty | Company
1 | $50.00 | 0 | Abc inc.
1 | $45.00 | 5 | Def inc.
1 | $35.00 | 15 | Xyz inc.
2 | $36.00 | 4 | Abc inc.
2 | $45.00 | 5 | Def inc.
2 | $35.00 | 1 | Xyz inc.
3 | $20.00 | 2 | Abc inc.
3 | $45.00 | 0 | Def inc.
3 | $35.00 | 5 | Xyz inc.
But there needs to be the following logic:
Show the row with the lowest Price and Qty > 0, including the referring Company to that Result.
Else...
Show the row with the lowest price, including the referring Company to that Price.
Which would look something like this:
ItemID | Price | Qty | Company
1 | $35.00 | 15 | Xyz inc.
2 | $35.00 | 1 | Xyz inc.
3 | $20.00 | 2 | Abc inc.
I haven't tried anything because I honestly don't know what to try or what to even ask in my question. Any suggestions?
Here is one way to do it:
SELECT I.ItemId
, S.Price
, S.Qty
, S.Company
FROM dbo.Item I
CROSS APPLY (
SELECT MIN(Price) Price
FROM dbo.Sku MP
WHERE I.ItemId = MP.ItemId
AND Qty > 0
) MP
CROSS APPLY (
SELECT
TOP 1 Price
, Qty
, Company
FROM dbo.Sku S
WHERE S.ItemId = I.ItemId
AND S.Price = MP.Price
) S
How about something like this, using row number:
select
i.ItemId, s.Price, s.Qty, s.Company
from
item i
inner join
(
select
t.ItemId,
t.Price,
t.Qty,
t.Company,
RowNumber = row_number() over (PARTITION BY t.ItemId order by t.price asc, t.qty desc)
from
Sku t
) s on s.itemid = i.ItemId
where
s.RowNumber = 1
The row numbers "partition by" and "order by" are important here to ensure the correct results and this works even if all quantities for an item are 0.
Here is the query,
Select i.ItemID, s.Price, s.Qty, s.Company
From Item i
Inner Join Sku s
ON s.ItemID = i.ItemID
Inner Join (
select min(price) as Price, ItemID
from Sku
where Qty > 0
group by ItemId
)a
on a.ItemId = s.ItemId
and a.Price = s.Price
If you need only ItemId from Item table, you can remove the join with Item table itself as ItemId is present in Sku.
Select i.ItemID, min(s.Price) as price, s.Qty, s.Company From Item i
Inner Join Sku s ON s.ItemID = i.ItemID and s.qty>0
group by i.itemid, s.company, s.qty
Select i.ItemID, Min(s.Price) as Price, s.Qty, s.Company
From Item i Inner Join Sku s ON s.ItemID = i.ItemID
Where s.qty > 0
Group by i.itemid, s.qty, s.Company
Basically, you want the first sku record, ordered first by whether or not it has any stock (items with stock appearing first), and then by price, ascending. In the following query the common table expression returns the required SKU fields, along with the RowNum calculated field. RowNum is partitioned by ItemID (i.e. resets to 1 for each new ItemID) and ordered by the presence of stock first, and then by price. The main query then selects the first record from each ItemID:
WITH cte AS (
SELECT ItemID, Price, Qty, company,
ROW_NUMBER() OVER (PARTITION BY ItemId ORDER BY
CASE WHEN Qty > 0 THEN 1 ELSE 0 END DESC, Price ASC) AS RowNum
FROM #sku
)
SELECT ItemID, price, qty, Company FROM cte WHERE RowNum = 1