Extending SQL Server full-text index to search through foreign keys - sql-server

I know that a SQL Server full text index can not index more than one table. But, I have relationships in tables that I would like to implement full text indexes on.
Take the 3 tables below...
Vehicle
Veh_ID - int (Primary Key)
FK_Atr_VehicleColor - int
Veh_Make - nvarchar(20)
Veh_Model - nvarchar(50)
Veh_LicensePlate - nvarchar(10)
Attributes
Atr_ID - int (Primary Key)
FK_Aty_ID - int
Atr_Name - nvarchar(50)
AttributeTypes
Aty_ID - int (Primary key)
Aty_Name - nvarchar(50)
The Attributes and AttributeTypes tables hold values that can be used in drop down lists throughout the application being built. For example, Attribute Type of "Vehicle Color" with Attributes of "Black", "Blue", "Red", etc...
Ok, so the problem comes when a user is trying to search for a "Blue Ford Mustang". So what is the best solution considering that tables like Vehicle will get rather large?
Do I create another field in the "Vehicle" table that is "Veh Color" that holds the text value of what is selected in the drop down in addition to "FK Atr VehicleColor"?
Or, do I drop "FK Atr VehicleColor" altogether and add "Veh Color"? I can use text value of "Veh Color" to match against "Atr Name" when the drop down is populated in an update form. With this approach I will have to handle if Attributes are dropped from the database.
-- Note: could not use underscore outside of code view as everything between two underscores is italicized.

I believe it's a common practice to have separate denormalized table specifically for full-text indexing. This table is then updated by triggers or, as it was in our case, by SQL Server's scheduled task.
This was SQL Server 2000. In SQL Server you can have an indexed view with full-text index: http://msdn.microsoft.com/en-us/library/ms187317.aspx. But note that there are many restrictions on indexed views; for instance, you can't index a view that uses OUTER join.

You can create a view that pulls in whatever data you need, then apply the full-text index to the view. The view needs to be created with the 'WITH SCHEMABINDING' option, and needs to have a UNIQUE index.
CREATE VIEW VehicleSearch
WITH SCHEMABINDING
AS
SELECT
v.Veh_ID,
v.Veh_Make,
v.Veh_Model,
v.Veh_LicensePlate,
a.Atr_Name as Veh_Color
FROM
Vehicle v
INNER JOIN
Attributes a on a.Atr_ID = v.FK_Atr_VehicleColor
GO
CREATE UNIQUE CLUSTERED INDEX IX_VehicleSearch_Veh_ID ON VehicleSearch (
Veh_ID ASC
) ON [PRIMARY]
GO
CREATE FULLTEXT INDEX ON VehicleSearch (
Veh_Make LANGUAGE [English],
Veh_Model LANGUAGE [English],
Veh_Color LANGUAGE [English]
)
KEY INDEX IX_VehicleSearch_Veh_ID ON [YourFullTextCatalog]
WITH CHANGE_TRACKING AUTO
GO

As I understand it (I've used SQL Server a lot but never full-text indexing) SQL Server 2005 allows you to create full text indexes against a view. So you could create a view on
SELECT
Vehicle.VehID, ..., Color.Atr_Name AS ColorName
FROM
Vehicle
LEFT OUTER JOIN Attributes AS Color ON (Vehicle.FK_Atr_VehicleColor = Attributes.Atr_Id)
and then create your full-text index across this view, including 'ColorName' in the index.

Related

Any way to create function/method in SQL for combining columns in the SELECT?

Wrote a great simple function in SQL that apparently is not usable (or advisable) in my SELECT statement.
Have some intelligence behind combining Combinations of Company Name and Contact Name in our select and I find it's repeating across several views. Being a programmer, of course the right thing is to encapsulate that functionality for reuse across all views I'm created. But alas, from my searching it does not appear possible or recommended, at least not with UDFs.
The question: Is there any way to select the return value of a method/function/chunk of reusable code where I pass it the value of columns for each row... Or do I truly have to copy/paste the logic into each select statement?
SELECT formatName(company, contact, ' - ') as Name FROM company join contacts...
I know I can do this on the client (eventually), but client changes are not in scope for this phase of the project.
I guess I typed more in this question than just cutting and pasting a CASE statement into each view, but reuse is ingrained if me of course. :)
A better performing and DRY method to accomplish this is with a computed column.
A computed column is a virtual column that is not physically stored in
the table, unless the column is marked PERSISTED. A computed column
expression can use data from other columns to calculate a value for
the column to which it belongs. You can specify an expression for a
computed column in SQL Server 2017 by using SQL Server Management
Studio or Transact-SQL.
You can make this column persisted as well
PERSISTED Specifies that the Database Engine will physically store the
computed values in the table, and update the values when any other
columns on which the computed column depends are updated. Marking a
computed column as PERSISTED allows an index to be created on a
computed column that is deterministic, but not precise. For more
information, see Indexes on Computed Columns. Any computed columns
used as partitioning columns of a partitioned table must be explicitly
marked PERSISTED. computed_column_expression must be deterministic
when PERSISTED is specified.
alter table company add FullName as (FirstName + '-' + LastName) persisted;
Then, you could just add this column in your SELECT can can even query against it, if it's persisted.
What you can do is create a view that behaves like a table. Meaning it would have the performance of a table, can have indexes added etc. This view can have any of the columns of the underlying base table plus you can add calculated columns, such as [name]. This is accomplished by adding WITH SCHEMABINDING when creating the view. This view can then be used in lieu of the base table in all of your queries.
Here is an example.
The underlying base table with data:
CREATE TABLE dbo.company (
companyid int IDENTITY(1,1) NOT NULL,
company varchar(50) NULL,
contact varchar(50) NULL,
CONSTRAINT PK_company PRIMARY KEY CLUSTERED (companyid ASC)
) ON FG1
The view containing WITH SCHEMABINDING:
CREATE view dbo.VW_company WITH SCHEMABINDING AS
SELECT companyid,
CASE WHEN RTRIM(ISNULL(company,'')) <> '' AND RTRIM(ISNULL(contact,'')) <> '' THEN company +' - '+ contact
WHEN RTRIM(ISNULL(company,'')) <> '' THEN company
WHEN RTRIM(ISNULL(contact,'')) <> '' THEN contact
ELSE '' END as [Name]
FROM dbo.company
This view can now be used everywhere the table is used, without a performance hit. Furthermore, the calculated column [Name] can actually have an index added to it! That's something you cannot do with a function.

Will using an indexed view improve performance of SELECT COUNT queries?

I have a table with that will grow to several million rows over some years. As part of my web application, I have to query the count on a subset of this table whenever a user accesses a particular page. Someone with an architecty hat has said that they have a performance concern with that. Assuming they are correct, will adding an indexed view address this issue?
Sql that I want to be fast:
SELECT COUNT(*) FROM [dbo].[Txxx] WHERE SomeName = 'ZZZZ'
OR
SELECT COUNT_BIG(*) FROM [dbo].[Txxx] WHERE SomeName = 'ZZZZ'
Table:
CREATE TABLE [dbo].[Txxx](
[Id] [uniqueidentifier] ROWGUIDCOL NOT NULL,
[SomeName] [nvarchar](50) NOT NULL,
[SomeGuid] [uniqueidentifier] NOT NULL
CONSTRAINT [PK_Txxx] PRIMARY KEY CLUSTERED
(
[Id] ASC
)
View:
CREATE view dbo.Vxxx
WITH SCHEMABINDING
AS
SELECT SomeName, COUNT_BIG(*) AS UsedCount
FROM dbo.Txxx
GROUP BY SomeName
Index:
CREATE UNIQUE CLUSTERED INDEX [IV_COUNT] ON [dbo].[Vxxx]
(
[SomeName] ASC
)
Yes, but only Enterprise Edition will consider the indexed view during query compilation. To leverage the index on non-EE you need to select directly from the view and use the NOEXPAND hint:
NOEXPAND applies only to indexed views. An indexed view is a view with
a unique clustered index created on it. If a query contains references
to columns that are present both in an indexed view and base tables,
and the query optimizer determines that using the indexed view
provides the best method for executing the query, the query optimizer
uses the index on the view. This function is called indexed view
matching. Automatic use of indexed view by query optimizer is
supported only in specific editions of SQL Server.
Be warned that a indexed view like this will create write contention, because any update will lock and entire SomeName scope: only one transaction at a time will be able to insert, delete or update any row with SomeName = 'ZZZZ'.
Yes, that indexed view will definitely improve the performance of that particular query (assuming Enterprise Edition - Remus explains how to utilize it if you're not on Enterprise).
However, it isn't "free" - the index will need to be maintained for all DML operations to dbo.Txxx, will occupy space (though considerably less than the base table, in comparison), and will be subject to issues that also affect normal tables - such as fragmentation and (likely to a lesser extent in this case) page splits.

How do I get MS LightSwitch to recognize my View?

I've created a View from a table in another database. I have dbo rights to the databases so viewing and updating is not a problem. This particular View did not have an "id" column. So I added one to the View by using ROW_NUMBER. Now I had a problem with a table, in the same database, not showing up in LightSwitch but that was solved by changing the id column to be NOT NULL. I haven't done any real manipulation in LightSwitch. I'm still in the Import Your Data Source stage (ie. very beginning).
This View, in LightSwitch, is going to be read-only. No updating or deleting. From what I've read, LightSwitch needs a way to determine the PK of a Table or View. It either reads it from the schema (column set as a PK) or finds a column set as NOT NULL and uses that as the PK. Well I can't seem to do either of those things in SQL Server or LightSwitch, so I am stuck as to how to get LightSwitch to "see" my View.
for lightswitch to see your view you must have a primary key on a column of the table your are selecting from.
Example:
create table tbl_test
(
id int identity primary key not null,
value varchar(50)
)
create view vw_test
as
select *
from tbl_test
note:sometimes when you edit the primary key column in the view select statement it may cause lightswitch to not see it
Example:
create view vw_test
select cast(id as varchar(50) id,...
lightswitch would not see the table
Hope this was helpful! :)
What I do in this case is create a view with an ID column equal to the row number. Ensure the column you're basing the ID on is not null using the isnull() or coalesce() functions.
Example:
create view as
select distinct ID = row_number() over (order by isnull(Name,'')),
Name = isnull(Name,'')
from My_Table

Is it possible to create an indexed view from Xml Data in Sql Server 2008?

I see from the 2005 documentation that you cannot create an indexed view from an Xml column.
Is this possible in 2008 or 2008R2? I can't find any documentation saying that it is something that was added but am looking for confirmation and I don't have handy access to a 2008 environment at the moment.
EDIT
My motivation behind this is that the amount of Xml is growing to the point where SSRS reports which aggregate data from the Xml are becoming slow.
Depending on your need, what you could do is this:
create a set of stored functions that extract certain bits of key information from your XML (function receives XML as input, extracts the info using XPath/XQuery, returns a VARCHAR or INT or something value)
CREATE FUNCTION dbo.SomeFunction(#Input XML)
RETURNS VARCHAR(20)
WITH SCHEMABINDING
AS BEGIN
......
END
add those key bits to your base table as computed columns that reference those functions, with the PERSISTED keyword:
ALTER TABLE dbo.YourTable
ADD ComputedColumns1 AS dbo.SomeFunction(XmlColumn) PERSISTED
create your view on the table and those computed columns, with schemabinding:
CREATE VIEW vYourView
WITH SCHEMABINDING
AS
SELECT (list of columns)
FROM dbo.YourTable
create a unique, clustered index on that view - unless you've violated any of the requirements of the indexed view, this should work just fine:
CREATE UNIQUE CLUSTERED INDEX CIX_YourView ON dbo.vYourView(.....)
This works fine if you need to extract a small number of key bits of information from an XML column - it's definitely not recommended for lots of XML elements / values.
I don't believe this is possible. Without a better explanation of what you are trying to do, one suggestion I can offer is to pull the XML apart before insert (perhaps using an instead of trigger, or doing this shredding at the application layer) and storing the part(s) you want to use for the indexed view in separate non-XML columns.

How do I create a unique constraint that also allows nulls?

I want to have a unique constraint on a column which I am going to populate with GUIDs. However, my data contains null values for this columns. How do I create the constraint that allows multiple null values?
Here's an example scenario. Consider this schema:
CREATE TABLE People (
Id INT CONSTRAINT PK_MyTable PRIMARY KEY IDENTITY,
Name NVARCHAR(250) NOT NULL,
LibraryCardId UNIQUEIDENTIFIER NULL,
CONSTRAINT UQ_People_LibraryCardId UNIQUE (LibraryCardId)
)
Then see this code for what I'm trying to achieve:
-- This works fine:
INSERT INTO People (Name, LibraryCardId)
VALUES ('John Doe', 'AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA');
-- This also works fine, obviously:
INSERT INTO People (Name, LibraryCardId)
VALUES ('Marie Doe', 'BBBBBBBB-BBBB-BBBB-BBBB-BBBBBBBBBBBB');
-- This would *correctly* fail:
--INSERT INTO People (Name, LibraryCardId)
--VALUES ('John Doe the Second', 'AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA');
-- This works fine this one first time:
INSERT INTO People (Name, LibraryCardId)
VALUES ('Richard Roe', NULL);
-- THE PROBLEM: This fails even though I'd like to be able to do this:
INSERT INTO People (Name, LibraryCardId)
VALUES ('Marcus Roe', NULL);
The final statement fails with a message:
Violation of UNIQUE KEY constraint 'UQ_People_LibraryCardId'. Cannot insert duplicate key in object 'dbo.People'.
How can I change my schema and/or uniqueness constraint so that it allows multiple NULL values, while still checking for uniqueness on actual data?
What you're looking for is indeed part of the ANSI standards SQL:92, SQL:1999 and SQL:2003, ie a UNIQUE constraint must disallow duplicate non-NULL values but accept multiple NULL values.
In the Microsoft world of SQL Server however, a single NULL is allowed but multiple NULLs are not...
In SQL Server 2008, you can define a unique filtered index based on a predicate that excludes NULLs:
CREATE UNIQUE NONCLUSTERED INDEX idx_yourcolumn_notnull
ON YourTable(yourcolumn)
WHERE yourcolumn IS NOT NULL;
In earlier versions, you can resort to VIEWS with a NOT NULL predicate to enforce the constraint.
SQL Server 2008 +
You can create a unique index that accept multiple NULLs with a WHERE clause. See the answer below.
Prior to SQL Server 2008
You cannot create a UNIQUE constraint and allow NULLs. You need set a default value of NEWID().
Update the existing values to NEWID() where NULL before creating the UNIQUE constraint.
SQL Server 2008 And Up
Just filter a unique index:
CREATE UNIQUE NONCLUSTERED INDEX UQ_Party_SamAccountName
ON dbo.Party(SamAccountName)
WHERE SamAccountName IS NOT NULL;
In Lower Versions, A Materialized View Is Still Not Required
For SQL Server 2005 and earlier, you can do it without a view. I just added a unique constraint like you're asking for to one of my tables. Given that I want uniqueness in column SamAccountName, but I want to allow multiple NULLs, I used a materialized column rather than a materialized view:
ALTER TABLE dbo.Party ADD SamAccountNameUnique
AS (Coalesce(SamAccountName, Convert(varchar(11), PartyID)))
ALTER TABLE dbo.Party ADD CONSTRAINT UQ_Party_SamAccountName
UNIQUE (SamAccountNameUnique)
You simply have to put something in the computed column that will be guaranteed unique across the whole table when the actual desired unique column is NULL. In this case, PartyID is an identity column and being numeric will never match any SamAccountName, so it worked for me. You can try your own method—be sure you understand the domain of your data so that there is no possibility of intersection with real data. That could be as simple as prepending a differentiator character like this:
Coalesce('n' + SamAccountName, 'p' + Convert(varchar(11), PartyID))
Even if PartyID became non-numeric someday and could coincide with a SamAccountName, now it won't matter.
Note that the presence of an index including the computed column implicitly causes each expression result to be saved to disk with the other data in the table, which DOES take additional disk space.
Note that if you don't want an index, you can still save CPU by making the expression be precalculated to disk by adding the keyword PERSISTED to the end of the column expression definition.
In SQL Server 2008 and up, definitely use the filtered solution instead if you possibly can!
Controversy
Please note that some database professionals will see this as a case of "surrogate NULLs", which definitely have problems (mostly due to issues around trying to determine when something is a real value or a surrogate value for missing data; there can also be issues with the number of non-NULL surrogate values multiplying like crazy).
However, I believe this case is different. The computed column I'm adding will never be used to determine anything. It has no meaning of itself, and encodes no information that isn't already found separately in other, properly defined columns. It should never be selected or used.
So, my story is that this is not a surrogate NULL, and I'm sticking to it! Since we don't actually want the non-NULL value for any purpose other than to trick the UNIQUE index to ignore NULLs, our use case has none of the problems that arise with normal surrogate NULL creation.
All that said, I have no problem with using an indexed view instead—but it brings some issues with it such as the requirement of using SCHEMABINDING. Have fun adding a new column to your base table (you'll at minimum have to drop the index, and then drop the view or alter the view to not be schema bound). See the full (long) list of requirements for creating an indexed view in SQL Server (2005) (also later versions), (2000).
Update
If your column is numeric, there may be the challenge of ensuring that the unique constraint using Coalesce does not result in collisions. In that case, there are some options. One might be to use a negative number, to put the "surrogate NULLs" only in the negative range, and the "real values" only in the positive range. Alternately, the following pattern could be used. In table Issue (where IssueID is the PRIMARY KEY), there may or may not be a TicketID, but if there is one, it must be unique.
ALTER TABLE dbo.Issue ADD TicketUnique
AS (CASE WHEN TicketID IS NULL THEN IssueID END);
ALTER TABLE dbo.Issue ADD CONSTRAINT UQ_Issue_Ticket_AllowNull
UNIQUE (TicketID, TicketUnique);
If IssueID 1 has ticket 123, the UNIQUE constraint will be on values (123, NULL). If IssueID 2 has no ticket, it will be on (NULL, 2). Some thought will show that this constraint cannot be duplicated for any row in the table, and still allows multiple NULLs.
For people who are using Microsoft SQL Server Manager and want to create a Unique but Nullable index you can create your unique index as you normally would then in your Index Properties for your new index, select "Filter" from the left hand panel, then enter your filter (which is your where clause). It should read something like this:
([YourColumnName] IS NOT NULL)
This works with MSSQL 2012
When I applied the unique index below:
CREATE UNIQUE NONCLUSTERED INDEX idx_badgeid_notnull
ON employee(badgeid)
WHERE badgeid IS NOT NULL;
every non null update and insert failed with the error below:
UPDATE failed because the following SET options have incorrect settings: 'ARITHABORT'.
I found this on MSDN
SET ARITHABORT must be ON when you are creating or changing indexes on computed columns or indexed views. If SET ARITHABORT is OFF, CREATE, UPDATE, INSERT, and DELETE statements on tables with indexes on computed columns or indexed views will fail.
So to get this to work correctly I did this
Right click [Database]-->Properties-->Options-->Other
Options-->Misscellaneous-->Arithmetic Abort Enabled -->true
I believe it is possible to set this option in code using
ALTER DATABASE "DBNAME" SET ARITHABORT ON
but i have not tested this
It can be done in the designer as well
Right click on the Index > Properties to get this window
Create a view that selects only non-NULL columns and create the UNIQUE INDEX on the view:
CREATE VIEW myview
AS
SELECT *
FROM mytable
WHERE mycolumn IS NOT NULL
CREATE UNIQUE INDEX ux_myview_mycolumn ON myview (mycolumn)
Note that you'll need to perform INSERT's and UPDATE's on the view instead of table.
You may do it with an INSTEAD OF trigger:
CREATE TRIGGER trg_mytable_insert ON mytable
INSTEAD OF INSERT
AS
BEGIN
INSERT
INTO myview
SELECT *
FROM inserted
END
It is possible to create a unique constraint on a Clustered Indexed View
You can create the View like this:
CREATE VIEW dbo.VIEW_OfYourTable WITH SCHEMABINDING AS
SELECT YourUniqueColumnWithNullValues FROM dbo.YourTable
WHERE YourUniqueColumnWithNullValues IS NOT NULL;
and the unique constraint like this:
CREATE UNIQUE CLUSTERED INDEX UIX_VIEW_OFYOURTABLE
ON dbo.VIEW_OfYourTable(YourUniqueColumnWithNullValues)
In my experience - if you're thinking a column needs to allow NULLs but also needs to be UNIQUE for values where they exist, you may be modelling the data incorrectly. This often suggests you're creating a separate sub-entity within the same table as a different entity. It probably makes more sense to have this entity in a second table.
In the provided example, I would put LibraryCardId in a separate LibraryCards table with a unique not-null foreign key to the People table:
CREATE TABLE People (
Id INT CONSTRAINT PK_MyTable PRIMARY KEY IDENTITY,
Name NVARCHAR(250) NOT NULL,
)
CREATE TABLE LibraryCards (
LibraryCardId UNIQUEIDENTIFIER CONSTRAINT PK_LibraryCards PRIMARY KEY,
PersonId INT NOT NULL
CONSTRAINT UQ_LibraryCardId_PersonId UNIQUE (PersonId),
FOREIGN KEY (PersonId) REFERENCES People(id)
)
This way you don't need to bother with a column being both unique and nullable. If a person doesn't have a library card, they just won't have a record in the library cards table. Also, if there are additional attributes about the library card (perhaps Expiration Date or something), you now have a logical place to put those fields.
Maybe consider an "INSTEAD OF" trigger and do the check yourself? With a non-clustered (non-unique) index on the column to enable the lookup.
As stated before, SQL Server doesn't implement the ANSI standard when it comes to UNIQUE CONSTRAINT. There is a ticket on Microsoft Connect for this since 2007. As suggested there and here the best options as of today are to use a filtered index as stated in another answer or a computed column, e.g.:
CREATE TABLE [Orders] (
[OrderId] INT IDENTITY(1,1) NOT NULL,
[TrackingId] varchar(11) NULL,
...
[ComputedUniqueTrackingId] AS (
CASE WHEN [TrackingId] IS NULL
THEN '#' + cast([OrderId] as varchar(12))
ELSE [TrackingId_Unique] END
),
CONSTRAINT [UQ_TrackingId] UNIQUE ([ComputedUniqueTrackingId])
)
You can create an INSTEAD OF trigger to check for specific conditions and error if they are met. Creating an index can be costly on larger tables.
Here's an example:
CREATE TRIGGER PONY.trg_pony_unique_name ON PONY.tbl_pony
INSTEAD OF INSERT, UPDATE
AS
BEGIN
IF EXISTS(
SELECT TOP (1) 1
FROM inserted i
GROUP BY i.pony_name
HAVING COUNT(1) > 1
)
OR EXISTS(
SELECT TOP (1) 1
FROM PONY.tbl_pony t
INNER JOIN inserted i
ON i.pony_name = t.pony_name
)
THROW 911911, 'A pony must have a name as unique as s/he is. --PAS', 16;
ELSE
INSERT INTO PONY.tbl_pony (pony_name, stable_id, pet_human_id)
SELECT pony_name, stable_id, pet_human_id
FROM inserted
END
You can't do this with a UNIQUE constraint, but you can do this in a trigger.
CREATE TRIGGER [dbo].[OnInsertMyTableTrigger]
ON [dbo].[MyTable]
INSTEAD OF INSERT
AS
BEGIN
SET NOCOUNT ON;
DECLARE #Column1 INT;
DECLARE #Column2 INT; -- allow nulls on this column
SELECT #Column1=Column1, #Column2=Column2 FROM inserted;
-- Check if an existing record already exists, if not allow the insert.
IF NOT EXISTS(SELECT * FROM dbo.MyTable WHERE Column1=#Column1 AND Column2=#Column2 #Column2 IS NOT NULL)
BEGIN
INSERT INTO dbo.MyTable (Column1, Column2)
SELECT #Column2, #Column2;
END
ELSE
BEGIN
RAISERROR('The unique constraint applies on Column1 %d, AND Column2 %d, unless Column2 is NULL.', 16, 1, #Column1, #Column2);
ROLLBACK TRANSACTION;
END
END
CREATE UNIQUE NONCLUSTERED INDEX [UIX_COLUMN_NAME]
ON [dbo].[Employee]([Username] ASC) WHERE ([Username] IS NOT NULL)
WITH (ALLOW_PAGE_LOCKS = ON, ALLOW_ROW_LOCKS = ON, PAD_INDEX = OFF, SORT_IN_TEMPDB = OFF,
DROP_EXISTING = OFF, IGNORE_DUP_KEY = OFF, STATISTICS_NORECOMPUTE = OFF, ONLINE = OFF,
MAXDOP = 0) ON [PRIMARY];
this code if u make a register form with textBox and use insert and ur textBox is empty and u click on submit button .
CREATE UNIQUE NONCLUSTERED INDEX [IX_tableName_Column]
ON [dbo].[tableName]([columnName] ASC) WHERE [columnName] !=`''`;

Resources