Statistics dropped when an Object is dropped? - sql

If an object (table/indexes..) is dropped, will the statistics related to that object be still available or gets dropped along with the object that is dropped?

From virtual-dba.com
Just Note when Dropping a Table/Indexes: If any statistic types are
associated with the table, then the database disassociates the
statistics types and removes any user-defined statistics collected
with the statistics type.

What is default trace?
The default trace is a new feature first included with SQL Server 2005 which provides auditing on schema modifications such as table creation and stored procedure deletion and the like. It does run by default, but you can turn it on ..
For more info: https://www.mssqltips.com/sqlservertip/1739/using-the-default-trace-in-sql-server/
In this query.. use EventClass 92 and 93 to track database auto-growth events. Here is the query to find who dropped / created or altered object in database or database itself.
DECLARE #current VARCHAR(500);
DECLARE #start VARCHAR(500);
DECLARE #indx INT;
SELECT #current = path
FROM sys.traces
WHERE is_default = 1;
SET #current = REVERSE(#current)
SELECT #indx = PATINDEX('%\%', #current)
SET #current = REVERSE(#current)
SET #start = LEFT(#current, LEN(#current) - #indx) + '\log.trc';
-- Change filter as needed
SELECT
CASE EventClass
WHEN 46 THEN 'Object:Created'
WHEN 47 THEN 'Object:Deleted'
WHEN 164 THEN 'Object:Altered'
END,
DatabaseName, ObjectName, HostName, ApplicationName, LoginName,
StartTime
FROM
::fn_trace_gettable(#start, DEFAULT)
WHERE
EventClass IN (46,47,164) AND EventSubclass = 0 AND DatabaseID <> 2
ORDER BY
StartTime DESC
You can use this query for finding all details of created, deleted, alter table info...
Here some different default trace in SQL Server....
SELECT DISTINCT Trace.EventID, TraceEvents.NAME AS Event_Desc
FROM ::fn_trace_geteventinfo(1) Trace,
sys.trace_events TraceEvents
WHERE Trace.eventID = TraceEvents.trace_event_id
This above query is used for Event Class and also in above example using (Object:Created, Object:Deleted, Object:Altered); you can change as per your requirement.

Related

Updating a column in every table in a schema in SQL Server

I want to update the column Last_Modified in every table in a given schema. This column is updated with latest timestamp if another column in the same table (ENDTIME) is updated.
To do this I have the following script in SQL Server:
DECLARE #TotalRows FLOAT
SET #TotalRows = (SELECT COUNT(*) FROM table1)
DECLARE #TotalLoopCount INT
SET #TotalLoopCount = CEILING(#TotalRows / 100000)
DECLARE #InitialLoopCount INT
SET #InitialLoopCount = 1
DECLARE #AffectedRows INT
SET #AffectedRows = 0
DECLARE #intialrows INT;
SET #intialrows = 1
DECLARE #lastrows INT
SET #lastrows = 100000;
WHILE #InitialLoopCount <= #TotalLoopCount
BEGIN
WITH updateRows AS
(
SELECT
t1.*,
ROW_NUMBER() OVER (ORDER BY caster) AS seqnum
FROM
table1 t1
)
UPDATE updateRows
SET last_modified = ENDTIME AT TIME ZONE 'Central Standard Time'
WHERE last_modified IS NULL
AND updateRows.ENDTIME IS NOT NULL
AND updateRows.seqnum BETWEEN #intialrows AND #lastrows;
SET #AffectedRows = #AffectedRows + ##ROWCOUNT
SET #intialrows = #intialrows + 100000
SET #lastrows = #lastrows + 100000
-- COMMIT
SET #Remaining = #TotalRows - #AffectedRows
SET #InitialLoopCount = #InitialLoopCount + 1
END
This script determines the count of a table, divides it by 100000 and runs only that many loops to perform the entire update. It breaks down the update in batches/loops and then perform updates on certain rows until it completes updating them all.
This script is only for 1 table, i.e table1. I want to now modify this script in such a way that it dynamically takes all the tables in a schema and runs the above script for each of them. Let's say the schema name is schema1 and it has 32 tables, so this script should run for all those 32 tables.
I am able to retrieve the tables in schema1 but I am not able to dynamically send those to this script. Can anyone please help me with this?
To dynamically change table names at runtime you're going to need something like sp_executesql. See here for an example of its use: https://stackoverflow.com/a/3556554/22194
Then you could have an outer cursor that fetches the table names and then assembles the queries in a string and executes them. Its going to look horrible though.
If your schema doesn't change much another approach would be to generate a long script with a section for each table. You generate the script by querying the table names and then repeating the script with each different table name. Excel is actually pretty good for doing that sort of thing - paste your table names into Excel, use Excel to generate the script then copy/paste it back into SSMS.
This will be a long repetitive script but will avoid the disadvantage of having all the SQL in strings.

SQL Server : update value if it does not exist in a column

I am using SQL Server 2008 and trying to create a statement which will update a single value within a row from another table if a certain parameter is met. I need to make this as simple as possible for a member of my team to use.
So in this case I want to store 2 values, the Sales Order and the reference. Unfortunately the Sales order has a unique identifier that I need to record and enter into the jobs table and NOT the actual sales order reference.
The parameter which needs to be met is that the Sales order unique identifier cannot exist anywhere in the sales order column within the jobs table. I can get this to work when the while value is set to 1 but not when it's set to 0 and in my head it should be set to 0. Anyone got any ideas why this doesn't work?
/****** Attach an SO to a WO ******/
/****** ONLY EDIT THE VALUES BETWEEN '' ******/
Declare #Reference nvarchar(30);
Set #Reference = 'WO16119';
Declare #SO nvarchar(30);
Set #SO = '0000016205';
/****** DO NOT ALTER THE CODE BEYOND THIS POINT!!!!!!!!!!!!! ******/
/* store more values */
Declare #SOID nvarchar(30);
Set #SOID = (Select SOPOrderReturnID
FROM Test_DB.dbo.SOTable
Where DocumentNo = #SO);
/* check if update should run */
Declare #Check nvarchar (30);
Set #Check = (Select case when exists (select *
from Test_DB.dbo.Jobs
where SalesOrderNumber != #SOID)
then CAST(1 AS BIT)
ELSE CAST(0 AS BIT) End)
While (#Check = 0)
/* if check is true run code below */
Begin
Update Test_DB.dbo.jobs
SET SalesOrderNumber = (Select SOPOrderReturnID
FROM Test_DB.dbo.SOPOrderReturn
Where DocumentNo = #SO)
Where Reference = #Reference
END;
A few comments. First in order to avoid getting into a never ending loop you may want to change your while for an IF statement. You aren't changing the #check value so that will run forever:
IF (#Check = 0)
BEGIN
/* if check is true run code below */
Update Test_DB.dbo.jobs
SET SalesOrderNumber = (Select SOPOrderReturnID
FROM Test_DB.dbo.SOPOrderReturn
Where DocumentNo = #SO)
Where Reference = #Reference
END
Then, without knowing your application I would say that the way you make checks is going to require you to lock your tables to avoid other users invalidating the results of your SELECTs.
I would go instead to creating a UNIQUE constraint over the column you want to be unique and handle the error gracefully. This way you don't need to create big locks on your tables
CREATE UNIQUE INDEX IX_UniqueIndex ON Test_DB.dbo.Jobs(SalesOrderNumber)
As per your comment if you cannot create a unique index you may want to try the following SQL although it could cause too much locking and be affected by race conditions:
IF NOT EXISTS (SELECT 1 FROM Test_DB.dbo.Jobs j INNER JOIN Test_DB.dbo.SOTable so ON j.SalesOrderNumber = so.SPOrderReturnId)
BEGIN
UPDATE Test_DB.dbo.jobs
SET SalesOrderNumber = so.SOPOrderReturnID
FROM
Test_DB.dbo.Jobs j
INNER JOIN Test_DB.dbo.SOTable so ON j.SalesOrderNumber = so.SPOrderReturnId
Where
Reference = #Reference
END
The risk of this are that you are running to separate queries (the select and the update) so between them the state of the database could change. So it may be possible that the first query returns nothing exists for that Id but at the moment of the update other user has inserted/updated that data so the previous result is no longer true.
You can try to avoid this problem by using a isolation level that locks the table on the read (like Serializable) but that could cause locks and even deadlocks in the database.
The best solution here is the unique index. If a certain column has to be unique inside a table the best controller system is the db itself by defining constraints.

SQL Server 2008 dynamic cross database View

We have a database called AVL in SQL Server 2008 R2 SE. This database has many tables, but there is one in particular called ASSETLOCATION that has 46 millon rows right now and accounts for 99.9% of the total database size.
This table has information from 2008 to date, and the actual rate of growing is about 120k records daily.
Now, there are 2 situations we like to address:
Performance is starting to degrade slowly, and everything is
optimized so there's not much to do
Backup times are increasing and is becoming a problem (we do 1 full
backup everyday). The BAK file is 11GB, after winrar does his thing
the final size 2GB and then a script sends the file offsite. We have
a T1 and pulling 2GB through the wire is taking about 5 hours.
All this is normal but here's the catch I want to capitalize on: +90% of SQL statements use information only 3 months old or less, in other words, data from 2008, 2009 and 2010 don't get accessed often.
I was thinking on creating one new database for each year. Lets say:
- AVL2008 database, only table there will be ASSETLOCATION with records from 2008
- AVL2009 database, only table there will be ASSETLOCATION with records from 2009
- AVL2010 database, only table there will be ASSETLOCATION with records from 2010
As you have already guessed data from the past don't get changed, so this will be great from the backup perspective since the AVL database will only have the records from the current year. This approach will also help performance a lot.
Now for the problem. Assume the ASSETLOCATION table has the following columns:
- IDASSETLOCATION (int, PK identity)
- IDASSET (int, FK to ASSET table)
- WHEN (datetime)
- LATLONG (varchar(22), spatial info)
I need to create a view in the AVL database called "vASSETLOCATION", witch is quite simple, but I don't want the view accessing all the databases and joining the ASSETLOCATION tables via UNION, rather the only ones needed based on the WHEN field. For example:
select * from vASSETLOCATION where [WHEN] between '2008-01-01' and '2008-01-02'
In this case the view should ONLY ACCESS the AVL2008.ASSETLOCATION table
select * from vASSETLOCATION where [WHEN] between '2008-12-29' and '2009-01-05'
In this case the view should access AVL2008.ASSETLOCATION and AVL2009.ASSETLOCATION
select * from vASSETLOCATION where
([WHEN] between '2008-01-01' and '2008-01-01')
or
([WHEN] = getdate())
In this case the view should access AVL2008.ASSETLOCATION and AVL.ASSETLOCATION
I know a table scalar UDF in place of the view will solve the problem, but there are more than only 4 fields and [WHEN] is not the only field we may want to include in the where part.
Before anyone suggest it, the table partitioning feature will perhaps help in performance, but NOT in the backup problem.
If there a way to do this in a view?
Thanks.-
This sounds like a classic case for table partitioning or distributed partitioned views.
However you can work around this without ponying up the price for Enterprise edition (or doing all the prep work required to support those features) using some smart code that looks at the problem a little differently. You don't want a single view that accesses all of the tables across the different databases, but what if you had multiple views and a stored procedure to control how they're accessed?
Create views for the most common access patterns. Perhaps you have a view that covers date ranges for 2008-2010, 2008-2009, 2009-2010, etc. They might look like this:
CREATE VIEW dbo.vAL_2008_2009
AS
SELECT * FROM AVL2008.dbo.ASSETLOCATION
UNION ALL
SELECT * FROM AVL2009.dbo.ASSETLOCATION;
GO
CREATE VIEW dbo.vAL_2008_2010
AS
SELECT * FROM AVL2008.dbo.ASSETLOCATION
UNION ALL
SELECT * FROM AVL2009.dbo.ASSETLOCATION
UNION ALL
SELECT * FROM AVL2010.dbo.ASSETLOCATION;
GO
-- etc. etc.
Now your code that handles the queries can take the input date parameters and calculate which view it needs to query. For example:
CREATE PROCEDURE dbo.DetermineViews
#StartDate DATETIME,
#EndDate DATETIME,
#optionalToday BIT = 0
AS
BEGIN
SET NOCOUNT ON;
DECLARE #sql NVARCHAR(MAX) = N'';
SET #sql = #sql + N'SELECT * FROM ' + CASE
WHEN #StartDate >= '20080101' AND #EndDate < '20090101' THEN 'AVL2008.dbo.ASSETLOCATION'
WHEN #StartDate >= '20080101' AND #EndDate < '20100101' THEN 'dbo.vAL_2008_2009'
WHEN #StartDate >= '20080101' AND #EndDate < '20110101' THEN 'dbo.vAL_2008_2010'
-- etc. etc.
WHEN YEAR(#StartDate) = YEAR(CURRENT_TIMESTAMP) THEN 'AVL.dbo.ASSETLOCATION'
ELSE '' END;
IF #OptionalToday = 1 AND YEAR(#StartDate) <> YEAR(CURRENT_TIMESTAMP)
BEGIN
SET #sql = #sql + N'UNION ALL SELECT * FROM AVL.dbo.ASSETLOCATION'
END
SET #sql = #sql + ' WHERE [WHEN] BETWEEN '''
+ CONVERT(CHAR(8), #StartDate, 112) + ''' AND '''
+ CONVERT(CHAR(8), #EndDate, 112) + '''';
IF #OptionalToday = 1
BEGIN
SET #sql = #sql + ' OR ([WHEN] >= DATEDIFF(DAY, 0, CURRENT_TIMESTAMP)
AND [WHEN] < DATEADD(DAY, 1, DATEDIFF(DAY, 0, CURRENT_TIMESTAMP)';
END
PRINT #sql;
-- EXEC sp_executeSQL #sql;
END
GO
I'm probably missing some of your business logic, and you'll certainly want to add some error-handling in there and test the junk out of it, but this is a relatively easy-to-maintain solution that only requires updating that coincides with creating a new database to archive last year's data, which it sounds like only happens once a year.

No query plan for procedure in SQL Server 2005

We have a SQL Server DB with 150-200 stored procs, all of which produce a viewable query plan in sys.dm_exec_query_plan except for one. According to http://msdn.microsoft.com/en-us/library/ms189747.aspx:
Under the following conditions, no Showplan output is returned in the query_plan column of the returned table for sys.dm_exec_query_plan:
If the query plan that is specified by using plan_handle has been evicted from the plan cache, the query_plan column of the returned table is null. For example, this condition may occur if there is a time delay between when the plan handle was captured and when it was used with sys.dm_exec_query_plan.
Some Transact-SQL statements are not cached, such as bulk operation statements or statements containing string literals larger than 8 KB in size. XML Showplans for such statements cannot be retrieved by using sys.dm_exec_query_plan unless the batch is currently executing because they do not exist in the cache.
If a Transact-SQL batch or stored procedure contains a call to a user-defined function or a call to dynamic SQL, for example using EXEC (string), the compiled XML Showplan for the user-defined function is not included in the table returned by sys.dm_exec_query_plan for the batch or stored procedure. Instead, you must make a separate call to sys.dm_exec_query_plan for the plan handle that corresponds to the user-defined function.
And later..
Due to a limitation in the number of nested levels allowed in the xml data type, sys.dm_exec_query_plan cannot return query plans that meet or exceed 128 levels of nested elements.
I'm confident that none of these apply to this procedure. The result never has a query plan, no matter what the timing, so 1 doesn't apply. There are no long string literals or bulk operations, so 2 doesn't apply. There are no user defined functions or dynamic SQL, so 3 doesn't apply. And there's little nesting, so the last doesn't apply. In fact, it's a very simple proc, which I'm including in full (with some table names changed to protect the innocent). Note that the parameter-sniffing shenanigans postdate the problem. It still happens even if I use the parameters directly in the query. Any ideas on why I don't have a viewable query plan for this proc?
ALTER PROCEDURE [dbo].[spGetThreadComments]
#threadId int,
#stateCutoff int = 80,
#origin varchar(255) = null,
#includeComments bit = 1,
#count int = 100000
AS
if (#count is null)
begin
select #count = 100000
end
-- copy parameters to local variables to avoid parameter sniffing
declare #threadIdL int, #stateCutoffL int, #originL varchar(255), #includeCommentsL bit, #countL int
select #threadIdL = #threadId, #stateCutoffL = #stateCutoff, #originL = #origin, #includeCommentsL = #includeComments, #countL = #count
set rowcount #countL
if (#originL = 'Foo')
begin
select * from FooComments (nolock) where threadId = #threadId and statusCode <= #stateCutoff
order by isnull(parentCommentId, commentId), dateCreated
end
else
begin
if (#includeCommentsL = 1)
begin
select * from Comments (nolock)
where threadId = #threadIdL and statusCode <= #stateCutoffL
order by isnull(parentCommentId, commentId), dateCreated
end
else
begin
select userId, commentId from Comments (nolock)
where threadId = #threadIdL and statusCode <= #stateCutoffL
order by isnull(parentCommentId, commentId), dateCreated
end
end
Hmm, perhaps the tables aren't really tables. They could be views or something else.
try putting dbo. or whatever the schema is in front of all of the table names, and then check again.
see this article:
http://www.sommarskog.se/dyn-search-2005.html
quote from the article:
As you can see, I refer to all tables
in two-part notation. That is, I also
specify the schema (which in SQL
7/2000 parlance normally is referred
to as owner.) If I would leave out the
schema, each user would get his own
his own private version of the query
plan

Batch commit on large INSERT operation in native SQL?

I have a couple large tables (188m and 144m rows) I need to populate from views, but each view contains a few hundred million rows (pulling together pseudo-dimensionally modelled data into a flat form). The keys on each table are over 50 composite bytes of columns. If the data was in tables, I could always think about using sp_rename to make the other new table, but that isn't really an option.
If I do a single INSERT operation, the process uses a huge amount of transaction log space, typicalyl filing it up and prompting a bunch of hassle with the DBAs. (And yes, this is probably a job the DBAs should handle/design/architect)
I can use SSIS and stream the data into the destination table with batch commits (but this does require the data to be transmitted over the network, since we are not allowed to run SSIS packages on the server).
Any things other than to divide the process up into multiple INSERT operations using some kind of key to distribute the rows into different batches and doing a loop?
Does the view have ANY kind of unique identifier / candidate key? If so, you could select those rows into a working table using:
SELECT key_columns INTO dbo.temp FROM dbo.HugeView;
(If it makes sense, maybe put this table into a different database, perhaps with SIMPLE recovery model, to prevent the log activity from interfering with your primary database. This should generate much less log anyway, and you can free up the space in the other database before you resume, in case the problem is that you have inadequate disk space all around.)
Then you can do something like this, inserting 10,000 rows at a time, and backing up the log in between:
SET NOCOUNT ON;
DECLARE
#batchsize INT,
#ctr INT,
#rc INT;
SELECT
#batchsize = 10000,
#ctr = 0;
WHILE 1 = 1
BEGIN
WITH x AS
(
SELECT key_column, rn = ROW_NUMBER() OVER (ORDER BY key_column)
FROM dbo.temp
)
INSERT dbo.PrimaryTable(a, b, c, etc.)
SELECT v.a, v.b, v.c, etc.
FROM x
INNER JOIN dbo.HugeView AS v
ON v.key_column = x.key_column
WHERE x.rn > #batchsize * #ctr
AND x.rn <= #batchsize * (#ctr + 1);
IF ##ROWCOUNT = 0
BREAK;
BACKUP LOG PrimaryDB TO DISK = 'C:\db.bak' WITH INIT;
SET #ctr = #ctr + 1;
END
That's all off the top of my head, so don't cut/paste/run, but I think the general idea is there. For more details (and why I backup log / checkpoint inside the loop), see this post on sqlperformance.com:
Break large delete operations into chunks
Note that if you are taking regular database and log backups you will probably want to take a full to start your log chain over again.
You could partition your data and insert your data in a cursor loop. That would be nearly the same as SSIS batchinserting. But runs on your server.
create cursor ....
select YEAR(DateCol), MONTH(DateCol) from whatever
while ....
insert into yourtable(...)
select * from whatever
where YEAR(DateCol) = year and MONTH(DateCol) = month
end
I know this is an old thread, but I made a generic version of Arthur's cursor solution:
--Split a batch up into chunks using a cursor.
--This method can be used for most any large table with some modifications
--It could also be refined further with an #Day variable (for example)
DECLARE #Year INT
DECLARE #Month INT
DECLARE BatchingCursor CURSOR FOR
SELECT DISTINCT YEAR(<SomeDateField>),MONTH(<SomeDateField>)
FROM <Sometable>;
OPEN BatchingCursor;
FETCH NEXT FROM BatchingCursor INTO #Year, #Month;
WHILE ##FETCH_STATUS = 0
BEGIN
--All logic goes in here
--Any select statements from <Sometable> need to be suffixed with:
--WHERE Year(<SomeDateField>)=#Year AND Month(<SomeDateField>)=#Month
FETCH NEXT FROM BatchingCursor INTO #Year, #Month;
END;
CLOSE BatchingCursor;
DEALLOCATE BatchingCursor;
GO
This solved the problem on loads of our large tables.
There is no pixie dust, you know that.
Without knowing specifics about the actual schema being transfered, a generic solution would be exactly as you describe it: divide processing into multiple inserts and keep track of the key(s). This is sort of pseudo-code T-SQL:
create table currentKeys (table sysname not null primary key, key sql_variant not null);
go
declare #keysInserted table (key sql_variant);
declare #key sql_variant;
begin transaction
do while (1=1)
begin
select #key = key from currentKeys where table = '<target>';
insert into <target> (...)
output inserted.key into #keysInserted (key)
select top (<batchsize>) ... from <source>
where key > #key
order by key;
if (0 = ##rowcount)
break;
update currentKeys
set key = (select max(key) from #keysInserted)
where table = '<target>';
commit;
delete from #keysInserted;
set #key = null;
begin transaction;
end
commit
It would get more complicated if you want to allow for parallel batches and partition the keys.
You could use the BCP command to load the data and use the Batch Size parameter
http://msdn.microsoft.com/en-us/library/ms162802.aspx
Two step process
BCP OUT data from Views into Text files
BCP IN data from Text files into Tables with batch size parameter
This looks like a job for good ol' BCP.