SQL Server - I want to select database based on an identifier used - c#

I have multiple databases. Can I hit one of the databases based on an identifier which is dynamic?
e.g. I have three databases DB1,DB2,DB3.
I have a query select * from tblEmployees.(This table is present in all the three DBs). I have an identifier(or some variable ) whose value can be 1 or 2 or 3 and based on the value of this variable which I get dynamically when my service is hit, I would like to choose the DB from which the values should be obtained.
Can this be done? My DB is SQL Server and front end is asp.net.
My connection strings are stored in web.config file. Can I have multiple connection string which will have the same server with diff db names and select one of them based on the identifier.

1.In real world most of the time you have to store your connection strings in your web.config file .
so there you can let's keep three connection strings which will have the same server but different databases name, then you can select one of the connection-string for your app connection to the required database.
2.you can build that connection string on run-time if you need.
using these technique you will never have to write 2 or more queries just change the query string and your queries will work for all the databases.

You can do like this
if(val == 1)
{
select * from [DB1].[dbo].[tblEmployees]
}
else if(val == 2)
{
select * from [DB2].[dbo].[tblEmployees]
}

Try this one -
DECLARE #ID INT
SELECT #ID = 2
DECLARE #SQL NVARCHAR(500)
SELECT #SQL = 'SELECT * FROM DB' + CAST(#ID AS CHAR(1)) + '.dbo.tblEmployees'
PRINT #SQL
EXEC sys.sp_executesql #SQL
Output -
SELECT * FROM DB2.dbo.tblEmployees

T-SQL way:
declare #db int
if #db = 1
begin
use [db1]
select *
from tblEmployees
end
if #db = 2
begin
use [db2]
select *
from tblEmployees
end
-- and so on

IMO you're best bet would be to use a different connection-string to achieve multi-tenency against similar alike databases. Ideally, abstracted away behind some code so that most of your code doesn't need to know about it, but just does:
using(var conn = Somewhere.GetOpenConnection()) {
// ...
}
or worst-case:
using(var conn = Somewhere.GetOpenConnection(Environment.Published)) {
// ...
}
(here Environment is an enum to what the various databases represent)
where GetOpenConnection figures out which database is needed, and either looks up on constructs the correct connection string.
But to be specific:
you cannot parameterize the DB name in a query
using use between operations would be a really bad idea in terms of connection re-use
you can explicitly use three-part identifiers (i.e. DB1..SomeTable or DB1.dbo.SomeTable), but that does not scale naturally to lots of databases

Related

SQL: Pull List Of Tables From Specified Database While Attached To Another

I am facing a peculiar issue with loading a list of tables from a specific database (well rather a group of databases) while attached to the master database. Currently my query loads all of the databases on the server, then loops through those databases sending information back to the client via RAISERROR. As this loop is executing I need a nested loop to load all of the tables for the current database for later transmission as a SELECT once the query has completed. The issue I'm running into is that this will be executed as a single query inside of C# code. Ideally I would like to load everything in SQL and return it to the client for processing. For example:
WHILE (#dbLoop < #dbCount) BEGIN
-- Do cool things and send details back to client.
SET #dbName = (SELECT _name FROM dbTemp WHERE _id = #dbLoop);
-- USE [#dbName]
-- Get a count of the tables from info schema on the newly specified database.
WHILE (#tableLoop < #tableCount) BEGIN
-- USE [#dbName]
-- Do super cool things and load tables from info schema.
SET #tableLoop += 1;
END
SET #dbLoop += 1;
END
-- Return the list of tables from all databases to the client for use with SQLDataAdapter.
SELECT * FROM tableTemp;
This topic is pretty straight forward; I just need a way to access tables in a specified database (preferably by name) without having to change the connection on the SqlConnection object, and without having to have a loop inside of my C# code to process the same query on each database on the C# side. It would be more efficient to load everything in SQL and send it back to the application. Any help that can be provided on this would be great!
Thanks,
Jamie
All the tables are in the meta data you can just do a query against that and join to your list of schemas you want to look at.
SELECT tab.name
FROM sys.tables AS tab
JOIN sys.schemas AS sch on tab.schema_id = sch.schema_id
JOIN dbTemp temp on sch.name = temp.[_name]
This returns a list of the table to return back as a result set.
The statement USE [#dbName] takes effect AFTER it is run (usually via the GO statement.
USE [#dbName]
GO
The above 2 lines would make you start using the new Database. You cannot use this in the middle of your SQL or SP.
One other option which you can use is to use the dot notation, i.e., dbname..tablename syntax to query your tables.
double dot notation post
Okay, after spending all day working on this, I have finally come up with a solution. I load all the databases into a table variable, then I begin looping through those databases and send back their details to the client. After the database details themselves have been sent to the client via RAISERROR I then utilize sp_executesql to execute a new sub-query with the current database specified to get the list of tables for processing at the end of the primary. The example below demonstrates the basic structure of this process for others experiencing this issue in the future.
Thank you all once again for your help!
-Jamie
DECLARE #LoopCounter INT = 1, #DatabaseCount INT = 0;
DECLARE #SQL NVARCHAR(MAX), #dbName NVARCHAR(MAX);
DECLARE #Databases TABLE ( _id INT, _name NVARCHAR(MAX) );
DECLARE #Tables TABLE ( _name NVARCHAR(MAX), _type NVARCHAR(15) );
INSERT INTO #Databases
SELECT ROW_NUMBER() OVER(ORDER BY name) AS id, name
FROM sys.databases
WHERE name NOT IN ( 'master', 'tempdb', 'msdb', 'model' );
SET #DatabaseCount = (SELECT COUNT(*) FROM #Databases);
WHILE (#LoopCounter <= #DatabaseCount) BEGIN
SET #dbName = (SELECT _name FROM #Databases WHERE _id = #LoopCounter);
SET #SQL NVARCHAR(MAX) = 'SELECT TABLE_NAME, TABLE_TYPE
FROM [' + #dbName + '].INFORMATION_SCHEMA.TABLES';
INSERT INTO #Tables EXEC sp_executesql #SQL;
SET #LoopCounter += 1;
END

Is there any way to set Database name as global name in sql server?

I have two databases as mentioned below:
[QCR_DEV]
[QCR_DEV_LOG]
All application data are stored in [QCR_DEV]. On each table of [QCR_DEV], there is a trigger that insert the details of insertion and update of [QCR_DEV] table into [QCR_DEV_LOG] database.
Suppose i have a table [party] in [QCR_DEV] database. Whenever i insert,update or delete some record in the table. There will be one insertion in table [party_log] which exists in [QCR_DEV_LOG] database. In short i am keeping the log or action performed on tables of [QCR_DEV] into [QCR_DEV_LOG] database.
When we connect to database through application, it connect to database somehow using connection-string. In my stored procedure, i did not use database name like:
Select * From [QCR_DEV].[party];
I am using like this:
Select * From [party];
This is because, in feature if i need to change database name then i will only need to change connection-string.
Now come to the point, i need to get data from [QCR_DEV_LOG] database. I am writing a stored procedure in which i need to get data from both databases like:
Select * From [QCR_DEV_LOG][party_log]
INNER JOIN [person] on [person].person_id = [QCR_DEV_LOG][party_log].person_id
where party_id = 1
This stored procedure is in [QCR_DEV] database. I need to get data from both databases. For this i need to mention the database name in query. I don't want this. Is there any way to set database name globally and use this name in my queries so that if in future i need to change database name, i only change from where it sets globally. Is there any way to do this?
I would second Jeroen Mostert comment and use synonyms:
CREATE SYNONYM [party_log] FOR [QCR_DEV_LOG].[dbo].[party_log];
And when the target database is renamed, this query would generate a migration script:
SELECT 'DROP SYNONYM [' + name + ']; CREATE SYNONYM [' + name + '] FOR ' + REPLACE(base_object_name, '[OldLogDbName].', '[NewLogDbName].') + ';'
FROM sys.synonyms
WHERE base_object_name LIKE '[OldLogDbName].%';
You could do this in the DEV database:
CREATE VIEW [dbo].[party_log]
AS
SELECT * FROM [QCR_DEV_LOG].[dbo].[party_log]
Then you can write SELECT-queries as if the [party_log] table exists in the DEV database.
Any WHERE.. or JOIN..ON.. clauses should get applied before the combined query is executed.
If the LOG database ever gets moved or renamed, then you'd only need to update the view (or a couple of views, but probably never a lot).
If you expect regular changes, or if you need to use this on multiple servers then you could use dynamic SQL:
IF OBJECT_ID('[dbo].[party_log]') IS NOT NULL DROP VIEW [dbo].[party_log]
-- etc, repeat to DROP other views
DECLARE #logdb VARCHAR(80) = 'QCR_DEV_LOG'
EXEC ('CREATE VIEW [dbo].[party_log] AS SELECT * FROM [' + #logdb + '].[dbo][party_log]')
-- etc, repeat to create other views

Entity Framework Code First String Comparison with Oracle Db

I'm having trouble doing case insensitive string comparison using code first against an Oracle db. Code looks something like this;
String filter = "Ali";
var employee = dbContext.Employees.Where(x => x.Name.Contains(filter)).FirstOrDefault();
The code above acts to be case sensitive. So I converted both the Name and filter to Uppercase;
String filter = "Ali";
filter = filter.ToUpper();
var employee = dbContext.Employees.Where(x => x.Name.ToUpper().Contains(filter)).FirstOrDefault();
Everything seemed to work at first, but then I realized it's not working when the employee's name or the filter contains the character 'i'. The problem is how the letter i works in Turkish.
In most languages, 'i' stands for the lowercase, and 'I' stands for the uppercase version of the character. However in Turkish, 'i's uppercase is 'İ', and 'I's lowercase is 'ı'. Which is a problem as Oracle uppercases the letter 'i' in the db as 'I'.
We do not have access to the db's character encoding settings as its effects cannot be foreseen easily.
What I've come up with is this, and it is very ugly.
String filterInvariant = filter.ToUpper(CultureInfo.InvariantCulture);
String filterTurkish = filter.ToUpper(CultureInfo.CreateSpecificCulture("tr-TR"));
var employee = dbContext.Employees.Where(x => x.Name.ToUpper().Contains(filterInvariant) || x.Name.ToUpper().Contains(filterTurkish)).FirstOrDefault();
It seems to fix some of the issues, but feels like a brute force workaround rather than a solid solution. What are the best practices, or alternatives to this workaround, while using Code First C# against an Oracle database?
Thanks in advance
Ditch using all the UPPER functions. Simply let Oracle do your language aware case-insensitive matching. This is done by setting your DB connection from C# to have the appropriate language parameters. This setting is just for your DB session, not a global change for the whole DB. I'm no C# wizard, so you'd have to figure out where to make these session settings in your db connection/pool code.
ALTER SESSION SET nls_language=TURKISH;
ALTER SESSION SET nls_comp=LINGUISTIC;
ALTER SESSION SET nls_sort=BINARY_CI;
If C# proves too difficult to find where to change this, you could set this up as a user/schema logon trigger (below), which sets these automatically for you at db connect time (replace SOMEUSER with your actual db username). This only affects any NEW db sessions, so if you have connections pooled, you'll want to cycle the DB connection pool to refresh the connections.
CREATE OR REPLACE TRIGGER SOMEUSER.SET_NLS_CASE_INSENSITIVE_TRG AFTER
LOGON ON SOMEUSER.SCHEMA
BEGIN
EXECUTE IMMEDIATE 'ALTER SESSION SET nls_language=TURKISH';
EXECUTE IMMEDIATE 'ALTER SESSION SET nls_comp=LINGUISTIC';
EXECUTE IMMEDIATE 'ALTER SESSION SET nls_sort=BINARY_CI';
END;
/
Here's a little test I did in an Oracle DB:
CREATE TABLE mypeople (name VARCHAR2(10 CHAR));
INSERT INTO mypeople VALUES ('Alİ Hassan');
INSERT INTO mypeople VALUES ('AlI Hassan');
INSERT INTO mypeople VALUES ('Ali Hassan');
INSERT INTO mypeople VALUES ('Alı Hassan');
SELECT name FROM mypeople WHERE name LIKE 'Ali%';
NAME
----------
Ali Hassan
ALTER SESSION SET nls_language=TURKISH;
ALTER SESSION SET nls_comp=LINGUISTIC;
ALTER SESSION SET nls_sort=BINARY_CI;
SELECT name FROM mypeople WHERE name LIKE 'Ali%';
NAME
----------
Alİ Hassan
AlI Hassan
Ali Hassan
The implementation of String.Contains is different for different providers, for example Linq2Sql is always case insensitive. The search is case sensitive or not depends on server settings. For example SQL Server by default has SQL_Latin1_General_CP1_CI_AS Collation and that is NOT case sensitive. For Oracle you can change this behavior at the session level: Case insensitive searching in Oracle (Issue a raw SQL query using context.Database.ExecuteSqlCommand method at the beginning of the session)
The problem is in the database, not in .NET, for example this query:
FILES.Where(t => t.FILE_NAME.ToUpper() == "FILE.TXT") // Get rows from file-table
translates into this Oracle SQL with the oracle provider I have:
SELECT t0.BINARY_FILE, t0.FILE_NAME, t0.FILE_SIZE, t0.INFO, t0.UPLOAD_DATE
FROM FILES t0
WHERE (UPPER(t0.FILE_NAME) = :p0)
-- p0 = [FILE.TXT]
The contains with First() becomes this:
SELECT * FROM (SELECT t0.BINARY_FILE, t0.FILE_NAME, t0.FILE_SIZE, t0.INFO, t0.UPLOAD_DATE
FROM FILES t0
WHERE ((UPPER(t0.FILE_NAME) LIKE '%' || :p0 || '%')
OR (UPPER(t0.FILE_NAME) LIKE '%' || :p1 || '%')))
WHERE ROWNUM<=1
-- p0 = [FILE.TXT]
-- p1 = [FİLE.TXT]
So it depends on your database's culture settings, ie without knowing them I would say the "overlap" with your solution is the best way to solve it. Why can't you just check the database culture settings?

Wish to observe stored procedures that exist on multiple database and across multiple servers

Having an issue on MS SQL 2005 Enterprise multiple servers where I want to get a collection of meta data across multiple servers and multiple databases. I saw on Stack Overflow a good example on using the magical sp_MSforeachdb that I altered a little bit below. Basically a MS stored procedure is being ran dynamically and it's looking for anytime a database (?) is like a name like 'Case(fourspaces)'. This is great and it gives me what I want but for only a single server. I want to do this for more, is it possible SQL gurus?
Example thus far:
SET NOCOUNT ON
DECLARE #AllTables table (CompleteTableName varchar(256))
INSERT INTO #AllTables (CompleteTableName)
EXEC sp_msforeachdb 'select distinct ##SERVERNAME+''.''+ ''?'' + ''.'' + p.name from [?].sys.procedures p (nolock) where ''?'' like ''Case____'''
SELECT * FROM #AllTables ORDER BY 1
Is there a way though to do this in SQL, Linq, or ADO.NET to perform this clever built in stored procedure that inserts into a table variable to do this multiple times across servers BUT...... Put that in one set. As far as I know you CANNOT switch servers in a single session in SQL Management Studio but I would love to be proved wrong on that one.
EG: I have a production environment with 8 Servers, each of those servers has many databases. I could run this multiple times but I was hoping that if the servers were linked already I could do this from the sys views somehow. However I am on an environment using SQL 2005 and got MS's download for the sys views and it looks like the sys.servers is on an island unto itself where the SERVERID does not seem to join to anything else.
I would be willing to use an ADO.NET reader or LINQ in a C# environment and possibly call the above TSQL code multiple times but ...... Is there a more efficient way to get the info directly in TSQL IF the servers are LINKED SERVERS? Just curious.
The overall purpose of this operation is for deployment purposes to see how many procedures exist across all servers and databases. Now we do have SQL compare from Redgate but I am unaware if it can script procs that don't exist to exist the same as set A. Even if it could I would like to try to make something on my own if feasible.
Any help is much appreciated and if you need further clarification please ask.
I figured it out, once you set up linked servers you can merely extend the linked server name to the left of the object to qualify it more distinctly.
EG instead of sp_msforeachdb I can do (Servername).MASTER..sp_msforeachdb. I can then iterate through my servers if they are LINKED(they are in my case) from the sys.servers table.
I did some things that would slow things down with my left join and that I store everything at once and then examine with a 'like' statement instead of an explicit qualifier. But overall I think this solution will provide an end user with flexibility to not know the exact name of an object to hunt for. I also like that I can now use this with SSIS, SSRS and ADO.NET as the procedure can do the hunting iteration for me and I do not have to do something in an apps memory but on the SQL server's. I'm sure others may have better ideas but I did not hear anything so this is mine:
Complete solution below:
Create PROC [PE].[DeployChecker]
(
#DB VARCHAR(128)
, #Proc VARCHAR(128)
)
AS
BEGIN
--declare variable for dynamic SQL
DECLARE
#SQL VARCHAR(512)
, #x int
-- remove temp table if it exists as it should not be prepopulated.
IF object_ID('tempdb..#Procs') IS NOT NULL
DROP TABLE tempdb..#Procs
-- Create temp table to catch built in sql stored procedure
CREATE TABLE #Procs --DECLARE #Procs table
(
ServerName varchar(64)
, DatabaseName VARCHAR(128)
, ObjectName VARCHAR(256)
)
SET #X = 1
-- Loops through the linked servers with matching criteria to examine how MANY there are. Do a while loop while they exist.
-- in our case the different servers are merely incrementing numbers so I merely do a while loop, you could be more explicit if needed.
WHILE #X <= (SELECT count(*) FROM sys.servers WHERE name LIKE 'PCTRSQL_')
BEGIN
-- for some reason I can't automate the 'sp_msforeachdb' proc to take dynamic sql but I can set a variable to do it and then run it.
SET #SQL = 'Insert Into #Procs Exec PCTRSQL' + CAST(#X AS VARCHAR(2)) + '.MASTER..sp_msforeachdb ' +
'''select ##SERVERNAME, ''''?'''', name from [?].sys.procedures (nolock) where ''''?'''' like ''''%' + #DB + '%'''' '''
Exec (#SQL)
SET #X = #X + 1
END
;
-- Find distinct Server detail
WITH s AS
(
SELECT Distinct
ServerName
, DatabaseName
FROM #Procs
)
-- do logic search in the select statement to see if there is a proc like what is searched for
, p AS
(
SELECT
ServerName
, DatabaseName
, CASE WHEN ObjectName LIKE '%' + #Proc + '%' THEN ObjectName END AS ProcName
FROM #Procs
where ObjectName LIKE '%' + #Proc + '%'
)
-- now do a left join from the distinct server cte to the lookup for the proc cte, we want to examine ALL the procs that match a critera
-- however if nothing eixsts we wish to show a NULL value of a single row for a reference to the Servername and Database
SELECT
s.ServerName
, s.DatabaseName
, p.ProcName
, CAST(CASE WHEN ProcName IS NOT NULL THEN 1 ELSE 0 END AS bit) AS ExistsInDB
FROM s
LEFT JOIN p ON s.ServerName = p.ServerName
AND s.DatabaseName = p.DatabaseName
ORDER BY DatabaseName, ServerName, ProcName
END

How do I protect this function from SQL injection?

public static bool TruncateTable(string dbAlias, string tableName)
{
string sqlStatement = string.Format("TRUNCATE TABLE {0}", tableName);
return ExecuteNonQuery(dbAlias, sqlStatement) > 0;
}
The most common recommendation to fight SQL injection is to use an SQL query parameter (several people on this thread have suggested it).
This is the wrong answer in this case. You can't use an SQL query parameter for a table name in a DDL statement.
SQL query parameters can be used only in place of a literal value in an SQL expression. This is standard in every implementation of SQL.
My recommendation for protecting against SQL injection when you have a table name is to validate the input string against a list of known table names.
You can get a list of valid table names from the INFORMATION_SCHEMA:
SELECT table_name
FROM INFORMATION_SCHEMA.Tables
WHERE table_type = 'BASE TABLE'
AND table_name = #tableName
Now you can pass your input variable to this query as an SQL parameter. If the query returns no rows, you know that the input is not valid to use as a table. If the query returns a row, it matched, so you have more assurance you can use it safely.
You could also validate the table name against a list of specific tables you define as okay for your app to truncate, as #John Buchanan suggests.
Even after validating that tableName exists as a table name in your RDBMS, I would also suggest delimiting the table name, just in case you use table names with spaces or special characters. In Microsoft SQL Server, the default identifier delimiters are square brackets:
string sqlStatement = string.Format("TRUNCATE TABLE [{0}]", tableName);
Now you're only at risk for SQL injection if tableName matches a real table, and you actually use square brackets in the names of your tables!
As far as I know, you can't use parameterized queries to perform DDL statements/ specify table names, at least not in Oracle or Sql Server. What I would do, if I had to have a crazy TruncateTable function, that had to be safe from sql injection would be to make a stored procedure that checks that the input is a table that is safe to truncate.
-- Sql Server specific!
CREATE TABLE TruncableTables (TableName varchar(50))
Insert into TruncableTables values ('MyTable')
go
CREATE PROCEDURE MyTrunc #tableName varchar(50)
AS
BEGIN
declare #IsValidTable int
declare #SqlString nvarchar(50)
select #IsValidTable = Count(*) from TruncableTables where TableName = #tableName
if #IsValidTable > 0
begin
select #SqlString = 'truncate table ' + #tableName
EXECUTE sp_executesql #SqlString
end
END
If you're allowing user-defined input to creep into this function via the tablename variable, I don't think SQL Injection is your only problem.
A better option would be to run this command via its own secure connection and give it no SELECT rights at all. All TRUNCATE needs to run is the ALTER TABLE permission. If you're on SQL 2005 upwards, you could also try using a stored procedure with EXECUTE AS inside.
CREATE OR REPLACE PROCEDURE truncate(ptbl_name IN VARCHAR2) IS
stmt VARCHAR2(100);
BEGIN
stmt := 'TRUNCATE TABLE '||DBMS_ASSERT.SIMPLE_SQL_NAME(ptbl_name);
dbms_output.put_line('<'||stmt||'>');
EXECUTE IMMEDIATE stmt;
END;
Use a stored procedure. Any decent db library (MS Enterprise Library is what I use) will handle escaping string parameters correctly.
Also, re:parameterized queries: I prefer to NOT have to redeploy my app to fix a db issue. Storing queries as literal strings in your source increases maintenance complexity.
Have a look at this link
Does this code prevent SQL injection?
Remove the unwanted from the tableName string.
I do not think you can use param query for a table name.
There are some other posts which will help with the SQL injection, so I'll upvote those, but another thing to consider is how you will be handling permissions for this. If you're granting users db+owner or db_ddladmin roles so that they can truncate tables then simply avoiding standard SQL injection attacks isn't sufficient. A hacker can send in other table names which might be valid, but which you wouldn't want truncated.
If you're giving ALTER TABLE permissions to the users on the specific tables that you will allow to be truncated then you're in a bit better shape, but it's still more than I like to allow in a normal environment.
Usually TRUNCATE TABLE isn't used in normal day-to-day application use. It's used for ETL scenarios or during database maintenance. The only situation where I might imagine it would be used in a front-facing application would be if you allowed users to load a table which is specific for that user for loading purposes, but even then I would probably use a different solution.
Of course, without knowing the specifics around why you're using it, I can't categorically say that you should redesign, but if I got a request for this as a DBA I'd be asking the developer a lot of questions.
Use parameterized queries.
In this concrete example you need protection from SQL injection only if table name comes from external source.
Why would you ever allow this to happen?
If you are allowing some external entity (end user, other system, what?)
to name a table to be dropped, why won't you just give them admin rights.
If you are creating and removing tables to provide some functionality for end user,
don't let them provide names for database objects directly.
Apart from SQL injection, you'll have problems with name clashes etc.
Instead generate real table names yourself (e.g DYNTABLE_00001, DYNTABLE_00002, ...) and keep a table that connects them to the names provided by user.
Some notes on generating dynamic SQL for DDL operations:
In most RDBMS-s you'll have to use dynamic SQL and insert table names as text.
Be extra careful.
Use quoted identifiers ([] in MS SQL Server, "" in all ANSI compliant RDBMS).
This will make avoiding errors caused by invalid names easier.
Do it in stored procedures and check if all referenced objects are valid.
Do not do anything irreversible. E.g. don't drop tables automatically.
You can flag them to be dropped and e-mail your DBA.
She'll drop them after the backup.
Avoid it if you can. If you can't, do what you can to minimize rights to other
(non-dynamic) tables that normal users will have.
You could use SQLParameter to pass in tableName value. As far as I know and tested, SQLParameter takes care of all parameter checking and thus disables possibility of injection.
If you can't use parameterized queries (and you should) ... a simple replace of all instances of ' with '' should work.
string sqlStatement = string.Format("TRUNCATE TABLE {0}", tableName.Replace("'", "''"));

Categories