SQL query for combining data from the same table - sql

I have this table :
create table mytable
(ID varchar(10), VNDCOD INT, MANUF varchar(10), PRICE INT, COST INT);
insert into mytable values
('4', 1000, 'AG', 5455, 9384),
('4', 1000, 'A1', 16, 31),
('4', 2000, 'AG', 5253, 8339)
I want to be able to select something like this:
ID MANUF PRICE COST PRICE COST
4 AG 5455 9384 5253 8339
4 A1 16 31
If there is two MANUF for an ID, we should combine them on one line where the PRICE and COST of the one with VNDCOD 1000 is on the left and VNDCOD 2000 on the right like in my expected result. I don't know if it is possible to do this in one query. Any help would be greatly appreciated.

Either use a self-join or a MAX(CASE) (fiddle):
select t1.ID, t1.MANUF, t1.PRICE, t1.COST, t2.PRICE, t2.COST
from mytable as t1 left join mytable as t2
on t1.ID = t2.ID
and t1.MANUF = t2.MANUF
and t2.VNDCOD = 2000
where t1.VNDCOD = 1000
;
select ID, MANUF,
max(case when VNDCOD = 1000 then PRICE end),
max(case when VNDCOD = 1000 then COST end),
max(case when VNDCOD = 2000 then PRICE end),
max(case when VNDCOD = 2000 then COST end)
from mytable
group by ID, MANUF

You can readily do this with conditional aggregation:
select id,
max(case when vndcode = 1000 then price end) as price_1000,
max(case when vndcode = 1000 then cost end) as cost_1000,
max(case when vndcode = 2000 then price end) as price_2000,
max(case when vndcode = 2000 then cost end) as cost_2000
from mytable t
group by id;

Related

How can I retrieve this schema by SQL query?

This is my table and its data :
-------------------------------------
rid mid qty price tname
-------------------------------------
10 A 1000 400 Buy
11 A 2000 420 Buy
12 B 1700 600 Buy
13 A 600 450 Sell
And I want to have such output :
----------------------------------------------------------------
mid SUM_Buy tname SUM_Sell tname SUM_Buy_minus_SUM_Sell
----------------------------------------------------------------
A 3000 Buy 600 Sell 2400
B 1700 Buy NULL NULL NULL
Thank you for updating your question with consumable data. An even better approach is posting ddl and sample data so people can just grab it and roll. I did that for you.
declare #something table
(
rid int
, mid char(1)
, qty int
, price int
, tname varchar(10)
)
insert #something values
(10, 'A', 1000, 400, 'Buy')
, (11, 'A', 2000, 420, 'Buy')
, (12, 'B', 1700, 600, 'Buy')
, (13, 'A', 600 , 450, 'Sell')
In that type of format it is super easy for others to help.
You can solve this using conditional aggregation. I used tname1 and tname2 because outside of SSMS you don't want multiple columns to have the same name. But those are probably just noise and not really needed as they provide no benefit to the results.
select s.mid
, Sum_Buy = sum(case when tname = 'Buy' then qty end)
, tname1 = 'Buy'
, Sum_Sell = sum(case when tname = 'Sell' then qty end)
, tname2 = 'Sell'
, SUM_Buy_minus_SUM_Sell = sum(case when tname = 'Buy' then qty end) - sum(case when tname = 'Sell' then qty end)
from #something s
group by s.mid
order by s.mid
You might try this ( use grouping by mid column with aggregations by contribution of case..when statements ) :
with t(rid,mid,qty,price,tname) as
(
select 10,'A',1000,400,'Buy' union all
select 11,'A',2000,420,'Buy' union all
select 12,'B',1700,600,'Buy' union all
select 13,'A',600,450,'Sell'
)
select t.mid, sum(case when tname='Buy' then qty else 0 end) as SUM_Buy,
min(case when tname='Buy' then tname else null end) as tname,
sum(case when tname='Sell' then qty else null end) as SUM_Sell,
max(case when tname='Sell' then tname else null end) as tname,
(sum(case when tname='Buy' then qty else 0 end) -
sum(case when tname='Sell' then qty else null end)) as
SUM_Buy_minus_SUM_Sell
from t
group by t.mid

Tuning oracle subquery in select statement

I have a master table and a reference table as below.
WITH MAS as (
SELECT 10 as CUSTOMER_ID, 1 PROCESS_ID, 44 PROCESS_TYPE, 200 as AMOUNT FROM DUAL UNION ALL
SELECT 10 as CUSTOMER_ID, 1 PROCESS_ID, 44 PROCESS_TYPE, 250 as AMOUNT FROM DUAL UNION ALL
SELECT 10 as CUSTOMER_ID, 2 PROCESS_ID, 45 PROCESS_TYPE, 300 as AMOUNT FROM DUAL UNION ALL
SELECT 10 as CUSTOMER_ID, 2 PROCESS_ID, 45 PROCESS_TYPE, 350 as AMOUNT FROM DUAL
), REFTAB as (
SELECT 44 PROCESS_TYPE, 'A' GROUP_ID FROM DUAL UNION ALL
SELECT 44 PROCESS_TYPE, 'B' GROUP_ID FROM DUAL UNION ALL
SELECT 45 PROCESS_TYPE, 'C' GROUP_ID FROM DUAL UNION ALL
SELECT 45 PROCESS_TYPE, 'D' GROUP_ID FROM DUAL
) SELECT ...
My first select statement which works correctly is this one:
SELECT CUSTOMER_ID,
SUM(AMOUNT) as AMOUNT1,
SUM(CASE WHEN PROCESS_TYPE IN (SELECT PROCESS_TYPE FROM REFTAB WHERE GROUP_ID = 'A')
THEN AMOUNT ELSE NULL END) as AMOUNT2,
COUNT(CASE WHEN PROCESS_TYPE IN (SELECT PROCESS_TYPE FROM REFTAB WHERE GROUP_ID = 'D')
THEN 1 ELSE NULL END) as COUNT1
FROM MAS
GROUP BY CUSTOMER_ID
However, to address a performance issue, I changed it to this select statement:
SELECT CUSTOMER_ID,
SUM(AMOUNT) as AMOUNT1,
SUM(CASE WHEN GROUP_ID = 'A' THEN AMOUNT ELSE NULL END) as AMOUNT2,
COUNT(CASE WHEN GROUP_ID = 'D' THEN 1 ELSE NULL END) as COUNT1
FROM MAS A
LEFT JOIN REFTAB B ON A.PROCESS_TYPE = B.PROCESS_TYPE
GROUP BY CUSTOMER_ID
For the AMOUNT2 and COUNT1 columns, the values stay the same. But for AMOUNT1, the value is multiplied because of the join with the reference table.
I know I can add 1 more left join with an additional join condition on GROUP_ID. But that won't be any different from using a subquery.
Any idea how to make the query work with just 1 left join while not multiplying the AMOUNT1 value?
I know I can add 1 more left join with adding aditional GROUP_ID clause but it wont be different from subquery.
You'd be surprised. Having 2 left joins instead of subqueries in the SELECT gives the optimizer more ways of optimizing the query. I would still try it:
select m.customer_id,
sum(m.amount) as amount1,
sum(case when grpA.group_id is not null then m.amount end) as amount2,
count(grpD.group_id) as count1
from mas m
left join reftab grpA
on grpA.process_type = m.process_type
and grpA.group_id = 'A'
left join reftab grpD
on grpD.process_type = m.process_type
and grpD.group_id = 'D'
group by m.customer_id
You can also try this query, which uses the SUM() analytic function to calculate the amount1 value before the join to avoid the duplicate value problem:
select m.customer_id,
m.customer_sum as amount1,
sum(case when r.group_id = 'A' then m.amount end) as amount2,
count(case when r.group_id = 'D' then 'X' end) as count1
from (select customer_id,
process_type,
amount,
sum(amount) over (partition by customer_id) as customer_sum
from mas) m
left join reftab r
on r.process_type = m.process_type
group by m.customer_id,
m.customer_sum
You can test both options, and see which one performs better.
Starting off with your original query, simply replacing your IN queries with EXISTS statements should provide a significant boost. Also, be wary of summing NULLs, perhaps your ELSE statements should be 0?
SELECT CUSTOMER_ID,
SUM(AMOUNT) as AMOUNT1,
SUM(CASE WHEN EXISTS(SELECT 1 FROM REFTAB WHERE REFTAB.GROUP_ID = 'A' AND REFTAB.PROCESS_TYPE = MAS.PROCESS_TYPE)
THEN AMOUNT ELSE NULL END) as AMOUNT2,
COUNT(CASE WHEN EXISTS(SELECT 1 FROM REFTAB WHERE REFTAB.GROUP_ID = 'D' AND REFTAB.PROCESS_TYPE = MAS.PROCESS_TYPE)
THEN 1 ELSE NULL END) as COUNT1
FROM MAS
GROUP BY CUSTOMER_ID
The normal way is to aggregate the values before the group by. You can also use conditional aggregation, if the rest of the query is correct:
SELECT CUSTOMER_ID,
SUM(CASE WHEN seqnum = 1 THEN AMOUNT END) as AMOUNT1,
SUM(CASE WHEN GROUP_ID = 'A' THEN AMOUNT ELSE NULL END) as AMOUNT2,
COUNT(CASE WHEN GROUP_ID = 'D' THEN 1 ELSE NULL END) as COUNT1
FROM MAS A LEFT JOIN
(SELECT B.*, ROW_NUMBER() OVER (PARTITION BY PROCESS_TYPE ORDER BY PROCESS_TYPE) as seqnum
FROM REFTAB B
) B
ON A.PROCESS_TYPE = B.PROCESS_TYPE
GROUP BY CUSTOMER_ID;
This ignores the duplicates created by the joins.

(Oracle) How to get SUM values based on values in other columns?

Suppose there is data like below:
ID Name Cost
ID1 A 10
ID1 A 60
ID1 B 20
ID1 C 20
ID2 B 10
ID2 B 50
ID2 C 50
ID3 B 5
Here in the table above, ID and NAME are not unique.
And I want to get SUM values based on NAME, so the expected result is like below:
ID A_Costs B_Costs C_Costs AB_Costs
ID1 70 20 20 90
ID2 60 50 60
ID3 5 5
A_Cost, B_Costs, and C_Costs are costs when the name is A, B or C.
But what do I do if I want to get costs when the name is A and B?
So what I was trying to do was this:
Select t2.ID,
SUM(DECODE (t2.name, 'A', t2.Cost, null)),
SUM(DECODE (t2.name, 'B', t2.Cost, null))
--(select sum(t1.cost) from table t1. where t1.name in ('A','B') and t1.id = t2.id)
from table t2
group by t2.id
But this does not work.
How do I get the costs when the name is A and B like the line I commented out? Is there any effective way to get the value like that in one query?
Thank you in advance.
If you want to use decode(), you can do:
sum(decode(t2.name, 'A', t2.cost, 'B' t2.cost))
Or you can use a case statement:
sum(case when t2.name in ('A', 'B') then t2.cost end)
Full query:
select id,
sum(case when name = 'A' then cost end) as a_costs,
sum(case when name = 'B' then cost end) as b_costs,
sum(case when name = 'C' then cost end) as c_costs,
sum(case when name IN ('A', 'B') then cost end) as ab_costs
from SomeTable
group by id
order by id
SQL Fiddle Demo
You will also have to aggregate after using sum in the inner query.
select
id, max(a_cost) as A_Costs, max(b_cost) as B_Costs,
max(c_cost) as C_Costs, nvl(max(a_cost),0) + nvl(max(b_cost),0) as AB_Costs
from (
select ID,
sum(case when name = 'A' then cost end) as a_cost,
sum(case when name = 'B' then cost end) as b_cost,
sum(case when name = 'C' then cost end) as c_cost
from table
group by id
) t
group by id

SQL Inner join not working to get change from previous date

I have a table with the following data :
TradeDate Stock BuySell DayClose
--------------------------------------
10-Dec-12 ABC 1 11
10-Dec-12 ABC 2 12
11-Dec-12 ABC 1 11.5
11-Dec-12 ABC 2 12.5
11-Dec-12 DEF 1 15
11-Dec-12 DEF 2 16
and I want to query on it for a particular date 11-Dec-2012 to get the following output :
Stock Buy Sell Mid Change
--------------------------------------
ABC 11.5 12.5 12.0 0.5
DEF 15 16 15.5
Since DEF does not have data for the previous date, change should be blank for it.
I have created the following query :
Select Stock,
AVG(CASE BuySell WHEN 1 THEN DayClose END) AS 'Buy',
AVG(CASE BuySell WHEN 2 THEN DayClose END) As 'Sell',
Sum(DayClose/2) as 'Mid',
Sum(Change/2) AS Change
FROM (
select t1.Stock, t1.BuySell, t1.DayClose, Sum(t1.DayClose - t2.DayClose) as Change
FROM #myTable as t1 inner join #myTable as t2 on
t1.Stock = t2.Stock
where
t1.TradeDate = '2012-12-11' AND
t2.TradeDate = (SELECT TOP 1 TradeDate FROM #myTable WHERE TradeDate < '2012-12-11' ORDER BY TradeDate DESC)
GROUP BY
t1.Stock, t1.buysell, t1.dayclose ) AS P1 GROUP BY stock
I created a temp table #mytable for this purpose :
drop table #mytable
CREATE TABLE #myTable
(
TradeDate datetime,
stock varchar(20),
buysell int,
dayclose decimal(10,2)
)
insert into #mytable values ('10-dec-2012', 'abc' , 1, 11)
insert into #mytable values ('10-dec-2012', 'abc' , 2, 12)
insert into #mytable values ('11-dec-2012', 'abc' , 1, 11.5)
insert into #mytable values ('11-dec-2012', 'abc' , 2, 12.5)
insert into #mytable values ('11-dec-2012', 'def' , 1, 15)
insert into #mytable values ('11-dec-2012', 'def' , 2, 16)
But I am not able to get the required output, rather getting
Stock Buy Sell Mid Change
--------------------------------------------------------------
abc 11.500000 12.500000 12.00000 1.00
Can someone tell me where am I going wrong. I seem to be lost in here.
Thanks,
Monika
Please try:
;WITH T1 as(
SELECT a.TradeDate
,a.stock
,SUM(CASE WHEN a.BuySell = 1 THEN a.DayClose ELSE 0 END) Buy
,SUM(CASE WHEN a.BuySell = 2 THEN a.DayClose ELSE 0 END) Sell
,SUM(a.DayClose) / 2 AS Mid
FROM #mytable a
GROUP BY a.TradeDate, a.stock
)SELECT t.*,
t.Mid - PR.Mid AS Change
FROM T1 t
LEFT JOIN
T1 PR ON
PR.TradeDate = DATEADD(DAY, -1, t.TradeDate)
AND PR.stock = t.stock
Try this:
SELECT a.TradeDate
,a.stock
,SUM(CASE WHEN a.BuySell = 1 THEN a.DayClose ELSE 0 END) Buy
,SUM(CASE WHEN a.BuySell = 2 THEN a.DayClose ELSE 0 END) Sell
,SUM(a.DayClose) / 2 AS Mid
INTO #temp
FROM #mytable a
GROUP BY a.TradeDate, a.stock
SELECT t.*,
t.Mid - previousRecord.Mid AS Change
FROM #temp t
LEFT JOIN
#temp previousRecord ON
previousRecord.TradeDate = DATEADD(DAY, -1, t.TradeDate)
AND previousRecord.stock = t.stock
DROP TABLE #temp
All you have to do now is to select the data for a date.
Select Stock,
AVG(CASE BuySell WHEN 1 THEN DayClose END) AS 'Buy',
AVG(CASE BuySell WHEN 2 THEN DayClose END) As 'Sell',
Sum(DayClose/2) as 'Mid',
Sum(Change/2) AS Change
FROM (
select t1.Stock, t1.BuySell, t1.DayClose, Sum( t1.DayClose - t2.DayClose ) as Change
FROM #myTable as t1 left join #myTable as t2 on t2.TradeDate = (SELECT TOP 1 TradeDate FROM #myTable WHERE TradeDate < t1.TradeDate ORDER BY TradeDate DESC)
and t1.Stock = t2.Stock and t1.buysell=t2.buysell
where
t1.TradeDate = '11-12-2012'

Query from different tables

I have 2 tables
TABLE A
INV AMT DISC
1001 1500 150
1002 3000 300
TABLE B
INV DESC AMT
1001 CHARGES 100
1001 FREIGHT 30
1001 INSURANCE 20
1002 CHARGES 215
1002 FREIGHT 32
1002 INSURANCE 25
To combine both tables, I used the following query (given by Mikael Eriksson):-
select
A.inv,
A.amount,
A.disc,
B.charges,
B.freight,
B.insurance
from #TableA as A
inner join (
SELECT t.inv,
MAX(CASE WHEN t.description = 'CHARGES' THEN t.amount ELSE NULL END) AS charges,
MAX(CASE WHEN t.description = 'FREIGHT' THEN t.amount ELSE NULL END) AS freight,
MAX(CASE WHEN t.description = 'INSURANCE' THEN t.amount ELSE NULL END) AS insurance
FROM #TableB as t
GROUP BY t.inv) as B
on A.inv = B.inv
Then, I'll have the following output:-
INV AMT DISC CHARGES FREIGHT INSURANCE
1001 1500 150 100 30 20
1002 3000 300 215 32 25
Question, how can I add a query into the earlier statement if I want say, where charges is equal to 100. The final result will look like this:-
INV AMT DISC CHARGES FREIGHT INSURANCE
1001 1500 150 100 30 20
select
A.inv,
A.amt,
A.disc,
B.charges,
B.freight,
B.insurance
from TableA as A
inner join (
SELECT t.inv,
MAX(CASE WHEN t.descr = 'CHARGES' THEN t.amt ELSE NULL END) AS charges,
MAX(CASE WHEN t.descr = 'FREIGHT' THEN t.amt ELSE NULL END) AS freight,
MAX(CASE WHEN t.descr = 'INSURANCE' THEN t.amt ELSE NULL END) AS insurance
FROM TableB as t
GROUP BY t.inv) as B
on A.inv = B.inv
where charges = 100 --<< just add this
Depending on how many rows contain tableb.amt = 100 and descr = 'charges', you may be better off writing the query this way.
select a.inv, a.amt, a.disc,
b.amt AS charges,
c.amt AS freight,
d.amt AS insurance
from tablea a
inner join tableb b on b.inv = a.inv and b.descr = 'CHARGES' and b.amt = 100
left join tableb c on c.inv = a.inv and c.descr = 'FREIGHT'
left join tableb d on d.inv = a.inv and d.descr = 'INSURANCE'
Test Data
create table tablea (inv int, amt int, disc int);
insert tablea select 1001,1500,150;
insert tablea select 1002,3000,300;
create table tableb (inv int, descr varchar(10), amt int);
insert tableb select 1001, 'CHARGES', 100;
insert tableb select 1001, 'FREIGHT', 30;
insert tableb select 1001, 'INSURANCE', 20;
insert tableb select 1002, 'CHARGES', 215;
insert tableb select 1002, 'FREIGHT', 32;
insert tableb select 1002, 'INSURANCE', 25;
Output
inv amt disc charges freight insurance
----------- ----------- ----------- ----------- ----------- -----------
1001 1500 150 100 30 20
Warning: Null value is eliminated by an aggregate or other SET operation.
(1 row(s) affected)
Note: Using the proposed 2nd query, the warning is not present in the output