PosrgreSQL Pivot Table - sql

I need to make a PIVOT table from Source like this table
FactID UserID QTY Product
1 10 100 A
2 10 200 B
3 10 300 C
4 12 50 A
5 12 60 B
6 12 70 C
7 15 500 A
8 15 550 B
9 15 600 C
Need Pivot Like this
UserID A B C
10 100 200 300
12 50 60 70
15 500 550 600
My try
Select UserID,
CASE WHEN product = 'A' then QTY end as A,
CASE WHEN product = 'B' then QTY end as B,
CASE WHEN product = 'C' then QTY end as C
from public.table
And Result
UserID A B C
10 100 100 100
10 200 200 200
10 300 300 300
12 50 50 50
12 60 60 60
12 70 70 70
15 500 500 500
15 550 550 550
15 600 600 600
Where's my mistake? Maybe there's another way to do it?

Very close. You just need aggregation:
Select UserID,
SUM(CASE WHEN product = 'A' then QTY end) as A,
SUM(CASE WHEN product = 'B' then QTY end) as B,
SUM(CASE WHEN product = 'C' then QTY end) as C
from public.table
group by UserId;
In Postgres, though, this would normally use the FILTER clause instead of CASE:
Select UserID,
SUM(qty) FILTER (WHERE product = 'A') as A,
SUM(qty) FILTER (WHERE product = 'B') as B,
SUM(qty) FILTER (WHERE product = 'C') as C
from public.table
group by UserId;

You need aggregate function as
Select UserID,
Max(CASE WHEN product = 'A' then QTY end) as A,
Max(CASE WHEN product = 'B' then QTY end) as B,
Max(CASE WHEN product = 'C' then QTY end) as C
from public.table
Group by userid

Related

Grouping like records

We have a set of data where there are 2 records for each CODE. An example of the data is this:
TICKER CODE SCORE PRICE PCF
---------------------------------------
ABC 23 A 100 20
DEF 23 B 200 30
XXX 52 C 300 40
YYY 52 D 400 50
GHI 86 E 500 60
JKL 86 F 600 70
MNO 27 G 700 80
PQR 27 H 800 90
So, what we need to do is create a query which will return the columns of the like records by CODE into 1 record like this:
CODE,TICKER_1,SCORE_1,PRICE_1,PCF_1,TICKER_2,SCORE_2,PRICE_2,PCF_2
23,ABC,A,100,20,DEF,B,200,30
52,XXX,C,300,40,YYY,D,400,40
86,GHI,E,500,60,JKL,F,600,70
27,MNO,G,700,80,PQR,H,800,90
So, that they are combined by like CODE values.
You may try the following which assigns uses ROW_NUMBER to assign a row number for each code entry before using MAX with a case expression to filter for each entry.
Eg.
SELECT
CODE,
MAX(CASE WHEN rn=1 THEN TICKER END) AS TICKER_1,
MAX(CASE WHEN rn=1 THEN SCORE END) AS SCORE_1,
MAX(CASE WHEN rn=1 THEN PRICE END) AS PRICE_1,
MAX(CASE WHEN rn=1 THEN PCF END) AS PCF_1,
MAX(CASE WHEN rn=2 THEN TICKER END) AS TICKER_2,
MAX(CASE WHEN rn=2 THEN SCORE END) AS SCORE_2,
MAX(CASE WHEN rn=2 THEN PRICE END) AS PRICE_2,
MAX(CASE WHEN rn=2 THEN PCF END) AS PCF_2
FROM (
SELECT
m.*,
ROW_NUMBER() OVER (PARTITION BY CODE ORDER BY TICKER) as rn
FROM mytable m
) m1
GROUP BY
CODE
Outputs:
CODE
TICKER_1
SCORE_1
PRICE_1
PCF_1
TICKER_2
SCORE_2
PRICE_2
PCF_2
23
ABC
A
100
20
DEF
B
200
30
27
MNO
G
700
80
PQR
H
800
90
52
XXX
C
300
40
YYY
D
400
50
86
GHI
E
500
60
JKL
F
600
70
For debugging purposes, the output of the subquery
SELECT
m.*,
ROW_NUMBER() OVER (PARTITION BY CODE ORDER BY TICKER) as rn
FROM mytable m
looks like this:
TICKER
CODE
SCORE
PRICE
PCF
RN
ABC
23
A
100
20
1
DEF
23
B
200
30
2
MNO
27
G
700
80
1
PQR
27
H
800
90
2
XXX
52
C
300
40
1
YYY
52
D
400
50
2
GHI
86
E
500
60
1
JKL
86
F
600
70
2
View working demo on db fiddle
Notable alternatives
Instead of CASE WHEN rn=1 THEN TICKER END you could also use the DECODE function available in oracle as DECODE(rn,1,TICKER)
You may also use a pivot as shown below (NB. Column names are not as in the expected result)
WITH cte as (
SELECT
m.*,
ROW_NUMBER() OVER (PARTITION BY CODE ORDER BY TICKER) as rn
FROM mytable m
)
SELECT * FROM cte
PIVOT (
MAX(TICKER) as "TICKER",
MAX(SCORE) as "SCORE",
MAX(PRICE) as "PRICE",
MAX(PCF) as "PCF"
FOR rn IN (1,2)
)
Outputs:
CODE
1_TICKER
1_SCORE
1_PRICE
1_PCF
2_TICKER
2_SCORE
2_PRICE
2_PCF
23
ABC
A
100
20
DEF
B
200
30
27
MNO
G
700
80
PQR
H
800
90
52
XXX
C
300
40
YYY
D
400
50
86
GHI
E
500
60
JKL
F
600
70
View working demo on db fiddle here

How to find missing Number along with the numbers present in table in Oracle

I am trying to fetch data for one of my clients, but there are missing tokens in his data. I tried the below query for fetching the missing data and inserting into one dump table, but would like to find a more optimized way where I find the missing tokens along with the token's present in the table.
SELECT MinToken + 1 Level
FROM (SELECT Min(Token) AS MINTOKEN, MAX(Token) AS MAXTOKEN
FROM dmp_SellerOrders
Where OrderNo = 1
AND TradeDate = '27-Oct-20')
CONNECT BY LEVEL < MAXTOKEN - MINTOKEN
MINUS
SELECT TOKEN
FROM dmp_SellerOrders
Where (OrderNo, TranserialNo) IN (SELECT OrderNo, MAX(TranserialNo)
FROM dmp_SellerOrders
GROUP BY OrderNo)
AND TradeDate = '27-Oct-20';
Actual Data in Table looks like this,
Original Order OrderNo TranserialNo Token Qty Price
1 1 25 100 100
1 1 26 100 100
1 1 27 100 100
1 1 28 100 100
1 1 30 100 100
1 1 31 100 100
Order Price Modified OrderNo TranserialNo Token Qty Price
1 2 25 100 200
1 2 26 100 200
1 2 27 100 200
1 2 28 100 200
1 2 30 100 200
1 2 31 100 200
I need data to show by grouping Qty, as show below,
OrderNo MinToken MaxToken Qty Price
1 25 28 100 200
1 29 29 0 0
1 30 31 100 200
But if I group qty then I get below out,
OrderNo MinToken MaxToken Qty Price
1 25 31 100 200
1 29 29 0 0
Can any one please help me, how can I get the output as expected.
Regards,
Mehul
Here a simplified solution without orderNo and TradeDate to demonstrate the concept.
You perform following steps in subsequent subqueries
get all tokens and outer join to the order table to get a complete sequence
find the LAG of the qty column
set grp_id to 1 if there is a break. i.e. if the previos qty is null and the current not null or vice versa - else to 0
cummulate the grp_id using analytic form of SUM
here the result with the sample data
TOKEN QTY PRICE QTY_LAG GRP_ID CUM_GRP_ID
---------- ---------- ---------- ---------- ---------- ----------
25 100 100 1 1
26 100 100 100 0 1
27 100 100 100 0 1
28 100 100 100 0 1
29 100 1 2
30 100 100 1 3
The last step is a simple GROUP BY on qty, price with added cummulated group id cum_grp_id which splits the non adjacent groups.
Query
with orders as (
select 25 token, 100 qty, 100 price from dual union all
select 26 token, 100 qty, 100 price from dual union all
select 27 token, 100 qty, 100 price from dual union all
select 28 token, 100 qty, 100 price from dual union all
select 30 token, 100 qty, 100 price from dual union all
select 31 token, 100 qty, 100 price from dual),
tokens as (
select min(token) min_token, max(token) max_token from orders),
all_tokens as (
select min_token - 1 + level token from tokens
connect by level <= max_token - min_token),
grp as (
select
t.token,
o.qty, o.price,
lag(o.qty) over (order by t.token) as qty_lag
from all_tokens t
left outer join orders o
on t.token = o.token),
grp2 as (
select
token, qty, price, qty_lag,
case when qty is null and qty_lag is null or qty is not null and qty_lag is not null then 0 else 1 end as grp_id
from grp),
grp3 as (
select
token, qty, price, qty_lag, grp_id,
sum(grp_id) over (order by token) cum_grp_id
from grp2)
select min(token), max(token), qty, price
from grp3
group by cum_grp_id, qty, price
order by 1
result
MIN(TOKEN) MAX(TOKEN) QTY PRICE
---------- ---------- ---------- ----------
25 28 100 100
29 29
30 30 100 100
Adapt if required by partitioning the analytic function by orderNo and/or tradeDate.

To select data from multiple records in SQL Server having a common ID

I need to select/concat data from 2 tables in SQL Server I'm using Left Join, but the data is returned as multiple records.
Below are the sample tables
Table1
Id Name Age
1 Sk 20
2 Rb 30
Table2
ID Bike Price Table1Id
1 RX 200 1
2 CD 250 1
3 FZ 300 1
4 R1 400 2
The desired output is
ID Name Age Bike1 Price1 Bike2 Price2 Bike3 Price3
1 Sk 20 RX 200 CD 250 FZ 300
2 Rb 30 R1 400 NULL NULL NULL NULL
A sample format of the query I'm using
SELECT A.ID, A.Name, B.Bike, B.Price FROM Table1 A LEFT JOIN Table2 B ON
A.id = B.Table1Id order by A.id
The output I'm getting from the above query is
ID Name Age Bike Price
1 Sk 20 RX 200
1 Sk 20 CD 250
1 Sk 20 FZ 300
2 Rb 30 R1 400
I need the data as one record for a particular ID and not multiple records (As seen in the desired output). Tired using offset, but offset will return only limited result not the entire records.
Any suggestions on how this can be achieved?
If you know the maximum number of bikes per person, you can use conditional aggregation:
SELECT ID, Name,
MAX(CASE WHEN seqnm = 1 THEN Bike END) as bike_1,
MAX(CASE WHEN seqnm = 1 THEN Price END) as price_1,
MAX(CASE WHEN seqnm = 2 THEN Bike END) as bike_2,
MAX(CASE WHEN seqnm = 2 THEN Price END) as price_2,
MAX(CASE WHEN seqnm = 3 THEN Bike END) as bike_3,
MAX(CASE WHEN seqnm = 3 THEN Price END) as price_3
FROM (SELECT A.ID, A.Name, B.Bike, B.Price,
ROW_NUMBER() OVER (PARTITION BY A.id ORDER BY B.Price) as seqnum
FROM Table1 A LEFT JOIN
Table2 B
ON A.id = B.Table1Id
) ab
GROUP BY ID, Name,
ORDER BY id

SQL: PIVOT with join

Newbie question:
I have table below
Period Customer Balance
40 1 10
40 2 15
39 1 9
38 1 10
38 2 20
I would like to order it so that I have one column for each period,
Customer BalancePeriod38 BalancePeriod39 BalancePeriod40
1 10 9 10
2 15 . 20
Is this possible?
You can pivot the data using aggregation with case:
select customer,
sum(case when period = 38 then balance else 0 end) as balance_period_38,
sum(case when period = 39 then balance else 0 end) as balance_period_39,
sum(case when period = 40 then balance else 0 end) as balance_period_40
from your_table
group by customer;

Query Two Different Table that have same field item

I have two table
Table_A
ID PostId Item Stock Price
1 1 A 30 10
2 1 B 40 20
3 2 A 50 5
4 3 A 50 25
Table_B
ID PostId Item_ID Sold Price
1 1 1 2 20
2 1 2 2 40
3 1 1 1 10
4 2 3 3 15
5 2 3 1 5
I want to queries from above two table that have same 'PostID' and COUNT and SUM some field group by 'PostID', expected output would be like this
Output
ID PostId Total Item Total Stock Total Buyer(s) Total Sold Total Price
1 1 2 70 3 5 70
I've try to JOIN it, but result still miss calculate
SELECT Table_A.PostId AS PostId, COUNT(Table_A.Item) AS Total_Item, SUM(Table_A.stock) AS Total_Stock, COUNT(Table_B.Item_ID) AS total_buyer, SUM( Table_B.Sold ) AS TotalSold, SUM( Table_B.Price ) AS Total_Price
FROM Table_A
LEFT JOIN Table_B
ON Table_A.PostId = Table_B.PostId
WHERE Table_A.PostId = '1'
GROUP BY Table_A.PostId
LIMIT 0 , 30
Any suggestion for this query problem?? Thank you
SELECT Table_B.PostId AS PostId,
MIN(Table_A.Total_Item) AS Total_Item,
MIN(Table_A.Total_Stock) AS Total_Stock,
COUNT(Table_B.Item_ID) AS total_buyer,
SUM( Table_B.Sold ) AS TotalSold,
SUM( Table_B.Price ) AS Total_Price
FROM Table_B
LEFT JOIN
(
SELECT PostId,
COUNT(Item) AS Total_Item,
SUM(stock) AS Total_Stock
FROM
Table_A
GROUP BY PostId
) Table_A
ON Table_B.PostId=Table_A.PostId
WHERE Table_B.PostId = '1'
GROUP BY Table_B.PostId
LIMIT 0 , 30