I've got a stored procedure that allows an IN parameter specify what database to use. I then use a pre-decided table in that database for a query. The problem I'm having is concatenating the table name to that database name within my queries. If T-SQL had an evaluate function I could do something like
eval(#dbname + 'MyTable')
Currently I'm stuck creating a string and then using exec() to run that string as a query. This is messy and I would rather not have to create a string. Is there a way I can evaluate a variable or string so I can do something like the following?
SELECT *
FROM eval(#dbname + 'MyTable')
I would like it to evaluate so it ends up appearing like this:
SELECT *
FROM myserver.mydatabase.dbo.MyTable
Read this... The Curse and Blessings of Dynamic SQL, help me a lot understanding how to solve this type of problems.
There's no "neater" way to do this. You'll save time if you accept it and look at something else.
EDIT: Aha! Regarding the OP's comment that "We have to load data into a new database each month or else it gets too large.". Surprising in retrospect that no one remarked on the faint smell of this problem.
SQL Server offers native mechanisms for dealing with tables that get "too large" (in particular, partitioning), which will allow you to address the table as a single entity, while dividing the table into separate files in the background, thus eliminating your current problem altogether.
To put it another way, this is a problem for your DB administrator, not the DB consumer. If that happens to be you as well, I suggest you look into partitioning this table.
try the sp_executesql built in function.
You can basically build up your SQL string in your proc, then call
exec sp_executesql #SQLString.
DECLARE #SQLString nvarchar(max)
SELECT #SQLString = '
SELECT *
FROM ' + #TableName
EXEC sp_executesql #SQLString
You can't specify a dynamic table name in SQL Server.
There are a few options:
Use dynamic SQL
Play around with synonyms (which means less dynamic SQL, but still some)
You've said you don't like 1, so lets go for 2.
First option is to restrict the messyness to one line:
begin transaction t1;
declare #statement nvarchar(100);
set #statement = 'create synonym temptablesyn for db1.dbo.test;'
exec sp_executesql #statement
select * from db_syn
drop synonym db_syn;
rollback transaction t1;
I'm not sure I like this, but it may be your best option. This way all of the SELECTs will be the same.
You can refactor this to your hearts content, but there are a number of disadvantages to this, including the synonym is created in a transaction, so you can't have two
of the queries running at the same
time (because both will be trying to
create temptablesyn). Depending
upon the locking strategy, one will
block the other.
Synonyms are permanent, so this is why you need to do this in a transaction.
There are a few options, but they are messier than the way you are already doing. I suggest you either:
(1) Stick with the current approach
(2) Go ahead and embed the SQL in the code since you are doing it anyway.
(3) Be extra careful to validate your input to avoid SQL Injection.
Also, messiness isn't the only problem with dynamic SQL. Remember the following:
(1) Dynamic SQL thwarts the server's ability to create a reusable execution plan.
(2) The ExecuteSQL command breaks the ownership chain. That means the code will run in the context of the user who calls the stored procedure NOT the owner of the procedure. This might force you to open security on whatever table the statement is running against and create other security issues.
Just a thought, but if you had a pre-defined list of these databases, then you could create a single view in the database that you connect to to join them - something like:
CREATE VIEW dbo.all_tables
AS
SELECT your_columns,
'db_name1' AS database_name
FROM db_name1.dbo.your_table
UNION ALL
SELECT your_columns,
'db_name2'
FROM db_name2.dbo.your_table
etc...
Then, you could pass your database name in to your stored procedure and simply use it as a paramater in a WHERE clause. If the tables are large, you might consider using an indexed view, indexed on the new database_name column (or whatever you call it) and the tables' primary key (I'm assuming from the question that the tables' schemas are the same?).
Obviously if your list of database changes frequently, then this becomes more problematic - but if you're having to create these databases anyway, then maintaining this view at the same time shouldn't be too much of an overhead!
I think Mark Brittingham has the right idea (here:
http://stackoverflow.com/questions/688425/evaluate-in-t-sql/718223#718223), which is to issue a use database command and write the sp to NOT fully qualify the table name. As he notes, this will act on tables in the login's current database.
Let me add a few possible elaborations:
From a comment by the OP, I gather the database is changed once a month, when it gets "too large". ("We have to load data into a new database each month or else it gets too large. – d03boy")
User logins have a default database, set with sp_defaultdb (deprecated) or ALTER LOGIN. If each month you move on to the new database, and don't need to run the sp on the older copies, just change the login's default db monthly, and again, don't fully qualify the table name.
The database to use can be set in the client login:
sqlcmd -U login_id -P password -d db_name, then exec the sp from there.
You can establish a connection to the database using the client of your choice (command line, ODBC, JDBC), then issue a use database command, the exec the sp.
use database bar;
exec sp_foo;
Once the database has been set using one of the above, you have three choices for executing the stored procedure:
You could just copy the sp along with the database, in to the new database. As long as the table name is NOT fully qualified, you'll operate on the new database's table.
exec sp_foo;
You could install the single canonical copy of the sp in its own database, call it procs, with the tablename not fully qualified, and then call its fuly qualified name:
exec procs.dbo.sp_foo;
You could, in each individual database, install a stub sp_foo that execs the fully qualified name of the real sp, and then exec sp_foo without qualifying it. The stub will be called, and it will call the real procedure in procs. (Unfortunately, use database dbname cannot be executed from within an sp.)
--sp_foo stub:
create proc bar.dbo.sp_foo
#parm int
as
begin
exec procs.dbo.sp_foo #parm;
end
go
However this is done, if the database is being changed, the real sp should be created with the WITH RECOMPILE option, otherwise it'll cache an execution plan for the wrong table. The stub of course doesn't need this.
You could create a SQL CLR Table-Valued UDF to access the tables. You have to tie it to the schema because TV-UDFs don't support dynamic schema. (My sample includes an ID and a Title column - modify for your needs)
Once you've done this, you should be able to do the follow query:
SELECT * FROM dbo.FromMyTable('table1')
You can include a multipart name in that string too.
SELECT * FROM dbo.FromMyTable('otherdb..table1')
to return the ID,Title columns from that table.
You will likely need to enable SQL CLR and turn on the TRUSTWORTHY option:
sp_configure 'clr enabled',1
go
reconfigure
go
alter database mydatabase set trustworthy on
Create a C# SQL Project, add a new UDF file, paste this in there. Set Project Property, Database, Permission Level to external. Build, deploy. Can be done without VisualStudio. Let me know if you need that.
using System;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using System.Collections;
using System.Data.SqlClient;
[assembly: CLSCompliant(true)]
namespace FromMyTable
{
public static partial class UserDefinedFunctions
{
[Microsoft.SqlServer.Server.SqlFunction(DataAccess = DataAccessKind.Read, IsDeterministic = true, SystemDataAccess = SystemDataAccessKind.Read, IsPrecise = true, FillRowMethodName = "FillRow",
TableDefinition = "id int, title nvarchar(1024)")]
public static IEnumerable FromMyTable(SqlString tableName)
{
return new FromMyTable(tableName.Value);
}
public static void FillRow(object row, out SqlInt32 id, out SqlString title)
{
MyTableSchema v = (MyTableSchema)row;
id = new SqlInt32(v.id);
title = new SqlString(v.title);
}
}
public class MyTableSchema
{
public int id;
public string title;
public MyTableSchema(int id, string title) { this.id = id; this.title = title; }
}
internal class FromMyTable : IEnumerable
{
string tableName;
public FromMyTable(string tableName)
{
this.tableName = tableName;
}
public IEnumerator GetEnumerator()
{
return new FromMyTableEnum(tableName);
}
}
internal class FromMyTableEnum : IEnumerator
{
SqlConnection cn;
SqlCommand cmd;
SqlDataReader rdr;
string tableName;
public FromMyTableEnum(string tableName)
{
this.tableName = tableName;
Reset();
}
public MyTableSchema Current
{
get { return new MyTableSchema((int)rdr["id"], (string)rdr["title"]); }
}
object IEnumerator.Current
{
get { return Current; }
}
public bool MoveNext()
{
bool b = rdr.Read();
if (!b) { rdr.Dispose(); cmd.Dispose(); cn.Dispose(); rdr = null; cmd = null; cn = null; }
return b;
}
public void Reset()
{
// note: cannot use a context connection here because it will be closed
// in between calls to the enumerator.
if (cn == null) { cn = new SqlConnection("server=localhost;database=mydatabase;Integrated Security=true;"); cn.Open(); }
if (cmd == null) cmd = new SqlCommand("select id, title FROM " + tableName, cn);
if (rdr != null) rdr.Dispose();
rdr = cmd.ExecuteReader();
}
}
}
declare #sql varchar(256);
set #sql = 'select * into ##myGlobalTemporaryTable from '+#dbname
exec sp_executesql #sql
select * from ##myGlobalTemporaryTable
copies into a global temporary table which you can then use like a regular table
If you have a reasonably manageable number of databases, it may be best to use a pre-defined conditional statement like:
if (#dbname = 'db1')
select * from db1..MyTable
if (#dbname = 'db2')
select * from db2..MyTable
if (#dbname = 'db3')
select * from db3..MyTable
...
you can generate this proc as part of your database creation scripts if you are changing the list of databases available to query.
This avoids security concerns with dynamic sql. You can also improve performance by replacing the 'select' statements with stored procedures targeting each database (1 cached execution plan per query).
if exists (select * from master..sysservers where srvname = 'fromdb')
exec sp_dropserver 'fromdb'
go
declare #mydb nvarchar(99);
set #mydb='mydatabase'; -- variable to select database
exec sp_addlinkedserver #server = N'fromdb',
#srvproduct = N'',
#provider = N'SQLOLEDB',
#datasrc = ##servername,
#catalog = #mydb
go
select * from OPENQUERY(fromdb, 'select * from table1')
go
Related
I adapted my code from the instructions here.
open FSharp.Data.Sql
let [<Literal>] connection_str = "Server=localhost;Port=3306;SSL Mode=None;Uid=<UID>;Pwd=<PWD>;Database=<DB>"
type provider = SqlDataProvider<Common.DatabaseProviderTypes.MYSQL, connection_str>
let context = provider.GetDataContext()
context.Procedures.SpGetFrontContracts.Invoke(1)
Intellisense works until the period after SpGetFrontContracts. After that, nothing. Trying to compile, I get:
Error FS0039 The field, constructor or member 'Invoke' is not defined.
I am otherwise able to connect to the database and insert and query data, as long as I stick to tables and views.
SpGetFrontContracts is a valid stored procedure in my database (its actual name is sp_get_front_contracts, but the type provider seems to remove underscores). I can run it successfully using HeidiSQL. In case it's useful, here's the create code:
CREATE DEFINER=`<UID>`#`localhost` PROCEDURE `sp_get_front_contracts`(
IN `Group` INT
)
LANGUAGE SQL
NOT DETERMINISTIC
CONTAINS SQL
SQL SECURITY DEFINER
COMMENT ''
BEGIN
SELECT p.DateTime, p.Contract, p.Volume, c.Name
FROM tbl_contract_price_data p
INNER JOIN tbl_contracts c on p.Contract = c.ID
WHERE c.`Group` = `Group`
ORDER BY p.DateTime
LIMIT 1000;
END
I tried creating a simpler sproc that was named 'test', took no parameters, and simply ran a select statement. It showed up in the type provider under Procedures, but again I could not call Invoke on it.
My best guess is that Invoke is being inherited from some namespace or assembly reference I don't have, so I'm currently looking through the SqlProvider source to try to figure out what it might be. I'm currently referencing:
FSharp.Core
FSharp.Data
FSharp.Data.SqlProvider
mscorlib
System
System.Core
System.Data
System.Numerics
System.ValueTyple
System.Xml.Linq
Thank you for any suggestions.
Edit: It looks like the action is in SqlDesignTime.fs in a function called generateSprocMethod that starts on line 346. I'll try to figure out what's going on in there.
Edit: After three days banging my head against this, I gave up and just used MySQL Connector/NET.
I'm not sure about your underlying data or structures, but I know these are two other (correct) ways to solve it:
WHERE c.`Group` = `Group` -- what you have.
-- possible corrections:
WHERE c.`Group` = p.`Group` -- did you mean this?
WHERE c.`Group` = "Group" -- or this?
I am calling a stored procedure from my Groovy code. The stored proc looks like this
SELECT * FROM blahblahblah
SELECT * FROM suchAndsuch
So basically, two SELECT statements and therefore two ResultSets.
sql.eachRow("dbo.testing 'param1'"){ rs ->
println rs
}
This works fine for a single ResultSet. How can I get the second one (or an arbitrary number of ResultSets for that matter).
You would need callWithAllRows() or its variant.
The return type of this method is List<List<GroovyRowResult>>.
Use this when calling a stored procedure that utilizes both output
parameters and returns multiple ResultSets.
This question is kind of old, but I will answer since I came across the same requirement recently and it maybe useful for future reference for me and others.
I'm working on a Spring application with SphinxSearch. When you run a query in sphinx, you get results, you need to run a second query to get the metadata for number of records etc...
// the query
String query = """
SELECT * FROM INDEX_NAME WHERE MATCH('SEARCHTERM')
LIMIT 0,25 OPTION MAX_MATCHES=25;
SHOW META LIKE 'total_found';
"""
// create an instance of our groovy sql (sphinx doesn't use a username or password, jdbc url is all we need)
// connection can be created from java, don't have to use groovy for it
Sql sql = Sql.newInstance('jdbc:mysql://127.0.0.1:9306/?characterEncoding=utf8&maxAllowedPacket=512000&allowMultiQueries=true','sphinx','sphinx123','com.mysql.jdbc.Driver')
// create a prepared statement so we can execute multiple resultsets
PreparedStatement ps = sql.getConnection().prepareStatement(query)
// execute the prepared statement
ps.execute()
// get the first result set and pass to GroovyResultSetExtension
GroovyResultSetExtension rs1 = new GroovyResultSetExtension(ps.getResultSet())
rs1.eachRow {
println it
}
// call getMoreResults on the prepared statement to activate the 2nd set of results
ps.getMoreResults()
// get the second result set and pass to GroovyResultSetExtension
GroovyResultSetExtension rs2 = new GroovyResultSetExtension(ps.getResultSet())
rs2.eachRow {
println it
}
Just some test code, this needs some improving on. You can loop the result sets and do whatever processing...
Comments should be self-explanatory, hope it helps others in the future!
I have a package which looks as follows:
Notice the two areas which I have marked with a red rectangle: they are identical in every way. Can I make changes to the package so I can avoid this duplication? It seems to me I cannot move them to a Data Flow Task since loops and File System Task do not exists there.
You can create a sub package for the Loop logic as #Bill mentioned, and the way to 'pass the resultset of a query on to another package' is as below (use SSIS 2012 as the example, I did the similar work SSIS 2005, so you only need to change the c# code to vb.net)
In your parent package, create a variable to hold the name of the resultSet variable in the parent package:
In your sub package, create a string variable parentResultSetName:
In your sub package, add a package configuration to mapping the parentResultSetName to the parent package variable resultSetVariableName:
Now, we can read the resultSet variable by the name in the script task of sub package:
public void Main()
{
// TODO: Add your code here
var dsName = Dts.Variables["parentResultSetName"].Value.ToString();
Variables variables = null;
DataSet resultSet = null;
Dts.VariableDispenser.LockForRead(dsName);
Dts.VariableDispenser.GetVariables(ref variables);
try
{
resultSet = variables[dsName].Value as DataSet;
if (resultSet != null)
{
MessageBox.Show("Sub package get: " + resultSet.Tables[0].Rows[0][0].ToString());
}
Dts.TaskResult = (int)ScriptResults.Success;
}
catch (Exception e)
{
Dts.Events.FireError(-1, "", e.Message, "", 0);
}
}
Here is the result:
Just place both your queries from "select batch logins" tasks to recordset and make another foreach loop over that recordset, executing those two queries from a variable.
I can see that in your second foreach loop there are some additional tasks, so you'll have incorporate them to "Select all batch logins" task somehow, or make constraints that'd match in both loops.
An alternative approach is to add an 'action' column to your 'batches' table. (or add an outrigger table). Work out beforehand what you want to do to the records. Then just delete the records and files once.
It looks like you are doing RBAR operations here.
So for example you run a couple of UPDATE statements against your tables that leave the tables in a state where each record is flagged for deletion or not.
Then after that you go through the loop and delete based off what is in the table.
It would make your package a lot simpler. and you can incorporate some 'commit' logic that makes sure a record and file are always deleted at the same time.
I wrote a Perl script to check the data in an Oracle database. Because the query process is very complex I chose to create a VIEW in the middle. Using this view the code could be largely simplified.
The Perl code run well when I used it to query the database starting from a file, like Perl mycode.pl file_a. The Perl code reads lines from file_a and creates/updates the view until the end of the input. The results I achieved are completely right.
The problem came when I simultaneously run
perl mycode.pl file_a
and
perl mycode.pl file_b
to access the same database. According to my observation, the VIEW used by the first process will be modified by the second process. These two processes were intertwined on the same view.
Is there any suggestion to make these two processes not conflict with one another?
The Perl code for querying database is normally like this, but the details in each real query is more complex.
my ($gcsta,$gcsto,$cms) = #t; #(details of #t is read from a line in file a or b)
my $VIEWSS = 'CREATE OR REPLACE VIEW VIEWSS AS SELECT ID,GSTA,GSTO,GWTA FROM TABLEA WHERE GSTA='.$gcsta.' AND GSTO='.$gcsto.' AND CMS='.$cms;
my $querying = q{ SELECT COUNT(*) FROM VIEWSS WHERE VIEWSS.ID=1};
my $inner_sth = $dbh->prepare($VIEWSS);
my $inner_rv = $inner_sth->execute();
$inner_sth = $dbh->prepare($querying);
$inner_rv = $inner_sth->execute();
You must
Create the view only once, and use it everywhere
Use placeholders in your SQL statements, and pass the actual parameters with the call to execute
Is this the full extent of your SQL? Probably not, but if so it really is fairly simple.
Take a look at this refactoring for some ideas. Note that is uses a here document to express the SQL. The END_SQL marker for the end of the text must have no whitespace before or after it.
If your requirement is more complex than this then please describe it to us so that we can better help you
my $stmt = $dbh->prepare(<<'END_SQL');
SELECT count(*)
FROM tablea
WHERE gsta = ? AND gsto = ? AND cms= ? AND id = 1
END_SQL
my $rv = $stmt->execute($gcsta, $gcsto, $cms);
If you must use a view then you should use placeholders in the CREATE VIEW as before, and make every set of changes into a transaction so that other processes can't interfere. This involves disabling AutoCommit when you create the database handle $dbh and adding a call to $dbh->commit when all the steps are complete
use strict;
use warnings;
use DBI;
my $dbh = DBI->connect('dbi:Oracle:mydbase', 'user', 'pass',
{ AutoCommit => 0, RaiseError => 1 } );
my $make_view = $dbh->prepare(<<'END_SQL');
CREATE OR REPLACE VIEW viewss AS
SELECT id, gsta, gsto, gwta
FROM tablea
WHERE gsta = ? AND gsto = ? AND cms= ? AND id = 1
END_SQL
my $get_count = $dbh->prepare(<<'END_SQL');
SELECT count(*)
FROM viewss
WHERE id = 1
END_SQL
while (<>) {
my ($gcsta, $gcsto, $cms) = split;
my $rv = $make_view->execute($gcsta, $gcsto, $cms);
$rv = $get_count->execute;
my ($count) = $get_count->fetchrow_array;
$dbh->commit;
}
Is the view going to be the same or different?
If the views are all the same then create it only once, or check if it exists with the all_views table : http://docs.oracle.com/cd/B12037_01/server.101/b10755/statviews_1202.htm#i1593583
You can easily create a view including your pid with the $$ variable to be the pid, but it wont be unique across computers, oracle has also some unique ids, see http://docs.oracle.com/cd/B14117_01/server.101/b10759/functions150.htm, for example, the SESSIONID.
But do you really need to do this? why dont you prepare a statement and then execute it? http://search.cpan.org/dist/DBI/DBI.pm#prepare
thanks,
mike
Assume SQL Server 2005 / 2008 with a large number of databases. Is there any way to quickly tell which database, if any, is attached to a particular .mdf file?
We've dropped some databases over time and would like to clean up some lingering .mdf's to clear up space on the server. Currently the only way I know of is to look at the properties of each database, one by one, in Management Studio and make a list of the files they're attached to. Looking for something a little more efficient than this, if anything exists.
sys.master_files contains one row per database for the first file (id = 1) for that database. That is, system tables will always be in fileid = 1 for each database
This is all you need:
SELECT
DB_NAME(database_id), physical_name
FROM
sys.master_files
This may help.
declare #files table (
db_name sysname,
physical_name nvarchar(260)
)
insert into #files
exec sp_MSforeachdb 'select "?", physical_name from ?.sys.database_files'
select db_name, physical_name
from #files
select db_name(database_id), * from sys.master_files
Will list all the files of all the databases known on the system.
You could also use OrcaMDF for this:
using (var file = new MdfFile(#"C:\Database.mdf"))
{
var bootPage = file.GetBootPage();
Console.WriteLine(bootPage.DatabaseName);
}
This'll allow you to query the mdf's for their database name without attaching them to the database server. Note that this should be done on the primary data file, in case there are multiple files. Disclaimer - I'm the author of OrcaMDF.
Looping through all files in the data directory, it'd be easy to join that with sys.databases and see which ones don't match up, and are hence non-attached mdf files.
Edit: Posted a more thorough example on my blog: http://improve.dk/archive/2011/05/19/checking-which-database-is-stored-in-a-deattached-mdf-file.aspx
Low tech solution... move the mdf file to another location. If it is attached, SQL server would not let you move it :)
From command prompt
cd X:\TheDir\Where\MDF\File\Are
mkdir UnusedMdf
move *.mdf UnusedDBFiles
move *.ldf UnusedDBFiles
All the unused files would be moved to UnusedDBFiles.
private bool IsDbAttached()
{
const string isAttachedSqL = #"SELECT count(*)
FROM sys.master_files
WHERE DB_NAME(database_id) = #DbName";
bool isAttached = false;
try
{
using (var connection = new SqlConnection(this.connectionString))
using (var command = new SqlCommand(isAttachedSqL, connection))
{
command.Parameters.Add("#DbName", SqlDbType.VarChar).Value = "dbName";
connection.Open();
var count = command.ExecuteScalar();
isAttached = (int)count > 0;
}
}
catch
{
throw;
}
return isAttached;
}