SQL Condition for sum - sql

I have a sql statement with many inner join tables, as you can see below I have many conditional SUM statements , these sums are giving me wrong (very large) numbers as the inner join is repeating the same values in my source select pool. I was wondering id there is a way to limit these sum conditions lets say to EMPLIDs. The code is :
SELECT
A.EMPL_CTG,
B.DESCR AS PrName,
SUM(A.CURRENT_COMPRATE) AS SALARY_COST_BUDGET,
SUM(A.BUDGET_AMT) AS BUDGET_AMT,
SUM(A.BUDGET_AMT)*100/SUM(A.CURRENT_COMPRATE) AS MERIT_GOAL,
SUM(C.FACTOR_XSALARY) AS X_Programp,
SUM(A.FACTOR_XSALARY) AS X_Program,
COUNT(A.EMPLID) AS EMPL_CNT,
COUNT(D.EMPLID),
SUM(CASE WHEN A.PROMOTION_SECTION = 'Y' THEN 1 ELSE 0 END) AS PRMCNT,
SUM(CASE WHEN A.EXCEPT_IND = 'Y' THEN 1 ELSE 0 END) AS EXPCNT,
(SUM(CASE WHEN A.PROMOTION_SECTION = 'Y' THEN 1 ELSE 0 END)+SUM(CASE WHEN A.EXCEPT_IND = 'Y' THEN 1 ELSE 0 END))*100/(COUNT(A.EMPLID)) AS PEpercent
FROM
EMP_DTL A INNER JOIN EMPL_CTG_L1 B ON A.EMPL_CTG = B.EMPL_CTG
INNER JOIN
ECM_PRYR_VW C ON A.EMPLID=C.EMPLID
INNER JOIN ECM_INELIG D on D.EMPL_CTG=A.EMPL_CTG and D.YEAR=YEAR(getdate())
WHERE
A.YEAR=YEAR(getdate())
AND B.EFF_STATUS='A'
GROUP BY
A.EMPL_CTG,
B.DESCR
ORDER BY B.DESCR
I already tried moving D.YEAR=YEAR(getdate()) to the where clause. Any help would be greatly appereciated

The probable reason of your very large numbers is probably due to the result of Cartesian product of joining A -> B, A -> C and A -> D where tables C and D appear to have multiple records. So, just example... if A has 10 records, and C has 10 for each of the A records, you now have 10 * 10 records... Finally, join that to D table with 10 records, you now have 10 * 10 * 10 for each "A", thus your bloated answers.
Now, how to resolve. I have taken your "C" and "D" tables and "Pre-Aggregated" those counts based on the join column basis. This way, they will each have only 1 record with the total already computed at that level, joined back to A table and you lose your Cartesian issue.
Now, for table B, it appears that is a lookup table only and would only be a single record result anyhow.
SELECT
A.EMPL_CTG,
B.DESCR AS PrName,
SUM(A.CURRENT_COMPRATE) AS SALARY_COST_BUDGET,
SUM(A.BUDGET_AMT) AS BUDGET_AMT,
SUM(A.BUDGET_AMT)*100/SUM(A.CURRENT_COMPRATE) AS MERIT_GOAL,
PreAggC.X_Programp,
SUM(A.FACTOR_XSALARY) AS X_Program,
COUNT(A.EMPLID) AS EMPL_CNT,
PreAggD.DCount,
SUM(CASE WHEN A.PROMOTION_SECTION = 'Y' THEN 1 ELSE 0 END) AS PRMCNT,
SUM(CASE WHEN A.EXCEPT_IND = 'Y' THEN 1 ELSE 0 END) AS EXPCNT,
( SUM( CASE WHEN A.PROMOTION_SECTION = 'Y' THEN 1 ELSE 0 END
+ CASE WHEN A.EXCEPT_IND = 'Y' THEN 1 ELSE 0 END ) *
100 / COUNT(A.EMPLID) AS PEpercent
FROM
EMP_DTL A
INNER JOIN EMPL_CTG_L1 B
ON A.EMPL_CTG = B.EMPL_CTG
AND B.EFF_STATUS='A'
INNER JOIN ( select
C.EMPLID,
SUM(C.FACTOR_XSALARY) AS X_Programp
from
ECM_PRYR_VW C
group by
C.EMPLID ) PreAggC
ON A.EMPLID = PreAggC.EMPLID
INNER JOIN ( select
D.EMPLID,
COUNT(*) AS DCount
from
ECM_INELIG D
where
D.Year = YEAR( getdate())
group by
D.EMPLID ) PreAggD
ON A.EMPLID = PreAggD.EMPLID
WHERE
A.YEAR=YEAR(getdate())
GROUP BY
A.EMPL_CTG,
B.DESCR
ORDER BY
B.DESCR

Related

Convert multiple rows to columns but as one rows

How do i achieve achieve all this records under one row since every employee has one of each NHIF, NSSF and KRA number?
Below is the query i used but the all appear separate rows.
SELECT DISTINCT
hrmemployeehdr.employeeslno,
hrmemployeehdr.employeecode,
hrmemployeehdr.employeefirstname,
hrmemployeehdr.employeemiddlename,
hrmemployeehdr.employeelastname,
hrmDesignationHdr.DesignationName,
hrmemployeehdr.DateOfBirth,
hrmemployeehdr.DateOfJoin,
hlocationhdr.locationname,
hrmEmployeeIdentityDtl.IDProofReferenceNo AS [National ID],
(CASE
WHEN hrmEmployeeDeductionSettingsDtl.DeductionCode = 1 THEN hrmEmployeeDeductionSettingsDtl.EmployeeRegID
ELSE ''
END) AS NHIF,
(CASE
WHEN hrmEmployeeDeductionSettingsDtl.DeductionCode = 2 THEN hrmEmployeeDeductionSettingsDtl.EmployeeRegID
ELSE ''
END) AS NSSF,
(CASE
WHEN hrmEmployeeDeductionSettingsDtl.DeductionCode = 3 THEN hrmEmployeeDeductionSettingsDtl.EmployeeRegID
ELSE ''
END) AS KRA,
hrmemployeestatusdtl.Email AS [Employee Email],
huser.email AS [User Account Email],
hrmEmployeeGradeHdr.GradeName,
hDepartment.DepartmentName
FROM hrmemployeehdr
JOIN hrmemployeestatusdtl ON hrmemployeestatusdtl.employeeslno = hrmemployeehdr.employeeslno
JOIN hdivision ON hdivision.divisioncode = hrmemployeestatusdtl.divisioncode
JOIN hlocationhdr ON hlocationhdr.locationcode = hrmemployeestatusdtl.workinglocationcode
JOIN hDepartment ON hDepartment.DepartmentCode = hrmemployeestatusdtl.DepartmentCode
JOIN hrmDesignationHdr ON hrmDesignationHdr.DesignationCode = hrmemployeestatusdtl.DesignationCode
JOIN hrmEmployeeCategoryHdr ON hrmEmployeeCategoryHdr.CategoryCode = hrmemployeestatusdtl.CategoryCode
JOIN hrmEmployeeGradeHdr ON hrmEmployeeGradeHdr.GradeCode = hrmemployeestatusdtl.GradeCode
LEFT JOIN huser ON huser.employeeslno = hrmemployeehdr.employeeslno
JOIN hMasterValue ON hMasterValue.MasterValueID = hrmemployeestatusdtl.MasterValue_EmploymentStatusID
JOIN hrmEmployeeIdentityDtl ON hrmEmployeeIdentityDtl.EmployeeSlno = hrmemployeehdr.EmployeeSlno
INNER JOIN hMasterValue a ON a.MasterValueID = hrmEmployeeIdentityDtl.MasterValue_IDProofTypeID
INNER JOIN hrmEmployeeDeductionSettingsDtl ON hrmEmployeeDeductionSettingsDtl.EmployeeSlno = hrmemployeehdr.EmployeeSlno
LEFT JOIN hrmDeductionHdr ON hrmDeductionHdr.DeductionCode = hrmEmployeeDeductionSettingsDtl.DeductionCode
WHERE hrmemployeestatusdtl.employeeslno NOT IN (SELECT hrmemploymentstoppageandtermination.employeeslno
FROM hrmemploymentstoppageandtermination)
AND hrmEmployeeIdentityDtl.MasterValue_IDProofTypeID = 2741005
--and hrmemployeestatusdtl.email = huser.email
--and huser.isemployee = 1
-- select * from huser
ORDER BY employeefirstname ASC;
Use conditional aggregation on the detail table to condense all those rows into one for each employee. Something like:
with edet as (select employeeslno,
max(CASE DeductionCode when 1 THEN EmployeeRegID ELSE '' END) AS NHIF,
max(CASE DeductionCode when 2 THEN EmployeeRegID ELSE '' END) AS NSSF,
max(CASE DeductionCode when 3 THEN EmployeeRegID ELSE '' END) AS KRA
from dbo.hrmEmployeeDeductionSettingsDtl
group by employeeslno)
select emp.employeeslno, ...,
edet.NHIF, edet.NSSF, edet.KRA, ...
from dbo.hrmemployeehdr as emp
inner join edet on emp.employeeslno = edet.employeeslno
...
order by ...
;
Notice the formatting changes that HELP everyone read and understand the code as well as the good habits of using aliases, schema-qualified table names, statement terminator, etc. As already mentioned, the other joins might be contributing to the problem - but this addresses the 1:3 relationship between the header and detail table.

Return a Count of 0 When No Rows

OK, I've looked this up and tried a number of solutions, but can't get it to work. I'm a bit of a novice. Here's my original query - how can I get it to return 0 for an account when there are no results in the student table?
SELECT a.NAME
,count(s.student_sid)
FROM account a
JOIN inst i ON a.inst_sid = i.root_inst_sid
JOIN inst_year iy ON i.inst_sid = iy.inst_sid
JOIN student s ON iy.inst_year_sid = s.inst_year_sid
WHERE s.demo = 0
AND s.STATE = 1
AND i.STATE = 1
AND iy.year_sid = 16
AND a.account_sid IN (
20187987
,20188576
,20188755
,52317128
,20189249
)
GROUP BY a.NAME;
Use an outer join, moving the condition on that table into the join:
select a.name, count(s.student_sid)
from account a
join inst i on a.inst_sid = i.root_inst_sid
join inst_year iy on i.inst_sid = iy.inst_sid
left join student s on iy.inst_year_sid = s.inst_year_sid
and s.demo = 0
and s.state = 1
where i.state = 1
and iy.year_sid = 16
and a.account_sid in (20187987, 20188576, 20188755, 52317128, 20189249)
group by a.name;
count() does not count null values, which s.student_sid will be if no rows join from student.
You need to LEFT JOIN and then SUM() over the group where s.student_sid is not null:
select
a.name,
sum(case when s.student_sid is null then 0 else 1 end) as student_count
from account a
join inst i on a.inst_sid = i.root_inst_sid
join inst_year iy on i.inst_sid = iy.inst_sid
left join student s
on iy.inst_year_sid = s.inst_year_sid
and s.demo = 0
and s.state = 1
where i.state = 1
and iy.year_sid = 16
and a.account_sid in (20187987, 20188576, 20188755, 52317128, 20189249)
group by a.name;
This is assuming that all of the fields in the student table that you are filtering on are optional. If you don't want to enforce removal of records where, say, s.state does not equal 1, then you need to move the s.state=1 predicate into the WHERE clauses.
If, for some reason, you are getting duplicate student IDs and students are being counted twice, then you can change the aggregate function to this:
count(distinct s.student_id) as student_count
...which is safe to do as count(distinct ...) ignores null values.

Count Frequency based on Bit Flag

SELECT
ROW_NUMBER() OVER (PARTITION BY dicei.IsLocked ORDER BY DocumentInstanceChapterExpanded.PK_DocumentInstanceChapterExpanded)
,DocumentInstance.PK_DocumentInstance
,DocumentInstanceChapterExpanded.PK_DocumentInstanceChapterExpanded
,dicei.IsLocked
FROM DocumentInstance INNER JOIN
DocumentInstanceChapter ON DocumentInstance.PK_DocumentInstance = DocumentInstanceChapter.FK_DocumentInstance INNER JOIN
DocumentInstanceChapter AS DocumentInstanceChapter_1 ON
DocumentInstanceChapter.PK_DocumentInstanceChapter = DocumentInstanceChapter_1.FK_DocumentInstanceChapter INNER JOIN
DocumentInstanceChapterExpanded ON
DocumentInstanceChapter_1.PK_DocumentInstanceChapter = DocumentInstanceChapterExpanded.FK_DocumentInstanceChapter INNER JOIN
DocumentInstanceChapterExpanded AS DocumentInstanceChapterExpanded_1 ON
DocumentInstanceChapter.PK_DocumentInstanceChapter = DocumentInstanceChapterExpanded_1.FK_DocumentInstanceChapter INNER JOIN
DocumentInstanceChapterExpandedItem AS dicei ON
DocumentInstanceChapterExpanded.PK_DocumentInstanceChapterExpanded = dicei.FK_DocumentInstanceChapterExpanded
WHERE (DocumentInstance.PK_DocumentInstance = 455)
AND DocumentInstanceChapterExpanded_1.PK_DocumentInstanceChapterExpanded = 50730
As you can see the picture what i wanted to do was add a Column which would indicate
**Result Expected**
ExpandeditemKey IsLocked StatusColumn
50797 0 Mixed
50797 0 Mixed
50797 1 Mixed
50797 1 Mixed
50797 1 Mixed
50798 1 Lock
50798 1 Lock
50798 1 Lock
If it contains 0 and 1 'Mixed'
If it contains 1 only 'Lock'
If it contains 0 only 'Unlock'
it does not necessary need to be string column, i tried using OverBy Clause if i can used Partition by for the Islock Bit Field but was not able to
Thanks for having a look.
I suggest usng MIN/MAX as window functions, then you can add a case expression that works across a partition. e.g.
SELECT
ROW_NUMBER() OVER (PARTITION BY dicei.IsLocked ORDER BY DocumentInstanceChapterExpanded.PK_DocumentInstanceChapterExpanded)
, DocumentInstance.PK_DocumentInstance
, DocumentInstanceChapterExpanded.PK_DocumentInstanceChapterExpanded
, dicei.IsLocked
, case when dicei.isLockedMin <> dicei.isLockedMax then 'Mixed'
when dicei.isLockedMax = 0 then 'Unlocked'
else 'Locked'
end StatusColumn
FROM DocumentInstance
INNER JOIN DocumentInstanceChapter ON DocumentInstance.PK_DocumentInstance = DocumentInstanceChapter.FK_DocumentInstance
INNER JOIN DocumentInstanceChapter AS documentinstancechapter_1 ON DocumentInstanceChapter.PK_DocumentInstanceChapter = documentinstancechapter_1.FK_DocumentInstanceChapter
INNER JOIN DocumentInstanceChapterExpanded ON documentinstancechapter_1.PK_DocumentInstanceChapter = DocumentInstanceChapterExpanded.FK_DocumentInstanceChapter
INNER JOIN DocumentInstanceChapterExpanded AS documentinstancechapterexpanded_1 ON DocumentInstanceChapter.PK_DocumentInstanceChapter = documentinstancechapterexpanded_1.FK_DocumentInstanceChapter
INNER JOIN (
select d.*
, min(d.IsLocked) over(partition by d.FK_DocumentInstanceChapterExpanded) isLockedMin
, max(d.IsLocked) over(partition by d.FK_DocumentInstanceChapterExpanded) isLockedMax
from DocumentInstanceChapterExpandedItem d
) AS dicei ON DocumentInstanceChapterExpanded.PK_DocumentInstanceChapterExpanded = dicei.FK_DocumentInstanceChapterExpanded
WHERE (DocumentInstance.PK_DocumentInstance = 455)
AND documentinstancechapterexpanded_1.PK_DocumentInstanceChapterExpanded = 50730
edit, due to the bit column, case expressions were needed:
, min(case when d.IsLocked = 1 then 1 else 0 end) over(partition by d.FK_DocumentInstanceChapterExpanded) isLockedMin
, max(case when d.IsLocked = 1 then 1 else 0 end) over(partition by d.FK_DocumentInstanceChapterExpanded) isLockedMax

To compute sum regarding to a constraint

I'm using PostgreSQL 8.4.
I have the following sql-query:
SELECT p.partner_id,
CASE WHEN pa.currency_id = 1 THEN SUM(amount) ELSE 0 END AS curUsdAmount,
CASE WHEN pa.currency_id = 2 THEN SUM(amount) ELSE 0 END AS curRubAmount,
CASE WHEN pa.currency_id = 3 THEN SUM(amount) ELSE 0 END AS curUahAmount
FROM public.player_account AS pa
JOIN player AS p ON p.id = pa.player_id
WHERE p.partner_id IN (819)
GROUP BY p.partner_id, pa.currency_id
The thing is that query does not what I expected. I realize that, but now I want to understand what exactly that query does. I mean, what SUM will be counted after the query executed. Could you clarify?
I think you have the conditions backwards in the query:
SELECT p.partner_id,
SUM(CASE WHEN pa.currency_id = 1 THEN amount ELSE 0 END) AS curUsdAmount,
SUM(CASE WHEN pa.currency_id = 2 THEN amount ELSE 0 END) AS curRubAmount,
SUM(CASE WHEN pa.currency_id = 3 THEN amount ELSE 0 END) AS curUahAmount
FROM public.player_account pa JOIN
player p
ON p.id = pa.player_id
WHERE p.partner_id IN (819)
GROUP BY p.partner_id;
Note that I also removed currency_id from the group by clause.
Maybe one row per (partner_id, currency_id) does the job. Faster and cleaner that way:
SELECT p.partner_id, pa.currency_id, sum(amount) AS sum_amount
FROM player_account pa
JOIN player p ON p.id = pa.player_id
WHERE p.partner_id = 819
AND pa.currency_id IN (1,2,3) -- may be redundant if there are not other
GROUP BY 1, 2;
If you need 1 row per partner_id, you are actually looking for "cross-tabulation" or a "pivot table". In Postgres use crosstab() from the additional module tablefunc , which is very fast. (Also available for the outdated version 8.4):
SELECT * FROM crosstab(
'SELECT p.partner_id, pa.currency_id, sum(amount)
FROM player_account pa
JOIN player p ON p.id = pa.player_id
WHERE p.partner_id = 819
AND pa.currency_id IN (1,2,3)
GROUP BY 1, 2
ORDER BY 1, 2'
,VALUES (1), (2), (3)'
) AS t (partner_id int, "curUsdAmount" numeric
, "curRubAmount" numeric
, "curUahAmount" numeric); -- guessing data types
Adapt to your actual data types.
Detailed explanation:
PostgreSQL Crosstab Query

how to write this query using joins?

i have a table campaign which has details of campaign mails sent.
campaign_table: campaign_id campaign_name flag
1 test1 1
2 test2 1
3 test3 0
another table campaign activity which has details of campaign activities.
campaign_activity: campaign_id is_clicked is_opened
1 0 1
1 1 0
2 0 1
2 1 0
I want to get all campaigns with flag value 3 and the number of is_clicked columns with value 1 and number of columns with is_opened value 1 in a single query.
ie. campaign_id campaign_name numberofclicks numberofopens
1 test1 1 1
2 test2 1 1
I did this using sub-query with the query:
select c.campaign_id,c.campaign_name,
(SELECT count(campaign_id) from campaign_activity WHERE campaign_id=c.id AND is_clicked=1) as numberofclicks,
(SELECT count(campaign_id) from campaign_activity WHERE campaign_id=c.id AND is_clicked=1) as numberofopens
FROM
campaign c
WHERE c.flag=1
But people say that using sub-queries are not a good coding convention and you have to use join instead of sub-queries. But i don't know how to get the same result using join. I consulted with some of my colleagues and they are saying that its not possible to use join in this situation. Is it possible to get the same result using joins? if yes, please tell me how.
This should do the trick. Substitute INNER JOIN for LEFT OUTER JOIN if you want to include campaigns which have no activity.
SELECT
c.Campaign_ID
, c.Campaign_Name
, SUM(CASE WHEN a.Is_Clicked = 1 THEN 1 ELSE 0 END) AS NumberOfClicks
, SUM(CASE WHEN a.Is_Opened = 1 THEN 1 ELSE 0 END) AS NumberOfOpens
FROM
dbo.Campaign c
INNER JOIN
dbo.Campaign_Activity a
ON a.Campaign_ID = c.Campaign_ID
GROUP BY
c.Campaign_ID
, c.Campaign_Name
Assuming is_clicked and is_opened are only ever 1 or 0, this should work:
select c.campaign_id, c.campaign_name, sum(d.is_clicked), sum(d.is_opened)
from campaign c inner join campaign_activity d
on c.campaign_id = d.campaign_id
where c.flag = 1
group by c.campaign_id, c.campaign_name
No sub-queries.
Hmm. Is what you want as simple as this? I'm not sure I'm reading the question right...
SELECT
campaign_table.campaign_id, SUM(is_clicked), SUM(is_opened)
FROM
campaign_table
INNER JOIN campaign_activity ON campaign_table.campaign_id = campaign_activity.campaign_id
WHERE
campaign_table.flag = 1
GROUP BY
campaign_table.campaign_id
Note that with an INNER JOIN here, you won't see campaigns where there's nothing corresponding in the campaign_activity table. In that circumstance, you should use a LEFT JOIN, and convert NULL to 0 in the SUM, e.g. SUM(IFNULL(is_clicked, 0)).
I suppose this should do it :
select * from campaign_table inner join campaign_activity on campaign_table.id = campaign_activity.id where campaign_table.flag = 3 and campaign_activity.is_clicked = 1 and campaign_activity.is_opened = 1
Attn : this is not tested in a live situation
The SQL in it's simplest form and most robust form is this: (formatted for readability)
SELECT
campaign_table.campaign_ID, campaign_table.campaign_name, Sum(campaign_activity.is_clicked) AS numberofclicks, Sum(campaign_activity.is_open) AS numberofopens
FROM
campaign_table INNER JOIN campaign_activity ON campaign_table.campaign_ID = campaign_activity.campaign_ID
GROUP BY
campaign_table.campaign_ID, campaign_table.campaign_name, campaign_table.flag
HAVING
campaign_table.flag=1;