I have 10 identical databases.
I get the database names at runtime.
I want to store rows into a collection of objects.
I also only want one hit on the database server.
My current approach:-
In a query (no stored procedures for X reason) I get list of databases and store in a temporary table.
Then I iterate through each database and create a dynamic query and execute it.
DECLARE #MaxRownum int SET #MaxRownum = (SELECT MAX(RowNum) FROM #Databases)
DECLARE #Iter int SET #Iter = 1
WHILE #Iter <= #MaxRownum
BEGIN
DECLARE #Database varchar(255) SELECT #Database = Databases FROM #Databases
WHERE RowNum = #Iter
IF HAS_DBACCESS(#Database) > 0
BEGIN
//appending query
END
SET #Iter = #Iter + 1
END
EXEC(#Query)
Can I use Linq + entity framework with one hit to server, without dynamic query and without hampering the performance? Is there any better solution?
Having no idea what your query is (I asked but you did not supply it), and not sure that you understand it is going to be extremely difficult to supply database names as variables without a "dynamic query", here is a much simpler way to do it IMHO:
DECLARE #sql NVARCHAR(MAX);
SELECT #sql = N'';
SELECT #sql = #sql + CHAR(13) + CHAR(10) + 'UNION ALL'
--// you will need to fill in your "//appending query" stuff here:
+ ' SELECT ... FROM ' + QUOTENAME(Databases) + '.dbo.tablename'
FROM #Databases
WHERE HAS_DBACCESS(Databases) = 1;
SET #sql = STUFF(#sql, 1, 9, '');
EXEC sp_executesql #sql;
Related
I want to duplicate all my tables in SQL Server, all table names would have had "temp" added at the beginning. And all of them would have had added an extra column (the same to all). I don't need whole code, just general idea how to do it.
A straightforward way to go:
You need to fetch the table names from your database (probably using INFORMATION_SCHEMA.TABLES).
For each of those tables from step 1, you need to generate a corresponding SELECT ... INTO statement.
You need to execute each generated SQL statement from step 2.
You already have a solution with a cursor. This is one without a cursor:
DECLARE #script VARCHAR(MAX) = '';
SELECT #script = #script + 'SELECT * INTO [temp'+ TABLE_NAME +'] FROM [' + TABLE_NAME + '];' + CHAR(13) + CHAR(10) FROM INFORMATION_SCHEMA.TABLES
EXEC (#script);
Remark: The CHAR(13) + CHAR(10) is not necessary; just added for readability if you want to check the script first (using PRINT instead EXEC).
Edit:
An additional question in the comments to add a checksum value in the resulting tables could be done as follows:
DECLARE #script VARCHAR(MAX) = '';
SELECT #script = #script + 'SELECT CHECKSUM(*) AS [__checksum], * INTO [temp'+ TABLE_NAME +'] FROM [' + TABLE_NAME + '];' + CHAR(13) + CHAR(10) FROM INFORMATION_SCHEMA.TABLES
EXEC (#script);
Using HASHBYTES instead of CHECKSUM is probably better, but it accepts only two parameters: the hash algorithm and a single value to hash. So in that case, you probably need to pass a string value by manually concatenating all the fields of your tables, and that may be somewhat troublesome to add in a dynamic query like mine. It would probably result in something more complex than just three lines...
Well, something like this, actually:
DECLARE #script NVARCHAR(MAX) = N'';
WITH
[Columns] AS
(
SELECT
TABLE_NAME AS [TableName],
COLUMN_NAME AS [ColumnName],
ROW_NUMBER() OVER (PARTITION BY TABLE_NAME ORDER BY ORDINAL_POSITION) AS [ColSeq]
FROM
INFORMATION_SCHEMA.COLUMNS
),
[Tables] AS
(
SELECT
[TableName],
CAST(N'[' + [ColumnName] + N']' AS NVARCHAR(MAX)) AS [ColumnList],
[ColSeq]
FROM
[Columns] AS C
WHERE
[ColSeq] = (SELECT MAX([ColSeq])
FROM [Columns]
WHERE [TableName] = C.[TableName])
UNION ALL
SELECT T.[TableName], N'[' + C.[ColumnName] + N'], ' + T.[ColumnList], C.[ColSeq]
FROM
[Tables] AS T
INNER JOIN [Columns] AS C ON C.[TableName] = T.[TableName] AND C.[ColSeq] = T.[ColSeq] - 1
)
SELECT #script = #script + N'SELECT HASHBYTES(''md5'', CONCAT(N'''', ' + [ColumnList] + N')) AS [__checksum], * INTO [temp' + [TableName] + N'] FROM [' + [TableName] + N'];' + NCHAR(13) + NCHAR(10)
FROM [Tables]
WHERE [ColSeq] = 1;
EXEC (#script);
Remarks:
In the recursive CTE [Tables], which is used for concatenating the column names of each table in a comma-separated string value, I started at the last column and moved backwards to ease the filter condition in my main query.
I added an additional first parameter N'' to the CONCAT calls in the resulting #script contents, since the CONCAT function requires at least 2 arguments, which would be troublesome in this case when processing tables with just one column.
In this case, despite the somewhat worse performance, it might be clearer and easier to fall back to using a cursor, like #HasanMahmood suggested in his answer...
try this code:
get all the table name form information schema and run a dynamic sql to create tables
DECLARE #script varchar(max)
DECLARE db_cursor CURSOR FOR
SELECT script = 'Select * Into [temp'+ TABLE_NAME +'] From ' + QUOTENAME(TABLE_NAME) FROM INFORMATION_SCHEMA.TABLES
OPEN db_cursor
FETCH NEXT FROM db_cursor INTO #script
WHILE ##FETCH_STATUS = 0
BEGIN
EXEC(#script)
--PRINT #script
FETCH NEXT FROM db_cursor INTO #script
END
CLOSE db_cursor
DEALLOCATE db_cursor
I have problem, I'm having trouble viewing user information using stored procedure. The procedure accepts three parameters: table, column and searchBySomething. Every time I want to search for a user using another column, the column variable receives the column of the id and the searchBySomething variable receives specific id, the procedure is work but when I'm send another column I'm get the error message
Invalid column name (the data)
The procedure looks like this :
ALTER PROCEDURE [dbo].[userDetailsDisplay]
#table NVARCHAR(30),
#column NVARCHAR(30),
#searchBySomething NVARCHAR(30)
DECLARE #sql NVARCHAR(100)
SET #sql = 'SELECT * FROM ' + #table + ' WHERE ' + #column + ' = ' + #searchBySomething
EXECUTE sp_executesql #sql
So the specific error you're getting is because you're not checking the input to see if the string being passed into #column actually exists. You can check for it's existence against the metadata catalog view sys.columns doing something like this:
if not exists
(
select 1
from sys.columns
where object_id = object_id(#table)
and name = #column
)
begin
raiserror('Column %s does not exist in table %t', 16, 1, #column, #table)
return
end
However I would be remiss if I didn't point out two things.
First, this dynamic table dynamic where clause pattern is very bad practice. If it's for someone who already has database access, they can simply query the tables themselves. And if it's for an external user, well, you've basically given them full database read access through this procedure. Of course there are some rare occasions where this pattern is needed, so if you're dead set on using dynamic sql, that leads me to my next point.
The code you've written is vulnerable to a SQL injection attack. Any time you use dynamic SQL you must be VERY careful how it's constructed. Say I passed in the column name ; drop database [admin]-- Assuming you had such a database, my could would happily be executed and your database would disappear.
How to make dynamic SQL safe is a complex topic, but if you're serious about learning more about it, this is probably one of the best articles you can find. http://www.sommarskog.se/dynamic_sql.html
By parameterizing your query and using quotename() on the table and column, I modified it to look like this. This will still throw weird errors if someone tries to do an injection attack, but at least it wont actually execute their code.
create procedure [dbo].[userDetailsDisplay]
#table nvarchar(30),
#column nvarchar(30),
#searchBySomething nvarchar(30)
as
begin
declare
#sql nvarchar(max),
#params nvarchar(1000)
if not exists
(
select 1
from sys.columns
where object_id = object_id(#table)
and name = #column
)
begin
raiserror('Column %s does not exist in table %t', 16, 1, #column, #table)
return
end
select #sql = '
select *
from ' + quotename(#table) + ' WHERE ' + quotename(#column) + ' = #searchBySomething'
execute sp_executesql
#stmt = #sql,
#params = '#searchBySomething nvarchar(30)',
#searchBySomething = #searchBySomething
end
Just check to make sure that the column exist in the table.
for each #table called, check that the #column variable is in that table.
SET #sql = 'SELECT * FROM ' + #table + ' WHERE ' + #column + ' = ' +''' #searchBySomething +''''
Ex : select * from table where column ='value'
Not worried about SQL Injection or anything of the like, just trying to get this to work. Using SSMS and Visual Studio.
I have C# code that passes a variable, GlobalVariables.username, to an SQL parameter.
private void btnNext_Click(object sender, EventArgs e)
{
if (checkIntrotoPublicSpeaking.Checked || checkEffectiveOralCommunication.Checked || checkProfComm.Checked)
{
List<SqlParameter> sqlOralComm = new List<SqlParameter>();
sqlOralComm.Add(new SqlParameter("Username", GlobalVariables.username));
sqlOralComm.Add(new SqlParameter("IntrotoPublicSpeaking", cboxIntrotoPublicSpeaking.Text));
sqlOralComm.Add(new SqlParameter("EffectiveOralCommunication", cboxEffectiveOralCommunication.Text));
sqlOralComm.Add(new SqlParameter("ProfComm", cboxProfComm.Text));
DAL.ExecSP("CreateOralComm", sqlOralComm);
}
}
I've been reading into Dynamic SQL and saw that to pass the table name as a parameter, you have to construct it manually and execute it as "SET..." etc, etc. I've been trying slightly different modifications of the last 3 lines below. Each time, I'm greeted with an "invalid syntax near ..." exception pertaining to different parts of that line. In stack exchange it's broken into 3 lines but in SSMS it's one line, a little easier to read.
Status is nvarchar column and Course is an int column.
ALTER PROCEDURE [dbo].[CreateOralComm]
-- Add the parameters for the stored procedure here
#Username nvarchar(30),
#IntrotoPublicSpeaking nvarchar(3),
#EffectiveOralCommunication nvarchar(3),
#ProfComm nvarchar(3)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
DECLARE #sql as nvarchar(max)
SET #sql = 'UPDATE ' + #Username + ' SET Grade = ' +
#IntrotoPublicSpeaking + ' Status = "Completed" WHERE Course = 7600105';
EXEC sp_executesql #sql;
END
GO
I know that global variable works, I have another line of code that's just a MessageBox displaying the value and it's correct. Just can't get those last few lines of SQL to work. I'm trying out just this first part, #IntrotoPublicSpeaking, before I move onto the other 2.
Any help would be really appreciated.
Two things here:
DECLARE #sql as nvarchar(max)
SET #sql = 'UPDATE ' + #Username + ' SET Grade = ' +
#IntrotoPublicSpeaking + ' Status = "Completed" WHERE Course = 7600105';
EXEC sp_executesql #sql;
Missing comma before Status and I think you do need to use single quotes
DECLARE #sql as nvarchar(max)
SET #sql = 'UPDATE ' + #Username + ' SET Grade = ' +
#IntrotoPublicSpeaking + ', Status = ''Completed'' WHERE Course = 7600105';
EXEC sp_executesql #sql;
I, want to write the best combination login either SQL store procedure or ASP.net for the following combination diagram.
Each field in the diagram has 4 combinations. So, total it will be 20 combinations. I, don't want to write 20 if else statement in SQL server or c#.
Here is the UI for the logic.
The user can either select Quotation no or combination of 20 etc. I, don't want to write 20 if else statement.
Is their any better way to write a statement in SQL or C# to make it better.
For example
the user can select from either Quotation no or agency name or start date or end date or combination of two or more field.
What is the best way to write the algorithm?
Here is the combination
1- Search By only Quotation No
2- Search By only Agency No
3- Search By only Start Date
4- Search By only End Date
5- Search By only contract No
6 - Quotation No + Agency No
7 - Quotation No + Start Date
8 - Quotation No + End Date
9 - Select by All fields
I stumbled upon a similar problem some time ago while trying to perform a search using many filters. The best solution I found was to use a dynamic SQL query, in which the statement is built based on the parameters.
The select clause of the sql statement is static but the from clause and the where clause is based on the parameters.
CREATE PROCEDURE dbo.SearchQuotation
(
#QuotationNo INT,
#AgencyName VARCHAR(50),
#StartDate DATETIME,
#EndDate DATETIME,
#Term INT
)
AS
BEGIN
DECLARE #SQL NVARCHAR(4000)
SELECT #SQL = N'SELECT * FROM Quotations WHERE 1 = 1'
DECLARE #ParametersDefinition NVARCHAR(4000)
SELECT #ParametersDefinition = N'#QuotationNoParameter INT,
#AgencyNameParameter VARCHAR(50),
#StartDateParameter DATETIME,
#EndDateParameter DATETIME,
#TermParameter INT'
IF #QuotationNo IS NOT NULL
SELECT #SQL = #SQL + N' AND QuotationNo = #QuotationNoParameter '
IF #AgencyName IS NOT NULL
SELECT #SQL = #SQL + N' AND AgencyName = #AgencyNameParameter '
IF #StartDate IS NOT NULL
SELECT #SQL = #SQL + N' AND StartDate = #StartDateParameter '
IF #EndDate IS NOT NULL
SELECT #SQL = #SQL + N' AND EndDate = #EndDateParameter '
IF #Term IS NOT NULL
SELECT #SQL = #SQL + N' AND Term = #TermParameter '
EXECUTE sp_executesql
#SQL,
#ParametersDefinition,
#QuotationNoParameter = #QuotationNo,
#AgencyNameParameter = #AgencyName,
#StartDateParameter = #StartDate,
#EndDateParameter = #EndDate,
#TermParameter = #Term
END
From the SQL side you can achieve by this way:
SELECT * FROM Qoutes AS q
WHERE (q.QoutationNo = #QoutationNo OR #QoutationNo IS NULL)
AND (q.AgencyName = #AgencyName OR #AgencyName IS NULL)
AND (q.StartDate = #StartDate OR #StartDate IS NULL)
AND (q.EndDate = #EndDate OR #EndDate IS NULL)
AND (q.Term = #Term OR #Term IS NULL)
Pass NULL value if it is not selected from the web page.
I have a int array of ID's (a lot of checkboxes I can choose from) which I want to get in one database call though a stored procedure.
Is there a way to work with an array of these ID's in SQL Server? I believe it should be something with splitting the array and then loop it (in sql). I just don't know how?
SQL Server 2008
There are many ways to do this:
Pass in a varchar parameter of the values separated by commas and parse that out (not very efficient, but for a small amount of data, not too bad except for the parsing bit)
Pass in XML and use the built in XML functions (SQL Server 2005+ has better support for this than earlier versions)
Use table value parameters (SQL Server 2008+)
Since you are using SQL Server 2008, use table value parameters.
EDIT: Example below
As #Oded mentioned, table valued parameters is the best option.
However, if for some reason you can't use these (perhaps your calling framework's limitations), you can use the following to perform the split to table:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER FUNCTION [dbo].[SplitToTable]
(
#List varchar(max), #Delim varchar(1)
)
RETURNS TABLE
AS
RETURN
(
WITH csvtbl(Start, [Stop]) AS (
SELECT Start = convert(bigint, 1), [Stop] =
charindex(#Delim COLLATE Slovenian_BIN2, #list + #Delim)
UNION ALL
SELECT Start = [Stop] + 1, [Stop] = charindex(#Delim
COLLATE Slovenian_BIN2, #list + #Delim, [Stop] + 1)
FROM csvtbl
WHERE ([Stop] > 0)
)
SELECT substring(#list, Start, CASE WHEN [Stop] > 0 THEN [Stop] -
Start ELSE 0 END) AS Value
FROM csvtbl
WHERE ([Stop] > 0)
)
You need to be aware of the default recursion depth of 100. If this isn't enough, increase it by adding the following to your outer calling query:
OPTION (MAXRECURSION 1000) -- or 0 for unlimited
EXAMPLE
SELECT *
FROM MyTable as t
WHERE t.ID IN (
SELECT *
FROM dbo.SplitToTable('1,2,12,34,101', ',')
)
It can be used on joins, etc., too.
I think you need something like...
Declare #query as varchar(500)
Declare #valuesList as varchar(100)
set #valuesList = '1,2,3'
set #query = 'select * From tableName where id in ( ' + #valuesList + ')'
exec(#query)
TO REVERSE THE PROCESS
DECLARE #t TABLE
(
ID int
)
INSERT INTO #t
VALUES (1), (3), (5), (7), (9)
SELECT STUFF(
(
SELECT ',' + CAST(t.ID AS VARCHAR(10))
FROM #t t
FOR XML PATH('')
), 1, 1, '') AS CSV
Courtesy SQLAuthority.