SQL Server determine if values are monotonous - sql

I got a table with some measurements, containing basically records. And now, I need to determine, if the values are monotonically increasing from time to time, decreasing or none of the above.
I achieved the desired result using CTE expression (the code is below), but the solution seems quite reduntant to me.
Is there a better way to determine, if the field value sequence is monotone, or not?
CREATE TABLE [dbo].[Measurements](
[ObjectID] [int] IDENTITY(1,1) NOT NULL,
[measDate] [datetime] NULL,
[measValue] [float] NULL
) ON [PRIMARY];
DECLARE
#ObjectID INT = 1;
with measSet as (
select row_number() over(order by measDate ) rownum, measValue, measDate
from dbo.Measurements M
where M.measDate > convert( datetime, '2013-10-02 08:13:00', 120)
and M.ObjectID = #ObjectID
)
select case when count(b.DiffSign) = 1 then 1 else 0 end as IsMonotone
from (
select DiffSign from
(
select MSS.measDate , MSS.measValue, MSS.measValue- MSSD.measValue as Diff,
case
when MSS.measValue- MSSD.measValue is null then NULL
when MSS.measValue- MSSD.measValue= 0 then NULL
when MSS.measValue- MSSD.measValue< 0
then -1
else 1
end as DiffSign
from measSet MSS
left join measSet MSSD
on MSSD .rownum = MSS.rownum - 1
) a
where a.DiffSign is not null
group by a.DiffSign
) b

If you don't care about knowing what particular records are breaking the monotony, then you could use something like this, which is a little more compact:
SELECT CASE WHEN COUNT(*) = 0 THEN 1 ELSE 0 END AS IsMonotone
FROM (
SELECT ROW_NUMBER() OVER (ORDER BY measDate) AS RowNum, measValue
FROM Measurements
) T1 INNER JOIN (
SELECT ROW_NUMBER() OVER (ORDER BY measValue) AS RowNum, measValue
FROM Measurements
) T2 ON T1.RowNum = T2.RowNum
WHERE T1.measValue <> T2.measValue

Related

Multilevel CTE Expression slows the execution (Reposted with changes) [duplicate]

This question already exists:
Multilevel CTE Expression slows the execution [closed]
Closed 1 year ago.
The community reviewed whether to reopen this question 1 year ago and left it closed:
Duplicate This question has been answered, is not unique, and doesn’t differentiate itself from another question.
In SQL Server, I have a table-valued function which contains a multi-level CTE as shown in the code here:
CREATE FUNCTION [dbo].[GetDataset_Test]
(#XMLBlock XML, #Id INT)
RETURNS
#tempTable TABLE
(
methodID INT,
[Id] INT,
SavingMessage varchar(50)
)
AS
BEGIN
DECLARE #ObjectID INT = 2;
DECLARE #ExchangeRate INT = 2;
DECLARE #Cond INT = 1;
DECLARE #Param1 varchar(50) = 'name1';
DECLARE #Param2 varchar(50) = 'name2';
WITH CTE AS
(
SELECT
Col1,
con,
DiscountLine,
tempNumber,
ItemCostExVAT,
Quantity,
SomeValue,
methodID,
VATAmount,
SubTypeID,
Line,
VATMultiplier,
ROUND(dbo.GetAmountCustomFunction(COALESCE(Id, AnotherId), COALESCE(Price, PriceValue)), 2) As [Amount]
FROM
dbo.GetObject(#Id, #ObjectID, #ParameterBlock) AS BC
INNER JOIN
dbo.fnPrices(#ID, #CurrencyID) BPD ON BPD.Id = BC.productid
),
CTE1 AS
(
SELECT
*,
CASE WHEN con = 0 THEN Quantity ELSE 1 END AS Quantity
FROM
CTE
WHERE
1 = SomeValue
),
CTE2 AS
(
Select *,
MIN(CASE WHEN DiscountLine=1 THEN 1 ELSE 20 END) over (PARTITION by tempNumber) As StockControlled,
SUM(ItemCostExVAT * Quantity) OVER ( PARTITION BY tempNumber ) AS tempCost ,
ROUND(CASE
WHEN methodID = 8 THEN DiscValue
WHEN methodID = 2 THEN END ,
DicsValue,VATAmount),2) AS AmountExVAT
From CTE1
),
CTE3
(
SELECT
*,
ROUND(CASE
WHEN SubTypeID = 1 AND Line = 1 THEN -1 * AmountExVAT
ELSE 20
END, 2) PriceExVAT
FROM
CTE2
),
CTE4
(
SELECT
*,
ROUND(#ExchangeRate * CASE WHEN #Cond = 1 THEN AmountExVAT * VATMultiplier ELSE 20 END, 2) CashBack
FROM
CTE3
),
CTE5
(
SELECT
*,
dbo.FormatMessage(#Param1, #Param2) AS SavingMessage
FROM
CTE4
)
INSERT INTO #tempTable
SELECT
methodID, [Id], SavingMessage
FROM
CTE5
RETURN
END
In above query because of multi-level CTE and table value parameter I can think that its trying to query recursively and taking more execution time.
I know that we cannot use temporary table as function parameter, is there any alternative of this or can I use temporary table by any way in function?
Or can I make some changes in CTE to improve my T-SQL function query execution time?

Incremental Group BY

How I can achieve incremental grouping in query ?
I need to group by all the non-zero values into different named groups.
Please help me write a query based on columns date and subscribers.
If you have SQL Server 2012 or newer, you can use few tricks with windows functions to get this kind of grouping without cursors, with something like this:
select
Date, Subscribers,
case when Subscribers = 0 then 'No group'
else 'Group' + convert(varchar, GRP) end as GRP
from (
select
Date, Subscribers,
sum (GRP) over (order by Date asc) as GRP
from (
select
*,
case when Subscribers > 0 and
isnull(lag(Subscribers) over (order by Date asc),0) = 0 then 1 else 0 end as GRP
from SubscribersCountByDay S
) X
) Y
Example in SQL Fiddle
In general I advocate AGAINST cursors but in this case it ill not hurt since it ill iterate, sum up and do the conditional all in one pass.
Also note I hinted it with FAST_FORWARD to not degrade performance.
I'm guessing you do want what #HABO commented.
See the working example below, it just sums up until find a ZERO, reset and starts again. Note the and #Sum > 0 handles the case where the first row is ZERO.
create table dbo.SubscribersCountByDay
(
[Date] date not null
,Subscribers int not null
)
GO
insert into dbo.SubscribersCountByDay
([Date], Subscribers)
values
('2015-10-01', 1)
,('2015-10-02', 2)
,('2015-10-03', 0)
,('2015-10-04', 4)
,('2015-10-05', 5)
,('2015-10-06', 0)
,('2015-10-07', 7)
GO
declare
#Date date
,#Subscribers int
,#Sum int = 0
,#GroupId int = 1
declare #Result as Table
(
GroupName varchar(10) not null
,[Sum] int not null
)
declare ScanIt cursor fast_forward
for
(
select [Date], Subscribers
from dbo.SubscribersCountByDay
union
select '2030-12-31', 0
) order by [Date]
open ScanIt
fetch next from ScanIt into #Date, #Subscribers
while ##FETCH_STATUS = 0
begin
if (#Subscribers = 0 and #Sum > 0)
begin
insert into #Result (GroupName, [Sum]) values ('Group ' + cast(#GroupId as varchar(6)), #Sum)
set #GroupId = #GroupId + 1
set #Sum = 0
end
else begin
set #Sum = #Sum + #Subscribers
end
fetch next from ScanIt into #Date, #Subscribers
end
close ScanIt
deallocate ScanIt
select * from #Result
GO
For the OP: Please next time write the table, just posting an image is lazy
In a version of SQL Server modern enough to support CTEs you can use the following cursorless query:
-- Sample data.
declare #SampleData as Table ( Id Int Identity, Subscribers Int );
insert into #SampleData ( Subscribers ) values
-- ( 0 ), -- Test edge case when we have a zero first row.
( 200 ), ( 100 ), ( 200 ),
( 0 ), ( 0 ), ( 0 ),
( 50 ), ( 50 ), ( 12 ),
( 0 ), ( 0 ),
( 43 ), ( 34 ), ( 34 );
select * from #SampleData;
-- Run the query.
with ZerosAndRows as (
-- Add IsZero to indicate zero/non-zero and a row number to each row.
select Id, Subscribers,
case when Subscribers = 0 then 0 else 1 end as IsZero,
Row_Number() over ( order by Id ) as RowNumber
from #SampleData ),
Groups as (
-- Add a group number to every row.
select Id, Subscribers, IsZero, RowNumber, 1 as GroupNumber
from ZerosAndRows
where RowNumber = 1
union all
select FAR.Id, FAR.Subscribers, FAR.IsZero, FAR.RowNumber,
-- Increment GroupNumber only when we move from a non-zero row to a zero row.
case when Groups.IsZero = 1 and FAR.IsZero = 0 then Groups.GroupNumber + 1 else Groups.GroupNumber end
from ZerosAndRows as FAR inner join Groups on Groups.RowNumber + 1 = FAR.RowNumber
)
-- Display the results.
select Id, Subscribers,
case when IsZero = 0 then 'no group' else 'Group' + Cast( GroupNumber as VarChar(10) ) end as Grouped
from Groups
order by Id;
To see the intermediate results just replace the final select with select * from FlagsAndRows or select * from Groups.

Case when rowcount

SELECT distinct ID from table
Result
Letter
1 A
2 B
How can I make the select display the following when the result is as above:
What I was thinking but not working yet:
SELECT CASE WHEN ##ROWCOUNT(ID) = 2 THEN 'AB'
ELSE ID END AS ID
FROM table
Result would be
Letter
1 AB
Your requirements really aren't clear.
Based on various assumptions, this might give you what you want.
SELECT Count(*) OVER (PARTITION BY 937)
, CASE WHEN Count(*) OVER (PARTITION BY 937) = 2 THEN
'AB'
ELSE
id
END As id
, id
FROM your_table
Try:
Select
CASE WHEN (ROW_NUMBER() OVER (ORDER BY [ID])) = 2 THEN 'AB'
ELSE [ID] END AS [ID]
FROM table
group by
[ID]
What's above will also only work if [ID] and 'AB' are the same data type. If not, then you'll need to cast [ID] to a varchar in your ELSE statement. That'd be: ELSE cast([ID] as varchar(8)) END AS [ID]
Am not sure what you are trying to achieve. But to me it looks like this is what you are trying..
Declare a Variable and initialize it with Distinct Count of ID and use it in select
Declare #cnt int
SELECT #cnt=count(distinct ID) from table
SELECT CASE WHEN #cnt = 2 THEN 'AB'
ELSE ID END AS ID
FROM table

OR in WHERE clause is slowing down sql query performance ( sql server)

I'm seeing a performance issue in one of the sproc that we use in our application.
It’s a very big sproc and I have narrowed it down to the part where I'm seeing performance issue.
It’s in the where clause ( copied below). Query estimation plan shows this part takes about 80%.
Logic is that #AssignedToIds and #AssignedToRoleIds can be null, it is it null then we will pull all the records.
Temp tables can have multiple rows. Any help on improving the performance is greatly appreciated.
#AssignedTo and #AssignedToRole are temp tables.
#AssignedTo has only one value in the table and #AssignedToRole is empty
SQL:-
SELECT DISTINCT TOP 2000 t.Member_Party_PartyId AS Member_Party_PartyId
FROM Task t
WHERE t.IsDeleted = 0
AND (
t.DueDate >= #DueStart
OR #DueStart IS NULL
)
AND (
t.DueDate <= #DueEnd
OR #DueEnd IS NULL
)
AND (
(
#FilterType = 'MyPatients'
AND t.AssignedUserId = #UserId
)
OR #FilterType != 'MyPatients'
)
AND (#FilterType != 'MyRole')
AND (
(
#FilterType = 'MyGroup'
AND t.AssignedUserId IN (
SELECT PartyId
FROM #OrgMembers
)
)
OR #FilterType != 'MyGroup'
)
AND (
(
#FilterType = 'Custom'
AND vpad.Provider IN (
SELECT PartyId
FROM #OrgMembers
)
)
OR #FilterType != 'Custom'
)
AND (
(
#ActiveCase = 1
AND cases.CaseId IS NOT NULL
)
OR #ActiveCase = 0
)
AND (
t.TaskStatusId IN (
SELECT TaskStatusId
FROM #TaskStatus
)
)
AND (
t.TaskCategoryId IN (
SELECT TaskCategoryId
FROM #TaskCategory
)
OR #TaskCategoryIds IS NULL
)
AND (
t.TaskPriorityId IN (
SELECT TaskPriorityId
FROM #TaskPriority
)
OR #TaskPriorityIds IS NULL
)
AND (
rm.RegistryId IN (
SELECT RegistryId
FROM #Registry
)
OR #RegistryIds IS NULL
)
AND (
(
fg.CareMeasureId IN (
SELECT CareMeasureId
FROM #CareMeasure
)
AND exclusion.MemberId IS NULL
)
OR #CareMeasureIds IS NULL
)
AND (
vpad.OrganizationId IN (
SELECT OrganizationId
FROM #Organization
)
OR (
SELECT count(OrganizationId)
FROM #Organization
) = 0
)
AND (
vpad.Provider IN (
SELECT ProviderId
FROM #Provider
)
OR #ProviderIds IS NULL
)
AND (
cases.CaseTypeId IN (
SELECT CaseTypeId
FROM #CaseType
)
OR #CaseIds IS NULL
)
AND
--(case when #AssignedToIds Is Not Null And then t.AssignedUserId in (select AssignedToId from #AssignedTo))
(
(
t.AssignedUserId IN (
SELECT AssignedToId
FROM #AssignedTo
)
OR (
#AssignedToIds IS NULL
AND #AssignedToRoleIds IS NULL
)
)
OR (
t.AssignedRoleId IN (
SELECT AssignedRoleId
FROM #AssignedToRole
)
OR (
#AssignedToRoleIds IS NULL
AND #AssignedToIds IS NULL
)
)
)
AND (
vpad.OrganizationId IN (
SELECT OrganizationId
FROM #UserOrgs
)
OR (
(
SELECT count(OrganizationId)
FROM #UserOrgs
) = 0
)
OR (#RoleType <> 'Manager')
)
AND (
(
mhp.MemberHealthPlanTypeId IN (
SELECT HealthPlanId
FROM #HealthPlan
)
AND hpds.HierarchyOrder IS NOT NULL
)
OR #HealthPlanIds IS NULL
)
OPTION (RECOMPILE);
You Could try adding
option(recompile)
To the end of that SQL query. See if that speeds it up a bit.
Personally with so many conditions in this where clause it is going to be a nightmare to figure out where the performance issue is.
If it was me I would look to split this query down into smaller queries so that you are working on ever decreasing sub sets.
eg get the results of just
INSERT INTO myWorkingTable (some columns here....)
SELECT
DISTINCT TOP 2000
t.Member_Party_PartyId AS Member_Party_PartyId
FROM
Task t
WHERE
t.IsDeleted = 0
then from these results work through the next set of queries and where possible include any of your conditional logic.
eg.
so for example your logic for:
(
t.DueDate >= #DueStart
OR
#DueStart IS NULL
)
could be
IF(#DueStart IS NOT NULL)
BEGIN
--LEAVE ONLY THOSE ITEMS WHERE #DueStart >= dueDate
DELETE FROM myWorkingTable WHERE t.DueDate < #DueStart
END
So other conditions like this can be performed outside of the "main" query.
Then you can eventually run a execution plan to check against the complete query and then apply any suggested indexes that the plan suggests.
I know it doesn't directly answer the question but with something this monolithic it is going to be pretty much impossible for someone to just say "your problem is with this bit"
Although doing NULL Checks within a where clause can be costly.

How to get previous and next row's value effeciently in SQL server

Say I have these rows,
InstrumentID
547
698
708
InstrumentID is not autogenerated column.
Say if I pass the parameter in procedure as 698, I should get previous value as 547 and next value as 708. How do I do this efficiently in SQL?
I have this procedure but it is not efficient (and not correct).
Alter PROCEDURE GetNextAndPreviousInsturmentID
(
#InstrumentID varchar(14),
#PreviousInstrumentID varchar(14) OUT,
#NextInstrumentID varchar(14) OUT
)
AS
BEGIN
Declare #RowNum int = 0
Select #RowNum = ROW_NUMBER() Over (Order by Cast(#InstrumentID as decimal(18))) From Documents Where InstrumentID = #InstrumentID
;With normal As
(
Select ROW_NUMBER() Over (Order by Cast(#InstrumentID as decimal(18))) as RowNum, Cast(InstrumentID as decimal(18)) as InstrumentID
From Documents
)
Select #PreviousInstrumentID = InstrumentID From normal
Where RowNum = #RowNum - 1
Select #NextInstrumentID = InstrumentID From normal
Where RowNum = #RowNum + 1
END
GO
Here is a simpler solution, still it's more efficient
SELECT P.PreviousID, N.NextID
FROM
(SELECT MAX(D.InstrumentID) PreviousID
FROM Documents D
WHERE InstrumentID < #InstrumentID) P
CROSS JOIN
(SELECT MIN(D.InstrumentID) NextID
FROM Documents D
WHERE InstrumentID > #InstrumentID) N
Try this:
Alter PROCEDURE GetNextAndPreviousInsturmentID
(
#InstrumentID varchar(14),
#PreviousInstrumentID varchar(14) OUT,
#NextInstrumentID varchar(14) OUT
)
AS
BEGIN
Declare #Ids TABLE(Id varchar(14))
;With normal As
(
--Numerate our rows
Select ROW_NUMBER() Over (Order by Cast(Documents.InstrumentID as decimal(18)) as RowNumber,
Documents.InstrumentID
From Documents
)
--Insert three rows from our table with our id and previos/next id
INSERT INTO #Ids(Id)
SELECT TOP(3) normal.InstrumentID
FROM normal
WHERE RowNumber >=
(
SELECT RowNumber - 1
FROM normal
WHERE normal.InstrumentID = #InstrumentID
)
ORDER BY normal.RowNumber
--select next and previos ids
SELECT #PreviousInstrumentID = Min(CAST(Id as decimal(18))),
#NextInstrumentID = MAX(CAST(Id as decimal(18)))
FROM #Ids
END
GO
In MS SQL 2012 we have new window functions like FIRST_VALUE and LAST_VALUE, unfortunately in sql 2008 these functions are missing.
WITH CTE AS (
SELECT rownum = ROW_NUMBER() OVER (ORDER BY p.LogDate), p.LogDate
FROM DeviceLogs p
)
SELECT prev.logdate PreviousValue, CTE.logdate, nex.logdate NextValue
FROM CTE
LEFT JOIN CTE prev ON prev.rownum = CTE.rownum - 1
LEFT JOIN CTE nex ON nex.rownum = CTE.rownum + 1
GO
select
LAG(InstrumentID) OVER (ORDER BY InstrumentID) PreviousValue,
InstrumentID,
LEAD(InstrumentID) OVER (ORDER BY InstrumentID) NextValue
from documents
Hi I think this will be much more efficient:
Select Next :
select top 1 ID from mytable
where ID >'698'
order by ID asc
Select Prev:
select top 1 ID from mytable
where ID <'698'
order by ID desc