This question already has answers here:
SQL Server, division returns zero
(6 answers)
Closed 4 years ago.
I have a table consisting of databases, tables, variables, values and the frequency count. I want to query this and create a [Percent] column which will be the frequency out of the total at variable level.
I have managed to join on the total, but the percentage calculating doesn't seem to be working. I am getting 0 for every Percent value.
select t1.[database],
t1.[table],
t1.[variable],
t1.[value],
t1.[frequency],
t2.[vartotal],
([frequency]) / t2.vartotal *100 as [Percent]
from TestDB.dbo.Meta t1
join (select [database], [table_name], [variable], sum(frequency) as vartotal
from TestDB.dbo.Meta
group by [database],
[table],
[variable]) t2
on t1.[database]=t2.[database] and
t1.[table]=t2.[table] and
t1.[variable]=t2.[variable]
group by t1.[database], t1.[table_name], t1.variable,
t1.[value], t1.[frequency], t2.[vartotal]
Why is returning zeroes for all percentage calculations?
SQL Server does integer division, so 1/2 = 0, not 0.5. I usually just multiply by 1.0 to solve this problem:
([frequency] * 100.0 / t2.vartotal) as [Percent]
That said, you can use window functions for this calculation:
select . . .,
sum(frequency) over (partition by database, table, variable) as vartotal,
([frequency] * 100.0 / sum(frequency) over (partition by database, table, variable) ) as [Percent]
from TestDB.dbo.Meta t1;
This is much simpler and should have better performance than your query.
Probably an issue regarding what datatype frequency is. Try the following:
Also might want to add some extra parents to ensure order of operations.
select t1.[database],
t1.[table],
t1.[variable],
t1.[value],
t1.[frequency],
t2.[vartotal],
((TRY_CONVERT(FLOAT, [frequency])) / TRY_CONVERT(FLOAT, t2.vartotal)) * 100 as [Percent]
from TestDB.dbo.Meta t1
join (select [database], [table_name], [variable], sum(frequency) as vartotal
from TestDB.dbo.Meta
group by [database],
[table],
[variable]) t2
on t1.[database]=t2.[database] and
t1.[table]=t2.[table] and
t1.[variable]=t2.[variable]
group by t1.[database], t1.[table_name], t1.variable,
t1.[value], t1.[frequency], t2.[vartotal]
Related
How to calculate metrics between two tables? In addition, I noticed that when using FROM tbl1, tbl2, there are noises, the WHERE filters did not work, a total count(*) was returned
Query:
select
count(*) filter(WHERE tb_get_gap.system in ('LINUX','UNIX')) as gaps,
SUM(CAST(srvs AS INT)) filter(WHERE tb_getcountsrvs.type = 'LZ') as total,
100 - (( gaps / total ) * 100)
FROM tb_get_gap, tb_getcountsrvs
Error:
SQL Error [42703]: ERROR: column "gaps" does not exist
I need to count in the tb_get_gap table by fields = ('LINUX', 'UNIX'), then a SUM ()in thesrvs field in the
tb_getcountsrvs table by fields = 'LZ' in type, right after
making this formula 100 - ((gaps / total) * 100)
It would seem that you cannot define gaps and also use it in the same query. In SQL Server you would have to use the logic twice. Maybe a subquery would work better.
select 100 - (t.gaps / t.total) * 100)
from
(
select
count(*) filter(WHERE tb_get_gap.system in ('LINUX','UNIX')) as gaps,
SUM(CAST(srvs AS INT)) filter(WHERE tb_getcountsrvs.type = 'LZ') as total
FROM tb_get_gap, tb_getcountsrvs
) t
Ok I have a list of the months orders so that bit is easy.
SELECT COUNT(*) AS ITEMS,
For the next part easy to:
COUNT(DISTINCT(PICKSET_NO))AS PICKSETS,
Its the next part I cant work out:
SUM(ITEMS/PICKSETS) AS AVGPICKSETSIZE
FROM dbo.orders
Thanks for your help on this. Here is the code in one block.
SELECT
COUNT(*) AS ITEMS,
COUNT(DISTINCT(PICKSET_NO))AS PICKSETS,
SUM(ITEMS/PICKSETS)
FROM dbo.CollationOrders
GO
Repeat the expression:
SELECT COUNT(*) AS ITEMS,
COUNT(DISTINCT PICKSET_NO) AS PICKSETS,
COUNT(*) / (1.0 * COUNT(DISTINCT PICKSET_NO))
FROM dbo.CollationOrders;
You can't re-use column aliases in the same select.
The 1.0 is to prevent integer division.
You may do like this:
SELECT ITEMS,
PICKSETS,
ITEMS / (1.0 * PICKSETS)
FROM (
SELECT COUNT(*) AS ITEMS,
COUNT(DISTINCT PICKSET_NO) AS PICKSETS
FROM TableName)t
I ended up using:
SELECT
COUNT(*) AS ITEMS,
COUNT(DISTINCT om.PicksetNo) AS PICKSETS ,
FORMAT(COUNT(*) / (1.0 * COUNT(DISTINCT om.PicksetNo)),'N2'),
ca.YWK AS YWK
FROM CHDS_Common.dbo.OMOrder om
INNER JOIN CHDS_Management.dbo.Calendar ca ON om.EarliestPickDate = ca.DT
GROUP BY ca.YWK
I am currently working in sql 2012 visual management studio. I have two tables. Table1 has three columns (ItemNumber as varchar, Quantity as int, and TimeOrdered as datetime). Table2 has 2 columns (ItemNumber as varchar, and Price as float). Please note these item numbers are not the same, the part numbers on table 1 have a letter after the number while the table 2 item number does not. For example on table 1 the item number will look something like this 999999999-E and the other table will just be 999999999-. Therefore I must use a select Left for 10 digits to get the part number.
I need to pull a list of item numbers from table 1 based on the time ordered and then cross compare that list to table 2 and multiple the price times the quantity for a grand total. Here is my code so far:
SELECT sum(tbl.quantity * table2.price) as grandtotal,
tbl.PartNumber,
tbl.quanity,
table2.price
FROM
(SELECT left(itemnumber, 10) as itemnumber, quantity
FROM table1
WHERE TimeOrdered between
('2014-05-05 00:00:00.000')
AND
('2015-05-05 00:00:00.000')) as tbl
Left table2 on
tbl.partnumber =tbl2.itemnumber
I am receiving an error here for aggregate columns but I am not sure this is the correct way to go about this to begin with.
-------------update---------------
I got it working. Sorry for taking so long to get back to you guys, I was stuck in a meeting all day,
How About This. The case is just to avoid div by Zero errors.
SELECT sum( Isnull(tbl.quantity,0) * Isnull(table2.price,0) ) as grandtotal,
tbl.PartNumber,
Sum(tbl.quanity),
case when Isnull(Sum(tbl.quanity),0) = 0 then null else
sum(Isnull(tbl.quantity,0) * Isnull(table2.price,0) ) / Sum(tbl.quanity) end
as Price
FROM
(SELECT left(itemnumber, 10) as itemnumber, quantity FROM table1 WHERE TimeOrdered between
('2014-05-05 00:00:00.000')
AND ('2015-05-05 00:00:00.000')) as tbl
Left outer join table2 on
tbl.partnumber =tbl2.itemnumber
group by tbl.PartNumber
SQL server requires you to explicitly group by the columns you're not aggregating by. So you need to add group by tbl.PartNumber, tbl.quantity, table2.price. Of course, that's probably going to make the tbl.quantity * table2.price kind of useless. What are you actually trying to do? :)
Here is a fiddle with some sample data that should give you what you want. You need to include any non-aggregate columns in your group by.
Your code ends up as follows:
SELECT left(table1.ItemNumber, 10) as PartNumber,
table2.price,
sum(table1.quantity) as totalquantity,
sum(table1.quantity * table2.price) as grandtotal
FROM table1
INNER JOIN table2 ON left(table1.ItemNumber, 10) = table2.ItemNumber
WHERE t1.Timerordered BETWEEN '2014-05-05 00:00:00.000' AND '2015-05-05 00:00:00.000'
GROUP BY table1.ItemNumber, table2.price
SQL Fiddle Example
SELECT SUM(t1.quantity * t2.price) AS 'GrandTotal'
,SUM(t1.quantity) AS 'Quantity'
,t1.itemnumber
,t2.price
FROM Table1 t1
JOIN Table2 t2 ON LEFT(t1.itemnumber, 10) = t2.itemnumber
WHERE t1.Timeordered BETWEEN '2014-05-05 00:00:00.000' AND '2015-05-05 00:00:00.000'
GROUP BY t1.itemnumber, t2.price
I have 2 tables, one table contain all the emails that i sent to some people, and the other table contains all the people who registred in my website after i sent them those emails.
What i need to achieve is to calculate the percent fo the users that registred, so for now i have this:
SELECT COUNT(*) from email_sent //10
SELECT COUNT(*) from registred_users //2
For this example i need to build a query witch will return 20% because 2 out of 10 means 20%.
SELECT
mailsSent
, userAmount
, CONVERT(decimal(14,2), mailsSent) / CONVERT(decimal(14,2), userAmount) AS Percentage
FROM
(SELECT COUNT(*) AS mailsSent from email_sent) a
, (SELECT COUNT(*) AS userAmount from registred_users) b
Basically the two subqueries will get you your original values in the form of 2 aliased columns, which you can then divide from the main query.
Edit:
An alternative to the explicit conversion is multiplying with 1.0 to turn the value into numeric. As stated in my comment, only 1 conversion is actually necessary:
SELECT
mailsSent
, userAmount
, mailsSent * 1.0 / userAmount AS Percentage
FROM
(SELECT COUNT(*) AS mailsSent from email_sent) a
, (SELECT COUNT(*) AS userAmount from registred_users) b
I am looking for a way to derive a weighted average from two rows of data with the same number of columns, where the average is as follows (borrowing Excel notation):
(A1*B1)+(A2*B2)+...+(An*Bn)/SUM(A1:An)
The first part reflects the same functionality as Excel's SUMPRODUCT() function.
My catch is that I need to dynamically specify which row gets averaged with weights, and which row the weights come from, and a date range.
EDIT: This is easier than I thought, because Excel was making me think I required some kind of pivot. My solution so far is thus:
select sum(baseSeries.Actual * weightSeries.Actual) / sum(weightSeries.Actual)
from (
select RecordDate , Actual
from CalcProductionRecords
where KPI = 'Weighty'
) baseSeries inner join (
select RecordDate , Actual
from CalcProductionRecords
where KPI = 'Tons Milled'
) weightSeries on baseSeries.RecordDate = weightSeries.RecordDate
Quassnoi's answer shows how to do the SumProduct, and using a WHERE clause would allow you to restrict by a Date field...
SELECT
SUM([tbl].data * [tbl].weight) / SUM([tbl].weight)
FROM
[tbl]
WHERE
[tbl].date >= '2009 Jan 01'
AND [tbl].date < '2010 Jan 01'
The more complex part is where you want to "dynamically specify" the what field is [data] and what field is [weight]. The short answer is that realistically you'd have to make use of Dynamic SQL. Something along the lines of:
- Create a string template
- Replace all instances of [tbl].data with the appropriate data field
- Replace all instances of [tbl].weight with the appropriate weight field
- Execute the string
Dynamic SQL, however, carries it's own overhead. Is the queries are relatively infrequent , or the execution time of the query itself is relatively long, this may not matter. If they are common and short, however, you may notice that using dynamic sql introduces a noticable overhead. (Not to mention being careful of SQL injection attacks, etc.)
EDIT:
In your lastest example you highlight three fields:
RecordDate
KPI
Actual
When the [KPI] is "Weight Y", then [Actual] the Weighting Factor to use.
When the [KPI] is "Tons Milled", then [Actual] is the Data you want to aggregate.
Some questions I have are:
Are there any other fields?
Is there only ever ONE actual per date per KPI?
The reason I ask being that you want to ensure the JOIN you do is only ever 1:1. (You don't want 5 Actuals joining with 5 Weights, giving 25 resultsing records)
Regardless, a slight simplification of your query is certainly possible...
SELECT
SUM([baseSeries].Actual * [weightSeries].Actual) / SUM([weightSeries].Actual)
FROM
CalcProductionRecords AS [baseSeries]
INNER JOIN
CalcProductionRecords AS [weightSeries]
ON [weightSeries].RecordDate = [baseSeries].RecordDate
-- AND [weightSeries].someOtherID = [baseSeries].someOtherID
WHERE
[baseSeries].KPI = 'Tons Milled'
AND [weightSeries].KPI = 'Weighty'
The commented out line only needed if you need additional predicates to ensure a 1:1 relationship between your data and the weights.
If you can't guarnatee just One value per date, and don't have any other fields to join on, you can modify your sub_query based version slightly...
SELECT
SUM([baseSeries].Actual * [weightSeries].Actual) / SUM([weightSeries].Actual)
FROM
(
SELECT
RecordDate,
SUM(Actual)
FROM
CalcProductionRecords
WHERE
KPI = 'Tons Milled'
GROUP BY
RecordDate
)
AS [baseSeries]
INNER JOIN
(
SELECT
RecordDate,
AVG(Actual)
FROM
CalcProductionRecords
WHERE
KPI = 'Weighty'
GROUP BY
RecordDate
)
AS [weightSeries]
ON [weightSeries].RecordDate = [baseSeries].RecordDate
This assumes the AVG of the weight is valid if there are multiple weights for the same day.
EDIT : Someone just voted for this so I thought I'd improve the final answer :)
SELECT
SUM(Actual * Weight) / SUM(Weight)
FROM
(
SELECT
RecordDate,
SUM(CASE WHEN KPI = 'Tons Milled' THEN Actual ELSE NULL END) AS Actual,
AVG(CASE WHEN KPI = 'Weighty' THEN Actual ELSE NULL END) AS Weight
FROM
CalcProductionRecords
WHERE
KPI IN ('Tons Milled', 'Weighty')
GROUP BY
RecordDate
)
AS pivotAggregate
This avoids the JOIN and also only scans the table once.
It relies on the fact that NULL values are ignored when calculating the AVG().
SELECT SUM(A * B) / SUM(A)
FROM mytable
If I have understand the problem then try this
SET DATEFORMAT dmy
declare #tbl table(A int, B int,recorddate datetime,KPI varchar(50))
insert into #tbl
select 1,10 ,'21/01/2009', 'Weighty'union all
select 2,20,'10/01/2009', 'Tons Milled' union all
select 3,30 ,'03/02/2009', 'xyz'union all
select 4,40 ,'10/01/2009', 'Weighty'union all
select 5,50 ,'05/01/2009', 'Tons Milled'union all
select 6,60,'04/01/2009', 'abc' union all
select 7,70 ,'05/01/2009', 'Weighty'union all
select 8,80,'09/01/2009', 'xyz' union all
select 9,90 ,'05/01/2009', 'kws' union all
select 10,100,'05/01/2009', 'Tons Milled'
select SUM(t1.A*t2.A)/SUM(t2.A)Result from
(select RecordDate,A,B,KPI from #tbl)t1
inner join(select RecordDate,A,B,KPI from #tbl t)t2
on t1.RecordDate = t2.RecordDate
and t1.KPI = t2.KPI