Display only certain columns in results despite case statement in query - sql

DB-Fiddle
CREATE TABLE inventory (
id SERIAL PRIMARY KEY,
product VARCHAR,
quantity DECIMAL,
avg_price DECIMAL,
normal_price DECIMAL
);
INSERT INTO inventory
(product, quantity, avg_price, normal_price)
VALUES
('product_01', '800', '10', '10'),
('product_01', '300', '20', '90'),
('product_01', '200', '0', '50'),
('product_01', '500', '30', '80'),
('product_01', '600', '0', '60'),
('product_01', '400', '50', '40');
Expected Result:
product | quantity | final_price |
-------------|--------------|----------------|--------------
product_01 | 800 | 10 |
product_02 | 300 | 20 |
product_03 | 200 | 50 |
product_04 | 500 | 30 |
product_05 | 600 | 60 |
product_06 | 400 | 50 |
I only want to display the column quantity and final_price.
However, I have to use a CASE statement in my query and the syntax from postgresSQL is forcing me to add the column avg_price and normal_price to the query in order to make the CASE statement work:
SELECT
iv.product AS product,
iv.avg_price AS avg_price,
iv.normal_price AS normal_price,
SUM(iv.quantity) AS quantity,
(CASE WHEN iv.avg_price = 0 THEN iv.normal_price ELSE iv.avg_price END) AS final_price
FROM inventory iv
GROUP BY 1,2,3
ORDER BY 1;
Not sure if this is possible in postgresSQL but is there a way to only display the two columns as in the expected result?

I would suggest aggregating by the expression itself:
SELECT iv.product AS product,
SUM(iv.quantity) AS quantity,
(CASE WHEN iv.avg_price = 0 THEN iv.normal_price ELSE iv.avg_price END) AS final_price
FROM inventory iv
GROUP BY iv.product, final_price
ORDER BY 1;

Use explicit GROUP BY iv.product, iv.avg_price, iv.normal_price instead of GROUP BY 1, 2, 3:
SELECT
iv.product AS product,
SUM(iv.quantity) AS quantity,
(CASE WHEN iv.avg_price = 0 THEN iv.normal_price ELSE iv.avg_price END) AS final_price
FROM inventory iv
GROUP BY iv.product, iv.avg_price, iv.normal_price
ORDER BY 1;

Related

Calculate balance amount per prodcuct in redshift

DB-Fiddle
CREATE TABLE inventory (
id SERIAL PRIMARY KEY,
stock_date DATE,
product VARCHAR(255),
inbound_quantity INT,
outbound_quantity INT
);
INSERT INTO inventory
(stock_date, product, inbound_quantity, outbound_quantity
)
VALUES
('2020-01-01', 'Product_A', '900', '0'),
('2020-01-02', 'Product_A', '0', '300'),
('2020-01-03', 'Product_A', '400', '250'),
('2020-01-04', 'Product_A', '0', '100'),
('2020-01-05', 'Product_A', '700', '500'),
('2020-01-03', 'Product_B', '850', '0'),
('2020-01-08', 'Product_B', '100', '120'),
('2020-02-20', 'Product_B', '0', '360'),
('2020-02-25', 'Product_B', '410', '230'),
Expected Result:
stock_date
product
inbound_quantity
outbound_quantity
balance
2020-01-01
Product_A
900
0
900
2020-01-02
Product_A
0
300
600
2020-01-03
Product_A
400
250
750
2020-01-04
Product_A
0
100
650
2020-01-05
Product_A
700
500
850
2020-01-03
Product_B
740
0
740
2020-01-08
Product_B
100
120
720
2020-02-20
Product_B
0
360
360
2020-02-25
Product_B
410
230
540
2020-03-09
Product_B
290
0
830
I want to calculate the balance per product.
So far I have been able to develop this query below but it does not work.
I get error window "product" does not exist.
SELECT
iv.stock_date AS stock_date,
iv.product AS product,
iv.inbound_quantity AS inbound_quantity,
iv.outbound_quantity AS outbound_quantity,
SUM(iv.inbound_quantity - iv.outbound_quantity) OVER
(product ORDER BY stock_date ASC ROWS UNBOUNDED PRECEDING) AS Balance
FROM inventory iv
GROUP BY 1,2,3,4
ORDER BY 2,1;
How do I need to modify the query to make it work?
You are almost there
You should add partition by in front of product
SELECT
iv.stock_date AS stock_date,
iv.product AS product,
iv.inbound_quantity AS inbound_quantity,
iv.outbound_quantity AS outbound_quantity,
SUM(iv.inbound_quantity - iv.outbound_quantity) OVER
(partition by product ORDER BY stock_date ASC ROWS UNBOUNDED PRECEDING) AS Balance
FROM inventory iv
GROUP BY 1,2,3,4
ORDER BY 2,1;
So, it should be like this
The error message "product" does not exist is because you are trying to reference the column "product" in the OVER clause, but it is not included in the GROUP BY clause.
To fix this issue, you will need to include the "product" column in the GROUP BY clause, and also add a partition by clause to the OVER clause, so that the SUM function will calculate the balance per product.
Try this query:
SELECT
iv.stock_date AS stock_date,
iv.product AS product,
iv.inbound_quantity AS inbound_quantity,
iv.outbound_quantity AS outbound_quantity,
SUM(iv.inbound_quantity - iv.outbound_quantity) OVER
(PARTITION BY product ORDER BY stock_date ASC ROWS UNBOUNDED PRECEDING) AS Balance
FROM inventory iv
GROUP BY 1,2,3,4
ORDER BY 2,1;
This way, SUM function will only sum the inbound_quantity - outbound_quantity for each product, and not for all the products.
The query will return the expected result.

Assign total value of month to each day of month

DB-Fiddle
CREATE TABLE sales (
id SERIAL PRIMARY KEY,
country VARCHAR(255),
sales_date DATE,
sales_volume DECIMAL,
fix_costs DECIMAL
);
INSERT INTO sales
(country, sales_date, sales_volume, fix_costs
)
VALUES
('DE', '2020-01-03', '500', '0'),
('FR', '2020-01-03', '350', '0'),
('None', '2020-01-31', '0', '2000'),
('DE', '2020-02-15', '0', '0'),
('FR', '2020-02-15', '0', '0'),
('None', '2020-02-29', '0', '5000'),
('DE', '2020-03-27', '180', '0'),
('FR', '2020-03-27', '970', '0'),
('None', '2020-03-31', '0', '4000');
Expected Result:
sales_date | country | sales_volume | fix_costs
--------------|-------------|-------------------|-----------------
2020-01-03 | DE | 500 | 2000
2020-01-03 | FR | 350 | 2000
2020-02-15 | DE | 0 | 5000
2020-02-15 | FR | 0 | 5000
2020-03-27 | DE | 180 | 4000
2020-03-27 | FR | 970 | 4000
As you can see in my table I have a total of fix_costs assigned to the last day of each month.
In my results I want to assign this total of fix_costs to each day of the month.
Therefore, I tried to go with this query:
SELECT
s.sales_date,
s.country,
s.sales_volume,
f.fix_costs
FROM sales s
JOIN
(SELECT
((date_trunc('MONTH', sales_date) + INTERVAL '1 MONTH - 1 DAY')::date) AS month_ld,
SUM(fix_costs) AS fix_costs
FROM sales
WHERE country = 'None'
GROUP BY month_ld) f ON f.month_ld = LAST_DAY(s.sales_date)
WHERE country <> 'None'
GROUP BY 1,2,3;
For this query I get an error on the LAST_DAY(s.sales_date) since this expression does not exist in PostgresSQL.
However, I have no clue how I can replace it correctly in order to get the expected result.
Can you help me?
(MariaDB Fiddle as comparison)
demos:db<>fiddle
SELECT
s1.sales_date,
s1.country,
s1.sales_volume,
s2.fix_costs
FROM sales s1
JOIN sales s2 ON s1.country <> 'None' AND s2.country = 'None'
AND date_trunc('month', s1.sales_date) = date_trunc('month', s2.sales_date)
You need a natural self-join. Join conditions are:
First table without None records (s1.country <> 'None')
Second table only None records (s2.country = 'None')
Date: Only consider year and month part, ignore days. This can be achieved by normalizing the dates of both tables to the first of the month by using date_trunc(). So, e.g. '2020-02-15' results in '2020-02-01' and '2020-02-29' results in '2020-02-01' too, which works well as comparision and join condition.
Alternatively:
SELECT
*
FROM (
SELECT
sales_date,
country,
sales_volume,
SUM(fix_costs) OVER (PARTITION BY date_trunc('month', sales_date)) as fix_costs
FROM sales
) s
WHERE country <> 'None'
You can use the SUM() window function over the group of date_trunc() as described above. Then you need filter the None records afterwards
If I understand correctly, use window functions:
select s.*,
sum(fix_costs) over (partition by date_trunc(sales_date)) as month_fixed_costs
from sales;
Note that this assumes that fixed costs are NULL or 0 on other days -- which is true for the data in the question.

Using Pivot for multiple columns - SQL Server

I have these tables:
http://sqlfiddle.com/#!18/b871d/8
create table ItemOrder
(
ID int,
ItemNumber int,
Qty int,
Price int,
Cost int,
DateSold datetime
)
insert into ItemOrder (ID, ItemNumber, Qty, Price, Cost, DateSold)
Values
('1', '145', '5', '50', '25', '08-06-18'),
('2', '145', '5', '50', '25', '07-04-18'),
('3', '145', '5', '50', '25', '06-06-18')
Result:
| ID | ItemNumber | DateSold | Qty | Price | Cost |
|----|------------|----------------------|-----|-------|------|
| 1 | 145 | 2018-08-06T00:00:00Z | 5 | 50 | 25 |
| 2 | 145 | 2018-07-04T00:00:00Z | 5 | 50 | 25 |
| 3 | 145 | 2018-06-06T00:00:00Z | 5 | 50 | 25 |
But i was looking for a result that was split out by month like:
e.g.
| ID | ItemNumber | Aug-18 Qty | Aug-18 Price | Aug-18 Cost |July-18 Qty|July-18 Price|
|----|------------|------------|--------------|-------------|
| 1 | 145 | 5 | 50 | 25 |
and so on....
select
ID,
ItemNumber,
DateSold,
(
select ID, ItemNumber, Qty, DateSold
from ItemOrder
) x
PIVOT
(
SUM(QTY), SUM(Price), SUM(Cost) FOR DateSold in(DateSold1)
) p;
I have tried a couple of queries but cant seem to get it right. It would be great for any guidance. Thanks
I would suggest simply doing conditional aggregation:
select id, itemnumber,
sum(case when datesold >= '2018-08-01' and datesold < '2018-09-01' then qty else 0 end) as qty_201808,
sum(case when datesold >= '2018-08-01' and datesold < '2018-09-01' then price else 0 end) as price_201808,
sum(case when datesold >= '2018-07-01' and datesold < '2018-08-01' then qty else 0 end) as qty_201807,
sum(case when datesold >= '2018-07-01' and datesold < '2018-08-01' then price else 0 end) as price_201807
from itemorder
group by id, itemnumber
order by id, itemnumber;
Here is a SQL Fiddle.
WITH Table1 AS
(
select
ID,
ItemNumber,
CAST(year(DateSold) AS VARCHAR(4)) + ' ' + DATENAME(m, DateSold) AS [DateSold2],
Qty
from ItemOrder
)
select * from Table1
pivot (sum(Qty) for[DateSold2] IN ([2018 August], [2018 July], [2018 June])) as d
More what i was looking for :)
http://sqlfiddle.com/#!18/b871d/23

SQL PIVOT without aggregate columns

create table Product_Price
(
id int,
dt date,
SellerName varchar(20),
Product varchar(10),
ShippingTime varchar(20),
Price money
)
insert into Product_Price values (1, '2012-01-16','Sears','AA','2 days',32)
insert into Product_Price values (2, '2012-01-16','Amazon', 'AA','4 days', 40)
insert into Product_Price values (3, '2012-01-16','eBay','AA','1 days', 27)
insert into Product_Price values (4, '2012-01-16','Walmart','AA','Same day', 28)
insert into Product_Price values (5, '2012-01-16','Target', 'AA','3-4 days', 29)
insert into Product_Price values (6, '2012-01-16','Flipcart','AA',NULL, 30)
select *
from
(select dt, product, SellerName, sum(price) as price
from product_price group by dt, product, SellerName) t1
pivot (sum(price) for SellerName in ([amazon],[ebay]))as bob
)
I want 2 more columns in output (One is AmazonShippinTime another is eBayshippintime). How can I get these? Fiddle : http://sqlfiddle.com/#!3/2210d/1
Since you need to pivot on two columns and use different aggregates on both columns, I would use aggregate functions with a CASE expression to get the result:
select
dt,
product,
sum(case when SellerName = 'amazon' then price else 0 end) AmazonPrice,
max(case when SellerName = 'amazon' then ShippingTime end) AmazonShippingTime,
sum(case when SellerName = 'ebay' then price else 0 end) ebayPrice,
max(case when SellerName = 'ebay' then ShippingTime end) ebayShippingTime
from product_price
group by dt, product;
See SQL Fiddle with Demo. This gives a result:
| DT | PRODUCT | AMAZONPRICE | AMAZONSHIPPINGTIME | EBAYPRICE | EBAYSHIPPINGTIME |
|------------|---------|-------------|--------------------|-----------|------------------|
| 2012-01-16 | AA | 40 | 4 days | 27 | 1 days |

Calculation of balance after each transaction

I have table like this:
cust_id acc_no trans_id trans_type amount
1111 1001 10 credit 2000.0
1111 1001 11 credit 1000.0
1111 1001 12 debit 1000.0
2222 1002 13 credit 2000.0
2222 1002 14 debit 1000.0
I want a Hive query or sql query for every transaction done by a customer the balance should be calculated so.
I want output as follows:
cust_id acc_no trans_id trans_type amount balance
1111.0 1001.0 10.0 credit 2000.0 2000.0
1111.0 1001.0 11.0 credit 1000.0 3000.0
1111.0 1001.0 12.0 debit 1000.0 2000.0
2222.0 1002.0 13.0 credit 2000.0 2000.0
2222.0 1002.0 14.0 debit 1000.0 1000.0
I've tried
SELECT *
FROM (SELECT cust_id,
acc_no,
trans_id,
trans_type,
amount,
CASE
WHEN Trim(trans_type) = 'credit' THEN ball =
Trim(bal) + Trim(amt)
ELSE ball = Trim(bal) - Trim(amt)
end
FROM ban) l;
This query will do the trick :
SELECT t1.cust_id,t1.acc_no,t1.trans_id,t1.trans_type,t1.amount,
sum(t2.amount*case when t2.trans_type = 'credit' then 1
else -1 end) as balance
FROM Table1 t1
INNER JOIN Table1 t2 ON t1.cust_id = t2.cust_id AND
t1.acc_no = t2.acc_no AND
t1.trans_id >= t2.trans_id
GROUP BY t1.cust_id,t1.acc_no,t1.trans_id,t1.trans_type,t1.amount
See SQLFIDDLE : http://www.sqlfiddle.com/#!2/3b5d8/15/0
EDIT :
SQL Fiddle
MySQL 5.5.32 Schema Setup:
CREATE TABLE Table1
(`cust_id` int, `acc_no` int, `trans_id` int,
`trans_type` varchar(6), `amount` int)
;
INSERT INTO Table1
(`cust_id`, `acc_no`, `trans_id`, `trans_type`, `amount`)
VALUES
(1111, 1001, 10, 'credit', 2000.0),
(1111, 1001, 11, 'credit', 1000.0),
(1111, 1001, 12, 'debit', 1000.0),
(2222, 1002, 13, 'credit', 2000.0),
(2222, 1002, 14, 'debit', 1000.0)
;
Query 1:
SELECT t1.cust_id,t1.acc_no,t1.trans_id,t1.trans_type,t1.amount,
sum(t2.amount*case when t2.trans_type = 'credit' then 1
else -1 end) as balance
FROM Table1 t1
INNER JOIN Table1 t2 ON t1.cust_id = t2.cust_id AND
t1.acc_no = t2.acc_no AND
t1.trans_id >= t2.trans_id
GROUP BY t1.cust_id,t1.acc_no,t1.trans_id,t1.trans_type,t1.amount
Results:
| CUST_ID | ACC_NO | TRANS_ID | TRANS_TYPE | AMOUNT | BALANCE |
|---------|--------|----------|------------|--------|---------|
| 1111 | 1001 | 10 | credit | 2000 | 2000 |
| 1111 | 1001 | 11 | credit | 1000 | 3000 |
| 1111 | 1001 | 12 | debit | 1000 | 2000 |
| 2222 | 1002 | 13 | credit | 2000 | 2000 |
| 2222 | 1002 | 14 | debit | 1000 | 1000 |
A simple solution is to quantify each transaction (- or +) based on trans_type and then get cumulative sum using window function .
SELECT cust_id,
acc_no,
trans_id,
trans_type,
amount,
Sum (real_amount)
OVER (ORDER BY cust_id) AS balance
FROM (SELECT cust_id,
acc_no,
trans_id,
trans_type,
amount,
( CASE trans_type
WHEN 'credit' THEN amount
WHEN 'debit' THEN amount *- 1
END ) AS real_amount
FROM test) t
You could do this easily through a View, calculating this directly on the table is possible but leads to performance and scalability issues (the database will slow down as the table grows). By using a View the calculation is performed as-needed; if you index the view you can keep the balances up to date without impacting the performance of the transaction table.
If you really insist on it being in the transaction table itself you could possibly use a calculated column which runs a user-defined function to determine the current balance. However this will depend largey on the specific SQL backend you're using.
Here's a basic SELECT Statement which calculates the current balance by Account:
select
acc_no,
sum(case trans_type
when 'credit' then amount
when 'debit' then amount * -1
end) as Amount
from Transactions
group by acc_no
You can use window function:
select cust_id,
acc_no, trans_id, trans_type, amount,
sum(pre_balance) over (partition by cust_id order by trans_id) as balance
from
(select cust_id, acc_no, trans_id, trans_type,
amount,
amount as pre_balance from test
where trans_type = 'credit'
union
select cust_id, acc_no, trans_id, trans_type,
amount, -amount as pre_balance from
test where trans_type = 'debit'
order by trans_id) as sub;
with current_balances as (
SELECT
id,
user_id,
SUM(amount) OVER (PARTITION BY user_id ORDER BY created ASC) as current_balance
FROM payments_transaction pt
ORDER BY created DESC
)
SELECT
pt.id,
amount,
pt.user_id,
cb.current_balance as running_balance
FROM
payments_transaction pt
INNER JOIN
current_balances cb
ON pt.id = cb.id
ORDER BY created DESC
LIMIT 10;
This will work very efficiently for big returns, and won't break on filtering or limiting. Please note that if you select only for one user or a subset of them, provide user_id filter in both current_balances cte, and the main select to omit whole table scan.
Table (Transaction)
-
"id" "amount" "is_credit"
1 10000 1
2 2000 0
3 5000 1
Query :
SELECT *
FROM (
SELECT id, amount, SUM(CASE When is_credit=1 Then amount Else -amount End) OVER (ORDER BY id) AS balance
FROM `Transaction`
GROUP BY id, amount
)
ORDER BY id ;
Output :
"id" "amount" "is_credit" "balance"
1 10000 1 10000
2 2000 0 8000
3 5000 1 13000