SQL Server validation fails - sql-server

I am generating an idempotent SQL Script out of EF Core migrations. The script first runs custom SQL Statements and then drops a column. When I re-run the script, it fails because it says the column does not exist. This would be great, but our migrations have the standard EF Core guard that makes sure the code is not executed twice if the migration ran through. I have validated that the code is in fact not run and the migration is guarded correctly.
Somehow the SQL Statement fails before running. Error: [S0001][207] Line 6: Invalid column name 'MyId'.
Am I missing something or is this a known behavior of SQL Server?
IF NOT EXISTS(SELECT *
FROM [__EFMigrationsHistory]
WHERE [MigrationId] = N'Migration1')
BEGIN
SELECT MyId FROM MyTable;
END;
GO
IF NOT EXISTS(SELECT *
FROM [__EFMigrationsHistory]
WHERE [MigrationId] = N'Migration1')
BEGIN
ALTER TABLE [MyTable]
DROP COLUMN [MyId];
END;
GO
IF NOT EXISTS(SELECT *
FROM [__EFMigrationsHistory]
WHERE [MigrationId] = N'Migration1')
BEGIN
INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion])
VALUES (N'Migration1', N'6.0.9');
END;
GO

Related

Why is the table inside a non-met IF being validated before condition is met, resulting in error if table does not exist?

I am trying to execute a procedure with a parameter, and depending on the value of the parameter, three different IF conditions will be evaluated to verify which query it will execute from a linked server.
But when I execute the query, it seems to be checking if the tables inside all the IF exists before starting the query. And I know that only one of the table exists, that is why I am using the parameter, so it shouldn't fail. but I anyhow get the following error:
Msg 7314, Level 16, State 1, Line 25
The OLE DB provider "Microsoft.ACE.OLEDB.16.0" for linked server "LinkedServer" does not contain the table "D100". The table either does not exist or the current user does not have permissions on that table.
So in this code, assume that the parameter is 300. then I get the message above.
Do you know, if there is a way, to limit the query to do not check all the tables, but only the one where the IF condition will be met?
ALTER PROCEDURE[dbo].[Import_data]
#p1 int = 0
AS
BEGIN
SET NOCOUNT ON;
IF(#p1 = 100)
BEGIN
DROP TABLE IF EXISTS Table1
SELECT [Field1], [Field2], [Field3], [Field4], [Field5], [Field6]
INTO Table1
FROM[LinkedServer]...[D100]
END
IF(#p1 = 200)
BEGIN
DROP TABLE IF EXISTS Table2
SELECT[Field1], [Field2], [Field3], [Field4], [Field5], [Field6]
INTO Table2
FROM[LinkedServer]...[D200]
END
IF(#p1 = 300)
BEGIN
DROP TABLE IF EXISTS Table3
SELECT[Field1], [Field2], [Field3], [Field4], [Field5], [Field6]
INTO Table3
FROM[LinkedServer]...[D300]
END
END
I have tried googling it, but I found mostly workarounds as running a sub procedure, but it is not really a clean solution, I think.
Okay, it seems I that I found the answer. Even with an IF statement, the SQL Server validates the entire query before executing it, so the way to overcome it, is to use a Dynamic SQL Query.
"SQL Server Dynamic SQL is a programming technique that allows you to construct SQL statements dynamically at runtime. It allows you to create more general purpose and flexible SQL statement because the full text of the SQL statements may be unknown at compilation."
This is how the query looks now. so instead of multiple IF statements, the query changes dynamically depending on the parameter.
DECLARE #SQL NVARCHAR(MAX)
SET #SQL = N'DROP TABLE IF EXISTS Table1;
SELECT [Field1]
,[Field2]
,[Field3]
,[Field4]
,[Field5]
,[Field6]
INTO Table1
FROM [LinkedServer]...[D' + CONVERT(nvarchar(3),#p1) + N']'
EXEC sp_executesql #SQL

Visual Studio SQL Server database not migrating successfully because of missing column that seems to be present

At one point a few years ago I setup a project in Visual Studio to output to an Azure SQL V12 database, and tried to use sqlcmd with migration scripts (I say this also to point out that at one point everything did work, and everything discussed below made it into production).
My postdeployment script looks something like this:
PRINT N'Starting post deployment';
:setvar WorkDirectory "F:\path\to\scripts"
PRINT N'Starting 1.01';
:r $(WorkDirectory)\1.01\Deployment.sql
PRINT N'Starting 1.02';
:r $(WorkDirectory)\1.02\Deployment.sql
PRINT N'Starting 1.03'; :r $(WorkDirectory)\1.03\Deployment103.sql
PRINT N'Starting 1.04'; :r $(WorkDirectory)\1.04\Deployment104.sql
After not making any changes to the project (not consciously anyway), I've discovered that when I try and debug/generate the database and apply the migration scripts, I get an error because a column is referenced that doesn't exist on the table. The following is the offending part from the Deployment104.sql file:
IF NOT EXISTS (SELECT *
FROM sys.columns
WHERE Name = N'MissingColumn'
AND Object_ID = Object_ID(N'TableA'))
BEGIN
ALTER TABLE TableA
ADD MissingColumn INT NOT NULL
END
GO
IF NOT EXISTS (SELECT 1
FROM information_schema.Routines
WHERE ROUTINE_NAME = 'LIST_CODES'
AND ROUTINE_TYPE = 'PROCEDURE'
AND ROUTINE_SCHEMA = 'dbo' )
BEGIN;
EXEC('CREATE PROCEDURE dbo.LIST_CODES as set nocount on;')
END
GO
ALTER PROCEDURE [dbo].LIST_CODES
AS
SELECT *
FROM CodesTable ct
LEFT JOIN TableA ta ON ct.CodeId = ta.CodeId
WHERE ta.MissingColumn= 1999
ORDER BY ct.CodeId
GO
The Db ends up partially created at this point. However, when I run that first check for whether the column exists, I actually get an entry back, implying that the column does exist in TableA. And yet I don't see it in Server Explorer (VS 2019 preview 16.4), and obviously the next part of the script doesn't see it either.
What am I doing wrong, and/or how can I make the script and the db agree as to which columns exist and which do not?
EDIT: as an update, while running the PostDeploymentScript fails, running the problematic script itself (Deployment104 in this case) works just fine, after which both the column and the stored procedure show up exactly as expected in the server explorer and can be referenced by other queries.
I believe the issue is that you are trying to add a non null column to an existing table. You would need to add a default value for example of 0 or use a data type that supports null. For example:
BEGIN
alter table TableA
ADD MissingColumn int NOT NULL DEFAULT 0
END
There is also a semicolon after the second BEGIN.
Current:
IF NOT EXISTS ( SELECT 1 FROM information_schema.Routines
WHERE ROUTINE_NAME = 'LIST_CODES'AND ROUTINE_TYPE = 'PROCEDURE' AND ROUTINE_SCHEMA = 'dbo' )
BEGIN;
EXEC('CREATE PROCEDURE dbo.LIST_CODES as set nocount on;')
END
GO
Fixed:
IF NOT EXISTS ( SELECT 1 FROM information_schema.Routines
WHERE ROUTINE_NAME = 'LIST_CODES'AND ROUTINE_TYPE = 'PROCEDURE' AND ROUTINE_SCHEMA = 'dbo' )
BEGIN
EXEC('CREATE PROCEDURE dbo.LIST_CODES as set nocount on;')
END
GO
After these changes I was able to get the statement to run in sqlfiddle. Here is the link http://sqlfiddle.com/#!18/51253/3
The answer ended up being splitting the command into its own batch, unfortunately not detectable through the posted code. The last instruction in Deployment103.sql was an ALTER PROCEDURE, which has to appear by itself in a batch, but the terminating GO was missing.
It looks like t-sql won't complain about this (because in the file, it does look like it's along in its batch). That led to the next instructions not working correctly, until the first GO statement in Deployment104.sql.
Adding the following GO resolved the issues (as does terminating Deployment103.sql with a GO)
PRINT N'Starting 1.03'; :r $(WorkDirectory)\1.03\Deployment103.sql
PRINT N'Starting 1.04';
GO
:r $(WorkDirectory)\1.04\Deployment104.sql

INSERT in Stored Procedure called via JDBC

I have an MSSQL Server 2008 (Express) set up.
In my database I have a set of tables and a stored procedure.
What I want to achieve is get any changes that have been made to an existing table, and return them at the end of the procedure. The stored procedure I have created works fine when I run it locally within MSSQL Management Studio.
However, when I call the procedure through a JDBC connection certain parts of the procedure seem to have not completed.
The summary of what I'm trying to do is as follows:
1) Put a snapshot of the data contained in CurrentTableA into #CurrentShotA (temporary table)
2) Compare #CurrentShotA with PreviousTableA
3) Insert differences into #TempTableB
(this equates to new rows or altered rows in #CurrentShotA)
4) Empty PreviousTableA
5) Insert contents of #CurrentShotA into PreviousTableA
6) Select * from #TempTableB (return all new rows and changes)
Step 6 returns the data correctly the first time it is called via JDBC.
When the procedure is called the second and subsequent times it is clear that step 5 has not completed as expected. PreviousTableA is always empty when it should contain a snapshot of the old data.
Question is why does the procedure work properly when called with in MSSQL Management studio but not when I call it via JDBC?
CREATE PROCEDURE [dbo].[getUpdatedSchedules]
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Check of the temporary table exists, delete if it does
IF OBJECT_ID('#TempTableB','U')IS NOT NULL
begin
drop table #TempTableB
end
-- Force the creation of the temporary tables quickly
select * into #TempTableB from dbo.CurrentTableA where 1=0
select * into #CurrentShotA from dbo.CurrentTableA where 1=0
-- Get the differences between schedules and put into #TempTableB
insert #CurrentShotA select * from dbo.CurrentTableA
insert #TempTableB select * from #CurrentShotA
except select * from dbo.PreviousTableA
TRUNCATE TABLE dbo.PreviousTableA
insert dbo.PreviousTableA select * from #CurrentShotA
select * from #TempTableB
END
GO
I'm new enough to stored procedures and MSSQL configuration so I have considered that it might be a permissions issue. I login to MSSQL Studio using SQL authentication that is not linked to a windows account and the procedure runs as normal so I don't think it's permissions.
I hope my explanation and question is clear enough. I'd appreciate any thoughts or suggestions as to what I am doing wrong.
There's no need to check if #TempTableB exists since it will be dropped automatically once the procedure finishes executing, just as the other temp tables are dropped automatically; perhaps you expect these tables to have data on subsequent calls to the proc and that's why you think it doesn't work when you call it from Java?
When you execute these SQL statements from SSMS they may have data in them since it's all the same database session, but that's not the case when called using JDBC.

Error with the below script

I am performing the below operation. I am getting the error and unable to find what the error is.Could any one help me finding it.
a) Check for the availability of DESTINATION data base. If it is not exist, create the data base and move the tables to the data base.
b) If the table exists in the DESTINATION data base then no process required for the table.
if db_id('Destination')is null
begin
Create database Destination
select * into TabDestination from [Source].[dbo].[TabSource]
end
else
begin
use Destination
go
if('TabDestination' in (select name from sys.objects where type = 'u'))
insert into TabDestination select * from [Source].[dbo].[TabSource]
end
I am getting fallowing error
Msg 911, Level 16, State 1, Line 8
Database 'Destination' does not exist. Make sure that the name is entered correctly.
Msg 102, Level 15, State 1, Line 3
Incorrect syntax near 'end'.
Your problem is with the USE, from the documentation:
USE is executed at both compile and execution time...
If the database specified doesn't exist at compile time then the query will fail. You can see this by trying to run the following query:
IF 1 = 2
BEGIN
USE NonexistantDatabase
END
This query fails despite the fact that the USE statement is never executed.
You should instead change your query to use database qualified names, for example:
INSERT INTO Destination.dbo.Table SELECT * FROM Source.dbo.Table
Few problems here:
After Create database Destination you need to use that database before you do the select * into TabDestination... as you will create TabDestination in some other DB.
The Go in the middle of the begin...end block won't work.
To specify your database for the inserts to TabDesitination you'd be better to use the fully qualified name of the table than calling Use, eg Insert Destiation.dbo.TabDestination...
You need to use If Exists (select... for the second if statement.
Because your Database may not exists when the script compiles, a lot of the sql needs to be exec'd dynamically.
So your script could be re-written as:
if db_id('Destination')is null
begin
Create database Destination
exec ('select * into Destination.dbo.TabDestination from [Source].[dbo].[TabSource]')
end
else
begin
if exists (select name from Destination.sys.objects where name = 'TabDestination' and type = 'u')
insert into Destination.dbo.TabDestination select * from [Source].[dbo].[TabSource]
end
A variation on #Jon Egerton's answer, however there is one case you've neglected to cover: the database exists but the table does not.
DECLARE #sql NVARCHAR(MAX) = N'SELECT *
INTO Destination.dbo.TabDestination
FROM Source.dbo.TabSource;';
IF DB_ID('Destination') IS NULL
BEGIN
PRINT 'Creating database...';
CREATE DATABASE Destination;
PRINT 'Selecting into new table...';
EXEC sp_executeSQL #sql;
END
ELSE
BEGIN
IF EXISTS (SELECT 1 FROM Destination.sys.tables WHERE schema_id = 1
AND name = N'TabDestination')
BEGIN
PRINT 'Inserting into existing table...';
INSERT Destination.dbo.TabDestination SELECT * FROM Source.dbo.TabSource;
END
ELSE
BEGIN
PRINT 'Selecting into new table...';
EXEC sp_executeSQL #sql;
END
END
EDIT
Added PRINT statements for debugging purposes, as I suggested in the follow-up to #Jon's answer.
You just need to get rid of the GO command, its a batch separator so it breaks your begin/end. Oh and you can't use USE like that either.

SQL script runs fine on one database, errors on another

We have a script that must allow for being re-run several times.
We have an MS-SQL script that updates a table if a (now obsolete) column exists, then deletes the column. To ensure that the script can be run several times, it first checks for the existence of a column before performing the updates.
The script works as expected on our dev database, updating the data on the first run, then displaying the message 'Not updating' on subsequent runs.
On our test database the script runs fine on the first run, but errors with "Invalid column name 'OldColumn'" on subsequent runs; if I comment out the UPDATE and ALTER statements it runs as expected.
Is there a way to force the script to run even if there's a potential error, or is it something to do with how the database was set-up? (fingers crossed I'm not looking like a complete noob!)
IF EXISTS (SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'MyTable' AND COLUMN_NAME = 'OldColumn')
BEGIN
PRINT 'Updating and removing old column...'
UPDATE MyTable SET NewColumn='X' WHERE OldColumn=1;
ALTER TABLE MyTable DROP COLUMN OldColumn;
END
ELSE
PRINT 'Not updating'
GO
As a work around you could do
IF EXISTS (SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'MyTable' AND COLUMN_NAME = 'OldColumn')
BEGIN
PRINT 'Updating and removing old column...'
EXEC ('UPDATE MyTable SET NewColumn=''X'' WHERE OldColumn=1;');
ALTER TABLE MyTable DROP COLUMN OldColumn;
END
ELSE
PRINT 'Not updating'

Resources