SQL Rounding integers with no decimals - sql

I have what seems like a simple problem. I just want to show percentages with no decimal places and have the total add to 100. Here's a little snipit:
create table genderTable
(person varchar(10),
isMale varchar(5))
insert into genderTable values ('Mary', 'false')
insert into genderTable values ('Frank', 'true')
insert into genderTable values ('Bill', 'true')
insert into genderTable values ('Jessie', 'false')
insert into genderTable values ('Sue', 'false')
insert into genderTable values ('Beth', 'false')
insert into genderTable values ('Kris', 'false')
declare #total as int
set #total = 7
select
CASE isMale
WHEN 'True' THEN 'Male'
ELSE 'Female'
END as Gender,
CASE
WHEN #total > 0 THEN ROUND((count(isMale) * 100 / #total), 0)
ELSE 0
END as GenderPercent
from genderTable
group by isMale
The totals add to 99% instead of 100%. I've tried various rounding, but I either get one decimal or 99%. Any help? Please keep in mind, in another example, I have to do the same for ethnicity where there are more than two values, so subtracting from 100 probably won't work...

For you particular problem, I get numbers that add up to 100 when I do:
select isMale, count(*), sum(count(*)) over (),
round(100.0 * count(*) / sum(count(*)) over (), 0)
from genderTable t
group by isMale;
The actual problem with your implementation is that SQL Server does integer arithmetic. So, the expression ROUND((count(isMale) * 100 / #total), 0) is doing the calculation with integer division -- taking the floor() of the ratio before implementing the round().
There are ways to do what you want. They are more easily implemented in SQL Server 2012+ than in earlier versions:
select isMale,
round(100.0 * cnt / tot, 0) as p,
(case when seqnum = 1
then 100 - sum(round(100.0 * cnt / tot, 0)) over (order by seqnum desc rows between unbounded preceding and 1 preceding)
else round(100.0 * cnt / tot, 0)
end) as p_tot_100
from (select isMale, count(*)*1.0 as cnt, sum(1.0*count(*)) over () as tot,
row_number() over (order by isMale) as seqnum
from genderTable t
group by isMale
) t;
The idea is to sum the rounded versions of all other rows and subtract this from 100 for one of the rows.
EDIT:
The "integer" versions of these:
select isMale, count(*), sum(count(*)) over (),
cast(round(100.0 * count(*) / sum(count(*)) over (), 0) as int)
from genderTable t
group by isMale;
and:
select isMale,
round(100.0 * cnt / tot, 0) as p,
cast((case when seqnum = 1
then 100 - sum(round(100.0 * cnt / tot, 0)) over (order by seqnum desc rows between unbounded preceding and 1 preceding)
else round(100.0 * cnt / tot, 0)
end) as int) as p_tot_100
from (select isMale, count(*)*1.0 as cnt, sum(1.0*count(*)) over () as tot,
row_number() over (order by isMale) as seqnum
from genderTable t
group by isMale
) t;
I'm pretty sure that small integers are represented exactly, even with floating point representation, so there will be no problem of round() producing a value such as 29.99999999999997 instead of 30.

I just saw the answer above. Nice work Gordon!
Here is a similar version of the answer:
SELECT CASE isMale
WHEN 'True'
THEN 'Male'
ELSE 'Female'
END AS Gender
,CAST(left(round(count(isMale) * 100.00 /(select count(*) from gendertable), 0),2) AS VARCHAR) + '%' AS GenderPercent
FROM genderTable
GROUP BY isMale

Related

Use of count in where statement sql

I have N transactions with camera_id = 6 and i want to sample every N // 100 transaction.
I have the following query:
SELECT t.id from (
SELECT id, camera_id, start_ts, ROW_NUMBER() OVER (ORDER BY start_ts) AS rownum
FROM transactions
WHERE camera_id = 6
) as t
where t.rownum % (N / 100) = 1
order by t.start_ts
How can i change it so i don't need additional query for determining N?
Untested
Does the following work for you - add a windowed count in addition to your Rownumber and use that:
SELECT t.id from (
SELECT id, camera_id, start_ts,
Row_Number() OVER (ORDER BY start_ts) AS rownum,
Count(*) over() Qty
FROM transactions
WHERE camera_id = 6
) as t
where t.rownum % (Qty / 100) = 1
order by t.start_ts

How to output different 25th, 50th, 75th percentiles in single Teradata query?

I had got stuck few hours back on around something similar and worked out a less messy code for outputting 25th, 50th, 75th percentiles in a single Teradata query. Can be further extended to produce a "5 point summary". For minimum and maximum change static values according to your population estimate.
Somewhere someone had asked for an elegant approach. Sharing mine.
Here's the code:
SELECT MAX(PER_MIN) AS PER_MIN,
MAX(PER_25) AS PER_25,
MAX(PER_50) AS PER_50,
MAX(PER_75) AS PER_75,
MAX(PER_MAX) AS PER_MAX
FROM (SELECT CASE WHEN ROW_NUMBER() OVER(ORDER BY DURATION_MACRO_CURR ASC) = CAST(COUNT(*) OVER() * 0.01 AS INT) THEN DURATION_MACRO_CURR END AS PER_MIN,
CASE WHEN ROW_NUMBER() OVER(ORDER BY DURATION_MACRO_CURR ASC) = CAST(COUNT(*) OVER() * 0.25 AS INT) THEN DURATION_MACRO_CURR END AS PER_25,
CASE WHEN ROW_NUMBER() OVER(ORDER BY DURATION_MACRO_CURR ASC) = CAST(COUNT(*) OVER() * 0.50 AS INT) THEN DURATION_MACRO_CURR END AS PER_50
CASE WHEN ROW_NUMBER() OVER(ORDER BY DURATION_MACRO_CURR ASC) = CAST(COUNT(*) OVER() * 0.75 AS INT) THEN DURATION_MACRO_CURR END AS PER_75
CASE WHEN ROW_NUMBER() OVER(ORDER BY DURATION_MACRO_CURR ASC) = CAST(COUNT(*) OVER() * 0.99 AS INT) THEN DURATION_MACRO_CURR END AS PER_MAX
FROM PROD_EXP_DL_CVM.PROD_CVM
WHERE PW_END_DATE = '2016-10-18'
) BASE
Here's the desired output:
I would do this using conditional aggregation:
select min(DURATION_MACRO_CURR) as min_val,
min(case when seqnum / 0.25 >= cnt then DURATION_MACRO_CURR end) as 25_percentile,
min(case when seqnum / 0.50 >= cnt then DURATION_MACRO_CURR end) as 50_percentile,
min(case when seqnum / 0.75 >= cnt then DURATION_MACRO_CURR end) as 75_percentile,
max(DURATION_MACRO_CURR) as max_val
from (select pc.*,
row_number() over (order by DURATION_MACRO_CURR) as seqnum,
count(*) over () as cnt
from PROD_EXP_DL_CVM.PROD_CVM pc
where pc.PW_END_DATE = '2016-10-18'
) pc;

Get previous row updated value using LAG Without using Recursive CTE

How to use LAG function to get the updated previous row value (without using Recursive CTE). Please check the screenshot for sample output
Query Tried
Declare #Tbl as Table(SNO Int,Credit Money,Debit Money,PaidDate Date)
Insert into #Tbl
SELECT * FROM (VALUES (1,0,12,'7Jan16'), (2,10,0,'6Jan16'), (3,15,0,'5Jan16'), (4,0,5,'4Jan16'), (5,0,3,'3Jan16'), (6,0,2,'2Jan16'), (7,20,0,'1Jan16')) AS X(SNO,Credit,Debit,PaidDate)
Select
T.SNO,
T.Credit,
T.Debit,
TotalDebit = Case When Credit < LAG(T.Debit, 1, 0) OVER (ORDER BY SNO) Then Debit + (LAG(T.Debit, 1, 0) OVER (ORDER BY SNO)-Credit) Else Debit End,
Amount = Case When Credit < LAG(T.Debit, 1, 0) OVER (ORDER BY SNO) Then 0 Else Credit-LAG(T.Debit, 1, 0) OVER (ORDER BY SNO) End,
T.PaidDate
From #Tbl T
UPDATE:
Can get the expected result using recursive CTE, but when i convert the query to function and when i join the function with 3000 record, takes long time to execute. That's why i am trying to convert the query without recursive CTE part.
Recursive CTE Query:
Declare #Tbl as Table(SNO Int,Credit Money,Debit Money,PaidDate Date)
Insert into #Tbl
SELECT * FROM (VALUES (1,0,12,'7Jan16'), (2,10,0,'6Jan16'), (3,15,0,'5Jan16'), (4,0,5,'4Jan16'), (5,0,3,'3Jan16'), (6,0,2,'2Jan16'), (7,20,0,'1Jan16')) AS X(SNO,Credit,Debit,PaidDate)
;With Temp As(/* Detect Debited amount */
Select Top 1 SNO,Credit,Debit,Debit As TotalDebit,Credit As Amount,PaidDate From #Tbl
Union All
Select
R.SNO,
R.Credit,
R.Debit,
TotalDebit = Case When R.Credit < RP.TotalDebit Then R.Debit + (RP.TotalDebit-R.Credit) Else R.Debit End,
Amount = Case When R.Credit < RP.TotalDebit Then 0 Else R.Credit-RP.TotalDebit End,
R.PaidDate
From #Tbl R
Inner Join Temp RP ON R.SNO-1=RP.SNO
)
Select * From Temp
Spreadsheet sample:
https://docs.google.com/spreadsheets/d/1FNwzgGxmLiLFS_R5QANnfd16Iw64xhF0gWTc4ZocKsk/edit?usp=sharing
Performance here is suffering from recursive CTE. CTE on it's own is just syntactic sugar.
Just for this particular sample data this works without recursion:
Declare #Tbl as Table(SNO Int,Credit Money,Debit Money,PaidDate Date)
Insert into #Tbl
SELECT * FROM (VALUES (1,0,12,'7Jan16'), (2,10,0,'6Jan16'), (3,15,0,'5Jan16'), (4,0,5,'4Jan16'), (5,0,3,'3Jan16'), (6,0,2,'2Jan16'), (7,20,0,'1Jan16')) AS X(SNO,Credit,Debit,PaidDate);
With CTE1 As (
Select *
, CASE WHEN Credit > 0 THEN LEAD(1 - SIGN(Credit), 1, 1) OVER (ORDER BY SNO) ELSE 0 END As LastCrPerBlock
From #Tbl
), CTE2 As (
Select *
, SUM(LastCrPerBlock) OVER (ORDER BY SNO DESC ROWS UNBOUNDED PRECEDING) As BlockNumber
From CTE1
), CTE3 As (
Select *
, SUM(Credit - Debit) OVER (PARTITION BY BlockNumber) As BlockTotal
, SUM(Credit - Debit) OVER (PARTITION BY BlockNumber ORDER BY SNO ROWS UNBOUNDED PRECEDING) As BlockRunningTotal
From CTE2
)
Select SNO, Credit, Debit
, CASE WHEN BlockRunningTotal < 0 THEN -BlockRunningTotal ELSE 0 END As TotalDebit
, CASE WHEN BlockRunningTotal > 0 THEN CASE WHEN Credit < BlockRunningTotal THEN Credit ELSE BlockRunningTotal END ELSE 0 END As Amount
, PaidDate
From CTE3
Order By SNO;
This can help evaluate performance, but it will fail if in any block total of Debits exceed total of Credits. If BlockTotal is negative then it must be merged with one or several following blocks and that can't be done without iteration or recursion.
In real life I would dump CTE3 into temporary table and cycle over it merging blocks until there are no more negative BlockTotals.
From Y.B's answer, added recursive CTE to handle if any BlockTotal have negative. Cannot use while loop for recursion because i converted this query to inline table valued function.(Multi-statement table valued function is very slow)
Declare #Tbl as Table(ReceiptNo varchar(50),Credit Money,Debit Money,PaidDate Date)
Insert into #Tbl
SELECT * FROM (VALUES ('R1',20,0,'1Jan16'),('R2',0,2,'2Jan16'),('R3',0,3,'3Jan16'),('R4',0,5,'4Jan16'),('R5',10,0,'5Jan16'),('R6',0,1,'6Jan16'),('R7',0,10,'7Jan16')) AS X(ReceiptNo,Credit,Debit,PaidDate);
With Receipts As (
Select
SNO = ROW_NUMBER() OVER(ORDER BY PaidDate Desc),ReceiptNo,Credit,Debit,PaidDate,
LastCrPerBlock = CASE WHEN Credit > 0 THEN LEAD(1 - SIGN(Credit), 1, 1) OVER (ORDER BY PaidDate DESC) ELSE 0 END
From #Tbl
), Blocks As (
Select *
, SUM(LastCrPerBlock) OVER (ORDER BY SNO DESC ROWS UNBOUNDED PRECEDING) As BlockNumber
From Receipts
), BlockTotal As (
Select *
, SUM(Credit - Debit) OVER (PARTITION BY BlockNumber) As BlockTotal
, SUM(Credit - Debit) OVER (PARTITION BY BlockNumber ORDER BY SNO ROWS UNBOUNDED PRECEDING) As BlockRunningTotal
From Blocks
),
ReceiptAmount As (
Select ReceiptNo,
Amount = CASE WHEN BlockRunningTotal > 0 THEN CASE WHEN Credit < BlockRunningTotal THEN Credit ELSE BlockRunningTotal END ELSE 0 END,
Debit = IIF(BlockNumber<>LEAD(BlockNumber) OVER(ORDER BY SNO) and BlockRunningTotal<0,ABS(BlockRunningTotal),0),
PaidDate
From BlockTotal
),
FinalReceipt2012 As (
Select
SNO = ROW_NUMBER() OVER(ORDER BY PaidDate Desc),ReceiptNo,Amount,Debit,PaidDate,
Recur = IIF(Exists(Select Top 1 R1.Amount From ReceiptAmount R1 Where Debit>0),1,0)
From ReceiptAmount
Where Amount>0 or Debit>0
),
FinalReceipt As (
Select * From FinalReceipt2012 Where Recur=0 OR SNO=1
Union All
Select
R.SNO,R.ReceiptNo,
Amount = Case When R.Amount < RP.Debit Then 0 Else R.Amount-RP.Debit End,
Debit = Case When R.Amount < RP.Debit Then R.Debit + (RP.Debit-R.Amount) Else R.Debit End,
R.PaidDate,0 As Recur
From FinalReceipt2012 R
Inner Join FinalReceipt RP ON R.SNO=RP.SNO+1
Where R.Recur=1
)
Select ReceiptNo,Amount,PaidDate From FinalReceipt Where Amount>0
Input:
Output:

Golf Scoring SQL Query

I have the following MS SQL query which cross references three tables, (tTeam, tPlayer and tScores) to get the total "Net score", "Gross score" and "Position" ordered by Net score and Team.
SELECT TeamID, Team, NetScore, Gross,
CASE WHEN cnt > 1 THEN 'T' + CAST(rnk AS VARCHAR(5))
ELSE CAST(rnk AS VARCHAR(5))
END Pos
FROM (
SELECT tTeam.TeamID,
tTeam.Title AS Team,
SUM(CONVERT(INT, tScores.Net_Score)) AS NetScore,
SUM(CONVERT(INT, tScores.Out_Score) + CONVERT(int, tScores.In_Score)) AS Gross,
rank() OVER ( ORDER BY SUM(CONVERT(INT, tScores.Net_Score))) rnk,
COUNT(*) OVER ( PARTITION BY SUM(CONVERT(INT, tScores.Net_Score))) cnt
FROM tScores INNER JOIN tPlayer ON tScores.PlayerID = tPlayer.PlayerID INNER JOIN tTeam ON tPlayer.TeamID = tTeam.TeamID
WHERE tTeam.TournamentID = 13
GROUP BY tTeam.TeamID, tTeam.Title ) temp
ORDER BY NetScore, Team
The query works great but (and here is where i need some help), it is calculating all of the players Net and Gross scores by team when all I need it to do is calculate the "4 lowest Player's Net and Gross Scores" by team only.
I have spent the last day and a half pulling my hair out with this one and any help will be greatly appreciated.
Thanks in advance!
If I understood correctly that you want to sum four lowest scores per player only, you might use another set of row_numbers to isolate lowest scores. I don't think that rn_gross is necessary (based on rank() function) but I included it nevertheless. If there is no need for separate numbering remove conditionals from sums and add and lowestScores.rn_net <= 4 to where clause.
; with lowestScores as
(
select *,
ROW_NUMBER() over (PARTITION by PlayerID
order by CONVERT(INT, Net_Score)) rn_net,
ROW_NUMBER() over (PARTITION by PlayerID
order by CONVERT(INT, Net_Score) + CONVERT(int,In_Score)) rn_gross
from tScores
),
temp as
(
SELECT tTeam.TeamID,
tTeam.Title AS Team,
SUM(CASE WHEN rn_net <= 4 THEN CONVERT(INT, lowestScores.Net_Score) END) AS NetScore,
SUM(CASE WHEN rn_gross <= 4 THEN CONVERT(INT, lowestScores.Out_Score) END
+ CASE WHEN rn_gross <= 4 THEN CONVERT(int, lowestScores.In_Score) END) AS Gross,
rank() OVER ( ORDER BY SUM(CASE WHEN rn_net <= 4 THEN CONVERT(INT, lowestScores.Net_Score) END)) rnk,
COUNT(*) OVER ( PARTITION BY SUM(CASE WHEN rn_net <= 4 THEN CONVERT(INT, lowestScores.Net_Score) END)) cnt
FROM lowestScores
INNER JOIN tPlayer
ON lowestScores.PlayerID = tPlayer.PlayerID
INNER JOIN tTeam
ON tPlayer.TeamID = tTeam.TeamID
WHERE tTeam.TournamentID = 13
GROUP BY tTeam.TeamID, tTeam.Title
)
SELECT TeamID, Team, NetScore, Gross,
CASE WHEN cnt > 1
THEN 'T' + CAST(rnk AS VARCHAR(5))
ELSE CAST(rnk AS VARCHAR(5))
END Pos
FROM temp
ORDER BY NetScore, Team

insert 0 into successive row with same fields value

I have a table containing the following line:
I'd like to create a view that displays the following result (without changing my original table) :
For each line having the same id,day,month and year I'd like to leave a single line with the cost and count and insert 0 in the others.
Here is a portable approach not requiring PARTITION. I have assumed you will not have the same datetimeIN value for more than one row in a group:
select t.id, t.day, t.month, t.year,
case when tm.id is null then 0 else t.cost end as cost,
case when tm.id is null then 0 else t.Count end as Count,
t.datetimeIN, t.datetimeOUT
from MyTable t
left outer join (
select id, day, month, year, min(datetimeIN) as minIN
from MyTable
group by id, day, month, year
) tm on t.id = tm.id
and t.day = tm.day
and t.month = tm.month
and t.year = tm.year
and t.datetimeIN = tm.minIN
You can do something like this:
SELECT id, day, month, year,
CASE WHEN nNum = 1 then cost else 0 end as cost,
CASE WHEN nNum = 1 then "Count" else 0 end as "Count",
datetimeIN, datetimeOUT
FROM (
SELECT id, day, month, year,
cost, "Count", datetimeIN, datetimeOUT,
row_number() OVER (PARTITION BY id, day, month, year
ORDER BY datetimeIN) as nNum
FROM TableName
) A
It uses row_number() to number the rows, and then a CASE statement to single out the first one and make it behave differently.
See it working on SQL Fiddle here.
or, using a common table expression:
with commonTableExp ([day], [month], [year], minDate) as (
select [day], [month], [year], min(datetimeIn)
from #temp
group by [day], [month], [year])
select id,
dt.[day],
dt.[month],
dt.[year],
case when datetimein = minDate then [cost] else 0 end,
case when datetimein = minDate then [count] else 0 end,
dateTimeIn
from #temp dt join commonTableExp cte on
dt.[day] = cte.[day] and
dt.[month] = cte.[month] and
dt.[year] = cte.[year]
order by dateTimeIn
Query
Select id, [day], [month], [year], Case When K.RowID = 1 Then [cost] Else 0 End as Cost, Case When K.RowID = 1 Then [count] Else 0 End as [count], [DateTimeIN], [DateTimeOut] From
(
select ROW_NUMBER() Over(Partition by id, [day], [month], [year] Order by ID ) as RowID, * From Testing
)K
Drop table Testing
Click here to see SQL Profiler details for Red Filter's Query
Click here to see SQL Profiler details for my Query
For More Information You can see SQL Fiddle