How to measure data stored by 1 client in a SaaS database - size

I have a multi tenant database for my SaaS web application. I want to set a limit of 10GB data storage per Client. How do I enforce this limit in SQL Azure. I see a lot of SaaS companies putting such limits, but how to measure how much storage 1 client has consumed in the database?
Tx

One option to isolate the data is to have a separate schema per tenant. And you can measure the size of the schema using below script.
SET NOCOUNT ON
declare #SourceDB sysname
DECLARE #sql nvarchar (4000)
IF #SourceDB IS NULL BEGIN
SET #SourceDB = DB_NAME () -- The current DB
END
CREATE TABLE #Tables ( [schema] sysname
, TabName sysname )
SELECT #sql = 'insert #tables ([schema], [TabName])
select TABLE_SCHEMA, TABLE_NAME
from ['+ #SourceDB +'].INFORMATION_SCHEMA.TABLES
where TABLE_TYPE = ''BASE TABLE'''
EXEC (#sql)
---------------------------------------------------------------
-- #TabSpaceTxt Holds the results of sp_spaceused.
-- It Doesn't have Schema Info!
CREATE TABLE #TabSpaceTxt (
TabName sysname
, [Rows] varchar (11)
, Reserved varchar (18)
, Data varchar (18)
, Index_Size varchar ( 18 )
, Unused varchar ( 18 )
)
---------------------------------------------------------------
-- The result table, with numeric results and Schema name.
CREATE TABLE #TabSpace ( [Schema] sysname
, TabName sysname
, [Rows] bigint
, ReservedMB numeric(18,3)
, DataMB numeric(18,3)
, Index_SizeMB numeric(18,3)
, UnusedMB numeric(18,3)
)
DECLARE #Tab sysname -- table name
, #Sch sysname -- owner,schema
DECLARE TableCursor CURSOR FOR
SELECT [SCHEMA], TabNAME
FROM #tables
OPEN TableCursor;
FETCH TableCursor into #Sch, #Tab;
WHILE ##FETCH_STATUS = 0 BEGIN
SELECT #sql = 'exec [' + #SourceDB
+ ']..sp_executesql N''insert #TabSpaceTxt exec sp_spaceused '
+ '''''[' + #Sch + '].[' + #Tab + ']' + '''''''';
Delete from #TabSpaceTxt; -- Stores 1 result at a time
EXEC (#sql);
INSERT INTO #TabSpace
SELECT #Sch
, [TabName]
, convert(bigint, rows)
, convert(numeric(18,3), convert(numeric(18,3),
left(reserved, len(reserved)-3)) / 1024.0)
ReservedMB
, convert(numeric(18,3), convert(numeric(18,3),
left(data, len(data)-3)) / 1024.0) DataMB
, convert(numeric(18,3), convert(numeric(18,3),
left(index_size, len(index_size)-3)) / 1024.0)
Index_SizeMB
, convert(numeric(18,3), convert(numeric(18,3),
left(unused, len([Unused])-3)) / 1024.0)
[UnusedMB]
FROM #TabSpaceTxt;
FETCH TableCursor into #Sch, #Tab;
END;
CLOSE TableCursor;
DEALLOCATE TableCursor;
select [SCHEMA],SUM(ReservedMB)SizeInMB from #TabSpace
group by [SCHEMA]

Related

How do I get list of all table names in all databases on all Sql Servers using TSQL?

What is the best way to get the names of all of the tables in all Databases on all Sql Servers? There are many databases in many Servers. So I wanna list Server names and database names with table names.
For example
Id ServerName databaseName Tablename
1 server1 Product Vegetables
2 server2 Product Milks
3 server1 Customer People
Probably I will use following query with openquery() but I did'nt do it.
SET NOCOUNT ON
DECLARE #AllTables table (CompleteTableName nvarchar(4000))
INSERT INTO #AllTables (CompleteTableName)
EXEC sp_msforeachdb 'select ##SERVERNAME+''.''+''?''+''.''+s.name+''.''+t.name from [?].sys.tables t inner join sys.schemas s on t.schema_id=s.schema_id'
SET NOCOUNT OFF
SELECT * FROM #AllTables ORDER BY 1
delete from [DatabaseName].[dbo].[hak_tables]
declare #counter int;
declare #openquery nvarchar(2000), #tsql nvarchar(2000), #servername nvarchar(2000)
create table #temp(
id int,
servername nvarchar(50)
)
insert into #temp(id,servername)
values (1,'ServerName'),
(2,'[ServerName -2]'),
(3,'[ServerName -3]')
create table #tablehavuzu(
ID int identity (1,1),
sunucuAdi nvarchar(1000),
databaseName nvarchar(1000),
name nvarchar(1000),
type nvarchar(1000),
create_date datetime,
modify_date datetime
)
declare #cmd3 nvarchar(max)
set #counter = 1
while #counter <= (select count(1) from #temp)
begin
create table #databasename(
name nvarchar(1000),
counter int
)
set #servername = (select servername from #temp where id=#counter)
declare #sorg nvarchar(max)
set #sorg='insert into #databasename
select name,ROW_NUMBER() over(order by name asc) from '+#servername+'.master.sys.databases '
exec (#sorg)
declare #counter1 int;
set #counter1 = 1
declare #databaseCount int
set #databaseCount = (select count(1) from #databasename)
while #counter1 <= #databaseCount
begin
declare #databaseName nvarchar(1000),#sql nvarchar(1000)
begin try
set #databaseName = (select name from #databasename where counter=#counter1)
set #sql ='select ##SERVERNAME sunucuAdi, '''''+#databaseName+''''' as databaseName, name, type, create_date, modify_date from '+#servername+'.'+#databaseName+'.sys.tables'
set #cmd3= 'select * from openquery('+#servername+','''+#sql+''')'
insert into #tablehavuzu
exec (#cmd3)
end try
begin catch
end catch
set #counter1 = #counter1 +1;
end
set #counter = #counter + 1;
drop table #databasename
end
insert into [M3].[dbo].[hak_tables]
select * from #tablehavuzu
drop table #temp
drop table #tablehavuzu
The below query will list all the table of all the databases in an instance - So you can execute it in all the instances to get those -
declare #sql nvarchar(max);
select #sql =
(select ' UNION ALL
SELECT ' + + quotename(name,'''') + ' as database_name,
s.name COLLATE DATABASE_DEFAULT
AS schema_name,
t.name COLLATE DATABASE_DEFAULT as table_name
FROM '+ quotename(name) + '.sys.tables t
JOIN '+ quotename(name) + '.sys.schemas s
on s.schema_id = t.schema_id'
from sys.databases
where state=0
order by [name] for xml path(''), type).value('.', 'nvarchar(max)');
set #sql = stuff(#sql, 1, 12, '') + ' order by database_name,
schema_name,
table_name';
execute (#sql);

SQL query error: The multi-part identifier "sys.schemas" could not be bound [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 4 years ago.
Improve this question
I'm getting the error
The multi-part identifier sys.schemas could not be bound. The
multi-part identifier sys.tables could not be bound.
With the following code:
SET NOCOUNT ON
DECLARE #Statement NVARCHAR(MAX) = ''
DECLARE #Statement2 NVARCHAR(MAX) = ''
DECLARE #FinalStatement NVARCHAR(MAX) = ''
DECLARE #TABLE_SCHEMA SYSNAME = sys.schemas
DECLARE #TABLE_NAME SYSNAME = sys.tables
SELECT
#Statement = #Statement + 'SUM(CASE WHEN ' + COLUMN_NAME + ' IS NULL THEN 1 ELSE 0 END) AS ' + COLUMN_NAME + ',' + CHAR(13) ,
#Statement2 = #Statement2 + COLUMN_NAME + '*100 / OverallCount AS ' + COLUMN_NAME + ',' + CHAR(13) FROM
INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = #TABLE_NAME AND TABLE_SCHEMA = #TABLE_SCHEMA
IF ##ROWCOUNT = 0
RAISERROR('TABLE OR VIEW with schema "%s" and name "%s" does not exists or you do not have appropriate permissions.',16,1, #TABLE_SCHEMA, #TABLE_NAME)
ELSE
BEGIN
SELECT #FinalStatement =
'SELECT ' + LEFT(#Statement2, LEN(#Statement2) -2) + ' FROM (SELECT ' + LEFT(#Statement, LEN(#Statement) -2) +
', COUNT(*) AS OverallCount FROM ' + #TABLE_SCHEMA + '.' + #TABLE_NAME + ') SubQuery'
EXEC(#FinalStatement)
END
Where is my code wrong?
sysname is some character variant. You can't do
DECLARE #TABLE_SCHEMA SYSNAME = sys.schemas
DECLARE #TABLE_NAME SYSNAME = sys.tables
you need to use quotes as with char, varchar, etc.. Use
DECLARE #TABLE_SCHEMA SYSNAME = 'sys.schemas'
DECLARE #TABLE_NAME SYSNAME = 'sys.tables'
to fix the error.
Edit:
And since you apparently want to use these values to filter on the schema name and table name separately (
WHERE TABLE_NAME = #TABLE_NAME AND TABLE_SCHEMA = #TABLE_SCHEMA
) you should only assign the right part to each variable:
DECLARE #TABLE_SCHEMA SYSNAME = 'some_schema_name'
DECLARE #TABLE_NAME SYSNAME = 'some_table_name'
A schema with the name sys.schemas is unlikely to exist. As well as a table named sys.tables (I'm talking about just the name. Of course this is otherwise a qualified identifier including the schema.).
E.g.:
DECLARE #TABLE_SCHEMA SYSNAME = 'sys'
DECLARE #TABLE_NAME SYSNAME = 'tables'
(But sys.tables doesn't seem to bee listed in information_schema.columns. But I guess your not after system but "regular" tables anyway.)
I think what you are looking for has similarities to what this person achieved here:
recursively go through each table and find the total number of NULL values.
But do bear in mind that this can take a while depending on the size of your database.
<!-- language: lang-SQL -->
DECLARE #schemaName AS sysname;
DECLARE #tableName AS sysname;
DECLARE #columnName AS sysname;
DECLARE #schema_id AS int;
DECLARE #object_id AS int;
DECLARE #column_id AS int;
DECLARE #isNullable AS bit;
DECLARE #lastSchema_id AS int;
DECLARE #lastTable_id AS int;
DECLARE #recordCount AS bigint;
DECLARE #nullCnt AS bigint;
DECLARE #SQL as nvarchar(max);
DECLARE #paramDefinition NVARCHAR(max);
if exists(select name from tempdb..sysobjects where name LIKE'#Columns%')
DROP TABLE #Columns;
CREATE TABLE #Columns (
schema_id int,
object_id int,
column_id int,
schemaName sysname,
tableName sysname,
columnName sysname,
recordCnt bigint,
nullCnt bigint,
nullPct numeric(38,35) );
-- Set to the #lastSchema_id and #lastTable_id to NULL so that the first
-- loop through the cursor the record count is generated.
SET #lastSchema_id = NULL;
SET #lastTable_id = NULL;
-- List of all the user schemas.tables.columns
-- in the database
DECLARE c_Cursor CURSOR FOR
SELECT schemas.schema_id
, all_objects.object_id
, all_columns.column_id
, schemas.name AS schemaName
, all_objects.name AS tableName
, all_columns.name AS columnName
, all_columns.is_nullable
FROM sys.schemas
INNER JOIN sys.all_objects
ON schemas.schema_id = all_objects.schema_id
AND all_objects.type = 'U'
INNER JOIN sys.all_columns
ON all_objects.object_id = all_columns.object_id
ORDER BY schemas.schema_id
, all_objects.object_id
, all_columns.column_id;
OPEN c_Cursor;
FETCH NEXT FROM c_Cursor
INTO #schema_id
, #object_id
, #column_id
, #schemaName
, #tableName
, #columnName
, #isNullable;
-- Loop through the cursor
WHILE ##FETCH_STATUS = 0
BEGIN
-- Get the record count for the table we are currently working on if this is
-- the first time we are encountering the table.
IF ( ( #schema_id <> #lastSchema_id ) OR ( #object_id <> #lastTable_id )
OR ( #lastSchema_id IS NULL ) OR ( #lastTable_id IS NULL ) )
BEGIN
SET #SQL = N'SELECT #recordCount = COUNT(1) FROM ' + QUOTENAME(#schemaName) + N'.' + QUOTENAME(#tableName) + ';';
SET #paramDefinition = N'#recordCount bigint OUTPUT';
exec sp_executesql #SQL,
#paramDefinition,
#recordCount = #recordCount OUTPUT;
END
-- If the column is NOT NULL, there is no reason to do a count
-- Set the nullCnt and nullPct to 0
IF ( #isNullable = 0 )
BEGIN
INSERT INTO #Columns
( [schema_id]
, [object_id]
, column_id
, schemaName
, tableName
, columnName
, recordCnt
, nullCnt
, nullPct )
VALUES
( #schema_id
, #object_id
, #column_id
, #schemaName
, #tableName
, #columnName
, #recordCount
, 0
, 0.0 );
END
-- If the column is NULL, count the number of NULL fields in the table.
ELSE
BEGIN
SET #SQL = N'SELECT #nullCnt = COUNT(1) FROM ' + QUOTENAME(#schemaName) + N'.' + QUOTENAME(#tableName) +
N' WHERE ' + QUOTENAME(#columnName) + N' IS NULL;';
SET #paramDefinition = N'#nullCnt bigint OUTPUT';
PRINT #SQL;
exec sp_executesql #SQL,
#paramDefinition,
#nullCnt = #nullCnt OUTPUT;
INSERT INTO #Columns
( [schema_id]
, [object_id]
, column_id
, schemaName
, tableName
, columnName
, recordCnt
, nullCnt
, nullPct )
VALUES
( #schema_id
, #object_id
, #column_id
, #schemaName
, #tableName
, #columnName
, #recordCount
, #nullCnt
-- USE NULLIF in case there are no recods in the table
, ISNULL( #nullCnt * 1.0 / NULLIF( #recordCount, 0) * 100.0, 0 ) );
END
-- Set the #lastSchema_id and #lastTable_id so that on
-- the next loop, if it's the same table there is no
-- need to recount the columns for the table.
SET #lastSchema_id = #schema_id;
SET #lastTable_id = #object_id;
FETCH NEXT FROM c_Cursor
INTO #schema_id
, #object_id
, #column_id
, #schemaName
, #tableName
, #columnName
, #isNullable;
END;
CLOSE c_Cursor;
DEALLOCATE c_Cursor;
SELECT *
FROM #Columns;
https://social.msdn.microsoft.com/Forums/sqlserver/en-US/3e097cad-f355-4f7e-b265-f7e6137670bf/challenge-how-to-count-nulls-for-each-column-in-every-table-in-vldb-etc?forum=transactsql

SQL Loop through all tables and get the max value from a specific column

I'm trying to create an audit table that checks the loaded date for that table.
Basically, I want to loop through all tables in the database and check for a specific column - LoadedDate and return the max value for that column for each table
SELECT TABLE_NAME
INTO #TableList
FROM INFORMATION_SCHEMA.COLUMNS
WHERE column_name = 'LoadedDate'
SELECT MAX(LoadedDate) FROM #TableName -- I guess using a cursor to loop through #TableList
in to a results table
TableName LoadedDate
Table 1 2016-06-01
Table 2 2016-07-01
Table 3 2016-06-01
and so on.
You can try this code, but it will consume some time
SELECT TABLE_NAME,TABLE_SCHEMA
INTO #TableList
FROM INFORMATION_SCHEMA.COLUMNS
WHERE column_name = 'LoadedDate'
CREATE TABLE #TempResult (TableName VARCHAR(100), MaxDate DATETIME2)
DECLARE #TableName VARCHAR(100)
,#TableSchema VARCHAR(100)
DECLARE #SqlQuery NVARCHAR(MAX)
WHILE(EXISTS(SELECT TOP(1) * FROM #TableList))
BEGIN
SELECT TOP(1) #TableName = TABLE_NAME, #TableSchema = TABLE_SCHEMA FROM #TableList
DELETE #TableList WHERE TABLE_NAME = #TableName
SET #TableName = #TableSchema +'.'+ #TableName
SET #SqlQuery = 'SELECT '''+#TableName+''' AS ''TableName'', MAX(UpdatedDate) AS MaxDate FROM '+ #TableName
INSERT INTO #TempResult
EXECUTE sp_executesql #SqlQuery
END
SELECT * from #TempResult
DROP TABLE #TableList
DROP TABLE #TempResult
Perhaps a little dynamic SQL
Select Table_Name = cast('' as varchar(150))
,LoadedDate = GetDate()
Into #TableList
Where 1=0
Declare #SQL varchar(max) = '>>>'
Select #SQL = #SQL + SQL
From (
Select Table_Name,SQL='Union All Select Table_Name='''+Table_Name+''',LoadedDate=max(LoadedDate) From ['+Table_Name+'] '
From INFORMATION_SCHEMA.COLUMNS
Where column_name Like '%UTC%' --= 'LoadedDate'
) A
Set #SQL = Replace(#SQL,'>>>Union All','Insert Into #TableList ')
Exec(#SQL)
Select * from #TempResult

Truncate tables in databases T-SQL stored procedure

I am planning to write a stored procedure in T-SQL to truncate the tables in a specific database and schema.
The idea is to store all the table names in a table. The table has got 4 columns, those are database name, schema name, table name and flag.
If the flag value is set 0, then the stored procedure should truncate all those tables.
Can anybody give some insight on this?
Suppose you have a table called #tables where you store the details of the tables to be truncated, You can use the following logic/cursor inside your stored procedure to Truncate these tables.
Table Holding Tables Info
DECLARE #Tables TABLE (DBName SYSNAME , SchemaName SYSNAME
,TableName SYSNAME, Flag INT);
INSERT INTO #Tables VALUES
('TestDB' , 'dbo', 'TestTable1', 0),
('TestDB' , 'dbo', 'TestTable2', 0),
('TestDB' , 'dbo', 'TestTable3', 1)
Stored Procedure Code
Declare #DB SYSNAME
, #Schema SYSNAME
, #Table SYSNAME
, #Sql VARCHAR(MAX);
Declare Cur CURSOR LOCAL FAST_FORWARD FOR
SELECT DBName , SchemaName , TableName
FROM #Tables
WHERE Flag = 0
OPEN Cur
FETCH NEXT FROM Cur INTO #DB , #Schema , #Table
WHILE (##FETCH_STATUS = 0)
BEGIN
SET #Sql = N'TRUNCATE TABLE '
+ QUOTENAME (#DB) + '.' + QUOTENAME (#Schema) + '.' + QUOTENAME (#Table)
Exec sp_executesql #Sql
FETCH NEXT FROM Cur INTO #DB , #Schema , #Table
END
CLOSE Cur
DEALLOCATE Cur
Perhaps something like this
Declare #Table table (dbname varchar(50),schem varchar(50),tablename varchar(50),flag bit)
Insert into #Table (dbname,schem,tablename,flag) values
('dbName1','dbo','MyTable1',0),
('dbName2','dbo','MyTableA',1),
('dbName1','dbo','MyTableB',0),
('dbName2','dbo','MyTableZ',1),
('dbName1','dbo','MyTable2',0)
Declare #SQL varchar(max) = ''
Select #SQL = coalesce(#SQL,' ') + ';truncate table [' +dbname +'].[' + schem +'].[' + tablename +']'
From #Table
Where Flag=1
Select #SQL
--Exec(#SQL)

How can I get all table names, row counts and their data sizes in a SQL Server 2012 database?

I need to analyze my database. I want to get all table names, their record counts and actual data size of these tables. As you know, record count sometimes may be low but actual size of table can be very high.
Do you have such a script?
Will this work for you? It's a script I use:
SELECT
S.name +'.'+ T.name as TableName,
Convert(varchar,Cast(SUM(P.rows) as Money),1) as [RowCount],
Convert(varchar,Cast(SUM(a.total_pages) * 8 as Money),1) AS TotalSpaceKB,
Convert(varchar,Cast(SUM(a.used_pages) * 8 as Money),1) AS UsedSpaceKB,
(SUM(a.total_pages) - SUM(a.used_pages)) * 8 AS UnusedSpaceKB
FROM sys.tables T
INNER JOIN sys.partitions P ON P.OBJECT_ID = T.OBJECT_ID
INNER JOIN sys.schemas S ON T.schema_id = S.schema_id
INNER JOIN sys.allocation_units A ON p.partition_id = a.container_id
WHERE T.is_ms_shipped = 0 AND P.index_id IN (1,0)
GROUP BY S.name, T.name
ORDER BY SUM(P.rows) DESC
By the way, I cast the counts as money so I can get commas.
Another method using CURSOR and temp table.
IF OBJECT_ID(N'tempdb..[#TableSizes]') IS NOT NULL
DROP TABLE #TableSizes ;
GO
CREATE TABLE #TableSizes
(
TableName nvarchar(128)
, [RowCount] int
, ReservedSpaceKB int
, DataSpaceKB int
, IndexSizeKB int
, UnusedSpaceKB int
) ;
GO
DECLARE RecCountCursor CURSOR FOR
SELECT S.name+'.'+T.name AS TableName
FROM Sys.tables T INNER JOIN sys.schemas S ON (S.schema_id = T.schema_id)
WHERE S.principal_id = 1
ORDER BY TableName
OPEN RecCountCursor ;
DECLARE #TableName nvarchar(128) ;
FETCH RecCountCursor INTO #TableName ;
WHILE ( ##FETCH_STATUS = 0 )
BEGIN
CREATE TABLE #TempTableSizes
(
[TableName] nvarchar(128)
, [RowCount] char(11)
, [ReservedSpace] varchar(18)
, [DataSpace] varchar(18)
, [IndexSize] varchar(18)
, [UnusedSpace] varchar(18)
) ;
INSERT INTO #TempTableSizes
exec sp_spaceused #objname = #TableName;
UPDATE #TempTableSizes SET [TableName] = #TableName;
INSERT INTO #TableSizes
SELECT [TableName], [RowCount],
SUBSTRING([ReservedSpace], 1, CHARINDEX(' KB', [ReservedSpace])),
SUBSTRING([DataSpace], 1, CHARINDEX(' KB', [DataSpace])),
SUBSTRING([IndexSize], 1, CHARINDEX(' KB', [IndexSize])),
SUBSTRING([UnusedSpace], 1, CHARINDEX(' KB', [UnusedSpace]))
FROM #TempTableSizes
DROP TABLE #TempTableSizes;
FETCH RecCountCursor INTO #TableName ;
end
CLOSE RecCountCursor ;
DEALLOCATE RecCountCursor ;
SELECT *
FROM [#TableSizes]
ORDER BY [ReservedSpaceKB] DESC ;
DROP TABLE #TableSizes ;
Use:
SELECT ##servername;
IF EXISTS(SELECT name FROM tempdb.sys.tables WHERE name LIKE '#spaceUsed%')
BEGIN
DROP TABLE #spaceUsed;
END;
CREATE TABLE #spaceUsed (
name VARCHAR(255) ,
rows INT ,
reserved VARCHAR(50) ,
data VARCHAR(50) ,
index_size VARCHAR(50) ,
unused VARCHAR(50));
EXEC sp_msforeachtable
#command1 ='
--
INSERT INTO #spaceUsed
exec sp_spaceused N''?'';
'
,#whereand = ' And Object_id In (Select Object_id From sys.objects
Where SCHEMA_NAME(Schema_ID) like ''%'')';
DECLARE
#spaceUsedData TABLE (
name VARCHAR(255) ,
rows INT ,
reservedMB BIGINT NULL ,
dataMB BIGINT NULL ,
index_sizeMB BIGINT NULL ,
unusedMB BIGINT NULL);
INSERT INTO #spaceUsedData (name , rows , reservedMB , dataMB ,index_sizeMB ,unusedMB)
SELECT name , rows ,
Convert ( BIGINT ,Ltrim(Rtrim(Replace(reserved ,'KB' ,'')) ))/1024 ,
Convert ( BIGINT ,Ltrim(Rtrim(Replace(data ,'KB' ,'')) ))/1024 ,
Convert ( BIGINT ,Ltrim(Rtrim(Replace(index_size ,'KB' ,'')) ))/1024 ,
Convert ( BIGINT ,Ltrim(Rtrim(Replace(unused ,'KB' ,'')) ))/1024
FROM #spaceUsed;
SELECT * , reservedMB+ dataMB+index_sizeMB+unusedMB AS TotalMB FROM #spaceUsedData
ORDER BY rows DESC;