SQL Server Order by numeric varchar - sql-server

I have this query:
select cast(MONTH(c.dataregistro) as VARCHAR(2)) as DATA, numberofquotesreturned, count(*) AS CountNumberQuotes from contratacao c
inner join teleportpedidocotacao tpc on c .idcontratacao = tpc.idcontratacao
where cast(c.dataregistro as date) > '2014-01-01 00:00:00' AND numberofquotesreturned = 5
group by numberofquotesreturned, cast(MONTH(c.dataregistro) as VARCHAR(2))
order by cast(MONTH(c.dataregistro) as VARCHAR(2))
The results are:
10 5 347
2 5 112
3 5 914
4 5 786
5 5 1184
6 5 2361
7 5 2430
8 5 2184
9 5 2709
I want to order by the whole number on the first column and as you can see the '10' comes first. How can I fix it?

Just remove the cast to varchar:
select MONTH(c.dataregistro) as DATA, numberofquotesreturned, count(*) AS CountNumberQuotes from contratacao c
inner join teleportpedidocotacao tpc on c .idcontratacao = tpc.idcontratacao
where cast(c.dataregistro as date) > '2014-01-01 00:00:00' AND numberofquotesreturned = 5
group by numberofquotesreturned, MONTH(c.dataregistro)
order by MONTH(c.dataregistro)

Related

Finding A Time When A Value Changed

I am still learning many new things about SQL such as PARTITION BY and CTEs. I am currently working on a query which I have cobbled together from a similar question I found online. However, I can not seem to get it to work as intended.
The problem is as follows -- I have been tasked to show rank promotions in an organization from the begining of 2022 to today. I am working with 2 primary tables, an EMPLOYEES table and a PERIODS table. This periods table captures a snapshot of any given employee each month - including their rank at the time. Each of these months is also assigned a PeriodID (e.g. Jan 2022 = PeriodID 131). Our EMPLOYEE table holds the employees current rank. These ranks are stored as an int (e.g. 1,2,3 with 1 being lowest rank). It is possible for an employee to rank up more than once in any given month.
I have simplified the used query as much as I can for the sake of this problem. Query follows as:
;WITH x AS
(
SELECT
e.EmployeeID, p.PeriodID, p.RankID,
rn = ROW_NUMBER() OVER (PARTITION BY e.EmployeeID ORDER BY p.PeriodID DESC)
FROM employees e
LEFT JOIN periods p on p.EmployeeID= e.EmployeeID
WHERE p.PeriodID <= 131 AND p.PeriodID >=118 --This is the time range mentioned above
),
rest AS (SELECT * FROM x WHERE rn > 1)
SELECT
main.EmployeeID,
PeriodID = MIN(
CASE
WHEN main.CurrentRankID = Rest.RankID
THEN rest.PeriodID ELSE main.PeriodID
END),
main.RankID, rest.RankID
FROM x AS main LEFT OUTER JOIN rest ON main.EmployeeID = rest.EmployeeID
AND rest.rn >1
LEFT JOIN periods p on p.EmployeeID = e.EmployeeID
WHERE main.rn = 1
AND NOT EXISTS
(
SELECT 1 FROM rest AS rest2
WHERE EmployeeID = rest.EmployeeID
AND rn < rest.rn
AND main.RankID <> rest.RankID
)
and p.PeriodID <= 131 AND p.PeriodID >=118
GROUP BY main.EmployeeID, main.PeriodID, main.RankID, rest.RankID
As mentioned before, this query was borrowed from a similar question and modified for my own use. I imagine the bones of the query is good and maybe I have messed up a variable somewhere but I can not seem to locate the problem line. The end goal is for the query to result in a table showing the EmployeeID, PeriodID, the rank they are being promoted from, and the rank they are being promoted to in the month the promotion was earned. Similar to the below.
EmployeeID
PeriodID
PerviousRankID
NewRank
123
131
1
2
123
133
2
3
Instead, my query is spitting out repeating previous/current ranks and the PeriodIDs seem to be static (such as what is shown below).
EmployeeID
PeriodID
PerviousRankID
NewRank
123
131
1
1
123
131
1
1
I am hoping someone with a greater knowledge base on these functions is able to quickly notice my mistake.
If we assume some example DML/DDL (it's really helpful to provide this with your question):
DECLARE #Employees TABLE (EmployeeID INT IDENTITY, Name VARCHAR(20), RankID INT);
DECLARE #Periods TABLE (PeriodID INT, EmployeeID INT, RankID INT);
INSERT INTO #Employees (Name, RankID) VALUES ('Jonathan', 10),('Christopher', 10),('James', 10),('Jean-Luc', 8);
INSERT INTO #Periods (PeriodID, EmployeeID, RankID) VALUES
(1,1,1),(2,1,1),(3,1,1),(4,1,8 ),(5,1,10),(6,1,10),
(1,2,1),(2,2,1),(3,2,1),(4,2,8 ),(5,2,8 ),(6,2,10),
(1,3,1),(2,3,1),(3,3,7),(4,3,10),(5,3,10),(6,3,10),
(1,4,1),(2,4,1),(3,4,1),(4,4,8 ),(5,4,9 ),(6,4,9 )
Then we can accomplish what I think you're looking for using a OUTER APPLY then aggregates the values based on the current-row values:
SELECT e.EmployeeID, e.Name, e.RankID AS CurrentRank, ap.PeriodID AS ThisPeriod, p.PeriodID AS LastRankChangePeriodID, p.RankID AS LastRankChangedFrom, ap.RankID - p.RankID AS LastRankChanged
FROM #Employees e
LEFT OUTER JOIN #Periods ap
ON e.EmployeeID = ap.EmployeeID
OUTER APPLY (
SELECT EmployeeID, MAX(PeriodID) AS PeriodID
FROM #Periods
WHERE EmployeeID = e.EmployeeID
AND RankID <> ap.RankID
AND PeriodID < ap.PeriodID
GROUP BY EmployeeID
) a
LEFT OUTER JOIN #Periods p
ON a.EmployeeID = p.EmployeeID
AND a.PeriodID = p.PeriodID
ORDER BY e.EmployeeID, ap.PeriodID DESC
Using the correlated subquery we get a view of the data which we can filter using the current-row values, and we aggregate that to return the period we're looking for (where it's before this period, and it's not the same rank). Then it's just a join back to the Periods table to get the values.
You used an LEFT JOIN, so I've preserved that using an OUTER APPLY. If you wanted to filter using it, it would be a CROSS APPLY instead.
EmployeeID
Name
CurrentRank
ThisPeriod
LastRankChangePeriodID
LastRankChangedFrom
LastRankChanged
1
Jonathan
10
6
4
8
2
1
Jonathan
10
5
4
8
2
1
Jonathan
10
4
3
1
7
1
Jonathan
10
3
1
Jonathan
10
2
1
Jonathan
10
1
2
Christopher
10
6
5
8
2
2
Christopher
10
5
3
1
7
2
Christopher
10
4
3
1
7
2
Christopher
10
3
2
Christopher
10
2
2
Christopher
10
1
3
James
10
6
3
7
3
3
James
10
5
3
7
3
3
James
10
4
3
7
3
3
James
10
3
2
1
6
3
James
10
2
3
James
10
1
4
Jean-Luc
8
6
5
9
-1
4
Jean-Luc
8
5
4
8
1
4
Jean-Luc
8
4
3
1
7
4
Jean-Luc
8
3
4
Jean-Luc
8
2
4
Jean-Luc
8
1
Now we can see what the previous change looked like for each period. Currently Jonathan is has RankID 10. Last time that was different was in PeriodID 4 when it was 8. The same was true for PeriodID 5. In PeriodID 4 he had RankID 8, and prior to that he had RankID 1. Before that his Rank hadn't changed.
Jean-Luc was actually demoted as his last change. I don't know if this is possible within your model.

How to Group Items in a Count statement

I'm trying to create a query that will return Total Claims reported in 0-3 days, 4-7 days, 8-14 days, and 15+
Select DATEDiff(DD,LossDate,DateReported) As TimeToReport,Count(ClaimId) As Num from LossRun
where PolicyNum='1234567890'
And PolTerm='201403'
Group By DATEDiff(DD,LossDate,DateReported)
order by DATEDiff(DD,LossDate,DateReported);
This is what i get
TimeToReport NumofClaims
0 5
1 3
2 1
3 4
4 3
5 2
6 2
7 2
8 1
12 1
13 1
14 2
15 2
48 1
52 1
107 1
121 1
147 1
533 1
Basically i want to see the total for 0-3, 4-7,8-14,and the rest,,,, timeToReport
You can try to use SUM with CASW WHEN
select
SUM(CASW WHEN TimeToReport <= 3 THEN NumofClaims ELSE 0 END) '0~3 day',
SUM(CASW WHEN TimeToReport >= 4 AND TimeToReport <=7 THEN NumofClaims END) '4-7 days',
SUM(CASW WHEN TimeToReport >= 8 AND TimeToReport <=14 THEN NumofClaims ELSE 0 END) '8-14 days',
SUM(CASW WHEN TimeToReport >= 15 THEN NumofClaims ELSE 0 END) '15+ day'
from (
Select DATEDiff(DD,LossDate,DateReported) As TimeToReport,Count(ClaimId) As Num
from LossRun
where PolicyNum='1234567890'
And PolTerm='201403'
Group By DATEDiff(DD,LossDate,DateReported)
) t
The most simple way is going to be by creating your own temp table which includes the min and max for each bucket and then joining to it.
declare #t table (OrderedID int, EmpID int, EffDate date, Salary money)
insert into #t
values
(1,1234,'20150101',1)
,(2,1234,'20160101',2)
,(3,1234,'20170101',8)
,(4,1234,'20180101',15)
,(1,2351,'20150101',17)
,(5,1234,'20190101',4)
,(5,1234,'20190101',2)
,(5,1234,'20190101',9)
declare #Bin table (MinVal int, MaxVal int)
insert into #Bin
values
(1,3)
,(4,6)
,(7,9)
,(10,15)
,(15,20)
,(20,25)
Select
B.MinVal,count(T.EmpID) as EmpsInBin
From #t T
inner join #Bin B on T.Salary between B.MinVal and B.MaxVal
group by B.MinVal
Output
MinVal EmpsInBin
1 3
4 1
7 2
10 1
15 2

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

select data for every 5 minute interval

I have the following query which is giving me 24 hour interval data, I want to get the data for every 5 minute interval
IF OBJECT_ID('tempdb..#mytemptable') IS NOT NULL DROP TABLE #mytemptable;
SELECT AVG( CAST ([PT_100SCALED_VAL0] AS bigint) ) AS PT_100SCALED_VAL0_AVERAGE
INTO #MYTEMPTABLE FROM ( SELECT t1.[timestamp], t1.[PT_100SCALED_VAL0] FROM
[REPORT] t1 WHERE t1.[timestamp] >= '2016-01-06 00:00:00' and t1.[timestamp] <='2016-01-06 23:59:59' )
TEMPTABLE
SELECT t1.[timestamp], t1.[PT_100SCALED_VAL0] , RM_temp.* FROM [REPORT]
t1 , #MYTEMPTABLE RM_temp WHERE t1.[timestamp] >= '2016-01-06 00:00:00' and t1.[timestamp] <='2016-01-06 23:59:59' -- This select will give the final result.
my result for the above query is
timestamp PT_100SCALED_VAL0 PT_100SCALED_VAL0_AVERAGE
2016-01-06 13:31:27.807 1 5
2016-01-06 13:32:27.810 2 5
2016-01-06 13:33:27.817 3 5
2016-01-06 13:34:27.827 4 5
2016-01-06 13:35:27.827 5 5
2016-01-06 13:36:27.837 6 5
2016-01-06 13:37:27.840 7 5
2016-01-06 13:38:27.847 8 5
2016-01-06 13:39:27.850 9 5
my expected result is
timestamp PT_100SCALED_VAL0 PT_100SCALED_VAL0_AVERAGE
2016-01-06 13:35:27.827 5 5
2016-01-06 13:39:27.850 9 5
I have tried using with and partion by but i am getting error incorrect syntax near if
Declare #start datetime, #end datetime
Select #start=CAST(CAST(getdate() as date) as datetime), #end=CAST((DATEADD(DAY,1,#start)) as datetime)
;With DateTimeSequence( Dt ) as
(
Select #start as Stime
union all
Select DATEADD(MINUTE,5,Dt)
from DateTimeSequence
where Dt < DATEADD(MINUTE,-5,#end)
)select Dt as StartTime,DATEADD(MINUTE,5,Dt) as EndTime from DateTimeSequence
Option (MaxRecursion 1000)
Use the above CTE with your query and join with your query and use as desired.

Join/UNION ALL to show result in different columns

I am fetching COUNT from 3 different table based on some conditions but to group them on time interval. (Like: 1 hour, 30 minutes.)
I need the following output:
Date Interval Success Un-Success Closed CLInotFound
2/20/2016 01:01 – 02:00 5 3 2 13
2/20/2016 02:01 – 03:00 14 9 23 5
2/20/2016 03:01 – 04:00 8 67 89 345
2/20/2016 04:01 – 05:00 2 23 92 12
2/20/2016 05:01 – 06:00 44 55 78 98
2/20/2016 06:01 – 07:00 12 87 56 445
I am able to calculate them separately but when I am trying to combine the result gets different.
Query 1 For Success & Un-Success:
SELECT CONVERT(VARCHAR(5), A.InsertionDate ,108) AS 'Interval',
COUNT(CASE WHEN A.call_result = 0 then 1 ELSE NULL END) AS 'Success',
COUNT(CASE WHEN A.call_result = 1 then 1 ELSE NULL END) AS 'Un-Success'
from dbo.AutoRectifier A
WHERE CONVERT(DateTime,A.InsertionDate,101) BETWEEN '2016-02-19 02:10:35.000' AND '2016-02-19 07:15:35.000'
GROUP BY A.InsertionDate;
Query 2 For Closed:
SELECT CONVERT(VARCHAR(5), C.DateAdded ,108) AS 'Interval',
COUNT(*) AS 'Closed' FROM dbo.ChangeTicketState C
WHERE C.SourceFlag = 'S-CNR' AND C.RET LIKE '%CLOSE%'
AND C.DateAdded BETWEEN '2016-02-19 02:10:35.000' AND '2016-02-19 07:15:35.000'
GROUP BY C.DateAdded;
Query 3 For CLI Not Found:
SELECT CONVERT(VARCHAR(5), T.DateAdded ,108) AS 'Interval',
COUNT(*) 'CLI Not Found' FROM dbo.TICKET_INFO T
WHERE T.CONTACT_NUMBER = '' AND T.DateAdded BETWEEN '2016-02-19 02:10:35.000' AND '2016-02-19 07:15:35.000'
GROUP BY T.DateAdded;
You have got several problems to solve in you question.
You have to produce a union result set from Query1, Query2, Query3 to group it. You can use UNION ALL for it but all 3 queries must have similar column list for it. So, add
0 as Closed, 0 as CLInotFound
to select-list of the Query1,
add
0 as Success, 0 as Un-Success, 0 as CLInotFound
to select-list of the Query2 and add
0 as Success, 0 as Un-Success, 0 as Closed
to Query3
Then you can write
select * from Query1
union all
select * from Query2
union all
select * from Query3
Don't convert date to varchar at Query1, Query2, Query3. Better return datetime from query to use it for grouping after union. So, query 1 will look like
SELECT A.InsertionDate AS Date, ...
Query2 -
SELECT C.DateAdded AS Date, ...
etc.
Then you can group results on per-hour basis, for instance using GROUP BY SUBSTRING(CONVERT(VARCHAR(20), Date ,120), 1, 13)
So, the result will look like
SELECT SUBSTRING(CONVERT(VARCHAR(20), Date ,120), 1, 13) as Interval,
sum(Success) as
sum(Un-Success) as,
sum(Closed) as,
sum(CLInotFound) as
from (
select * from Query1
union all
select * from Query2
union all
select * from Query3
) q
GROUP BY SUBSTRING(CONVERT(VARCHAR(20), Date ,120), 1, 13)
Its result have slightly different format of Date and Interval field, but shows the idea.
You can use GROUP BY DATEPART(yy, Date), DATEPART(mm, Date), DATEPART(dd, Date), DATEPART(hh, Date) instead of GROUP BY SUBSTRING(CONVERT(VARCHAR(20), Date ,120), 1, 13) and format if as you wish.
Also result set does not contain intervals that not present at original data.
You can add Query4, containing all intervals required and zeros at all fields to fix it.

Resources