t-sql grouping rows between ranges - sql-server

I have this query that returns Quantity based discount list
with cte([item code],price,discount,rngLow,rngHigh,id) as
(
select 'xxx-xxxxxxx' as [item code],l.t$pric,l.t$disc,lqanp=l.t$qanp,hqanp=h.t$qanp,id = row_number() over(partition by h.t$qanp order by h.t$qanp) from EdiCatalog l
join ediCatalog h on l.comno=h.comno and l.t$cpls=h.t$cpls and l.t$cuno=h.t$cuno and h.t$item=l.t$item and l.t$qanp < h.t$qanp
where l.comno=#comno and l.t$cpls=#cpls and l.t$cuno=#cuno
)
select * from cte
returning result set
How do I transform the result set to this

You can start with this:
SELECT *
FROM cte a
WHERE rngHigh=(
SELECT MIN(rngHigh)
FROM cte b
WHERE b.rngLow=a.rngLow
)
Which will give you this result set:
discount rngLow rngHigh
40 1 9
68 9 23
73 23 47
75 47 299

#Frazz,
Here is what I have now
with little alteration to what you suggested..
SELECT rngLow=case rngLow
when 1 then rnglow
else rnglow+1 end,rngHigh,discount,id
FROM cte a
WHERE rngHigh=(
SELECT MIN(rngHigh)
FROM cte b
WHERE b.rngLow=a.rngLow
)

Related

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 comparaison nested on one column

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.

Select certain rows with respect to their min max values

In T-SQL (MSSql 2008R2) I Would like to select certain rows from a table or set of results;
StoreId StoreName BrochureId PageId Rank Distance
43561 X 1627 11608 73 598.10
43561 X 1627 11591 68 598.10
43561 X 1627 11615 41 598.10
43827 Y 1727 21708 75 1414.69
43827 Y 1727 21591 62 1414.69
43827 Y 1727 21615 44 1414.69
43919 Z 1827 31809 77 2487.35
43919 Z 1827 31591 60 2487.35
43919 Z 1827 31615 39 2487.35
Would like to select only rows with lowest distance and with the highest rank, as such;
StoreId StoreName BrochureId PageId Rank Distance
43561 X 1627 11608 73 598.10
43827 Y 1727 21708 75 1414.69
43919 Z 1827 31809 77 2487.35
Thank you for your help.
You can use ROW_NUMBER for this.
SELECT * FROM (
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY StoreName ORDER BY Distance, [Rank] DESC) Rn
FROM
Table1
) t
WHERE Rn = 1
there are other ranking functions you can use as well.. for example, if you use RANK instead of ROW_NUMBER here, you can include ties in your result as well.
SQL Ranking Functions
Use a correlated subquery in your where clause... assuming your table name is MyTable, something like this should get what you want:
SELECT [StoreId], [StoreName], [BrochureId], [PageId], [Rank], [Distance]
FROM MyTable m
WHERE [Rank] = (SELECT MAX([Rank]) FROM MyTable x WHERE x.StoreId = m.StoreId)
OR [Distance] = (SELECT MIN([Distance]) FROM MyTable y WHERE y.StoreId = m.StoreId)
(note, I enclosed the column names in square brackets because "Rank" is a reserved SQL Keyword)

Find combination of values on same table with condition in SQL server

I have a table named PlayerScore that contains the player name and their average scores:
Id Name Average
1 Sakib 80
2 Tamim 70
3 Mushfiq 60
4 Sabbir 50
5 Ashraful 20
6 Aftab 40
7 Rubel 30
8 Kalu 10
I want to find their partnership combination based on a condition that,
palyer whose average score is greater than 40 can not be partner with players whose score is less than 40.
I tried the following query :
select a.Name,a.Average,b.Name,b.Average from ((select * from PlayerScore where Average<=40) as a inner join (select * from PlayerScore where Average<=40) as b on a.Id < b.Id)
union
select a.Name,a.Average,b.Name,b.Average from ((select * from PlayerScore where Average>=40) as a inner join (select * from PlayerScore where Average>=40) as b on a.Id < b.Id)
that results in :
Name Average Name Average
Aftab 40 Kalu 10
Aftab 40 Rubel 30
Ashraful 20 Aftab 40
Ashraful 20 Kalu 10
Ashraful 20 Rubel 30
Mushfiq 60 Aftab 40
Mushfiq 60 Sabbir 50
Rubel 30 Kalu 10
Sabbir 50 Aftab 40
Sakib 80 Aftab 40
Sakib 80 Mushfiq 60
Sakib 80 Sabbir 50
Sakib 80 Tamim 70
Tamim 70 Aftab 40
Tamim 70 Mushfiq 60
Tamim 70 Sabbir 50
Is their any solution without using UNION
select distinct a.Name,a.Average,b.Name,b.Average
from PlayerScore a
join PlayerScore b
on a.Id < b.Id
and ( a.Average<=40 and b.Average<=40
or a.Average>=40 and b.Average>=40
)
it will likely result in the same exceution plan.
Maybe you can do something like this:
SELECT
t.*,
t2.*
FROM
PlayerScore AS t
CROSS JOIN PlayerScore AS t2
WHERE t.Average>=40 AND t2.Average<40
ORDER BY t.Name
You can create 2 groups based on your condition and give them different values and then do a join based on the value. Something like this.
;WITH PlayerScore as
(
SELECT 1 AS Id,'Sakib' AS Name,80 AS Average
UNION ALL SELECT 2,'Tamim',70
UNION ALL SELECT 3,'Mushfiq',60
UNION ALL SELECT 4,'Sabbir',50
UNION ALL SELECT 5,'Ashraful',20
UNION ALL SELECT 6,'Aftab',40
UNION ALL SELECT 7,'Rubel',30
UNION ALL SELECT 8,'Kalu',10
),PlayerCriteria AS
(
SELECT *,CASE WHEN Average >= 40 THEN 1 ELSE 0 END joincondition
FROM PlayerScore
)
SELECT * FROM PlayerCriteria C1
INNER JOIN PlayerCriteria C2 ON C1.joincondition = C2.joincondition
AND C1.Id > C2.Id

Group by month in 6 month range sql server

I have a query that groups data by month, but there are some months that do not display simply because there is no data to display/group by. Is it possible to return the months and 0s for those months?
Here is my query
DECLARE #IMPORT_DATE AS DATETIME
SET #IMPORT_DATE = GETDATE()
SELECT COALESCE(COUNT(*),0), RIGHT(YEAR_MONTH_VALUE,2)
FROM VW_CALLS
WHERE CLIENT_ID = 2
AND START_DATETIME BETWEEN DATEADD("m", -5, #IMPORT_DATE) AND #IMPORT_DATE
GROUP BY YEAR_MONTH_VALUE
ORDER BY YEAR_MONTH_VALUE
And it returns this:
(No column name) (No column name)
740 11
1929 12
3864 01
But I would like this:
(No column name) (No column name)
0 08
0 09
0 10
740 11
1929 12
3864 01
You can use a recursive CTE like this:
DECLARE #MonthStart CHAR(2) = '08'
;WITH MonthsRange AS (
SELECT m = #MonthStart, rn = 1
UNION ALL
SELECT m = (CASE WHEN m = '12' THEN CAST('01' AS CHAR(2))
ELSE CAST(REPLICATE('0', 2 - LEN(CAST((CAST(m AS INT) + 1) AS CHAR(2)))) +
CAST((CAST(m AS INT) + 1) AS CHAR(2)) AS CHAR(2))
END),
rn = rn + 1
FROM MonthsRange
WHERE rn < 6
)
SELECT *
FROM MonthsRange
in order to get all months you want to include in your final result set:
m rn
=======
08 1
09 2
10 3
11 4
12 5
01 6
Now you can left join the above CTE against you query to get the result set you want:
;WITH MonthsRange AS (
... cte statements here
)
SELECT COALESCE(vw.cnt, 0) AS cnt, mr.m AS [Month]
FROM MonthsRange AS mr
LEFT JOIN (
SELECT COALESCE(COUNT(*),0) AS cnt, RIGHT(YEAR_MONTH_VALUE,2) AS m,
YEAR_MONTH_VALUE AS ym
FROM VW_CALLS
WHERE CLIENT_ID = 2 AND
START_DATETIME BETWEEN DATEADD("m", -5, #IMPORT_DATE) AND #IMPORT_DATE
GROUP BY YEAR_MONTH_VALUE) AS vw ON mr.m = vw.m
ORDER BY vw.ym

Resources