How to create a "materialized something" that accesses different tables, depending on a specific setting - sql

I want a program to access a table/view/stored procedure, etc. (something materialized, let's call it X) that abstracts the real location of the data contained in three basic tables (the tables have the same definition in all locations).
I would want X to fetch the server name, catalog name and table name from somewhere (a table, probably) and access the specific three basic tables. The caller of X would not know which specific tables were being called.
How can I do this in SQL Server (2008)?

Like a function, a view can't use dynamic SQL - it can't go find some metadata reference somewhere and adjust accordingly.
I think the closest thing to what you want is a synonym. Let's say you have three different databases, A, B and C. In A the table you want the view to reference is dbo.foo, in B it is dbo.bar, and in Cit is dbo.splunge. So then you could create a synonym like so in each database:
USE A;
GO
CREATE SYNONYM dbo.YourCommonViewName FOR dbo.foo;
GO
USE B;
GO
CREATE SYNONYM dbo.YourCommonViewName FOR dbo.bar;
GO
USE C;
GO
CREATE SYNONYM dbo.YourCommonViewName FOR dbo.splunge;
GO
Now this technically isn't a view, but in each database you can say...
SELECT <cols> FROM dbo.YourCommonViewName;
...and it will return the data from the database-specific table.
To do this in a stored procedure would be much simpler. Say you store the server, database and table name in some table, e.g. dbo.lookup:
CREATE TABLE dbo.lookup
(
id INT PRIMARY KEY,
[server] SYSNAME,
[database] SYSNAME,
[table] SYSNAME,
active BIT NOT NULL DEFAULT (0)
);
-- you may want a constraint or trigger to ensure
-- only one row can be active at any one time.
INSERT dbo.lookup(id, [server], [database], [table])
SELECT 1,N'serverA',N'databaseA',N'tableA'
UNION ALL SELECT 2,N'serverB',N'databaseB',N'tableB';
Now your program can say:
UPDATE dbo.lookup SET active = 1 WHERE ... ?
And your stored procedure can be:
CREATE PROCEDURE dbo.whatever
AS
BEGIN
SET NOCOUNT ON;
DECLARE #sql NVARCHAR(MAX);
SELECT #sql = N'SELECT <cols> FROM ' + QUOTENAME([server])
+ '.' + QUOTENAME([database]) + '.dbo.' + QUOTENAME([table])
FROM dbo.lookup WHERE active = 1;
EXEC sp_executesql #sql;
END
GO
I still don't understand the point, and I don't know what you're planning to do when two different users expect to call your program at the same time, and they each should get results from a different location.

Agreed with Aaron on the fact that views and functions cannot use dynamic sql.
Still what you can do is build a clr table valued function. In that you can play with .net code and query whatever you want. And build you data accordingly and output what you need.
So instead of querying the data like
select * from myview
you can query it
select * from dbo.clr_mymockupview()

Create SYNONYMs to your remote servers.
Create your VIEW to concatenate your locations together using UNION ALL.
Since you said "tables", join your tables before the UNION ALL and hopefully, MS will perform the JOIN remotely.

Use a union query with parameters for database, server, and catalog:
Select col1, col2, <etc.>, 'table1' as tablename, 'server1' as servername, 'catalog1' as catname from server1.catalog1.table1
Union Select col1, col2, <etc.>, 'table2' as tablename, 'server2' as servername, 'catalog2' as catname from server2.catalog2.table2
Union Select col1, col2, <etc.>, 'table3' as tablename, 'server3' as servername, 'catalog3' as catname from server3.catalog3.table3
Then filter based on your 3 criteria. This probably won't be blazing fast but will wonk with std. SQL.

Related

SQL Server select ONLY columns with permissions

Let's say that I have TableA that has 5 columns:
Col1
Col2
Col3
Col4
Col5
and I am connected to SQL Server via User1. This User1 has permissions to select col1, col2, col3 only
I can do this
select *
from information_schema.COLUMN_PRIVILEGES
and loop for all return columns and build my select statement, but I have more than 500 select statements in my project (BIG HR Management System), so I need another way with less code modification.
Now if I try
Select * from TableA
it will return an error.
So how can I select only those columns with permissions without change all my code?
Edited: I want a method to override SQL exception when selecting a column with no permission, just return null and show no exception
Edited 2:- the select * was just an example, to be more simple :
if I have 2 SQL Server users, the first one has permission in col1 - col3
The second one has permission to col4-col6
what is the best method to select columns with permission to every user
I strongly agree with the comments suggesting that different queries be written for different behaviors, rather than doing this dynamically for each user. While I understand the desire to do it dynamically (and you certainly can), it's probably going to be of equal effort, or more to alter all your existing queries to work dynamically.
By the way, you said queries instead of procedures. No clue if that's actually what you're doing, but if your app just constructs ad hoc sql, I'd recommend using stored procedures instead. If you already are, ignore this last comment.
That said, here is a bit of code which illustrates how you could do this dynamically. Whether you write this code in each of your procedures, or build a function which returns a string (representing the columns the user is entitled to), or build this behavior into a separate stored procedure which gets called by each proc, hopefully this should point you towards your solution.
I built a little mock setup here so this should be fully re-runnable. I just picked one of the existing service accounts (NT Service\SQLWriter) as an arbitrary user for illustrative purposes.
-- Assumes you have a dB called test
use test
go
-- Set up test table
if object_id('test.dbo.colpriv', 'U') is not null drop table test.dbo.colpriv
create table dbo.colpriv
(
Col1 int,
Col2 int,
Col3 int
)
insert into dbo.colpriv values (1, 3, 5)
grant select on dbo.colpriv (Col1, Col3) to [NT SERVICE\SQLWriter]
go
declare
#Principal varchar(100) = 'NT SERVICE\SQLWriter', -- Just picked one out of a hat from sys.server_principials
#Sql nvarchar(max),
#Columns nvarchar(max)
select
#Columns = stuff((select ',' + quotename(column_name)
from test.INFORMATION_SCHEMA.COLUMN_PRIVILEGES
where table_name = 'colpriv'
and grantee = #Principal
order by column_name
for xml path('')), 1, 1, ''),
#Sql = '
select ' + #Columns + '
from dbo.colpriv'
exec sp_executesql #Sql

Store a database name in variable & then using it dynamically

I have a Table in my Database Which have name of all the Database of my Server
Table Look like
create Table #db_name_list(Did INT IDENTITY(1,1), DNAME NVARCHAR(100))
INSERT INTO #db_name_list
SELECT 'db_One ' UNION ALL
SELECT 'db_Two' UNION ALL
SELECT 'db_Three' UNION ALL
SELECT 'db_four' UNION ALL
SELECT 'db_five'
select * from #db_name_list
I have so many SP in my Database..Which uses multiple table and Join Them..
At Present I am using the SQL code like
Select Column from db_One..Table1
Left outer join db_two..Table2
on ....some Condition ....
REQUIREMENT
But I do not want to HARDCODE the DATABASE Name ..
I want store DataBase name in Variable and use that .
Reason :: I want to restore same Database with Different name and want to Run those SP..At Present we Cant Do ,Because I have used db_One..Table1
or db_two..Table2
I want some thing like ...
/SAMPLE SP/
CREATE PROCEDURE LOAD_DATA
AS
BEGIN
DECLARE #dbname nvarchar(500)
set #dbname=( SELECT DNAME FROM #db_name_list WHERE Did=1)
set #dbname2=( SELECT DNAME FROM #db_name_list WHERE Did=2)
PRINT #DBNAME
SELECT * FROM #dbname..table1
/* or */
SELECT * FROM #dbname2.dbo.table1
END
i.e using Variable Instead of Database name ..
But it thow error
"Incorrect syntax near '.'."
P.S This was posted by some else on msdn but the answer there was not clear & I had the same kind of doubt. So please help
You can't use a variable like this in a static sql query. You have to use the variable in dynamic sql instead, in order to build the query you want to execute, like:
DECLARE #sql nvarchar(500) = 'SELECT * FROM ' + #dbname + '.dbo.mytable'
EXEC(#sql);
There seem to be a couple of options for you depending on your circumstances.
1. Simple - Generalise your procedures
Simply take out the database references in your stored procedure, as there is no need to have an explicit reference to the database if it is running against the database it is stored in. Your select queries will look like:
SELECT * from schema.table WHERE x = y
Rather than
SELECT * from database.schema.table WHERE x = y
Then just create the stored procedure in the new database and away you go. Simply connect to the new database and run the SP. This method would also allow you to promote the procedure to being a system stored procedure, which would mean they were automatically available in every database without having to run CREATE beforehand. For more details, see this article.
2. Moderate - Dynamic SQL
Change your stored procedure to take a database name as a parameter, such as this example:
CREATE PROCEDURE example (#DatabaseName VARCHAR(200))
AS
BEGIN
DECLARE #SQL VARCHAR(MAX) = 'SELECT * FROM ['+#DatabaseName+'].schema.table WHERE x = y'
EXEC (#SQL)
END

dynamic sql not working . Regular sql working [duplicate]

It looks like #temptables created using dynamic SQL via the EXECUTE string method have a different scope and can't be referenced by "fixed" SQLs in the same stored procedure.
However, I can reference a temp table created by a dynamic SQL statement in a subsequence dynamic SQL but it seems that a stored procedure does not return a query result to a calling client unless the SQL is fixed.
A simple 2 table scenario:
I have 2 tables. Let's call them Orders and Items. Order has a Primary key of OrderId and Items has a Primary Key of ItemId. Items.OrderId is the foreign key to identify the parent Order. An Order can have 1 to n Items.
I want to be able to provide a very flexible "query builder" type interface to the user to allow the user to select what Items he want to see. The filter criteria can be based on fields from the Items table and/or from the parent Order table. If an Item meets the filter condition including and condition on the parent Order if one exists, the Item should be return in the query as well as the parent Order.
Usually, I suppose, most people would construct a join between the Item table and the parent Order tables. I would like to perform 2 separate queries instead. One to return all of the qualifying Items and the other to return all of the distinct parent Orders. The reason is two fold and you may or may not agree.
The first reason is that I need to query all of the columns in the parent Order table and if I did a single query to join the Orders table to the Items table, I would be repoeating the Order information multiple times. Since there are typically a large number of items per Order, I'd like to avoid this because it would result in much more data being transfered to a fat client. Instead, as mentioned, I would like to return the two tables individually in a dataset and use the two tables within to populate a custom Order and child Items client objects. (I don't know enough about LINQ or Entity Framework yet. I build my objects by hand). The second reason I would like to return two tables instead of one is because I already have another procedure that returns all of the Items for a given OrderId along with the parent Order and I would like to use the same 2-table approach so that I could reuse the client code to populate my custom Order and Client objects from the 2 datatables returned.
What I was hoping to do was this:
Construct a dynamic SQL string on the Client which joins the orders table to the Items table and filters appropriate on each table as specified by the custom filter created on the Winform fat-client app. The SQL build on the client would have looked something like this:
TempSQL = "
INSERT INTO #ItemsToQuery
OrderId, ItemsId
FROM
Orders, Items
WHERE
Orders.OrderID = Items.OrderId AND
/* Some unpredictable Order filters go here */
AND
/* Some unpredictable Items filters go here */
"
Then, I would call a stored procedure,
CREATE PROCEDURE GetItemsAndOrders(#tempSql as text)
Execute (#tempSQL) --to create the #ItemsToQuery table
SELECT * FROM Items WHERE Items.ItemId IN (SELECT ItemId FROM #ItemsToQuery)
SELECT * FROM Orders WHERE Orders.OrderId IN (SELECT DISTINCT OrderId FROM #ItemsToQuery)
The problem with this approach is that #ItemsToQuery table, since it was created by dynamic SQL, is inaccessible from the following 2 static SQLs and if I change the static SQLs to dynamic, no results are passed back to the fat client.
3 around come to mind but I'm look for a better one:
1) The first SQL could be performed by executing the dynamically constructed SQL from the client. The results could then be passed as a table to a modified version of the above stored procedure. I am familiar with passing table data as XML. If I did this, the stored proc could then insert the data into a temporary table using a static SQL that, because it was created by dynamic SQL, could then be queried without issue. (I could also investigate into passing the new Table type param instead of XML.) However, I would like to avoid passing up potentially large lists to a stored procedure.
2) I could perform all the queries from the client.
The first would be something like this:
SELECT Items.* FROM Orders, Items WHERE Order.OrderId = Items.OrderId AND (dynamic filter)
SELECT Orders.* FROM Orders, Items WHERE Order.OrderId = Items.OrderId AND (dynamic filter)
This still provides me with the ability to reuse my client sided object-population code because the Orders and Items continue to be returned in two different tables.
I have a feeling to, that I might have some options using a Table data type within my stored proc, but that is also new to me and I would appreciate a little bit of spoon feeding on that one.
If you even scanned this far in what I wrote, I am surprised, but if so, I woul dappreciate any of your thoughts on how to accomplish this best.
You first need to create your table first then it will be available in the dynamic SQL.
This works:
CREATE TABLE #temp3 (id INT)
EXEC ('insert #temp3 values(1)')
SELECT *
FROM #temp3
This will not work:
EXEC (
'create table #temp2 (id int)
insert #temp2 values(1)'
)
SELECT *
FROM #temp2
In other words:
Create temp table
Execute proc
Select from temp table
Here is complete example:
CREATE PROC prTest2 #var VARCHAR(100)
AS
EXEC (#var)
GO
CREATE TABLE #temp (id INT)
EXEC prTest2 'insert #temp values(1)'
SELECT *
FROM #temp
1st Method - Enclose multiple statements in the same Dynamic SQL Call:
DECLARE #DynamicQuery NVARCHAR(MAX)
SET #DynamicQuery = 'Select * into #temp from (select * from tablename) alias
select * from #temp
drop table #temp'
EXEC sp_executesql #DynamicQuery
2nd Method - Use Global Temp Table:
(Careful, you need to take extra care of global variable.)
IF OBJECT_ID('tempdb..##temp2') IS NULL
BEGIN
EXEC (
'create table ##temp2 (id int)
insert ##temp2 values(1)'
)
SELECT *
FROM ##temp2
END
Don't forget to delete ##temp2 object manually once your done with it:
IF (OBJECT_ID('tempdb..##temp2') IS NOT NULL)
BEGIN
DROP Table ##temp2
END
Note: Don't use this method 2 if you don't know the full structure on database.
I had the same issue that #Muflix mentioned. When you don't know the columns being returned, or they are being generated dynamically, what I've done is create a global table with a unique id, then delete it when I'm done with it, this looks something like what's shown below:
DECLARE #DynamicSQL NVARCHAR(MAX)
DECLARE #DynamicTable VARCHAR(255) = 'DynamicTempTable_' + CONVERT(VARCHAR(36), NEWID())
DECLARE #DynamicColumns NVARCHAR(MAX)
--Get "#DynamicColumns", example: SET #DynamicColumns = '[Column1], [Column2]'
SET #DynamicSQL = 'SELECT ' + #DynamicColumns + ' INTO [##' + #DynamicTable + ']' +
' FROM [dbo].[TableXYZ]'
EXEC sp_executesql #DynamicSQL
SET #DynamicSQL = 'IF OBJECT_ID(''tempdb..##' + #DynamicTable + ''' , ''U'') IS NOT NULL ' +
' BEGIN DROP TABLE [##' + #DynamicTable + '] END'
EXEC sp_executesql #DynamicSQL
Certainly not the best solution, but this seems to work for me.
I would strongly suggest you have a read through http://www.sommarskog.se/arrays-in-sql-2005.html
Personally I like the approach of passing a comma delimited text list, then parsing it with text to table function and joining to it. The temp table approach can work if you create it first in the connection. But it feel a bit messier.
Result sets from dynamic SQL are returned to the client. I have done this quite a lot.
You're right about issues with sharing data through temp tables and variables and things like that between the SQL and the dynamic SQL it generates.
I think in trying to get your temp table working, you have probably got some things confused, because you can definitely get data from a SP which executes dynamic SQL:
USE SandBox
GO
CREATE PROCEDURE usp_DynTest(#table_type AS VARCHAR(255))
AS
BEGIN
DECLARE #sql AS VARCHAR(MAX) = 'SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = ''' + #table_type + ''''
EXEC (#sql)
END
GO
EXEC usp_DynTest 'BASE TABLE'
GO
EXEC usp_DynTest 'VIEW'
GO
DROP PROCEDURE usp_DynTest
GO
Also:
USE SandBox
GO
CREATE PROCEDURE usp_DynTest(#table_type AS VARCHAR(255))
AS
BEGIN
DECLARE #sql AS VARCHAR(MAX) = 'SELECT * INTO #temp FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = ''' + #table_type + '''; SELECT * FROM #temp;'
EXEC (#sql)
END
GO
EXEC usp_DynTest 'BASE TABLE'
GO
EXEC usp_DynTest 'VIEW'
GO
DROP PROCEDURE usp_DynTest
GO

Finding #temp table in sysobjects / INFORMATION_SCHEMA

I am running a SELECT INTO statement like this so I can manipulate the data before finally dropping the table.
SELECT colA, colB, colC INTO #preop FROM tblRANDOM
However when I run the statement and then, without dropping the newly created table, I then run either of the following statements, the table isn't found? Even scanning through object explorer I can't see it. Where should I be looking?
SELECT [name] FROM sysobjects WHERE [name] = N'#preop'
SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = '#preop'
Temp tables aren't stored in the local database, they're stored in tempdb. Also their name isn't what you named them; it has a hex code suffix and a bunch of underscores to disambiguate between sessions. And you should use sys.objects or sys.tables, not the deprecated sysobjects (note the big warning at the top) or the incomplete and stale INFORMATION_SCHEMA views.
SELECT name FROM tempdb.sys.objects WHERE name LIKE N'#preop[_]%';
If you are trying to determine if such an object exists in your session, so that you know if you should drop it first, you should do:
IF OBJECT_ID('tempdb.dbo.#preop') IS NOT NULL
BEGIN
DROP TABLE #preop;
END
In modern versions (SQL Server 2016+), this is even easier:
DROP TABLE IF EXISTS #preop;
However if this code is in a stored procedure then there really isn't any need to do that... the table should be dropped automatically when the stored procedure goes out of scope.
I'd prefer to query tempdb in such manner:
IF EXISTS (SELECT * FROM tempdb.sys.objects
WHERE object_id = OBJECT_ID(N'tempdb.[dbo].[#MyProcedure]')
AND type in (N'P', N'PC'))
BEGIN
print 'dropping [dbo].[#MyProcedure]'
DROP PROCEDURE [dbo].[#MyProcedure]
END
GO
Below is how I got the columns for a temporary table:
CREATE TABLE #T (PK INT IDENTITY(1,1), APP_KEY INT PRIMARY KEY)
SELECT * FROM tempdb.INFORMATION_SCHEMA.COLUMNS c WHERE c.TABLE_NAME LIKE '#T%'

Querying the same table for a list of databases in MS SQL Server

This is my first time posting on SO, so please go easy!
I'm attempting to write a SQL script that queries the same table for a list of databases in a single SQL Server instance.
I have successfully queried the list of databases that I required using the following, and inserting this data into a temp table.
Select name Into #Versions
From sys.databases
Where name Like 'Master%'
Master is suffixed with numerical values to identify different environments.
Select * From #Versions
Drop Table #Versions
The table name I am trying to query, is the same in each of the databases, and I want to extract the newest value from this table and insert it into the temp table for each of the database names returned.
I have tried researching this but to no avail. I am fairly comfy with SQL but I fear I could be out of my depth here.
You can do the following. Once you have the list of your databases, you can build up the query (you need to edit it for your purpose).
Select name Into #Versions
From sys.databases
Where name Like 'test%'
declare #sql as varchar(max) = ''
select #sql = #sql + 'INSERT INTO sometable SELECT TOP 1 * FROM ' + name + '..sourcetable ORDER BY somedate DESC; '
FROM #Versions
exec (#sql)
Drop Table #Versions
Look at The undocumented sp_MSforeachdb procedure and here