Pivot in PostgreSQL using with clause - sql

I need some help in writing the SQL query.
I want to convert
Product
Employee
Sales
Sales_inUSD
Sales_inEuro
A
Anand
10000
121.24
114.26
A
Yash
12000
145.51
137.10
B
Anand
13000
157.64
148.37
B
Yash
15000
181.89
171.42
to
Product
Anand
Yash
Currency
A
10000
12000
Rupee
A
121.24
145.51
USD
A
114.26
137.10
Euro
B
13000
15000
Rupee
B
157.64
181.89
USD
B
148.37
171.42
Euro
I have to achieve this using with clause, I cannot make any kind of the table. and I have to do it in Postgresql where PIVOT/UNPIVOT doesn't work.

UNPIVOT can be done by using a cross join with a VALUES clause:
select t.product,
t.employee,
u.*
from the_table t
cross join lateral (
values ('Rupee', t.sales), ('EUR', t.sales_ineuro), ('USD', t.sales_inusd)
) as u(currency, sales)
;
Doing a PIVOT can then be done using conditional aggregation.
select t.product,
max(u.sales) filter (where employee = 'Anand') as anand,
max(u.sales) filter (where employee = 'Yash') as yash,
u.currency
from the_table t
cross join lateral (
values ('Rupee', t.sales), ('EUR', t.sales_ineuro), ('USD', t.sales_inusd)
) as u(currency, sales)
group by t.product, u.currency
order by t.product,
case u.currency
when 'Rupee' then 1
when 'USD' then 2
else 3
end
A common table expression isn't really necessary, but can be used if you want:
with unpivot as (
select t.product,
t.employee,
u.*
from the_table t
cross join lateral (
values ('Rupee', t.sales), ('EUR', t.sales_ineuro), ('USD', t.sales_inusd)
) as u(currency, sales)
)
select product,
max(sales) filter (where employee = 'Anand') as anand,
max(sales) filter (where employee = 'Yash') as yash,
currency
from unpivot
group by product, currency
order by product,
case currency
when 'Rupee' then 1
when 'USD' then 2
else 3
end

Related

SQL query when joining two tables

I need to join two tables in SQL and I need to find the counts of how many customer id's in table A are also found in table B, extracting how many Customer id in A's also purchased in table B by year. My query is as follows:
SELECT
a.year, count(distinct(a.id),
count (distinct(b.id)
FROM
purchase as A,
purchase2 as B
WHERE
(a.id=b.id)
AND
a.year>2010
GROUP BY a.year
Is this correct? do I need to include count(distinct(b.id) in the select statement? do I also need to group by b.year?
thanks in advance for any assistance
Change your distinct, and you can do an inner join to be sure :
SELECT
A.year,
count(DISTINCT A.id),
count (DISTINCT B.id)
FROM
purchase as A
INNER JOIN purchase2 B ON B.id = A.id
WHERE
A.year>2010
GROUP BY
A.year,
B.year
I think union all and aggregation is the best approach. Start with the information per id/year:
select id, year, max(in_a), max(in_b)
from ((select distinct id, year, 1 as in_a, 0 as in_b
from purchase
) union all
(select distinct id, year, 0 as in_a, 1 as in_b
from purchase2
)
) ab
group by id, year;
Then aggregate this by year:
select year,
sum(in_a) as total_a,
sum(in_b) as total_b,
sum(in_a * in_b) as in_both
from (select id, year, max(in_a), max(in_b)
from ((select distinct id, year, 1 as in_a, 0 as in_b
from purchase
) union all
(select distinct id, year, 0 as in_a, 1 as in_b
from purchase2
)
) ab
group by id, year
) iy
group by year;

calculate the balance amount and insert another table

I need some help on building an sql query, I have below two queries, I want to combine the sum of debit substracted from credit and then insert result as balance into another table
select sum(amount)
from ACCOUNT_TRANSACTIONS
where CUSTOMER_USER_NAME='55555' and transaction_type='credit' and account_type='customer' and IS_DELETED='false'
select sum(amount)
from ACCOUNT_TRANSACTIONS
where CUSTOMER_USER_NAME='55555' and transaction_type='debit' and account_type='customer' and IS_DELETED='false'
Could use CROSS APPLY
select a.*, b.CreditSum, c.DebitSum
from ACCOUNT_TRANSACTIONS a
cross apply
(select sum(amount) as CreditSum
from ACCOUNT_TRANSACTIONS
where CUSTOMER_USER_NAME='55555' and transaction_type='credit' and account_type='customer' and IS_DELETED='false'
) b
cross apply
(select sum(amount) as DebitSum
from ACCOUNT_TRANSACTIONS
where CUSTOMER_USER_NAME='55555' and transaction_type='debit' and account_type='customer' and IS_DELETED='false'
) c
where a.CUSTOMER_USER_NAME='55555' and a.account_type='customer' and a.IS_DELETED='false'
You can do this using conditional aggregation:
select sum(case when transaction_type = 'credit' then amount when transaction_type = 'debit' then - amount end) as balance
from ACCOUNT_TRANSACTIONS
where CUSTOMER_USER_NAME = '55555' and
account_type = 'customer' and
IS_DELETED = 'false' ;
You would then insert this into another table using insert, although I'm not sure how a single value in a single row would be useful.

SQL Select Group By Min() - but select other

I want to select the ID of the Table Products with the lowest Price Grouped By Product.
ID Product Price
1 123 10
2 123 11
3 234 20
4 234 21
Which by logic would look like this:
SELECT
ID,
Min(Price)
FROM
Products
GROUP BY
Product
But I don't want to select the Price itself, just the ID.
Resulting in
1
3
EDIT: The DBMSes used are Firebird and Filemaker
You didn't specify your DBMS, so this is ANSI standard SQL:
select id
from (
select id,
row_number() over (partition by product order by price) as rn
from orders
) t
where rn = 1
order by id;
If your DBMS doesn't support window functions, you can do that with joining against a derived table:
select o.id
from orders o
join (
select product,
min(price) as min_price
from orders
group by product
) t on t.product = o.product and t.min_price = o.price;
Note that this will return a slightly different result then the first solution: if the minimum price for a product occurs more then once, all those IDs will be returned. The first solution will only return one of them. If you don't want that, you need to group again in the outer query:
select min(o.id)
from orders o
join (
select product,
min(price) as min_price
from orders
group by product
) t on t.product = o.product and t.min_price = o.price
group by o.product;
SELECT ID
FROM Products as A
where price = ( select Min(Price)
from Products as B
where B.Product = A.Product )
GROUP BY id
This will show the ID, which in this case is 3.

Oracle Complex Sort - Multiple Children

I have a table as follows:
BRAND_ID PRODUCT_ID PRODUCT_DESC PRODUCT_TYPE
100 1000 Tools A
100 1500 Tools A
200 2000 Burgers B
300 3000 Clothing C
300 4000 Makeup D
300 5000 Clothing C
So a Brand can have multiple products, all of the same type or mixed types. If a brands products are all of the same type I need them first in the result, sorted by product type, followed by brands that have different product types. I can do this programatically but I wanted to see if there is a way to do it in the query.
I don't have access to Oracle, but I believe something along these lines should work...
WITH
ranked_data
AS
(
SELECT
COUNT(DISTINCT product_type) OVER (PARTITION BY brand_id) AS brand_rank,
MIN(product_type) OVER (PARTITION BY brand_id) AS first_product_type,
*
FROM
yourTable
)
SELECT
*
FROM
ranked_data
ORDER BY
brand_rank,
first_product_type,
brand_id,
product_type,
product_description
An alternative is to JOIN on to a sub-query to calculate the two sorting fields.
SELECT
yourTable.*
FROM
yourTable
INNER JOIN
(
SELECT
brand_id,
COUNT(DISTINCT product_type) AS brand_rank,
MIN(product_type) AS first_product_type,
FROM
yourTable
GROUP BY
brand_id
)
AS brand_summary
ON yourTable.brand_id = brand_summary.brand_id
ORDER BY
brand_summary.brand_rank,
brand_summary.first_product_type,
yourTable.brand_id,
yourTable.product_type,
yourTable.product_description
How about selecting from a sub-select that figures out number of distinct brands and then sorting by the count?
select t.BRAND_ID,
t.PRODUCT_ID,
t.PRODUCT_DESC,
t.PRODUCT_TYPE
from (select t2.BRAND_ID,
t2.PRODUCT_ID,
count(distinct t2.PRODUCT_TYPE) cnt
from YOURTABLE t2
group by t2.BRAND_ID, t2.PRODUCT_ID) data
join YOURTABLE t on t.BRAND_ID = data.BRAND_ID and t.PRODUCT_ID = data.PRODUCT_ID
order by data.cnt, BRAND_ID, PRODUCT_ID, PRODUCT_TYPE

SQL latest record per group with an aggregated column

I have a table similar to this:
STOCK_ID TRADE_TIME PRICE VOLUME
123 1 5 100
123 2 6 150
456 1 7 200
456 2 8 250
For each stock I want to get latest price (where latest is just the max trade_time) and aggregated volume, so for the above table I want to see:
123 6 250
456 8 450
I've just discovered that the current query doesn't (always) work, ie there's no guarantee that the price selected is always the latest:
select stock_id, price, sum(volume) group by stock_id
Is this possible to do without subqueries? Thanks!
As you didn't specify the database you are using Here is some generic SQL that will do what you want.
SELECT
b.stock_id,
b.trade_time,
b.price,
a.sumVolume
FROM (SELECT
stock_id,
max(trade_time) AS maxtime,
sum(volume) as sumVolume
FROM stocktable
GROUP BY stock_id) a
INNER JOIN stocktable b
ON b.stock_id = a.stock_id and b.trade_time = a.maxtime
In SQL Server 2005 and up, you could use a CTE (Common Table Expression) to get what you're looking for:
;WITH MaxStocks AS
(
SELECT
stock_id, price, tradetime, volume,
ROW_NUMBER() OVER(PARTITION BY stock_ID ORDER BY TradeTime DESC) 'RowNo'
FROM
#stocks
)
SELECT
m.StockID, m.Price,
(SELECT SUM(VOLUME)
FROM maxStocks m2
WHERE m2.STock_ID = m.Stock_ID) AS 'TotalVolume'
FROM maxStocks m
WHERE rowno = 1
Since you want both the last trade as well as the volume of all trades for each stock, I don't see how you could do this totally without subqueries, however....
declare #Stock table(STOCK_ID int,TRADE_TIME int,PRICE int,VOLUME int)
insert into #Stock values(123,1,5,100),(123,2,6,150),(456,1,7,200),(456,2,8,250)
Select Stock_ID,Price,(Select sum(Volume) from #Stock B where B.Stock_ID=A.Stock_ID)Volume from #Stock A
where A.Trade_Time=(Select max(Trade_Time) from #Stock)
select a.stock_id, b.price , sum(a.volume) from tablename a
join (select stock_id, max(trade_time), price from tablename
group by stock_id) b
on a.stock_id = b.stock_id
group by stock_id