I have a pretty straightforward sql query that I'm running on DB2 I-series 4 which is just performing some simple joins. The issue is that my materials table has it so that multiple material/color combos can be used on each body of work. Right now I'm getting rows of every combination but I want to consolidate so that if the value of test.materials.sequence is 2 then it creates two additional columns (if there's no sequence 2 for the combination then it would just be 0 and 0 in those columns.
THe query:
Select bod.code, mat.material, mat.mat_color,
from test.skus sk
inner join test.Bodies bod on sk.body_id = bod.id
inner join test.categories prc on prc.id = sk.category_id
inner join test.skus_to_materials stm on sk.id = stm.sku_id
inner join test.materials mat on stm.mat_id = mat.id
order by prc.desc;
Tables:
skus
id | code | body_id | category_id
-------------------------------------------
1 12345 9912 3
2. 12346 9913 3
Bodies
id | code
--------------------------
9912 1234-5
9913 1234-6
categories
id | category
------------------
3 Test
skus_to_materials
id | sku_id | mat_id | sequence
--------------------------------------
1 1 221 1
2 1 222 2
3 2 223 1
materials
id | material | mat_color
-------------------------------
221 Fabric black
222 Fabric white
223 Leather brown
This is my current result:
code | material | mat_color
-------------------------
1234-5 | Fabric | black
1234-5 | Fabric | white
This is the result I would want:
code | material1 | mat_color1 | material2 | mat_color2
----------------------------------------------------------
1234-5 Fabric black Fabric white
1234-6 Leather brown 0 0
See how 1234-6 only has the one material combination, so material2 and mat_color2 would be zeroes.
Is there a way through grouping and row-operations to achieve this?
UPDATE:
In answer to Charles' answer, I've realized some issues in one case with more data. After grouping by material, color, and desc I realized that I was getting the missing records but now ending up with this issue:
code | material1 | color1 | material2 | color2
------------------------------------------------------------
1234-5 Fabric White 0 0
1234-5 0 0 Leather white
1234-5 Leather Brown 0 0
1234-5 Leather Tan 0 0
1234-6 Fabric Black 0 0
1234-6 0 0 Leather Black
1234-7 Fabric White 0 0
I think Charles first answer is closer. Here is what I got:
SELECT SK.ID SKU
,BOD.CODE
,MAT.MATERIAL MATERIAL_1
,MAT.MAT_COLOR MATERIAL_2
,COALESCE(MAT2.MATERIAL, '0') MATERIAL_2
,COALESCE(MAT2.MAT_COLOR, '0') COLOR_2
FROM SKUS SK
INNER JOIN BODIES BOD ON SK.BODY_ID = BOD.ID
INNER JOIN CATEGORIES PRC ON PRC.ID = SK.CATEGORY_ID
INNER JOIN SKUS_TO_MATERIALS STM ON SK.ID = STM.SKU_ID AND STM.SEQUENCE = 1
INNER JOIN MATERIALS MAT ON STM.MAT_ID = MAT.ID
LEFT JOIN SKUS_TO_MATERIALS STM2 ON SK.ID = STM2.SKU_ID AND STM2.SEQUENCE = 2
LEFT JOIN MATERIALS MAT2 ON STM2.MAT_ID = MAT2.ID;
Resulting in:
SKU
CODE
MATERIAL_1
MATERIAL_2
MATERIAL_2
COLOR_2
6
BodD
Fabric
Black
0
0
4
BodB
Fabric
Black
Leather
Black
3
BodA
Fabric
Black
0
0
2
BodA
Fabric
Black
Leather
Black
1
BodA
Fabric
Black
0
0
1
BodA
Fabric
White
0
0
5
BodC
Leather
Brown
0
0
1
BodA
Leather
Brown
0
0
1
BodA
Leather
Black
0
0
View on DB Fiddle
"rows to columns" is known as "pivoting" the data.
Db2 for IBM i doesn't have a built method to pivot. However, assuming a distinct set of values, you can hardcode it.
Something like so should do what you want:
Select bod.code, max(mat1.material), max(mat1.mat_color),
max(mat2.material), max(mat2.mat_color)
from test.skus sk
inner join test.Bodies bod on sk.body_id = bod.id
inner join test.categories prc on prc.id = sk.category_id
inner join test.skus_to_materials stm on sk.id = stm.sku_id
inner join test.materials mat1 on stm.mat_id = mat1.id
and mat1.sequence = 1
left outer join test.materials mat2 on stm.mat_id = mat2.id
and mat2.sequence = 2
group by bod.code
order by prc.desc;
EDIT
Ok if sequence is actually in the skus_to_materials then you'll need a different set of joins. Actually in that case, I'd probably just go with a CTE based statement.
with allrows as (
Select bod.code, prc.desc, stm.sequence
, mat.material, mat.mat_color,
from test.skus sk
inner join test.Bodies bod on sk.body_id = bod.id
inner join test.categories prc on prc.id = sk.category_id
inner join test.skus_to_materials stm on sk.id = stm.sku_id
inner join test.materials mat on stm.mat_id = mat.id
}
select
code
, max(case when sequence = 1 then material else '0' end) as material1
, max(case when sequence = 1 then color else '0' end) as color1
, max(case when sequence = 2 then material else '0' end) as material2
, max(case when sequence = 2 then color else '0' end) as color2
from allRows
group by code
order by desc;
Related
I am currently working on part of a project which is about products and variants.
Product Table
productID productName
-------------------------
1 Perfume X
2 Perfume Y
Variants Table
variantID variantName productID
1 color 1
2 volume 1
VariantValue Table
variantValueID variantValue variantID
1 Red 1
2 Blue 1
3 Green 1
4 100ML 2
5 50ML 2
ProductVariant Table
productVariantID productID sku price
1 1 111 50.00
2 1 222 30.00
4 1 333 15.00
5 2 444 10.95
ProductDetail Table
ProductDetailID productVariantID variantValueID
1 1 1
2 1 4
3 2 1
4 2 5
5 4 2
6 4 5
relationship
and when I call this query ..
SELECT p.productName + ' ' + STRING_AGG(vv.variantValue,' ') product,pv.price
FROM
product p
JOIN ProductVariant pv ON pv.productID = p.productID
JOIN ProductDetail pd ON pv.productVariantID = pd.productVariantID
join VariantValue vv ON pd.variantValueID = vv.variantValueID
GROUP BY pd.productVariantID, p.productName,pv.price
I get the following result ..
product price
Perfume X Red 100ML 50.00
Perfume X Red 50ML 30.00
Perfume X Blue 50ML 15.00
So far so good, it shows all products with the prices but when I insert a new product without variants (simple product) it does not appeared. In my case Perfume Y does not have any variants.
I tried using right and full join but in vain. How I show all products including those product that have no variants.
I think you just want a left join:
SELECT p.productName + COALESCE(' ' + STRING_AGG(vv.variantValue,' '), '') as product,
pv.price
FROM product p LEFT JOIN
ProductVariant pv
ON pv.productID = p.productID LEFT JOIN
ProductDetail pd
ON pv.productVariantID = pd.productVariantID LEFT JOIN
VariantValue vv
ON pd.variantValueID = vv.variantValueID
GROUP BY p.productName, pv.price
Tables
TRANSACTIONS
SUPP_ID | PAYMENT
----------+----------
1001 200
1002 100
1005 250
MASTER_SUPPLIERS
SUPP_ID | AREA
----------+----------
1001 ABC
1002 XYZ
1003 TYU
1004 MNO
1005 PQR
Intention:
Find those count of suppliers area wise where no payment (NO_TRANS_CNT) has been received
SELECT AREA, COUNT(*) AS NO_TRANS_CNT FROM MASTER_SUPPLIERS
WHERE AREA NOT IN (SELECT DISTINCT(AREA) FROM TRANSACTIONS)
GROUP BY AREA
AREA | NO_TRANS_CNT
----------+--------------
TYU 1
MNO 1
Want to ask: Now, I also want to add the column TOTAL_SUPPLIERS in this area
AREA | TOTAL SUPPLIERS | NO_TRANS_CNT
----------+--------------------+----------------
ABC 1 0
XYZ 1 0
TYU 1 1
MNO 1 1
PQR 1 0
I think it can be achieved using JOINs, but I am not able to get how ?
Try this:
SELECT
M.AREA
, COUNT(1) TOTAL_SUPPLIERS
, COUNT(CASE WHEN T.SUPP_ID IS NULL THEN 1 END) NO_TRANS_CNT
FROM MASTER_SUPPLIERS M
LEFT JOIN TRANSACTIONS T ON T.SUPP_ID = M.SUPP_ID
GROUP BY M.AREA;
Something like
select M.AREA, COUNT(*) as TOTAL_SUPPLIERS, COUNT(T.PAYMENT) as NO_TRANS_CNT
from MASTER_SUPPLIERS M left join TRANSACTIONS T
on M.SUPP_ID = T.SUPP_ID
group by M.AREA;
could work.
Note that the COUNT(T.PAYMENT) only counts those where the PAYMENT is not NULL.
Use a left join, but start with suppliers:
select ms.area, count(*) as num_suppliers, count(t.supp_id) as num_transactions
from master_suppliers ms left join
transactions t
on t.supp_id = m.supp_id
group by ms.area;
The left join keeps everything in the first table -- which is what you want, along with matching rows from the second.
When you count the number of transactions, the argument to count() should either be a column used in the on clause or the primary key.
I have the following tables, and values:
t_cars
---------------------------------------------------------
nCars_ID sName sModel sIdentifier
---------------------------------------------------------
1 BMW 3 series D-78-JHG
2 Volvo C30 B-56-KHT
3 Fiat Doblo H-72-ABN
4 Volvo C40 J-78-YTR
t_feature
---------------------------
nFeature_ID sName
---------------------------
1 CMC
2 Doors
3 Color
4 Type
5 Weight
6 Engine
7 Power
t_cars_feature
-------------------------------------------------------------------
nCarsFeature_ID nCars_ID nFeature_ID sValue
------------------------------------------------------------------
1 2 1 2500
2 2 2 5
3 2 4 Diesel
4 2 3 Green
5 3 1 1900
6 3 2 3
7 3 4 Otto
8 3 5 2300 KG
9 1 1 1900
10 1 3 Blue
11 1 4 Diesel
12 1 5 2100 KG
I need to retrieve from DB the cars that has CMC feature, has Color feature, AND CMC = 1900 AND Color = 'Blue' ONLY
I have tried:
SELECT t_cars.sName, t_cars.sModel, t_cars.sIdentifier
FROM t_cars, t_feature, t_cars_feature
WHERE t_feature.nFeature_ID = t_cars_feature.nFeature_ID
AND t_cars.nCars_ID = t_cars_feature.nCars_ID
AND [/*condition that get me cars that has CMC feature, has Color feature, AND CMC = 1900 AND Color = 'Blue' ONLY*/]
I have tried the condition like that:
Trial 1:
AND t_feature.sName = 'CMC'
AND t_feature.sName = 'Color'
AND t_cars_feature.sValue = '1900'
AND t_cars_feature.sValue = 'Blue'
and get me nothing
I have also tried:
Trial 2:
AND t_feature.sName IN ('CMC','Color')
AND t_cars_feature.sValue IN ('1900','Blue')
and get me all records that has CMC 1900 OR color 'Blue' (probably I got here an cartesian product)
In real situation I could have several t_feature.sName values, and several t_cars_feature.sValue values, that is why trial 1 are not suitable for me ...
Can you help me ? Thank you.
Use this query:
SELECT *
FROM t_cars
WHERE EXISTS (SELECT * FROM t_cars_feature AS cf
JOIN t_feature AS f ON (f.nFeature_ID = cf.nFeature_ID)
WHERE t_cars.nCars_ID = cf.nCars_ID AND f.sName = 'CMC' AND cf.sValue = '1900')
AND
EXISTS (SELECT * FROM t_cars_feature AS cf
JOIN t_feature AS f ON (f.nFeature_ID = cf.nFeature_ID)
WHERE t_cars.nCars_ID = cf.nCars_ID AND f.sName = 'Color' AND cf.sValue = 'Blue');
See the full example here.
I would like to comment about some of the things you've done there:
It is generally considered a bad practice to notate the type as a prefix in the column name, or in the object's name.
Your usage of the IN clause is wrong. I suggest you to try and practice about this matter a bit. When you supply a list of values inside the IN clause, it means that any of those values should result in a true value for the predicate - not that both of the them should exists.
Please note that when you use the WHERE clause with multiple predicates separated by an "AND", you require all of them to exists in a SINGLE row. Check out the result of your JOIN and see if this is the case to understand the case better.
It is considered a better practice to use a JOIN (i.e. INNER JOIN) and not a cartesian product (,) - even though both queries will usually compile into the same execution plan. It is just more readable and understandable.
You could do it with EXISTS and NOT EXISTS:
--Alias = Table
-- C = t_cars
-- CF = t_cars_feature
-- F = t_feature
SELECT *
FROM t_cars C
WHERE EXISTS (SELECT *
FROM t_cars_feature CF INNER JOIN t_feature F ON CF.nFeature_ID = F.nFeature_ID
WHERE CF.nCars_ID = C.nCars_ID --matches t_cars ID
AND F.sName = 'CMC' --has CMC feature
AND F.sName = 'Color' --has color feature
AND CF.sValue = '1900' --CMC = 1900
AND CF.sValue = 'Blue' --Color = 'Blue'
)
AND NOT EXISTS
(SELECT *
FROM t_cars_feature CF INNER JOIN t_feature F ON CF.nFeature_ID = F.nFeature_ID
WHERE CF.nCars_ID = C.nCars_ID --matches t_cars ID
AND F.sName = 'CMC' --has CMC feature
AND F.sName = 'Color' --has color feature
AND CF.sValue = '1900' --CMC = 1900
AND CF.sValue <> 'Blue' --Color = 'Blue ONLY'
)
You can make some use of conditional aggregation to get the desired results
SELECT t_cars.sName, t_cars.sModel, t_cars.sIdentifier
FROM t_cars
JOIN t_cars_feature ON t_cars.nCars_ID = t_cars_feature.nCars_ID
JOIN t_feature ON t_feature.nFeature_ID = t_cars_feature.nFeature_ID
WHERE t_feature.sName IN ('CMC','Color')
GROUP BY t_cars.sName, t_cars.sModel, t_cars.sIdentifier
HAVING SUM(CASE WHEN t_feature.sName ='CMC' AND t_cars_feature.sValue ='1900' THEN 1 ELSE 0 END) > 0
AND SUM(CASE WHEN t_feature.sName ='Color' AND t_cars_feature.sValue ='Blue' THEN 1 ELSE 0 END) >0
Demo
I have 4 tables I want to join to display items sold and items returned. The issue I'm having is that the sold and returned items are in the same table and I'm having trouble returning a 0 for the 'Return' column when an item is sold without returns and a 0 for the 'Sold' column when an item is returned without a sale. Here's what I'm trying to do,
Locations
LocationID LocationName
1 NY
2 LA
3 Chicago
Items
ItemID ItemName
A Sandwich
B Salad
C Wrap
Deliveries
DeliveryID LocationID
1 1
2 2
3 3
4 1
DeliveryRecords
RecordID DeliveryID ItemID Quantity RecordType
1 1 A 3 SOLD
2 1 B 4 SOLD
3 1 C 5 SOLD
4 1 C 2 RETURN
5 2 A 3 SOLD
6 2 B 2 SOLD
7 3 B 2 SOLD
8 3 C 3 SOLD
9 4 A 1 RETURN
And I want my query to return,
Location DeliveryID Item Sold Returned
NY 1 Sandwich 3 0
NY 1 Salad 4 0
NY 1 Wrap 5 2
LA 2 Sandwich 3 0
LA 2 Salad 2 0
Chicago 3 Salad 2 0
Chicago 3 Wrap 3 0
NY 4 Wrap 0 1
I think you just want conditional aggregation (after a bunch of joins):
select l.locationname, dr.deliveryid, i.itemname,
sum(case when dr.recordtype = 'SOLD' then quantity else 0 end) as sold,
sum(case when dr.recordtype = 'RETURN' then quantity else 0 end) as returned
from deliveryrecords dr join
deliveries d
on dr.deliveryid = d.deliveryid join
location l
on d.locationid = l.locationid join
items i
on dr.itemid = i.itemid
group by l.locationname, dr.deliveridy, i.itemname;
Using JOIN and SUM:
SQL Fiddle
SELECT
l.LocationName,
dr.DeliveryID,
i.ItemName,
[Sold] = SUM(CASE WHEN dr.RecordType = 'SOLD' THEN dr.Quantity ELSE 0 END),
[Returned] = SUM(CASE WHEN dr.RecordType = 'RETURN' THEN dr.Quantity ELSE 0 END)
FROM DeliveryRecords dr
INNER JOIN Items i
ON i.ItemID = dr.ItemId
INNER JOIN Deliveries d
ON d.DeliveryID = dr.DeliveryID
INNER JOIN Locatins l
ON l.LocationID = d.LocationId
GROUP BY
dr.DeliveryID, l.LocationName, i.ItemName
I'm trying to aggregate depending on the conditional if player_id (Gary)
has greater, equal, or less score then player_id("other")
my schema has
players(player_id, name)
matches(match_id, home_team(player_id), away_team(player_id) )
outcome(outcome_id, match_id, home_score:integer, away_score:integer
Output from:
select m.match_id, p.name AS home_team, p1.name AS away_team, o.home_score, o.away_score
from players p
inner join matches m on (p.player_id = m.home_team)
inner join players p1 on (p1.player_id = m.away_team)
inner join outcomes o on (m.match_id = o.match_id);
match_id | player_id | player_id | home_score | away_score
----------+-----------+-----------+------------+------------
1 | 1 | 2 | 1 | 2
2 | 2 | 1 | 1 | 3
3 | 3 | 1 | 3 | 2
Wanted output:
player_id | Wins | Draws | Losses
-------------+------+-------+--------
1 | 1 | 0 | 2
2 ... | ... | .. | ...
My schema are open for alteration.
EDIT(sqlfiddle): http://www.sqlfiddle.com/#!2/7b6c8/1
I would use UNION ALL to get every outcome twice, once for home and once for away player. The second time home_score/away_score should be switched, to get correct sums for away player.
select
d.player_id,
d.name,
sum(d.home_score > d.away_score) as wins,
sum(d.home_score = d.away_score) as draws,
sum(d.home_score < d.away_score) as loses
from (
select p.player_id, p.name, o.home_score, o.away_score
from players p
join matches m on p.player_id = m.home_team
join outcomes o on o.match_id = m.match_id
union all
select p.player_id, p.name, o.away_score as home_score, o.home_score as away_score
from players p
join matches m on p.player_id = m.away_team
join outcomes o on o.match_id = m.match_id) d
group by d.player_id, d.name
Returns:
PLAYER_ID NAME WINS DRAWS LOSES
1 Gary 1 0 2
2 Tom 1 0 1
3 Brad 1 0 0
sqlFiddle demo: http://www.sqlfiddle.com/#!2/7b6c8/21
For a solution without a subquery and unions: http://www.sqlfiddle.com/#!2/7b6c8/31
SELECT
p.player_id,
COALESCE(SUM(o1.home_score > o1.away_score or o2.home_score < o2.away_score), 0) wins,
COALESCE(SUM(o1.home_score = o1.away_score or o2.home_score = o2.away_score), 0) draws,
COALESCE(SUM(o1.home_score < o1.away_score or o2.home_score > o2.away_score), 0) losses
FROM players p
LEFT JOIN matches m1 ON (p.player_id = m1.home_team)
LEFT JOIN players p1 ON (p1.player_id = m1.away_team)
LEFT JOIN outcomes o1 ON (m1.match_id = o1.match_id)
LEFT JOIN matches m2 ON (p.player_id = m2.away_team)
LEFT JOIN players p2 ON (p2.player_id = m2.home_team)
LEFT JOIN outcomes o2 ON (m2.match_id = o2.match_id)
GROUP BY p.player_id
Results:
PLAYER_ID WINS DRAWS LOSSES
1 1 0 2
2 1 0 1
3 1 0 0
4 0 0 0
5 0 0 0