How to get columns names that been updated - sql

Suppose I have a table like this :
Table 1:
date account name type status open_account_date
31.12.14 1000 20 40 50 31.12.14
2.1.15 1000 10 10 50 31.12.14
3.1.15 1000 5 15 50 31.12.14
and I want to build a summary table like this for the first quarter :
account numOfChanges Changes
1000 4 (name, type)
The first row in table 1 indicats that that the account was opened and somebody enterd for the account some details but the others indicats changes but i want to know which fields has been changed. Is there any suggestion or an idea how to perform this?

DECLARE #StartOfQuarter DATE = '1/1/2015'
;WITH cteRcordStateBeforeQuarter AS (
SELECT
[date]
,account
,name
,[type]
,[status]
,open_account_date
,RowNum = ROW_NUMBER() OVER (PARTITION BY account ORDER BY [date] DESC)
FROM
#Table
WHERE
[date] < #StartOfQuarter
)
, cteRecordStatesDuringQuarter AS (
SELECT
[date]
,account
,name
,[type]
,[status]
,open_account_date
,RowNum = ROW_NUMBER() OVER (PARTITION BY account ORDER BY [date] ASC) + 1
--add 1 because the first row is going to be the last one prior to quarter
,LatestChangeRowNum = ROW_NUMBER() OVER (PARTITION BY account ORDER BY [date] DESC)
FROM
#Table
WHERE
DATEPART(QQ,[date]) = 1
--change to suite ongoing needs such as quater and YEAR([date]) = ??
)
, cteRecursive AS (
SELECT
[date]
,account
,name
,[type]
,[status]
,open_account_date
,RowNum
,LatestChangeRowNum = 0
,NumOfChanges = 0
,[Changes] = CAST('' AS VARCHAR(100))
FROM
cteRcordStateBeforeQuarter
WHERE
RowNum = 1
UNION ALL
SELECT
q.[date]
,q.account
,q.name
,q.[type]
,q.[status]
,q.open_account_date
,q.RowNum
,LatestChangeRowNum = CAST(q.LatestChangeRowNum AS INT)
,NumOfChanges = r.NumOfChanges
+ CASE WHEN ISNULL(q.name,-99999) <> ISNULL(r.name,-99999) THEN 1 ELSE 0 END
+ CASE WHEN ISNULL(q.[type],-99999) <> ISNULL(r.[type],-99999) THEN 1 ELSE 0 END
+ CASE WHEN ISNULL(q.[status],-99999) <> ISNULL(r.[status],-99999) THEN 1 ELSE 0 END
+ CASE WHEN ISNULL(q.open_account_date,'1/1/1900') <> ISNULL(r.open_account_date,'1/1/1900') THEN 1 ELSE 0 END
,[Changes] = CAST(ISNULL(r.[Changes],'')
+ CASE WHEN ISNULL(q.name,-99999) <> ISNULL(r.name,-99999) AND r.[Changes] NOT LIKE '%name%' THEN ',name' ELSE '' END
+ CASE WHEN ISNULL(q.[type],-99999) <> ISNULL(r.[type],-99999) AND r.[Changes] NOT LIKE '%type%' THEN ',type' ELSE '' END
+ CASE WHEN ISNULL(q.[status],-99999) <> ISNULL(r.[status],-99999) AND r.[Changes] NOT LIKE '%status%' THEN ',status' ELSE '' END
+ CASE WHEN ISNULL(q.open_account_date,'1/1/1900') <> ISNULL(r.open_account_date,'1/1/1900') AND r.[Changes] NOT LIKE '%open_account_date%' THEN ',open_account_date' ELSE '' END
AS VARCHAR(100))
FROM
cteRecordStatesDuringQuarter q
INNER JOIN cteRecursive r
ON q.account = r.account
AND q.RowNum = r.RowNum + 1
)
SELECT
account
,NumOfChanges
,[Changes] = REPLACE(IIF(CHARINDEX(',',[Changes]) = 1, RIGHT([Changes],LEN([Changes]) - 1),[Changes]),',',', ')
FROM
cteRecursive
WHERE
LatestChangeRowNum = 1
If you where using SQL 2012 + it would be a little easier because you could use LAG or LEAD window functions and IIF instead of case. But a recursive cte works pretty well too. I assume your recordset will have multiple accounts as well as multiple quarters and there for multiple recordstates prior to the quarter. Due to this you will need to tweak the date logic slightly but this will give you the gist.
First find the record state prior to the quarter. Then find all of the changes during the quarter. Add some row numbers to determine which is the first and which is the last change. Then use a recursive cte to test for changes. In the end you just need to format the string the way you want.

Related

Complex switch Case SQL

I am stuck in a complex SQL switch case statement in the midst of a stored procedure.
I am copying the middle part of the stored procedure:
AND
(
#Statuses IS NULL
OR
[BF].[BookingFileStatusID] IN
(
SELECT [ID]
FROM #BookingFileStatuses)
AND
(
#Statuses IS NULL
OR
**((
(
SELECT count([ID])
FROM #BookingFileStatuses) >= 1)
AND
(
1 = (
CASE
WHEN [S].servicetypeid IS NULL THEN 1
WHEN (
[S].servicetypeid <> 3
AND
[BF].bookingfilestatusid IN ( 16, 17, 18, 19, 20, 23)
)
THEN 1
WHEN (
[S].servicetypeid = 3
AND
[BF].bookingfilestatusid IN
(
SELECT [ID]
FROM #BookingStatuses)
)
THEN 1
ELSE 0
END)
)
)**
)
)
Highlighted portion should return some results.
i.e. replace the highlighted portion with if else.
Please assist.
You could rewrite the whole thing without the case statement. It is easier to read this way.
(
(SELECT Count([ID]) FROM #BookingFileStatuses) >= 1
AND
( [S].ServiceTypeID IS NULL
OR ([S].ServiceTypeID <> 3 AND [BF].BookingFileStatusID in (16,17,18,19,20,23))
OR ([S].ServiceTypeID = 3 AND [BF].BookingFileStatusID in (SELECT [ID] FROM #BookingStatuses))
)
)
In this part :
AND ( #Statuses IS NULL
OR
1 = (CASE WHEN #BookingStatusCount = 0 THEN 1
ELSE
xxxxxxxxxxxxxxxx
END)
)
you are making Or with two cases : 1=( 1 or sth else). so you don't need the second query at all. because alway you can make one value. I would like to write this:
AND ( #Statuses IS NULL
OR
1 = (CASE WHEN #BookingStatusCount = 0 THEN 1 else 5)
)

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.

SQL Conversion failed when converting row number

So I've been trying to add an Dynamic Row Number column with out using Dynamic SQL. However when I try I get an error 'Conversion failed when converting character string to smalldatetime data type.'
I Know the Error is coming from in the functions So if you want to just look at the switch case in the function that is the problem, but here is the stored procedure just in case you need to see it.
I have a store procedure which looks like this:
ALTER PROCEDURE [dbo].[MMS_EdgateMainQueue]
-- Add the parameters for the stored procedure here
#OrderByColumnID int = 3,
#Skip int = 0,
#Take int = 0,
#Descending bit = 1,
#ResultCount INT OUTPUT
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
Declare #UrlTitlePrefix varchar(2080) = '<a href="/Title/PageByExtTitleID?ActionName=Edgate&ExtTitleID='
Declare #UrlProducerPrefix varchar(2080) = '<a href="/Producers/ByExtVendorID?ActionName=Details&ExtVendorID='
Declare #Urlmidfix varchar(100) = '">'
Declare #UrlPostFix varchar(100) = '</a>'
SELECT TOP (#Take)
[row_numb],
#UrlTitlePrefix + ExtTitleID + #Urlmidfix + ExtTitleID + #UrlPostFix as [Item #],
f.Title as Name,
#UrlProducerPrefix + f.ExtVendorID + #Urlmidfix + f.DisplayName + #UrlPostFix as Producer,
f.Created as Created,
isnull(f.Academic, '') as Academic,
isnull(f.Sears,'') as Sears,
isnull(f.Editor, '') as Editor,
CONVERT(INT, f.[Copy]) AS Copy,
f.[Segment],
CONVERT(INT, f.[Taxonomy]) AS Taxonomy,
f.[Priority]
FROM EdgateNewTitlesInnerQuery(#OrderByColumnID, #Descending) as f
Where f.[row_numb] Between ((#Skip * #Take) + 1) and ((#Skip + 1) * #Take) order by f.[row_numb]
END
And The Inner Function Looks Like:
ALTER FUNCTION [dbo].[EdgateNewTitlesInnerQuery]
(
#OrderByColumnID int,
#Descending bit
)
RETURNS TABLE
AS
RETURN
(
SELECT DISTINCT
v.ExtVendorID,
t.ID,
t.ExtTitleID,
t.Title,
v.DisplayName,
t.Created,
ecs.Title as [Academic],
ssub.Title as [Sears],
etw.EditorName as [Editor],
etw.CopyDone AS [Copy],
etw.SegmentsStatus as [Segment],
etw.TaxonomyDone AS [Taxonomy],
CASE WHEN wft.[Priority] is null THEN 0 ELSE wft.[Priority] END as [Priority],
--row_number() OVER (ORDER BY t.Created DESC) AS [row_number]
row_number() OVER (ORDER BY
CASE #OrderByColumnID WHEN 0 THEN t.ExtTitleID
WHEN 1 THEN t.Title
WHEN 2 THEN v.DisplayName
WHEN 3 THEN t.Created
WHEN 4 THEN ecs.Title
WHEN 5 THEN ssub.Title
WHEN 6 THEN etw.EditorName
WHEN 7 THEN etw.CopyDone
WHEN 8 THEN etw.SegmentsStatus
WHEN 9 THEN etw.TaxonomyDone
WHEN 10 THEN CASE WHEN wft.[Priority] is null THEN 0 ELSE wft.[Priority] END
ELSE t.Created
END DESC ) AS [row_numb]
FROM [Title] t
join EdgateTitleWorkflow etw on etw.FK_TitleID = t.ID
join Vendor v on v.ExtVendorID = t.ProducerID
join CollectionItem i on i.TitleID = t.ID and i.CollectionID = 16
left join [EdgateSuggestedAcademicSubject] esas on esas.FK_TitleID = t.ID and esas.isPrimary = 1
left join EC_Subject ecs on ecs.ID = esas.FK_SubjectID
left join [FMGSuggestedSears] fss on fss.FK_TitleID = t.ID and fss.isPrimary = 1
left join [FMGSearsSubjects] ssub on ssub.ID = fss.SearsSubjectID and ssub.ParentID is null
left join [WorkFlow_Tracker] wft on wft.TitleID = t.ID
where (etw.CopyDone = 0 or etw.TaxonomyDone = 0 or etw.SegmentsStatus = 0)
)
I've tried passing this in as a string originally but it just didn't sort at all. So I was looking at similar problems and tried this solution Here
but my switch Case is now throwing a Conversion Error. Does anyone have an Idea on how to fix this?
The problem is that case is an expression that returns one type, defined during compilation. You can fix this with a separate case for each key. I think this is the statement you want:
row_number() OVER (ORDER BY (CASE WHEN #OrderByColumnID = 0 THEN t.ExtTitleID END),
(CASE WHEN #OrderByColumnID = 1 THEN t.Title END),
(CASE WHEN #OrderByColumnID = 2 THEN v.DisplayName END),
(CASE WHEN #OrderByColumnID = 3 THEN t.Created END),
(CASE WHEN #OrderByColumnID = 4 THEN ecs.Title END),
(CASE WHEN #OrderByColumnID = 5 THEN ssub.Title END),
(CASE WHEN #OrderByColumnID = 6 THEN etw.EditorName END),
(CASE WHEN #OrderByColumnID = 7 THEN etw.CopyDone END),
(CASE WHEN #OrderByColumnID = 8 THEN etw.SegmentsStatus END),
(CASE WHEN #OrderByColumnID = 9 THEN etw.TaxonomyDone END),
(CASE WHEN #OrderByColumnID = 10 THEN COALESCE(wft.[Priority], 0) END)
t.Created DESC
)

SQL Server determine if values are monotonous

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

SQL SUM and nested where clause

I have an SQL that takes an id, a start date and an end date as parameters. I then sum certain that are restricted by the where clause. However I need to also sum the same column that doesnt take into account the data params and calculates the total for a particular driver. For example:
SELECT DISTINCT
PMTS.VolunteerId,
PMTS.ChargeRate,
SUM(
CASE [Type]
WHEN 418 THEN Amount
ELSE 0
END
) AS DriverMileage,
SUM(
CASE [Type]
WHEN 1000 THEN Amount
ELSE 0
END
) AS GPMileage,
SUM(
CASE [Type]
WHEN 1001 THEN Amount
ELSE 0
END
) AS Reimbursements,
SUM(
CASE [Type]
WHEN 1002 THEN Amount
ELSE 0
END
) AS MobilePhoneCharges,
SUM(Mileage) AS TotalMileageWeek,
B.TotalMileage,
VOLS.Address1,
VOLS.Address2,
VOLS.Town,
ISNULL(VOLS.Surname, '') + ', ' + ISNULL(VOLS.Forename, '') AS SurnameForename ,
ISNULL(VOLS.County, '') + ' ' + ISNULL(VOLS.PostCode, '') AS CountyPostcode
FROM dbo.vVolunteerPayments PMTS
INNER JOIN dbo.vVolunteers VOLS ON PMTS.VolunteerId = VOLS.VolunteerID
--total mileage
INNER JOIN (select VolunteerId, sum(Mileage) as TotalMileage
FROM dbo.vVolunteerPayments GROUP BY VolunteerId) b ON
b.VolunteerID=PMTS.VolunteerId
WHERE
PMTS.VolunteerId = #volunteerid
AND
PMTS.PaymentEventID = 0
AND
PMTS.DateCreated BETWEEN #sd AND #ed
GROUP BY
PMTS.VolunteerId
,Address1
,Address2
,Town
,County
,PostCode
,Surname
,Forename
,B.TotalMileage
,PMTS.ChargeRate
So the SUM(Mileage) As MileageTotal ignores the where clause
Try this:
Select
a.Name,
SUM(Mileage) As MileageWeek,
SUM(Mileage) As MileageTotal,
b.TotMiles
FROM <table> a
JOIN (select name,sum(Mileage) as TotMiles
FROM <table> GROUP By name) b ON b.name=a.name
WHERE
Journey.DateCreated BETWEEN #startdate AND #enddate
Replace < table > with your actual table name (Journey???)
Eliminate the where clause and do conditional summation:
Select Name,
SUM(case when Journey.DateCreated BETWEEN #startdate AND #enddate then Mileage
else 0
end) As MileageWeek,
SUM(Mileage) As MileageTotal
from Journey
I also added back in the from clause (presumably you left it out accidentally).
SUM(
CASE [Type]
WHEN 418 THEN Amount
ELSE 0
END
) AS DriverMileage,
to
sum(case when [type] = 418 and PMTS.DateCreated BETWEEN #sd AND #ed then amount
else 0
end) as DriverMileage
You may need to include more conditionals from the where clause -- I'm not sure which total you want.