CREATE VIEW AS SELECT FROM with check if table exists - sql-server

Is it possible to check, when creating a view, that the table I am selecting from actually exists? To prevent Invalid object name 'MyItems' or Incorrect syntax near keyword VIEW.
-- ok this is super simplistic
-- but it is enough to illustrate the point
CREATE VIEW vw_MyView
AS
SELECT DISTINCT Id, Name, COUNT(CategoryId) OVER (PARTITION BY Id) AS Total
FROM MyItems
GO
I have a bunch of these views that get created as part of the batch script. In this example, if MyItems doesn't exist the execution breaks the entire batch script.
So, I thought I would just add a bunch of checks in front of every CREATE VIEW statement to make sure the table(s) actually exist.
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[MyItems]') AND type IN (N'U'))
-- CREATE VIEW <snip></snip>
GO
But as it turns out the CREATE VIEW statement must be the first statement in a query batch.
Back my question, is there some kind of workaround for catching when CREATE VIEW fails to complete because it is reading from none existent table?
I am using MS SQL Server 2008 R2.

Another alternative using dynamic SQL:
DECLARE #sqlcmd NVARCHAR(MAX);
SELECT #sqlcmd =
'CREATE VIEW vw_MyView
AS
SELECT DISTINCT Id, Name, COUNT(CategoryId) OVER (PARTITION BY Id) AS Total
FROM MyItems
GO';
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[MyItems]') AND type IN (N'U'))
BEGIN
EXEC sp_executesql #sqlcmd
END
ELSE
BEGIN
PRINT 'Table doesnt exist'
END

This code May helps you,Try once
IF EXISTS (
SELECT 1
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME = 'MyItems'
AND TABLE_TYPE = 'BASE TABLE'
)
BEGIN
IF NOT EXISTS (
SELECT 1
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME = 'vw_MyView'
AND TABLE_TYPE = 'VIEW'
)
BEGIN
CREATE VIEW vw_MyView
AS
SELECT DISTINCT Id
,NAME
,COUNT(CategoryId) OVER (PARTITION BY Id) AS Total
FROM MyItems
END
END

Related

Join a table whose name is stored in the first table

I have a first table [TABLE1] with columns [ID], [Description], [DetailTable]. I want to join [TABLE1] with the [DetailTable]. The name of [DetailTable] is stored in [TABLE1] column.
"SELECT * FROM TABLE1 CROSS JOIN ?????"
Any suggestions?
So... if you cheat and SELECT * from the detailtab, you could do something a bit like this, with dynamic SQL:
-- For the example, choose either 1 or 2 to see the #foo table or the #bar table
DECLARE #Id INT = 1
-- EXAMPLE SCENARIO SETUP --
CREATE TABLE #ListOfTables
( ID INT IDENTITY(1,1) NOT NULL
,[Description] NVARCHAR(255) NOT NULL
,[DetailTable] NVARCHAR(255) NOT NULL);
CREATE TABLE #foo
(Foothing VARCHAR(20));
CREATE TABLE #bar
(Barthing VARCHAR(20));
-- TEST VALUES --
INSERT #ListOfTables VALUES ('foo','#foo'),('bar','#bar');
INSERT #foo VALUES ('A foothing Foothing');
INSERT #bar VALUES ('A barthing Barthing');
-- THE SCRIPT --
DECLARE #SQL NVARCHAR(MAX) = '';
SELECT #SQL =
' SELECT Tab.Id, Tab.[Description], Tab2.*
FROM #ListOfTables AS Tab
CROSS JOIN ' + T.DetailTable + ' AS Tab2
WHERE Tab.Id = ' + CONVERT(VARCHAR(10),#Id)
FROM #ListOfTables T
WHERE T.Id = #Id;
PRINT #SQL
EXEC sp_executesql #SQL;
-- CLEAN UP --
DROP TABLE #ListOfTables;
DROP TABLE #bar;
DROP TABLE #foo;
However, I have to agree with the comments that this is a pretty nasty way to do things. If you want to choose particular columns and the columns are different for each detail table, then things will start to get really unpleasant... Does this give you something you can start with?
Remember, the best solution will almost certainly involve redesigning things so you don't have to jump through these hoops!
All of the detail tables must have identical schema.
Create a view that unions all the tables
CREATE VIEW vAllDetails AS
SELECT 'DETAIL1' table_name, * from DETAIL1
UNION ALL
SELECT 'DETAIL2' table_name, * from DETAIL2
UNION ALL
SELECT 'DETAIL3' table_name, * from DETAIL3
When you join against this view, SQL Server can generate a plan that uses a "startup predicate expression". For example, a plan like this: sample plan. At first glance, it looks like SQL is going to scan all of the detail tables, but it won't. The left most filters include a "startup predicate", so for each row we read from table1, only if TableName matches will that branch be executed.

SQL checks Column names first instead of condition and gives me an Error Invalid column name. How to fix this?

I am trying to test a column in a source table to check if it has any duplicate values. But here the situation is, the source table does not contain that column always. I have a code where it runs fine if the Column exists but gives me an Invalid column name error if the column doesn't exist.
I tried many codes where I check for the table name and the column name before executing the code. None of them worked. Below is such example:
IF EXISTS(
SELECT *
FROM INFORMATION_SCHEMA.COLUMNS
WHERE
TABLE_NAME = 'SourceData'
AND COLUMN_NAME = 'dataid')
Begin
select'SourceData' as TableName, a.dataid, b.dupcount from SourceData a inner join (select dataid, count(*) as dupcount from SourceData group by dataid having count(*) > 1) b on a.dataid = b.dataid
End
I want a code where it should run even if the column doesn't exist where it should give me No Output or NULL Output. If that column exists then it should check for the duplicate values in that column.
The problem is that the statements are all checked at compile time already and when the objects don't exist that fails.
You can resolve this by using EXECUTE to execute your query. Though it would still fail if the objects aren't there, you can now first check for their existence and skip the EXECUTE if they aren't there. That way the compiler never "sees" the query and can never nag about the objects' non-existence.
IF EXISTS (...)
BEGIN
EXECUTE ('SELECT ...'); -- your query goes in there as a string constant
END
ELSE
BEGIN
-- do something else
END;
You can use either dynamic SQL or better is to use stored procedures for that. This is an example:
CREATE TABLE testMissingColumn
(
a int
)
GO
ALTER TABLE testMissingColumn ADD b INT;
-- this will fail as even if it exists already, at runtime it wasn't there
IF EXISTS ( SELECT *
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'testMissingColumn'
AND COLUMN_NAME = 'b' )
SELECT b FROM testMissingColumn;
-- will not fail as query is unknown at runtime
IF EXISTS ( SELECT *
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'testMissingColumn'
AND COLUMN_NAME = 'b' )
EXEC('SELECT b FROM testMissingColumn; ');
GO
-- stored procedures doesn't care about missing objects :)
CREATE PROCEDURE sfFestMissingColumn
AS
SELECT b FROM testMissingColumn;
GO
EXEC sfFestMissingColumn

Is there a way to check if an object is a table or a view in SQL Server?

I have a view that I am changing to a table in SQL Server.
I am dropping the view and then the next part of my code I am establishing the table.
My code works the first time I run it (when the object is a view), but when I need to run it multiple times, I get this error:
Cannot use DROP VIEW with 'engineer.Well' because 'engineer.Well' is a table. Use DROP TABLE.
I've been looking online but cannot find a way to check if an object is a table or a view, and the subsequently drop the object.
Any advice would be greatly appreciated.
Right now it looks like this,
IF OBJECT_ID('engineer.well') IS NOT NULL
BEGIN
DROP TABLE [engineer].[Well]
PRINT '<<< DROPPED TABLE Vendor >>>'
END
I am playing around with a way to check if the object is a table and then drop it, or check if it is a view then drop it.
You can query the system views.
DECLARE #Type varchar(2)
SELECT #Type = type
FROM sys.objects o
JOIN sys.schemas s ON o.schema_id = s.schema_id
WHERE o.name = 'well'
AND s.name = 'engineer'
IF #Type = 'U'
BEGIN
DROP TABLE [engineer].[Well]
PRINT '<<< DROPPED TABLE Vendor >>>'
END
IF #Type = 'V'
BEGIN
DROP VIEW [engineer].[Well]
PRINT '<<< DROPPED VIEW Vendor >>>'
END
OBJECTPROPERTY(OBJECT_ID('name'), 'IsView')
https://learn.microsoft.com/en-us/sql/t-sql/functions/objectproperty-transact-sql?view=sql-server-2017
or
SELECT id, type FROM sysobjects where id=OBJECT_ID('objectName')
You can query sys.objects table:
select type_desc, * from sys.objects where object_id = object_id('[dbo].[DimDates]')

SQL Query Delete rows from a table

I have a table #temp. The data in #temp are table names in a database. I wish to only show the table names of which the table has data. How can I do this without using dynamic SQL?
My sample data is as below:
create TABLE #temp (Table_Name VARCHAR(50))
insert into #temp values ('#temp1')
,('#temp2')
,('#temp3')
,('#temp4')
create TABLE #temp1 (Col1 int)
insert into #temp1 values (1)
,(3)
,(4)
create TABLE #temp2 (Col1 int)
insert into #temp2 values (7)
,(9)
,(6)
create TABLE #temp3 (Col1 int)
create TABLE #temp4 (Col1 int)
I manually delete the blank tables, How to do this using a query for numerous blank tables?
DELETE FROM #temp
WHERE Table_Name = '#temp3'
or Table_Name = '#temp4'
This is the result I want
select * from #temp
-- It only shows the two table names which are not blank
DROP TABLE #temp
DROP TABLE #temp1
DROP TABLE #temp2
DROP TABLE #temp3
DROP TABLE #temp4
This is my old query for this question:
DECLARE #TABLE_NAME VARCHAR(50), #COMMAND VARCHAR(500), #COUNT INT, #COUNTT INT
DECLARE #CountResults TABLE (CountReturned INT)
create TABLE #TABLE_NAME (TABLE_NAME VARCHAR(50))
SELECT #COUNTT= COUNT(*) FROM #temp
WHILE #COUNTT > 0
BEGIN
SELECT TOP 1 #TABLE_NAME = Table_Name FROM #temp
SET #COMMAND = 'SELECT COUNT(*) FROM ' + #TABLE_NAME
INSERT #CountResults EXEC (#COMMAND)
SET #Count = (SELECT * FROM #CountResults)
BEGIN TRANSACTION
DELETE #CountResults
ROLLBACK TRANSACTION
IF(#Count > 0)
BEGIN
INSERT INTO #TABLE_NAME VALUES (#TABLE_NAME)
END
DELETE FROM #temp WHERE Table_Name = #TABLE_NAME
SELECT #COUNTT= COUNT(*) FROM #temp
END
SELECT * FROM #TABLE_NAME
I don't know of any way to determine whether or not a table is empty without querying that table, which in your case means dynamic SQL. Your comments make it sound like you're okay with this but are looking for a way to do this more concisely than using a loop. Here's a (limited) possibility:
declare #sql nvarchar(max);
select #sql =
-- coalesce() ensures that UNION ALL is inserted before every SELECT but the first.
coalesce(#sql + N' union all ', N'') +
-- Select each table name. Note that SQL Server allows table names that contain
-- single quotes. In this case (or in the case of plain old bad/malicious data in
-- #temp), we need to make sure those characters are enclosed within the string
-- literal we're building.
N'select ''' + replace(table_name, N'''', N'''''') +
-- Use EXISTS to make sure there are one or more records in the table.
N''' where exists (select 1 from ' + quotename(table_name) + N')'
from #temp;
exec sp_executesql #sql;
This will build and execute a query that looks like this:
select '#temp1' where exists (select 1 from [#temp1])
union all
select '#temp2' where exists (select 1 from [#temp2])
union all
select '#temp3' where exists (select 1 from [#temp3])
union all
select '#temp4' where exists (select 1 from [#temp4])
This approach has a few limitations that you should be aware of:
The query will fail if #temp contains any string which is not the name of a table or view. Normally I'd suggest mitigating this by using object_id() or querying INFORMATION_SCHEMA.TABLES, but the fact that you've loaded #temp with the names of other temp tables complicates matters.
The query will also fail if #temp contains a table name that explicitly names the table schema, e.g. dbo.Stuff, because quotename() will render it as [dbo.Stuff] rather than [dbo].[Stuff]. But if you omit quotename(), you run the risk of incorrect and/or damaging behavior if a table_name contains spaces or other problematic characters.
In short, if you just want something for personal use and are okay with making certain assumptions about the data in #temp, then something like the above ought to work. But if you want something that will work correctly and safely under any circumstances, then it's going to take some doing, enough so that even if you could avoid using some kind of a loop, doing so is unlikely to make things any less complicated.
I have a method that does not use dynamic sql. It uses the sysindexes table, which according to Microsoft is subject to change at their whim. So this may not be a good candidate for a production system. But it could be a good place to start. This is also a bit easier if your source table is not a temp table, since temp tables have actual names that do not match the name used to create them.
This script worked for me on SQL Server 2008 r2.
-- drop table #MyTempTable;
Create table #MyTempTable(Table_Name varchar(50));
insert #MyTempTable values ('#MyTempTable2');
insert #MyTempTable values ('#MyTempTable3');
insert #MyTempTable values ('#MyTempTable4');
Create table #MyTempTable2 (Col1 int);
insert #MyTempTable2 values (1);
Create table #MyTempTable4 (Col1 int);
Create table #MyTempTable3 (Col1 int);
SELECT *
FROM #MyTempTable M1
JOIN tempdb.sys.tables T ON T.name LIKE (M1.Table_Name + '%')
JOIN [tempdb].[dbo].[sysindexes] S ON S.id = T.object_id
WHERE S.rowcnt > 0
It's not an ideal solution, but it satisfies your requirements. If you play around with it in your environment, it might give you some insight into a better way to achieve your larger goals. good luck.
EDIT: sysindexes will have one entry per index on the table. Or in the case of my example, for the heap (with no index.) So if your base tables have multiple indexes, you will need to modify the query a bit. Maybe change the JOIN and WHERE clause to a WHERE EXISTS SELECT * FROM [tempdb].[dbo].[sysindexes] S WHERE S.id = T.object_id AND S.rowcnt > 0 Play with it and you should be able to get where you were asking.
EDIT 2: Replacing sys.tables with sysobjects.
SELECT *
FROM #MyTempTable M1
JOIN [tempdb].[dbo].[sysobjects] O ON O.name LIKE (M1.Table_Name + '%')
JOIN [tempdb].[dbo].[sysindexes] S ON S.id = O.id
WHERE S.rowcnt > 0
Based on DeadZone's Query, the following works for non temp tables:
SELECT DISTINCT Table_Name
INTO #TABLE_NAME
FROM #Temp M1
JOIN [dbo].[sysobjects] O ON O.name LIKE (M1.Table_Name + '%')
JOIN [dbo].[sysindexes] S ON S.id = O.id
WHERE S.rowcnt > 0

Add a column to a table, if it does not already exist

I want to write a query for MS SQL Server that adds a column into a table. But I don't want any error display, when I run/execute the following query.
I am using this sort of query to add a table ...
IF EXISTS (
SELECT *
FROM sys.objects
WHERE OBJECT_ID = OBJECT_ID(N'[dbo].[Person]')
AND TYPE IN (N'U')
)
But I don't know how to write this query for a column.
You can use a similar construct by using the sys.columns table io sys.objects.
IF NOT EXISTS (
SELECT *
FROM sys.columns
WHERE object_id = OBJECT_ID(N'[dbo].[Person]')
AND name = 'ColumnName'
)
IF COL_LENGTH('table_name', 'column_name') IS NULL
BEGIN
ALTER TABLE table_name
ADD [column_name] INT
END
Another alternative. I prefer this approach because it is less writing but the two accomplish the same thing.
IF COLUMNPROPERTY(OBJECT_ID('dbo.Person'), 'ColumnName', 'ColumnId') IS NULL
BEGIN
ALTER TABLE Person
ADD ColumnName VARCHAR(MAX) NOT NULL
END
I also noticed yours is looking for where table does exist that is obviously just this
if COLUMNPROPERTY( OBJECT_ID('dbo.Person'),'ColumnName','ColumnId') is not null
Here's another variation that worked for me.
IF NOT EXISTS (SELECT 1
FROM INFORMATION_SCHEMA.COLUMNS
WHERE upper(TABLE_NAME) = 'TABLENAME'
AND upper(COLUMN_NAME) = 'COLUMNNAME')
BEGIN
ALTER TABLE [dbo].[Person] ADD Column
END
GO
EDIT:
Note that INFORMATION_SCHEMA views may not always be updated, use SYS.COLUMNS instead:
IF NOT EXISTS (SELECT 1
FROM SYS.COLUMNS....
IF NOT EXISTS (SELECT * FROM syscolumns
WHERE ID=OBJECT_ID('[db].[Employee]') AND NAME='EmpName')
ALTER TABLE [db].[Employee]
ADD [EmpName] VARCHAR(10)
GO
I Hope this would help. More info
When checking for a column in another database, you can simply include the database name:
IF NOT EXISTS (
SELECT *
FROM DatabaseName.sys.columns
WHERE object_id = OBJECT_ID(N'[DatabaseName].[dbo].[TableName]')
AND name = 'ColumnName'
)
IF NOT EXISTS (SELECT 1 FROM SYS.COLUMNS WHERE
OBJECT_ID = OBJECT_ID(N'[dbo].[Person]') AND name = 'DateOfBirth')
BEGIN
ALTER TABLE [dbo].[Person] ADD DateOfBirth DATETIME
END

Resources