Related
I have a business need to project when a specific task needs to be done based on the usage of a task.
For example, you need to change the oil in your car every 3000 miles. Some days you drive 300 miles, and other days you drive 500 miles. When you hit 3000, you change the oil, and restart the counter. Based on a projected usage table, return a set of all the oil change dates.
I could do this in a table-valued function or some other 'coded' solution.
But I thought I could do it in one statement, a recursive cte perhaps.
I'm having difficulties 'joining' the next date into the WHERE of the recursive part.
And SQL doesn't like 'TOP 1' in a recursive CTE at all. :)
I would like a set like this:
This is what I've got:
WITH cte_MilesMX (RateDate,RunningRateMiles)
AS
(
-- Initial query
SELECT TOP 1 *
FROM (
SELECT
RateDate,
SUM(RateMiles) OVER (ORDER BY RateDate) AS RunningRateMiles
FROM dbo.RatesbyDay
WHERE RateDate > '2020-01-01') q1
WHERE q1.RunningRateMiles >= 3000
UNION ALL
-- Recursive part
SELECT TOP 1 *
FROM (
SELECT
rbd.RateDate,
SUM(RateMiles) OVER (ORDER BY rbd.RateDate) AS RunningRateMiles
FROM dbo.RatesbyDay rbd
JOIN cte_MilesMX cte
ON 1 = 1
WHERE rbd.RateDate > cte.RateDate) q1
WHERE q1.RunningRateMiles >= 3000
)
SELECT *
FROM cte_MilesMX
If you want to fool with this, here is the example:
Any help would be greatly appreciated.
Thanks.
CREATE TABLE RatesbyDay(
RateDate DATE,
RateMiles INT);
INSERT INTO RatesbyDay VALUES ('2020-01-01',600)
INSERT INTO RatesbyDay VALUES ('2020-01-02',450)
INSERT INTO RatesbyDay VALUES ('2020-01-03',370)
INSERT INTO RatesbyDay VALUES ('2020-01-04',700)
INSERT INTO RatesbyDay VALUES ('2020-01-05',100)
INSERT INTO RatesbyDay VALUES ('2020-01-06',480)
INSERT INTO RatesbyDay VALUES ('2020-01-07',430)
INSERT INTO RatesbyDay VALUES ('2020-01-08',200)
INSERT INTO RatesbyDay VALUES ('2020-01-09',590)
INSERT INTO RatesbyDay VALUES ('2020-01-10',380)
INSERT INTO RatesbyDay VALUES ('2020-01-11',220)
INSERT INTO RatesbyDay VALUES ('2020-01-12',320)
INSERT INTO RatesbyDay VALUES ('2020-01-13',360)
INSERT INTO RatesbyDay VALUES ('2020-01-14',600)
INSERT INTO RatesbyDay VALUES ('2020-01-15',450)
INSERT INTO RatesbyDay VALUES ('2020-01-16',475)
INSERT INTO RatesbyDay VALUES ('2020-01-17',300)
INSERT INTO RatesbyDay VALUES ('2020-01-18',190)
INSERT INTO RatesbyDay VALUES ('2020-01-19',435)
INSERT INTO RatesbyDay VALUES ('2020-01-20',285)
INSERT INTO RatesbyDay VALUES ('2020-01-21',350)
INSERT INTO RatesbyDay VALUES ('2020-01-22',410)
INSERT INTO RatesbyDay VALUES ('2020-01-23',250)
INSERT INTO RatesbyDay VALUES ('2020-01-24',300)
INSERT INTO RatesbyDay VALUES ('2020-01-25',250)
INSERT INTO RatesbyDay VALUES ('2020-01-26',650)
INSERT INTO RatesbyDay VALUES ('2020-01-27',180)
INSERT INTO RatesbyDay VALUES ('2020-01-28',280)
INSERT INTO RatesbyDay VALUES ('2020-01-29',200)
INSERT INTO RatesbyDay VALUES ('2020-01-30',100)
INSERT INTO RatesbyDay VALUES ('2020-01-31',100)
-- this returns the 1st oil change assuming we just changed it on 1-1-2020
SELECT TOP 1 *
FROM (
SELECT
RateDate,
SUM(RateMiles) OVER (ORDER BY RateDate) AS RunningRateMiles
FROM dbo.RatesbyDay
WHERE RateDate > '2020-01-01') q1
WHERE q1.RunningRateMiles >= 3000
-- the above query returned 1-9-2020 as the oil change, so when is the next one.
SELECT TOP 1 *
FROM (
SELECT
RateDate,
SUM(RateMiles) OVER (ORDER BY RateDate) AS RunningRateMiles
FROM dbo.RatesbyDay
WHERE RateDate > '2020-01-09') q1
WHERE q1.RunningRateMiles >= 3000
-- etc. etc.
SELECT TOP 1 *
FROM (
SELECT
RateDate,
SUM(RateMiles) OVER (ORDER BY RateDate) AS RunningRateMiles
FROM dbo.RatesbyDay
WHERE RateDate > '2020-01-17') q1
WHERE q1.RunningRateMiles >= 3000
SELECT TOP 1 *
FROM (
SELECT
RateDate,
SUM(RateMiles) OVER (ORDER BY RateDate) AS RunningRateMiles
FROM dbo.RatesbyDay
WHERE RateDate > '2020-01-26') q1
WHERE q1.RunningRateMiles >= 3000
This isn't a recursive CTE but it does do what you're what you're trying to do. The technique goes by a couple different names... Usually either "Quirky Update" or "Ordered Update".
First thing, notice that I added two new columns to your table and a clustered index. They are in fact necessary but if are unwilling or unable to modify the existing table, this works just as well with a #TempTable.
For more detailed information, see Solving the Running Total and Ordinal Rank Problems (Rewritten)
Also... fair warning, this technique isn't without it's detractors due to the fact that Microsoft doesn't guarantee that it will work as expected.
USE tempdb;
GO
IF OBJECT_ID('tempdb.dbo.RatesByDay', 'U') IS NOT NULL
BEGIN DROP TABLE tempdb.dbo.RatesByDay; END;
GO
CREATE TABLE tempdb.dbo.RatesByDay (
RateDate date NOT NULL
CONSTRAINT pk_RatesByDay PRIMARY KEY CLUSTERED (RateDate), -- clustered index is needed to control the direction of the update.
RateMiles int NOT NULL,
IsChangeDay bit NULL,
MilesSinceLastChange int NULL
);
GO
INSERT tempdb.dbo.RatesByDay (RateDate, RateMiles) VALUES
('2020-01-01',600),('2020-01-02',450),('2020-01-03',370),('2020-01-04',700),('2020-01-05',100),('2020-01-06',480),
('2020-01-07',430),('2020-01-08',200),('2020-01-09',590),('2020-01-10',380),('2020-01-11',220),('2020-01-12',320),
('2020-01-13',360),('2020-01-14',600),('2020-01-15',450),('2020-01-16',475),('2020-01-17',300),('2020-01-18',190),
('2020-01-19',435),('2020-01-20',285),('2020-01-21',350),('2020-01-22',410),('2020-01-23',250),('2020-01-24',300),
('2020-01-25',250),('2020-01-26',650),('2020-01-27',180),('2020-01-28',280),('2020-01-29',200),('2020-01-30',100),
('2020-01-31',100);
--=====================================================================================================================
DECLARE
#RunningMiles int = 0,
#Anchor date;
UPDATE rbd SET
#RunningMiles = rbd.MilesSinceLastChange = CASE WHEN #RunningMiles < 3000 THEN #RunningMiles ELSE 0 END + rbd.RateMiles,
rbd.IsChangeDay = CASE WHEN #RunningMiles < 3000 THEN 0 ELSE 1 END,
#Anchor = rbd.RateDate
FROM
dbo.RatesByDay rbd WITH (TABLOCKX, INDEX (1))
WHERE 1 = 1
AND rbd.RateDate > '2020-01-01'
OPTION (MAXDOP 1);
-------------------------------------
SELECT * FROM dbo.RatesByDay rbd;
And the results...
RateDate RateMiles IsChangeDay MilesSinceLastChange
---------- ----------- ----------- --------------------
2020-01-01 600 NULL NULL
2020-01-02 450 0 450
2020-01-03 370 0 820
2020-01-04 700 0 1520
2020-01-05 100 0 1620
2020-01-06 480 0 2100
2020-01-07 430 0 2530
2020-01-08 200 0 2730
2020-01-09 590 1 3320
2020-01-10 380 0 380
2020-01-11 220 0 600
2020-01-12 320 0 920
2020-01-13 360 0 1280
2020-01-14 600 0 1880
2020-01-15 450 0 2330
2020-01-16 475 0 2805
2020-01-17 300 1 3105
2020-01-18 190 0 190
2020-01-19 435 0 625
2020-01-20 285 0 910
2020-01-21 350 0 1260
2020-01-22 410 0 1670
2020-01-23 250 0 1920
2020-01-24 300 0 2220
2020-01-25 250 0 2470
2020-01-26 650 1 3120
2020-01-27 180 0 180
2020-01-28 280 0 460
2020-01-29 200 0 660
2020-01-30 100 0 760
2020-01-31 100 0 860
You can do this with a recursive query:
with
data as (select r.*, row_number() over(order by ratedate) rn from ratesbyday r),
cte as (
select d.*, ratemiles total, ratemiles newtotal from data d where rn = 1
union all
select d.*,
c.newtotal + d.ratemiles,
case when c.newtotal < 3000 and c.newtotal + d.ratemiles >= 3000 then 0 else c.newtotal + d.ratemiles end
from cte c
inner join data d on d.rn = c.rn + 1
)
select ratedate, ratemiles, total
from cte
where newtotal = 0
order by ratedate
The query starts by enumerating the rows. Then, it iteratively walks them, starting from the "first" one; everytime we exceed the 3000 miles threshold, we reset the running miles count. We can then filter on "reset" rows.
Demo on DB Fiddle:
ratedate | ratemiles | total
:--------- | --------: | ----:
2020-01-07 | 430 | 3130
2020-01-15 | 450 | 3120
2020-01-25 | 250 | 3245
If there may be more than 100 rows in your dataset, you need to add option (maxrecursion 0) at the very end of the query.
In this instance I would use a rolling agg and then use the mod operator to find the points where it hits the 3000 interval.
Using the table desc and inserts above here is an example:
-- When the mod value "resets" then the oil change is due, check this using LAG
SELECT
agg.RateDate
,agg.RateMiles
,agg.MilesAgg
,agg.MilesAgg%3000 AS ModValue
,CASE WHEN agg.MilesAgg%3000 < LAG(agg.MilesAgg) OVER(ORDER BY agg.RateDate)%3000
THEN 'Due'
ELSE 'NotDue'
END
FROM
(
--Get the rolling total of miles
SELECT
rbd.RateDate
,rbd.RateMiles
,SUM(rbd.RateMiles) OVER(ORDER BY rbd.RateDate ROWS UNBOUNDED PRECEDING) AS MilesAgg
FROM #RatesByDay rbd
) agg
Results, first day is counting the 600 miles as being AFTER the oil change
RateDate Mi MiAgg Mod IsDue?
--------------------------------------
2020-01-01 600 600 600 NotDue
2020-01-02 450 1050 1050 NotDue
2020-01-03 370 1420 1420 NotDue
2020-01-04 700 2120 2120 NotDue
2020-01-05 100 2220 2220 NotDue
2020-01-06 480 2700 2700 NotDue
2020-01-07 430 3130 130 Due
2020-01-08 200 3330 330 NotDue
2020-01-09 590 3920 920 NotDue
2020-01-10 380 4300 1300 NotDue
2020-01-11 220 4520 1520 NotDue
2020-01-12 320 4840 1840 NotDue
2020-01-13 360 5200 2200 NotDue
2020-01-14 600 5800 2800 NotDue
2020-01-15 450 6250 250 Due
2020-01-16 475 6725 725 NotDue
2020-01-17 300 7025 1025 NotDue
2020-01-18 190 7215 1215 NotDue
2020-01-19 435 7650 1650 NotDue
2020-01-20 285 7935 1935 NotDue
2020-01-21 350 8285 2285 NotDue
2020-01-22 410 8695 2695 NotDue
2020-01-23 250 8945 2945 NotDue
2020-01-24 300 9245 245 Due
2020-01-25 250 9495 495 NotDue
2020-01-26 650 10145 1145 NotDue
2020-01-27 180 10325 1325 NotDue
2020-01-28 280 10605 1605 NotDue
2020-01-29 200 10805 1805 NotDue
2020-01-30 100 10905 1905 NotDue
2020-01-31 100 11005 2005 NotDue
Sample Data in Yellow the desired output
Just need your expert help on this.
I need to get the output based on T-SQL. Whenever the invoice_line_id_link is 0 put the product_id value if not look into the id column and get the product_id of that row.
Here's the script.
declare #t table
(id int, invoice_id int, product_id int, invoice_line_id_link int);
insert into #t values
(53,10,383,0),
(54,10,344,53),
(55,10,920,53),
(57,10,384,0),
(58,10,359,57),
(59,10,242,57),
(60,10,284,0);
select id, invoice_id, product_id,invoice_line_id_link, null desiredoutput from #t
based on the image if possible to populate using TSQL
Based on the image you have provided , here is the piece of code producing desired output.
Table Populate
declare #t table (id int, invoice_id int, product_id int, invoice_line_id_link int);
insert into #t values (53,10,383,0), (54,10,344,53), (55,10,920,53), (57,10,384,0), (58,10,359,57), (59,10,242,57), (60,10,284,0);
Code for Output
select *,CASE WHEN invoice_line_id_link = 0 THEN product_id
ELSE (select t1.product_id from #t t1 where t1.id = t2.invoice_line_id_link)
END from #t t2
Output
id invoice_id product_id invoice_line_id_link desiredoutput
53 10 383 0 383
54 10 344 53 383
55 10 920 53 383
57 10 384 0 384
58 10 359 57 384
59 10 242 57 384
60 10 284 0 284
*/
I have the result of my query in a temp table which looks something like shown below :
CREATE TABLE #temptable
(
productid INT,
date DATE,
Indicator varchar(max),
VendorCode INT,
morning INT,
noon INT,
evening INT
)
insert into #temptable values (101,'8-5-2016', 'High', 202, 0,1,0)
insert into #temptable values (101,'8-6-2016', 'High', 202, 0,0,1)
insert into #temptable values (101,'8-5-2016', 'Low', 202, 0,0,1)
insert into #temptable values (101,'8-6-2016', 'Low', 202, 0,0,1)
insert into #temptable values (101,'8-5-2016', 'Avg', 202, 1,0,1)
insert into #temptable values (101,'8-6-2016', 'Avg', 202, 0,0,1)
select * from #temptable
I need the output to look something like this :
I looked at using pivots but looks like that works only with aggregates ? Is there an easy way to do this ?
You can get the result you want by first applying the unpivot operator, and then pivot the result:
select
productid, VendorCode, date, time, Low, High, Avg
from (
select productid, VendorCode, date, time, Indicator, val
from #temptable
unpivot (val for time in ([morning],[noon],[evening])) u
) t
pivot (max(val) for indicator in ([Low],[High],[Avg])) p
order by
productid, VendorCode, date,
case time
when 'Morning' then 1
when 'Noon' then 2
when 'Evening' then 3
end
The case expression at the end of the order by clause makes sure the result is ordered correctly (Morning, Noon, Evening).
First do UNPIVOT then PIVOT
SELECT
piv.productid ,
piv.date ,
piv.VendorCode ,
piv.Tmp AS [Time],
piv.Low ,
piv.High ,
piv.Avg
from
(
select *
from #temptable
unpivot
(
[Time]
for [Tmp] in (morning, noon, evening)
) u
) src
pivot
(
min([Time])
for Indicator in ([Low], [High], [Avg])
) piv
Result:
productid date VendorCode Time Low High Avg
101 2016-08-05 202 evening 1 0 1
101 2016-08-05 202 morning 0 0 1
101 2016-08-05 202 noon 0 1 0
101 2016-08-06 202 evening 1 1 1
101 2016-08-06 202 morning 0 0 0
101 2016-08-06 202 noon 0 0 0
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];
SQL Server 2000
My Table:
CARDNO CARDEVENTDATE CARDEVENTTIME
121 20090610 025050
121 20090611 040000
121 20090611 050000
121 20090611 020000
122 20090611 030001
122 20090611 030000
123 20090611 080000
123 20090611 100000
123 20090611 132449
123 20090611 025959
124 20090610 030000
124 20090612 030001
125 20090611 030002
125 20090612 040000
Cardno is Separate Table
Cardeventdate, cardeventtime is separate table
From the above table I want to get Top Time and Bottom Time for the Particular cardeventdate and Cardno
For the 121, 20090611, Top Time is 040000, Bottom Time is 020000
For 123, 20090611, Top Time is 080000, Bottom Time is 025959 …
Like this I need.
I used Min (time) and Max (time), But it showing like this.
For CardNo – 121
Cardeventdate – 20090611
Min Time – 020000
Max Time – 040000
I don’t want to get min and Max, I need only top and Bottom (or) First and Last time value of the particular Date and Cardno.
I used this Query
SELECT RowNumber = IDENTITY (int, 1, 1), CARDNO, CARDEVENTDATE, CARDEVENTTIME INTO #Table1 FROM T_CARDEVENT SELECT t1.CARDNO, t1.CARDEVENTDATE, t1.CARDEVENTTIME FROM #Table t1 INNER JOIN (SELECT RowNumber = MIN(RowNumber), CARDEVENTDATE, CARDNO FROM #Table1 t WHERE (cardeventdate > 20090601) GROUP BY cardno, cardeventdate UNION ALL SELECT MAX(RowNumber), CARDEVENTDATE, CARDNO FROM #Table1 t WHERE (cardeventdate > 20090601) GROUP BY cardno, cardeventdate) t2 ON t2.rownumber = t1.rownumber
Output:
ROWNUMBER CARDNO CARDEVENTDATE CARDEVENTTIME
335 0121 20090611 040000
1099 0121 20090611 050000
1100 0121 20090611 025050
336 0121 20090612 020000
337 0122 20090611 030001
338 0122 20090612 030000
339 0123 20090611 080000
1101 0123 20090611 100000
1102 0123 20090611 132449
340 0123 20090612 025959
341 0124 20090611 030000
342 0124 20090612 030001
343 0125 20090611 030002
344 0125 20090612 040000
So Here Row Number is created for all columns, from that how I have to take First Time and Last Time for the Particular Date.
Expecting Output
CARDNO CARDEVENTDATE CARDEVENTTIME Expecting
0121 20090611 040000 Top Value
0121 20090611 020000 No Need
0121 20090611 025050 Bottom Value
……… so on
Need Query Help.
Well, unless you have some additional fields to establish an order, this is non deterministic. Given the three values for the 23-04-2009 - how is it that APPLE is the first and ROSE is the last? If the ID and the DATE are the same for all three entries, there's no order defined to filter out "GRAPHE"......
Marc
UPDATE: I expanded on Lieven's idea a bit and got this working in my setup:
DECLARE #TempTable TABLE (RowNumber INT IDENTITY(1,1),
DayNumber INT,
ID VARCHAR(3), DateField DATETIME, Value VARCHAR(32))
INSERT INTO #TempTable(DayNumber, id, datefield, value)
SELECT DATEPART(DAYOFYEAR, DateField), ID, DateField, Value
FROM #Table
SELECT *
FROM #TempTable t
INNER JOIN
(SELECT RowNumber = MIN(RowNumber), DayNumber, ID
FROM #TempTable t
GROUP BY DayNumber, t.ID
UNION ALL
SELECT MAX(RowNumber), DayNumber, ID
FROM #TempTable t
GROUP BY DayNumber, t.ID) t2
ON t2.RowNumber = t.RowNumber
GO
I'm basically creating a temp table with additional info - an artificial "RowNumber" to create some order, and the "DayNumber" to get dates grouped by date only (without time).
Seems to work ok for me - does it work for you, too?
Jash, if you execute this script, does it give you the results you'd expect?
CREATE TABLE #T_Cardevent (CARDNO VARCHAR(3), CARDEVENTDATE VARCHAR(8), CARDEVENTTIME VARCHAR(8))
INSERT INTO #T_Cardevent VALUES ('121', '20090610', '025050')
INSERT INTO #T_Cardevent VALUES ('121', '20090611', '040000')
INSERT INTO #T_Cardevent VALUES ('121', '20090611', '050000')
INSERT INTO #T_Cardevent VALUES ('121', '20090611', '020000')
INSERT INTO #T_Cardevent VALUES ('122', '20090611', '030001')
INSERT INTO #T_Cardevent VALUES ('122', '20090611', '030000')
INSERT INTO #T_Cardevent VALUES ('123', '20090611', '080000')
INSERT INTO #T_Cardevent VALUES ('123', '20090611', '100000')
INSERT INTO #T_Cardevent VALUES ('123', '20090611', '132449')
INSERT INTO #T_Cardevent VALUES ('123', '20090611', '025959')
INSERT INTO #T_Cardevent VALUES ('124', '20090610', '030000')
INSERT INTO #T_Cardevent VALUES ('124', '20090612', '030001')
INSERT INTO #T_Cardevent VALUES ('125', '20090611', '030002')
INSERT INTO #T_Cardevent VALUES ('125', '20090612', '040000')
SELECT
RowNumber = IDENTITY (int, 1, 1)
, CARDNO
, CARDEVENTDATE
, CARDEVENTTIME
INTO #Table
FROM #T_CARDEVENT
SELECT t1.CARDNO, t1.CARDEVENTDATE, t1.CARDEVENTTIME
FROM #Table t1
INNER JOIN (
SELECT RowNumber = MIN(RowNumber), CARDEVENTDATE, CARDNO
FROM #Table t
GROUP BY cardno, cardeventdate
UNION ALL SELECT MAX(RowNumber), CARDEVENTDATE, CARDNO
FROM #Table t
GROUP BY cardno, cardeventdate) t2 ON t2.rownumber = t1.rownumber
ORDER BY 1, 2, 3
DROP TABLE #Table
DROP TABLE #T_Cardevent
I havn't tried it, but maybe something like this might do it:
SELECT TOP(1) FROM Table
WHERE Date='Some-date'
AND Id=Some-Id
ORDER BY Date ASC
UNION
SELECT TOP(1) FROM Table
WHERE Date='Some-date'
AND Id=Some-Id
ORDER BY Date Desc
to cheat you can add an autonumber feild
AUtoID ID DATE VALUE
1 001 23:04:2009 APPLE
2 001 23:04:2009 GRAPHE
3 001 23:04:2009 ROSE
4 001 24:04:2009 BERRY
5 001 24:04:2009 TIFFANY
6 001 24:04:2009 ORGANE
7 001 24:04:2009 SILVER
You can then do min and max against it
I usually use insert into a tempory table which is defined with the ID, technically the insertion order isn't garenteed going in.
Here's the solution using inline views:
with
tempFirst as (
select id, date, value,
row_number() over (partition by id, date order by date asc) as rownum1
from table1
),
tempLast as (
select *,
row_number() over (partition by id, date order by rownum1 desc) as rownum2
from tempFirst
)
select id, date, value from tempFirst where rownum1 = 1
union
select id, date, value from tempLast where rownum2 = 1
I have tested the output, which is:
ID DATE VALUE
001 2009-04-23 APPLE
001 2009-04-23 ROSE
001 2009-04-24 BERRY
001 2009-04-24 SILVER