Change sub query to join - sql

all I have used subquery[Below] to identify the percentage. But I need a query without subquery. Can anyone please help me, how to use joins to calculate the percentage?
Query used
SELECT 'Dropping_Percentage',
( Cast(dropped_count AS DECIMAL(16, 9)) / Cast(new_count AS DECIMAL(16, 9
)) ) *
100
FROM (SELECT count AS New_count,
'1' a
FROM new_count)a,
(SELECT Count(*) Dropped_count,
'1' b
FROM pfo_bhi_new N
RIGHT JOIN pfo_bhi_old o
ON o.id_membid_claimid_c = N.id_membid_claimid_c
WHERE N.id_membid_claimid_c IS NULL)c
WHERE a.a = c.b

"I need a query without subquery."
Answer:
SELECT Count(*) All_count
, SUM(CASE WHEN N.id_membid_claimid_c IS NULL THEN 1 else 0 end) as Dropped_count
, SUM(CASE WHEN N.id_membid_claimid_c IS NULL THEN 1 else 0 end) * 1.0 / Count(*) as Dropping_Percentage
FROM pfo_bhi_new N
RIGHT JOIN pfo_bhi_old o ON o.id_membid_claimid_c = N.id_membid_claimid_c
Explanation:
Reviewing the main query with assumptions about the content:
SELECT Count(*) Dropped_count, '1' b
FROM pfo_bhi_new N
RIGHT JOIN pfo_bhi_old o ON o.id_membid_claimid_c = N.id_membid_claimid_c
WHERE N.id_membid_claimid_c IS NULL
should give you the number of records in pfo_bhi_old that do not appear in pfo_bhi_new. Assumption here is that you need to do the total based on the existing right join. All the matching and non matching records.
Therefore It's possible to count all the existing records by removing the where clause,
COUNT(*) would give you that total.
Next you want to count the ones where there's no match which will give you the "Dropping count" the value you had before with the where clause, that is where ones where the id_membid_claimid_c was null that is SUM(CASE WHEN N.id_membid_claimid_c IS NULL THEN 1 else 0 end). Said differently it will add 1 to the sum only when the id_membid_claimid_c field is null, otherwise it will add zero (0).
I've multiplied the numerator by 1.0 to force SQL Server to use decimal values and make the query easier to read.
Here's what it should look like if you needed to use decimals(16,9) as the result.
SELECT Count(*) All_count
, SUM(CASE WHEN N.id_membid_claimid_c IS NULL THEN 1 else 0 end) as Dropped_count
, SUM(CASE WHEN N.id_membid_claimid_c IS NULL THEN 1 else 0 end) * 1.0 / Count(*) as Dropping_Percentage
, CAST(SUM(CASE WHEN N.id_membid_claimid_c IS NULL THEN 1 else 0 end) AS DECIMAL(16,9)) / CAST(Count(*) AS DECIMAL(16,9)) as Dropping_Pct_16_9
FROM pfo_bhi_new N
RIGHT JOIN pfo_bhi_old o ON o.id_membid_claimid_c = N.id_membid_claimid_c

Related

Combining multiple rows in a single row

I have SQL Query like this in SSMS
select distinct (b.TransactionNumber),
(case when b.Amount > 0 then c.total else 0 end) as 'Total Sales',
(case when b.TenderID = 1 then b.Amount else 0 end) as 'Cash',
(case when b.TenderID = 20 then b.Amount else 0 end) as 'Gift Certificates'
from [Transaction] c
inner join TenderEntry b on c.TransactionNumber = b.TransactionNumber
but the output is(see image for reference)
This should be the expected output(see image for reference)
I would expect one row per transaction number, especially given your use of select distinct:
select t.TransactionNumber, te.total as total_sales,
sum(case when t.TenderID = 1 then t.Amount else 0 end) as Cash,
sum(case when t.TenderID = 20 then t.Amount else 0 end) as Gift_Certificates
from TenderEntry te join
Transaction t
on te.TransactionNumber = t.TransactionNumber
group by t.TransactionNumber, te.total;
This produces one row per transaction.
Note the changes to the query:
The table aliases are meaningful (i.e. abbreviations of table names) rather than arbitrary letters.
The column aliases do not use single quotes. Only use single quotes for string and date constants.
The column aliases have been simplified so they do not need to be escaped.
It occurs to me that you might want to "list" the cash and gifts in the two columns. This would look like:
select TransactionNumber,
max(case when seqnum = 1 then total end) as total_sales,
sum(case when tenderId = 1 then amount end) as cash,
sum(case when tenderId = 20 then amount end) as Gift_Certificates
from (select t.TransactionNumber, te.total, t.amount, t.TenderID,
row_number() over (partition by t.TransactionNumber, t.TenderId order by t.amount) as seqnum
from TenderEntry te join
Transaction t
on te.TransactionNumber = t.TransactionNumber
where tenderid in (1, 20)
) x
group by t.TransactionNumber, seqnum;
This is only a partial answer, but I put it here because I cannot fit it into comments well enough.
It is likely that you do not want the DISTINCT component in the select. SELECT DISTINCT find all unique rows. So if you have 3 rows which all have some differences, it will show all three. If, on the other hand, there were two rows the same (e.g., they paid for a $100 item with two $50 vouchers) it would just ignore one of them.
Instead, you probably need to become familiar with 'GROUP BY' which allows you to find totals etc across multiple rows.
For example (although not tested)
select
(b.TransactionNumber),
(case when b.Amount > 0 then c.total else 0 end) as 'Total Sales',
SUM(case when b.TenderID = 1 then b.Amount else 0 end) as 'Cash',
SUM(case when b.TenderID = 20 then b.Amount else 0 end) as 'Gift Certificates'
from [Transaction] c
inner join TenderEntry b on c.TransactionNumber = b.TransactionNumber
GROUP BY (b.TransactionNumber, (case when b.Amount > 0 then c.total else 0 end))
In the above, I have removed the DISTINCT, added 'SUM' for the two transaction values (cash/certificates) and the GROUP BY across the TransactionNumber and Total sales (as that seems to be common across the transaction).
For the above data, what that would produce is 1 line of data for TransactionNumber = 1, with sales = 250 (as all the lines have that), and totals for cash and gift certificates (150 and 100 respectively if my maths is correct).
However, that is not your desired answer, - you want this transaction to go over two lines. Firstly, are you sure of that?
If you do, then we need another criteria by which to group them and you will need to specify that e.g., why does this transaction's report need to go over two lines rather than just one?
Other notes
'Transaction' is a special word within SQL Server. You are allowed to use it, but I would consider renaming the table.

Sum a column and perform more calculations on the result? [duplicate]

This question already has an answer here:
How to use an Alias in a Calculation for Another Field
(1 answer)
Closed 3 years ago.
In my query below I am counting occurrences in a table based on the Status column. I also want to perform calculations based on the counts I am returning. For example, let's say I want to add 100 to the Snoozed value... how do I do this? Below is what I thought would do it:
SELECT
pu.ID Id, pu.Name Name,
COUNT(*) LeadCount,
SUM(CASE WHEN Status = 'Working' THEN 1 ELSE 0 END) AS Working,
SUM(CASE WHEN Status = 'Uninterested' THEN 1 ELSE 0 END) AS Uninterested,
SUM(CASE WHEN Status = 'Converted' THEN 1 ELSE 0 END) AS Converted,
SUM(CASE WHEN SnoozedId > 0 THEN 1 ELSE 0 END) AS Snoozed,
Snoozed + 100 AS Test
FROM
Prospects p
INNER JOIN
ProspectsUsers pu on p.OwnerId = pu.SalesForceId
WHERE
p.Store = '108'
GROUP BY
pu.Name, pu.Id
ORDER BY
Name
I get this error:
Invalid column name 'Snoozed'.
How can I take the value of the previous SUM statement, add 100 to it, and return it as another column? What I was aiming for is an additional column labeled Test that has the Snooze count + 100.
You can't use one column to create another column in the same way that you are attempting. You have 2 options:
Do the full calculation (as #forpas has mentioned in the comments above)
Use a temp table or table variable to store the data, this way you can get the first 5 columns, and then you can add the last column or you can select from the temp table and do the last column calculations from there.
You can not use an alias as a column reference in the same query. The correct script is:
SELECT
pu.ID Id, pu.Name Name,
COUNT(*) LeadCount,
SUM(CASE WHEN Status = 'Working' THEN 1 ELSE 0 END) AS Working,
SUM(CASE WHEN Status = 'Uninterested' THEN 1 ELSE 0 END) AS Uninterested,
SUM(CASE WHEN Status = 'Converted' THEN 1 ELSE 0 END) AS Converted,
SUM(CASE WHEN SnoozedId > 0 THEN 1 ELSE 0 END)+100 AS Snoozed
FROM
Prospects p
INNER JOIN
ProspectsUsers pu on p.OwnerId = pu.SalesForceId
WHERE
p.Store = '108'
GROUP BY
pu.Name, pu.Id
ORDER BY
Name
MSSQL does not allow you to reference fields (or aliases) in the SELECT statement from within the same SELECT statement.
To work around this:
Use a CTE. Define the columns you want to select from in the CTE, and then select from them outside the CTE.
;WITH OurCte AS (
SELECT
5 + 5 - 3 AS OurInitialValue
)
SELECT
OurInitialValue / 2 AS OurFinalValue
FROM OurCte
Use a temp table. This is very similar in functionality to using a CTE, however, it does have different performance implications.
SELECT
5 + 5 - 3 AS OurInitialValue
INTO #OurTempTable
SELECT
OurInitialValue / 2 AS OurFinalValue
FROM #OurTempTable
Use a subquery. This tends to be more difficult to read than the above. I'm not certain what the advantage is to this - maybe someone in the comments can enlighten me.
SELECT
5 + 5 - 3 AS OurInitialValue
FROM (
SELECT
OurInitialValue / 2 AS OurFinalValue
) OurSubquery
Embed your calculations. opinion warning This is really sloppy, and not a great approach as you end up having to duplicate code, and can easily throw columns out-of-sync if you update the calculation in one location and not the other.
SELECT
5 + 5 - 3 AS OurInitialValue
, (5 + 5 - 3) / 2 AS OurFinalValue
You can't use a column alias in the same select. The column alias do not precedence / sequence; they are all created after the eval of the select result, just before group by and order by.
You must repeat code :
SELECT
pu.ID Id,pu.Name Name,
COUNT(*) LeadCount,
SUM(CASE WHEN Status = 'Working' THEN 1 ELSE 0 END) AS Working,
SUM(CASE WHEN Status = 'Uninterested' THEN 1 ELSE 0 END) AS Uninterested,
SUM(CASE WHEN Status = 'Converted' THEN 1 ELSE 0 END) AS Converted,
SUM(CASE WHEN SnoozedId > 0 THEN 1 ELSE 0 END) AS Snoozed,
SUM(CASE WHEN SnoozedId > 0 THEN 1 ELSE 0 END)+ 100 AS Test
FROM
Prospects p
INNER JOIN
ProspectsUsers pu on p.OwnerId = pu.SalesForceId
WHERE
p.Store = '108'
GROUP BY
pu.Name, pu.Id
ORDER BY
Name
If you don't want to repeat the code, use a subquery
SELECT
ID, Name, LeadCount, Working, Uninterested,Converted, Snoozed, Snoozed +100 AS test
FROM
(SELECT
pu.ID Id,pu.Name Name,
COUNT(*) LeadCount,
SUM(CASE WHEN Status = 'Working' THEN 1 ELSE 0 END) AS Working,
SUM(CASE WHEN Status = 'Uninterested' THEN 1 ELSE 0 END) AS Uninterested,
SUM(CASE WHEN Status = 'Converted' THEN 1 ELSE 0 END) AS Converted,
SUM(CASE WHEN SnoozedId > 0 THEN 1 ELSE 0 END) AS Snoozed
FROM Prospects p
INNER JOIN ProspectsUsers pu on p.OwnerId = pu.SalesForceId
WHERE p.Store = '108'
GROUP BY pu.Name, pu.Id) t
ORDER BY Name
or a view

Using Math to calculate a percentage

OK, imagine I have a single column with 200 rows, in each row is a value either 4,8,2 or 0. Is there a way to divide the COUNT of times 2 and 0 occur against the COUNT of times 4 and 8 occur? Like this:
(COUNT(2, 0) / COUNT(4, 8)) * 100
Thanks! :)
EDIT:
Got a Divide By 0 error on the following statement, although doing COUNT(*) where Completion_Event_Type_Key IN (2,0) OR (8,4) returns over 3000000 rows
SELECT DISTINCT
SD.Last_Secondary_School_Name AS 'School', ((SELECT COUNT(*) where CC.Completion_Event_Type_Key IN (2,0))/(SELECT COUNT(*) where CC.Completion_Event_Type_Key IN (4, 8)))*100
FROM [AUTDataWarehouse].[dbo].[Fact_Admission] AS FA
INNER JOIN [AUTDataWarehouse].[dbo].[Dim_Student_Demographics] AS SD ON SD.Student_Demographics_Key = FA.Student_Demographics_Key
INNER JOIN [AUTDataWarehouse].[dbo].[Fact_SDR_Course_Completion] AS CC ON FA.Student_Demographics_Key = CC.Student_Demographics_Key
GROUP BY
SD.Last_Secondary_School_Name, CC.Completion_Event_Type_Key
ORDER BY SD.Last_Secondary_School_Name
You can do it like this:
select
sum(case when value in (2,0) then 1 else 0 end) /
sum(case when value in (4,8) then 1 else 0 end) * 100
from table
Beware division by zero, though. You might need special handling if there could be no rows with 4,8.
Given the query in your question, your SQL should look something like this:
SELECT
SD.Last_Secondary_School_Name AS 'School',
sum(case when CC.Completion_Event_Type_Key IN (2,0) then 1 else 0 end) /
sum(case when CC.Completion_Event_Type_Key IN (4,8) then 1 else 0 end) * 100
FROM [AUTDataWarehouse].[dbo].[Fact_Admission] AS FA
INNER JOIN [AUTDataWarehouse].[dbo].[Dim_Student_Demographics] AS SD ON SD.Student_Demographics_Key = FA.Student_Demographics_Key
INNER JOIN [AUTDataWarehouse].[dbo].[Fact_SDR_Course_Completion] AS CC ON FA.Student_Demographics_Key = CC.Student_Demographics_Key
GROUP BY SD.Last_Secondary_School_Name, CC.Completion_Event_Type_Key
ORDER BY SD.Last_Secondary_School_Name

Oracle sql tuning multiple counts and distinct

Hey dudes i have the following query running on oracle.
SELECT DISTINCT
T_TRATAMIENTO.CampaignID AS CAMPAIGNID,
T_TRATAMIENTO.OfferID AS OFFERID,
T_CALENDARIO.ActualDate AS ACTUALDATE,
count(CASE T_TRATAMIENTO.CntrlTreatmtFlag WHEN 0 THEN T_TRATAMIENTO.TreatmentSize END) as NUM_OF_OFFERS,
count(CASE T_TRATAMIENTO.CntrlTreatmtFlag WHEN 1 THEN T_TRATAMIENTO.TreatmentSize END) as NUM_OF_OFFERS_CG,
count (distinct (case T_TRATAMIENTO.CntrlTreatmtFlag when 0 then T_TRATAMIENTO.OfferHistoryID END)) as NUM_OFF_VERS,
count (distinct (case T_TRATAMIENTO.CntrlTreatmtFlag when 1 then T_TRATAMIENTO.OfferHistoryID END)) as NUM_OFF_VERS_CG,
count(distinct (CASE WHEN T_TRATAMIENTO.CntrlTreatmtFlag = 0 and T_ESTATUSCONTACTO.CountsAsContact=1 THEN T_HISTORIALCONTACTO.CustomerID END)) as UNIQUE_RECIPIENTS,
count(distinct (CASE T_TRATAMIENTO.CntrlTreatmtFlag WHEN 1 THEN T_HISTORIALCONTACTO.CustomerID END)) as UNIQUE_RECIP_CG FROM
T_ESTATUSCONTACTO,
T_CALENDARIO,
T_TRATAMIENTO
LEFT OUTER JOIN
T_HISTORIALCONTACTO ON
T_TRATAMIENTO.PackageID = T_HISTORIALCONTACTO.PackageID
WHERE
T_HISTORIALCONTACTO.CellID = T_TRATAMIENTO.CellID
AND
T_HISTORIALCONTACTO.ContactStatusID = T_ESTATUSCONTACTO.ContactStatusID
AND
T_HISTORIALCONTACTO.DateID = T_CALENDARIO.DateID
AND
T_TRATAMIENTO.HasDetailHistory = 0 GROUP BY
T_TRATAMIENTO.CampaignID,
T_TRATAMIENTO.OfferID, T_CALENDARIO.ActualDate;
Table T_HISTORIALCONTACTO has 80 million records, still growing, other tables just less than 100 records, and thereĀ“s timing in response. Also making full scan. I had already implement indexes but still shows slow performance.
How can i tune this sql query? What would u recommend. I really apprecciate ur help. Thanxs in advance
Firstly you don't need first DISTINCT because you have group by on first 3 columns (CAMPAIGNID, OFFERID,ACTUALDATE).
Secondly I recommend avoid "cartesian merge join" which is very consuming. So try this new join approach.
If cartesian join will still occur try to make "join" between these tables: T_ESTATUSCONTACTO, T_CALENDARIO, T_TRATAMIENTO.
Currently you have only separated joins to T_HISTORIALCONTACTO
SELECT
T_TRATAMIENTO.CampaignID AS CAMPAIGNID,
T_TRATAMIENTO.OfferID AS OFFERID,
T_CALENDARIO.ActualDate AS ACTUALDATE,
count(CASE T_TRATAMIENTO.CntrlTreatmtFlag WHEN 0 THEN T_TRATAMIENTO.TreatmentSize END) as NUM_OF_OFFERS,
count(CASE T_TRATAMIENTO.CntrlTreatmtFlag WHEN 1 THEN T_TRATAMIENTO.TreatmentSize END) as NUM_OF_OFFERS_CG,
count (distinct (case T_TRATAMIENTO.CntrlTreatmtFlag when 0 then T_TRATAMIENTO.OfferHistoryID END)) as NUM_OFF_VERS,
count (distinct (case T_TRATAMIENTO.CntrlTreatmtFlag when 1 then T_TRATAMIENTO.OfferHistoryID END)) as NUM_OFF_VERS_CG,
count(distinct (CASE WHEN T_TRATAMIENTO.CntrlTreatmtFlag = 0 and T_ESTATUSCONTACTO.CountsAsContact=1 THEN T_HISTORIALCONTACTO.CustomerID END)) as UNIQUE_RECIPIENTS,
count(distinct (CASE T_TRATAMIENTO.CntrlTreatmtFlag WHEN 1 THEN T_HISTORIALCONTACTO.CustomerID END)) as UNIQUE_RECIP_CG FROM
T_TRATAMIENTO
LEFT OUTER JOIN T_HISTORIALCONTACTO ON T_HISTORIALCONTACTO.CellID = T_TRATAMIENTO.CellID
JOIN T_CALENDARIO on T_HISTORIALCONTACTO.DateID = T_CALENDARIO.DateID
JOIN T_ESTATUSCONTACTO on T_HISTORIALCONTACTO.ContactStatusID = T_ESTATUSCONTACTO.ContactStatusID
WHERE
T_TRATAMIENTO.HasDetailHistory = 0
GROUP BY
T_TRATAMIENTO.CampaignID,
T_TRATAMIENTO.OfferID, T_CALENDARIO.ActualDate;

SQL - How to count yes and no items

I am using SQL Server 2008.
I am writing a query where I need to count how many yesses (1) and how many nos (0 or NULL).
SELECT B.Brand, B.BrandID, COUNT(M.ModelID) AS TotalModels
FROM Brands B LEFT JOIN Models M ON B.BrandID = M.BrandID
GROUP BY B.Brand, B.BrandID
ORDER BY B.Brand
There's another field called IsBestValue in the Model table that will be NULL, 0, or 1. I want to be able to count TotalBestValueYes, TotalBestValueNo, and TotalBestValueNULL.
A long time ago...I use to use something like ..
(CASE WHEN IsBestValue = 1 END) // ADD ONE TO TotalBestValueYes
(CASE WHEN IsBestValue = 0 END) // ADD ONE TO TotalBestValueNo
(CASE WHEN IsBestValue = NULL END) // ADD ONE TO TotalBestValueNULL
Is using CASE in the fashion a good idea? Bad idea? Overkill?
Is there are better way to count yesses and nos and NULLs?
I don't see anything wrong with using the CASE like that if this is what you mean.
SELECT B.Brand,
B.BrandID,
COUNT(M.ModelID) AS TotalModels,
SUM((CASE WHEN M.IsBestValue = 1 THEN 1 ELSE 0 END)) TotalBestValueYes,
SUM((CASE WHEN M.IsBestValue = 0 THEN 1 ELSE 0 END)) TotalBestValueNo,
SUM((CASE WHEN M.IsBestValue IS NULL THEN 1 ELSE 0 END)) TotalBestValueNull,
FROM Brands B
LEFT JOIN Models M ON B.BrandID = M.BrandID
GROUP BY B.Brand,
B.BrandID
ORDER BY B.Brand
The is the perfect case for CASE (pun intended).
CASE is a very well optimized operator and was designed for just such a usage scenario.
The normal syntax for a conditional count is along the lines of:
SELECT SUM (CASE WHEN x=y then 1 ELSE 0 END) as 'XequalsY'
...
select count(nullif(IsBestValue, 0)) as TotalBestValueYes,
count(nullif(IsBestValue, 1)) as TotalBestValueNo,
count(case when IsBestValue is null then 1 end) as TotalBestValueNull