SQL Server : pivot or not to pivot - sql-server

I have a table in which I run a select statement and returns the following data
Name date skill seconds calls
----------------------------------------------
bob 9/2/2016 706 12771 56
bob 9/2/2016 707 4061 16
bob 9/2/2016 708 2577 15
bob 9/2/2016 709 2156 6
I want to return like below one row of data:
Name date 706sec 706call 707sec 707call 708sec 708call 709sec 709call
----------------------------------------------------------------------------------
bob 9/2/2016 12771 56 4061 16 2577 15 2156 6
My first attempt was a pivot but does not return a single row:
Select
name, date, seconds, calls, [706], [707],[708],[709],
from
(Select
name, date, skill, seconds, calls
From
tablecalls
Where
date between '09/02/2016 00:00' and '09/02/2016 23:59'
and name = 'bob') as b
pivot
(sum(seconds) for skill in ([706], [707], [708], [709] )) as p1
This returns:
name date calls 706sec 707sec 708sec 709sec
---------------------------------------------------------
bob 9/2/2016 6 NULL NULL NULL 2156
bob 9/2/2016 15 NULL NULL 2577 NULL
bob 9/2/2016 16 NULL 4061 NULL NULL
bob 9/2/2016 56 12771 NULL NULL NULL
Maybe PIVOT is not the right way to do this. Is there another way?

can you format your data in question. I am not sure whether I get youe point?
CREATE TABLE #tt([name] VARCHAR(10),[date] DATE,skill INT,seconds INT, calls int )
INSERT INTO #tt
SELECT 'bob','9/2/2016',706,12771,56 UNION all
SELECT 'bob','9/2/2016',707,4061,16 UNION all
SELECT 'bob','9/2/2016',708,2577,15 UNION all
SELECT 'bob','9/2/2016',709,2156,6
SELECT * FROM (
SELECT t.name,t.date,c.* FROM #tt AS t
CROSS APPLY(VALUES(LTRIM(t.skill)+'sec',t.seconds),(LTRIM(t.skill)+'calls',t.seconds)) c(t,v)
) AS A
PIVOT(MAX(v) FOR t IN ([706sec],[706calls],[707sec],[707calls],[708sec],[708calls],[709sec],[709calls])) p
name date 706sec 706calls 707sec 707calls 708sec 708calls 709sec 709calls
---------- ---------- ----------- ----------- ----------- ----------- ----------- ----------- ----------- -----------
bob 2016-09-02 12771 12771 4061 4061 2577 2577 2156 2156
if the count of skill is not fix, you can use dynamic script:
DECLARE #col VARCHAR(max),#sql VARCHAR(max)
SELECT #col=ISNULL(#col+',[','[')+LTRIM(skill)+'sec],['+LTRIM(skill)+'calls]' FROM #tt GROUP BY skill
SET #sql='
SELECT * FROM (
SELECT t.name,t.date,c.* FROM #tt AS t
CROSS APPLY(VALUES(LTRIM(t.skill)+''sec'',t.seconds),(LTRIM(t.skill)+''calls'',t.seconds)) c(t,v)
) AS A
PIVOT(MAX(v) FOR t IN ('+#col+')) p'
EXEC (#sql)

Related

Finding missing records in joined tables using ID and Dates in SQL?

I'm having trouble writing a check that will find missing records.
My tables are Provider, ProviderRelationship, Agreement, and AgreementProvider. I need to find missing records where the Provider has a ProviderRelationship, but no record for AgreementProvider for that specific time frame.
Both ProviderRelationship and AgreementProvider have StartDate and EndDate fields. Providers can only have 1 current ProviderRelationship, but they can have multiple ProviderRelationship entries. For example, ProviderA has ProviderRelationship1 on 3/17/2019, then ProviderRelationship2 from 3/18/2019 to 6/30/2020 and AgreementProvider2 for the same dates.
The closest I've come is this, which gets Providers that have never had any AgreementProvider entries at all. When I add in the date validation, it also includes the previous ProviderRelationships.
CREATE TABLE dbo.Provider
ProviderID INT IDENTITY(1,1) NOT NULL,
Name VARCHAR(200) NULL
ProviderID Name
9003055 ABC
6102 DEF
2743 Desired
9999 Ideal
CREATE TABLE dbo.ProviderRelationship
ProviderRelationshipID INT IDENTITY(1,1) NOT NULL,
ParentProviderID INT NOT NULL,
ProviderID INT NOT NULL,
StartDate DATE NOT NULL,
EndDate DATE NOT NULL
ProviderRelationshipID ParentProviderID ProviderID StartDate EndDate
1 9003055 9003055 2017-03-01 2017-03-27 --providers who are the parent provider should not appear
2 1021 9003055 2017-03-27 2017-03-27
3 1021 9003055 2017-03-28 2100-01-01 --should not appear
4 184 6102 2015-07-01 2015-07-01
5 6102 6102 2015-07-02 2100-01-01
6 244 2743 2015-07-01 2100-01-01 --there is no AgreementProviderID record for this one
7 1234 9999 2018-08-01 2019-09-01
8 4321 9999 2019-10-01 2100-01-01 --this is the ideal result, since there is a gap in AgreementProvider records
CREATE TABLE dbo.Agreement
AgreementID INT IDENTITY(1,1) NOT NULL
ProviderID INT NULL, --this is the ParentProviderID
RefRegionID INT NULL
AgreementID ProviderID
1 1021
2 1021
3 184
4 184
5 184
6 184
7 244
8 244
9 244
10 1234
11 4321
CREATE TABLE dbo.AgreementProvider
AgreementProviderID INT IDENTITY(1,1) NOT NULL
AgreementID INT NULL,
ProviderID INT NULL,
StartDate DATE NULL,
EndDate DATE NULL
AgreementProviderID AgreementID ProviderID StartDate EndDate
1 1 9003055 2017-03-28 2020-06-30
2 2 9003055 2017-03-28 2020-06-30
3 10 1234 2018-08-01 2020-06-30
SELECT DISTINCT o.*
FROM Provider p
JOIN dbo.ProviderRelationship prel ON prel.ProviderID = p.ProviderID
JOIN dbo.Agreement a ON a.ProviderID = prel.ParentProviderId
JOIN dbo.AgreementProvider ap ON ap.AgreementID = a.AgreementID
WHERE prel.ProviderID <> prel.ParentProviderId
AND p.ProviderID NOT IN (SELECT ap2.ProviderID FROM dbo.AgreementProvider ap2
JOIN dbo.ProviderRelationship prel2 ON prel2.ProviderID = ap2.ProviderID
WHERE prel.ParentProviderId = prel2.ParentProviderId)
It should not compare the date to a previous date, so ProviderID 9003055 should not appear. It should also not consider ProviderID when the ParentProviderID matches. It should get both Desired and Ideal ProviderIDs: Desired does not have a record, and Ideal does not have a current record for the current ProviderRelationship.
How would I rewrite this to include the specific time frame for each ProviderRelationship compared to the AgreementProvider information for those dates?
Ideal output would be this:
ProviderID ParentProviderID RelationshipStartDate RelationshipEndDate
2743 244 2015-07-01 2100-01-01
9999 4321 2019-10-01 2100-01-01
Maybe it's already doing what I want, because there are invalid entries for the ProviderIDs that I don't want included. Maybe this is good enough, I will have to confirm.

How to get only a distinct row

I have a table which is shown on below
id locale roaming rent
301 NULL 18.00 NULL
300 NULL NULL 5.00
299 11.00 NULL NULL
298 NULL NULL 4.00
297 NULL 20.00 NULL
296 NULL NULL 6.00
295 9.00 NULL NULL
294 NULL 20.00 NULL
293 10.00 NULL NULL
I want to get only one value in each column (without id column) in a row. How can I do it? BUT I want to do it only in one select query.
A possible solution:
SELECT (
SELECT TOP 1 [local]
FROM tbl
WHERE [local] IS NOT NULL
ORDER BY [id] DESC
) AS [local]
,(
SELECT TOP 1 [roaming]
FROM tbl
WHERE [roaming] IS NOT NULL
ORDER BY [id] DESC
) AS [roaming]
,(
SELECT TOP 1 [rent]
FROM tbl
WHERE [rent] IS NOT NULL
ORDER BY [id] DESC
) AS [rent]
I do it so
DECLARE #locale AS DECIMAL(11,2)
DECLARE #roaming AS DECIMAL(11,2)
DECLARE #rent AS DECIMAL(11,2)
SELECT #locale=COALESCE(locale, #locale), #roaming=COALESCE(roaming, #roaming), #rent=COALESCE(rent, #rent) FROM my_table
SELECT #locale as locale, #roaming as roaming, #rent as rent

SQL Pivot function for healthcare diagnosis codes

I'm trying to pivot data in my query (SQL Server 2008 R2) and I only need 2 columns pivoted but there may be up to 20 columns after the pivot. Here is my test data with up to 5 diagnosis codes:
pid DiagnosisCode
111 145.9
111 17.43
111 17.84
111 196.2
111 202.81
112 204.21
112 249.71
112 263.8
112 145.9
113 269.8
113 276.7
The output I'm trying to get looks like this:
pid | code1 | code2 | code3 | code4 | code5 | code6 | code... | code20
----------------------------------------------------------------------------
111 145.9 17.43 17.84 196.2 202.81 NULL NULL NULL
112 204.21 249.71 263.8 145.9 NULL NULL NULL NULL
113 269.8 276.7 NULL NULL NULL NULL NULL NULL
The code I have is :
select pid, DiagnosisCode
from
(select pid, DiagnosisCode, row_number() over(partition by pid order by pid)
as seq
from #temp
) as src
pivot
(min(DiagnosisCode)
for seq
in (DiagnosisCode)) pvt
For whatever reason, this function isn't clicking with me.I know that the MIN() aggregate function is required, but I don't need one in my output. I added the ROW_NUMBER() line so there would be a sequence field after reading one post, but I'm not sure why its needed. I've been reading all the other Pivot posts here and on other sites. I know this has been gone over many times on this site, but if you could help me understand what else I need in my query to get this to work I would be grateful.
You need to Fix your columns; the SQL Pivot table is different from Access, this is an example
declare #t as table (pid int , DiagnosisCode decimal(10,1))
insert #t select 111 , 145.9
insert #t select 111 , 17.43
insert #t select 111 , 17.84
insert #t select 111 , 196.2
insert #t select 111 , 202.81
insert #t select 112 , 204.21
insert #t select 112 , 249.71
insert #t select 112 , 263.8
insert #t select 112 , 145.9
insert #t select 113 , 269.8
insert #t select 113 , 276.7
select * from #t
select pid
,code1
,code2
,code3
,code4
,code5
,code6
,code7
,code8
,code9
,code10
,code11
,code12
,code13
,code14
,code15
,code16
,code17
,code18
,code19
,code20
from
(
select pid, DiagnosisCode, 'code' + cast(row_number() over(partition by pid order by pid) as varchar(2))
as seq
from #t
) as src
pivot
(min(DiagnosisCode)
for seq
in (
code1
,code2
,code3
,code4
,code5
,code6
,code7
,code8
,code9
,code10
,code11
,code12
,code13
,code14
,code15
,code16
,code17
,code18
,code19
,code20
)) pvt
pid code1 code2 code3 code4 code5 code6 code7 .... code20
---- ------ ------ ------- ------- ------ ------ ------ -------
111 145.9 17.4 17.8 196.2 202.8 NULL NULL NULL
112 204.2 249.7 263.8 145.9 NULL NULL NULL NULL
113 269.8 276.7 NULL NULL NULL NULL NULL NULL

Struggling with a dynamic pivot on multiple columns with one being concatenated

I have a table with data like so:
Employee PRDate Type Code Amount Subject Eligible
1234 1/1/2015 D 1 100.00 100.00 0.00
1234 1/1/2015 D 2 200.00 0.00 0.00
5678 1/1/2015 D 1 500.00 40.00 500.00
1234 1/1/2015 E 1 300.00 30.00 300.00
5678 1/1/2015 E 1 700.00 700.00 500.00
1234 1/1/2015 E 2 400.00 200.00 0.00
1234 1/8/2015 L 55 40.00 40.00 40.00
And I need for the data to be displayed like this:
Employee PRDate D1Amt D1Subj D1Elig D2Amt D2Subj D2Elig E1Amt E1Subj E1Elig E2Amt E2Subj E2Elig L55Amt L55Subj L55Elig
1234 1/1/2015 100.00 100.00 0.00 200.00 0.00 0.00 300.00 30.00 300.00 400.00 200.00 0.00 40.00 40.00 40.00
4678 1/1/2015 500.00 40.00 500.00 700.00 700.00 500.00
I can pivot on one column but when I try combining the Type and Code columns to get the one I get conversion errors (Type is a varchar and code is a tinyint). I'm not sure how to get to the desired results other than dynamic pivot. Can the desired results be achieved?
I've gotten this far but I can't figure out how to combine the type, code and each money columns (amount, subject and eligible) to get the data under the correct columns.
IF EXISTS (
SELECT *
FROM sys.tables
WHERE name LIKE '#temp285865%')
DROP TABLE #temp285865;
Create table dbo.#temp285865
(
EDLCodetemp varchar(10)
);
INSERT INTO #temp285865
(
[EDLCodetemp]
)
SELECT DISTINCT EDLCode
FROM #results
ORDER BY EDLCode;
-- Building a comma separated list of EDLCodes in #edltemp
DECLARE #cols varchar(1000);
SELECT #cols = COALESCE(#cols + ',[' + [EDLCodetemp] + ']', '[' + [EDLCodetemp] + ']')
FROM #temp285865;
-- Building the query appending columns
DECLARE #query varchar(4000);
SET #query =
'SELECT [CoName],
[PRCo],
[PRGroup],
[PREndDate],
[PaySeq],
[EDLType],
[Hours],
[SubjectAmt],
[EligibleAmt],
[PaidMth],
[LastName],
[FirstName],
[UseOver],
[OverAmt],
[Amount],
[PRGRDescrip],
[LimitPeriod],
[LimitMth],
[PREHEmployee],
[SortName],
[PaybackAmt],
[PaybackOverAmt],
[PaybackOverYN],
[PRDTEmployee],
[TrueEarns], '
+ #cols + ' FROM
(
SELECT [CoName],
[PRCo],
[PRGroup],
[PREndDate],
[PaySeq],
[EDLType],
[Hours],
[SubjectAmt],
[EligibleAmt],
[PaidMth],
[LastName],
[FirstName],
[PRDLDescrip],
[PRECDescrip],
[UseOver],
[OverAmt],
[Amount],
[PRGRDescrip],
[LimitPeriod],
[LimitMth],
[PREHEmployee],
[SortName],
[PaybackAmt],
[PaybackOverAmt],
[PaybackOverYN],
[PRDTEmployee],
[TrueEarns],
[EDLCode]
FROM #results
) p
PIVOT (
MAX(EDLCode)
FOR [EDLCode] IN (' + #cols + ')
)
as pvt';
EXEC(#query);
DROP TABLE #temp285865;
The following PIVOT with dynamic SQL would give you the result you want, based on the input data you provided (I changed the PRDate in the last row though).
The first statement builds an intermediate table #bt with the column names you want and the associated value. Then the column names are built in #cols for the dynamic SQL statement. Finally the intermediate table #bt is pivoted with a dynamic SQL statement using the #cols to pivot.
SET NOCOUNT ON;
CREATE TABLE #t(
Employee INT,
PRDate DATETIME,
Type CHAR(1),
Code TINYINT,
Amount DECIMAL(28,2),
Subject DECIMAL(28,2),
Eligible DECIMAL(28,2)
);
INSERT INTO #t(Employee,PRDate,Type,Code,Amount,Subject,Eligible)VALUES(1234,'2015-01-01','D',1,100.00,100.00,0.00);
INSERT INTO #t(Employee,PRDate,Type,Code,Amount,Subject,Eligible)VALUES(1234,'2015-01-01','D',2,200.00,0.00,0.00);
INSERT INTO #t(Employee,PRDate,Type,Code,Amount,Subject,Eligible)VALUES(5678,'2015-01-01','D',1,500.00,40.00,500.00);
INSERT INTO #t(Employee,PRDate,Type,Code,Amount,Subject,Eligible)VALUES(1234,'2015-01-01','E',1,300.00,30.00,300.00);
INSERT INTO #t(Employee,PRDate,Type,Code,Amount,Subject,Eligible)VALUES(5678,'2015-01-01','E',1,700.00,700.00,500.00);
INSERT INTO #t(Employee,PRDate,Type,Code,Amount,Subject,Eligible)VALUES(1234,'2015-01-01','E',2,400.00,200.00,0.00);
INSERT INTO #t(Employee,PRDate,Type,Code,Amount,Subject,Eligible)VALUES(1234,'2015-01-01','L',55,40.00,40.00,40.00);
SELECT
Employee,
PRDate,
Type+CAST(Code AS VARCHAR(3))+ca.name AS colname,
ca.val
INTO
#bt
FROM
#t
CROSS APPLY(
SELECT Amount AS val,'Amt' AS name
UNION ALL
SELECT Subject AS val,'Subj' AS name
UNION ALL
SELECT Eligible AS val,'Elig' AS name
) AS ca;
/* If you need to SUM for all dates, instead use this statement to create #bt
SELECT
Employee,
Type+CAST(Code AS VARCHAR(3))+ca.name AS colname,
ca.val
INTO
#bt
FROM
(
SELECT
Employee,
Type,
Code,
SUM(Amount) AS Amount,
SUM(Subject) AS Subject,
SUM(Eligible) AS Eligible
FROM
#t
GROUP BY
Employee,
Type,
Code
) AS t
CROSS APPLY(
SELECT Amount AS val,'Amt' AS name
UNION ALL
SELECT Subject AS val,'Subj' AS name
UNION ALL
SELECT Eligible AS val,'Elig' AS name
) AS ca;
*/
DECLARE #cols VARCHAR(8000);
SET #cols=STUFF(
(SELECT DISTINCT
',['+colname+']'
FROM
#bt
FOR XML PATH('')),
1,
1,
''
);
DECLARE #sql VARCHAR(MAX);
SET #sql='
SELECT
*
FROM
#bt
PIVOT(
MAX(val)
FOR colname IN ('+#cols+')
) AS piv
';
EXEC (#sql);
DROP TABLE #bt;
DROP TABLE #t;
The result is the following:
Employee PRDate D1Amt D1Elig D1Subj D2Amt D2Elig D2Subj E1Amt E1Elig E1Subj E2Amt E2Elig E2Subj L55Amt L55Elig L55Subj
1234 2015-01-01 100.00 0.00 100.00 200.00 0.00 0.00 300.00 300.00 30.00 400.00 0.00 200.00 40.00 40.00 40.00
5678 2015-01-01 500.00 500.00 40.00 NULL NULL NULL 700.00 500.00 700.00 NULL NULL NULL NULL NULL NULL

Finding the difference within a SQL Server query and dividing by seconds elapsed between records

Hi I have a table of meter values in a SQL Server database, which contains a table with the following columns:
Timestamp, meterID, rawValue
I am trying to graph the water usage rate using a query and google charts, the problem is that I need to calculate the rate from the raw meter values which are updated every 15 to 30 minutes.
I want to run a query that returns the values for a specific water meter.
MeterID, Timestamp, (rawValue-previousRawValue)/(timestamp difference in seconds)
any help is much appreciated.
Edit 1: I have modified index definition to eliminate LookUp operator => fewer logical reads.
Edit 2: I have added the second solution based on quirky update method. Please read this article (Solving the Running Total and Ordinal Rank Problems) written by Jeff Moden.
First solution can be tested with SQL Server 2005/2008:
--Create test table
CREATE TABLE dbo.MeterValues
(
ID INT IDENTITY(1,1) PRIMARY KEY
,[Timestamp] DATETIME NOT NULL
,MeterID INT NOT NULL
,RawValue INT NOT NULL
);
CREATE UNIQUE INDEX IUN_MeterValues_MeterID_Timestamp
--SQL Server 2008
ON dbo.MeterValues (MeterID, [Timestamp])
INCLUDE (RawValue)
--SQL Server 2005
--ON dbo.MeterValues (MeterID, [Timestamp],RawValue)
--DROP INDEX dbo.MeterValues.IUN_MeterValues_MeterID_Timestamp
--Insert some values
INSERT dbo.MeterValues ([Timestamp], MeterID, RawValue)
SELECT '2011-01-01T00:00:00', 1, 100
UNION ALL
SELECT '2011-01-01T00:00:15', 1, 105
UNION ALL
SELECT '2011-01-01T00:00:30', 1, 102
UNION ALL
SELECT '2011-01-01T00:00:45', 1, 108
UNION ALL
SELECT '2011-01-01T00:01:00', 1, 109
UNION ALL
SELECT '2011-01-01T00:00:00', 2, 1000
UNION ALL
SELECT '2011-01-01T00:00:15', 2, 900
UNION ALL
SELECT '2011-01-01T00:00:30', 2, 1105
UNION ALL
SELECT '2011-01-01T00:00:45', 2, 1050
UNION ALL
SELECT '2011-01-01T00:01:00', 2, 910;
--Check test data
SELECT *
FROM dbo.MeterValues mv
ORDER BY mv.MeterID, mv.ID DESC;
--Solution
WITH ValuesWithRowNumber
AS
(
SELECT mv.MeterID
,mv.RawValue
,mv.[Timestamp]
,ROW_NUMBER() OVER(PARTITION BY mv.MeterID ORDER BY mv.[Timestamp] ASC) RowNum
FROM dbo.MeterValues mv
)
SELECT crt.MeterID
,crt.[Timestamp] AS CrtTimestamp
,prev.[Timestamp] AS PrevTimestamp
,crt.RawValue AS CrtRawValue
,prev.RawValue AS PrevRawValue
,(crt.RawValue - prev.RawValue)*1.00/DATEDIFF(SECOND, prev.[Timestamp], crt.[Timestamp]) Diff
,STR((crt.RawValue - prev.RawValue)*1.00/DATEDIFF(SECOND, prev.[Timestamp], crt.[Timestamp])*100, 10, 2)+'%' [Percent]
FROM ValuesWithRowNumber crt --crt=current
LEFT JOIN ValuesWithRowNumber prev ON crt.MeterID = prev.MeterID --prev=previous
AND crt.RowNum - 1 = prev.RowNum
ORDER BY crt.MeterID, crt.[Timestamp] DESC;
--By, by
DROP TABLE dbo.MeterValues;
Results:
MeterID CrtTimestamp PrevTimestamp CrtRawValue PrevRawValue Diff Percent
----------- ----------------------- ----------------------- ----------- ------------ --------------------------------------- -----------
1 2011-01-01 00:01:00.000 2011-01-01 00:00:45.000 109 108 0.0666666666666 6.67%
1 2011-01-01 00:00:45.000 2011-01-01 00:00:30.000 108 102 0.4000000000000 40.00%
1 2011-01-01 00:00:30.000 2011-01-01 00:00:15.000 102 105 -0.2000000000000 -20.00%
1 2011-01-01 00:00:15.000 2011-01-01 00:00:00.000 105 100 0.3333333333333 33.33%
1 2011-01-01 00:00:00.000 NULL 100 NULL NULL NULL
2 2011-01-01 00:01:00.000 2011-01-01 00:00:45.000 910 1050 -9.3333333333333 -933.33%
2 2011-01-01 00:00:45.000 2011-01-01 00:00:30.000 1050 1105 -3.6666666666666 -366.67%
2 2011-01-01 00:00:30.000 2011-01-01 00:00:15.000 1105 900 13.6666666666666 1366.67%
2 2011-01-01 00:00:15.000 2011-01-01 00:00:00.000 900 1000 -6.6666666666666 -666.67%
2 2011-01-01 00:00:00.000 NULL 1000 NULL NULL NULL
The second solution can/should work with SQL 2000/2005/2008 (please read "The RULES" section from Jeff Moden article):
--Create test table
CREATE TABLE dbo.MeterValues
(
MeterID INT NOT NULL
,[Timestamp] DATETIME NOT NULL
,RawValue INT NOT NULL
,Diff NUMERIC(10,3) NULL
,PRIMARY KEY CLUSTERED(MeterID,[Timestamp])
);
--Insert some values
INSERT dbo.MeterValues ([Timestamp], MeterID, RawValue)
SELECT '2011-01-01T00:00:00', 1, 100
UNION ALL
SELECT '2011-01-01T00:00:15', 1, 105
UNION ALL
SELECT '2011-01-01T00:00:30', 1, 102
UNION ALL
SELECT '2011-01-01T00:00:45', 1, 108
UNION ALL
SELECT '2011-01-01T00:01:00', 1, 109
UNION ALL
SELECT '2011-01-01T00:00:00', 2, 1000
UNION ALL
SELECT '2011-01-01T00:00:15', 2, 900
UNION ALL
SELECT '2011-01-01T00:00:30', 2, 1105
UNION ALL
SELECT '2011-01-01T00:00:45', 2, 1050
UNION ALL
SELECT '2011-01-01T00:01:00', 2, 910;
--Check test data
SELECT *
FROM dbo.MeterValues mv
ORDER BY mv.MeterID, mv.[Timestamp];
DECLARE #OldRawValue INT
,#Diff NUMERIC(10,3)
,#OldMeterID INT
,#OldTimestamp DATETIME;
PRINT '*****Star*****'
--Calculations
UPDATE dbo.MeterValues WITH(TABLOCKX)
SET #Diff = CASE WHEN #OldMeterID = MeterID THEN (RawValue - #OldRawValue)*1.00/DATEDIFF(SECOND,#OldTimeStamp,[TimeStamp]) END
,Diff = #Diff
,#OldRawValue = RawValue
,#OldMeterID = MeterID
,#OldTimestamp = [Timestamp]
OPTION(MAXDOP 1);
--Results
SELECT *
FROM dbo.MeterValues mv
ORDER BY mv.MeterID, mv.[Timestamp];
PRINT '*****Stop*****'
--By, by
DROP TABLE dbo.MeterValues;
Results:
MeterID Timestamp RawValue Diff
----------- ----------------------- ----------- ---------------------------------------
1 2011-01-01 00:01:00.000 109 0.067
1 2011-01-01 00:00:45.000 108 0.400
1 2011-01-01 00:00:30.000 102 -0.200
1 2011-01-01 00:00:15.000 105 0.333
1 2011-01-01 00:00:00.000 100 NULL
2 2011-01-01 00:01:00.000 910 -9.333
2 2011-01-01 00:00:45.000 1050 -3.667
2 2011-01-01 00:00:30.000 1105 13.667
2 2011-01-01 00:00:15.000 900 -6.667
2 2011-01-01 00:00:00.000 1000 NULL
Try this
Select a.metered,a.timestamp,
(a.rawValue-b.rawValue)/(a.timestamp-b.timestamp)
From meters A
Join (selec top 2 rawValue,Timestamp
From meters where metered = #meter
order by timestamp DESC) b
On b.timestamp <> a.timestamp and a.meterId=B.meterId
Added a DESC to the timestamp in the middle query. This will cause the most recent two timestamps to be returned, and then the JOIN will "filter out" the one that matches the current row from A
I made a few minor changes to both my query and #Bogdan's query to make them as similar as possible, then compared them. Bogdan's modified query is at the bottom of this post.
Stacked together in the same query, according to the SQL Server Query Execution Plan, mine is 53% of the query cost, and Bogdan's is 47%.
For the data set offered in the Bogdan's post:
My query: 6 scans and 27 logical reads
Bogdan's: 6 scans and 72 logical reads
I added values every 15secs up to 5mins for both meterID 1 and 2, for a total of 42 records, then reran the queries with SQL Server Profiler.
Where my query wins on reads, Bogdan's still wins on CPU and Duration.
CPU SCANS READS DURATION
--------------------------------------
Mine 47 22 313 249ms
Bogdan's 16 22 972 15ms
--------------------------------------
I'm making a few assumptions, like that your MeterID is an INT. Change that as necessary.
I'm also assuming since you want to run the query for a specific meter ID, that it will be passed in as a parameter to a stored procedure.
This should work on SQL Server 2005 and later.
I do a few things that might distract from the actual solution. The core logic is really within the WHILE loop.
CREATE PROCEDURE [dbo].[GetMeterResults]
#MeterID INT
AS
BEGIN
-- create a temp table to store results
CREATE TABLE #tempResults
(
MeterID INT,
[Timestamp] DATETIME,
Result FLOAT
)
DECLARE
#Timestamp DATETIME,
#RawValue INT,
#LastTimestamp DATETIME,
#LastRawValue INT,
#FirstRun BIT = 1
DECLARE cr CURSOR FAST_FORWARD FOR
SELECT
[Timestamp],
RawValue
FROM
YourTable
WHERE
MeterID = #MeterID
ORDER BY
[Timestamp]
OPEN cr
FETCH NEXT FROM cr INTO #Timestamp, #RawValue
WHILE (##FETCH_STATUS = 0)
BEGIN
IF (#FirstRun = 1)
BEGIN -- the first run
SELECT #FirstRun = 0 -- flip the bit for all future runs
END
ELSE -- all subsequent runs after the first
BEGIN
INSERT INTO
#tempResults
SELECT
#MeterID,
#Timestamp,
(#RawValue - #LastRawValue) * 1.00 / DATEDIFF(s, #LastTimestamp, #Timestamp)
END
-- save the current values for comparison on the next run
SELECT
#LastTimestamp = #Timestamp,
#LastRawValue = #RawValue
FETCH NEXT FROM cr INTO #Timestamp, #RawValue
END
CLOSE CR
DEALLOCATE CR
-- return the result set
SELECT
*
FROM
#tempResults
-- clean up the temp table
DROP TABLE #tempResults
END
GO
Bogdan's modified query that filters by MeterID for an apples-to-apples comparison with my query above:
DECLARE #MeterID INT = 1;
WITH ValuesWithRowNumber
AS
(
SELECT mv.MeterID
,mv.RawValue
,mv.[Timestamp]
,ROW_NUMBER() OVER(PARTITION BY mv.MeterID ORDER BY mv.[Timestamp] ASC) RowNum
FROM dbo.MeterValues mv
WHERE mv.MeterID = #MeterID
)
SELECT crt.MeterID
,crt.[Timestamp] AS CrtTimestamp
,prev.[Timestamp] AS PrevTimestamp
,crt.RawValue AS CrtRawValue
,prev.RawValue AS PrevRawValue
,(crt.RawValue - prev.RawValue)*1.00/DATEDIFF(SECOND, prev.[Timestamp], crt.[Timestamp]) Diff
FROM ValuesWithRowNumber crt --crt=current
JOIN ValuesWithRowNumber prev ON crt.RowNum - 1 = prev.RowNum
ORDER BY crt.[Timestamp];

Resources