Dividing two SQL query results - sql

I'm just trying to learn how to take a value from a column, in this case how much JJ spent on product a, and divide it by the sum of the total Product A sales and turn it into a percentage.
My SQL understanding is pretty low level right now, so the simpler the response the better.
SELECT
JJ / Result * 100 AS percentage
FROM
(SELECT
([Product A] AS JJ
FROM [Test].[dbo].[TableA]
WHERE [Customer Name] = 'JJ'
SELECT SUM([Product A]) AS Result
FROM [Test].[dbo].[TableA]
)
--JJ/Result * 100 = ProdAPercentSales)

You could use a case expression to find JJ's purchases, and divide their sum with the total sum:
SELECT SUM(CASE [Customer name] WHEN 'JJ' THEN [Product A] ELSE 0 END) /
SUM([Product A]) * 100 AS [Percentage]
FROM [Test].[dbo].[TableA]

Related

How do I join these 3 tables, do a sum calculation and group by multiple values?

So I have these 3 tables below:
User Table:
Deposit Table:
Withdrawal Table:
Question:
How do I join these 3 tables together, having the values grouped by:
Month
Sex
Age
State
Expected Output:
As seen from my expected output above, I want to separate these users into different buckets
i.e.
Month: January, Sex: F, Age: 18, State: Arizona (Bucket 1)
Month: January, Sex: M, Age: 21, State: Texas (Bucket 2)
and find the sum(Deposit.amount) & sum (Withdrawal.amount) group by the fields above ^
as well as how many users are in this bucket.
Clarification:
"Month" column in my Expected Ouput is based on Transaction Month for both Deposit & Withdrawal table combined, I just added the "Account Created Month" column in User table just in case its possible to do a 3-table join through Month values.
This should work:
SELECT tr.Month, u.Sex, u.Age, u.Stage
, Sum(CASE WHEN [Type] = 'D' THEN tr.Amount ELSE 0 END) As [Total Deposit Amount]
, Sum(CASE WHEN [Type] = 'W' THEN tr.Amount ELSE 0 END) As [Total Withdrawal Amount]
, COUNT(distint u.UserID) As [Number of Users in this Category]
FROM (
SELECT userid, [Deposit Amount] As Amount, [Transaction Month] As Month, 'D' As [Type] FROM [Deposit]
UNION ALL
SELECT userid, [Withdrawal Amount], [Transaction Month], 'W' FROM [Withdrawal]
) tr
INNER JOIN User u ON u.UserID = tr.UserId
GROUP BY tr.Month, u.Sex, u.Age, u.State
Really, deposits and withdrawals should be the same table (Transactions) to begin with, where the only difference is withdrawals are negative.
Also... please stop using images to represent your sample data and results. It makes things so much harder on us to help you, which makes it less likely you'll get a good or prompt answer.

SQL: WITH ROLLUP per department without totals

I have some SQL needed for creating a file for export. I have a lot of it working as it should. The one major stumbling block is with the WITH ROLLUP portion.
I need to get totals by VendorNo(store) and by DepartmentID(dept), but not with overall totals. The Department is gleaned by getting the first 3 digits of a column as you can see below. Grouping on that as well as the VendorNo is giving me what I need but also throwing overall totals in there.
How can I either pare out the overall totals from WITH ROLLUP OR rewrite some portions to accomplish the same totaling per VendorNo, Dept?
So far, I have:
select
'VDP' as [Record Id],
'TRU' as [Client Id],
o.vendorNo as [Store Number],
CASE WHEN LEFT(HVI.EncodeData, 3) IS NULL THEN 'S' ELSE 'D' END as [Feed Level],
LEFT(HVI.EncodeData, 3) AS [DepartmentId],
CONVERT(Date, pto.PrintBatch) as [Metric Date],
CASE WHEN pto.pickbit = 1 THEN 'ISPU_ORDER_PICKUP' ELSE 'ISPU_ORDER_CREATED' END as [Metric ID], -- 15 chars ISPU_ORDER_CREATED or ISPU_ORDER_PICKUP TOTO: VALIDATE THIS WORKS !!
'A' AS [Metric Category],
'D' AS [Metric Granularity],
REPLICATE('0', 9 - len(CAST(SUM(OI.Quantity) AS int))) + CAST(CAST(SUM(OI.Quantity) as Int)as VARCHAR) AS [Data Value 1], -- needed to get the left-padded 9 char max value
NULL as [Data Value 2],
NULL as [Data Value 3],
NULL as [Data Value 4],
NULL as [Data Value 5],
NULL as [Data Value 6],
NULL as [Operation Code]
FROM
pickticketsorders pto
inner join orders o
on pto.orderno = o.orderno
and o.rank = 3
and pto.printbatch >= '6-12-2015'
and pto.printbatch < '6-13-2015'
INNER JOIN HostVendorItems HVI
on HVI.ItemId = pto.ItemId
INNER JOIN OrderItems OI
on OI.OrderNo = o.OrderNo
GROUP BY
o.VendorNo,
LEFT(HVI.EncodeData, 3),
pto.PrintBatch,
pto.PickBit
WITH ROLLUP
ORDER BY
o.VendorNo,
DepartmentId ,
pto.PrintBatch
Which gives me: (the line that has StoreNo as NULL needs to be removed)
Use GROUPING SETS. I am not sure exactly which groups you want, but this seems to match what you are asking for:
GROUP BY GROUPING SETS ((o.VendorNo, LEFT(HVI.EncodeData, 3), pto.PrintBatch, pto.PickBit),
(o.VendorNo, LEFT(HVI.EncodeData, 3)),
(o.VendorNo)
)
You might want to read more about GROUPING SETS in the documentation.
You could also add :
HAVING GROUPING(o.VendorNo) = 0

Add a column into a select for calculation

I have 2 columns from a table and i want to add a third column to output the result of a calculation
select statement at the moment is:
select revenue, cost
from costdata
my 2 columns are revenue and cost
table name: costdata
my formula is: = ((revenue - cost)/ revenue)*100
I want the third column to be named 'result'
any idea on how to do this in a select statement?
SELECT revenue
, cost
, ((revenue - cost) / revenue) * 100 As result
FROM costdata
You mentioned in the comments that you get a divide by zero error. This wll occur when revenue equals zero.
What you want to happen in this scenario is up to you but this should get you started
SELECT revenue
, cost
, CASE WHEN revenue = 0 THEN
0
ELSE
((revenue - cost) / revenue) * 100
END As result
FROM costdata
Query:
SELECT revenue,
cost,
CASE WHEN revenue <> 0
THEN ((revenue - cost) / revenue) * 100
ELSE 0 END As result
FROM costdata
Try,
select revenue, cost,((revenue - cost)/ revenue)*100 AS result
from costdata
SELECT revenue, cost, ((revenue - cost)/ revenue)*100 AS result FROM costdata

How to combine rows into one, MS SQL Server management Studio

I think this is a tricky one, How do you combine all of these rows into one? Is it possible? Maybe using temp tables, the values on the left from combo onwards are null but could contain a value. Any ideas? Let me know if you've got any questions.
I would like to show just one line for this unit, i.e/ RecordNumber Reg_string Parts Materials Labour Combo Smart Total Estimate. Record number does not need to be selected in the query but might help when building the script. This is my script so far.
RecordNumber Reg_string Parts Materials Labour Combo Smart Total Estimate
1 AB12ABC NULL NULL NULL 222.27 0 222.27
2 AB12ABC NULL NULL NULL 76.7 0 76.7
3 AB12ABC NULL NULL NULL 48.1 0 48.1
4 AB12ABC NULL NULL NULL 49.4 0 49.4
5 AB12ABC NULL NULL 12 NULL 1 13
6 AB12ABC NULL NULL NULL 255 NULL 255
7 AB12ABC NULL NULL NULL 255 NULL 255
I would like the output to be like this please:
RecordNumber Reg_string Parts Materials Labour Combo Smart Total Estimate
1 AB12ABC NULL NULL 12 906.47 1 919.47
My script is this if it helps:
SELECT UnitData_Vehicle.Reg_String AS Registration, UnitData_Vehicle.VehID_String AS [Vendor Code], UnitData_Vehicle.Manu_String AS Make,
UnitData_Vehicle.ManuNS_String AS [Make NS], SUM(CONVERT(money, UnitData_Vehicle_CompCond.Materials_String)) AS [Materials Price],
CONVERT(money, UnitData_Vehicle_CompCond.Labour_String) AS [Labour Price], CONVERT(money, UnitData_Vehicle_CompCond.Combo_String)
AS [Combination Price], CONVERT(money, UnitData_Vehicle_CompCond.PartSpend_String) AS [Parts Price], CONVERT(money,
UnitData_Vehicle_CompCond.SmartRep_String) AS [Smart Price], CONVERT(money, UnitData_Vehicle_CompCond.Estimate_String)
AS [Estimate EX VAT]
FROM UnitData_Vehicle INNER JOIN
UnitData_Vehicle_CompCond ON UnitData_Vehicle.InspectionResultId = UnitData_Vehicle_CompCond.InspectionResultId
WHERE (UnitData_Vehicle.InspectionProcedureName LIKE '%- inspection') AND (DATEDIFF(day, UnitData_Vehicle.InspectionDateTime, GETDATE()) = 2)
AND Reg_String = 'AB12ABC'
GROUP BY UnitData_Vehicle.EstimDate_DateTime, UnitData_Vehicle.Reg_String, UnitData_Vehicle.VehID_String, UnitData_Vehicle.Manu_String,
UnitData_Vehicle.ManuNS_String, CONVERT(money, UnitData_Vehicle_CompCond.Labour_String), CONVERT(money,
UnitData_Vehicle_CompCond.Combo_String), CONVERT(money, UnitData_Vehicle_CompCond.PartSpend_String), CONVERT(money,
UnitData_Vehicle_CompCond.SmartRep_String), CONVERT(money, UnitData_Vehicle_CompCond.Estimate_String)
SELECT
UnitData_Vehicle.Reg_String AS Registration,
UnitData_Vehicle.VehID_String AS [Vendor Code],
UnitData_Vehicle.Manu_String AS Make,
UnitData_Vehicle.ManuNS_String AS [Make NS],
SUM(CONVERT(money, UnitData_Vehicle_CompCond.Materials_String)) AS [Materials Price],
SUM(CONVERT(money, UnitData_Vehicle_CompCond.Labour_String)) AS [Labour Price],
SUM(CONVERT(money, UnitData_Vehicle_CompCond.Combo_String)) AS [Combination Price],
SUM(CONVERT(money, UnitData_Vehicle_CompCond.PartSpend_String)) AS [Parts Price],
SUM(CONVERT(money, UnitData_Vehicle_CompCond.SmartRep_String)) AS [Smart Price],
SUM(CONVERT(money, UnitData_Vehicle_CompCond.Estimate_String)) AS [Estimate EX VAT]
FROM UnitData_Vehicle INNER JOIN UnitData_Vehicle_CompCond
ON UnitData_Vehicle.InspectionResultId = UnitData_Vehicle_CompCond.InspectionResultId
WHERE (UnitData_Vehicle.InspectionProcedureName LIKE '%- inspection') AND (DATEDIFF(day, UnitData_Vehicle.InspectionDateTime, GETDATE()) = 2) AND Reg_String = 'AB12ABC'
GROUP BY UnitData_Vehicle.EstimDate_DateTime, UnitData_Vehicle.Reg_String, UnitData_Vehicle.VehID_String, UnitData_Vehicle.Manu_String,UnitData_Vehicle.ManuNS_String
Your query is huge and hard to read, but what you need to essentially do is SUM the columns add add a "GROUP BY Reg_string" to the end of your query, obviously do not include the "RecordNumber" in your output as it can not be grouped.
Your query isn't very easy to read, but this is essentially what you'd have to do if all of your fields were in the same table, just amend it to do what you need regarding table joins etc...
SELECT Reg_String),
SUM(Parts) As Parts,
SUM(Materials) AS Materials,
SUM(Labour) AS Labour,
SUM(Combo) AS Combo,
SUM(Smart) AS Smart,
SUM(TotalEstimate) AS TotalEstimate
FROM MyTable
WHERE Reg_String = 'AB12ABC'
GROUP BY Reg_String
If I am not committing mistake , Total Estimate = Labour + Combo + Smart ?
Below is the query
SELECT Reg_String,
SUM(Parts) As Parts,
SUM(Materials) AS Materials,
SUM(Labour) AS Labour,
SUM(Combo) AS Combo,
SUM(Smart) AS Smart,
SUM(Labour + Combo + Smart) AS TotalEstimate
FROM MyTable
GROUP BY Reg_String
SELECT MAX(Reg_String),
SUM(Parts) As Parts,
SUM(Materials) AS Materials,
SUM(Labour) AS Labour,
SUM(Combo) AS Combo,
SUM(Smart) AS Smart,
SUM(Labour + Combo + Smart) AS TotalEstimate
FROM MyTable
GROUP BY Reg_String
use MAX(Reg_String) to display Reg_String also since max will return same result because query is grouping by Reg_String

sum divided values problem (dealing with rounding error)

I've a product that costs 4€ and i need to divide this money for 3 departments.
On the second column, i need to get the number of rows for this product and divide for the number of departments.
My query:
select
department, totalvalue,
(totalvalue / (select count(*) from departments d2 where d2.department = p.product))
dividedvalue
from products p, departments d
where d.department = p.department
Department Total Value Divided Value
---------- ----------- -------------
A 4 1.3333333
B 4 1.3333333
C 4 1.3333333
But when I sum the values, I get 3,999999. Of course with hundreds of rows i get big differences...
Is there any chance to define 2 decimal numbers and round last value? (my results would be 1.33 1.33 1.34)
I mean, some way to adjust the last row?
In order to handle this, for each row you would have to do the following:
Perform the division
Round the result to the appropriate number of cents
Sum the difference between the rounded amount and the result of the division operation
When the sum of the differences exceeds the lowest decimal place (in this case, 0.01), add that amount to the results of the next division operation (after rounding).
This will distribute fractional amounts evenly across the rows. Unfortunately, there is no easy way to do this in SQL with simple queries; it's probably better to perform this in procedural code.
As for how important it is, when it comes to financial applications and institutions, things like this are very important, even if it's only by a penny, and even if it can only happen every X number of records; typically, the users want to see values tie to the penny (or whatever your unit of currency is) exactly.
Most importantly, you don't want to allow for an exploit like "Superman III" or "Office Space" to occur.
With six decimals of precision, you would need about 5,000 transactions to notice a difference of one cent, if you round the final number to two decimals. Increasing the number of decimals to an acceptable level would eliminate most issues, i.e. using 9 decimals you would need about 5,000,000 transactions to notice a difference of a cent.
Maybe you can make a forth row that will be Total - sum(A,B,C).
But it depends on what you want to do, if you need exact value, you can keep fractions, else, truncate and don't care about the virtual loss
Also can be done simply by adding the rounding difference of a particular value to the next number to be rounded (before rounding). This way the pile remains always the same size.
Here's a TSQL (Microsoft SQL Server) implementation of the algorithm provided by Martin:
-- Set parameters.
DECLARE #departments INTEGER = 3;
DECLARE #totalvalue DECIMAL(19, 7) = 4.0;
WITH
CTE1 AS
(
-- Create the data upon which to perform the calculation.
SELECT
1 AS Department
, #totalvalue AS [Total Value]
, CAST(#totalvalue / #departments AS DECIMAL(19, 7)) AS [Divided Value]
, CAST(ROUND(#totalvalue / #departments, 2) AS DECIMAL(19, 7)) AS [Rounded Value]
UNION ALL
SELECT
CTE1.Department + 1
, CTE1.[Total Value]
, CTE1.[Divided Value]
, CTE1.[Rounded Value]
FROM
CTE1
WHERE
Department < #departments
),
CTE2 AS
(
-- Perform the calculation for each row.
SELECT
Department
, [Total Value]
, [Divided Value]
, [Rounded Value]
, CAST([Divided Value] - [Rounded Value] AS DECIMAL(19, 7)) AS [Rounding Difference]
, [Rounded Value] AS [Calculated Value]
FROM
CTE1
WHERE
Department = 1
UNION ALL
SELECT
CTE1.Department
, CTE1.[Total Value]
, CTE1.[Divided Value]
, CTE1.[Rounded Value]
, CAST(CTE1.[Divided Value] + CTE2.[Rounding Difference] - ROUND(CTE1.[Divided Value] + CTE2.[Rounding Difference], 2) AS DECIMAL(19, 7))
, CAST(ROUND(CTE1.[Divided Value] + CTE2.[Rounding Difference], 2) AS DECIMAL(19, 7))
FROM
CTE2
INNER JOIN CTE1
ON CTE1.Department = CTE2.Department + 1
)
-- Display the results with totals.
SELECT
Department
, [Total Value]
, [Divided Value]
, [Rounded Value]
, [Rounding Difference]
, [Calculated Value]
FROM
CTE2
UNION ALL
SELECT
NULL
, NULL
, SUM([Divided Value])
, SUM([Rounded Value])
, NULL
, SUM([Calculated Value])
FROM
CTE2
;
Output:
You can plug in whatever numbers you want at the top. I'm not sure if there is a mathematical proof for this algorithm.