SQL Case Switch Default to Show Placeholder Image on Nulls - sql

I am trying to get a list of products and images from two tables. When I join them and use a case switch on the HasImage column from the products table, I get this error:
Error:
Subquery returned more than 1 value.
This is not permitted when the subquery follows =, !=, <, <= , >, >=
or when the subquery is used as an expression.
When a product does not have an image, I want to replace it with a default image.
Here is the select statement:
SELECT
P.[ProductId]
,P.[ProductName]
--If HasImage is false show the default.jpg
,Case P.[HasImage]
WHEN 'True' THEN (Select I.[FileName] as ProductImage FROM [ProductImages] I
INNER JOIN [Product] P on P.ProductId = I.ProductId
WHERE I.Sequence=0)
WHEN 'False' THEN 'default.jpg'
END
FROM [Product] P
LEFT JOIN [ProductImages] I
on P.ProductId = I.ProductId
The problem is in the Case When 'True'. That's what throws the error.
Product Table
ProductId ProductName HasImage
1 Coffee Mug True
2 Pen False
3 Pencil False
Product Images Table
ProductId Sequence FileName
1 0 Mug_Image1.jpg
1 1 Mug_2.jpg
1 2 Mug_Img3.jpg
There are multiple images for ProductId=1, but I use Sequence = 0 to return only one.
The returned data I want should look like this:
ProductId ProductName ProductImage
1 Coffee Mug Mug_Image1.jpg
2 Pen default.jpg
3 Pencil default.jpg
I have tried various combinations of coalesce(NULLIF, Left Joins, and different statments, but I haven't gotten all three products to display as desired.

In addition to my comment below OP, this is a query I believe should have been written in the first place.
SELECT
P.[ProductId]
,P.[ProductName]
--If HasImage is false show the default.jpg
,Case P.[HasImage]
WHEN 'True'
THEN I.[FileName]
WHEN 'False'
THEN 'default.jpg'
END
FROM [Product] P
LEFT JOIN [ProductImages] I
ON P.ProductId = I.ProductId
-- Filter Sequence 0 only
-- All products will be retrieved
-- whether they have associated Image with Sequence = 0
AND I.Sequence = 0
Filtering right side of left join allows you to retain properties of left join AND join only rows of interest. If HasImage serves just to mark existance of Images and not as business rule (show/don't show image of this particular product), you might remove case in favor of simple isnull(I.FileName, 'default.jpg').
Alternatively (Sql Server 2005 and newer) you might use CROSS APPLY to retrieve images:
SELECT
P.[ProductId]
,P.[ProductName]
,I.[FileName]
FROM [Product] P
OUTER APPLY
(
SELECT CASE P.[HasImage]
WHEN 'True'
THEN ProductImages.[FileName]
WHEN 'False'
THEN 'default.jpg'
END FileName
FROM [ProductImages]
WHERE P.ProductId = ProductImages.ProductId
AND I.Sequence = 0
) I

Why do you want to go for a INNER JOIN?
If you are sure that when HasImage is TRUE your table will have an image then better go for a straight join.
Try this query -
SELECT P.[ProductId],
P.[ProductName],
Case P.[HasImage]
WHEN 'True' THEN
(Select
TOP 1 -- For Oracle
I.[FileName] as ProductImage FROM [ProductImages] I
WHERE P.ProductId = I.ProductId
ORDER BY I.Sequence
LIMIT 1 -- For MySQL
)
WHEN 'False' THEN 'default.jpg'
END
FROM [Product] P

Related

SQL Query to apply join on basis of case Condition

I have a requirement where I need to fetch the Dimension Key of Region table on basis of the following preference.
Fetch dimension key on basis of Zipcode of Physical address(PA)
If the first condition is not satisfied that fetch dimension key on basis of the Zip Code of the Mailing address
If the second condition is also not satisfied than fetch the dimension key on basis of the Parish Code of Physical address
Else fetch dimension key on basis of parish Code of Mailing address.
I am trying to use the below query but is giving multiple records since all left joins are getting evaluated. I want that it should not go on the second condition if the first condition is satisfied.
select REGION_DIM_SK, CASE_NUM
from (
select distinct COALESCE(RDIM.REGION_DIM_SK, RDIM1.REGION_DIM_SK, RDIM2.REGION_DIM_SK, RDIM3.REGION_DIM_SK) AS REGION_DIM_SK
, DC.CASE_NUM, ADDR_TYPE_CD
FROM rpt_dm_ee_intg.CASE_PERSON_ADDRESS dc
left join rpt_dm_ee_prsnt.REGION_DIM RDIM on dc.ZIP_CODE = RDIM.ZIP_CODE and RDIM.REGION_EFF_END_DT IS NULL and dc.addr_type_cd='PA' AND dc.EFF_END_DT IS NULL
left join rpt_dm_ee_prsnt.REGION_DIM RDIM1 ON dc.ZIP_CODE = RDIM1.ZIP_CODE AND RDIM1.REGION_EFF_END_DT IS NULL AND dc.addr_type_cd='MA' AND DC.EFF_END_DT IS NULL
left join (
select PARISH_CD, min(REGION_DIM_SK) as REGION_DIM_SK
from rpt_dm_ee_prsnt.REGION_DIM
where REGION_EFF_END_DT is null
group by PARISH_CD
) RDIM2 ON dc.addr_type_cd='PA' and dc.PARISH_CD = RDIM2.PARISH_CD AND DC.EFF_END_DT IS NULL
left join (
select PARISH_CD, min(REGION_DIM_SK) as REGION_DIM_SK
from rpt_dm_ee_prsnt.REGION_DIM
where REGION_EFF_END_DT is null
group by PARISH_CD
) RDIM3 ON dc.addr_type_cd='MA' and dc.PARISH_CD = RDIM3.PARISH_CD AND DC.EFF_END_DT IS NULL
) A
where REGION_DIM_SK is not null
) RD on RD.case_num = rpt_dm_ee_intg.CASE_PERSON_ELIGIBILITY.CASE_NUM
Use multiple left joins. Your query is rather hard to follow -- it has other tables and references not described in the problem.
But the idea is:
select t.*,
coalesce(rpa.dim_key, rm.dim_key, rpap.dim_key, rmp.dim_key) as dim_key
from t left join
dim_region rpa
on t.physical_address_zipcode = rpa.zipcode left join
dim_region rm
on t.mailing_address_zipcode = rm.zipcode and
rpa.zipcode is null left join
dim_region rpap
on t.physical_addresss_parishcode = rpap.parishcode and
rm.zipcode is null left join
dim_region rmp
on t.physical_addresss_parishcode = rmp.parishcode and
rpap.zipcode is null
The trick is to put the conditions in CASE WHEN:
SELECT *
FROM table1 a
JOIN table2 b
ON CASE
WHEN a.code is not null and a.code = b.code THEN 1
WHEN a.type = b.type THEN 1
ELSE 0
END = 1
For your example you can reduce the code to just two joins, it can't be done in one as you are joining two different tables.
SELECT CASE WHEN RDIM.addres IS NULL THEN RDIM2.addres ELSE RDIM.addres
FROM rpt_dm_ee_intg.CASE_PERSON_ADDRESS dc
LEFT JOIN rpt_dm_ee_prsnt.REGION_DIM RDIM ON CASE
WHEN (dc.ZIP_CODE = RDIM.ZIP_CODE
AND RDIM.REGION_EFF_END_DT IS NULL
AND dc.addr_type_cd='PA'
AND dc.EFF_END_DT IS NULL) THEN 1
WHEN (dc.ZIP_CODE = RDIM1.ZIP_CODE
AND RDIM1.REGION_EFF_END_DT IS NULL
AND dc.addr_type_cd='MA'
AND DC.EFF_END_DT IS NULL) THEN 1
ELSE 0
END = 1
LEFT JOIN
(SELECT PARISH_CD,
min(REGION_DIM_SK) AS REGION_DIM_SK
FROM rpt_dm_ee_prsnt.REGION_DIM
WHERE REGION_EFF_END_DT IS NULL
GROUP BY PARISH_CD) RDIM2 ON CASE
WHEN (dc.addr_type_cd='PA'
AND dc.PARISH_CD = RDIM2.PARISH_CD
AND DC.EFF_END_DT IS NULL
AND RDIM.ZIP_CODE IS NULL) THEN 1
WHEN (dc.addr_type_cd='MA'
AND dc.PARISH_CD = RDIM3.PARISH_CD
AND DC.EFF_END_DT IS NULL
AND RDIM.ZIP_CODE IS NULL) THEN 1
ELSE 0
END = 1
edit
If you don't want to have nulls from RDIM2 table if RDIM1 zip code is present the logic could be easily extended to support that. You just need to add AND RDIM.ZIP_CODE IS NULL to CASE WHEN conditions.

SQL: If there are two rows that contain same record, want it to display one

based on my question above, below is the SQL
SELECT ets_tools.tools_id, ets_borrower.fullname, ets_team.team_name, ets_borrow.time_from,
ets_borrow.time_to, ets_borrow.borrow_id FROM ets_tools
INNER JOIN ets_tools_borrow ON ets_tools.tools_id = ets_tools_borrow.tools_id
INNER JOIN ets_borrow ON ets_borrow.borrow_id = ets_tools_borrow.borrow_id
INNER JOIN ets_borrower ON ets_borrower.badgeid = ets_borrow.badgeid
INNER JOIN ets_team ON ets_team.team_id = ets_borrower.team_id
WHERE ets_tools.borrow_id IS NOT NULL AND ets_borrow.status_id = 1 AND ets_borrow.time_to IS NULL
and the result display like this:
From the image above, we can see that the borrow_id with value 1 display two rows. Now, how to display only one borrow_id for value 1 since its duplicate the same things.
Anyone can help?
Assuming you want to retain the record having the smallest tools_id, you could aggregate by the other columns and take the MIN of tools_id:
SELECT
MIN(ets_tools.tools_id) AS tools_id,
ets_borrower.fullname,
ets_team.team_name,
ets_borrow.time_from,
ets_borrow.time_to,
ets_borrow.borrow_id
FROM ets_tools
INNER JOIN ets_tools_borrow ON ets_tools.tools_id = ets_tools_borrow.tools_id
INNER JOIN ets_borrow ON ets_borrow.borrow_id = ets_tools_borrow.borrow_id
INNER JOIN ets_borrower ON ets_borrower.badgeid = ets_borrow.badgeid
INNER JOIN ets_team ON ets_team.team_id = ets_borrower.team_id
WHERE
ets_tools.borrow_id IS NOT NULL AND
ets_borrow.status_id = 1 AND
ets_borrow.time_to IS NULL
GROUP BY
ets_borrower.fullname,
ets_team.team_name,
ets_borrow.time_from,
ets_borrow.time_to,
ets_borrow.borrow_id;
Try this:
Change the SELECT to SELECT TOP 1 WITH TIES
And at the end add ORDER BY ROW_NUMBER() OVER(PARTITION BY ets_borrow.borrow_id ORDER BY ets_tools.tools_id)

SQL Using Multiply 3 Times with different ID's

I have tables called Products and ProductsDetails. I want to get something like the price of an order. So let's say I want 5 pairs of "Headphonesv1" ( Comparing with not ID but name, since name could change ), 2 packs of "GumOrbit" and 7 packs of "crisps". Pair of headphonesv1 costs 10$, gum 1$ and crisps 2$. So the answer that I should get is bill ID, Bill date, and TotalCost which is = 66. My question is how do I make multiple calculations? The code that I've been trying with one at least but I get syntax error:
SELECT Products.billID, Products.Date, (ProductsDetails.Price * 5 WHERE ProductsDetails.name LIKE 'Headphonesv1')
FROM Products INNER JOIN ProductsDetails ON Products.billdID = ProductsDetails.billID
Also have tried inserting SELECT inside () but then the values that I get are wrong and creating one more inner join inside doesn't seem promising
I think if you just want to see that total cost for multiple items you can use a aggregate and case expression to get the SUM.
SELECT Products.billID,
Products.Date,
SUM(CASE WHEN ProductsDetails.name LIKE 'Headphonesv1' THEN ProductsDetails.Price * 5
WHEN ProductsDetails.name LIKE 'GumOrbit' THEN ProductsDetails.Price * 2
WHEN ProductsDetails.name LIKE 'crisps' THEN ProductsDetails.Price * 7
END) TotalCost
FROM Products
INNER JOIN ProductsDetails ON Products.billdID = ProductsDetails.billID
GROUP BY Products.billID,
Products.Date
this seems very hard coded to serve much use though
Your previous code has a few synax erros. you are closing your parenthesis in the wrong location, and the FROM ... clause should be before the WHERE ... clause. Change your code to this:
SELECT Products.billID, Products.Date, (ProductsDetails.Price * 5)
FROM Products INNER JOIN ProductsDetails ON Products.billdID = ProductsDetails.billID
WHERE ProductsDetails.name LIKE 'Headphonesv1'
and let me know if it works now.
Edited:
The OP requested how to obtain that information for more than one Product. In that case you could switch the statement to:
SELECT SUM(Cost)
FROM (
SELECT Products.billID, Products.Date,
(ProductsDetails.Price *
(CASE WHEN ProductsDetails.name='Headphonesv1' THEN 5 ELSE
CASE WHEN ProductsDetails.name='GumOrbit' THEN 7 ELSE 0 END
END)
) AS Cost
FROM Products INNER JOIN ProductsDetails ON Products.billdID = ProductsDetails.billID
WHERE (ProductsDetails.name = 'Headphonesv1') OR (ProductsDetails.name = 'GumOrbit')
)
And you can continue to add more products.
You probably have another two tables: cart(cartID int/*,more info*/) and cartItems(cartID int /*FK*/, item varchar(50),qty int) where you add something. Finally,
select sum(pd.price * ci.qty) tot
FROM Products p
INNER JOIN ProductsDetails pd ON p.billdID = pd.billID
inner join cartItems ci on ci.item = pd.name
inner join cart c on c.cartID = ci.cartID
where c.cartID = 123456

Joining a derived table postgres

I have 4 tables:
Competencies: a list of obviously competencies, static and a library
Competency Levels: refers to an associated group of competencies and has a number of competencies I am testing for
call_competency: a list of all 'calls' that have recorded the specified competency
competency_review_status: proving whether each call_competency was reviewed
Now I am trying to write this query to count a total and spit out the competency, id and whether a user has reached the limit. Everything works except for when I add the user. I am not sure what I am doing wrong, once I limit call competency by user in the where clause, I get a small subset that ONLY exists in call_competency returned when I want the entire list of competencies.
The competencies not reached should be false, ones recorded appropriate number true. A FULL list from the competency table.
I added the derived table, not sure if this is right, obviously it doesn't run properly, not sure what I'm doing wrong and I'm wasting time. Any help much appreciated.
SELECT comp.id, comp.shortname, comp.description,
CASE WHEN sum(CASE WHEN crs.grade = 'Pass' THEN 1 ELSE CASE WHEN crs.grade = 'Fail' THEN -1 ELSE 0 END END) >= comp_l.competency_break_level
THEN TRUE ELSE FALSE END
FROM competencies comp
INNER JOIN competency_levels comp_l ON comp_l.competency_group = comp.competency_group
LEFT OUTER JOIN (
SELECT competency_id
FROM call_competency
WHERE call_competency.user_id IN (
SELECT users.id FROM users WHERE email= _studentemail
)
) call_c ON call_c.competency_id = comp.id
LEFT OUTER JOIN competency_review_status crs ON crs.id = call_competency.review_status_id
GROUP BY comp.id, comp.shortname, comp.description, comp_l.competency_break_level
ORDER BY comp.id;
(Shooting from the hip, no installation to test)
It looks like the below should do the trick. You apparently had some of the joins mixed up, with a column from a relation that was not referenced. Also, the CASE statement in the main query could be much cleaner.
SELECT comp.id, comp.shortname, comp.description,
(sum(CASE WHEN crs.grade = 'Pass' THEN 1 WHEN crs.grade = 'Fail' THEN -1 ELSE 0 END) >= comp_l.competency_break_level) AS reached_limit
FROM competencies comp
JOIN competency_levels comp_l USING (competency_group)
LEFT JOIN (
SELECT competency_id, review_status_id
FROM call_competency
JOIN users ON id = user_id
WHERE email = _studentemail
) call_c ON call_c.competency_id = comp.id
LEFT JOIN competency_review_status crs ON crs.id = call_c.review_status_id
GROUP BY comp.id, comp.shortname, comp.description
ORDER BY comp.id;

WHERE in Sql, combining two fast conditions multiplies costs many times

I have a fairly complex sql that returns 2158 rows' id from a table with ~14M rows. I'm using CTEs for simplification.
The WHERE consists of two conditions. If i comment out one of them, the other runs in ~2 second. If i leave them both (separated by OR) the query runs ~100 seconds. The first condition alone needs 1-2 seconds and returns 19 rows, the second condition alone needs 0 seconds and returns 2139 rows.
What can be the reason?
This is the complete SQL:
WITH fpcRepairs AS
(
SELECT FPC_Row = ROW_NUMBER()OVER(PARTITION BY t.SSN_Number ORDER BY t.Received_Date, t.Claim_Creation_Date, t.Repair_Completion_Date, t.Claim_Submitted_Date)
, idData, Repair_Completion_Date, Received_Date, Work_Order, SSN_number, fiMaxActionCode, idModel,ModelName
, SP=(SELECT TOP 1 Reused_Indicator FROM tabDataDetail td INNER JOIN tabSparePart sp ON td.fiSparePart=sp.idSparePart
WHERE td.fiData=t.idData
AND (td.Material_Quantity <> 0)
AND (sp.SparePartName = '1254-3751'))
FROM tabData AS t INNER JOIN
modModel AS m ON t.fiModel = m.idModel
WHERE (m.ModelName = 'LT26i')
AND EXISTS(
SELECT NULL
FROM tabDataDetail AS td
INNER JOIN tabSparePart AS sp ON td.fiSparePart = sp.idSparePart
WHERE (td.fiData = t.idData)
AND (td.Material_Quantity <> 0)
AND (sp.SparePartName = '1254-3751')
)
), needToChange AS
(
SELECT idData FROM tabData AS t INNER JOIN
modModel AS m ON t.fiModel = m.idModel
WHERE (m.ModelName = 'LT26i')
AND EXISTS(
SELECT NULL
FROM tabDataDetail AS td
INNER JOIN tabSparePart AS sp ON td.fiSparePart = sp.idSparePart
WHERE (td.fiData = t.idData)
AND (td.Material_Quantity <> 0)
AND (sp.SparePartName IN ('1257-2741','1257-2742','1248-2338','1254-7035','1248-2345','1254-7042'))
)
)
SELECT t.idData
FROM tabData AS t INNER JOIN modModel AS m ON t.fiModel = m.idModel
INNER JOIN needToChange ON t.idData = needToChange.idData -- needs to change FpcAssy
LEFT OUTER JOIN fpcRepairs rep ON t.idData = rep.idData
WHERE
rep.idData IS NOT NULL -- FpcAssy replaced, check if reused was claimed correctly
AND rep.FPC_Row > 1 -- other FpcAssy repair before
AND (
SELECT SP FROM fpcRepairs lastRep
WHERE lastRep.SSN_Number = rep.SSN_Number
AND lastRep.FPC_Row = rep.FPC_Row - 1
) = rep.SP -- same SP, must be rejected(reused+reused or new+new)
OR
rep.idData IS NOT NULL -- FpcAssy replaced, check if reused was claimed correctly
AND rep.FPC_Row = 1 -- no other FpcAssy repair before
AND rep.SP = 0 -- not reused, must be rejected
order by t.idData
Here's the execution plan:
Download: http://www.filedropper.com/exeplanfpc
Try to use UNION ALL of 2 queries separately instead of OR condition.
I've tried it many times and it really helped. I've read about this issue in Art Of SQL .
Read it, you can find many useful information about performance issues.
UPDATE:
Check related questions
UNION ALL vs OR condition in sql server query
http://www.sql-server-performance.com/2011/union-or-sql-server-queries/
Can UNION ALL be faster than JOINs or do my JOINs just suck?
Check Wes's answer
The usage of the OR is probably causing the query optimizer to no longer use an index in the second query.