Can a computed column in design reference another in sql server? - database

I have 2 computed columns, [StartTime] and [EndTime]
The [StartTime] and [EndTime] are calculated with a formula using the [Week] and [Year] columns.
Now I need another computed column, [Status] that is calculated using the first two. But it gives me an error in formula when I try to use one of them inside the formula of [Status]
I really need this to work as I have no alternative. But is this even possible?
Here you go Mr -1 :
(case when [IsVOR]=(1) then 'VOR'
when [MarkedAsCompleteOn] IS NULL AND [Year]<datepart(year,getdate()) then 'Overdue'
when [MarkedAsCompleteOn] IS NULL AND [Year]>datepart(year,getdate()) then 'Not Due'
when [MarkedAsCompleteOn] IS NULL AND [Year]=datepart(year,getdate()) AND [Week]<datepart(iso_week,getdate()) then 'Overdue'
when [MarkedAsCompleteOn] IS NULL AND [Year]=datepart(year,getdate()) AND [Week]=datepart(iso_week,getdate()) then 'Due'
when [MarkedAsCompleteOn] IS NULL AND [Year]=datepart(year,getdate()) AND [Week]>datepart(iso_week,getdate()) then 'Not Due'
when [MarkedAsCompleteOn] IS NOT NULL AND [Year]<datepart(year,[MarkedAsCompleteOn]) then 'Late'
when [MarkedAsCompleteOn] IS NOT NULL AND [Year]>datepart(year,[MarkedAsCompleteOn]) then 'Early'
when [MarkedAsCompleteOn] IS NOT NULL AND [Year]=datepart(year,[MarkedAsCompleteOn]) AND [Week]<datepart(iso_week,[MarkedAsCompleteOn]) then 'Late'
when [MarkedAsCompleteOn] IS NOT NULL AND [Year]=datepart(year,[MarkedAsCompleteOn]) AND [Week]=datepart(iso_week,[MarkedAsCompleteOn]) then 'On Time'
when [MarkedAsCompleteOn] IS NOT NULL AND [MarkedAsCompleteOn]<[AllocatedTimeStart] then 'Early' end)
The last part of it causes the error :
[MarkedAsCompleteOn]<[AllocatedTimeStart] then 'Early'
And the error is generic :
- Error validating the formula for column 'Status'.

No. One computed column can not be based on a different computed column. There's a specific error for this (1759):
Computed column '%s' in table '%s' is not allowed to be used in another computed-column definition.
You could create a view, based on the table, and perform a second round of computations within the view definition. Whether you then perform all activity against the view (adding triggers if required), or only use it for lookup is a design decision you'd need to make.
Of course, one you add the idea of using a view, you can build up multiple (not just 2) layers of computation by using a Common Table expression.
You do lose the ability to make the computed column persisted, unless the view is eligible for becoming an indexed view - not that this matters in this case, since it seems to be based on date calculations, so probably not persistable anyway.

I found my own solution in the end, by using a function, this kept my production app going without any changes.
USE [DBNAME]
GO
/****** Object: UserDefinedFunction [dbo].[GetStatus] Script Date: 01/02/2013 11:08:29 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER FUNCTION [dbo].[GetStatus](#CompletedOn DATETIME, #Year INT, #Week INT)
RETURNS NVARCHAR(MAX)
AS BEGIN
DECLARE #CurrentDate DATETIME
DECLARE #StartTime DATETIME
DECLARE #EndTime DATETIME
DECLARE #Status NVARCHAR(MAX)
SET #CurrentDate = GETDATE()
SET #StartTime = (dateadd(week,#Week-(1),dateadd(day,(-1),dateadd(week,datediff(week,(0),CONVERT([varchar](4),#Year,(0))+'-01-01'),(1)))))
SET #EndTime = (dateadd(week,#Week-(1),dateadd(day,(-1),dateadd(week,datediff(week,(0),CONVERT([varchar](4),#Year,(0))+'-01-01'),(8)))))
SET #Status = '-'
IF(#CompletedOn IS NULL)
BEGIN
IF(#CurrentDate < #StartTime)
SET #Status = 'Not Due'
IF(#CurrentDate > #StartTime AND #CurrentDate < #EndTime)
SET #Status = 'Due'
IF(#CurrentDate > #EndTime)
SET #Status = 'Overdue'
END
ELSE
BEGIN
IF(#CompletedOn < #StartTime)
SET #Status = 'Early'
IF(#CompletedOn > #EndTime)
SET #Status = 'Late'
ELSE
SET #Status = 'On Time'
END
RETURN #Status
END
And in the status field I put the formula :
([dbo].[GetStatus]([MarkedAsCompleteOn],[Year],[Week]))
This is my first ever function in sql server, so it may not be optimum.

Related

Stored procedure Date From and Date To

I am trying to create a stored procedure for filtering orders. Basically the users have the option of filtering the order by date from and date to. So they can do search via date from, date to or use both if it makes sense?
Anyhow here is my SQL Server stored procedure so far
ALTER PROCEDURE [dbo].[CN_GetOrderItemByCustID]
#CustomerID int,
#OrderItemWRClass varchar(max) = NULL,
#OrderItemSKUName varchar(50) = NULL,
#OrderItemDateFrom Datetime,
#OrderItemDateTo Datetime
AS
BEGIN
SET NOCOUNT ON;
IF DATEDIFF(d, #OrderItemDateFrom, '01/01/1970') = 0
SET #OrderItemDateFrom = null
IF DATEDIFF(d, #OrderItemDateTo, '01/01/1970') = 0
SET #OrderItemDateTo = null
-- Insert statements for procedure here
SELECT
COM_OrderItem.OrderItemID, COM_Order.OrderID,
COM_Order.OrderDate, COM_OrderItem.OrderItemUnitCount,
COM_OrderItem.OrderItemStatus, COM_OrderItem.OrderItemSKUNAME,
COM_OrderItem.OrderItemSKUID
FROM
COM_OrderItem
INNER JOIN
COM_Order ON COM_Order.OrderID = COM_OrderItem.OrderItemOrderID
WHERE
COM_Order.OrderCustomerID = #CustomerID
OR COM_OrderItem.OrderItemWRClass LIKE #OrderItemWRClass + '%'
OR COM_OrderItem.OrderItemSKUName LIKE #OrderItemSKUName + '%'
OR CONVERT(VARCHAR, COM_Order.OrderDate, 120) LIKE #OrderItemDateFrom + '%'
ORDER BY
COM_Order.OrderDate DESC
However I am not sure on how to put the date from (OrderItemDateFrom) and date to (OrderItemDateTo) in the final SQL statement?
Should I be using OR CONVERT(VARCHAR, COM_Order.OrderDate, 120) LIKE #OrderItemDateFrom + '%' -- which gives me an error
Conversion failed when converting date and/or time from character string.
I know in a normal SQL query I would use Between OrderItemDateFrom and OrderItemDateTo
Thanks
Use this logic
ALTER PROCEDURE [dbo].[CN_GetOrderItemByCustID]
-- Add the parameters for the stored procedure here
#CustomerID int,
#OrderItemWRClass varchar(max) = NULL,
#OrderItemSKUName varchar(50) = NULL,
#OrderItemDateFrom Datetime,
#OrderItemDateTo Datetime
AS
BEGIN
SET NOCOUNT ON;
IF DATEDIFF(d,#OrderItemDateFrom,'01/01/1970')=0 SET #OrderItemDateFrom = '01/01/1970';
IF DATEDIFF(d,#OrderItemDateTo,'01/01/1970')=0 SET #OrderItemDateTo = '31/12/2199';
-- Insert statements for procedure here
SELECT COM_OrderItem.OrderItemID, COM_Order.OrderID, COM_Order.OrderDate, COM_OrderItem.OrderItemUnitCount, COM_OrderItem.OrderItemStatus, COM_OrderItem.OrderItemSKUNAME,
COM_OrderItem.OrderItemSKUID
FROM COM_OrderItem
INNER JOIN COM_Order ON COM_Order.OrderID = COM_OrderItem.OrderItemOrderID
WHERE COM_Order.OrderCustomerID = #CustomerID OR COM_OrderItem.OrderItemWRClass LIKE #OrderItemWRClass + '%' OR COM_OrderItem.OrderItemSKUName LIKE #OrderItemSKUName + '%'
OR (COM_OrderDate>=#OrderItemDateFrom && COM_OrderDate<=#OrderItemDateTo )
ORDER BY COM_Order.OrderDate DESC
Try it . It should work.
Your logic can be simplified a little by allowing NULL values for #OrderItemDateFrom and #OrderItemDateTo. Also, if filters values and column values are all DATETIMEs, you should try to compare directly to allow indexes usages (if any applied on the DATETIME column):
ALTER PROCEDURE [dbo].[CN_GetOrderItemByCustID]
#CustomerID int,
#OrderItemWRClass varchar(max) = NULL,
#OrderItemSKUName varchar(50) = NULL,
#OrderItemDateFrom Datetime = NULL, -- TODO: change caller to not provide parameter, or leave it to null
#OrderItemDateTo Datetime = NULL -- TODO: change caller to not provide parameter, or leave it to null
AS
BEGIN
SET NOCOUNT ON;
-- when working with dates try to use an unambiguous format like 'YYYY-MM-DD'
SET #OrderItemDateFrom = ISNULL(#OrderItemDateFrom, '1970-01-01')
-- assign a very large date to act like not provided
-- going one day after to catch DATETIMEs with provided time
SET #OrderItemDateTo = DATEADD(day, 1, ISNULL(#OrderItemDateTo, '3000-01-01'))
-- Insert statements for procedure here
SELECT
COM_OrderItem.OrderItemID, COM_Order.OrderID,
COM_Order.OrderDate, COM_OrderItem.OrderItemUnitCount,
COM_OrderItem.OrderItemStatus, COM_OrderItem.OrderItemSKUNAME,
COM_OrderItem.OrderItemSKUID
FROM COM_OrderItem
INNER JOIN COM_Order ON COM_Order.OrderID = COM_OrderItem.OrderItemOrderID
WHERE COM_Order.OrderCustomerID = #CustomerID
OR COM_OrderItem.OrderItemWRClass LIKE #OrderItemWRClass + '%'
OR COM_OrderItem.OrderItemSKUName LIKE #OrderItemSKUName + '%'
-- between can be used
OR (COM_OrderDate BETWEEN #OrderItemDateFrom AND #OrderItemDateTo)
ORDER BY
COM_Order.OrderDate DESC
END
Another option is to use dynamic SQL and construct it based on parameters values (i.e. insert WHERE condition if filter value is provided). This is particularly useful when filters numbers is relatively low compared to the total number of filters, as ORs are not performance friendly.
NOTE: shouldn't your filters apply in conjuction (i.e. use AND instead of OR)? It would make sense to allow the user to filter by several value in the same time.
ALTER PROCEDURE [dbo].[CN_GetOrderItemByCustID]
#CustomerID int,
#OrderItemWRClass varchar(max) = NULL,
#OrderItemSKUName varchar(50) = NULL,
#OrderItemDateFrom Datetime,
#OrderItemDateTo Datetime
AS
BEGIN
SET NOCOUNT ON;
IF DATEDIFF(d, #OrderItemDateFrom, '01/01/1970') = 0
SET #OrderItemDateFrom = null
IF DATEDIFF(d, #OrderItemDateTo, '01/01/1970') = 0
SET #OrderItemDateTo = null
-- Insert statements for procedure here
SELECT
COM_OrderItem.OrderItemID, COM_Order.OrderID,
COM_Order.OrderDate, COM_OrderItem.OrderItemUnitCount,
COM_OrderItem.OrderItemStatus, COM_OrderItem.OrderItemSKUNAME,
COM_OrderItem.OrderItemSKUID
FROM
COM_OrderItem
INNER JOIN
COM_Order ON COM_Order.OrderID = COM_OrderItem.OrderItemOrderID
WHERE
COM_Order.OrderCustomerID = #CustomerID
OR COM_OrderItem.OrderItemWRClass LIKE #OrderItemWRClass + '%'
OR COM_OrderItem.OrderItemSKUName LIKE #OrderItemSKUName + '%'
OR (#OrderItemDateFrom IS NULL OR COM_Order.OrderDate >=#OrderItemDateFrom)
OR (#OrderItemDateTo IS NULL OR COM_Order.OrderDate <=#OrderItemDateTo)
ORDER BY
COM_Order.OrderDate DESC
You should Try this.
OR (#OrderItemDateFrom IS NULL OR COM_Order.OrderDate >=#OrderItemDateFrom)
OR (#OrderItemDateTo IS NULL OR COM_Order.OrderDate <=#OrderItemDateTo)
Just Edit and try this condition..

SQL Procedure not showing any value

Below is the procedure I created. When I run the query individually I find records in the database but when I execute the procedure it is not fetching any records. where have I gone wrong?
ALTER PROCEDURE [dbo].[GetTransferList]
#date1 datetime='2015-01-01 00:00:00.000',
#date2 datetime='2017-01-01 00:00:00.000',
#shipto varchar(50)=''
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
-- Insert statements for procedure here
DECLARE
#sql NVARCHAR(MAX),
#paramlist NVARCHAR(4000),
#nl CHAR(2) = CHAR(13) + CHAR(10),
#ParamDefinition NVarchar(2000);
SET #sql = 'SELECT A.ItemDescription,A.PurchaseOrderID,A.QuantityReceived,A.Price,A.StoreID,
C.ItemType,C.BinLocation,
B.PONumber,B.ShipTo,B.StoreID
FROM [dbo].[PurchaseOrderEntry] A, [dbo].[PurchaseOrder] B,[dbo].[Item] C
WHERE A.PurchaseOrderID=B.ID AND A.ItemID=C.ID ';
IF (#date1 IS NOT NULL) AND (#date2 IS NOT NULL )
SET #sql += ' AND B.[RequiredDate] between #date1 AND #date2';
IF #shipto IS NOT NULL --!='ALL'
SET #sql += ' AND B.ShipTo = #shipto ';
SET #sql += ' GROUP BY C.BinLocation,A.Price,C.ItemType, B.ID ,A.ItemDescription,
A.PurchaseOrderID,A.QuantityReceived,A.StoreID,B.PONumber,B.ShipTo,B.StoreID'
Set #ParamDefinition =' #shipto varchar(50),
#date1 datetime,
#date2 datetime'
Execute sp_Executesql #sql,
#ParamDefinition,
#shipto,
#date1,
#date2
If ##ERROR <> 0 GoTo ErrorHandler
Set NoCount OFF
Return(0)
ErrorHandler:
Return(##ERROR)
END
the query fetching value is below,
SELECT A.ItemDescription,A.PurchaseOrderID,A.QuantityReceived,A.Price,A.StoreID,
C.ItemType,C.BinLocation,
B.PONumber,B.ShipTo,B.StoreID
FROM [dbo].[PurchaseOrderEntry] A, [dbo].[PurchaseOrder] B,[dbo].[Item] C
WHERE A.PurchaseOrderID=B.ID AND A.ItemID=C.ID
GROUP BY C.BinLocation,A.Price,C.ItemType, B.ID ,A.ItemDescription,
A.PurchaseOrderID,A.QuantityReceived,A.StoreID,B.PONumber,B.ShipTo,B.StoreID
I suspect your biggest problem is #shipto varchar(50)=''. In your proc you then test for null
So this line....
IF #shipto IS NOT NULL
Will not be true if you don't pass anything in it will equal an empty string (unless you actually pass a null value in)
Change this to:
#shipto varchar(50) = null
Then you can test for null as you are doing and this code
IF #shipto IS NOT NULL --!='ALL'
SET #sql += ' AND B.ShipTo = #shipto ';
Will be test true if you do not provide this parameter.
If you ever want to add AND B.ShipTo = '' just pass in a value of '' to #shipto parameter.
However, this bit doesn't need to be dynamic as this will do the same thing
b.shipto = Coalesce(#shipto, B.ShipTo)
The same goes for your date parameters. They will only ever be null if you actually pass in a null value.
As you have no aggregate functions such as SUM, MAX, COUNT etc you don't need the group by.
So my personal preference here would be....
ALTER PROCEDURE [dbo].[GetTransferList]
#date1 datetime=null,
#date2 datetime=null,
#shipto varchar(50) = null
--This will make your parameters optional. IE. You dont have to provide them when you call it
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
IF(#date1 IS NULL) SET #date1 = cast('1899-1-1' as datetime) --This could be anytime in the past thatis prior to any records you hold
IF(#date2 IS NULL) SET #date2 = cast('2100-1-1' as datetime) -- A date well in the future
--You could also set directly as the default values for the parameter but if you do you will have a problem if `null` is passed in.
SELECT A.ItemDescription,A.PurchaseOrderID,A.QuantityReceived,A.Price,A.StoreID,
C.ItemType,C.BinLocation,
B.PONumber,B.ShipTo,B.StoreID
FROM [dbo].[PurchaseOrderEntry] A, [dbo].[PurchaseOrder] B,[dbo].[Item] C
WHERE A.PurchaseOrderID=B.ID AND A.ItemID=C.ID AND B.[RequiredDate] between #date1 AND #date2
AND B.ShipTo = COALESCE(#shipto, b.ShipTo)
If ##ERROR <> 0 GoTo ErrorHandler
Set NoCount OFF
Return(0)
ErrorHandler:
Return(##ERROR)
END
Now you can provide one date (return all records up to a date or all records after a date) or two dates (return all records between dates) or not provide any to see them all. Same for #shipto.
I strongly believe that your date parameter is not matching with the available records, as the query that works fine out side the procedure does not have date condition.
The Date parameters in the proc is marked with default values
if the front end is C# (not sure about other front end code).
The date parameter will be passed with minimum date value or
Null value will be passed if you are not added date parameters in the front end code
But both the case date parameter will have some date values.
Assign the date values to your working query as condition and check.
As asked by Mr.Fred, grouping in your query may not required unless if you are going to have any aggregated fields.

Query uses Index Scan instead of Seek when OR #param = 1 added to WHERE clause

I'm going through stored procedures to make them sargable and I noticed something unexpected about how the index was used.
There's a non-clustered index on DateColumn, and a clustered index on the table (not directly referenced in the query).
While the following uses an index seek on the non-clustered index that has DateColumn as an index column:
DECLARE #timestamp as datetime
SET #timestamp = '2014-01-01'
SELECT column1, column2 FROM Table WHERE DateColumn > #timestamp
However the following uses an index scan:
DECLARE #timestamp as datetime
DECLARE #flag as bit
SET #timestamp = '2014-01-01'
SET #flag = 0
SELECT column1, column2 FROM Table WHERE (DateColumn > #timestamp) OR (#flag = 1)
I put the brackets in just in case, but of course it made no difference.
Because the #flag = 1 has nothing to do with the table, I was expecting a seek in both cases. Out of interest if I change it to 0 = 1 it uses index seek again. The #flag value is a parameter for the procedure that tells the query to return all records, so not something I can hard code in reality.
Is there a way to make this use a seek instead of a scan? The only option I can think of is the following, however in reality the queries are much more complex, so duplication like this hurts readability and maintainability:
DECLARE #timestamp as datetime
DECLARE #flag as bit
SET #timestamp = '2014-01-01'
SET #flag = 0
IF #flag = 1
BEGIN
SELECT column1, column2 FROM Table
END
ELSE
BEGIN
SELECT column1, column2 FROM Table WHERE DateColumn > #timestamp
END
Try with dynamic SQL like this.
DECLARE #flag BIT,
#query NVARCHAR(500)
SET #flag=0
SET #query='
SELECT <columnlist>
FROM <tablename>
WHERE columnname = value
or 1=' + CONVERT(NVARCHAR(1), #flag)
EXEC Sp_executesql
#query
Your dynamic solution is actually better because you won't get caught out when you pass in #flag=1 the first time and that's what you get for all subsequent calls. As #RaduGheorghiu says, a scan is better than a seek in these cases.
If it were me I would have 2 procedures, one for "get everything" and one for "get for date". Two procedures, two usages, two query plans. If the repetition bothers you, you can introduce a view.
Just for completeness I'm going to post an option that I just realised works in my specific situation. This is probably what I'm going to use due to simplicity, however this probably won't work for 99.9% of cases, so I don't consider it a better answer than the dynamic SQL.
declare #flag as int
declare #date as datetime
set #flag = 1
set #date = '2015-08-11 09:12:08.553'
set #date = (select case #flag when 1 then '1753-01-01' else #date end)
select Column1, Column2
from Table_1
where DateColumn > #date
The above works because the DateColumn stores the modified date for the record (I'm returning deltas). It has a default value of getdate() and is set to getdate() on updates. This means in this specific case I know that for a value of #date = '1753-01-01' all records will be returned.

How to pass optional date parameters in SQL Server

I think this should be simple, but I keep running into problems.
I simply want to return all data from a table that lies between a date range. But I want the date range to be optional.
ALTER PROCEDURE [dbo].[sp_ExistingPlacements_Get]
#DateFrom DATE = NULL,
#DateTo DATE = NULL
AS
BEGIN
SET NOCOUNT ON;
SELECT *
FROM tblExistingPlacements
WHERE
CreatedDT > COALESCE(NULLIF(#DateFrom, ''), #DateFrom)
AND
CreatedDT < COALESCE(NULLIF(#DateTo, GETDATE()), #DateTo)
END
So, if no dates are passed in, we return the entire table.
If only the start date (DateFrom) is passed, we return rows > the start date and all the up to the current date.
If only the End date (DateTo) is passed then return all the rows < the End Date
And of course if both dates are passed, return all the rows inbetween those dates.
Am I going the wrong route with COALESCE ?
Use ISNULL(#parameter) OR (--your condition--) instead of COALESCE:
BEGIN
SET NOCOUNT ON;
SELECT *
FROM tblExistingPlacements
WHERE
((#DateFrom IS NULL) OR CreatedDT > #DateFrom)
AND
((#DateTo IS NULL) OR CreatedDT < #DateTo)
END
If parameter was not provided ISNULL return TRUE, so second part of OR won't matter.
Do not do this. SQL will have to create one execution plan that works in any situation. As unituitive as it sounds, is better to have three separate queries:
ALTER PROCEDURE [dbo].[sp_ExistingPlacements_Get]
#DateFrom DATE = NULL,
#DateTo DATE = NULL
AS
BEGIN
SET NOCOUNT ON;
IF (#DateFrom IS NULL and #DateTo IS NULL)
SELECT field, field, field
FROM tblExistingPlacements
WHERE CreatedDT < GETUTCDATE();
ELSE IF (#DateFrom IS NULL)
SELECT field, field, field
FROM tblExistingPlacements
WHERE CreatedDT < #dateTo;
ELSE IF (#DateTo IS NULL)
SELECT field, field, field
FROM tblExistingPlacements
WHERE CreatedDT BETWEEN #DateFrom AND GETUTCDATE();
ELSE
SELECT field, field, field
FROM tblExistingPlacements
WHERE CreatedDT BETWEEN #DateFrom AND #DateTo;
END
The wisdom of returning the entire table when no parameters are specified is highly questionable, but that is not the point. Besides:
never use * in queries, always specify the projection list explicitly
always use UTC times in the database
For a thorough discussion of this topic see Dynamic Search Conditions in T-SQL.

How to compare multiple values in one column against a delimited string in a stored procedure

Ok, first question, and you guys scare me with how much you know, so please be gentle...
I am trying to pass in a delimited string and convert it to an array in a stored procedure and then use the array to check against values in a column. Here's the catch. I'm trying to take the preexisting table, that checks for one association and expand it to allow for multiple associations.
So the column annAssociations might have three ids, 4,16,32, but I need to check if it belongs to the groupIds being queried, 6,12,32. Since one of the values matched, it should return that row.
Here is the procedure as it exists.
CREATE PROCEDURE [dbo].[sp_annList]
-- Date Range of Announcements.
#dateBegin datetime,
#dateEnd datetime,
-- Announcement type and associations.
#annType varchar(50),
#annAssociation varchar(255)
AS
BEGIN
-- Set the SELECT statement for the announcements.
SET NOCOUNT ON;
-- See if this should be a limited query
IF #annAssociation <> ''
Begin
SELECT *
FROM announcements
WHERE (date1 <= #dateEnd AND date2 >= #dateBegin) -- See if the announcement falls in the date range.
AND annType = #annType -- See if the announcement is the right type.
AND annAssociations LIKE (select SplitText from dbo.fnSplit(#annAssociation, ','))
ORDER BY title
END
Else
Begin
SELECT *
FROM announcements
WHERE (date1 <= #dateEnd AND date2 >= #dateBegin)
AND annType = #annType
ORDER BY title
End
END
And here is the method I'm using to convert the delimited string and store it in a temporary table.
CREATE Function [dbo].[fnSplit](#text text, #delimitor nchar(1))
RETURNS
#table TABLE
(
[Index] int Identity(0,1),
[SplitText] varchar(10)
)
AS
BEGIN
declare #current varchar(10)
declare #endIndex int
declare #textlength int
declare #startIndex int
set #startIndex = 1
if(#text is not null)
begin
set #textLength = datalength(#text)
while(1=1)
begin
set #endIndex = charindex(#delimitor, #text, #startIndex)
if(#endIndex != 0)
begin
set #current = substring(#text,#startIndex, #endIndex - #StartIndex)
Insert Into #table ([SplitText]) values(#current)
set #startIndex = #endIndex + 1
end
else
begin
set #current = substring(#text, #startIndex, datalength(#text)-#startIndex+1)
Insert Into #table ([SplitText]) values(#current)
break
end
end
end
return
END
Sorry for the long question. I just wanted to get all the info out there. I've been researching for days, and I either don't know where to look or am missing something obvious.
You probably won't get much better performance than this approach (well you might see better performance with a CLR split function but at 3 or 4 items you won't see much difference):
SELECT *
FROM announcements AS a
WHERE ...
AND EXISTS (SELECT 1 FROM dbo.fnSplit(#annAssociation) AS n
WHERE ',' + a.annList + ',' LIKE '%,' + n.SplitText + ',%');
The key here is that you only need to split up one of the lists.
You really should stop storing multiple values in the annAssocations column. Each id is a separate piece of data and should be stored separately (in addition to better conforming to normalization, it will make queries like this simpler).

Resources