I need some help conceptualizing a project...
I have to run 4 different queries and send the results as the body of an email to defined recipients.
The problem is I will need to automate this process as I need to send the results every morning at 9am..., My initial thought was to just setup a job in SQL Server2000 and let that job to email the results, however this particular database is in SQL Server 2000...,
So then thought I could create a C# or Visual Basic program and use windows scheduler to run and email the reports, however I again come back to the fact that it is SQL Server 2000 and there is not a Stored Procedure to send mail.
I was able to find a Send Mail stored procedure online, but then couldn't figure out how to attach results to a parameter.
Any insight on how others would handle this would be greatly appreciated.
Thanks,
AJ
SQL 2000 does have jobs.
http://msdn.microsoft.com/en-us/library/aa215382(v=sql.80).aspx
How to create a job (Transact-SQL)
To create a job
1.Execute sp_add_job to create a job.
2.Execute sp_add_jobstep to create one or more job steps.
3.Execute sp_add_jobschedule to create a job schedule.
Email in SQL 2000 can be done via Outlook, but it's kind of a pain... Blat is free and does not need Outlook or POP3.
To avoid attachments and present a nice looking email, simply concat your row data into an HTML table and assign the result to the body. If you need more than 8000 characters, you'll have to use the text data type, but might be somewhat difficult
declare #result varchar(8000)
set #result = '<table>'
select #result = #result + '<tr><td>' + col1 + '</td><td>' + col2 + '</td></tr>'
from whereever
where something = something_else
order by 1
set #result = #result + '</table>'
http://www.blat.net/
Sample code: http://www.devx.com/dbzone/Article/42178
Exec stp_BlatMail 'ServerName', 'fhtapia#gmail.com',
'System Maintenance: Low Memory', 'D:\Data\TempFiles\MemoryRpt.txt'
CREATE PROCEDURE stp_BlatMail (#FromName AS VARCHAR (1000), #To AS VARCHAR(1000),
#Subject AS VARCHAR(1000), #BODY AS VARCHAR(8000),
#ATTACHMENTS AS VARCHAR(1024) = NULL) AS
-- By: Francisco H Tapia <fhtapia#gmail.com>
-- Date: 8/19/2003
-- Purpose: Provide Outlook free SMTP Mailing
SET NOCOUNT ON
DECLARE #CMD as VARCHAR(8000), #result as INT
IF #TO = ''
BEGIN
SELECT 'ERROR: NO TO Name'
END
ELSE
BEGIN
SELECT #CMD = ' D:\Data\Common\blat.exe - -subject "'+ #Subject
+ '" -t "' + #To
+ '" -sender "SystemUID#Domain.com" -from "'
+ #FromName
+'" -f "SQLMail" -ReplyTo "SystemUID#Domain.com" -org "My Company Name" -x "X-INFO: " -noh
-noh2 -server "ExchangeServerName" -port 25 -u EmailUID -pw Password -body "'
+ LTRIM(RTRIM(#Body)) + '" '
+ ' -q '
If #ATTACHMENTS <> ''
BEGIN
SELECT #CMD = #CMD + ' -attach "' + #ATTACHMENTS + '" '
END
ELSE IF #Attachments IS NOT NULL
BEGIN
SELECT 'NO ATTACHMENT FOUND'
END
EXEC #result = master..xp_cmdShell #CMD, NO_OUTPUT
END
SET NOCOUNT OFF
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
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 am trying to figure a way to execute a seemingly complex join scenario, and am unsure as to how to go about it.
Some Background info:
-I have a 'ProjectCategory' table, which contains a foreign key 'ProjectID' and 'CategoryID' to the Project and Category tables respectively. One project could have as many assigned categories to it as there are existing (up to 10)
-I have a 'Budget' table and a 'Sponsor' Table.
-My 'Project' table is related to my 'Budget' Table in that all Projects have an associated BudgetID
-My 'Budget' Table is related to my 'Sponser' table in that all Budgets have an associated SponsorID.
-'Project' table and 'Sponsor' table are not directly related.
An example of the result set that I am trying to get is firstly:
SponsorName(Field in sponsor table) - ProjectName - Category
___________________________________ ___________ ________
A ABC categoryA
A ABC categoryB
A DEF categoryX
A DEF categoryZ
I would then like to use a PIVOT to show the data like:
SponsorName - ProjectName -categoryA - categoryB -categoryC - categoryD ...
___________ ___________ _________ _________ _________ _________
A ABC X X
A DEF X X
B EFG X X
Where the Xs mark which categories are associated with each project/sponsor combination. The filling in of the Xs is maybe something I will do in the codebehind or using other stored procedures, but this is the basic idea of what I am trying to do.
I am having trouble even figuring out how to write a query to get back the first set before I even implement a pivot to show it as the second set, so I am a bit intimidated by this task. Any help greatly appreciated, and please let me know if you need any more information
Assuming SQL Server, I use a stored procedure for the bulk of Dynamic PIVOTS. (Listed Below)
The source could be a table, #temp or even SQL
Exec [prc-Pivot] '#Temp','Category','max(''X'')[]','SponsorName,ProjectName',null
Returns
SponsorName ProjectName categoryA categoryB categoryD categoryX categoryZ
A ABC X X NULL NULL NULL
A DEF NULL NULL NULL X X
B EFG X NULL X NULL NULL
The Stored Procedure
CREATE PROCEDURE [dbo].[prc-Pivot] (
#Source varchar(1000), -- Any Table or Select Statement
#PvotCol varchar(250), -- Field name or expression ie. Month(Date)
#Summaries varchar(250), -- aggfunction(aggValue)[optionalTitle]
#GroupBy varchar(250), -- Optional additional Group By
#OtherCols varchar(500) ) -- Optional Group By or aggregates
AS
--Exec [prc-Pivot] 'Select Year=Year(TR_Date),* From [Chinrus-Series].[dbo].[DS_Treasury_Rates]','''Q''+DateName(QQ,TR_Date)','avg(TR_Y10)[-Avg]','Year','count(*)[Records],min(TR_Y10)[Min],max(TR_Y10)[Max],Avg(TR_Y10)[Avg]'
Set NoCount On
Set Ansi_Warnings Off
Declare #Vals varchar(max),#SQL varchar(max);
Set #Vals = ''
Set #OtherCols= IsNull(', ' + #OtherCols,'')
Set #Source = case when #Source Like 'Select%' then #Source else 'Select * From '+#Source end
Create Table #TempPvot (Pvot varchar(100))
Insert Into #TempPvot
Exec ('Select Distinct Convert(varchar(100),' + #PvotCol + ') as Pvot FROM (' + #Source + ') A')
Select #Vals = #Vals + ', isnull(' + Replace(Replace(#Summaries,'(','(CASE WHEN ' + #PvotCol + '=''' + Pvot + ''' THEN '),')[', ' END),NULL) As [' + Pvot ) From #TempPvot Order by Pvot
Drop Table #TempPvot
Set #SQL = Replace('Select ' + Isnull(#GroupBy,'') + #OtherCols + #Vals + ' From (' + #Source + ') PvtFinal ' + case when Isnull(#GroupBy,'')<>'' then 'Group By ' + #GroupBy + ' Order by ' + #GroupBy else '' end,'Select , ','Select ')
--Print #SQL
Exec (#SQL)
Set NoCount Off
Set Ansi_Warnings on
I am taking backups of certain sql server databases programmatically using c#. I figured that Microsoft.SqlServer.Management.Smo and some other libraries are made for this purpose. Now I can backup a database. Very nice. Here is the code :
var server = new Server(#"" + InstanceName);
var backuper = new Backup();
try
{
backuper.Action = BackupActionType.Database;
backuper.Database = DbName;
backuper.Devices.AddDevice(DbName + ".bak", DeviceType.File);
backuper.BackupSetName = DbName + " - Yedek";
backuper.BackupSetDescription = "Açık Bulut Depo - " + DbName + " - Yedek";
backuper.ExpirationDate = DateTime.Now.AddYears(20);
server.ConnectionContext.Connect();
backuper.SqlBackup(server);
}
catch(Exception ex){//..}
My question here is how can I get the path of the device that the database backed up into?
I know I can specify my own path as :
backuper.Devices.AddDevice("C:\SOMEPATH\" + DbName + ".bak", DeviceType.File);
Then I can actually know where it is, but what I want to do is back it up to its default location and get its path. Please help me out with this.
Correct Answer to this duplicate can be found here: https://stackoverflow.com/a/8791588/331889
Server.BackupDirectory;
Given you are already using SMO Objects it should be the simplest answer.
From this blog post, you could use the function below:
http://www.mssqltips.com/sqlservertip/1966/function-to-return-default-sql-server-backup-folder/
CREATE FUNCTION dbo.fn_SQLServerBackupDir()
RETURNS NVARCHAR(4000)
AS
BEGIN
DECLARE #path NVARCHAR(4000)
EXEC master.dbo.xp_instance_regread
N'HKEY_LOCAL_MACHINE',
N'Software\Microsoft\MSSQLServer\MSSQLServer',N'BackupDirectory',
#path OUTPUT,
'no_output'
RETURN #path
END;
I normally execute the below stored procedure immediately after backuper.SqlBackup(server);
to return the most recent backup destination path. I use this approach because using SMO, i give the application user the flexibility of backing up to any location / drive or even to a USB disk. So a user may decide not to backup to the default backup location and i want to return that location after the backup process is successfully completed.
USE [YouDatabaseNameHere]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author: SQL.NET Warrior
-- =============================================
CREATE PROCEDURE [dbo].[GetBackupHistory]
AS
BEGIN
SET NOCOUNT ON;
DECLARE #SQLVer SQL_VARIANT
,#DBName VARCHAR(128)
,#NumDays SMALLINT
,#SQL VARCHAR(1024)
,#WhereClause VARCHAR(256)
SET #DBName = Null
;
SET #NumDays = 14
;
SET #SQLVer = CONVERT(INTEGER, PARSENAME(CONVERT(VARCHAR(20),SERVERPROPERTY('ProductVersion')),4));
SET #WhereClause = 'WHERE a.type IN (''D'',''I'')
And a.backup_start_date > GETDATE()- ' + CAST(#NumDays AS VARCHAR)+''
IF #DBName IS NOT NULL
BEGIN
SET #WhereClause = #WhereClause + '
AND a.database_name = '''+ #DBName +''''
END
SET #SQL = '
SELECT TOP 1 a.database_name,a.backup_start_date
,b.physical_device_name AS BackupPath
,a.position
,a.type
,a.backup_size/1024/1024 AS BackupSizeMB
,' + CASE
WHEN #SQLVer < 10
THEN '0'
ELSE 'a.compressed_backup_size/1024/1024'
END + ' AS CompressedBackMB
FROM msdb.dbo.backupset a
INNER JOIN msdb.dbo.backupmediafamily b
ON a.media_set_id = b.media_set_id
' + #WhereClause + '
ORDER BY a.backup_start_date DESC;';
--PRINT #SQL
EXECUTE (#SQL);
END;
GO
We are working on a C# application, we've been using Linq to SQL or standard ADO (when performance needed) to work with SQL Server.
We have a table layed out like so:
Customer ID, Year/Month, Product Name, Quantity
Each customer has additional columns per product.
We need display this information in a data grid like so:
Customer, Year/Month, Product A Quantity, Product B Quantity, Product C Quantity, etc.
What query could give us these results? And how could it be dynamic no matter what products are added and removed? We will be using a ListView in WPF for displaying the data.
We would just store the information differently, but they can add/remove products all the time.
Will PIVOT work?
(PS - the product names are really in another table for normalization, I changed it a little for simplicity for you guys)
The sql pivot command can be used but it requires the columns to be hard-coded. You could either hard-code them, use dynamic sql to generate the columns, or only get the raw data from sql without a pivot and do the data massaging in c#.
You can use pivot with dynamic SQL. Following T-SQL code is taken from this article on sqlteam.com. I've tried to modify the sample for your needs. Also beware of dangers using dynamic SQL, it might lead to SQL Injection if a product name contains apostrophe.
Create a stored proc first;
CREATE PROCEDURE crosstab
#select varchar(8000),
#sumfunc varchar(100),
#pivot varchar(100),
#table varchar(100)
AS
DECLARE #sql varchar(8000), #delim varchar(1)
SET NOCOUNT ON
SET ANSI_WARNINGS OFF
EXEC ('SELECT ' + #pivot + ' AS pivot INTO ##pivot FROM ' + #table + ' WHERE 1=2')
EXEC ('INSERT INTO ##pivot SELECT DISTINCT ' + #pivot + ' FROM ' + #table + ' WHERE '
+ #pivot + ' Is Not Null')
SELECT #sql='', #sumfunc=stuff(#sumfunc, len(#sumfunc), 1, ' END)' )
SELECT #delim=CASE Sign( CharIndex('char', data_type)+CharIndex('date', data_type) )
WHEN 0 THEN '' ELSE '''' END
FROM tempdb.information_schema.columns
WHERE table_name='##pivot' AND column_name='pivot'
SELECT #sql=#sql + '''' + convert(varchar(100), pivot) + ''' = ' +
stuff(#sumfunc,charindex( '(', #sumfunc )+1, 0, ' CASE ' + #pivot + ' WHEN '
+ #delim + convert(varchar(100), pivot) + #delim + ' THEN ' ) + ', ' FROM ##pivot
DROP TABLE ##pivot
SELECT #sql=left(#sql, len(#sql)-1)
SELECT #select=stuff(#select, charindex(' FROM ', #select)+1, 0, ', ' + #sql + ' ')
EXEC (#select)
SET ANSI_WARNINGS ON
Then try the following (I haven't test it, you might need to add qty to select statement)
EXECUTE crosstab 'select ProductID,CustomerID, YearMonth from sales group by ProductId', 'sum(qty)','ProductId','sales'
If you want to try a method that doesn't involve dynamic SQL, you could go through C#.
This guy ran a test comparing the two: http://weblogs.sqlteam.com/jeffs/jeffs/archive/2005/05/12/5127.aspx