SELECT
#MaxSeq = (CASE
WHEN (SELECT COUNT(*) FROM app_Attachments
WHERE app_Attachments.AppId = #appID) <= 0
THEN 1
ELSE (SELECT (MAX(seq)+1)
FROM app_Attachments
WHERE app_Attachments.AppId = #appID)
END)
DECLARE #Id INT;
SELECT #Id = (COALESCE((SELECT MAX(Id)+1 FROM app_Attachments), 1))
INSERT INTO app_Attachments (Id, AppID, AttName, AttContentType, AttData, AddedBy, AddedDate, Seq)
SELECT
#Id, AppId, ImgNames, ImgType, Bytes, #AddedBy, #AddedDate, #MaxSeq
FROM
#Attachments --user defined table
CREATE TYPE [dbo].[AppsAttachments] AS TABLE
(
[AppId] [INT] NULL,
[Bytes] [VARBINARY](MAX) NULL,
[ImgNames] [NVARCHAR](MAX) NULL,
[ImgType] [NVARCHAR](200) NULL
)
Insert into table using stored procedure that has user defined table as parameter only success if table passed has one row, it was working well when the primary key is set as identity, after I changed the primary key into manual integer to be entered the problem occurred
if Id is not identity, then you need to ensure the Id is not duplicates. Use Row_number() to generate a running sequence of number
insert into app_Attachments (Id,AppID,AttName,AttContentType,AttData,AddedBy,AddedDate,Seq)
select #Id + ROW_NUMBER() OVER (ORDER BY AppId) - 1,
AppId,
ImgNames,
ImgType,
Bytes,
#AddedBy,
#AddedDate,
#MaxSeq
from #Attachments --user defined table
if you also need to increase the Seq as well, add a row_number() to the query
I have a stored procedure which returns me the average issue price of products. There will be multiple rows for each issue, and so I group by barcode, and also the number of stores that have inserted a row for this issue, and get the average of IssuePrice as this comes from user input:
ALTER PROCEDURE [dbo].[GetUnknownBarcodeReport]
#Status INT,#StoreTypeID INT
AS
BEGIN
SELECT COUNT(StoreCode) AS [# Stores]
,MAX(IssueName) AS IssueName
,AVG(IssuePrice) AS IssuePrice
,Barcode
,Product.EAN13 AS [Matched Product Code]
,Product.Name AS [Matched Product Name]
,Product.MainCatagory AS [Product Catagory]
FROM UnknownBarcodes
LEFT JOIN Product on LEFT(UnknownBarcodes.Barcode,13) = Product.EAN13
WHERE UnknownBarcodeStatusID = #Status
AND LEN(Barcode) >= 10
AND StoreTypeID = #StoreTypeID
GROUP BY Barcode, Product.EAN13, Product.Name, Product.MainCatagory
ORDER BY
CASE WHEN #status = 1 THEN
COUNT(StoreCode)
WHEN #status = 2 THEN
COUNT(StoreCode)
WHEN #status = 3 THEN
MAX(DateInserted)
END DESC
END
The same product can be reported multiple times and therefore have multiple rows, but sometimes users will enter different prices. If ten users all enter the same barcode and say the price is 100 then the AVG(IssuePrice) is also 100. However, if 9 enter 100, and the last row is entered as 1 then the AVG changes to 90.1
I would like to replace the AVG with the mathematical equivalent of MODE so that the above example would still return 100 because there are more 100s reported than any other value. Is this possible in SQL?
To help re-create this, the script for the table is below:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[UnknownBarcodes](
[ID] [int] IDENTITY(1,1) NOT NULL,
[StoreCode] [int] NULL,
[DateInserted] [datetime] NULL,
[StoreTypeID] [int] NULL,
[Barcode] [varchar](100) NULL,
[UnknownBarcodeStatusID] [int] NULL,
[StatusDescription] [varchar](1000) NULL,
[IssueName] [varchar](100) NULL,
[IssuePrice] [int] NULL,
[AutoReported] [bit] NULL,
CONSTRAINT [PK_UnknownBarcodes] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
)
GO
SET ANSI_PADDING OFF
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[Product](
[EAN13] [bigint] NOT NULL,
[Name] [varchar](250) NULL,
[MainCatagory] [varchar](100) NULL,
CONSTRAINT [PK_Product] PRIMARY KEY CLUSTERED
(
[EAN13] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
)
GO
SET ANSI_PADDING OFF
GO
INSERT INTO [dbo].[UnknownBarcodes] ([StoreCode],[DateInserted],[StoreTypeID],[Barcode],[UnknownBarcodeStatusID],[StatusDescription],[IssueName],[IssuePrice],[AutoReported]) VALUES (412,2015-07-15 08:01:03.817,1,'977096171301112',3,'Scanning issues regarding the reported unknown item','ASIAN TRADER FREE TRADE',200,1)
INSERT INTO [dbo].[UnknownBarcodes] ([StoreCode],[DateInserted],[StoreTypeID],[Barcode],[UnknownBarcodeStatusID],[StatusDescription],[IssueName],[IssuePrice],[AutoReported]) VALUES (843,2015-07-15 08:01:03.817,1,'977096171301112',3,'Scanning issues regarding the reported unknown item','ASIAN TRADER FREE TRADE',10,1)
INSERT INTO [dbo].[UnknownBarcodes] ([StoreCode],[DateInserted],[StoreTypeID],[Barcode],[UnknownBarcodeStatusID],[StatusDescription],[IssueName],[IssuePrice],[AutoReported]) VALUES (860,2015-07-15 08:01:03.817,1,'977096171301112',3,'Scanning issues regarding the reported unknown item','ASIAN TRADER FREE TRADE',10,1)
INSERT INTO [dbo].[UnknownBarcodes] ([StoreCode],[DateInserted],[StoreTypeID],[Barcode],[UnknownBarcodeStatusID],[StatusDescription],[IssueName],[IssuePrice],[AutoReported]) VALUES (864,2015-07-15 08:01:03.817,1,'977096171301112',3,'Scanning issues regarding the reported unknown item','ASIAN TRADER FREE TRADE',10,1)
INSERT INTO [dbo].[UnknownBarcodes] ([StoreCode],[DateInserted],[StoreTypeID],[Barcode],[UnknownBarcodeStatusID],[StatusDescription],[IssueName],[IssuePrice],[AutoReported]) VALUES (964,2015-07-15 08:01:03.817,1,'977096171301112',3,'Scanning issues regarding the reported unknown item','ASIAN TRADER FREE TRADE',10,1)
INSERT INTO [dbo].[UnknownBarcodes] ([StoreCode],[DateInserted],[StoreTypeID],[Barcode],[UnknownBarcodeStatusID],[StatusDescription],[IssueName],[IssuePrice],[AutoReported]) VALUES (1061,2015-07-15 08:01:03.817,1,'977096171301112',3,'Scanning issues regarding the reported unknown item','ASIAN TRADER FREE TRADE',10,1)
INSERT INTO [dbo].[UnknownBarcodes] ([StoreCode],[DateInserted],[StoreTypeID],[Barcode],[UnknownBarcodeStatusID],[StatusDescription],[IssueName],[IssuePrice],[AutoReported]) VALUES (1350,2015-07-15 08:01:03.817,1,'977096171301112',3,'Scanning issues regarding the reported unknown item','ASIAN TRADER FREE TRADE',10,1)
INSERT INTO [dbo].[UnknownBarcodes] ([StoreCode],[DateInserted],[StoreTypeID],[Barcode],[UnknownBarcodeStatusID],[StatusDescription],[IssueName],[IssuePrice],[AutoReported]) VALUES (1375,2015-07-15 08:01:03.817,1,'977096171301112',3,'Scanning issues regarding the reported unknown item','ASIAN TRADER FREE TRADE',10,1)
INSERT INTO [dbo].[UnknownBarcodes] ([StoreCode],[DateInserted],[StoreTypeID],[Barcode],[UnknownBarcodeStatusID],[StatusDescription],[IssueName],[IssuePrice],[AutoReported]) VALUES (1489,2015-07-15 08:01:03.817,1,'977096171301112',3,'Scanning issues regarding the reported unknown item','ASIAN TRADER FREE TRADE',10,1)
INSERT INTO [dbo].[UnknownBarcodes] ([StoreCode],[DateInserted],[StoreTypeID],[Barcode],[UnknownBarcodeStatusID],[StatusDescription],[IssueName],[IssuePrice],[AutoReported]) VALUES (1531,2015-07-15 08:01:03.817,1,'977096171301112',3,'Scanning issues regarding the reported unknown item','ASIAN TRADER FREE TRADE',10,1)
You can see that the MODE of the above set should be 10 even though one row has a value of 200 which skews the AVG
From: http://blogs.lessthandot.com/index.php/datamgmt/datadesign/calculating-mean-median-and-mode-with-sq/
MODE
To Calculate the mode with sql server, we first need to get the counts for each value in the set. Then, we need to filter the data so that values equal to the count are returned.
Declare #Temp Table(Id Int Identity(1,1), Data Decimal(10,5))
Insert into #Temp Values(1)
Insert into #Temp Values(2)
Insert into #Temp Values(5)
Insert into #Temp Values(5)
Insert into #Temp Values(5)
Insert into #Temp Values(6)
Insert into #Temp Values(6)
Insert into #Temp Values(6)
Insert into #Temp Values(7)
Insert into #Temp Values(9)
Insert into #Temp Values(10)
Insert into #Temp Values(NULL)
SELECT TOP 1 with ties DATA
FROM #Temp
WHERE DATA IS Not NULL
GROUP BY DATA
ORDER BY COUNT(*) DESC
In your example the functional sql could be accomplished by a subquery:
(select top 1 IssuePrice from UnknownBarcodes barx where barx.BarCode = bar.Barcode group by barx.IssuePrice order by count(*) DESC ) IssuePrice_MODE
Total query:
declare #Status int, #StoreTypeId int;
set #Status = 3;
set #StoreTypeId = 1;
SELECT
COUNT(bar.StoreCode) AS [# Stores]
,MAX(bar.IssueName) AS IssueName
,AVG(bar.IssuePrice) AS IssuePrice
,(select top 1 IssuePrice from UnknownBarcodes barx where barx.BarCode = bar.Barcode group by barx.IssuePrice order by count(*) DESC ) IssuePrice_MODE
,bar.Barcode
,Product.EAN13 AS [Matched Product Code]
,Product.Name AS [Matched Product Name]
,Product.MainCatagory AS [Product Catagory]
FROM UnknownBarcodes bar
LEFT JOIN Product on LEFT(bar.Barcode,13) = Product.EAN13
WHERE bar.UnknownBarcodeStatusID = #Status
AND LEN(bar.Barcode) >= 10
AND bar.StoreTypeID = #StoreTypeID
GROUP BY bar.Barcode, Product.EAN13, Product.Name, Product.MainCatagory
ORDER BY
CASE WHEN #status = 1 THEN
COUNT(bar.StoreCode)
WHEN #status = 2 THEN
COUNT(bar.StoreCode)
WHEN #status = 3 THEN
MAX(bar.DateInserted)
end
DESC
The query below might be a bit more efficient on large datasets since it restricts the results that have to be iterated
declare #Status int, #StoreTypeId int;
set #Status = 3;
set #StoreTypeId = 1;
with FirstQuery as (SELECT
*
FROM UnknownBarcodes bar
LEFT JOIN Product on LEFT(bar.Barcode,13) = Product.EAN13
WHERE bar.UnknownBarcodeStatusID = #Status
AND LEN(bar.Barcode) >= 10
AND bar.StoreTypeID = #StoreTypeID
)
select COUNT(StoreCode) AS [# Stores]
,MAX(IssueName) AS IssueName
,AVG(IssuePrice) AS IssuePrice
,Barcode
,(select top 1 IssuePrice from FirstQuery barx where barx.BarCode = FirstQuery.Barcode group by barx.IssuePrice order by count(*) DESC ) IssuePrice_MODE
,EAN13 AS [Matched Product Code]
,Name AS [Matched Product Name]
,MainCatagory AS [Product Catagory]
from FirstQuery
GROUP BY Barcode, EAN13, Name, MainCatagory
ORDER BY
CASE WHEN #status = 1 THEN
COUNT(StoreCode)
WHEN #status = 2 THEN
COUNT(StoreCode)
WHEN #status = 3 THEN
MAX(DateInserted)
END DESC
I have use inner query to calculate MODE.
SELECT COUNT(StoreCode) AS [# Stores]
,MAX(IssueName) AS IssueName
,AVG(IssuePrice) AS IssuePrice,
(select top 1 issuePrice from [UnknownBarcodes] where ID = UnknownBarcodes.id group by IssuePrice having IssuePrice > 1) issuePrice_MODE
,Barcode
,Product.EAN13 AS [Matched Product Code]
,Product.Name AS [Matched Product Name]
,Product.MainCatagory AS [Product Catagory]
FROM UnknownBarcodes
LEFT JOIN Product on LEFT(UnknownBarcodes.Barcode,13) = Product.EAN13
WHERE UnknownBarcodeStatusID = 3
AND LEN(Barcode) >= 10
AND StoreTypeID = 1
GROUP BY Barcode, Product.EAN13, Product.Name, Product.MainCatagory
How does this work for what you are after? No idea how well it will perform on larger datasets though:
;with cte as
(
select count(1) over (partition by b.IssueName
,p.EAN13
,p.Name
,p.MainCatagory
) as [# Stores]
,b.IssueName
,b.IssuePrice
,row_number() over (partition by b.IssueName
,p.EAN13
,p.Name
,p.MainCatagory
order by count(1) desc
) as IssuePriceSort
,b.Barcode
,p.EAN13
,p.Name
,p.MainCatagory
from UnknownBarcodes as b
left join Product as p
on left(b.Barcode,13) = p.EAN13
where UnknownBarcodeStatusID = #Status
and len(Barcode) >= 10
and StoreTypeID = #StoreTypeID
group by b.Barcode
,b.IssueName
,b.IssuePrice
,p.EAN13
,p.Name
,p.MainCatagory
)
select [# Stores]
,[IssueName]
,[IssuePrice]
,[IssuePriceSort]
,[Barcode]
,[EAN13]
,[Name]
,[MainCatagory]
from cte
where IssuePriceSort = 1
I have a table for phone numbers like this :
ID PhoneNumber Enabled GrupID CountryID
----------- -------------------- ------- ------ -----------
10444 ***001000999 1 NULL 1
10445 ***001000998 1 NULL 1
10446 ***001000994 1 NULL 1
10447 ***001000990 1 NULL 1
10448 ***001000989 1 NULL 1
This table has 68992507 rows.
I want to select some random phone number from it.
I can get my random number query by this stored procedure:
here I select random numbers, insert to a #table and then update the selected numbers .
CREATE proc [dbo].[Mysp_GetRandom]
#countryid int,
#count int
as
declare #tbl table([ID] [int] ,
[PhoneNumber] [nchar](20) NOT NULL,
[Enabled] [bit] NULL,
[GrupID] [tinyint] NULL,
[CountryID] [int] NULL)
INSERT INTO #tbl
SELECT TOP (#count) *
FROM tblPhoneNumber
WHERE CountryID = #countryid
AND GrupID is null
ORDER BY binary_checksum(ID * rand())
UPDATE tblPhoneNumber
SET GrupID = 1
WHERE ID IN (SELECT ID FROM #tbl)
SELECT * FROM #tbl
The problem is that it takes a long time for the query to run. For example this query takes 12:30 minutes ...
DECLARE #return_value int
EXEC #return_value = [dbo].[Mysp_GetRandom]
#countryid = 14, #count = 3
SELECT 'Return Value' = #return_value
and I have an ndex on this table :
CREATE NONCLUSTERED INDEX [NonClusteredIndex-20150415-172433]
ON [dbo].[tblPhoneNumber] ([CountryID] 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) ON [PRIMARY]
GO
Execution plan is as below :
Thanks ...
Add grupID to index key column and add other required columns in include clause of your NC index NonClusteredIndex-20150415-172433.
Execution plan is already giving you the same hint on adding missing index.
P.S Mark it as answer if it helped you.
Look at your query plan, the first INSERT statement takes almost 100% of the time, and 70% of it is sorting. There's not much you can do with it since you already using BINARY_CHECKSUM. May be the table was filled in random enough manner to get off with taking consecutive rows starting from random offset, like this:
SELECT ID FROM tblPhoneNumber WHERE CountryID = #countryid
AND GrupID is null ORDER BY ID OFFSET CONVERT(int,
rand()*(select count(*) from tblPhoneNumber)-#count-1)
ROWS FETCH NEXT #count ROWS ONLY
You should replace your order by clause.
You could create fairly random ids:
declare #count int = 100
; with ids(id, hex) as (
Select 1, convert(bigint, convert(varbinary, '0x'+right(newid(), 6), 1 ))
Union all
Select id+1, convert(bigint, convert(varbinary, '0x'+right(newid(), 6), 1 ))
From ids
Where id+1 <= #count
)
Select * from ids
Option (MAXRECURSION 0)
Then you can join it table with your table on ID.
You should review your indexes (mentionned by others) and add an index on phone Id.
Let me apologize in advance for the length of this question. I don't see how to ask it without giving all the definitions.
I've inherited a SQL Server 2005 database that includes a homegrown implementation of change tracking. Through triggers, changes to virtually every field in the database are stored in a set of three tables. In the application for this database, the user can request the history of various items, and what's returned is not just changes to the item itself, but also changes in related tables. The problem is that in some cases, it's painfully slow, and in some cases, the request eventually crashes the application. The client has also reported other users having problems when someone requests history.
The tables that store the change data are as follows:
CREATE TABLE [dbo].[tblSYSChangeHistory](
[id] [bigint] IDENTITY(1,1) NOT NULL,
[date] [datetime] NULL,
[obj_id] [int] NULL,
[uid] [varchar](50) NULL
This table tracks the tables that have been changed. Obj_id is the value that Object_ID() returns.
CREATE TABLE [dbo].[tblSYSChangeHistory_Items](
[id] [bigint] IDENTITY(1,1) NOT NULL,
[h_id] [bigint] NOT NULL,
[item_id] [int] NULL,
[action] [tinyint] NULL
This table tracks the items that have been changed. h_id is a foreign key to tblSYSChangeHistory. item_id is the PK of the changed item in the specified table. action indicates insert, delete or change.
CREATE TABLE [dbo].[tblSYSChangeHistory_Details](
[id] [bigint] IDENTITY(1,1) NOT NULL,
[i_id] [bigint] NOT NULL,
[col_id] [int] NOT NULL,
[prev_val] [varchar](max) NULL,
[new_val] [varchar](max) NULL
This table tracks the individual changes. i_id is a foreign key to tblSYSChangeHistory_Items. col_id indicates which column was changed, and prev_val and new_val indicate the original and new values for that field.
There's actually a fourth table that supports this architecture. tblSYSChangeHistory_Objects maps plain English descriptions of operations to particular tables in the database.
The code to look up the history for an item is incredibly convoluted. It's one branch of a very long SP. Relevant parameters are as follows:
#action varchar(50),
#obj_id bigint = 0,
#uid varchar(50) = '',
#prev_val varchar(MAX) = '',
#new_val varchar(MAX) = '',
#start_date datetime = '',
#end_date datetime = ''
I'm storing them to local variables right away (because I was able to significantly speed up another SP by doing so):
declare #iObj_id bigint,
#cUID varchar(50),
#cPrev_val varchar(max),
#cNew_val varchar(max),
#tStart_date datetime,
#tEnd_date datetime
set #iObj_id = #obj_id
set #cUID = #uid
set #cPrev_val = #prev_val
set #cNew_val = #new_val
set #tStart_date = #start_date
set #tEnd_date = #end_date
And here's the code from that branch of the SP:
create table #r (obj_id int, item_id int, l tinyint)
create clustered index #ri on #r (obj_id, item_id)
insert into #r
select object_id(obj_name), #iObj_id, 0
from dbo.tblSYSChangeHistory_Objects
where obj_type = 'U' and descr = cast(#cPrev_val AS varchar(150))
declare #i tinyint, #cnt int
set #i = 1
while #i <= 4
begin
insert into #r
select obj_id, item_id, #i
from dbo.vSYSChangeHistoryFK a with (nolock)
where exists (select null from #r where obj_id = a.rel_obj_id and item_id = a.rel_item_id and l = #i - 1)
and not exists (select null from #r where obj_id = a.obj_id and item_id = a.item_id)
set #cnt = ##rowcount
insert into #r
select rel_obj_id, rel_item_id, #i
from dbo.vSYSChangeHistoryFK a with (nolock)
where object_name(obj_id) not in (<this is a list of particular tables in the database>)
and exists (select null from #r where obj_id = a.obj_id and item_id = a.item_id and l between #i - 1 and #i)
and not exists (select null from #r where obj_id = a.rel_obj_id and item_id = a.rel_item_id)
set #i = case #cnt + ##rowcount when 0 then 100 else #i + 1 end
end
select date, obj_name, item, [uid], [action],
pkey, item_id, id, key_obj_id into #tCH_R
from dbo.vSYSChangeHistory a with (nolock)
where exists (select null from #r where obj_id = a.obj_id and item_id = a.item_id)
and (#cUID = '' or uid = #cUID)
and (#cNew_val = '' or [action] = #cNew_val)
declare ch_item_cursor cursor for
select distinct pkey, key_obj_id, item_id
from #tCH_R
where item = '' and pkey <> ''
open ch_item_cursor
fetch next from ch_item_cursor
into #cPrev_val, #iObj_id, #iCol_id
while ##fetch_status = 0
begin
set #SQLStr = 'select #val = ' + #cPrev_val +
' from ' + object_name(#iObj_id) + ' with (nolock)' +
' where id = #id'
exec sp_executesql #SQLStr,
N'#val varchar(max) output, #id int',
#cNew_val output, #iCol_id
update #tCH_R
set item = #cNew_val
where key_obj_id = #iObj_id
and item_id = #iCol_id
fetch next from ch_item_cursor
into #cPrev_val, #iObj_id, #iCol_id
end
close ch_item_cursor
deallocate ch_item_cursor
select date, obj_name,
cast(item AS varchar(254)) AS item,
uid, [action],
cast(id AS int) AS id
from #tCH_R
order by id
return
As you can see, the code uses a view. Here's that definition:
ALTER VIEW [dbo].[vSYSChangeHistoryFK]
AS
SELECT i.obj_id, i.item_id, c1.parent_object_id AS rel_obj_id, i2.item_id AS rel_item_id
FROM dbo.vSYSChangeHistoryItemsD AS i INNER JOIN
sys.foreign_key_columns AS c1 ON c1.referenced_object_id = i.obj_id AND c1.constraint_column_id = 1 INNER JOIN
dbo.vSYSChangeHistoryItemsD AS i2 ON c1.parent_object_id = i2.obj_id INNER JOIN
dbo.tblSYSChangeHistory_Details AS d1 ON d1.i_id = i.min_id AND d1.col_id = c1.referenced_column_id INNER JOIN
dbo.tblSYSChangeHistory_Details AS d1k ON d1k.i_id = i2.min_id AND d1k.col_id = c1.parent_column_id AND ISNULL(d1.new_val,
ISNULL(d1.prev_val, '')) = ISNULL(d1k.new_val, ISNULL(d1k.prev_val, '')) --LEFT OUTER JOIN
UNION ALL
SELECT i0.obj_id, i0.item_id, c01.parent_object_id AS rel_obj_id, i02.item_id AS rel_item_id
FROM dbo.vSYSChangeHistoryItemsD AS i0 INNER JOIN
sys.foreign_key_columns AS c01 ON c01.referenced_object_id = i0.obj_id AND c01.constraint_column_id = 1 AND col_name(c01.referenced_object_id,
c01.referenced_column_id) = 'ID' INNER JOIN
dbo.vSYSChangeHistoryItemsD AS i02 ON c01.parent_object_id = i02.obj_id INNER JOIN
dbo.tblSYSChangeHistory_Details AS d01k ON i02.min_id = d01k.i_id AND d01k.col_id = c01.parent_column_id AND ISNULL(d01k.new_val,
d01k.prev_val) = CAST(i0.item_id AS varchar(max))
And finally, that view uses one more view:
ALTER VIEW [dbo].[vSYSChangeHistoryItemsD]
AS
SELECT h.obj_id, m.item_id, MIN(m.id) AS min_id
FROM dbo.tblSYSChangeHistory AS h INNER JOIN
dbo.tblSYSChangeHistory_Items AS m ON h.id = m.h_id
GROUP BY h.obj_id, m.item_id
Working with the Profiler, it appears that view vSYSChangeHistoryFK is the big culprit, and my testing suggests that the particular problem is in the join between the two copies of vSYSChangeHistoryItemsD and the foreign_key_columns table.
I'm looking for any ideas on how to give acceptable performance here. The client reports sometimes waiting as much as 15 minutes without getting results. I've tested up to nearly 10 minutes with no result in at least one case.
If there were new language elements in 2008 or later that would solve this, I think the client would be willing to upgrade.
Thanks.
Wow that's a mess. Your big gain should be in removing the cursor. I see 'where exists' - that's nice and efficient b/c as soon as it finds one match it aborts. And I see 'where not exists' - by definition that has to scan everything. Is it finding the top 4? You can do better with using ROW_NUMBER() OVER (PARTITON BY [whatever makes it unique] ORDER BY [whatever your id is]. It's hard to tell. select object_id(obj_name), #iObj_id, 0 makes it seem like only the #i=1 loop actually does anything (?)
If that is what it's doing, you could write it as
SELECT * from
(
select ROW_NUMBER() OVER (PARTITION BY obj_id ORDER BY item_id desc) as Row,
obj_id, item_id
FROM bo.vSYSChangeHistoryFK a with (nolock)
where obj_type = 'U' and descr = cast(#cPrev_val AS varchar(150))
) paged
where Row between 1 and 4
ORDER BY Row
A DBA level change that could help would be to set up a partitioning scheme based on date. Roll over to a new partition every so often. Put the old partitions on different disks. Most queries may only need to hit the recent partition, which will be say 1/5th the size that it used to be, making it much faster without changing anything else.
Not a full answer, sorry. That mess would take hours to parse