I am trying to figure out how to make a sort of "consumption" query where an INT value column (X) is subtracted from another INT column (Y) until it reaches 0, then stop. The column DesiredResult and DesiredResultExplanation are here only for reference to the math being performed. This takes place in DESC date order (future consuming back to the present)
My initial approach was to use window functionality, but the problem is once the value (Y) reaches 0, it needs to stop performing a running total. Had similar issues using a CTE as well.
If changing the table structure will help at all, this can be done.
Version: SQL Server 2014 or higher
Thanks!
DECLARE #test TABLE
(
ID INT IDENTITY (1,1)
,PeriodDate DATE
,X INT
,Y INT
,DesiredResult INT
,DesiredResultExplanation VARCHAR(100)
)
INSERT INTO #test VALUES ('2017-05-01', 100,0, 100,'Nothing left to subtract. Value is unchanged')
INSERT INTO #test VALUES ('2017-05-08', 200,0, 200,'Nothing left to subtract. Value is unchanged')
INSERT INTO #test VALUES ('2017-05-15', 300,0, 100,'300 - 200 = 100 (Orig -1100 has been consumed)')
INSERT INTO #test VALUES ('2017-05-22', 400,0,-200,'400 - 600 = -200 ')
INSERT INTO #test VALUES ('2017-05-29', 500,-1100,-600, '500 - 1100 = -600')
SELECT *
FROM #test
ORDER BY PeriodDate DESC
DEMO
WITH cte as (
SELECT *,
SUM(X) OVER (ORDER BY PeriodDate DESC) accumulated
FROM #test
), parameter as (
SELECT 1100 as startY
)
SELECT *,
CASE WHEN accumulated <= startY
THEN accumulated - startY
WHEN LAG(accumulated) OVER (ORDER BY PeriodDate DESC) < startY
THEN accumulated - startY
ELSE X
END as newDesire
FROM cte
CROSS JOIN parameter
ORDER BY PeriodDate DESC;
OUTPUT
EDIT: You can change the LAG condition with
WHEN accumulated - X < startY
Related
so I'm working on a SQL CPU utilization script that gets the last (for ex, 10 mins of CPU usage), for a SQL instance as available from sys.dm_os_ring_buffers - pretty standard script.
however, what I want to do, is grab this info, but count the consecutive occurrences in the sample (ie 10 mins), so if for 10 mins (10 consecutive records where value > 90%) do X
here's the code i'm using: (EDITED FOR CORRECT CODE)
DECLARE #ts BIGINT;
DECLARE #lastNmin TINYINT;
SET #lastNmin = 10;
SELECT #ts =(SELECT cpu_ticks/(cpu_ticks/ms_ticks) FROM
sys.dm_os_sys_info);
SELECT TOP(#lastNmin)
SQLProcessUtilization AS [SQLServer_CPU_Utilization],
SystemIdle AS [System_Idle_Process],
100 - SystemIdle - SQLProcessUtilization AS
[Other_Process_CPU_Utilization],
DATEADD(ms,-1 *(#ts - [timestamp]),GETDATE())AS [Event_Time]
FROM (SELECT record.value('(./Record/#id)[1]','int')AS record_id,
record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)
[1]','int')AS [SystemIdle],record.value
('(./Record/SchedulerMonitorEvent/SystemHealth/ProcessUtilization)
[1]','int')AS [SQLProcessUtilization],
[timestamp]
FROM (SELECT[timestamp], convert(xml, record) AS [record]
FROM sys.dm_os_ring_buffers
WHERE ring_buffer_type =N'RING_BUFFER_SCHEDULER_MONITOR'AND record
LIKE'%%')AS x )AS y
ORDER BY record_id DESC;
Thanks
It sounds like you want a gaps and islands approach. Here's what I came up with:
DROP TABLE IF EXISTS #tmp;
DECLARE #ts BIGINT;
DECLARE #lastNmin TINYINT;
SET #lastNmin = 10;
SELECT #ts =
(
SELECT cpu_ticks / (cpu_ticks / ms_ticks) FROM sys.dm_os_sys_info
);
SELECT TOP (#lastNmin)
SQLProcessUtilization AS [SQLServer_CPU_Utilization],
SystemIdle AS [System_Idle_Process],
100 - SystemIdle - SQLProcessUtilization AS [Other_Process_CPU_Utilization],
DATEADD(ms, -1 * (#ts - [timestamp]), GETDATE()) AS [Event_Time]
INTO #tmp
FROM
(
SELECT record.value('(./Record/#id)[1]', 'int') AS record_id,
record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS [SystemIdle],
record.value('(./Record/SchedulerMonitorEvent/SystemHealth/ProcessUtilization)[1]', 'int') AS [SQLProcessUtilization],
[timestamp]
FROM
(
SELECT [timestamp],
CONVERT(XML, record) AS [record]
FROM sys.dm_os_ring_buffers
WHERE ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR'
AND record LIKE '%%'
) AS x
) AS y
ORDER BY record_id DESC;
WITH cte AS (
SELECT *, CAST(CASE WHEN [System_Idle_Process] >= 95 THEN 1 ELSE 0 END AS BIT) as [HighCPU]
FROM #tmp
),
GapsAndIslands AS (
SELECT *,
ROW_NUMBER() OVER (ORDER BY cte.Event_Time) AS rn1,
ROW_NUMBER() OVER (PARTITION BY cte.HighCPU ORDER BY cte.Event_Time) AS rn2
FROM cte
)
SELECT *, rn1 - rn2 AS GroupID
FROM GapsAndIslands
ORDER BY GapsAndIslands.Event_Time;
By way of explanation, I'm creating three synthetic columns
a boolean representing the condition you're looking to track (NB - I'm using a different metric than you should because my CPU usage is low!)
a row number column across the entire data set
a row number column for each distinct value of the tracked metric
What makes this solution work is noting that the difference in those two row number columns will be the same for consecutive rows that have the same value for your tracked metric and will change on the boundaries. I've left that as GroupID in the final result set and you can use that to track groups of consecutive rows.
If you instead replace that last select with this:
SELECT MIN(Event_Time), MAX(Event_Time)
FROM GapsAndIslands
WHERE [HighCPU] = 1
GROUP BY rn1 - rn2
ORDER BY MIN(Event_Time);
That will give you the time ranges for when the tracked metric was above threshold.
I'm attempting to create a recursive CTE statement that adds blank rows in between data points that will later for interpolation. I'm a beginner with SQL and this is my first time using CTE's and am having some difficulty finding the proper way to do this.
I've attempted a few different slight variations on the code I have provided below after some research but haven't grasped a good enough understanding to see my issue yet. The following code should simulate sparse sampling by taking a observation every 4 hours from the sample data set and the second portion should add rows with there respective x values every 0.1 of an hour which will later be filled with interpolated values derived from a cubic spline.
--Sample Data
create table #temperatures (hour integer, temperature double precision);
insert into #temperatures (hour, temperature) values
(0,18.5),
(1,16.9),
(2,15.3),
(3,14.1),
(4,13.8),
(5,14.7),
(6,14.7),
(7,13.5),
(8,12.2),
(9,11.4),
(10,10.9),
(11,10.5),
(12,12.3),
(13,16.4),
(14,22.3),
(15,27.2),
(16,31.1),
(17,34),
(18,35.6),
(19,33.1),
(20,25.1),
(21,21.3),
(22,22.3),
(23,20.3),
(24,18.4),
(25,16.8),
(26,15.6),
(27,15.4),
(28,14.7),
(29,14.1),
(30,14.2),
(31,14),
(32,13.9),
(33,13.9),
(34,13.6),
(35,13.1),
(36,15),
(37,18.2),
(38,21.8),
(39,24.1),
(40,25.7),
(41,29.9),
(42,28.9),
(43,31.7),
(44,29.4),
(45,30.7),
(46,29.9),
(47,27);
--1
WITH xy (x,y)
AS
(
SELECT TOP 12
CAST(hour AS double precision) AS x
,temperature AS y
FROM #temperatures
WHERE cast(hour as integer) % 4 = 0
)
Select x,y
INTO #xy
FROM xy
Select [x] As [x_input]
INTO #x_series
FROM #xy
--2
with recursive
, x_series(input_x) as (
select
min(x)
from
#xy
union all
select
input_x + 0.1
from
x_series
where
input_x + 0.1 < (select max(x) from x)
)
, x_coordinate as (
select
input_x
, max(x) over(order by input_x) as previous_x
from
x_series
left join
#xy on abs(x_series.input_x - xy.x) < 0.001
)
The first CTE works as expected and produces a list of 12 (a sample every 4 hours for two days) but the second produces syntax error. The expected out put would be something like
(4,13.8), (4.1,null/0), (4.2,null/0),....., (8,12.2)
I dont think you need recursive.
What about this:
SQL DEMO
SELECT DISTINCT n = number *1.0 /10 , #xy.x, #xy.y
FROM master..[spt_values] step
LEFT JOIN #xy
ON step.number*1.0 /10 = #xy.x
WHERE number BETWEEN 40 AND 480
This 480 is based on the two days you mention.
OUTPUT
You dont even need the temporal table
SELECT DISTINCT n = number *1.0 /10 , #temperatures.temperature
FROM master..[spt_values] step
LEFT JOIN #temperatures
ON step.number *1.0 / 10 = #temperatures.hour
AND #temperatures.hour % 4 = 0
WHERE number BETWEEN 40 AND 480;
I don't think you need a recursive CTE here. I think a solution like this would be a better approach. Modify accordingly.
DECLARE #max_value FLOAT =
(SELECT MAX(hour) FROM #temperatures) * 10
INSERT INTO #temperatures (hour, temperature)
SELECT X.N / 10, NULL
FROM (
select CAST(ROW_NUMBER() over(order by t1.number) AS FLOAT) AS N
from master..spt_values t1
cross join master..spt_values t2
) X
WHERE X.N <= #max_value
AND X.N NOT IN (SELECT hour FROM #temperatures)
Use the temp table #xy produced in --1 you have, the following will give you a x series:
;with x_series(input_x)
as
(
select min(x) AS input_x
from #xy
union all
select input_x + 0.1
from x_series
where input_x + 0.1 < (select max(x) from #xy)
)
SELECT * FROM x_series;
Can we repeat last value of a column in SSRS? As in attachment, all blank rows in
the last column should be filled with the latest value 702
I used Previous, Last functions but nothing helped
That's achievable if you do this:
Step 1. For your source, you build a sql query where you group the data by Year, AbsoluteMonth, etc.
So for each Year / AbsoluteMonth pair the report has only ONE value.
Step 2. Use below formula:
=IIf(IsNothing(Sum(Fields!Amt.Value)), Last(Fields!Amt.Value, "Year"), Sum(Fields!Amt.Value))
Here "Year" is group name, and Amt - your field name, which is probably R_Pax
Step3. (optional) Sort the data if it's not naturally sorted to provide the correct last value.
Step 1 is very important. Otherwise the cell with empty value will not show the last total, it will show the last value for a month, so if month (1) has values 30, 50, 60, and month (2) doesn't have any values, then it will show 60 for month(2), month(3), etc..., not sum(30+50+60).
You better insert the remaining blank records with last value into your dataset before pass the data to report.I assume your table is matrix.
DECLARE #Today DATETIME
SET #Today = GETDATE()
DECLARE #MatrixData TABLE (
Month1 INT
, Year1 INT
, Value INT
)
INSERT INTO #MatrixData (Month1, Year1, Value)
SELECT MONTH(DATEADD(MONTH, Id * -1, #Today)) AS Date1Month, YEAR(DATEADD(MONTH, Id * -1, #Today)) AS Date1Year, Id * 10 AS Value1
FROM (
SELECT TOP 60 ROW_NUMBER() OVER (ORDER BY Id) AS Id
FROM SysObjects
) A
ORDER BY Date1Year, Date1Month
SELECT * FROM #MatrixData
-- Insert blank month of last year with last value
INSERT INTO #MatrixData (Month1, Year1, Value)
SELECT A.RunningMonth, A1.MaxYear, A1.LastValue
FROM (
SELECT TOP 12 ROW_NUMBER() OVER (ORDER BY Id) AS RunningMonth
FROM SysObjects
) A
INNER JOIN (
-- Get Last Value in #MatrixData
SELECT A.MinMonth, A.MaxMonth, A.MaxYear, A1.Value AS LastValue
FROM (
-- Get Max Month Last Year in #MatrixData
SELECT MAX(A1.Month1) AS MinMonth, A.MaxMonth, A.MaxYear
FROM (
-- Get Max Month & Max Year
SELECT MAX(Month1) AS MaxMonth, MAX(Year1) AS MaxYear
FROM #MatrixData
) A
INNER JOIN #MatrixData A1 ON A.MaxYear = A1.Year1
GROUP BY A.MaxMonth, A.MaxYear
) A
INNER JOIN #MatrixData A1 ON A.MinMonth = A1.Month1 AND A.MaxYear = A1.Year1
) A1 ON A.RunningMonth > A1.MinMonth AND A.RunningMonth <= A1.MaxMonth
SELECT * FROM #MatrixData
We can do it at SQL end and fetch data to SSRS
Steps:
Do pivot if needed
Get the data at granularity column. Here it is Absolute Month
Then use the SQL method to replcae the Nulls/ last values which are empty with the last highest value
Ref:
`select a.AbsoluteMonth,Mon
,first_value(a.S1_pax)over(partition by a.v1_p order by num ) as S_Pax
,first_value(a.S2_pax)over(partition by a.v2_p order by num ) as S2_Pax`
from
(select *
,sum(case when S1_pax is null then 0 else 1 end) over (order by num) as v1_p
,sum(case when S2_pax is null then 0 else 1 end) over (order by num) as v2_p
from X_Table
)a
And fill all places respectively. Plz refer below output
In Oracle it is done like this. SQL Server has both COALESCE and LAG functions. So this must be possible with SQL Server also. There is also another stackoverflow question similar to this. Just could not locate it.
create table mytab(n number, m number);
insert into mytab values(1,null);
insert into mytab values(2,null);
insert into mytab values(3,44949);
insert into mytab values(4,null);
insert into mytab values(5,null);
insert into mytab values(6,null);
insert into mytab values(7,null);
insert into mytab values(8,null);
insert into mytab values(9,null);
insert into mytab values(10,null);
insert into mytab values(11,74631);
insert into mytab values(12,null);
insert into mytab values(13,null);
select t.*, coalesce(m, lag(m ignore nulls) over (order by n))
from mytab t;
How do I sum the time? Here is the SQL I have a problem with:
drop table #temp
Create TABLE #Temp (EmpID varchar(50),Inout varchar(50),Punchdate datetime2(0),rowid int, INTotal datetime ,Outtotal datetime)
declare #ttt int
--truncate table #Temp
--drop table #temp
;WITH timediff AS
( select ID, In_out,Punch_Date , ROW_NUMBER() OVER ( ORDER BY Punch_date) AS [row]
-- Create an index number ordered by time.
from tblCAPdata tbl
where ID='00007971' and In_Out!='Null Mode' and CONVERT(date,Punch_Date)=CONVERT(date,'2015-12-30 00:00:00')
)
insert Into #Temp
select *,
convert(varchar(8),dateadd(mi, ISNULL(DATEDIFF(MINUTE,
(SELECT other.Punch_Date
FROM timediff Other
WHERE other.[row] = timediff.[row]-1 and In_Out='In' and In_Out!='Out' ),
timediff.Punch_Date),0),0),108)
AS INTimedifferance,
-- convert(varchar(8),dateadd(mi,datediff(Minute, day_start, day_end),0),108)
CONVERT(varchar(8),dateadd(mi, ISNULL(DATEDIFF(MINUTE,
(SELECT other.Punch_Date
FROM timediff Other
WHERE other.[row] = timediff.[row]-1 and In_Out='Out' and In_Out!='In' ),
timediff.Punch_Date),0),0),108) AS OUTTimedifferance
FROM timediff
where NOT EXISTS (
SELECT *
FROM timediff omit
WHERE omit.In_Out = timediff.In_Out
AND omit.[row] = timediff.[row] - 1
);
select sum(INTotal) as v,sum(OuttotAL) from #Temp
The error message is:
Msg 8117, Level 16, State 1, Line 39
Operand data type datetime is invalid for sum operator.
PFA.
I need to take sum of In time and Out time which is in Date time type
This approach links the In and Out times to a single row for each employee IN punch, calculates the number of hours, then lets the last expression perform your desired calculation.
WITH TimesWithIds AS (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY ID, In_Out ORDER BY Punch_Date) AS RowNum
FROM [Table] T1
), InAndOuts (
SELECT
InTimes.ID,
InTimes.Punch_Date AS PunchIn,
OutTimes.Punch_Date AS PuchOut,
CONVERT(DECIMAL(12,4), DATEDIFF(SS, InTimes.Punch_Date, OutTimes.PunchDate))/3600 AS PunchHours -- Be sure this is large enough for you
FROM TimesWithIds InTimes
INNER JOIN TimesWithIds OutTimes
ON InTimes.RowNum = OutTimes.RowNum
AND InTimes.ID = OutTimes.ID
WHERE InTimes.In_Out = 'In'
AND OutTimes.In_Out = 'Out'
)
SELECT
ID,
SUM(PunchHours) AS PunchHours
FROM InAndOuts
GROUP BY
ID
The example above calculates a decimal form of total hours by each employee. Add any filters and use your desired data format. Keep in mind you can't store the total hours as a true date/time - has to be some numeric type since after 24 hours, a TIME datatype would just reset.
You need to convert to seconds (using DATEDIFF) or some other time unit, then sum. Then you can convert back to datetime if you need to.
In my SQL Server database, I have a table like this :
counter, value
12345, 10.1
12370, 10.5
12390, 9.7
12405, 10.1
12510, 12.3
Let's assume that I input a value of 5. I need to fill in the data between the first record and second record by increment of 5 in the counter column.
For example using Record 1 and Record 2, here are the additional data needs to be inserted into the table.
12345, 10.1 --> Record 1
12350, 10.1
12355, 10.1
12360, 10.1
12365, 10.1
12370, 10.5 --> Record 2
Other than using a database cursor to loop through each record in the table and then select the MIN counter after Record 1, is there any other way that I can achieve it with less I/O overhead ? I just need to insert additional counter between the range based on the input parameter.
Thanks for your input.
If you're wanting to compute a weighted average, there's no need to create these rows. You can just work out how many rows you would have added and use that information to calculate the average. E.g.:
declare #t table (counter int not null, value decimal(19,4) not null)
insert into #t(counter, value) values
(12345, 10.1),
(12370, 10.5),
(12390, 9.7 ),
(12405, 10.1),
(12510, 12.3)
declare #gap int
set #gap = 5
;With Numbered as (
select counter,value,ROW_NUMBER() OVER (ORDER BY counter) as rn
from #t
), Paired as (
select n1.counter,n1.value,
(n2.counter - n1.counter)/#gap as Cnt --What do we do for the last row?
from Numbered n1
left join
Numbered n2
on
n1.rn = n2.rn - 1
)
select SUM(value*COALESCE(Cnt,1))/SUM(COALESCE(Cnt,1)) from Paired
Where as you can (hopefully) see, I've currently decided that the last row counts as just 1, but anything else could be done there also.
Filling gaps with values is usually a problem best answered using a Numbers table (a table with a single int column containing numbers from 1 to some sufficiently large number):
declare #n1 int = 12345, #n2 int = 12370, #step int = 5
select #n1 + (n * #step)
from numbers
where n < (#n2 - #n1) / #step
a recursion should work as well:
;WITH
Initial AS (SELECT COUNTER,value FROM yourtable),
maxvalue AS (SELECT MAX(COUNTER) Mvalue FROM Initial),
recur AS (
SELECT COUNTER, value FROM yourtable
UNION ALL
SELECT counter+5,value FROM recur r WHERE COUNTER+5< (SELECT Mvalue FROM maxvalue)
AND NOT EXISTS (SELECT 1 FROM Initial o WHERE o.COUNTER=r.COUNTER+5)
)
SELECT * FROM recur ORDER BY COUNTER
just replace 'yourtable' with the name of your table