Generating missing report submissions via reference table - sql

I have a SQL query i'm currently working on which i would greatly appreciate some help with.
Here is a simplified version of the view I've been given to work on:
SELECT a.Organisation_Name
,a.Org_Id
,b.Activity_month
,SUM(b.Activity_Plan) 'Plan_Activity'
,SUM(b.Activity_Actual) 'Actual_Activity'
,SUM(b.Price_Actual) 'Actual_Price'
,SUM(b.Price_Plan) 'Plan_Price'
,COUNT(b.Instances) AS 'Record_Count'
,CASE WHEN COUNT(b.Instances) > 0 THEN 'Yes' ELSE 'No' END AS Submitted
FROM [ExampleDatabase].[dbo].[Organisation_Reference] a
LEFT JOIN [ExampleDatabase].[dbo].[Report_Submissions] b
ON a.Org_Id = b.Org_Id
AND ([Exmaple_Code] LIKE ('X') or [Example_Code] = 'X')
WHERE a.Category_Flag = 1
AND a.Example_Code in ('X','X','X','X','X')
GROUP BY
a.Organisation_Name
,a.Org_Id
,b.Activity_month
--
The Activity Month field is an Integer rather than a date, currently ranging from 1-8.
The problem i am facing is that within the [Report_Submissions] table, it only contains organisations which have actually submitted the reports, whereas the
[Organisation_Reference] table lists all the organisations which should be submitting.
Where the organisations have submitted the reports, the data is perfect and gives me a run down of all the details i need for each individual month.
Obviously if an organisation hasn't submitted then this detail wouldn't be available, but i do need to have a complete list of all organisations listed from the reference table for each individual month and whether they have submitted the reports or not.
At the moment where the 'Submitted' field = 'No' it's only bringing back one record for each organisation that has never submitted (With Activity_month coming through as null) and if an organisation has only submitted once or twice then it will include those submissions but still be missing the rest of the months from the result set.
I've tried various different joins etc. but I seem to be drawing a blank for a solution. Is there a way of generating this information within the script? Any advice would be great!
Kind Regards,
Mark

Since you just need numbers 1-8, using a subquery in your join to cross apply(values ()) to your Organisation_Reference table works well and does not make the query much more compliCated to read.
select
a.Organisation_Name
, a.Org_Id
, a.Activity_Month
, sum(b.Activity_Plan) 'Plan_Activity'
, sum(b.Activity_Actual) 'Actual_Activity'
, sum(b.Price_Actual) 'Actual_Price'
, sum(b.Price_Plan) 'Plan_Price'
, count(b.Instances) as 'record_count'
, case when count(b.Instances) > 0 then 'yes' else 'no' end as Submitted
from (
select o.*, t.Activity_Month
from [ExampleDatabase].[dbo].[Organisation_Reference] as o
cross apply (values (1),(2),(3),(4),(5),(6),(7),(8)) t(Activity_Month)
) as a
left join [ExampleDatabase].[dbo].[Report_Submissions] b
on a.Org_Id = b.Org_Id
and a.Activity_Month = b.Activity_Month
and ([exmaple_Code] like ('X') or [Example_Code] = 'X')
where a.Category_Flag = 1
and a.Example_Code in ('X','X','X','X','X')
group by
a.Organisation_Name
, a.Org_Id
, b.Activity_Month
You could also cross join with a numbers/tally table, or use a common table expression to generate the range of numbers you need. I would recommend either of those options as well, especially if your logic was more compliCated.
If Report_Submissions contains all of the months you want in your query, you could cross join the distinct Activity_Months from that table to your Organisation_Reference table.
select
a.Organisation_Name
, a.Org_Id
, a.Activity_Month
, sum(b.Activity_Plan) 'Plan_Activity'
, sum(b.Activity_Actual) 'Actual_Activity'
, sum(b.Price_Actual) 'Actual_Price'
, sum(b.Price_Plan) 'Plan_Price'
, count(b.Instances) as 'record_count'
, case when count(b.Instances) > 0 then 'yes' else 'no' end as Submitted
from (
select o.*, t.Activity_Month
from [ExampleDatabase].[dbo].[Organisation_Reference] as o
cross join (select distinct Activity_Month from Report_Submissions) t
) as a
left join [ExampleDatabase].[dbo].[Report_Submissions] b
on a.Org_Id = b.Org_Id
and a.Activity_Month = b.Activity_Month
and ([exmaple_Code] like ('X') or [Example_Code] = 'X')
where a.Category_Flag = 1
and a.Example_Code in ('X','X','X','X','X')
group by
a.Organisation_Name
, a.Org_Id
, b.Activity_Month

Related

SQL View slow when filtered. Is there a clean way to improve performance?

Let me open with:
SHOWPLAN permission denied in database 'MyDatabase'.
With that out of the way, I'll layout my situation.
So, The database I work with has a view that executes fairly quickly.
SELECT * FROM MyView
returns 32 rows in 1 second and includes a non-indexed column of values (IDs) I need to filter on.
If I filter on these IDs directly in the view:
SELECT * FROM MyView WHERE MyView.SomeId = 18
Things slow immensely and it takes 21 seconds to return the 20 rows with that ID.
As an experiment I pushed the unfiltered results into a temporary table and executed the filtered query on the the temporary table:
IF OBJECT_ID('tempdb..#TEMP_TABLE') IS NOT NULL
BEGIN
DROP TABLE #TEMP_TABLE
END
SELECT * INTO #TEMP_TABLE
FROM MyView;
SELECT *
FROM #TEMP_TABLE
WHERE #TEMP_TABLE.SomeId = 18
DROP TABLE #TEMP_TABLE
And found that it returns the filtered results far faster (roughly 1 second)
Is there a cleaner syntax or pattern that can be implemented to achieve the same performance?
UPDATE: View Definition and Description
Manually obfuscated, but I was careful so hopefully there aren't many errors. Still waiting on SHOWPLAN permissions, so Execution Plan is still pending.
The view's purpose is to provide a count of all the records that belong to a specific component (CMP.COMPONENT_ID = '100') grouped by location.
"Belonging" is determined by the record's PROC_CODE (mapped through PROC_ID) being within the CMP's inclusion range (CMP_INCs) and not in the CMP's exclusion range (CMP_EXCs).
In practice, exclusion ranges are created for individual codes (the bounds are always equal) making it sufficient to check that the code is not equal a bound.
PROC_CODES can (and don't always) have an alphabetic prefix or suffix, which makes the ISNUMERIC() comparison necessary.
Records store PROC_IDs for their PROC_CODEs, so it's necessary to convert the CMP's PROC_CODE ranges into a set of PROC_IDs for identifying which records belong to that component
The performance issue occurs when trying to filter by DEPARTMENT_ID or LOCATION_ID
[CO_RECORDS] is also a view, but if it's that deep I'm going turf this to someone with less red tape to fight through.
CREATE VIEW [ViewsSchema].[MyView] AS
WITH
CMP_INCs AS (SELECT RNG.*, COALESCE(RNG.RANGE_END, RNG.RANGE_BEG) [SAFE_END] FROM DBEngine.DBO.DB_CMP_RANGE [RNG] WHERE [RNG].COMPONENT_ID = '100'),
CMP_EXCs AS (SELECT CER.* FROM DBEngine.DBO.DB_CMP_EXC_RANGE CER WHERE CER.COMPONENT_ID = '100'),
CMP_PROC_IDs AS (
SELECT
DBEngine_ProcTable.PROC_ID [CMP_PROC_ID],
DBEngine_ProcTable.PROC_CODE [CMP_PROC_CODE],
DB_CmpTable.COMPONENT_ID [CMP_ID],
MAX(DB_CmpTable.COMPONENT_NAME) [CMP_NAME]
FROM [DBEngine].DBO.DBEngine_ProcTable DBEngine_ProcTable
LEFT JOIN CMP_INCs ON ISNUMERIC(DBEngine_ProcTable.PROC_CODE) = ISNUMERIC(CMP_INCs.RANGE_BEG)
AND(DBEngine_ProcTable.PROC_CODE = CMP_INCs.RANGE_BEG
OR DBEngine_ProcTable.PROC_CODE BETWEEN CMP_INCs.RANGE_BEG AND CMP_INCs.SAFE_END)
INNER JOIN DBEngine.DBO.DB_CmpTable ON CMP_INCs.COMPONENT_ID = DB_CmpTable.COMPONENT_ID
LEFT JOIN CMP_EXCs EXCS ON EXCS.COMPONENT_ID = DB_CmpTable.COMPONENT_ID AND EXCS.EXCL_RANGE_END = DBEngine_ProcTable.PROC_CODE
WHERE EXCS.EXCL_RANGE_BEG IS NULL
GROUP BY
DBEngine_ProcTable.PROC_ID,
DBEngine_ProcTable.PROC_CODE,
DBEngine_ProcTable.BILL_DESC,
DBEngine_ProcTable.PROC_NAME,
DB_CmpTable.COMPONENT_ID
)
SELECT
RECORD.LOCATION_NAME [LOCATION_NAME]
, RECORD.LOCATION_ID [LOCATION_ID]
, MAX(RECORD.[Department]) [DEPARTMENT]
, RECORD.[Department ID] [DEPARTMENT_ID]
, SUM(RECORD.PROCEDURE_QUANTITY) [PROCEDURE_COUNT]
FROM DBEngineCUSTOMRPT.ViewsSchema.CO_RECORDS [RECORDS]
INNER JOIN CMP_PROC_IDs [CV] ON [CV].CMP_PROC_ID = [RECORDS].PROC_ID
CROSS JOIN (SELECT DATEADD(M, DATEDIFF(M, 0,GETDATE()), 0) [FIRSTOFTHEMONTH]) VARS
WHERE [RECORDS].TYPE = 1
AND ([RECORDS].VOID_DATE IS NULL OR [RECORDS].VOID_DATE >= VARS.[FIRSTOFTHEMONTH] )
AND [RECORDS].POST_DATE < VARS.[FIRSTOFTHEMONTH]
AND [RECORDS].DOS_MONTHS_BACK = 2
GROUP BY [RECORDS].LOCATION_NAME, [RECORDS].[Department ID]
GO
Based on the swift down votes, the answer to my question is
'No, there is not a clean syntax based solution for the improved
performance, and asking for one is ignorant of the declarative nature
of SQL you simple dirty plebeian'.
From the requests for the view's definition, it's clear that performance issues in simple queries should be addressed by fixing the structure of the objects being queried ('MyView' in this case) rather than syntactical gymnastics.
For interested parties the issue was resolved by adding a Row_Number() column to the final select in the view definition, wrapping it in a CTE, and using the new column in an always true filter while selecting the original columns.
I have no idea if this is the optimal solution. It doesn't feel good to me, but it appears to be working.
CREATE VIEW [ViewsSchema].[MyView] AS
WITH
CMP_INCs AS (SELECT RNG.*, COALESCE(RNG.RANGE_END, RNG.RANGE_BEG) [SAFE_END] FROM DBEngine.DBO.DB_CMP_RANGE [RNG] WHERE [RNG].COMPONENT_ID = '100'),
CMP_EXCs AS (SELECT CER.* FROM DBEngine.DBO.DB_CMP_EXC_RANGE CER WHERE CER.COMPONENT_ID = '100'),
CMP_PROC_IDs AS (
SELECT
DBEngine_ProcTable.PROC_ID [CMP_PROC_ID],
DBEngine_ProcTable.PROC_CODE [CMP_PROC_CODE],
DB_CmpTable.COMPONENT_ID [CMP_ID],
MAX(DB_CmpTable.COMPONENT_NAME) [CMP_NAME]
FROM [DBEngine].DBO.DBEngine_ProcTable DBEngine_ProcTable
LEFT JOIN CMP_INCs ON ISNUMERIC(DBEngine_ProcTable.PROC_CODE) = ISNUMERIC(CMP_INCs.RANGE_BEG)
AND(DBEngine_ProcTable.PROC_CODE = CMP_INCs.RANGE_BEG
OR DBEngine_ProcTable.PROC_CODE BETWEEN CMP_INCs.RANGE_BEG AND CMP_INCs.SAFE_END)
INNER JOIN DBEngine.DBO.DB_CmpTable ON CMP_INCs.COMPONENT_ID = DB_CmpTable.COMPONENT_ID
LEFT JOIN CMP_EXCs EXCS ON EXCS.COMPONENT_ID = DB_CmpTable.COMPONENT_ID AND EXCS.EXCL_RANGE_END = DBEngine_ProcTable.PROC_CODE
WHERE EXCS.EXCL_RANGE_BEG IS NULL
GROUP BY
DBEngine_ProcTable.PROC_ID,
DBEngine_ProcTable.PROC_CODE,
DBEngine_ProcTable.BILL_DESC,
DBEngine_ProcTable.PROC_NAME,
DB_CmpTable.COMPONENT_ID
),
RESULTS as (
SELECT
RECORD.LOCATION_NAME [LOCATION_NAME]
, RECORD.LOCATION_ID [LOCATION_ID]
, MAX(RECORD.[Department]) [DEPARTMENT]
, RECORD.[Department ID] [DEPARTMENT_ID]
, SUM(RECORD.PROCEDURE_QUANTITY) [PROCEDURE_COUNT]
, ROW_NUMBER() OVER (ORDER BY TDL.[Medical Department ID], TDL.[BILL_AREA_ID], TDL.JP_POS_NAME) [ROW]
FROM DBEngineCUSTOMRPT.ViewsSchema.CO_RECORDS [RECORDS]
INNER JOIN CMP_PROC_IDs [CV] ON [CV].CMP_PROC_ID = [RECORDS].PROC_ID
CROSS JOIN (SELECT DATEADD(M, DATEDIFF(M, 0,GETDATE()), 0) [FIRSTOFTHEMONTH]) VARS
WHERE [RECORDS].TYPE = 1
AND ([RECORDS].VOID_DATE IS NULL OR [RECORDS].VOID_DATE >= VARS.[FIRSTOFTHEMONTH] )
AND [RECORDS].POST_DATE < VARS.[FIRSTOFTHEMONTH]
AND [RECORDS].DOS_MONTHS_BACK = 2
GROUP BY [RECORDS].LOCATION_NAME, [RECORDS].[Department ID]
)
SELECT
[LOCATION_NAME]
, [LOCATION_ID]
, [DEPARTMENT]
, [DEPARTMENT_ID]
, [PROCEDURE_COUNT]
FROM RESULTS
WHERE [ROW] > 0
GO

SQL - Summarizing Monthly Sales Data

I have two tables that I need to combine, but I can't get to seem the joins to work. The picture has three tables
_date_array2: Has a field DateMonthYr that contains all possible date/yr combinations
_sales_summ_tbl__ => Has 5 fields. Only months with sales show up. For example, you see only three records showing up for the second table. There is no 5/2016, for example, because there were no sales for that month.
My goal is to "pad" the second table to have TotalDemand of 0's for months with no sales. I am very close (see the third table), except I cannot get the PartNumber to show up for dates with no sales.
My guess is that it's due to the RIGHT JOIN. But I'm not sure how to handle this. The output I am hoping for is table 3 but with the part number populated for all entries.
And here is my code (the results from running this code are the third/last table in the picture):
SELECT TmpSalesTbl.PartNumber as PartNumber,
tmp_date_array.CreateDateMonth as CreateDateMonth,
tmp_date_array.CreateDateYear as CreateDateYear,
CASE WHEN TmpSalesTbl.TotalDemand is NULL THEN 0 ELSE TmpSalesTbl.TotalDemand END as TotalDemand
FROM #_sales_summ_tbl__ TmpSalesTbl
RIGHT JOIN #_date_array2 tmp_date_array on tmp_date_array.CreateDateMonthYr = TmpSalesTbl.CreateDateMonthYr
ORDER BY tmp_date_array.CreateDateYear, tmp_date_array.CreateDateMonth
It is more conventional to place the list of all dates first, then left join to the data and whilst using a case expression is fine an alternative is coalesce(). This should ensure all the wanted months/years display:
SELECT
tmpsalestbl.PartNumber AS partnumber
, tmp_date_array.CreateDateMonth AS createdatemonth
, tmp_date_array.CreateDateYear AS createdateyear
, COALESCE(tmpsalestbl.TotalDemand, 0) AS totaldemand
FROM #_date_array2 tmp_date_array
LEFT JOIN #_sales_summ_tbl__ tmpsalestbl ON tmp_date_array.CreateDateMonthYr = tmpsalestbl.CreateDateMonthYr
ORDER BY
tmp_date_array.CreateDateYear
, tmp_date_array.CreateDateMonth
To populate for evey partnumber, on every month, you will need a new subquery:
select distinct PartNumber #_sales_summ_tbl__
And then cross join that to the years/months so you have a complete set of years/months/parts.
SELECT
cj.PartNumber AS partnumber
, tmp_date_array.CreateDateMonth AS createdatemonth
, tmp_date_array.CreateDateYear AS createdateyear
, COALESCE(tmpsalestbl.TotalDemand, 0) AS totaldemand
FROM #_date_array2 tmp_date_array
CROSS JOIN (
SELECT DISTINCT
PartNumber FROM #_sales_summ_tbl__
) cj
LEFT JOIN #_sales_summ_tbl__ tmpsalestbl ON tmp_date_array.CreateDateMonthYr = tmpsalestbl.CreateDateMonthYr
AND cj.PartNumber = tmpsalestbl.PartNumber
ORDER BY
tmp_date_array.CreateDateYear
, tmp_date_array.CreateDateMonth
;

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;

Case Statement using Dates

I have a data set of clients. Each have a Start date and an End Date. A client can have multiple lines with different Start dates and End dates. I have another data set with Claims info and i want to know if they had claims during the time frame there state and end date.
how can i write this?
SELECT
M.[ID]
,EN.StartDate
,EN.EndDate
,[Has Cliams History] (Column to identify if yes or no)
FROM [Test].[dbo].[tblClients] M
left join [test].[dbo].[tblCliams] EN on EN.ID = M.ID
To illustrate the use of a case statement, the ClaimDate field represents the date of a claim. The query would look something like this.
SELECT
M.[ID]
,EN.StartDate
,EN.EndDate
,CASE
WHEN ClaimDate between EN.StartDate AND EN.EndDate THEN 'yes'
ELSE 'no'
END [Has Cliams History]
FROM [Test].[dbo].[tblClients] M
left join [test].[dbo].[tblClaims] EN on EN.ID = M.ID
Your question needs to provide more clarity on the definition of both tables and the relationship between the two. I am guessing you aren't actually joining a column called ID from both (at least I hope not). Here is a stab at it based on the assumption you have a ClientID on the tblClaims and also that you support ongoing clients via a NULL EndDate. Also assuming there are Start and End dates on tblClaims.
SELECT M.[ID]
, CASE WHEN MAX(claim.ID) IS NOT NULL THEN 1 ELSE 0 END AS HasClaimsHistory
FROM [Test].[dbo].[tblClients] client
LEFT JOIN [Test].[dbo].[tblCliams] claim on client.ID = claim.ClientID
AND claim.StartDate >= client.StartDate
AND (
client.EndDate IS NULL /* ongoing support? */
OR
claim.EndDate <= client.EndDate
)
GROUP BY M.[ID]
, CASE WHEN MAX(claim.ID) IS NOT NULL THEN 1 ELSE 0 END

Like SQL Statement SQL Server 2008R2

I have a simple query but I have mutiple records I need to filter out. I'm using the like statment with wild cards. Is there a better way do do this then writing out each one? Can I create a udf, table that it refrences? How? If I can. Thanks :)
SELECT a.SalesOrderNo ,
a.ShipExpireDate ,
a.CustomerNo ,
b.ItemCode ,
b.LineKey ,
b.QuantityOrdered ,
b.QuantityShipped ,
b.ItemCodeDesc ,
b.ExplodedKitItem
FROM dbo.SO_SalesOrderHeader a
LEFT JOIN dbo.SO_SalesOrderDetail b
ON a.SalesOrderNo = b.SalesOrderNo
WHERE b.ItemType = '1'
AND b.ItemCodeDesc NOT LIKE '%Cert%'
AND b.ItemCodeDesc NOT LIKE '%Fee%'
AND b.ItemCodeDesc NOT LIKE '%Tag%'
AND b.ItemCode NOT LIKE 'GF%'
AND b.ItemCode NOT LIKE 'PXDIALPREP'
AND b.ItemCode NOT LIKE '/C%'
AND a.ShipExpireDate = CONVERT(DATE, GETDATE(), 101)
Here's a different design that lets you put ItemCodeDesc in a seperate table (this could also be a TVF). I can't comment on performance though.
On a different note, be aware that because you are outer joining to sales order detail, this table can have NULL records. In turn your b.ItemType = '1' will always be FALSE when ItemType is NULL. So you may as well make it an inner join (and you might find your query plan is doing that anyway)
SELECT a.SalesOrderNo ,
a.ShipExpireDate ,
a.CustomerNo ,
b.ItemCode ,
b.LineKey ,
b.QuantityOrdered ,
b.QuantityShipped ,
b.ItemCodeDesc ,
b.ExplodedKitItem
FROM dbo.SO_SalesOrderHeader a
LEFT JOIN dbo.SO_SalesOrderDetail b
ON a.SalesOrderNo = b.SalesOrderNo
WHERE b.ItemType = '1'
AND b.ItemCode NOT LIKE 'GF%'
AND b.ItemCode NOT LIKE 'PXDIALPREP'
AND b.ItemCode NOT LIKE '/C%'
AND a.ShipExpireDate = CONVERT(DATE, GETDATE(), 101)
AND NOT EXISTS (
SELECT 1 FROM dbo.MappingTable MT
WHERE b.ItemCodeDesc LIKE MT.ItemCodeDesc
)
Note: I am guessing that your criteria is meant to filter out item types that can't be shipped (like Fees), adjust as per your requirements.
The problem you are encountering is a result of discrete values being stored in an ID. Looks like you should have a column IsShippable, or better yet a code table for ItemCodeType with rows of Cert, Fee, Tag, etc. and the IsShippable column there. if you had a code table then you'd be able to do
inner join ItemCodeTypes ict on ict.ItemCodeTypeId = b.ItemCodeTypeId and ict.IsShippable = 1
Cert, Fee, Tag, rows in the ItemCodeTypes table would have IsShippable = 0:
Id | Name | IsShippable
1 Cert 0
2 Fee 0
3 Tag 0
4 Product 1
5 Book 1
Edit: To more directly answer your question, you could make a view like this, and then when you query from it easily filter on Where IsShippable = 1:
Select CASE
When b.ItemCodeDesc LIKE '%Cert%' Then 0
When b.ItemCodeDesc LIKE '%Fee%' Then 0
--etc.
Else 1
END as IsShippable
,*
From dbo.SO_SalesOrderDetail