SQL - % split between sale types - sql

I need some SQL help.
I have a table where I'm trying to get the % split between online sales and POS sales. Below is the query I have written and the screenshot is what I get, but I don't know how to proceed from there.
I need a third column that shows the percentage split between online sales and POS sales.
That would be (POS/(POS+ONLINE))*100 and (ONLINE/(POS+ONLINE))*100
Here's my current query
SELECT
CASE
WHEN saleschannel = 1 THEN 'ONLINE'
WHEN saleschannel = 6 THEN 'ONLINE'
WHEN saleschannel = 7 THEN 'ONLINE'
WHEN saleschannel = 8 THEN 'ONLINE'
ELSE 'POS'
END AS CHANNEL,
COUNT(*) AS TICKET_QTY
FROM performancesales
WHERE salesdate BETWEEN '2022-08-01' AND '2023-01-31'
AND saleschannel IN ('1','6', '7', '8', '14')
GROUP BY CHANNEL
ORDER BY 2;
Thanks to Aaron his query was right and helped me. I have rewritten it to make it a bit faster. This is my final code
SELECT
channel,
COUNT(*) AS ticket_qty,
CAST(100.0 * COUNT(*) / SUM(COUNT(*)) OVER() AS DECIMAL(10,2)) AS percentage_split
FROM (
SELECT
CASE
WHEN saleschannel IN ('2','5','7') THEN 'ONLINE'
WHEN saleschannel IN ('0','1','4','6','8') THEN 'POS'
ELSE 'OTHER - (AGGREGATOR)'
END AS channel,
1 AS ticket
FROM performancesales
WHERE
salesdate BETWEEN '2022-08-01' AND '2023-01-31'
AND saleschannel IN ('0','1','2','3','4','5','6','7','8')
) t
GROUP BY channel;

I thnk this will do it,
WITH CTE AS
(
SELECT
CASE
WHEN saleschannel = 1 THEN 'ONLINE'
WHEN saleschannel = 6 THEN 'ONLINE'
WHEN saleschannel = 7 THEN 'ONLINE'
WHEN saleschannel = 8 THEN 'ONLINE'
ELSE 'POS'
END AS CHANNEL,
COUNT(*) AS TICKET_QTY
FROM
performancesales
WHERE
salesdate BETWEEN '2022-08-01' AND '2023-01-31'
AND
saleschannel IN ('1','6', '7', '8', '14')
GROUP BY
CHANNEL
)
SELECT
CHANNEL
,TICKET_QTY
,CAST(TICKET_QTY AS DECIMAL(10,2)) / SUM(TICKET_QTY) OVER() AS [Percentage]
FROM
CTE
Wrap the original query up as a CTE (Common Table Expression) [you can call it whatever you like]. the SUM()OVER() will calculate the total sum of the dataset for each row returned. You also have to convert one of the values to a decimal otherwise you will be dividing and integer by an integer and will get an integer result. If you need more precision, do DECIMAL (10,6)

You can do this using filtered aggregation.
select
count(*) filter (where saleschannel in ('1','6', '7', '8')) as pos_qty,
count(*) filter (where saleschannel = '14') as online_qty,
count(*) filter (where saleschannel in ('1','6', '7', '8'))::numeric / count(*) * 100 as pos_percentage,
count(*) filter (where saleschannel = '14')::numeric / count(*) * 100 as online_percentage
FROM performancesales
WHERE salesdate BETWEEN '2022-08-01' AND '2023-01-31'
AND saleschannel IN ('1','6', '7', '8', '14');

Related

SQL Server- SUM Subquery each other

I would like to ask for a little help in SQL Server.
How can I sum each other subquery and display it into one column.
I tested to add the subqueries in the SELECT statement, but, then I realize that I don't know how to sum between them, then I added them to the FROM statement to see If I adding them up in the SELECT statement could but work, but it didn't.
the idea is to substract B with A for each CodUbic.
'A' for purchases ($),
'B' for returned products ($)
SELECT
MONTH(FechaE) AS Mes,
YEAR(FechaE) AS Ano,
CodUbic,
TipoFac,
(SumFacA + SumFacB)
FROM
dbo.SAFACT,
(SELECT SUM(Monto) FROM dbo.SAFACT WHERE TipoFac IN ('A')) AS SumFacA,
(SELECT -SUM(Monto) FROM dbo.SAFACT WHere TipoFac IN ('B')) AS SumFacB
WHERE
TipoFac IN ('A', 'B')
GROUP BY
MONTH(FechaE),
YEAR(FechaE),
CodUbic,
TipoFac
ORDER BY
YEAR(FechaE) DESC,
MONTH(FechaE);
Expected Result:
Mes Ano CodUbic TotalSum
----------------------------------------------------
1 2022 0002-1 #### (Due the sum of A-B)
1 2022 0004-1 #### (Due the sum of A-B)
2 2022 0002-1 #### (Due the sum of A-B)
2 2022 0004-1 #### (Due the sum of A-B)
... ... ... ...
You are finally showing the expected result. It doesn't contain a column for TipoFac. This means, you want a result row per month and CodUbic, not per month, CodUbic, and TipoFac. Change your GROUP BY clause accordingly.
You get the difference between A and B with conditional aggregation (CASE WHEN inside the aggregation function).
SELECT
MONTH(FechaE) AS Mes, YEAR(FechaE) AS Ano, CodUbic,
SUM(CASE WHEN TipoFac = 'A' THEN monto ELSE -monto END) AS diff
FROM
dbo.SAFACT
WHERE
TipoFac IN ('A', 'B')
GROUP BY
MONTH(FechaE), YEAR(FechaE), CodUbic
ORDER BY
YEAR(FechaE) DESC, MONTH(FechaE), CodUbic
;
Simply:
SELECT
MONTH(FechaE) AS Mes,
YEAR(FechaE) AS Ano,
CodUbic,
SUM(CASE WHEN TipoFac = 'A' THEN ISNULL(Monto,0) ELSE 0 END) -
SUM(CASE WHEN TipoFac = 'B' THEN ISNULL(Monto,0) ELSE 0 END)
FROM
dbo.SAFACT
WHERE
TipoFac IN ('A', 'B')
GROUP BY
MONTH(FechaE),
YEAR(FechaE),
CodUbic
ORDER BY
YEAR(FechaE) DESC,
MONTH(FechaE);

Oracle SQL group by rollup and ratio_to_report

I have a table like this
I have a query to show the number of vehicles manufactured in each region and the percentage of vehicles manufactured in the region
select
COALESCE(Region, 'Total') AS Region,
count(veh_vin) as "Total Vehicles Manufactured",
round(ratio_to_report(count(veh_vin)) over(),2) as rr
from(
select
case
when substr(veh_vin,1,1) between 'A' and 'C' then 'Africa'
when substr(veh_vin,1,1) between 'J' and 'R' then 'Asia'
when substr(veh_vin,1,1) between 'S' and 'Z' then 'Europe'
when substr(veh_vin,1,1) between '1' and '5' then 'North America'
when substr(veh_vin,1,1) between '6' and '7' then 'Oceania'
when substr(veh_vin,1,1) between '8' and '9' then 'South America'
else 'Unknown'
end as Region,
veh_vin
from vehicle
)
group by ROLLUP(Region)
order by count(veh_vin), Region;
which gives me the following result
But I want something like this, which exclude the total when calculating the percentage.
Is it possible to achieve it with ratio_to_report? If not, how could I alter the query?
I prefer an explicit calculation. You can adjust the calculation (either way) for the aggregated row:
select coalesce(Region, 'Total') AS Region,
count(*) as "Total Vehicles Manufactured",
round(count(*) * 1.0 / sum(case when grouping_id(region) = 0 then count(*) end) over (), 2) as rr
from . . .
The same idea doesn't quite work with ratio_to_report() -- because it returns NULL instead of 1. But you could use:
coalesce(round(ratio_to_report(case when grouping_id(region) = 0 then count(*) end) over(), 2), 1) as rr
Here is a little db<>fiddle.

Filtering Data from a Query with SQL

I'm really running into issues with this SQL code I'm using. I've been posying about this, but not really getting answers. I tried to adapt this code to what I am trying to do, but I keep getting the screen shot of the error message screen grabbed below.
SELECT LoginID, ShiftNumber, PalletQTY, Group, ShiftDate
FROM Database
COUNT(PalletQTY) AS TotalCount
SUM(IF(Group='PUT',1,0)) AS ActiveCount,
ROUND((SUM(IF(TALLY_TRAN_MSTR.PRI_GRP_CD='PUT',1,0))*100/COUNT(TALLY_TRAN_MSTR.FULL_PLLT_QTY)),2) AS PctActive
FROM WBR_RW.TALLY_TRAN_MSTR TALLY_TRAN_MSTR
Now, there are many dates and many names with each date, so I want the code recognize that. I'm not sure if the code I'm trying to adapt is able to do that.
I'm really struggling at this point for how to make this code work, or find code that may work. Any help would be appreciated. Let me know if additional information is needed.
EDIT
I am trying view data of a person who is spent more than 75% of his time doing "PUT" for a specific date.
An example of what I'm trying to do is (Calculated manually)
McMillan has 3 items associated with his name 6/15/2017. 1 of those is "PUT". The total of the "PUT" items for that date for him are 132. The sum of everything associated with his name for that date is 167. 132/167 = 0.79. 0.79>0.75 so I want his data to show in my query results.
Malone has 4 items associated with his name on 6/15/2017. 1 of those is "PUT". The "PUT" summed is 4. The sum of all 4 items is 36. 4/36=0.11 0.11 < 0.75, so I want to get rid of that data.
Oracle does not support if(). Use proper Oracle syntax. I think the query should look more like this:
SELECT LoginID, ShiftNumber, PalletQTY, "Group", ShiftDate,
COUNT(PalletQTY) AS TotalCount,
SUM(CASE WHEN "Group" = 'PUT' THEN 1 ELSE 0 END) AS ActiveCount,
ROUND(SUM(CASE WHEN TALLY_TRAN_MSTR.PRI_GRP_CD = 'PUT' THEN 1 ELSE 0 END)*100 /
COUNT(TALLY_TRAN_MSTR.FULL_PLLT_QTY), 2) AS PctActive
FROM WBR_RW.TALLY_TRAN_MSTR TALLY_TRAN_MSTR
GROUP BY LoginID, ShiftNumber, PalletQTY, "Group", ShiftDate;
Aside from the basic syntax issues Gordon pointed out, you seem to be confusing the count() and sum() aggregate functions. You can do both, but from your description you're interest in the total quantity of pallets, not the number of entries for the group type.
You seem to want something more like this, Using dummy data based on your original description:
-- CTE for dummy data
with tally_tran_mstr (loginid, shiftdate, shiftnumber, pri_grp_cd, palletqty) as
(
select 'Steve', date '2017-06-15', 1, 'PUT', 40 from dual
union all select 'Steve', date '2017-06-15', 1, 'PUT', 80 from dual
union all select 'Steve', date '2017-06-15', 1, 'PUT', 45 from dual
union all select 'Steve', date '2017-06-15', 1, '???', 7 from dual
union all select 'Steve', date '2017-06-15', 1, '???', 5 from dual
union all select 'Steve', date '2017-06-15', 1, '???', 3 from dual
union all select 'Steve', date '2017-06-15', 1, '???', 1 from dual
)
-- end of CTE for dummy data
select loginid, shiftnumber, shiftdate,
count(*) as totalcount,
count(case when pri_grp_cd = 'PUT' then 1 end) as activecount,
round(100 * count(case when pri_grp_cd = 'PUT' then 1 end) / count(*), 2)
as pctactivecount,
sum(palletqty) as totalqty,
sum(case when pri_grp_cd = 'PUT' then palletqty else 0 end) as activeqty,
round(100 * sum(case when pri_grp_cd = 'PUT' then palletqty else 0 end) /
sum(palletqty), 2) as pctactiveqty
from tally_tran_mstr
group by loginid, shiftnumber, shiftdate;
LOGIN SHIFTNUMBER SHIFTDATE TOTALCOUNT ACTIVECOUNT PCTACTIVECOUNT TOTALQTY ACTIVEQTY PCTACTIVEQTY
----- ----------- ---------- ---------- ----------- -------------- ---------- ---------- ------------
Steve 1 2017-06-15 7 3 42.86 181 165 91.16
I've taken palletqty and group out of the select-list because if they are in there they have to be included in the group-by clause, which won't do the counting and summing at the right level.
That should give you one row of output for each person on each day/shift they have records for.
If you want to exclude the results rows where the percentage doesn't meet some threshold then you can add a having clause:
...
group by loginid, shiftnumber, shiftdate
having sum(case when pri_grp_cd = 'PUT' then palletqty else 0 end) /
sum(palletqty) > 0.8;
You may prefer to avoid repeating some of the terms; you can move the count/sum/group into a subquery (inline view), and then filter that with a where clause instead of a having clause:
select loginid, shiftnumber, shiftdate, totalcount, activecount,
round(100 * activecount / totalcount, 2) as pctactivecount,
totalqty, activeqty, round(100 * activeqty / totalqty, 2) as pctactiveqty
from (
select loginid, shiftnumber, shiftdate,
count(*) as totalcount,
count(case when pri_grp_cd = 'PUT' then 1 end) as activecount,
sum(palletqty) as totalqty,
sum(case when pri_grp_cd = 'PUT' then palletqty else 0 end) as activeqty
from tally_tran_mstr
group by loginid, shiftnumber, shiftdate
)
where activeqty / totalqty > 0.8;

Rewrite SQL query to return rows for each dataset

I have the following table for student's fee payments
[fee_id] INT
[user_id] INT
[payment] DECIMAL (18, 2)
[date] DATETIME
[fee_remaining] DECIMAL (18, 2)
[year] INT
[payment_method] NVARCHAR (50)
[fee_required] DECIMAL (18, 2)
This is my current query to display the number of students who have either paid, yet to pay or have partially paid their fees for the year
SELECT DISTINCT
(SELECT COUNT(*) AS Expr1
FROM fee_payments
WHERE (fee_remaining = 0)
AND (YEAR = #year)) AS Fully_Paid,
(SELECT COUNT(*) AS Expr1
FROM fee_payments
WHERE (fee_remaining = fee_required)
AND (YEAR = #year)) AS Unpaid,
(SELECT COUNT(*) AS Expr1
FROM fee_payments
WHERE (fee_remaining > 0)
AND (YEAR = #year)
AND (fee_remaining <> fee_required)) AS Partially_Paid
FROM fee_payments AS fee_payments_1
This is my output
Fully_Paid | Unpaid | Partially_Paid
-------------------------------------
8 | 1 | 5
Is it at all possible to have my output displayed as follows?
Status | Total
----------------------------
Fully Paid | 8
Unpaid | 1
Partially Paid | 5
Any help would be greatly appreciated
Use a case expression to assign the required status to each row and group by the calculated column.
select status, count(*) as total
from (
SELECT
case when fee_remaining = 0 then 'fully_paid'
when fee_remaining <> fee_required then 'partially_paid'
when fee_remaining = fee_required then 'unpaid'
end as status
FROM fee_payments
WHERE YEAR = #year) t
group by status
Also note this assumes fee_remaining and fee_required are non null values. If they can be null, use coalesce to handle them when comparing.
So without completely restructuring your query into something more efficient like kvp's answer, you could UNION each of the results instead of using them each as a sub-query:
SELECT 'Fully Paid' AS Status, COUNT(*) AS Total
FROM fee_payments
WHERE (fee_remaining = 0) AND (YEAR = #year)
UNION
SELECT 'Unpaid', COUNT(*)
FROM fee_payments
WHERE (fee_remaining = fee_required) AND (YEAR = #year)
UNION
SELECT 'Partially Paid', COUNT(*)
FROM fee_payments
WHERE (fee_remaining > 0) AND (YEAR = #year) AND (fee_remaining <> fee_required)
Your code appears to have more than one row per year. It seems like the last row would be the most informative, so I'm thinking something like this:
select sum(case when fee_remaining = 0 then 1 else 0 end) as FullyPaid,
sum(case when fee_remaining < fee_required then 1 else 0 end) as PartiallyPaid,
sum(case when fee_remaining = fee_required then 1 else 0 end) as Unpaid
from (select fp.*,
row_number() over (partition by user_id order by date desc) as seqnum
from fee_payments fp
where YEAR = #year
) fp
where seqnum = 1;
SELECT 'Fully Paid' as Status, COUNT(*) AS Total
FROM fee_payments
GROUP BY year
WHERE fee_remaining = 0
AND YEAR = #year
UNION ALL
SELECT 'Unpaid' as Status, COUNT(*) AS Total
FROM fee_payments
GROUP BY year
WHERE fee_remaining = fee_required
AND YEAR = #year
UNION ALL
SELECT 'Partially Paid' as Status, COUNT(*) AS Total
FROM fee_payments
GROUP BY year
WHERE fee_remaining > 0
AND YEAR = #year
AND fee_remaining <> fee_required

T-SQL GROUP BY with Count () and Calculations Not grouping and Counting correctly

I wrote code with the purpose of grouping at the "CurrentStatus", counting the number of records by "currentstatus" that are "IN' and "OUT", then dividing the "OUT" records by the total number of records for specific "CurrentStatus" by the Total number records for the "CurrentStatus" ( IN + OUT) to get the Percentage of "OUT" in the "CurrentStatus". Below is a snippet of the code.
SELECT DISTINCT
convert(date, Getdate(), 1) [Date],
channel,
CurrentStatus,
(select count(number) from dbo.vw_AP where channel = 'C' AND [In/Out of Tolerance] = 'in') [In],
(select count(number) from dbo.vw_AP where channel = 'C' AND [In/Out of Tolerance] = 'out') [Out],
count(number) [Total],
convert(Decimal(18,2), (1.0 * (select count(number) from dbo.vw_AgedPipeline where channel = 'C' AND [In/Out of Tolerance] = 'out') / count(number))) [OOTP]
FROM [dbo].[vw_AgedPipeline]
WHERE Channel = 'C'
GROUP BY CurrentStatus, channel
order by Channel, CurrentStatus
The Results this code brings back for "IN" is the total number of "IN' by Channel (instead of CurrentStatus), "OUT" is the total number of "OUT" by channel, and "TOTAL" is the total number by "CurrentStatus". I want the code to group by CurrentStatus for "IN", "OUT", and "TOTAL". Can anyone help?
;WITH CTE AS
(SELECT
convert(date, Getdate(), 1) [Date]
,v.channel
,v.CurrentStatus
,count(CASE WHEN A.[In/Out of Tolerance] = 'in' THEN 1 ELSE NULL END) [In]
,count(CASE WHEN A.[In/Out of Tolerance] = 'out' THEN 1 ELSE NULL END) [Out]
,count(v.number) [Total]
FROM [dbo].[vw_AgedPipeline] V
INNER JOIN dbo.vw_AP A ON v.Channel = A.Channel
WHERE V.Channel = 'C'
GROUP BY v.channel,v.CurrentStatus
order by v.channel,v.CurrentStatus
)
SELECT [Date]
,channel
,CurrentStatus
,[In]
,[Out]
,[Total]
,convert(Decimal(18,2), (1.0 * [Out]) / ([Total] * 1.0)) [OOTP]
FROM CTE