Aggregate SQL formula - sql-server

I want to get SUM divided by COUNT in a column
Select date,SUM(OOC) OOC,SUM(SUM_CVOICE) SUM, Count(SUM_CVOICE) Cnt,SUM(SUM_CVOICE)/Count(SUM_CVOICE) CVOICE
from #Temp
group by date
order by date
date OOC SUM Cnt CVOICE
03/01/2017 1569 19445 11252 1
04/01/2017 235 8299 4842 1
05/01/2017 154 11851 6361 1
It doesn't gives exact decimal values.

SQL Server does integer arithmetic. So, the simple answer is to convert your value to a non-integer in some way. A simple way is to multiply by * 1.0:
Select date, SUM(OOC) as OOC, SUM(SUM_CVOICE) as SUM,
Count(SUM_CVOICE) as cnt,
SUM(SUM_CVOICE)*1.0 / Count(SUM_CVOICE) as CVOICE
from #Temp
group by date
order by date;
However, what you are calculating is called the average. So, this is simpler:
Select date, sum(OOC) as OOC, sum(SUM_CVOICE) as SUM,
Count(SUM_CVOICE) as cnt,
avg(SUM_CVOICE * 1.0) as CVOICE
from #Temp
group by date
order by date;
Note that SQL Server also uses integer arithmetic for the average, so you need to convert to a non-integer as well.

disvision is tricky in SQL Server, you need to do implicit conversion, otherwise / will take the integer part of the result.
Select date,
SUM(OOC) as OOC,
SUM(SUM_CVOICE) as SUM,
Count(SUM_CVOICE) as Cnt,
SUM(SUM_CVOICE)*1.0/Count(SUM_CVOICE) as CVOICE
from #Temp
group by date
order by date

Related

SQL Server - Average a column that sometimes has numbers and sometimes strings

I'm working with a column that was poorly set up as nvarchar, it sometimes has a number, which I want to average, and sometimes letters, nulls, or an empty string value. How can I get an average of all of the numeric values that are greater than 0 for this column?
Side question: If I want to fix the column, what's the best way to do it without losing any of the numeric values?
Not bullet proof but should work:
WITH cte AS (
SELECT *
FROM your_tab
WHERE ISNUMERIC(col) = 1
)
SELECT AVG(CAST(col AS DECIMAL(18,2))) AS average
FROM cte
WHERE CAST(col AS DECIMAL(18,2)) > 0;
SQL Server 2012+ has great TRY_CAST function:
SELECT AVG(casted_col) AS average
FROM (
SELECT TRY_CAST(col AS DECIMAL(18,2)) AS casted -- NULL if cannot cast
FROM your_tab
) sub
WHERE casted_col > 0;

Rounding SQL server Select within bigger query

I am trying to round off a result in a query (1 column out of many)
Select * (DocTotal - Vat) * Rate AS TOTAL, ItemCode, Qty
From
ORDR
above is my select statement (from within a bigger view)
Doctotal is a decimal 20,2
Vat is a decimal 20,2
and
Rate is decimal 2,4
(The other columns aren't in question)
this returns a result as TOTAL with 6 decimal places, eg 353.690000
is it possible to round the result that is calculated to 2 decimal places.
Casting it to a decimal will implicitly round the resulting float value from the calculation.
select *,
cast((DocTotal - Vat) * Rate as decimal(20,2)) AS TOTAL,
ItemCode, Qty
from ORDR
So first rounding it with ROUND isn't actually required.
Or you could use CONVERT instead, if you prefere that syntax.
Works the same way in this case. But it's specific to SQL Server.
While CAST is a standard feature (SQL-92).
convert(decimal(20,2), (DocTotal - Vat) * Rate) AS TOTAL

T-SQL - Get last as-at date SUM(Quantity) was not negative

I am trying to find a way to get the last date by location and product a sum was positive. The only way i can think to do it is with a cursor, and if that's the case I may as well just do it in code. Before i go down that route, i was hoping someone may have a better idea?
Table:
Product, Date, Location, Quantity
The scenario is; I find the quantity by location and product at a particular date, if it is negative i need to get the sum and date when the group was last positive.
select
Product,
Location,
SUM(Quantity) Qty,
SUM(Value) Value
from
ProductTransactions PT
where
Date <= #AsAtDate
group by
Product,
Location
i am looking for the last date where the sum of the transactions previous to and including it are positive
Based on your revised question and your comment, here another solution I hope answers your question.
select Product, Location, max(Date) as Date
from (
select a.Product, a.Location, a.Date from ProductTransactions as a
join ProductTransactions as b
on a.Product = b.Product and a.Location = b.Location
where b.Date <= a.Date
group by a.Product, a.Location, a.Date
having sum(b.Value) >= 0
) as T
group by Product, Location
The subquery (table T) produces a list of {product, location, date} rows for which the sum of the values prior (and inclusive) is positive. From that set, we select the last date for each {product, location} pair.
This can be done in a set based way using windowed aggregates in order to construct the running total. Depending on the number of rows in the table this could be a bit slow but you can't really limit the time range going backwards as the last positive date is an unknown quantity.
I've used a CTE for convenience to construct the aggregated data set but converting that to a temp table should be faster. (CTEs get executed each time they are called whereas a temp table will only execute once.)
The basic theory is to construct the running totals for all of the previous days using the OVER clause to partition and order the SUM aggregates. This data set is then used and filtered to the expected date. When a row in that table has a quantity less than zero it is joined back to the aggregate data set for all previous days for that product and location where the quantity was greater than zero.
Since this may return multiple positive date rows the ROW_NUMBER() function is used to order the rows based on the date of the positive quantity day. This is done in descending order so that row number 1 is the most recent positive day. It isn't possible to use a simple MIN() here because the MIN([Date]) may not correspond to the MIN(Quantity).
WITH x AS (
SELECT [Date],
Product,
[Location],
SUM(Quantity) OVER (PARTITION BY Product, [Location] ORDER BY [Date] ASC) AS Quantity,
SUM([Value]) OVER(PARTITION BY Product, [Location] ORDER BY [Date] ASC) AS [Value]
FROM ProductTransactions
WHERE [Date] <= #AsAtDate
)
SELECT [Date], Product, [Location], Quantity, [Value], Positive_date, Positive_date_quantity
FROM (
SELECT x1.[Date], x1.Product, x1.[Location], x1.Quantity, x1.[Value],
x2.[Date] AS Positive_date, x2.[Quantity] AS Positive_date_quantity,
ROW_NUMBER() OVER (PARTITION BY x1.Product, x1.[Location] ORDER BY x2.[Date] DESC) AS Positive_date_row
FROM x AS x1
LEFT JOIN x AS x2 ON x1.Product=x2.Product AND x1.[Location]=x2.[Location]
AND x2.[Date]<x1.[Date] AND x1.Quantity<0 AND x2.Quantity>0
WHERE x1.[Date] = #AsAtDate
) AS y
WHERE Positive_date_row=1
Do you mean that you want to get the last date of positive quantity come to positive in group?
For example, If you are using SQL Server 2012+:
In following scenario, when the date going to 01/03/2017 the summary of quantity come to 1(-10+5+6).
Is it possible the quantity of following date come to negative again?
;WITH tb(Product, Location,[Date],Quantity) AS(
SELECT 'A','B',CONVERT(DATETIME,'01/01/2017'),-10 UNION ALL
SELECT 'A','B','01/02/2017',5 UNION ALL
SELECT 'A','B','01/03/2017',6 UNION ALL
SELECT 'A','B','01/04/2017',2
)
SELECT t.Product,t.Location,SUM(t.Quantity) AS Qty,MIN(CASE WHEN t.CurrentSum>0 THEN t.Date ELSE NULL END ) AS LastPositiveDate
FROM (
SELECT *,SUM(tb.Quantity)OVER(ORDER BY [Date]) AS CurrentSum FROM tb
) AS t GROUP BY t.Product,t.Location
Product Location Qty LastPositiveDate
------- -------- ----------- -----------------------
A B 3 2017-01-03 00:00:00.000

Having clause not working as expected

I have following statement
select pkid
from AttendancePosting
where datename(dw,AttDate) = 'Sunday' and empid=4 and attdate='2015-12-13'
group by PKId,timeout
--having 9=9
having cast(sum((datepart(minute, timeout)))/2 as float )+''=cast(datepart(minute,timeout) as float) +''
The problem is
having cast(sum((datepart(minute, timeout)))/2 as float )+''=cast(datepart(minute,timeout) as float) +''
Not working. both cast(sum((datepart(minute, timeout)))/2 as float ) and cast(datepart(minute,timeout) as float) bring the same value but still the select statement is not fetching any records, both returns 9
I have checked it like this
select pkid
from AttendancePosting
where datename(dw,AttDate) = 'Sunday' and empid=4 and attdate='2015-12-13'
group by PKId,timeout
having 9=9
And its bringing records, Any help will be appreciated.
First, your statement datepart(minute, timeout)/2 is going to return an integer. You can make SQL Server get more precise by being more precise like this datepart(minute, timeout)/2..
Second, floating point numbers are an approximation. You would do better to use ROUND() and specify the number of decimal places you think is appropriate. For example: round(sum((datepart(minute, timeout)))/2.0, 3).

SQL Filtering A Result Set To Return A Maximum Amount Of Rows At Even Intervals

I currently use SQL2008 where I have a stored procedure that fetches data from a table that then gets fed in to a line graph on the client. This procedure takes a from date and a too date as parameters to filter the data. This works fine for small datasets but the graph gets a bit muddled when a large date range is entered causes thousends of results.
What I'd like to do is provide a max amount of records to be returned and return records at evenly spaced intervals to give that amount. For example say I limited it to 10 records and the result set was 100 records I'd like the stored procedure to return every 10th record.
Is this possible wihtout suffering big performance issues and what would be the best way to achieve it? I'm struggling to find a way to do it without cursors and if thats the case I'd rather not do it at all.
Thanks
Assuming you use at least SQL2005, you could do somesting like
WITH p as (
SELECT a, b,
row_number() OVER(ORDER BY time_column) as row_no,
count() OVER() as total_count
FROM myTable
WHERE <date is in range>
)
SELECT a, b
FROM p
WHERE row_no % (total_cnt / 10) = 1
The where condition in the bottom calculates the modulus of the row number by the total number of records divided by the required number of final records.
If you want to use the average instead of one specific value, you would extend this as follows:
WITH p as (
SELECT a, b,
row_number() OVER(ORDER BY time_column) as row_no,
count() OVER() as total_count
FROM myTable
WHERE <date is in range>
),
a as (
SELECT a, b, row_no, total_count,
avg(a) OVER(partition by row_no / (total_cnt / 10)) as avg_a
FROM p
)
SELECT a, b, avg_a
FROM a
WHERE row_no % (total_cnt / 10) = 1
The formula to select one of the values in the final WHERE clause is used with the % replaced by / in the partition by clause.

Resources