SQL Server procedure / function to copy table to another database - sql

I need to copy a table from one database to another. My first guess was using following statement from my source code:
SELECT *
INTO TARGETDB.SCHEMA.TABLENAME
FROM SCHEMA.TABLENAME
(Edit: I know it won't set Primary Key's etc, that's okay)
Sadly the classes I have to use in my project pretty much destroy this statement and I have no possibility to work around that.
My next idea was creating a function or procedure in the SQL Server database, so I could use
SCHEMA.FUNCTNAME paramTARGETDB, paramTABLENAME
as statement from my code.
My current procedure looks like this:
CREATE PROCEDURE [SCHEMA].FUNCTNAME
#pVC_TARGETDB VARCHAR(240),
#pVC_TABLENAME VARCHAR(240)
AS
BEGIN
SELECT *
INTO #pVC_TARGETDB.SYSADM.#pVC_TABLENAME
FROM SCHEMA.#pVC_TABLENAME
END
but my knowledge of SQL isn't that big, and SQL Server Management Studio tells me there are syntax errors. Every #pVC_* is marked (Edit: In the first two occurances, they're not marked. Only in the block between BEGIN and END). The message I get is:
"Syntax Error near #pVC_* Expecting ID, QUOTED_ID or '.'."
I tried nearly every way of writing it I could imagine or find with Google, maybe it's easy to solve, maybe it's not. I couldn't do it, please help me.

You can dynamically create the SQL statement then execute it
DECLARE #SQL NVARCHAR(MAX)
SET #SQL =
('SELECT *
INTO ' + #pVC_TARGETDB + '.SYSADM.' + #pVC_TABLENAME +
' FROM
SCHEMA.' + #pVC_TABLENAME)
EXEC (#SQL)

Related

Insert data from linked server to another server in SQL without using openquery

I am trying to query data from a table in a linked server and insert it into a table in another server. I know this may easily be done using openquery(), however, I am not able to use that function here as my query is longer than the 800 character limit. So after some research I saw that I must use EXEC instead. I am still receiving errors. I know Oracle is a component as well and perhaps that is why the [server].[db].[schema].[table] syntax does not work.
I have never worked with linked servers before and I am fairly new to sql anyway so maybe there is something obvious I am missing although I have been researching this for quite some time now. Any tips or suggestions for an alternative method is much appreciated !
A shell of my code is below.
Declare #sql varchar(max) =
'
SELECT * INTO [server_name].[DB_name].[schema_name].[table]
FROM
(
select ….
from
) x'
EXEC (#sql) AT [Linked_Server_Name]

RPG Embedded SQL delete several records with a list

How can I delete specific records from a table? I have a list with objects which I have to delete.
I've tried to save these names in a variable withh seperators and compare the key column with these variable.
PGNALIST = '(''PGM1'',''PGM2'',''PGM3'',...)';
EXEC SQL DELETE FROM FILE WHERE FILEPGNA IN :PGNALIST;
But it only works if I have a variable with just ONE program name.
Do know how I can solve these problem?
Also need these for SQL updates...
The goal ist to use embedded Sql further.
Thanks a lot
Can't do it like that...
For static SQL, you'd have to have multiple variables:
EXEC SQL
DELETE FROM FILE
WHERE FILEPGNA IN (:p1, :p2, :p3, ...);
Of course you need to know how many, or at least the maximum number of values you'll need to pass in.
Alternatively, you can use dynamic SQL to build the statement at runtime..
dcl-c QUOTE '''';
dcl-s myStmt varchar(1000);
myStmt = 'delete from file where filepgna in ('
+ QUOTE + 'PGM1' + QUOTE
+ QUOTE + 'PGM2' + QUOTE
+ QUOTE + 'PGM3' + QUOTE
+ ')';
exec sql
execute immediate :myStmt;
note that the QUOTE constant just makes life a little easier.
Charles is correct in that you can't do this directly. Frequently these types of problems arise when you haven't gone far enough with SQL. A typical SQL solution would pull the delete list from another table doing something like below:
EXEC SQL
DELETE FROM FILE
WHERE FILEPGNA IN (
SELECT KEY FROM OTHERTABLE WHERE KEY LIKE 'PGM%');
If you are already pulling PGNALIST from another table, this is a lot more efficient and a lot less code.

Customizable database names and TempDB

I have a lump of SQL that looks a little like this
IF NOT EXISTS (SELECT * FROM sys.databases WHERE name = '{FOO}')
BEGIN
EXECUTE ('CREATE DATABASE {FOO}')
ALTER DATABASE {FOO} SET AUTO_CLOSE OFF
END
{FOO} is replaced at runtime with the name of a user configurable database. The logic is that I don't want to create the database if it already exists.
If {FOO} is tempdb then I get a failure when the query runs
Option 'AUTO_CLOSE' cannot be set in database 'tempdb'.
My question is why do I get this failure? SELECT * FROM sys.databases WHERE name = 'tempdb' returns zero results so surely my whole BEGIN/END pair shouldn't run? Indeed, if I put a print statement between begin and end, I don't see any output.
My guess is that SQL Server is doing some kind of linting on the SQL to make sure I don't muck around with tempdb? I have solved the problem by using EXECUTE instead, but I'm a little confused why I have to!
Try ensuring both commands are separate and within dynamic SQL, then the change to tempdb won't be caught by the parser:
EXEC sp_executesql N'CREATE DATABASE {FOO};';
EXEC sp_executesql N'ALTER DATABASE {FOO} SET AUTO_CLOSE OFF;';
This is similar to the reason you can't do this:
IF 1 = 1
BEGIN
CREATE TABLE #t1(id INT);
END
ELSE
BEGIN
CREATE TABLE #t1(x NVARCHAR(255));
END
Even though you and I know that only one of those #t1 code paths will ever be reached, SQL Server presumes that both paths could be reached at runtime, and so complains at parse time.

How should I pass a table name into a stored proc?

I just ran into a strange thing...there is some code on our site that is taking a giant SQL statement, modifying it in code by doing some search and replace based on some user values, and then passing it on to SQL Server as a query.
I was thinking that this would be cleaner as a parameterized query to a stored proc, with the user values as the parameters, but when I looked more closely I see why they might be doing it...the table that they are selecting from is variably dependant on those user values.
For instance, in one case if the values were ("FOO", "BAR") the query would end up being something like "SELECT * FROM FOO_BAR"
Is there an easy and clear way to do this? Everything I'm trying seems inelegant.
EDIT: I could, of course, dynamically generate the sql in the stored proc, and exec that (bleh), but at that point I'm wondering if I've gained anything.
EDIT2: Refactoring the table names in some intelligent way, say having them all in one table with the different names as a new column would be a nice way to solve all of this, which several people have pointed out directly, or alluded to. Sadly, it is not an option in this case.
First of all, you should NEVER do SQL command compositions on a client app like this, that's what SQL Injection is. (Its OK for an admin tool that has no privs of its own, but not for a shared use application).
Secondly, yes, a parametrized call to a Stored procedure is both cleaner and safer.
However, as you will need to use Dynamic SQL to do this, you still do not want to include the passed string in the text of the executed query. Instead, you want to used the passed string to look up the names of the actual tables that the user should be allowed to query in the way.
Here's a simple naive example:
CREATE PROC spCountAnyTableRows( #PassedTableName as NVarchar(255) ) AS
-- Counts the number of rows from any non-system Table, *SAFELY*
BEGIN
DECLARE #ActualTableName AS NVarchar(255)
SELECT #ActualTableName = QUOTENAME( TABLE_NAME )
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME = #PassedTableName
DECLARE #sql AS NVARCHAR(MAX)
SELECT #sql = 'SELECT COUNT(*) FROM ' + #ActualTableName + ';'
EXEC(#SQL)
END
Some have fairly asked why this is safer. Hopefully, little Bobby Tables can make this clearer:
0
Answers to more questions:
QUOTENAME alone is not guaranteed to be safe. MS encourages us to use it, but they have not given a guarantee that it cannot be out-foxed by hackers. FYI, real Security is all about the guarantees. The table lookup with QUOTENAME, is another story, it's unbreakable.
QUOTENAME is not strictly necessary for this example, the Lookup translation on INFORMATION_SCHEMA alone is normally sufficient. QUOTENAME is in here because it is good form in security to include a complete and correct solution. QUOTENAME in here is actually protecting against a distinct, but similar potential problem know as latent injection.
I should note that you can do the same thing with dynamic Column Names and the INFORMATION_SCHEMA.COLUMNS table.
You can also bypass the need for stored procedures by using a parameterized SQL query instead (see here: https://learn.microsoft.com/en-us/dotnet/api/system.data.sqlclient.sqlcommand.parameters?view=netframework-4.8). But I think that stored procedures provide a more manageable and less error-prone security facility for cases like this.
(Un)fortunately there's no way of doing this - you can't use table name passed as a parameter to stored code other than for dynamic sql generation. When it comes to deciding where to generate sql code, I prefer application code rather that stored code. Application code is usually faster and easier to maintain.
In case you don't like the solution you're working with, I'd suggest a deeper redesign (i.e. change the schema/application logic so you no longer have to pass table name as a parameter anywhere).
I would argue against dynamically generating the SQL in the stored proc; that'll get you into trouble and could cause injection vulnerability.
Instead, I would analyze all of the tables that could be affected by the query and create some sort of enumeration that would determine which table to use for the query.
Sounds like you'd be better off with an ORM solution.
I cringe when I see dynamic sql in a stored procedure.
One thing you can consider is to make a case statement that contains the same SQL command you want, once for each valid table, then pass as a string the table name into this procedure and have the case choose which command to run.
By the way as a security person the suggestion above telling you to select from the system tables in order to make sure you have a valid table seems like a wasted operation to me. If someone can inject passed the QUOTENAME() then then injection would work on the system table just as well as on the underlying table. The only thing this helps with it to ensure it is a valid table name, and I think the suggestion above is a better approach to that since you are not using QUOTENAME() at all.
Depending on whether the set of columns in those tables is the same or different, I'd approach it in two ways in the longer term:
1) if they the same, why not create a new column that would be used as a selector, whose value is derived from the user-supplied parameters ? (is it a performance optimization?)
2) if they are different, chances are that handling of them is also different. As such, it seems like splitting the select/handle code into separate blocks and then calling them separately would be a most modular approach to me. You will repeat the "select * from" part,
but in this scenario the set of tables is hopefully finite.
Allowing the calling code to supply two arbitrary parts of the table name to do a select from feels very dangerous.
I don't know the reason why you have the data spread over several tables, but it sounds like you are breaking one of the fundamentals. The data should be in the tables, not as table names.
If the tables have more or less the same layout, consider if it would be best to put the data in a single table instead. That would solve your problem with the dynamic query, and it would make the database layout more flexible.
Instead of Querying the tables based on user input values, you can pick the procedure instead.
that is to say
1. Create a procedure FOO_BAR_prc and inside that you put the query 'select * from foo_bar' , that way the query will be precompiled by the database.
2. Then based on the user input now execute the correct procedure from your application code.
Since you have around 50 tables, this might not be a feasible solution though as it would require lot of work on your part.
In fact, I wanted to know how to pass table name to create a table in stored procedure. By reading some of the answers and attempting some modification at my end, I finally able to create a table with name passed as parameter. Here is the stored procedure for others to check any error in it.
USE [Database Name]
GO
/****** Object: StoredProcedure [dbo].[sp_CreateDynamicTable] Script Date: 06/20/2015 16:56:25 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[sp_CreateDynamicTable]
#tName varchar(255)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #SQL nvarchar(max)
SET #SQL = N'CREATE TABLE [DBO].['+ #tName + '] (DocID nvarchar(10) null);'
EXECUTE sp_executesql #SQL
END
#RBarry Young
You don't need to add the brackets to #ActualTableName in the query string because it is already included in the result from the query in the INFORMATION_SCHEMA.TABLES. Otherwise, there will be error(s) when executed.
CREATE PROC spCountAnyTableRows( #PassedTableName as NVarchar(255) ) AS
-- Counts the number of rows from any non-system Table, SAFELY
BEGIN
DECLARE #ActualTableName AS NVarchar(255)
SELECT #ActualTableName = QUOTENAME( TABLE_NAME )
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME = #PassedTableName
DECLARE #sql AS NVARCHAR(MAX)
--SELECT #sql = 'SELECT COUNT(*) FROM [' + #ActualTableName + '];'
-- changed to this
SELECT #sql = 'SELECT COUNT(*) FROM ' + #ActualTableName + ';'
EXEC(#SQL)
END
I would avoid dynamic SQL at all costs.
Isn't the most elegant solution but does the job perfectly.
PROCEDURE TABLE_AS_PARAMTER (
p_table_name IN VARCHAR2
) AS
BEGIN
CASE p_table_name
WHEN 'TABLE1' THEN
UPDATE TABLE1
SET
COLUMN1 =1
WHERE
ID =1;
WHEN 'TABLE2' THEN
UPDATE TABLE1
SET
COLUMN1 =1
WHERE
ID =2;
END CASE;
COMMIT;
EXCEPTION
WHEN OTHERS THEN
ROLLBACK
END TABLE_AS_PARAMTER;

Why does a T-SQL block give an error even if it shouldn't even be executed?

I was writing a (seemingly) straight-forward SQL snippet that drops a column after it makes sure the column exists.
The problem: if the column does NOT exist, the code inside the IF clause complains that it can't find the column! Well, doh, that's why it's inside the IF clause!
So my question is, why does a piece of code that shouldn't be executed give errors?
Here's the snippet:
IF exists (select * from syscolumns
WHERE id=object_id('Table_MD') and name='timeout')
BEGIN
ALTER TABLE [dbo].[Table_MD]
DROP COLUMN timeout
END
GO
...and here's the error:
Error executing SQL script [...]. Invalid column name 'timeout'
I'm using Microsoft SQL Server 2005 Express Edition.
IF exists (select * from syscolumns
WHERE id=object_id('Table_MD') and name='timeout')
BEGIN
DECLARE #SQL nvarchar(1000)
SET #SQL = N'ALTER TABLE [dbo].[Table_MD] DROP COLUMN timeout'
EXEC sp_executesql #SQL
END
GO
Reason:
When Sql server compiles the code, they check it for used objects ( if they exists ). This check procedure ignores any "IF", "WHILE", etc... constructs and simply check all used objects in code.
It may never be executed, but it's parsed for validity by Sql Server. The only way to "get around" this is to construct a block of dynamic sql and then selectively execute it
Here's how I got it to work:
Inside the IF clause, I changed the ALTER ... DROP ... command with exec ('ALTER ... DROP ...')
It seems the SQL server does a validity check on the code when parsing it, and sees that a non-existing column gets referenced somewhere (even if that piece of code will never be executed).
Using the exec(ute) command wraps the problematic code in a string, the parser doesn't complain, and the code only gets executed when necessary.
Here's the modified snippet:
IF exists (select * from syscolumns
WHERE id=object_id('Table_MD') and name='timeout')
BEGIN
exec ('ALTER TABLE [dbo].[Table_MD] DROP COLUMN timeout')
END
GO
By the way, there is a similar issue in Oracle, and a similar workaround using the "execute immediate" clause.