Sql comparaison nested on one column - sql-server

Imagine a table :
ID Month Year Value 1
1 May 17 58
2 June 09 42
3 December 18 58
4 December 18 58
5 September 10 84
6 May 17 42
7 January 16 3
I want to return all the data that shares the same month and year where Value 1 is different. So in our example, I want to return 1 and 6 only but not 3 and 4 or any of the other entries.
Is there a way to do this? I am thinking about a combination of distinct and group by but can't seem to come up with the right answer being new to SQL.
Thanks.

It could be done without grouping, but with simple self-join:
select distinct t1.*
from [Table] t1
inner join [Table] t2 on
t1.Month = t2.Month
and t1.Year = t2.Year
and t1.Value_1 <> t2.Value_1
You can find some information and self-join examples here and here.

For each row you can examine aggregates in its group with the OVER clause. eg:
create table #t(id int, month varchar(20), year int, value int)
insert into #t(id,month,year,value)
values
(1,'May' ,17, 58 ),
(2,'June' ,09, 42 ),
(3,'December' ,18, 58 ),
(4,'December' ,18, 58 ),
(5,'September',10, 84 ),
(6,'May' ,17, 42 ),
(7,'January' ,16, 3 );
with q as
(
select *,
min(value) over (partition by month,year) value_min,
max(value) over (partition by month,year) value_max
from #t
)
select id,month,year,value
from q
where value_min <> value_max;

If I understood your question correctly, you are looking for the HAVING keyword.
If you GROUP BY Month, Year, Value_1 HAVING COUNT(*) = 1, you get all combinations of Month, Year and Value_1 that have no other occurrence.

Related

Use join with CTE

I have a table in which i am inserting some records every week. There is a column for Date. I want to compare the data of last week and this week using column key. Below is my table:
Name Date Key
ABC 07 June 1
BAC 07 June 2
WSD 07 June 3
QWE 14 June 9
QWT 14 June 2
DEF 14 June 1
CXZ 14 June 6
I want the data of 14 June in which key is same as in data of 07th june.
Desired output:
Name Date Key
QWT 14 June 2
DEF 14 June 1
I am using CTE to join but i am not getting the desired results.
;WITH T1
AS
(SELECT * FROM [Table] where [Date]= '07 June'),
T2
AS
(SELECT * FROM [Table] where [Date]= '14 June')
SELECT *
FROM T2
INNER JOIN T1 ON T1.[KEY] = T2.[KEY];
What you have should be returning the results you stated that you want. I would maybe simplify this a little bit to a single query with a self join. Something like this.
select t2.*
from [Table] t
join [Table] t2 on t.MyKey = t2.MyKey
where t.MyDate = '07 June'
and t2.MyDate = '14 June'
If you want the data of 14 June in which key is the same as in data of 07th June. You can use intersect:
select
t1.*
from
table
as t1
where
t1.MyDate = '07 June'
intersect select
t2.*
from
table
as t2
where
t2.MyDate = '14 June
You can also think everything dynamic as below. This will return you result regardless what is the date is. this will always compare a row with the row with date 7 day less.
You can check DEMO HERE
SELECT A.*
FROM your_table A
INNER JOIN your_table B
ON A.[Key] = B.[Key] AND DATEADD(DD,7,B.[Date]+ ' 2019') = A.[Date] + ' 2019'
-- Added 2019 To make the string as date

Time Complexity of SQL Cursor

I am using CURSOR to implement the following in SQL Server, I am only iterating through the table - The time complexity will be O(n) I think (?). But everywhere I read about CURSOR, it says CURSOR is a bad practice. So is there a better way to implement the following ?
Existing Table
month value
1 92
4 20
9 92
New Table
month value
1 92
2 92
3 92
4 20
5 20
6 20
7 20
8 20
9 92
10 92
11 92
12 92
The use of cursor isn't (primarily) bad because it has poor time complexity, but because it is more error-prone and harder to read than a simple query. You are correct that iterating over a table via cursor is O(n).
On to your problem at hand. If you have the months (1..12) stored somewhere, say Months, then you can do it like this:
WITH matchingMonths AS (
SELECT m.month, MAX(mav.month) as matchedMonth
FROM Months m, MonthsAndValues mav
WHERE m.month >= mav.month
GROUP BY m.month
)
SELECT mm.month, mav.value
FROM matchingMonths mm
JOIN MonthsAndValues mav on mav.month = mm.matchedMonth
Without such a table Months, you could generate it on-the-fly:
WITH Months(month) AS (
SELECT 1
UNION ALL
SELECT month + 1 FROM Months WHERE month < 12
),
matchingMonths AS (
SELECT m.month, MAX(mav.month) as matchedMonth
FROM Months m, MonthsAndValues mav
WHERE m.month >= mav.month
GROUP BY m.month
)
SELECT mm.month, mav.value
FROM matchingMonths mm
JOIN MonthsAndValues mav on mav.month = mm.matchedMonth

Accumulative Update for previous records

I have table that shows these information
Month NewClients OnHoldClients
5-2017 10 2
6-2017 16 4
7-2017 11 1
8-2017 15 6
9-2017 18 7
I am trying to find the accumulative total for each month
which is
(NewClients - OnHoldClients) + Previous Month Total
Something like this
Month NewClients OnHoldClients Total
5-2017 10 2 8
6-2017 16 4 20
7-2017 11 1 30
8-2017 15 6 39
9-2017 18 7 50
the query i tried to build was something like this but I think should be an easier way to do that
UPDATE MyTable
SET Total = (SELECT TOP 1 Total FROM MyTable B WHERE B.Month < A.Month) + NewClients - OnHoldClients
FROM MyTable A
Before we begin, note the mere fact that you're facing such calculative problem is a symptom that maybe you don't have the best possible design. Normally for this purpose calculated values are being stored along the way as the records are inserted. So i'd say you'd better have a total field to begin with and calculate it as records amass.
Now let's get down to the problem at hand. i composed a query which does that nicely but it's a bit verbose due to recursive nature of the problem. However, it yields the exact expected result:
DECLARE #dmin AS date = (SELECT min(mt.[Month]) from dbo.MyTable mt);
;WITH cte(_Month, _Total) AS (
SELECT mt.[Month] AS _Month, (mt.NewClients - mt.OnHoldClients) AS _Total
FROM dbo.MyTable mt
WHERE mt.[Month] = #dmin
UNION ALL
SELECT mt.[Month] AS _Month, ((mt.NewClients - mt.OnHoldClients) + ccc._Total) AS _Total
FROM dbo.MyTable mt
CROSS APPLY (SELECT cc._Total FROM (SELECT c._Total,
CAST((row_number() OVER (ORDER BY c._Month DESC)) AS int) as _Rank
FROM cte c WHERE c._Month < mt.[Month]) as cc
WHERE cc._Rank = 1) AS ccc
WHERE mt.[Month] > #dmin
)
SELECT c._Month, max(c._Total) AS Total
FROM cte c
GROUP BY c._Month
It is a recursive CTE structure that goes about each record all along the way to the initial month and adds up to the final Total value. This query only includes Month and Total fields but you can easily add the other 2 to the list of projection.
Try this
;WITH CTE([Month],NewClients,OnHoldClients)
AS
(
SELECT '5-2017',10,2 UNION ALL
SELECT '6-2017',16,4 UNION ALL
SELECT '7-2017',11,1 UNION ALL
SELECT '8-2017',15,6 UNION ALL
SELECT '9-2017',18,7
)
SELECT [Month],
NewClients,
OnHoldClients,
SUM(MonthTotal)OVER( ORDER BY [Month]) AS Total
FROM
(
SELECT [Month],
NewClients,
OnHoldClients,
SUM(NewClients-OnHoldClients)OVER(PArtition by [Month] Order by [Month]) AS MonthTotal
FROM CTE
)dt
Result,Demo:http://rextester.com/DKLG54359
Month NewClients OnHoldClients Total
--------------------------------------------
5-2017 10 2 8
6-2017 16 4 20
7-2017 11 1 30
8-2017 15 6 39
9-2017 18 7 50

SQL Server 2012 Computed column

ID Date Value Average
1 10/5/2017 15 15
2 10/6/2017 25 20
3 10/7/2017 35 25
4 10/8/2017 45 35
5 10/9/2017 55 45
6 10/10/2017 65 55
7 10/11/2017 75 65
If this is my table, I want average to be a computed column and its formula in general is average of previous 3 row's Value column.
(Ex. for 2nd row it is (25+15)/2 )
How can i do such a thing in computed column? Is there any better way to achieve this.
Thanks in advance.
i would go with a view and use avg windows function
select
id,
date,
value,
avg(value) over (order by id)
from table
Updated answer: you could use frames clause like below
Working Demo
;with cte(id,date,val)
as
(
select 1 ,'10/5/2017' , 15 UNION ALL
select 2 ,'10/6/2017' , 25 UNION ALL
select 3 ,'10/7/2017' , 35 UNION ALL
select 4 ,'10/8/2017' , 45 UNION ALL
select 5 ,'10/9/2017' , 55 UNION ALL
select 6 ,'10/10/2017', 65 UNION ALL
select 7 ,'10/11/2017', 75
)
SELECT *,avg(VAL) OVER (ORDER BY id rows between 2 PRECEDING and current row ) FROM CTE

SQL: return IDs whose val=min(exp)

Given a table like
pkg#, time
0, 20
1, 23
2, 34
3, 35
4, 59
I want to know the pkg# who has max/min time difference to its successor pkg (gap between 2 consecutive pkgs)
In this case, pkg-2 has min time difference (1), and pkg-3 has max time difference (14)
What's the sql that can return pkg# for min/max time difference to its next pkg?
If you are on SQL SERVER 2012 or above, you can try LEAD function here to get the next row value to align in your current row:
SELECT *, LEAD([time]) OVER(ORDER BY [pkg#]) as nexttime
FROM [your_table]
will yield something like this:
pkg time nexttime
0 20 23
1 23 34
2 34 35
3 35 59
4 59 NULL
Now compare these two columns values should give you what you want. (Note last row will have nexttime = NULL since there's no more row to get value from, so just filter it out when querying).
Assume new table name is new_table, to get max diff:
select top 1 *, nexttime-time as diff
from new_table
where nexttime is not null
order by (nexttime-time) desc
and to get min diff just order by nexttime-time
A slight twist on #xbb 's answer:
CREATE TABLE #t ( Pkg INT, Time INT );
INSERT #t ( Pkg, Time )
VALUES ( 0, 20 ),
( 1, 23 ),
( 2, 34 ),
( 3, 35 ),
( 4, 59 );
SELECT Pkg
, Time
, Time - LAG(Time) OVER ( ORDER BY Pkg ) AS TimeSincePrevious
, ABS(time - LEAD(Time) OVER ( ORDER BY Pkg )) AS TimeUntilNext
FROM #t;
DROP TABLE #t;
Will yield the result:
Pkg Time TimeSincePrevious TimeUntilNext
0 20 NULL 3
1 23 3 11
2 34 11 1
3 35 1 24
4 59 24 NULL
Take a look at solution below - I decomposed query into three steps:
WITH Ordered AS
(
SELECT ROW_NUMBER() OVER (ORDER BY pkg) rowNum, pkg, [time] FROM Test
),
Diffs AS
(
SELECT T1.pkg,
T2.[time]-T1.[time] diff,
MIN(T2.[time]-T1.[time]) OVER () minimum,
MAX(T2.[time]-T1.[time]) OVER () maximum
FROM Ordered T1
JOIN Ordered T2 ON T1.rowNum = T2.rowNum-1
)
SELECT pkg, diff FROM Diffs
WHERE diff=minimum OR diff=maximum
ORDER by diff
Number of rows
Join with offset 1, calculate diff, MIN and MAX
Filter rows not equal to min or max
Query may return more rows if tie occurs. Ties can be simply removed by replacing final SELECT with:
...
SELECT MIN(pkg) pkg, diff FROM Diffs
WHERE diff=minimum OR diff=maximum
GROUP BY diff
ORDER by diff

Resources