Trying to Update Case When Then, based on matching IDs in two tables - sql

I am trying to updated IDs in a table named Price, based on matches of IDs in a table named CW. Here is the SQL that I'm testing.
UPDATE Price
SET ISN = (case when CW.id_cd = 'ISN' THEN CW.id_number else ISN end),
SED = (case when CW.id_cd = 'SED' THEN CW.id_number else SED end),
CSP = (case when CW.id_cd = 'CSP' THEN CW.id_number else CSP end)
FROM CW
WHERE Price.ID_GLOBAL = CW.asset_id
Here is a screen shot of the two schemas. So, now the values in the yellow cells are missing IDs, but I'm trying to get them filled in with the values I'm showing here.
The weird thing is that most of the IDs in the Price table are updated fine, based on the same IDs in the CW table, but not all IDs are being updated. After doing the above update, when I run the SQL below, I get a bunch of records returned, but I would expect no records at all because all CSP (IDs) should have been updated in the Price table.
Select *
From Price
INNER JOIN CW
ON Price.id_global = CW.asset_id
Where Price.CSP IS NUll
AND CW.id_cd = 'CSP'

I think you have multiple rows in CW for each ID_GLOBAL. The problem is that only one matching row gets used. The solution is either a series of LEFT JOINs or pre-aggregation:
UPDATE Price
SET ISN = COALESCE(cw.isn, price.isn),
SED = COALESCE(cw.sed, price.sed),
CSP = COALESCE(cw.csp, price.csp)
FROM (SELECT asset_id,
MAX(case when CW.id_cd = 'ISN' THEN CW.id_number END) as ISN,
MAX(case when CW.id_cd = 'SED' THEN CW.id_number END) as SED,
MAX(case when CW.id_cd = 'CSP' THEN CW.id_number END) as CSP
FROM CW
GROUP BY asset_id
) cw
WHERE Price.ID_GLOBAL = CW.asset_id;
Note: This assumes that the new values are not ever NULL. If that is a possibility, the assignment logic needs to take that into account. I am guessing that complication is unnecessary.
EDIT:
Do this in three steps. For 'ISN':
UPDATE Price
SET ISN = cw.isn
FROM CW
WHERE Price.ID_GLOBAL = CW.asset_id AND
CW.id_cd = 'ISN';
I am guessing the COALESCE() is not necessary.

Related

Executing a CASE statement with multiple conditions

Query takes two tables does a left join, then filters out based off preferences. It runs well, however, when I add the 'case' statement I run into issues. The idea was to grab the first digits I need to classify them into a broader category. I need to be able to change the names of a field into something friendly. Any suggestions?
Values for the tr.TENANT_NAICS field follow a format similar to this:
543330- Other Computer Related | 2782 Science
548972- Socials
Would like to change to just:
Other Computer Related
Other Computer Related
Query
select
tr.OCCUPANCY_DATE, tr.END_DATE, tr.TENANT_NAICS,
pr.PROPERTY_STATUS, pr.NRA_BUILDING, pr.MARKET, pr.SUBMARKET, pr.FULL_ADDRESS_ONE_LINE,
pr.LEGAL_OWNER, pr.TRUE_OWNER,
lr.LESSOR_SUBLESSOR,
/* trying to fix here */
(case
when left(tr.TENANT_NAICS, 2)::numeric = 11 THEN 'Other Computer Related'
when left(tr.TENANT_NAICS, 2)::numeric = 21 THEN 'Mining'
when left(tr.TENANT_NAICS, 4)::numeric = 4821 THEN 'Construction'
else tr.TENANT_NAICS
end) as "Tenant Industry"
from space.tran tr
left join property pr on tr.ID = pr.ID
left join lease lr on tr.OID = lr.OID
where
tr.STATUS = 'Existing' and tr.MARKET = 'Seattle'
;
Edit: the exact error I receive is 'Numeric value 'Bu' is not recognized'.
Apparently TENANT_NAICS is a varchar column and there must be at least one value that starts with BU instead of digits. Try this SQL:
select
tr.OCCUPANCY_DATE, tr.END_DATE, tr.TENANT_NAICS,
pr.PROPERTY_STATUS, pr.NRA_BUILDING, pr.MARKET, pr.SUBMARKET, pr.FULL_ADDRESS_ONE_LINE,
pr.LEGAL_OWNER, pr.TRUE_OWNER,
lr.LESSOR_SUBLESSOR,
/* trying to fix here */
(case
when left(tr.TENANT_NAICS, 2) = '11' THEN 'Other Computer Related'
when left(tr.TENANT_NAICS, 2) = '21' THEN 'Mining'
when left(tr.TENANT_NAICS, 4) = '4821' THEN 'Construction'
else tr.TENANT_NAICS
end) as "Tenant Industry"
from space.tran tr
left join property pr on tr.ID = pr.ID
left join lease lr on tr.OID = lr.OID
where
tr.STATUS = 'Existing' and tr.MARKET = 'Seattle'
;

Conditional update either column1 or column2 or column3

I want to write a SQL update statement like
UPDATE p
SET CASE WHEN inv.name='your' THEN p.your_qty=inv.qty
CASE WHEN inv.name='other' THEN p.other_qty=inv.qty
CASE WHEN inv.name='my' THEN p.my_qty=inv.qty
FROM products p
JOIN Qty_products_inv inv
ON p.itemNo= inv.itemNo
You need to update all three columns. SET can not update different columns on different rows.
So, pivot your source before joining, then set each column back to its existing value if there is no new value to pick up...
UPDATE
p
SET
your_qty = COALESCE(inv.your_qty , p.your_qty ),
other_qty = COALESCE(inv.other_qty, p.other_qty),
my_qty = COALESCE(inv.my_qty , p.my_qty )
FROM
products p
JOIN
(
SELECT
itemNo,
MAX(CASE WHEN name='your' THEN qty END) AS your_qty,
MAX(CASE WHEN name='other' THEN qty END) AS other_qty,
MAX(CASE WHEN name='my' THEN qty END) AS my_qty
FROM
qty_products_inv
GROUP BY
itemNo
)
inv
ON p.itemNo = inv.itemNo
Apologies for typos I'm on my phone.
EDIT:
Don't do the following, from my original answer, as #JonArmstrong's comment shows it does not work (unless each row is only ever picking up one single change).
UPDATE
p
SET
your_qty = CASE WHEN inv.name='your' THEN inv.qty ELSE p.your_qty END,
other_qty = CASE WHEN inv.name='other' THEN inv.qty ELSE p.other_qty END,
my_qty = CASE WHEN inv.name='my' THEN inv.qty ELSE p.my_qty END
FROM
products p
JOIN
qty_products_inv inv
ON p.itemNo = inv.itemNo

SQL: Split rows with same ID into columns + left join

I have a cs cart database and I am trying to select all the attributes for all the products, the problem is that for each separate attribute for a product, my query creates a new row, I want to to have a single row for each products that has all the attributes into columns.
This is my query right now:
SELECT a.product_id, b.variant, c.description, d.product_code
FROM cscart_product_features_values a
LEFT JOIN cscart_product_feature_variant_descriptions b ON a.variant_id = b.variant_id
LEFT JOIN cscart_product_features_descriptions c ON a.feature_id = c.feature_id
LEFT JOIN cscart_products d ON a.product_id = d.product_id
After I run the query, I get the following result:
product_id;"variant";"description";"product_code"
38;"1st";"Grade Level";"750"
38;"Math";"Subject Area";"750"
38;"Evan-Moor";"Publisher";"750"
etc next product
What I want is this:
product_id;"product_code";"Grade Level";"Subject Area";"Publisher"
38;"750";"1st";"Math";"Evan-Moor"
etc next product
We only have 3 type of attributes: Grade Level, Subject Area and Publisher.
Any ideas how to improve my query and achieve this? I would be happy even with concatenating all 3 attributes in one column, delimited by ",".
This is a generic SQL solution using GROUP BY and MAX(case expression) to achieve the transformation of 3 rows into a single row with the 3 columns.
SELECT
v.product_id
, p.product_code
, MAX(CASE WHEN fd.description = 'Grade Level' THEN vd.variant END) AS GradeLevel
, MAX(CASE WHEN fd.description = 'Subject Area' THEN vd.variant END) AS SubjectArea
, MAX(CASE WHEN fd.description = 'Publisher' THEN vd.variant END) AS Publisher
FROM cscart_products p
LEFT JOIN cscart_product_features_values v ON p.product_id = v.product_id
LEFT JOIN cscart_product_feature_variant_descriptions vd ON v.variant_id = vd.variant_id
LEFT JOIN cscart_product_features_descriptions fd ON v.feature_id = fd.feature_id
GROUP BY
v.product_id
, p.product_code
This approach should work on just about any SQL database.
Note also that I have changed the order of tables because I presume there has to be a row in cscart_products, but there might not be related rows in the other tables.
I have also changed the aliases, personally I do not care for aliaes based on the order of use in a query (e.g. I just changed the order so I had to change all references). I have use 'p' = product, 'v' = variant, 'vd' = variant description & 'fd' = feature description' - with such a convention for aliases I can re-arrange the query without changing every reference.

SQL Case Update to two different column based on select

I'm trying to perform what I believe a very simple case-update SQL to two different column based on a select:
PROD_TB:
Product_Code Reg_Price Sale_Price
A 1000 2000
PRICE_TB:
Product_Code Type Price
A REG 3000
A SALE 4000
Desired update result:
PROD_TB:
Product_Code Reg_Price Sale_Price
A 3000 4000
What I attempted:
UPDATE PROD_TB
SET Reg_Price = CASE
WHEN PRICE_TB.Type = 'REG'
THEN PRICE_TB.Price
ELSE Reg_Price
END,
Sale_Price = CASE
WHEN PRICE_TB.Type = 'SALE'
THEN PRICE_TB.Price
ELSE Sale_Price
END
FROM
PROD_TB
JOIN
PRICE_TB ON PROD_TB.PRODUCT_CODE = PRICE_TB.PRODUCT_CODE
Running the above SQL only updates regular price, not the sale price. Does SQL not support these types of update query? Or did I make an elementary mistake?
Something like this? Basically, just join the set from the PRICE_TB on the condition of which column you want to get from it.
But this is assuming you will only ever have one PRICE_TB.TYPE per updated PROD_TB column name, and that each PROD_TB column always contains a value in PRICE_TB, otherwise it'll be NULL and that row won't be returned. So make sure you know the variations of data that can exist here.
UPDATE PROD
SET Reg_Price = REG.Price, Sale_Price = SALE.Price
FROM PROD_TB PROD
JOIN PRICE_TB REG ON REG.Product_Code = PROD.Product_Code AND REG.Type = 'REG'
JOIN PRICE_TB SALE ON SALE.Product_Code = PROD.Product_Code AND SALE.Type = 'SALE'
You need to get the data into one row first, here's example that fetches always the biggest price, in case there's more than one in PRICE_TB, otherwise it should work the same way as #Kahn's sql.
UPDATE
PROD
SET
PROD.Reg_Price = PRICE.Reg_Price,
PROD.Sales_Price = PRICE.Sales_Price
FROM PROD_TB PROD, cross apply (
select
max(CASE WHEN Type = 'REG' THEN Price ELSE 0 end) as Reg_Price,
max(CASE WHEN Type = 'SALE' THEN Price ELSE 0 end) as Sale_Price
from
PRICE_TB PRICE
where
PRICE.Product_Code = PROD.Product_Code
) PRICE
If your table PRICE_TB doesn't always contain both values, you could use this to make sure the table is updated anyway:
UPDATE t1
SET
Reg_Price = coalesce(t2.Price, t1.Reg_Price),
Sale_Price = coalesce(t3.Price, t1.Sale_Price)
FROM PROD_TB t1
LEFT JOIN
PRICE_TB t2
ON
t1.ProductCode = t2.ProductCode AND
t2.[Type] = 'REG'
LEFT JOIN
PRICE_TB t3
ON
t1.ProductCode = t3.ProductCode AND
t3.[Type] = 'SALE'
WHERE
t2.[Type] = 'REG' OR
t3.[Type] = 'SALE'

splitting select query into two or more parts

I am using TOAD for Oracle. While i implement some sql queries i encountered these problem:
I am using a few tables that each of them has approx. 10M rows for a select query. 2 tables have over 70M rows data.
Let's say i have;
a TRANSACTION table (prim. key: SQ_TRANSACTION_ID)
a TRANSACTION_DETAIL table (foreign keys: RF_TRANSACTION_ID,
RF_PRODUCT_ID)
a PRODUCT table (prim. key: SQ_PRODUCT_ID)
My select query is like;
SELECT TR.TRANSACTION_ID,
SUM(CASE WHEN PR.CD_PRODCUT_TYPE = 'A'
THEN TRD.CS_INVOICE_PRICE ELSE 0 END) A_PRODUCT_TOTAL,
SUM(CASE WHEN PR.CD_PRODCUT_TYPE <> 'A'
THEN TRD.CS_INVOICE_PRICE ELSE 0 END) B_PRODUCT_TOTAL
FROM TRANSACTION TR,
TRANSACTION_DETAIL TRD,
PRODUCT PR
WHERE TR.SQ_TRANSACTION_ID = TRD.RF_TRANSACTION_ID
AND TRD.RF_PRODUCT_ID = PR.SQ_PRODUCT_ID
GROUP BY TR.TRANSACTION_ID,
CASE WHEN PR.CD_PRODCUT_TYPE = 'A' THEN TRD.CS_INVOICE_PRICE ELSE 0 END,
CASE WHEN PR.CD_PRODCUT_TYPE <> 'A' THEN TRD.CS_INVOICE_PRICE ELSE 0 END
Is there a way to split this query into two or more parts with referenced each other by using their foreign/primary keys? I mean like splitting into two parts that first part fetches A_PRODUCT_TOTAL and second part fetches B_PRODUCT_TOTAL. Each part's transaction id should match at the result data.
A direct translation of your query would be:
SELECT TR.TRANSACTION_ID, SUM(TRD.CS_INVOICE_PRICE) A_PRODUCT_TOTAL
FROM TRANSACTION TR join
TRANSACTION_DETAIL TRD
on TR.SQ_TRANSACTION_ID = TRD.RF_TRANSACTION_ID join
PRODUCT PR
on TRD.RF_PRODUCT_ID = PR.SQ_PRODUCT_ID
WHERE PR.CD_PRODCUT_TYPE = 'A'
GROUP BY TR.TRANSACTION_ID,
CASE WHEN PR.CD_PRODCUT_TYPE = 'A' THEN TRD.CS_INVOICE_PRICE ELSE 0 END
However, I suspect that you don't want the second clause in the group by, because each transaction would be split into reows where the invoice price is the same:
SELECT TR.TRANSACTION_ID, SUM(TRD.CS_INVOICE_PRICE) A_PRODUCT_TOTAL
FROM TRANSACTION TR join
TRANSACTION_DETAIL TRD
on TR.SQ_TRANSACTION_ID = TRD.RF_TRANSACTION_ID join
PRODUCT PR
on TRD.RF_PRODUCT_ID = PR.SQ_PRODUCT_ID
WHERE PR.CD_PRODCUT_TYPE = 'A'
GROUP BY TR.TRANSACTION_ID;
The query for 'B' would be similar.