Count same column twice based on condition - sql

i have the following query to count the Amount field as Countused if Account_Fkey is not null
select Amount as amount2, Count(Amount) as CountUsed from tblGiftCards
where Account_Fkey is not null
group by Amount
result:
amount2 CountUsed
25 3
50 10
100 5
i want to calculate the amount of Amount as Amount not used in the same query where account fkey is null. So, the result will be:
amount2 CountUsed CountUnused
25 3 1
50 10 0
100 5 2
Thanks

You could use a conditional aggregation
select Amount as amount2
, sum( case when Account_Fkey is not null
and Amount is not null then 1 else 0 end) CountUsed
, sum( case when Account_Fkey is null
and Amount is not null then 1 else 0 end) CountNotUsed
from tblGiftCards
group by Amount

Try
select tab.amount2 , CountUsed , CountUnUsed from
(select Amount as amount2, Count(Amount) as CountUsed from tblGiftCards
where Account_Fkey is not null
group by Amount
) tab,
(select Amount as amount2, Count(Amount) as CountUnUsed from tblGiftCards
where Account_Fkey is null
group by Amount
)tab2
where tab.amount2 = tab2.amount2

I would simplify the logic and express this as:
select Amount as amount2, count(Account_Fkey) as CountUsed,
(count(*) - count(Account_Fkey)) as CountNotUsed
from tblGiftCards
where Amount is not null
group by Amount

Related

Calculate percentage of amount using SQL returning values in rows instead of columns

I am trying to find allocation percentage of assets, as to how much % of stocks, cash and bond, of a customer at account level.
portfolio table:
account_no
ticker
portfolio_type
port_percent
position_amt
1
ARKG
Stock
10
100
1
ARKG
Cash
90
100
1
ARKG
Bond
0
100
1
AAPL
Stock
100
200
2
TSLA
Stock
100
50
SQL used:
with total_position_amt as
(
select account_no,
sum(distinct position_amt) as total_position_amt
from portfolio
group by account_no
)
derived_balance as
(
select account_no,
portfolio_type,
(port_percent*position_amt)/100 as total_derived_balance
from portfolio
)
select account_no,
portfolio_type,
(sum(total_derived_balance)/total_position_amt)*100 as asset_percent
from derived_balance a
inner join total_position_amt b
on a.account_no = b.account_no
group by account_no, portfolio_type, position_amt
The above sql returns the value as rows instead of columns which is not what i was expecting.
Incorrect output:
account_no
portfolio_Type
asset_Percent
1
Stock
70
1
Cash
30
1
Bond
0
2
Stock
100
Expected output:
account_no
stock_percent
Cash_Percent
Bond_Percent
1
70
30
0
2
100
0
0
Please advise how I can achieve the expected output.
Instead of aggregating, computing values separately and joining two partial outputs, you can compute your values using window functions in two common table expressions:
first cte will transform to NULL all successive duplicate values of position_amt using the LAG function
second cte will divide sum of total_derived_balance per portfolio by the sum over the total_position_amt, and get the "asset_Percent"
Last step, which is missing to your current query as well, is applying the pivot, by selecting values from the "portfolio_Type" field, using CASE statements and apply aggregation.
WITH cte AS (
SELECT account_no,
portfolio_type,
(port_percent*position_amt)/100 as total_derived_balance,
CASE WHEN LAG(position_amt) OVER(
PARTITION BY account_no, position_amt
ORDER BY position_amt) IS NULL
THEN position_amt
END AS total_position_amt
FROM portfolio
), cte2 AS (
SELECT account_no, portfolio_type,
ROUND(100.0 * SUM(total_derived_balance) OVER(
PARTITION BY account_no, portfolio_type)
/ SUM(total_position_amt) OVER(
PARTITION BY account_no )) AS asset_Percent
FROM cte
)
SELECT account_no,
MAX(CASE WHEN portfolio_type = 'Stock'
THEN asset_Percent ELSE 0 END) AS Stock_Percent,
MAX(CASE WHEN portfolio_type = 'Cash'
THEN asset_Percent ELSE 0 END) AS Cash_Percent,
MAX(CASE WHEN portfolio_type = 'Bond'
THEN asset_Percent ELSE 0 END) AS Bond_Percent
FROM cte2
GROUP BY account_no
Check the demo here.

trying to find customers and their total sales who have shopped for brand1 as well as brand2

I am trying to find the customer who have bought brand1 and brand 2 and also the total sum of the dollar value spent. I have made a sample data base which has all the details.
I am able to get the customers who have bought both the brands, but the dollar amount should equal to the the sum of dollar amount spend in brand 1 and brand 2.
this is the link to the sample database:
https://dbfiddle.uk/?rdbms=oracle_11.2&fiddle=8a478b5743099ea5c76acd08e22c0c53
If I understand correctly, you can just use aggregation with an appropriate having clause:
select individual_id, sum(dollar_value_us)
from transaction_detail_mv
group by individual_id
having sum( case when brand_org_code = 'BRAND1' then 1 else 0 end) > 0 and
sum( case when brand_org_code = 'BRAND2' then 1 else 0 end) > 0
use corelated subquery
select individual_id, SUM (dollar_value_US) from
transaction_detail_mv t1
where t1.brand_org_code IN ('BRAND1','BRAND2')
AND EXISTS (SELECT 1
FROM TRANSACTION_DETAIL_MV t2 WHERE t1.individual_id=t2.individual_id
and BRAND_ORG_CODE = 'BRAND2')
GROUP BY
individual_id
INDIVIDUAL_ID SUM(DOLLAR_VALUE_US)
1 40
2 22
5 22

How to exclude rows from sum but still show them?

I have a table itemsInShippment with the following data:
itemid shippmentid qty
10 1 100
20 1 200
10 2 300
10 3 1000
and table shippments
shippmentid date shippmentstatus supplierid
1 2015-01-12 OK 5000
2 2015-01-17 OK 5000
3 2015-01-17 Cancelled 5000
I need to write a query that shows this details about specific shippment say shipmentid 1. My given parameters are supplierid and date. together they related to one shipment (unique).
For supplierid=5000 and date=2015-01-12 I want to get:
itemid qty qtyInOtherShipments
10 100 300 //1000 is canceled.
20 200 0
My query works fine without considering the cancelled:
SELECT cte.*
FROM
(SELECT
a.itemid, b.date, a.qty,
(coalesce( SUM(a.qty) OVER (PARTITION BY a.itemid), 0) -
coalesce( SUM(a.qty) OVER (PARTITION BY a.itemid, a.shipmentid) ,0) ) AS qtyInOtherShipments,
FROM
itemsInShippment a
LEFT JOIN
shippments b using (shippmentid)
WHERE
b.supplierid = 5000) AS cte
WHERE
cte.date = '2015-01-12'
the cte must be this way as in qtyInOtherShipments I Sum the total qty and then remove my own qty. In order to sum the total qty I can't do WHERE d.date=... inside I must do that outside.
This query gives:
itemid qty qtyInOtherShipments
10 100 1300
20 200 0
I'm having trouble taking under consideration the cancelled shipments.
if I change the Where to :
where b.supplierid = 5000 and b.shippmentstatus not like 'cancelled'
it works... I will see:
itemid qty qtyInOtherShipments
10 100 300
20 200 0
but if I run the query on cancelled shipments (supplierid=5000 and date=2015-01-17) I will get:
itemid qty qtyInOtherShipments
nothing
what I should have get is:
itemid qty qtyInOtherShipments
10 1000 300
so my problem is that I don't want to sum itemid that is related to cancelled but I still want to see this rows.
How do I get the correct result?
You want to exclude canceled items only from sums. So, do not filter them with where, just filter them on sums:
SUM(case when b.shippmentstatus <> 'cancelled' then a.qty end) OVER (PARTITION BY ...
Sum does not take in consideration null, that's why the above works. (When status is canceled the case expression will return null.)
A more efficient variant of Florian's answer exists for PostgreSQL 9.4, the filter clause for an aggregate.
SUM (a.qty) FILTER (WHERE b.shippmentstatus <> 'cancelled') OVER (PARTITION BY ...
See FILTER in the docs for aggregates. It's basically a mini-WHERE clause that applies only for that aggregate.
Thanks to #a_horse_with_no_name for pointing it out earlier.
Try Below query
create table #itemsInShippment (itemid int, shippmentid int, qty int)
insert into #itemsInShippment (itemid, shippmentid, qty)
SELECT 10 as itemid, 1 as shippmentid, 100 as qty UNION
SELECT 20 , 1, 200 UNION
SELECT 10 , 2, 300 UNION
SELECT 10 , 3, 1000
CREATE TABLE #shippments (shippmentid int , dt date, shippmentstatus varchar(50), supplierid int)
insert into #shippments (shippmentid, dt, shippmentstatus,supplierid)
SELECT 1 as shippmentid, '2015-01-12' as dt, 'OK' as shippmentstatus , 5000 as supplierid UNION ALL
SELECT 2, '2015-01-17', 'OK' , 5000 UNION ALL
SELECt 3, '2015-01-17' , 'Cancelled' , 5000
SELECT cte.*
FROM (
select a.itemid,b.dt,a.qty,
(coalesce( SUM(case when shippmentstatus <> 'Cancelled' then a.qty else 0 end) OVER (PARTITION BY a.itemid) ,0) -
coalesce( SUM(case when shippmentstatus <> 'Cancelled' then a.qty else 0 end) OVER (PARTITION BY a.itemid,a.shippmentid) ,0) )
AS qtyInOtherShipments
from #itemsInShippment a
left join #shippments b on a.shippmentid = b.shippmentid
where b.supplierid = 5000 --and shippmentstatus = 'Cancelled'
) as cte
where cte.dt='2015-01-12'

Use of AVG function to determine percentages in a SQL query

I want to know what percentage of records have a given value, where percentage is defined as the number of records that match the value divided by the total number of records. i.e. if there are 100 records, of which 10 have a null value for student_id and 20 have a value of 999999, then the percentage_999999 should be 20%. Can I use the AVG function to determine this?
Option 1:
SELECT year, college_name,
sum(case when student_id IN ('999999999') then 1 else 0 end) as count_id_999999999,
count_id_999999999/total_id as percent_id_999999999,
sum(case when student_id IS NULL then 1 else 0 end) as count_id_NULL,
count_id_NULL/total_id as percent_id_NULL
count(*) as total_id
FROM enrolment_data ed
GROUP BY year, college_name
ORDER BY year, college_name;
Option 2:
SELECT year, college_name,
sum(case when student_id IN ('999999999') then 1 else 0 end) as count_id_999999999,
avg(case when student_id IN ('999999999') then 1.0 else 0 end) as percent_id_999999999,
sum(case when student_id IS NULL then 1 else 0 end) as count_id_NULL,
avg(case when student_id IS NULL then 1.0 else 0 end) as percent_id_NULL
count(*) as total_id
FROM enrolment_data ed
GROUP BY year, college_name
ORDER BY year, college_name;
I created a similar table with 100 records, 20 999999999s, 10 nulls, and 70 1s. This worked for me on SQL Server:
select count(*), StudentID
from ScratchTbl
group by StudentID;
(No column name) StudentID
10 NULL
70 1
20 999999999
select avg(case when StudentID = '999999999' then 1.0 else 0.0 end) as 'pct_9s',
sum(case when StudentID = '999999999' then 1 else 0 end) as 'count_9s',
avg(case when StudentID is null then 1.0 else 0.0 end) as 'pct_null',
sum(case when StudentID is null then 1 else 0 end) as 'count_null'
from ScratchTbl
pct_9s count_9s pct_null count_null
0.200000 20 0.100000 10
I have a feeling that your use of the group by clause could be creating problems for you, perhaps select a specific year/college using the where clause (and get rid of the group by line) and see if you get the results you expect.

how to have a where clause in an aggregate function SQL Server

OK, I have a table that looks like this:
ID AMOUNT PAID
1 50.00 Y
2 100.00 N
3 200.00 Y
And I want to see something like:
Total Due Paid
350.00 1 2
So my SQL would look like (in my head...it doesnt work that way, which is why I'm here )
select sum(amount)
,count(paid where paid='y') as due
,count(paid where paid='n') as paid
from sometable where something=somethingelse
select sum(amount) as total,
sum(case paid when 'N' then 1 else 0 end) as due,
sum(case paid when 'Y' then 1 else 0 end) as paid
from sometable where something=somethingelse
One more option
SELECT SUM(AMOUNT) AS Total,
COUNT(CASE WHEN PAID = 'Y' THEN PAID END) AS Paid,
COUNT(CASE WHEN PAID = 'N' THEN PAID END) AS Due
FROM sometable
WHERE something = somethingelse
Demo om SQLFiddle