Oracle - Count condition case - sql

I am facing a problem with a query. My goal is to get all product names, unit of mass, quantity and number of pallets they are located in. The problem is with number of pallets of item.
QUERY:
SELECT "Product_Name"
, "Unit_of_Mass"
, SUM("Quantity_Per_UOM")
, Count(*) as "Number_Of_Pallet"
FROM
(select p.prod_desc as "Product_Name"
, s.quantity as "Quantity_Per_UOM"
, u.description as "Unit_of_Mass"
, s.container_id
, s.product_id
from wms_stock s
join wms_product p on p.product_id = s.product_id
join wms_uom u on p.uom_base_id = u.uom_id
)
group by "Product_Name", "Unit_of_Mass"
It almost works. The problem is I need to do some condition in Count(*) (that's what I think should be done). In table wms_stock I got product_id and container_id, and when in some row they are same it should count the number of pallets as 1 but still add the quantities.
So from first select:
Product_Name | Quantity | UnitOfMass | ContainerId | ProductId
A | 2 | kg | 10 | 11
A | 1 | kg | 10 | 11
B | 2 | kg | 11 | 12
I should get result
Product_Name | Quantity_Per_UOM | UnitOfMass | Number_Of_Pallet
A | 3 | kg | 1
B | 2 | kg | 1

You can try below condition in your select list -
COUNT(DISTINCT ContainerId || ProductId)
Just for your information, || is not an operator rather it is concatenation operator in Oracle. So i have just concat both the columns and picked up the distinct from them.

Related

Creating view from item table and price table

Let's say I have two tables. One is the orders table and the other is a price table giving the price of an item w.r.t. the number of items ordered.
The meaning of the test_price table is "up-to count items(inclusive) the cost per item is price". So 0-50 items cost 1.22 per item. 51-100 cost 1.20 per item.
table test_price
id | count | price
----+-------+-------
1 | 50 | 1.22
2 | 100 | 1.20
3 | 150 | 1.19
4 | 200 | 1.18
5 | 300 | 1.10
table test_orders
id | count
----+-------
1 | 12
2 | 50
3 | 65
4 | 155
5 | 400
So this means that order 1 for 12 items should be priced at 1.22 per item.
Order 5 should be priced at 1.10 per item.
I can get the price for a single order with
SELECT price FROM test_prices WHERE
count >= (SELECT count FROM test_orders WHERE id = 1)
ORDER BY count ASC LIMIT 1;
I would like to create a view that shows the orders with unit price and total price as columns
Join the tables on the count columns applying your conditions:
with cte as (select max(count) maxcount from test_price)
select
o.*, o.count * p.price total_price
from test_orders o inner join test_price p
on p.count = coalesce(
(select min(count) from test_price where o.count <= count),
(select maxcount from cte)
)
order by o.id;
See the demo.
Results:
| id | count | total_price |
| --- | ----- | ----------- |
| 1 | 12 | 14.64 |
| 2 | 50 | 61 |
| 3 | 65 | 78 |
| 4 | 155 | 182.9 |
| 5 | 400 | 440 |
I think your test_prices table is not quite right. I think it should be:
id | count | price
----+-------+-------
1 | 1 | 1.22
2 | 100 | 1.20
3 | 150 | 1.19
4 | 200 | 1.18
5 | 300 | 1.10
This says that for 1-99 quantity, the price is $1.22. For 100-149, the price $1.20, and so on.
With this structure, you can use a lateral join:
select o.*, p.price
from test_orders o left join lateral
(select p.*
from test_prices p
where p.count <= o.count
order by p.count desc
fetch first 1 row only
) p
on 1=1
The problem with your data is that price_table data is not all "up-to count", because the 5th row also means "up-to and beyond".
If you are able to add another row to price_table like (6, 10000, 1.10) then the solution is easy:
CREATE VIEW price_view AS
SELECT orders.id, orders.count, p.price, orders.count * p.price as total
FROM (
SELECT o.id, o.count, min(p.count) as pricebracket
FROM test_price p
LEFT JOIN test_orders o ON p.count >= o.count
GROUP BY o.id, o.count) orders
LEFT JOIN test_price p ON orders.pricebracket = p.count
ORDER BY orders.id
If you cannot get rid of this inconsistency in your data then you have to select maximum value first. So the query modifies like this:
CREATE VIEW price_view AS
WITH temptable as (SELECT max(p.count) as maxcount FROM test_price p)
SELECT orders.id, orders.count, p.price, orders.count * p.price as total
FROM (
SELECT o.id, o.count, min(p.count) as pricebracket
FROM test_price p
CROSS JOIN temptable
LEFT JOIN test_orders o ON p.count >=
(case when o.count > temptable.maxcount then temptable.maxcount else o.count end)
GROUP BY o.id, o.count) orders
LEFT JOIN test_price p ON orders.pricebracket = p.count
ORDER BY orders.id

Select that joins two tables Oracle PL/SQL

I've got two tables wchich I need to join with Select and I've got a problem.
The tables look like that:
table_price
Product_ID | Buy_date | Buy_price |
1 | 16.10.01 | 2.50 |
1 | 16.11.02 | 3.20 |
2 | 16.10.31 | 3.80 |
table expire_date
Product_ID | Count | Exp_date |
1 | 1000 | 17.10.01|
1 | 500 | 17.11.31|
2 | 500 | 17.11.01|
I need to write a select in Oracle PL/SQL wchich gives me following results:
Product_ID| Count | Exp_date| last_buy_price|
1 | 1000 | 17.10.01| 3.20 |
1 | 500 | 17.31.31| 3.20 |
2 | 500 | 17.11.01| 3.80 |
It means that it will give me every expire date with count of product from table expire_date and match it with last buy price from table_price with product_id (always with last buy price, ordered by column buy_date)
Please guys help me, I've tried so many codes and I still can't get satysfying results
A correlated subquery using keep is possibly the most performant method:
select ed.*,
(select max(p.buy_price) keep (dense_rank first order by p.buy_date desc)
from table_price p
where p.product_id = ed.product_id
) as last_buy_price
from expire_date ed;
You could, of course, also express this in the from clause:
select ed.*, p.last_buy_price
from expire_date ed left join
(select p.product_id,
max(p.buy_price) keep (dense_rank first order by p.buy_date desc) as last_buy_price
from table_price p
) p
on p.product_id = ed.product_id;
You can use ROW_NUMBER() :
SELECT ed.*,
tp.buy_price as last_buy_price
FROM expire_date ed
JOIN(SELECT s.*,
ROW_NUMBER() OVER(PARTITION BY s.product_id ORDER BY s.buy_date DESC) as rnk
FROM table_price s) tp
ON(ed.product_id = tp.product_id and tp.rnk = 1 )

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

Stock calculation in Postgres

I have a table p1 with transactions in Postgres like this:
| id | product_id | transaction_date | quantity |
|----|------------|------------------|----------|
| 1 | 1 | 2015-01-01 | 1 |
| 2 | 1 | 2015-01-02 | 2 |
| 3 | 1 | 2015-01-03 | 3 |
and p2 table with products like this:
| id | product | stock |
|----|--------------|-------|
| 1 | Product A | 15 |
stock in p2' has been be reduced for every new record in p1.
How to reconstruct previous states to get this result?
| product | first_stock | quantity | last_stock |
|-----------|-------------|----------|------------|
| Product A | 21 | 1 | 20 |
| Product A | 20 | 2 | 18 |
| Product A | 18 | 3 | 15 |
I have tried using lead() to get the quantity after the current row.
SELECT p2.product, p1.quantity, lead(p1.quantity) OVER(ORDER BY p1.id DESC)
FROM p1 INNER JOIN p2 ON p1.product_id = p2.id;
But how to calculate leading rows from the current stock?
You don't just need lead() you need the running sum over all rows in between to reconstruct previous states from transaction data:
SELECT p2.product
, p2.stock + px.sum_quantity AS first_stock
, px.quantity
, p2.stock + px.sum_quantity - quantity AS last_stock
FROM p2
JOIN (
SELECT product_id, quantity, transaction_date
, sum(quantity) OVER (PARTITION BY product_id
ORDER BY transaction_date DESC) AS sum_quantity
FROM p1
) px ON px.product_id = p2.id
ORDER BY px.transaction_date;
Assuming the course of events actually indicated by transaction_date.
Use the aggregate function sum() as window-aggregate function to get the running sum. Use a subquery, since we use the running sum of quantities (sum_quantity) multiple times.
For last_stock subtract quantity of the current row (after adding it redundantly).
Nitpick
Theoretically, it would be cheaper to use a custom frame definition for the window frame to only sum quantities up to preceding row, so we don't add and subtract the quantity of the current row redundantly. But that's more complex and hardly faster in reality:
SELECT p2.id, p2.product, px.transaction_date -- plus id and date for context
, p2.stock + COALESCE(px.pre_sum_q + px.quantity, 0) AS first_stock
, px.quantity
, p2.stock + COALESCE(px.pre_sum_q, 0) AS last_stock
FROM p2
LEFT JOIN (
SELECT id, product_id, transaction_date
, quantity
, sum(quantity) OVER (PARTITION BY product_id
ORDER BY transaction_date DESC
ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING) AS pre_sum_q
FROM p1
) px ON px.product_id = p2.id
ORDER BY px.transaction_date, px.id;
Explanation for the frame definition in this related answer:
Grouping based on sequence of rows
While being at it, also demonstrating how to prevent missing rows and NUll values with LEFT JOIN and COALESCE for products that don't have any related rows in p1, and a stable sort order if there are multiple transactions for the same product on the same day.
Still assuming all columns to be defined NOT NULL, or you need to do some more for corner cases with NULL values.

Select columns with and without group by

Having Table1
id | productname | store | price
-----------------------------------
1 | name a | store 1 | 4
2 | name a | store 2 | 3
3 | name b | store 3 | 6
4 | name a | store 3 | 4
5 | name b | store 1 | 7
6 | name a | store 4 | 5
7 | name c | store 3 | 2
8 | name b | store 6 | 5
9 | name c | store 2 | 1
I need to get all columns but only the rows with the
lowest price.
Result needed:
id | productname | store | price
-----------------------------------
2 | name a | store 2 | 3
8 | name b | store 6 | 5
9 | name c | store 2 | 1
My best try is:
SELECT ProductName, MIN(Price) AS minPrice
FROM Table1
GROUP BY ProductName
But then I need the ID and STORE for each row.
Try this
select p.* from Table1 as p inner join
(SELECT ProductName, MIN(Price) AS minPrice FROM Table1 GROUP BY ProductName) t
on p.productname = t.ProductName and p.price = t.minPrice
Select ID,ProductName,minPrice
from
(
SELECT ProductName, MIN(Price) AS minPrice
FROM Table1
GROUP BY ProductName
) t
join Table1 t1 on t.ProductName = t1.ProductName
You didn't mention your SQL dialect, but most DBMSes support Standard SQL's "Windowed Aggregate Functions":
select *
from
( select t.*,
RANK() OVER (PARTITION BY ProductName ORDER BY Price) as rnk
from table1 as t
) as dt
where rnk = 1
If multiple stores got the same lowest price all of them will be returned. If you want only a single shop you have to switch to ROW_NUMBER instead of RANK or add column(s) to the ORDER BY.
I think this query should do:
select min(t.id) id
, t.productname
, t.price
from table1 t
join
( select min(price) min_price
, productname
from table1
group
by productname
) v
on v.productname = t.productname
and v.price = t.min_price
group
by t.productname
, t.price
It determines the lowest price per product and fetches every line in the base table (t). This avoids duplicates by grouping on the productname and selecting the lowest id.
This should work for you:
SELECT * FROM `Table1` AS `t1`
WHERE (
SELECT count(*) FROM `Table1` AS `t2` WHERE `t1`.`productName` = `t2`.`productName` AND `t2`.`price` < `t1`.`price`) < 1
Check SqlFiddle
But if you have same products with same minimum price in two stores, you will get both of them in result output