Execution order of CASE statement SQL Server - sql-server

Does a CASE statement that has multiple parts that is part of an INSERTstatement execute in order and do the 'rules' for lack of a better word stay in place even after the next line? in the query below, does the PO_TYPE assignment overrule the next command - to look in a list of articles for example? So even if that article was in the list in the second part of the statement if it was type 05 or 07 it will still assign to Andrew?
Thanks.
/*INSERT values into the table using SELECT making sure to exclude vendor 20800 - (see last line of code)*/
INSERT INTO SCM_PO_EMPLOYEE_NAME (PO_NUMBER, PO_ITEM_NUMBER, MATERIAL, BUSINESS_UNIT_CODE,PO_TYPE,TEAM_MEMBER_NAME)
SELECT I.PO_NUMBER,
I.PO_ITEM_NUMBER,
I.MATERIAL,
B.BU_CODE,
H.PO_TYPE,
CASE WHEN H.PO_TYPE IN ('05','07') -- Promo PO type - should be on both po type and stock category
AND I.STOCK_CATEGORY LIKE ('A60383%') -- stock category is second part of the check
THEN 'AZ'
WHEN H.PO_TYPE = '02' -- ma PO type
THEN 'MB'
WHEN I.MATERIAL IN ( SELECT ARTICLE
FROM ADI_USER_MAINTAINED.dbo.SCM_EMPLOYEE_ARTICLE A ) -- Check the Employee to article table next
THEN A.TEAM_MEMBER_NAME -- If the PO number matches that conditions then assign the employee from the employee article table
WHEN M.BUSINESS_UNIT_CODE = B.BU_CODE -- if not use then go to the BU assignment (below)
THEN B.TEAM_MEMBER_NAME --- Use the team member name from the Employee_BU table
END AS [TEAM_MEMBER_NAME]
FROM PDX_SAP_USER.dbo.VW_PO_HEADER H
JOIN PDX_SAP_USER.dbo.VW_PO_ITEM I ON H.PO_NUMBER = I.PO_NUMBER
JOIN PDX_SAP_USER.dbo.VW_MM_MATERIAL M ON I.MATERIAL = M.MATERIAL
JOIN ADI_USER_MAINTAINED.dbo.SCM_EMPLOYEE_ARTICLE A ON I.MATERIAL = A.ARTICLE
JOIN ADI_USER_MAINTAINED.dbo.SCM_EMPLOYEE_BU B ON B.BU_CODE = M.BUSINESS_UNIT_CODE
WHERE H.VENDOR_NO <> '20800'; --Exclude '20800' as a vendor!!

A case expression is evaluated sequentially.
So, the second then is only evaluated when the first then does not return true. As an extreme example of this, consider:
select (case when 1=1 then 'true'
when 1/0 = 0 then 'error'
end)
This returns 'true' instead of error'ing out.

Related

The multi-part identifier "[column name]" could not be bound in UPDATE of TEMP Table

I am trying to create a stored procedure whereupon I input a (simple for now) query into a temp table, and then replace some of the data with data from a different table based on a key.
Here is the complete code:
CREATE PROCEDURE GetInquiryList
AS
BEGIN
SET NOCOUNT ON
IF OBJECT_ID('tempdb..#Inq ') IS NOT NULL
DROP TABLE #Inq
SELECT i.*,q.QuoteID INTO #Inq FROM Inquiries i left join Quotes q on i.InquiryId = q.InquiryId
WHERE i.YNDeleted = 0
--SELECT * FROM #Inq
UPDATE #Inq
SET j.InquiryCustomerName = c.CustomerName,
j.InquiryCustomerEmail = c.CustomerEmail,
j.InquiryCustomerPhone = c.CustomerPhone1,
j.InquiryBestTimetoCall = c.CustomerBestTimetoCall,
j.InquiryDay = c.customerDay,
j.InquiryNight = c.CustomerNight
SELECT c.CustomerName,
c.CustomerEmail,
c.CustomerPhone1,
c.CustomerBestTimetoCall,
c.customerDay,
c.CustomerNight
FROM Customers c
INNER JOIN #Inq j ON
j.InquiryCustomerID = c.CustomerID
SELECT * FROM #Inq
END
I get the following error:
Msg 4104, Level 16, State 1, Line 15 The multi-part identifier "j.InquiryCustomerName" could not be bound
I get this error for whatever column is placed first after the SET command.
Both query pieces of this work independently (the first select creating the temp table and the joined query at the bottom). The data returned is correct. I have tried using aliases (SELECT c.CustomerName AS Name, ...).
Originally, I used "#Inq i" in the second command, but changed to "j" out of an abundance of caution.
I have also run the command against the original table (substituting the Inquiry table for the temp table #Inq, and that fails as well).
Shortening it to this:
UPDATE #Inq
SET j.InquiryCustomerName = c.CustomerName,
j.InquiryCustomerEmail = c.CustomerEmail,
j.InquiryCustomerPhone = c.CustomerPhone1,
j.InquiryBestTimetoCall = c.CustomerBestTimetoCall,
j.InquiryDay = c.customerDay,
j.InquiryNight = c.CustomerNight
FROM Customers c
INNER JOIN #Inq j ON
j.InquiryCustomerID = c.CustomerID
I get a different error:
Msg 4104, Level 16, State 1, Line 15 The multi-part identifier "j.InquiryCustomerName" could not be bound
I'm sure it's probably something simple,(so simple that I can't find any references in any of my searches).
I'm sure it has something to do with the fact that you can't update the same instance of the table used in the join (I'm going to have to re-join again with a "k" alias). How do I do this?
data from the first query
data from the first query
data from the second select statement on the actual temp table
Here is what I updated the stored procedure to, which works exactly how I need it to:
SET NOCOUNT ON
IF OBJECT_ID('tempdb..#Inq ') IS NOT NULL
DROP TABLE #Inq
SELECT i.* INTO #Inq FROM (
select inquiries.InquiryId,
inquiries.InquiryDateReceived,
inquiries.InquiryCustomerID,
cust.CustomerName as InquiryCustomerName,
cust.CustomerEmail as InquiryCustomerEmail,
cust.CustomerPhone1 as InquiryCustomerPhone,
cust.CustomerBestTimeToCall as InquiryBestTimeToCall,
cust.CustomerDay as InquiryDay,
cust.CustomerNight as InquiryNight,
inquiries.InquiryServiceType,
inquiries.InquiryServiceID,
inquiries.InquiryTimeframe,
inquiries.InquiryProjectDescription,
inquiries.InquiryDateResponded,
inquiries.InquiryCustomerReply,
inquiries.YNMigrated,
inquiries.InquiryDateClosed,
inquiries.YNClosed,
inquiries.YNDeleted
from inquiries inner join dbo.Customers as cust
on inquiries.InquiryCustomerID = cust.CustomerID and inquiries.InquiryCustomerID > 0
UNION ALL
select inquiries.InquiryId,
inquiries.InquiryDateReceived,
inquiries.InquiryCustomerID,
InquiryCustomerName,
InquiryCustomerEmail,
InquiryCustomerPhone,
InquiryBestTimeToCall,
InquiryDay,
InquiryNight,
inquiries.InquiryServiceType,
inquiries.InquiryServiceID,
inquiries.InquiryTimeframe,
inquiries.InquiryProjectDescription,
inquiries.InquiryDateResponded,
inquiries.InquiryCustomerReply,
inquiries.YNMigrated,
inquiries.InquiryDateClosed,
inquiries.YNClosed,
inquiries.YNDeleted
from inquiries WHERE inquiries.InquiryCustomerID = 0
) i
select i.*, q.QuoteID
FROM #Inq i left join dbo.Quotes as q
on i.InquiryId = q.InquiryId
WHERE i.YNDeleted = 0
END
Just stop using this pattern without a really good reason. Here it only appears to create more work for the database engine with no obvious benefit. Your procedure - as posted - has trivially simple queries so why bother with the temp table and the update?
It is also time to start learning and using best practices. Terminate EVERY statement - eventually it will be required. Does order of the rows in your resultset matter? Usually it does and that is only guaranteed when that resultset is produced by a query that includes an ORDER BY clause.
As a developing/debugging short cut, you can harness the power of CTEs to help you build a working query. In this case, you can "stuff" your first query into a CTE and then simply join the CTE to Customers and "adjust" the columns you need in that resultset.
WITH inquiries as (
select inq.*, qt.QuoteID
FROM dbo.Inquiries as inq left join dbo.Quotes as qt
on inq.InquiryId = qt.InquiryId
WHERE inq.YNDeleted = 0
)
select inquiries.<col>,
...,
cust.CustomerName as "InquiryCustomerName",
...
from inquiries inner (? guessing) dbo.Customers as cust
on inquiries.InquiryCustomerID = cust.CustomerID
order by ...
;
Schema names added as best practice. Listing the columns you actually need in your resultset is another best practice. Note I did not do that for the query in the CTE but you should. You can choose to create aliases for your resultset columns as needed. I listed one example that corresponds to your UPDATE attempt.
It is odd and very suspicious that all of the columns you intended to UPDATE exist in the Inquiries table. Are you certain you need to do that at all? Do they actually differ from the related columns in the Customer table? Also odd that the value 0 exists in InquiryCustomerID - suggesting you might have not a FK to enforce the relationship. Perhaps that means you need to outer join rather than inner join (as I wrote). If an outer join is needed, then you will need to use CASE expressions to "choose" which value (the CTE value or the Customer value) to use for those columns.
After learning a lot more about how things get bound to models, and how to further use sql, here is what my stored procedure looks like:
ALTER PROCEDURE [dbo].[GetInquiryList]
#InquiryID int = 0
AS
BEGIN
SET NOCOUNT ON
select i.InquiryId,
i.InquiryDateReceived,
i.InquiryCustomerID,
InquiryCustomerName =
CASE i.InquiryCustomerID
WHEN 0 THEN i.InquiryCustomerName
ELSE c.CustomerName
END,
InquiryCustomerEmail =
CASE i.InquiryCustomerID
WHEN 0 THEN i.InquiryCustomerEmail
ELSE c.CustomerEmail
END,
InquiryCustomerPhone =
CASE i.InquiryCustomerID
WHEN 0 THEN i.InquiryCustomerPhone
ELSE c.CustomerPhone1
END,
InquiryBestTimetoCall =
CASE i.InquiryCustomerID
WHEN 0 THEN i.InquiryBestTimetoCall
ELSE c.CustomerBestTimetoCall
END,
InquiryDay =
CASE i.InquiryCustomerID
WHEN 0 THEN i.InquiryDay
ELSE c.CustomerDay
END,
InquiryNight =
CASE i.InquiryCustomerID
WHEN 0 THEN i.InquiryNight
ELSE c.CustomerNight
END,
i.InquiryServiceType,
i.InquiryServiceID,
i.InquiryTimeframe,
i.InquiryProjectDescription,
i.InquiryDateResponded,
i.InquiryCustomerReply,
i.YNMigrated,
i.InquiryDateClosed,
i.YNClosed,
i.YNDeleted, ISNULL(q.QuoteId,0) AS Quoteid
FROM dbo.Inquiries i
LEFT JOIN dbo.Quotes q ON i.InquiryId = q.InquiryId
LEFT JOIN dbo.Customers c ON i.InquiryCustomerID = c.CustomerId
WHERE i.YNDeleted = 0
END
I'm sure there are additional enhancements that could be made, but avoiding the union is a big savings. Thanks, everyone.

SQL Server : multipart identifier subquery

Is possible to use column org_level.level_name in the second JOIN? I have tried to merge the second JOIN but I got the next message :
The multi-part indentifier could not be found
Code:
SELECT
base.*,
org_level.level_id
FROM
dbo.raw_data base
LEFT JOIN
(SELECT DISTINCT
level_id AS level_name,
CASE
WHEN level_id = 'state' THEN 1
WHEN level_id = 'region' THEN 2
WHEN level_id = 'division' THEN 3
ELSE ''
END AS level_id
FROM
dbo.raw_data) AS org_level ON org_level.level_name = base.level_id
LEFT JOIN
(SELECT
loc.state_id,
CASE
WHEN org_level.level_name = 1 THEN 1
ELSE 0
END AS Number
FROM
dbo.locations AS loc) AS geo ON geo.state_id = base.Region_ID
In the second join, org_level does not exist, the scope of that subquery is limited to that specific subquery. Anything what is outside of that scope (say the parenthesis) is not visible to the query engine while processing the things within that scope.
You can move the CASE..WHEN construct to the main level SELECT list or you can use OUTER APPLY instead of LEFT JOIN.
The whole query does not make much sense. The first join uses the same table and just calculates some values using that CASE..WHEN construct and the result is joined back using the very same column.
Isn't this what you really want?
SELECT
base.*, -- Stars only visible at night, list the columns you really need.
CASE
WHEN level_id = 'state' THEN 1
WHEN level_id = 'region' THEN 2
WHEN level_id = 'division' THEN 3
ELSE ''
END AS level_id, -- Do you really want 2 columns named level_id in the result?
CASE
WHEN geo.state_id IS NOT NULL AND level_id = 1 THEN 1
ELSE 0
END AS Number
FROM
dbo.raw_data base
LEFT JOIN dbo.locations AS geo
ON geo.state_id = base.Region_ID

How to replace an aggregate null value by 0

I need to calculate a percentage, based on the amount of money on an user account and group the data by the account ID. I make in my calculation a sum of every payment that is used. A problem is that I also need to show acocunts without payments. My idea was to use a CASE statement to check if the aggragate sum gives a null or a value. When it returns null, I replace it by 0.
I have following query
SELECT
DA.ACCOUNT_ID,
ROUND((1 - ((CASE
WHEN SUM(FPP.AMOUNT_IN_DEFAULT_CURRENCY) IS NOT NULL THEN SUM(FPP.AMOUNT_IN_DEFAULT_CURRENCY)
ELSE 0
END) / FAT.PERCENTAGE_INDICATOR)) * 100,0) AS "Percentage"
FROM DIM_ACCOUNT DA
JOIN TRANSACTION_TABLE FAT ON FAT.ACCOUNT_ID = DA.ID
JOIN PAYMENTS_TABLE FPP ON FPP.ACCOUNT_ID = DA.ID
GROUP BY DA.ACCOUNT_ID
But when I execute this with test data, it doesn't work. The account is not added in my list. Is something wrong with my NULL handling?
When I strip of the query and only do the sum at the payment table, I get following output:
< null > (without spaces)
How can I make this work?
NULL is ignored by aggregate functions like sum/max/min...
If all column values are null then it will give error.
Sum(column) cannot be null
SELECT sum(t.num) AS sum_val
FROM (
SELECT null AS num
) t
Operand data type NULL is invalid for sum operator
if by 'without payments' you mean with no related records in PAYMENTS_TABLE then use a LEFT JOIN:
SELECT
DA.ACCOUNT_ID,
ROUND((1 - (ISNULL(SUM(FPP.AMOUNT_IN_DEFAULT_CURRENCY), 0) / FAT.PERCENTAGE_INDICATOR)) * 100,0) AS "Percentage"
FROM DIM_ACCOUNT DA
JOIN TRANSACTION_TABLE FAT ON FAT.ACCOUNT_ID = DA.ID
LEFT JOIN PAYMENTS_TABLE FPP ON FPP.ACCOUNT_ID = DA.ID
GROUP BY DA.ACCOUNT_ID;
a INNER JOIN does exclude the accounts without payments because requires a matching record in all the involved tables.

TSQL Select Statement using Case or Join

I am a little stuck on a situation that I have been trying to fight through. I have a page that allows a user to select all the filter options they want to search by and then it runs the query on that data.
Every field requires something to be picked but on a new field I am introducing, it's going to be optional.
It allows you to provide a list of supervisors and it will then provide all records where the agents supervisor is in the list provided; pretty straight forward. However, I am trying to make this optional as I don't want to always search by users. If I don't provide a name in the UI to pass to the stored procedure, then I want to ignore this part of the statement and get me everything regardless of the manager.
Here is the query I am working with:
SELECT a.[escID],
a.[escReasonID],
b.[ArchibusLocationName],
c.[ArchibusLocationName],
b.[DepartmentDesc],
c.[DepartmentDesc],
a.[escCreatedBy],
a.[escWorkedBy],
a.[escNotes],
a.[preventable],
a.[escalationCreated],
a.[escalationTracked],
a.[feedbackID],
typ.[EscalationType],
typ.[EscalationTypeText] AS escalationType,
d.reasonText AS reasonText
FROM [red].[dbo].[TFS_Escalations] AS a
LEFT OUTER JOIN
red.dbo.EmployeeTable AS b
ON a.escCreatedBy = b.QID
LEFT OUTER JOIN
red.dbo.EmployeeTable AS c
ON a.escWorkedBy = c.QID
LEFT OUTER JOIN
red.dbo.TFS_Escalation_Reasons AS d
ON a.escReasonID = d.ReasonID
INNER JOIN
dbo.TFS_EscalationTypes AS typ
ON d.escType = typ.EscalationType
WHERE B.[ArchibusLocationName] IN (SELECT location
FROM #tmLocations)
AND C.[ArchibusLocationName] IN (SELECT location
FROM #subLocations)
AND B.[DepartmentDesc] IN (SELECT department
FROM #tmDepartments)
AND C.[DepartmentDesc] IN (SELECT department
FROM #subDepartments)
AND DATEDIFF(second, '19700101', CAST (CONVERT (DATETIME, A.[escalationCreated], 121) AS INT)) >= #startDate
AND DATEDIFF(second, '19700101', CAST (CONVERT (DATETIME, A.[escalationCreated], 121) AS INT)) <= #endDate
AND a.[PREVENTABLE] IN (SELECT PREVENTABLE FROM #preventable)
AND b.MgrQID IN (SELECT leaderQID FROM #sourceLeaders)
The part that I am trying to make option is the very last line of the query:
AND b.MgrQID IN (SELECT leaderQID FROM #sourceLeaders)
Essentially, if there is no data in the temp table #sourceLeaders then it should ignore that piece of the query.
In all of the other instances of the WHERE clause, something is always required for those fields which is why that all works fine. I just cant figure out the best way to make this piece optional depending on if the temp table has data in it (the temp table is populated by the names entered in the UI that a user COULD search by).
So this line should be TRUE if something matches data in the table variable OR there is nothing in the table variable
AND
(
b.MgrQID IN (SELECT leaderQID FROM #sourceLeaders)
OR
NOT EXISTS (SELECT 1 FROM #sourceLeaders)
)
Similar to Nick.McDermaid's, but uses a case statement instead :
AND
(
1 = CASE WHEN NOT EXISTS(SELECT 1 FROM #sourceLeaders) THEN 1
WHEN b.MgrQID IN (SELECT leaderQID FROM #sourceLeaders) THEN 1
ELSE 0
END
)
Maybe at the top so you have a single check
DECLARE #EmptySourceLeaders CHAR(1)
IF EXISTS (SELECT 1 FROM #sourceLeaders)
SET #EmptySourceLeaders = 'N'
ELSE
SET #EmptySourceLeaders = 'Y'
Then in the joins
LEFT OUTER JOIN #SourceLeaders SL
ON b.MgrQID = SL.leaderQID
Then in the WHERE
AND (#EmptySourceLeaders = 'Y' OR SL.leaderQID IS NOT NULL)
lots of ways to do it.

SQL Case Statement - If first 'when' returns null then complete the 2nd when

Just wondering if anyone can help me , I am running a case statement that references a different table. It needs to look up the make, model and year of a car as well as the position (FL,FR,BL,BR) and return the kit number.
Up to 4 entries can exist in the table for the same vehicle with the fitting position column specifying which kit number to be selected, in order to only return 1 result i believe i need to put this in the where section of the query, if i add it anywhere else more than 1 value is returned.
However 4 entries won't always exist for the vehicle. A kit can exist for FL & BL but not FR and BR. Because of me adding the position column into the where section 'null' is returned.Rather than it returning nothing i want it to return the next part of the case statement.
This is where the sql works because a kit is available for FL
SELECT CAST (CASE WHEN '002' != 'UNI' THEN T0.U_MPLFK ELSE 'NOKIT' END AS VARCHAR)
FROM
[#CSOL_MILFORD] T0 INNER JOIN [dbo].[#CSOL_VEHICLES] T1 ON T0.[U_VehicleRef] = T1.[U_VehicleRef]
WHERE
T1.U_Manufacturer = 'Ford'
AND
T1.U_Model = 'Galaxy'
AND
T0.U_MPLFK > 1
AND
T0.U_FittingPosition = 'FL'
However when it changes to
SELECT CAST (CASE WHEN '002' != 'UNI' THEN T0.U_MPLFK ELSE 'NOKIT' END AS VARCHAR)
FROM
[#CSOL_MILFORD] T0 INNER JOIN [dbo].[#CSOL_VEHICLES] T1 ON T0.[U_VehicleRef] = T1.[U_VehicleRef]
WHERE
T1.U_Manufacturer = 'Ford'
AND
T1.U_Model = 'Galaxy'
AND
T0.U_MPLFK > 1
AND
T0.U_FittingPosition = 'FR'
I get no value retuned, i want it to return 'NOKIT'
Many Thanks,
Roisin
A left join returns a row with null columns if its on condition fails. So you could move the conditions to the on part of a left join. Something like:
...
FROM #CSOL_VEHICLES T1
LEFT JOIN
#CSOL_MILFORD T0
ON T0.U_VehicleRef = T1.U_VehicleRef
AND T0.U_MPLFK > 1
AND T0.U_FittingPosition = 'FR'
WHERE T1.U_Manufacturer = 'Ford'
AND T1.U_Model = 'Galaxy'
Having the condition in the on clause instead of the where clause means you'll get a row with nulls instead of no row.

Resources