Strange Query Plan for SQL Query - Clustered Index Seek - sql

I've got a really strange issue with a Query plan generated for a very simple SQL query. The query is searching a full text index, and returning the count of records.
For some reason, this SQL query is producing a Non Clustered Scan on an index, which I don't believe it is optimal to do. I believe that for the count, as the Primary Key is in the full text index, a clustered seek would be all that is required.
Would anyone have any suggestions on why such a query plan is being used?
Odd thing is, with slight different variants of the SQL, sometimes it uses the Clustered Index (which is really fast), sometimes it uses the Non Clustered Seek.
Here's the query:
EXEC sp_executesql N'SELECT count(T.[ID])
FROM [dbo].[Item] AS T
WHERE CONTAINS (
(
T.[Description]
)
,#P0M
)
'
,N'#P0M nvarchar(4000)'
,#P0M = N'"mouse*"'
Here's the Query Plan: https://i.stack.imgur.com/1XJcf.png As you can see, the Non Clustered Seek costs 51%, plus 8% Parallelism and 15% Hash match on the Bitmap.
The table has a lot of data. Over 3 million records.
Many thanks
Edit:
Here's the plan: https://www.brentozar.com/pastetheplan/?id=HyiABrg1K
Here's the table definition:
CREATE TABLE [dbo].[Item](
[ID] [uniqueidentifier] NOT NULL,
[Description] [nvarchar](500) NOT NULL,
[Manufacturer] [uniqueidentifier] NOT NULL,
[Manufacturer Name] [nvarchar](100) NULL,
[Manufacturer Item No.] [nvarchar](50) NOT NULL,
[BC Item No.] [varchar](20) NULL,
[CRM Item No.] [varchar](50) NULL,
[Category] [uniqueidentifier] NULL,
[Version No.] [varchar](50) NULL,
[Blocked] [bit] NULL,
[UNSPSC Code] [int] NULL,
[Barcode] [char](13) NULL,
[Last Update Date Time] [datetime] NULL,
[Weight (kg)] [decimal](18, 3) NULL,
[RRP] [decimal](18, 2) NULL,
[RRP Currency] [uniqueidentifier] NULL,
[timestamp] [timestamp] NOT NULL,
CONSTRAINT [PK_Item] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 99, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
There's also lots of foreign keys.
Here's the only other index:
CREATE UNIQUE NONCLUSTERED INDEX [Manufacturer Part No] ON [dbo].[Item]
(
[Manufacturer] ASC,
[Manufacturer Item No.] ASC
)
INCLUDE([ID]) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
GO

After lots of digging, I found this Query Optimizer Gone Wild - Full-Text Search Query Plans.
Looks like it is by design. A ContainsTable query doesnt need to join in order to get the row count.
the CONTAINS function must also scan a index on the source table to get the count

Related

Why columns value change into NULL when try to update only one column?

This is my table which I created in sql server...
CREATE TABLE [dbo].[Addresses_Table](
[AddressID] [int] IDENTITY(1,1) NOT NULL,
[BuildingName] [varchar](300) NULL,
[UnitNumber] [nvarchar](50) NULL,
[StreetNumber] [varchar](20) NULL,
[StreetName] [varchar](200) NULL,
[Suburb] [varchar](100) NULL,
[POBox] [varchar](20) NULL,
CONSTRAINT [PK_Addresses_Table] PRIMARY KEY CLUSTERED (
[AddressID] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = OFF, ALLOW_PAGE_LOCKS = OFF) ON [PRIMARY] ) ON [PRIMARY]
INSERT INTO [dbo].[Addresses_Table] (BuildingName,UnitNumber,StreetNumber, StreetName, Suburb) VALUES ('mybuilding', '101', '12','Street 1', 'TEST12')
When I try to update 'POBox' column value into '1234' (any value),
BuildingName, UnitNumber, StreetNumber, StreetName turn into NULL values.
I have attached my sql update query and results of it.
Please help me out to solve this problem....
It seems there is a trigger on the table that sets StreetName to NULL after the update. This is evidenced by the SSMS output that shows 2 rowcount messages, one when the row is updated directly and the other when the same row is updated by the trigger.
The trigger makes sense from a data perspective since StreetName does not apply to a PO box address.

Unique constraint on another column if not null

I want to create this table with the following constrains, is it possible to do with SQL Server Management Studio?
Id
ProductId [Nullable column]
CompanyId [Nullable column]
Username [unique for every productId - IF DeletedAt is not NULL]
Identifier [Nullable column] [unique for every companyId - IF DeletedAt is not NULL]
DeletedAt [Nullable column]
Update:
My table create query is the following:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[User](
[Id] [int] IDENTITY(1,1) NOT NULL,
[ProductId] [int] NULL,
[CompanyId] [int] NULL,
[Username] [nvarchar](max) NOT NULL,
[Identifier] [nvarchar](max) NULL,
[DeletedAt] [datetime2](7) NULL,
CONSTRAINT [PK_User] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF,
ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
You'll want to use a unique filtered index. Here you go for the table:
/****** Object: Table [dbo].[the_table] Script Date: 7/26/2018 4:04:00 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[the_table](
[id] [int] NOT NULL,
[productid] [varchar](50) NULL,
[companyid] [varchar](50) NULL,
[username] [varchar](50) NULL,
[identifier] [varchar](50) NULL,
[deleteat] [varchar](50) NULL,
CONSTRAINT [PK_the_table] PRIMARY KEY CLUSTERED
(
[id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
And the index for username:
SET ANSI_PADDING ON
GO
/****** Object: Index [UIDX_USERNAME] Script Date: 7/26/2018 4:03:31 PM ******/
CREATE NONCLUSTERED INDEX [UIDX_USERNAME] ON [dbo].[the_table]
(
[username] ASC
)
WHERE ([DELETEAT] IS NOT NULL)
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
It would be the same index logic for the other column, but I don't want to clutter this answer by posting more! I would not mess around with putting the logic in triggers or stored proc's, bad data could still sneak in. If you keep nice constraints, nobody can clutter your data up :)
You can use filtered indexes. The syntax looks like this:
create unique index unq_thetable_username on the_table(username)
where deleteAt is not null;
I don't know if you can point-and-click your way to this solution. I would just write the logic as above.
Create a unique index on ProductId and Username, and use a where clause to restrict that index to WHERE DeletedAt IS NOT NULL. Answer showing an example of a unique filtered index.
You may also need the clause AND ProductId IS NOT NULL, although that wasn't stated as a requirement, but since that column is nullable you ought to think about this.
Similar for Identifier.
You might also want to think about the value of a table where it is valid to have a row where all the columns except Id are null...

Clustered Index Update Slow Update On Large Table

I am having a problem updating a large table with millions of rows please advice to reduce the update time.
Table definition
CREATE TABLE [dbo].[tbl_sms_job_detail](
[JobDetailID] [int] IDENTITY(1,1) NOT NULL,
[JobID] [int] NULL,
[DistributorID] [int] NULL,
[ResellerID] [int] NULL,
[CustomerID] [int] NULL,
[SenderID] [nvarchar](50) NULL,
[PhoneNumber] [nvarchar](100) NULL,
[SMSMessage] [nvarchar](1000) NULL,
[MessageType] [nvarchar](50) NULL,
[MessageLength] [int] NULL,
[MessageParts] [int] NULL,
[ClientRate] [decimal](18, 5) NULL,
[ClientCost] [decimal](18, 5) NULL,
[ResellerRate] [decimal](18, 5) NULL,
[ResellerCost] [decimal](18, 5) NULL,
[DistributorRate] [decimal](18, 5) NULL,
[DistributorCost] [decimal](18, 5) NULL,
[RouteDetailID] [int] NULL,
[SMSID] [nvarchar](200) NULL,
[DLRStatus] [nvarchar](100) NULL,
[ErrorCode] [int] NULL,
[ErrorDescription] [nvarchar](2000) NULL,
[SentDate] [datetime] NULL,
[SentDateUTC] [datetime] NULL,
[SMSSource] [nvarchar](50) NULL,
[SMSType] [nvarchar](100) NULL,
[APISMSID] [int] NULL,
[DLRDate] [datetime] NULL,
[DLRDateUTC] [datetime] NULL,
CONSTRAINT [PK_tbl_sms_job_detail] PRIMARY KEY CLUSTERED
(
[JobDetailID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
CREATE NONCLUSTERED INDEX [NonClusteredIndex-20170919-173756] ON [dbo].[tbl_sms_job_detail] ( [JobID] ASC, [DLRStatus] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) GO
CREATE NONCLUSTERED INDEX [NonClusteredIndex-20170919-174142] ON [dbo].[tbl_sms_job_detail]
(
[SMSID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
GO
Update Procedure
CREATE Procedure [dbo].[sp_update_message_status]
#SMSID nvarchar(200),
#DLRStatus nvarchar(100),
#ErrorCode int,
#ErrorDescription nvarchar(2000)
AS
UPDATE tbl_sms_job_detail SET DLRStatus = #DLRStatus, ErrorCode = #ErrorCode, ErrorDescription = #ErrorDescription WHERE SMSID = #SMSID
Execution Plan
This Procedure is called up to 1000 times in several minutes and some of them fail to update as it takes time to update the previous record what can be done to increase the update of a record in this table.
I suspect that the issue is not being caused by the actual clustered index but by the effect the update query has.
MSSQL is a page based storage system. When adding records to the table, as the clustered index is on the field [JobDetailID] [int] IDENTITY(1,1) NOT NULL, each new record is applied to the last page currently at the table (if it will fit) or a new page is tacked onto the end of the table and the record stored in it.
Assuming that [DLRStatus] and/or [ErrorDescription] start of a null or empty strings, when the update sproc runs, it has to find some space in the page that the record is already in to store the new value in. SQL keeps a little space in each page file for such purpose, but when that space is used up, it will have to do a page split - splitting the contents of the one page file into the existing page file and a newly created blank page file. As the primary key is clustered, this new page file has to be inserted so it keeps the records stored in the table in the clustered index order. It is quite likely that this page splitting is at the root of the problem.
The amount of space that SQL keeps back before creating a new page is configurable, therefore one solution is to initially create the page files with plenty of 'expansion' room. On an index it is called the fill factor, but I am not sure what the correct term is for data pages (probably still a fill factor, but not sure).
Another alternative is to store the returned error information in a separate table and then store the primary key for the 'error information' record in table [tbl_sms_job_detail]. As long as the key is not a nvarchar / varchar (and who would do this anyway), the space required in the page file will already be reserved. Thus recording the error information requires appending the variable text information to the end of the last page file for the new table and updating a foreign key in your original table that already has space reserved for it and so doesn't trigger of a page slit.

SQL table growing inconsistently

There is a SQL table which is growing rapidly and inconsistently compared to it's intrinsic data. To make it short, there is a windows service backing up the content of .txt files in this table, the files weight from 1KB to 45KB approx. hence the nvarchar(max) column used to store the content of those text files.
When running the sp_spaceused command on this table, here is the result:
name rows reserved data index_size unused
Files 20402 814872 KB 813416 KB 1048 KB 408 KB
But when running this simple query, which gives me the total amount of data in bytes used by this table, the result is not anywhere near: (97231108 bytes).
SELECT (SUM(DATALENGTH(A)) +
SUM(DATALENGTH(B)) +
SUM(DATALENGTH(C)) +
SUM(DATALENGTH(D)) +
SUM(DATALENGTH(E)) +
SUM(DATALENGTH(F)) +
SUM(DATALENGTH(G)) +
SUM(DATALENGTH(H)) +
SUM(DATALENGTH(I))) AS BytesUsed
FROM Files
RESULT: 97231108 bytes
The create statement for this table goes like this:
CREATE TABLE [dbo].[Files](
[A] [int] IDENTITY(33515427,1) NOT NULL,
[B] [nvarchar](100) NOT NULL,
[C] [nvarchar](max) NOT NULL,
[D] [nvarchar](100) NOT NULL,
[E] [datetime] NULL,
[F] [nvarchar](2) NULL,
[G] [datetime] NULL,
[H] [nvarchar](100) NULL,
[I] [int] NULL,
CONSTRAINT [PK_Files] PRIMARY KEY CLUSTERED
(
[A] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF,
ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY],
CONSTRAINT [UK_Files_FileType_FileDate] UNIQUE NONCLUSTERED
(
[D] ASC,
[E] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF,
ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
ALTER TABLE [dbo].[Files] WITH CHECK ADD CONSTRAINT [FK_Files_FileStatus] FOREIGN
KEY([F])
REFERENCES [dbo].[F] ([F])
GO
ALTER TABLE [dbo].[Files] CHECK CONSTRAINT [FK_Files_FileStatus]
GO
Temporary Fix: I have recreated the table (DROP & CREATE), then copied the old table's data into the new one, this made the table go from 65GB to 108MB.
My question is:
What can make this table taking so much space and how can I prevent it from growing again?
Installing the latest service pack fixed the problem.

How to Insert into 2 Tables ProductOrder and ProductOrderLine using VBA & Foreign Keys in Excel

I am newbiew using VBA/FK/SQL server all in one. I am creating simple purchase order workbook user interface in excel.
I have created two tables:
CREATE TABLE [dbo].[PurchaseOrder](
[PKPurchaseOrderID] [bigint] IDENTITY(1,1) NOT NULL,
[PurchaseOrderNumber] [bigint] NULL,
[PurchaseOrderDate] [date] NULL,
[PurchaseOrderTime] [int] NULL,
[PurchaseOrderSupplierID] [nvarchar](50) NULL,
[ShipToA1] [nvarchar](50) NULL,
[ShipToA2] [nvarchar](50) NULL,
[ShipToA3] [nvarchar](50) NULL,
[ShipToA4] [nvarchar](50) NULL,
CONSTRAINT [PK_PurchaseOrder] PRIMARY KEY CLUSTERED
(
[PKPurchaseOrderID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF,
ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
and
CREATE TABLE [dbo].[PurchaseOrderLines](
[PKPurchaseOrderLineID] [bigint] IDENTITY(1,1) NOT NULL,
[FKPurchaseOrderID] [bigint] NULL,
[Quantity] [smallint] NULL,
[Item] [nchar](25) NULL,
[Description] [nvarchar](50) NULL,
[siteID] [nchar](10) NULL,
[UnitPrice] [money] NULL,
[LineTotal] [money] NULL,
CONSTRAINT [PK_PurchaseOrderLines] PRIMARY KEY CLUSTERED
(
[PKPurchaseOrderLineID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF,
ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
ALTER TABLE [dbo].[PurchaseOrderLines] WITH CHECK ADD CONSTRAINT
[FK_PurchaseOrderLines_PurchaseOrder] FOREIGN KEY([FKPurchaseOrderID])
REFERENCES [dbo].[PurchaseOrder] ([PKPurchaseOrderID])
ALTER TABLE [dbo].[PurchaseOrderLines] CHECK CONSTRAINT
[FK_PurchaseOrderLines_PurchaseOrder]
GO
I want to achieve that in the background when user clicks the button both tables are updated.
I am not sure how I can link brand new row created in the PurchaseOrder table with FK in the PurchaseorderLine table.
What i plan to do for single user interface:
Insert New Order
Use Max(PKPurchaseOrderID) as FK for new order lines table.
How can I determine currently inserted Order ID(PKPurchaseOrderID) if multiple users are working/submitting orders at the same time. I am afraid order lines may be assigned to different orders if I use my plan. e.g.
Please advise.
I use SQL Server 2008 and Excel 2007/2010
Many thanks
I would create a stored procedure that inserts the new row and returns the ID with SCOPE_IDENTITY()
Here's a decent article on the different "identity" methods in SQL Server.
http://blog.sqlauthority.com/2007/03/25/sql-server-identity-vs-scope_identity-vs-ident_current-retrieve-last-inserted-identity-of-record/