I have two tables, one is Tab_Users containing user details and the other is Tab_CardEntry containing their card entries. I need to get their minimum and maximum card entry time for each user on a daily basis.
Here is the SQL query I used
SELECT
A.Name,
ISNULL(CONVERT(VARCHAR, MIN(B.DATED), 108), 'OFF') LOGEDIN,
ISNULL(CONVERT(VARCHAR, MAX(B.DATED), 108), 'OFF') LOGEDOUT
FROM TAB_USERS A
LEFT OUTER JOIN (
SELECT
USERNAME,
DATED
FROM TAB_CARDENTRY
WHERE CONVERT(VARCHAR, DATED, 106) = CONVERT(VARCHAR, '02 Jan 2014', 106)
) B ON A.USERNAME = B.USERNAME
GROUP BY A.Name
ORDER BY 1
I need this type of output
Here is a Stored Proc that you can try
CREATE PROCEDURE usp_GetTimes #StartDate DATETIME, #EndDate DATETIME = NULL
AS
BEGIN
--Make sure that only 31 days of data is used otherwise the temp table will be too big
IF #EndDate IS NULL OR DATEDIFF(dd, #StartDate, #EndDate) > 31
SET #EndDate = DATEADD(dd, 31, #StartDate)
SELECT UserID, CONVERT(DATETIME, FLOOR(CONVERT(FLOAT, DateTimeIn))) ForDate, DateTimeIn, DateTimeOut
INTO #Times
FROM
(
SELECT DISTINCT MIN(a.DATED) OVER (PARTITION BY a.USERNAME, CONVERT(DATETIME, FLOOR(CONVERT(FLOAT, DATED)))) DateTimeIn,
MAX(a.DATED) OVER (PARTITION BY a.USERNAME, CONVERT(DATETIME, FLOOR(CONVERT(FLOAT, DATED)))) DateTimeOut,
USERNAME AS UserID
FROM TAB_CARDENTRY a
WHERE DATED BETWEEN #StartDate and #EndDate + 1
) a
SELECT DISTINCT ForDate INTO #Dates FROM #Times
--Select * from #Times order by 1
--Select * from #Dates
DECLARE #SQLstring VARCHAR(MAX)
SET #SQLstring = 'CREATE TABLE ##Test (UserID varchar(10), '
DECLARE #ForDate DATETIME
WHILE 0 < (SELECT COUNT(*) FROM #Dates)
BEGIN
SET ROWCOUNT 1
SELECT #ForDate = ForDate FROM #Dates order by ForDate
SET ROWCOUNT 0
SET #SQLstring = #SQLstring + '[Entry ' + CONVERT(VARCHAR(12), #ForDate, 106) + '] datetime, [Exit ' + CONVERT(VARCHAR(12), #ForDate, 106) + '] datetime,' + CHAR(13) + CHAR(10)
DELETE #Dates WHERE ForDate = #ForDate
END
SET #SQLstring = SUBSTRING(#SQLSTRING, 1, LEN(#SQLSTRING) - 3) + ')'
EXEC (#SQLstring)
CREATE INDEX idx1 on ##Test(UserID)
DECLARE #UserID VARCHAR(10),
#DateIn DATETIME,
#DateOut DATETIME
WHILE 0 < (SELECT COUNT(*) FROM #Times)
BEGIN
SET ROWCOUNT 1
SELECT #UserID = UserID, #DateIn = DateTimeIn, #DateOut = DateTimeOut, #ForDate = ForDate from #Times
SET ROWCOUNT 0
SET #SQLString = 'IF NOT EXISTS(SELECT 1 FROM ##Test WHERE UserID = ''' + #UserID + ''')' + CHAR(13) + CHAR(10) +
' INSERT ##Test (UserID, [Entry ' + CONVERT(VARCHAR(12), #ForDate, 106) + '], [Exit ' + CONVERT(VARCHAR(12), #ForDate, 106) + ']) ' +
' SELECT ''' + #UserID + ''', ''' + CONVERT(VARCHAR(20), #DateIn, 113) + ''', ''' + CONVERT(VARCHAR(20), #DateOut, 113) + '''' + CHAR(13) + CHAR(10) +
'ELSE' + CHAR(13) + CHAR(10) +
' UPDATE ##Test SET [Entry ' + CONVERT(VARCHAR(12), #ForDate, 106) + '] = ''' + CONVERT(VARCHAR(20), #DateIn, 113) + ''', ' + CHAR(13) + CHAR(10) +
' [Exit ' + CONVERT(VARCHAR(12), #ForDate, 106) + '] = ''' + CONVERT(VARCHAR(20), #DateOut, 113) + ''' WHERE UserID = ''' + #UserID + ''''
EXEC (#SQLstring)
DELETE #Times WHERE UserID = #UserID AND ForDate = #ForDate
END
SELECT * FROM ##Test ORDER BY 1
DROP TABLE ##Test
END
EDIT
I've looked at the query again and you might not like the output, so you can change a part of the SP to look like this:
The part that creates the temp table
WHILE 0 < (SELECT COUNT(*) FROM #Dates)
BEGIN
SET ROWCOUNT 1
SELECT #ForDate = ForDate FROM #Dates order by ForDate
SET ROWCOUNT 0
SET #SQLstring = #SQLstring + '[Entry ' + CONVERT(VARCHAR(12), #ForDate, 106) + '] varchar(20), [Exit ' + CONVERT(VARCHAR(12), #ForDate, 106) + '] varchar(20),' + CHAR(13) + CHAR(10)
DELETE #Dates WHERE ForDate = #ForDate
END
And the part that inserts the data into the table
WHILE 0 < (SELECT COUNT(*) FROM #Times)
BEGIN
SET ROWCOUNT 1
SELECT #UserID = UserID, #DateIn = DateTimeIn, #DateOut = DateTimeOut, #ForDate = ForDate from #Times
SET ROWCOUNT 0
SET #SQLString = 'IF NOT EXISTS(SELECT 1 FROM ##Test WHERE UserID = ''' + #UserID + ''')' + CHAR(13) + CHAR(10) +
' INSERT ##Test (UserID, [Entry ' + CONVERT(VARCHAR(12), #ForDate, 106) + '], [Exit ' + CONVERT(VARCHAR(12), #ForDate, 106) + ']) ' +
' SELECT ''' + #UserID + ''', ''' + CONVERT(VARCHAR(20), ISNULL(#DateIn, '''OFF''', 108) + ''', ''' + CONVERT(VARCHAR(20), ISNULL(#DateOut, '''OFF'''), 108) + '''' + CHAR(13) + CHAR(10) +
'ELSE' + CHAR(13) + CHAR(10) +
' UPDATE ##Test SET [Entry ' + CONVERT(VARCHAR(12), #ForDate, 106) + '] = ''' + CONVERT(VARCHAR(20), ISNULL(#DateIn, '''OFF'''), 108) + ''', ' + CHAR(13) + CHAR(10) +
' [Exit ' + CONVERT(VARCHAR(12), #ForDate, 106) + '] = ''' + CONVERT(VARCHAR(20), ISNULL(#DateOut, '''OFF''', 108) + ''' WHERE UserID = ''' + #UserID + ''''
EXEC (#SQLstring)
DELETE #Times WHERE UserID = #UserID AND ForDate = #ForDate
END
Related
I have the following code, I need to run this code through over 200 tables, can I create a list and a for loop in the variable to go through all the tables and fetch the results for each? Also, how can I include the table name for each row in this query? I also need to go through a list on the column but I can't figure that out from the table list.
DECLARE #table AS VARCHAR(100)
DECLARE #column as varchar(50)
set #table = 'x'
set #column = 'y'
declare #query as varchar(max)
set #query = ' SELECT CAST(MONTH(' + #column + ') AS VARCHAR(2)) + ''-'' + CAST(YEAR(' + #column + ') AS VARCHAR(4)) as date, count(*) as SRC_CNT
FROM ' + #table +
' WHERE ' + #column + ' >= ''2018-01-01'' AND ' + #column + '< ''2021-12-01''
group BY CAST(MONTH(' + #column + ') AS VARCHAR(2)) + ''-'' + CAST(YEAR(' + #column + ') AS VARCHAR(4))
order by date;'
exec(#query)
Rather than loop, I would create a dynamic batch, with all the statements you need, which you can then execute. You can use your best friend to debug the statement(s) if needed:
DECLARE #Column sysname = N'y',
#DateFrom date = '20180101',
#DateTo date = '20211201';
DECLARE #SQL nvarchar(MAX),
#CRLF nchar(2) = NCHAR(13) + NCHAR(10);
SELECT #SQL = STRING_AGG(N'SELECT RIGHT(CONVERT(varchar(10),' + QUOTENAME(#Column) + N',105),7) AS [Date],' + #CRLF +
--N' N' + QUOTENAME(t.[name],'''') + N' AS TableName,' + #CRLF + --Uncomment this line if you need it.
N' COUNT(*) AS SRC_CNT' + #CRLF +
N'FROM ' + QUOTENAME(s.[name]) + N'.' + QUOTENAME(t.[name]) + #CRLF +
N'WHERE ' + QUOTENAME(#Column) + N' >= #DateFrom AND ' + QUOTENAME(#Column) + N' < #DateTo' + #CRLF +
N'GROUP BY RIGHT(CONVERT(varchar(10),' + QUOTENAME(#Column) + N',105),7)' + #CRLF +
N'ORDER BY [date];',#CRLF)
FROM sys.schemas s
JOIN sys.tables t ON s.schema_id = t.schema_id
WHERE EXISTS (SELECT 1
FROM sys.columns c
WHERE c.object_id = t.object_id
AND c.[name] = #Column);
--PRINT #SQL; --Your best friend
EXEC sys.sp_executesql #SQL, N'#DateFrom date, #DateTo date', #DateFrom, #DateTo;
Given tables and sample data:
CREATE TABLE dbo.x(y date, r int);
CREATE TABLE dbo.y(y date, r int);
CREATE TABLE dbo.z(y date, r int);
INSERT dbo.x(y,r) VALUES('20180105',5),
('20180107',6),('20180509',7);
INSERT dbo.y(y,r) VALUES('20180905',5),
('20181007',6),('20181009',7);
INSERT dbo.z(y,r) VALUES('20180605',5),
('20180607',6),('20180609',7);
This dynamic SQL can be generated:
DECLARE #col sysname = N'y',
#StartDate date = '20180101',
#EndDate date = '20211201';
DECLARE #sql nvarchar(max) = N'',
#base nvarchar(max) = N'SELECT [Table Name] = $tblQ$,
[date] = DATEFROMPARTS(YEAR($col$), MONTH($col$), 1),
SRC_CNT = COUNT(*)
FROM dbo.$tbl$ WHERE $col$ >= #s AND y < #e
GROUP BY YEAR($col$), MONTH($col$)';
SELECT #sql = STRING_AGG(REPLACE(REPLACE(REPLACE
(#base, N'$tblQ$', QUOTENAME(t.name, char(39))
),N'$tbl$',QUOTENAME(t.name)), N'$col$', #col), N'
UNION ALL
') + ' ORDER BY [date];'
FROM sys.tables AS t
WHERE EXISTS (SELECT 1 FROM sys.columns
WHERE [object_id] = t.[object_id]
AND name = #col);
PRINT #sql;
EXEC sys.sp_executesql #sql,
N'#s date, #e date',
#StartDate, #EndDate;
Which produces a query like this (only tables that actually have that column name, could be made even safer by making sure they're using a date/time type):
SELECT [Table Name] = 'x',
[date] = DATEFROMPARTS(YEAR(y), MONTH(y), 1),
SRC_CNT = COUNT(*)
FROM dbo.[x] WHERE y >= #s AND y < #e
GROUP BY YEAR(y), MONTH(y)
UNION ALL
SELECT [Table Name] = 'y',
[date] = DATEFROMPARTS(YEAR(y), MONTH(y), 1),
SRC_CNT = COUNT(*)
FROM dbo.[y] WHERE y >= #s AND y < #e
GROUP BY YEAR(y), MONTH(y)
UNION ALL
SELECT [Table Name] = 'z',
[date] = DATEFROMPARTS(YEAR(y), MONTH(y), 1),
SRC_CNT = COUNT(*)
FROM dbo.[z] WHERE y >= #s AND y < #e
GROUP BY YEAR(y), MONTH(y) ORDER BY [date];
That generates output like this:
Table Name
date
SRC_CNT
x
2018-01-01
2
x
2018-05-01
1
z
2018-06-01
3
y
2018-09-01
1
y
2018-10-01
2
Example db<>fiddle
If you really want yyyy-MM instead of yyyy-MM-dd on the output, you can just change this line in the declaration of #base:
[date] = CONVERT(char(7), DATEFROMPARTS(YEAR($col$), MONTH($col$), 1), 120),
And I realized the requirement was MM-yyyy, in which case:
[date] = RIGHT(CONVERT(char(10),
DATEFROMPARTS(YEAR($col$), MONTH($col$), 1), 105), 7),
I usually do the following for tables:
SELECT TABLE_NAME
FROM [<DATABASE_NAME>].INFORMATION_SCHEMA.TABLES
WHERE TABLE_TYPE = 'BASE TABLE'
You said you could handle columns from there.
ALTER PROCEDURE [dbo].[Expense_monthly_report] #start_date VARCHAR(100),
#end_date VARCHAR(100)
AS
BEGIN
DECLARE #SQLQuery AS NVARCHAR(max)
DECLARE #PivotColumns AS NVARCHAR(max)
SET nocount ON;
SELECT #PivotColumns = COALESCE(#PivotColumns + ',', '')
+ Quotename( date_of_expp)
FROM (SELECT DISTINCT Month(date_of_exp) AS date_of_expp
FROM [dbo].[tbl_exp_details]
WHERE date_of_exp >= #start_date
AND date_of_exp <= #end_date) AS PivotExample
SET #SQLQuery = N'SELECT p.part_id,' + #PivotColumns
+ ',total_amount FROM (SELECT part_id,month(date_of_exp) as month_of_exp,exp_amount FROM [dbo].[tbl_exp_details]) as tbl PIVOT( sum(exp_amount) FOR month_of_exp IN ('
+ #PivotColumns
+ ')) AS P join (SELECT part_id,sum(exp_amount) as total_amount FROM [dbo].[tbl_exp_details] where date_of_exp>='
+ #start_date + ' and date_of_exp<=' + #end_date
+ ' group by part_id) as la on p.part_id=la.part_id'
EXEC Sp_executesql #SQLQuery
END
I am getting error
date incompatible with int
. Help me to find out the problem.
You have not delimited the date values in the dynamic query.
So
date_of_exp<=' + #end_date
becomes
date_of_exp<= 2017-07-19
which evaluates to this, and int won't implicitly convert to date
date_of_exp <= 1991
The correct way to use Sp_executesql and fix this is:
SET #SQLQuery = N'SELECT p.part_id,' + #PivotColumns
+ ',total_amount FROM (SELECT part_id,month(date_of_exp) as month_of_exp,exp_amount FROM [dbo].[tbl_exp_details]) as tbl PIVOT( sum(exp_amount) FOR month_of_exp IN ('
+ #PivotColumns
+ ')) AS P join (SELECT part_id,sum(exp_amount) as total_amount FROM [dbo].[tbl_exp_details] where date_of_exp>= #start_date and date_of_exp<=#end_date '
+ ' group by part_id) as la on p.part_id=la.part_id'
EXEC Sp_executesql #SQLQuery,
N'#start_date date, #end_date date',
#start_date,
#end_date
I have 2 tables, one with hoppers and the ingredients in them (Recorded once a day or when ingredients are changed)
Example:
Hoppers | Ingredients | Timestamp
--------+---------------+----------------------
Hop_1 | Ing_A | 8/22/2016 06:00:00
Hop_2 | Ing_B | 8/22/2016 06:00:00
etc...
And I have a second table that has totals used from each hopper recorded every hour
Example:
Name | Value | Timestamp
-------------------+----------+---------------------
Hop_1 Daily Total | 100 | 8/22/2016 11:00:00
Hop_1 Run Total | 30 | 8/22/2016 11:00:00
etc...
I would like to create a view that shows amount produced from each hopper and says the name.
Example:
Hop_1 Ingredient | Hop_1 Daily | Hop_1 Run | Timestamp
-----------------+-------------+-----------+-------------------
Ing_A | 100 | 30 | 8/22/2016 11:00:00
Sorry if it doesn't look good, I'm new at formatting
For the record I don't really think that structure is the best idea because it requires dynamic sql to pull it off with a couple of loops and lots of LEFT SELF JOINS. But here you go:
IF OBJECT_ID('tempdb..#Ingredients') IS NOT NULL
BEGIN
DROP TABLE #Ingredients
END
IF OBJECT_ID('tempdb..#Totals') IS NOT NULL
BEGIN
DROP TABLE #Totals
END
CREATE TABLE #Ingredients (Hoppers VARCHAR(25), Ingredeients VARCHAR(25), [Timestamp] DATETIME)
CREATE TABLE #Totals (Name VARCHAR(50), Value INT, [Timestamp] DATETIME)
INSERT INTO #Ingredients (Hoppers, Ingredeients, [Timestamp])
VALUES ('Hop_1','Ing_A','8/22/2016 06:00:00'),('Hop_2','Ing_B','8/22/2016 06:00:00'),('Hop_3','Ing_C','8/22/2016 06:00:00')
INSERT INTO #Totals (Name, Value, [Timestamp])
VALUES ('Hop_1 Daily Total',100,'8/22/2016 11:00:00'),('Hop_1 Run Total',30,'8/22/2016 11:00:00'),('Hop_1 Run Total',60,'8/22/2016 09:00:00')
,('Hop_2 Daily Total',500,'8/22/2016 11:00:00'),('Hop_2 Run Total',10,'8/22/2016 11:00:00'),('Hop_2 Run Total',5,'8/22/2016 10:00:00')
,('Hop_3 Daily Total',400,'8/22/2016 11:00:00'),('Hop_3 Run Total',85,'8/22/2016 11:00:00'),('Hop_3 Run Total',65,'8/22/2016 10:00:00')
DECLARE #HopperCount INT
SELECT #HopperCount = COUNT(DISTINCT i.Hoppers)
FROM
#Ingredients i
INNER JOIN #Totals t
ON t.Name LIKE i.Hoppers + '%'
DECLARE #I INT = 1
DECLARE #Sql NVARCHAR(MAX)
SET #Sql = '
;WITH cte AS (
SELECT
i.Hoppers
,i.Ingredeients
,Value
,t.[Timestamp]
,CASE WHEN t.Name LIKE ''%Daily%'' THEN 1 ELSE 0 END as IsDaily
,ROW_NUMBER() OVER (PARTITION BY i.Hoppers, t.name ORDER BY t.[Timestamp] DESC) as RunRowNum
,DENSE_RANK() OVER (PARTITION BY 1 ORDER BY i.Hoppers) as HooperNumber
FROM
#Ingredients i
INNER JOIN #Totals t
ON t.Name LIKE i.Hoppers + ''%''
AND i.[Timestamp] <= t.[Timestamp]
)
SELECT
c.Ingredeients AS [Hop_' + CAST(#I AS VARCHAR(10)) + ' Ingredient]
,SUM(CASE WHEN c.IsDaily = 1 THEN c.Value END) AS [Hop_' + CAST(#I AS VARCHAR(10)) + ' Daily]
,SUM(CASE WHEN c.IsDaily = 0 THEN c.Value END) AS [Hop_' + CAST(#I AS VARCHAR(10)) + ' Run]
,MAX(c.[Timestamp]) AS [Hop_' + CAST(#I AS VARCHAR(10)) + ' Timestamp] '
SET #I = 2
WHILE #I <= ISNULL(#HopperCount,0)
BEGIN
SET #Sql = #Sql + '
,c'+ CAST(#I AS VARCHAR(10)) + '.Ingredeients AS [Hop_' + CAST(#I AS VARCHAR(10)) + ' Ingredient]
,SUM(CASE WHEN c'+ CAST(#I AS VARCHAR(10)) + '.IsDaily = 1 THEN c' + CAST(#I AS VARCHAR(10)) + '.Value END) AS [Hop_' + CAST(#I AS VARCHAR(10)) + ' Daily]
,SUM(CASE WHEN c' + CAST(#I AS VARCHAR(10)) + '.IsDaily = 0 THEN c' + CAST(#I AS VARCHAR(10))+ '.Value END) AS [Hop_' + CAST(#I AS VARCHAR(10)) + ' Run]
,MAX(c' + CAST(#I AS VARCHAR(10)) + '.[Timestamp]) AS [Hop_' + CAST(#I AS VARCHAR(10)) + ' Timestamp] '
SET #I = #I + 1
END
SET #Sql = #Sql + '
FROM
cte c '
SET #I = 2
WHILE #I <= ISNULL(#HopperCount,0)
BEGIN
SET #Sql = #Sql + '
LEFT JOIN cte c' + CAST(#I AS VARCHAR(10)) + '
ON c.HooperNumber + ' + CAST(#I - 1 AS VARCHAR(10)) + ' = c' + CAST(#I AS VARCHAR(10)) + '.HooperNumber
AND c' + CAST(#I AS VARCHAR(10))+ '.RunRowNum = 1
AND c.IsDaily = c' + CAST(#I AS VARCHAR(10)) + '.IsDaily '
SET #I = #I + 1
END
SET #Sql = #Sql + '
WHERE
c.HooperNumber = 1
AND c.RunRowNum = 1
GROUP BY
c.Ingredeients
'
SET #I = 2
WHILE #I <= ISNULL(#HopperCount,0)
BEGIN
SET #Sql = #Sql + ',c' + CAST(#I AS VARCHAR(10)) + '.Ingredeients
'
SET #I = #I + 1
END
EXECUTE (#sql)
You could use a Pivot table for your need.
Here's a link : https://technet.microsoft.com/en-us/library/ms177410(v=sql.105).aspx
I need to store the result of the following dynamic pivot to a temp table. Any suggesiotns?
DECLARE #serviceid int = 66;
DECLARE #SQL nvarchar(max);
WITH E(n) AS( SELECT n FROM (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0))E(n) ),
cteTally(n) AS( SELECT TOP(SELECT /*TOP 1*/ COUNT(*) cnt
FROM #T WHERE serviceid = #serviceid--Comment this and uncomment the other part to use the full table. /*GROUP BY serviceid
ORDER BY cnt DESC*/) ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) n
FROM E a, E b )
SELECT #SQL = N'WITH CTE AS(' + CHAR(10) + CHAR(9) + N'SELECT *,
ROW_NUMBER() OVER( PARTITION BY serviceid ORDER BY serviceid) AS row_num' + CHAR(10) + CHAR(9) + N'FROM #T'
+ CHAR(10) + CHAR(9) + N'WHERE serviceid = #serviceid' + CHAR(10) + N')' + CHAR(10) + CHAR(9)
+ N'SELECT serviceid' + CHAR(10) + CHAR(9) + N',ProgramId' + CHAR(10)
+ (SELECT CHAR(9) + ',MAX( CASE WHEN row_num = ' + CAST( n AS nvarchar(3))
+ ' THEN Firstbilleddate END) AS Firstbilleddate' + CAST( n AS nvarchar(3))
+ CHAR(10) FROM cteTally FOR XML PATH(''), TYPE).value('./text()[1]', 'nvarchar(max)')
+ (SELECT CHAR(9) + ',MAX( CASE WHEN row_num = ' + CAST( n AS nvarchar(3)) + ' THEN CoveragePlanName END) AS CoveragePlanName'
+ CAST( n AS nvarchar(3)) + CHAR(10) FROM cteTally
FOR XML PATH(''), TYPE).value('./text()[1]', 'nvarchar(max)')
+ CHAR(9) + N',SUM( ChargeAmount) AS ChargeAmount' + CHAR(10) + CHAR(9) + N',SUM( AdjustmentAmount) AS AdjustmentAmount'
+ CHAR(10) + CHAR(9) + N',SUM( PaymentAmount) AS PaymentAmount' + CHAR(10) + N'FROM CTE' + CHAR(10) + N'GROUP BY serviceid'
+ CHAR(10) + CHAR(9) + N',ProgramId;' + CHAR(10); PRINT #SQL; EXECUTE sp_executesql #SQL, N'#serviceid int', #serviceid;
I think the only way is to use an OPENROWSET and execute your dynamic sql query there. You would need to pass your dynamic SQL (including variables) as a string.
Have a look at Insert results of a stored procedure into a temporary table
My boss insists I send him a daily status report "the hacker way" by outputing HTML from a stored procedure and sending the return to his email using Database mailer. My colleague and I both agree that we should be using SSRS for this matter but since we aren't the ones with the money we unfortunately have to get stuck doing it this way. I myself have never seen this done and am having some formatting issues getting my table to format with cells. Can anyone shed some light on how to get this to work?
BEGIN
DECLARE #tableHTML NVARCHAR(MAX) ;
SET #tableHTML =
N'<b>Shots Statistics for ' + convert(varchar, GETDATE()-1, 101) + ':</b>' +
N'<table border="1" width="400">' +
CAST (
(SELECT 'Practice:' + PracticeName as Status, 'Aprima ID:'+ AprimaSiteID,
'Daily Count:' + CAST(Daily AS nvarchar(5)), 'Monthly Count:' + CAST(Monthly AS nvarchar(5))
FROM [CriticalKeyDatabase].[dbo].[ShotsManagement]
ORDER BY Status
FOR XML PATH('tr'), TYPE
) AS NVARCHAR(MAX) ) +
N'</table>';
exec msdb.dbo.sp_send_dbmail #profile_name='aProfileName', #recipients='anEmail#Email.com', #body=#tableHTML, #subject='Daily Shots', #importance='High', #body_format = 'HTML'
END
GO
I am basing this off a previous employee he had who gave him the idea. I don't see any declarations for cells anywhere in his query. I am not the best at SQL either..
DECLARE #tableHTML NVARCHAR(MAX) ;
SET #tableHTML =
N'<b>Message Statistics for ' + convert(varchar, GETDATE()-1, 101) + ':</b>' +
N'<table border="1" width="400">' +
CAST ( ( SELECT td = Status, ' ',
td = convert(varchar, StatusValue) + '', ' '
FROM
(SELECT 'Unsent Messages' as Status, Count(*) as StatusValue
FROM [CriticalKeyDatabase].[dbo].[AllJobs_V]
where convert(varchar, CreateDate, 101) = convert(varchar, GETDATE()-1, 101) and ReadyToSend = 1
UNION
SELECT 'Total Messages' as Status, Count(*) as StatusValue
FROM [CriticalKeyDatabase].[dbo].[AllJobs_V]
where convert(varchar, CreateDate, 101) = convert(varchar, GETDATE()-1, 101)) A
ORDER BY StatusValue
FOR XML PATH('tr'), TYPE
) AS NVARCHAR(MAX) ) +
N'</table>' +
N'<b>Sender Statistics for ' + convert(varchar, GETDATE()-1, 101) + ':</b>' +
N'<table border="1" width="400">' +
CAST ( ( SELECT td = Status, ' ',
td = convert(varchar, StatusValue) + '', ' '
FROM
(SELECT 'Sender: ' + Requestor as Status, Count(*) as StatusValue
FROM [CriticalKeyDatabase].[dbo].[AllJobs_V]
where convert(varchar, CreateDate, 101) = convert(varchar, GETDATE()-1, 101)
group by Requestor) A
ORDER BY Status
FOR XML PATH('tr'), TYPE
) AS NVARCHAR(MAX) ) +
N'</table>' +
N'<b>Recipient Statistics for ' + convert(varchar, GETDATE()-1, 101) + ':</b>' +
N'<table border="1" width="400">' +
CAST ( ( SELECT td = Status, ' ',
td = convert(varchar, StatusValue) + '', ' '
FROM
(SELECT 'Recipient: ' + Recipient as Status, Count(*) as StatusValue
FROM [CriticalKeyDatabase].[dbo].[AllJobs_V]
where convert(varchar, CreateDate, 101) = convert(varchar, GETDATE()-1, 101)
group by Recipient) A
ORDER BY Status
FOR XML PATH('tr'), TYPE
) AS NVARCHAR(MAX) ) +
N'</table>' +
N'<b>Event Statistics for ' + convert(varchar, GETDATE()-1, 101) + ':</b>' +
N'<table border="1" width="400">' +
CAST ( ( SELECT td = Status, ' ',
td = convert(varchar, StatusValue) + '', ' '
FROM
(SELECT 'Event: ' + EventType as Status, Count(*) as StatusValue
FROM [CriticalKeyDatabase].[dbo].[AllJobs_V]
where convert(varchar, CreateDate, 101) = convert(varchar, GETDATE()-1, 101)
group by EventType) A
ORDER BY Status
FOR XML PATH('tr'), TYPE
) AS NVARCHAR(MAX) ) +
N'</table>';
exec msdb.dbo.sp_send_dbmail #profile_name='Localhost', #recipients='someemailAddresses#email.com', #body=#tableHTML, #subject='Daily Statistics', #importance='High', #body_format = 'HTML'
END
The previous employee's reports looked like this.
But mine are coming out like this.
All of the usual stuff about that being a bad practice aside, what your ex-coworker seems to be doing is leveraging SQL Server's native XML capabilities. You are not getting the "td" tags because they are not being assigned in your sub-query.
If you look at his queries, you will see the "td = ..." constructs. The reason they work is because the sub-query is being treated as XML (due to the FOR XML PATH construct) and thus the "td = " clauses are being mapped to XML nodes.
Try adding that to your code and see if you get the proper table cells...
BEGIN
DECLARE #tableHTML NVARCHAR(MAX) ;
SET #tableHTML =
N'<b>Shots Statistics for ' + convert(varchar, GETDATE()-1, 101) + ':</b>' +
N'<table border="1" width="400">' +
CAST (
(SELECT td = 'Practice:' + PracticeName as Status, 'Aprima ID:'+ AprimaSiteID,
td = 'Daily Count:' + CAST(Daily AS nvarchar(5)), 'Monthly Count:' + CAST(Monthly AS nvarchar(5))
FROM [CriticalKeyDatabase].[dbo].[ShotsManagement]
ORDER BY Status
FOR XML PATH('tr'), TYPE
) AS NVARCHAR(MAX) ) +
N'</table>';
exec msdb.dbo.sp_send_dbmail #profile_name='aProfileName', #recipients='anEmail#Email.com', #body=#tableHTML, #subject='Daily Shots', #importance='High', #body_format = 'HTML'
END
GO
This will htmlify and email the contents of a #temp table (including column names). All of the styling is done with CSS, so just change #style if you want it to look different. If you want to include multiple tables, you can modify it to add #table2, #table3, etc.
Example use:
SELECT *
INTO #email_data
FROM <yadda yadda>
EXEC [dbo].[email_table]
#tablename = '#email_data'
,#recipients = 'me#company.com; boss#company.com;'
,#subject = 'TPS Reports'
Scripts
CREATE PROCEDURE [dbo].[table_to_html] (
#tablename sysname,
#html xml OUTPUT,
#order varchar(4) = 'ASC'
) AS
BEGIN
DECLARE
#sql nvarchar(max),
#cols nvarchar(max),
#htmlcols xml,
#htmldata xml,
#object_id int = OBJECT_ID('[tempdb].[dbo].'+QUOTENAME(#tablename));
IF #order <> 'DESC' SET #order = 'ASC';
SELECT #cols = COALESCE(#cols+',','')+QUOTENAME([name])+' '+#order
FROM tempdb.sys.columns
WHERE object_id = #object_id
ORDER BY [column_id];
SET #htmlcols = (
SELECT [name] AS [th]
FROM tempdb.sys.columns
WHERE object_id = #object_id
ORDER BY [column_id] FOR XML PATH(''),ROOT('tr')
);
SELECT #sql = COALESCE(#sql+',','SELECT #htmldata = (SELECT ')+'ISNULL(LTRIM('+QUOTENAME([name])+'),''NULL'') AS [td]'
FROM tempdb.sys.columns
WHERE object_id = #object_id
ORDER BY [column_id];
SET #sql = #sql + ' FROM '+QUOTENAME(#tablename)+' ORDER BY '+#cols+' FOR XML RAW(''tr''), ELEMENTS)';
EXEC sp_executesql #sql, N'#htmldata xml OUTPUT', #htmldata OUTPUT
SET #html = (SELECT #htmlcols,#htmldata FOR XML PATH('table'));
END
GO
CREATE PROCEDURE [dbo].[email_table] (
#tablename sysname,
#recipients nvarchar(max),
#subject nvarchar(max) = '',
#order varchar(4) = 'ASC'
) AS
BEGIN
IF OBJECT_ID('[tempdb].[dbo].'+QUOTENAME(#tablename)) IS NULL RAISERROR('Table does not exist. [dbo].[email_table] only works with temporary tables.',16,1);
DECLARE #style varchar(max) = 'table {border-collapse:collapse;} td,th {white-space:nowrap;border:solid black 1px;padding-left:5px;padding-right:5px;padding-top:1px;padding-bottom:1px;} th {border-bottom-width:2px;}';
DECLARE #table1 xml;
EXEC [dbo].[table_to_html] #tablename, #table1 OUTPUT, #order;
DECLARE #email_body AS nvarchar(max) = (
SELECT
(SELECT
#style AS [style]
FOR XML PATH('head'),TYPE),
(SELECT
#table1
FOR XML PATH('body'),TYPE)
FOR XML PATH('html')
);
EXEC msdb.dbo.sp_send_dbmail
#recipients = #recipients,
#subject = #subject,
#body = #email_body,
#body_format = 'html';
END
GO