multiple record in a single row - sql

I have post graduation degree records of students in my database. student may have only one post graduation degree, some students may have more than one post graduation degree.
rollno | pgdegree | score
--------------------------
0001 | 41 | 56
0002 | 42 | 78
0002 | 49 | 75
0003 | 48 | 77
Here roll no. 0002 is more than one time and roll no. 0001,0003 are only one time.
i want my desired output as :
rollno | pgdegree1 | score1 | pgdegree2 | score2
------------------------------------------------
0001 | 41 | 56 | |
0002 | 42 | 78 | 49 | 75
0003 | 48 | 77 | |
Note : in my database any student can have one or two post gradation only. Not more than two PG degree.

Here is another solution using ROW_NUMBER() and conditional aggregation to save some unnecessary SELECTs :
SELECT s.rollno,
MAX(CASE WHEN s.rnk = 1 THEN s.pgdegree END) AS pgdegree1,
MAX(CASE WHEN s.rnk = 1 THEN s.score END) AS score1,
MAX(CASE WHEN s.rnk = 2 THEN s.pgdegree END) AS pgdegree2,
MAX(CASE WHEN s.rnk = 2 THEN s.score END) AS score2
FROM
(
SELECT t.*,
ROW_NUMBER() OVER (PARTITION BY t.rollno ORDER BY t.pgdegree, t.score) AS rnk
FROM YourTable t
) s
GROUP BY s.rollno

Do a self LEFT JOIN to add second pgdegree if available for a rollno. Do NOT EXISTS to only return rows with lowest pgdegree as t1.pgdegree.
select t1.rollno, t1.pgdegree, t1.score, t2.pgdegree, t2.score
from tablename t1
left join tablename t2
on t1.rollno = t2.rollno and t1.pgdegree < t2.pgdegree
where not exists (select * from tablename t3
where t1.rollno = t3.rollno
and t1.pgdegree > t3.pgdegree)

You can use a clever pivot query:
SELECT t.rollno,
SUM(CASE WHEN pgdegree = (SELECT MIN(pgdegree) FROM yourTable WHERE rollno = t.rollno)
THEN pgdegree ELSE 0 END) AS pgdegree1,
SUM(CASE WHEN pgdegree = (SELECT MIN(pgdegree) FROM yourTable WHERE rollno = t.rollno)
THEN score ELSE 0 END) AS score1,
SUM(CASE WHEN pgdegree = (SELECT MAX(pgdegree) FROM yourTable WHERE rollno = t.rollno)
THEN pgdegree ELSE 0 END) AS pgdegree2,
SUM(CASE WHEN pgdegree = (SELECT MAX(pgdegree) FROM yourTable WHERE rollno = t.rollno)
THEN score ELSE 0 END) AS score2
FROM yourTable t
GROUP BY t.rollno
Explanation:
The first two CASE statements have subqueries which will return pgdegree if that value happens to be the minimum value for that given rollno. This pgdegree and score will appear as the first two columns. Similarly, the last two CASE statements use the maximum value to generate the second two columns.

select
rollno,
(array_agg(pgdegree))[1] as pgdegree1,
(array_agg(score))[1] as score1,
(array_agg(pgdegree))[2] as pgdegree2,
(array_agg(score))[2] as score2
from
your_table
group by
rollno;

Related

SQL count where where column is greater than the other in group by?

Suppose I have a table money_table like:
team_id | money_spent | money_budget
--------------------------------------
123 | 3456.32 | 3466
964 | 236.32 | 200
123 | 9663 | 9400
964 | 3456.32 | 3466
The output table should be:
team_id | total_money_spent | total_money_budget | days_over_spent | days_under_spent
--------------------------------------
123 | 13119.32 | 12866 | 2 | 0
964 | 3692.64 | 3666 | 1 |. 1
The first 2 columns are easy with a group BY, I am wondering about the last 2 columns and how to tackle that. My initial query was:
SELECT
team_id,
SUM(money_spent) as total_money_spent,
SUM(money_budget) as total_money_budget
FROM money_table
GROUP BY team_id
ORDER BY team_id ASC
The works fine for the first 2 columns, but I am unable to think of how to get days_over_spent and days_under_spent.
Any suggestions?
Edit:
days_over_spent is the number of rows where money_spent > money_budget
days_under_spent is the number of rows where money_spent < money_budget
You could do the calculations for "over the budget" in a CTE
with tmp (t, s, b, o, u) as (
select
team,
spent,
budget,
case when spent > budget then 1 else 0 end,
case when spent < budget then 1 else 0 end
from budget
)
select
t as team,
sum(s) as total_spent,
sum(b) as total_budget,
sum(o) as days_over,
sum(u) as days_under
from tmp
group by t
Of course you can also just add the case into the query itself
select
team,
sum(spent),
sum(budget),
sum(case when spent > budget then 1 else 0 end),
sum(case when spent < budget then 1 else 0 end)
from budget
group by team
SELECT
team_id,
SUM(money_spent) as total_money_spent,
sum(money_budget) as total_money_budget,
sum(case when money_spent > money_budget then 1 else 0 end) as days_over_spent,
sum(case when money_spent < money_budget then 1 else 0 end) as days_under_spent
FROM money_table
GROUP BY team_id
ORDER BY team_id ASC

SQL Group By Multiple values on different columns

I have a table like this:
Section TestID Score
Section1 1 50
Section2 1 32
Section3 1 22
Section1 2 22
Section2 2 17
Section3 2 42
I'm looking to produce a table with each section and it's scores against all testIDs (up to a maximum of 3 scores). Is it possible to use a group by condition to produce a table similar to this:
Section Score1 Score2
Section1 50 22
Section2 32 17
Section3 22 42
With ROW_NUMBER() window function and conditional aggregation:
select t.section,
max(case when t.rn = 1 then t.score end) score1,
max(case when t.rn = 2 then t.score end) score2
from (
select *, row_number() over (partition by section order by testid) rn
from tablename
) t
group by t.section
See the demo.
Results:
> section | score1 | score2
> :------- | -----: | -----:
> Section1 | 50 | 22
> Section2 | 32 | 17
> Section3 | 22 | 42
You can use conditional aggregation:
select section,
max(case when testid = 1 then score end) as score_1,
max(case when testid = 2 then score end) as score_2
from t
group by section;

SQL calculate the sum of a column based on the date in two differents variables

I've a simple table in this forme :
BillItem (id,amount, volume, bill_date,....other fields)
I want to obtain in my query 4 differents sum of fields amount and volume based on the date
for example, in my table i've this data :
Id | amount | volume | bill_date | libelle
1 | 10 | 50 | 02/04/2016| bill1
2 | 20 | 55 | 02/04/2016| bill1
2 | 88 | 66 | 02/05/2016| bill1
3 | 30 | 60 | 03/05/2016| bill2
4 | 40 | 10 | 02/04/2016| bill3
5 | 50 | 20 | 02/05/2016| bill3
and the result must be like this :
bill1, sum_date_1=30, sum_date_2=88, sum_volume_date_1=105, sum_volume_date_2=66
bill2, sum_date_1=0, sum_date_2=30, sum_volume_date_1=0, sum_volume_date_2=60
bill3, sum_date_1=40, sum_date_2=50, sum_volume_date_1=10, sum_volume_date_2=20
i've this query with only two sum variable :
select ans.SERVICE_TYPE, ans.SERVICE_SUB_TYPE,
sum(bi.ACTUAL_AMOUNTVAT),sum(bi.ACTUAL_VOLUME), bi.BILL_DATE
from bill_item bi left outer join ANALYTIC_SECTION ans on ans.TREE_PATH=bi.REPORT_SECTION
where bi.account_id=7
and bi.BILL_DATE<='31/05/2016' and bi.BILL_DATE>='01/04/2016'
and ans.REPORT_TYPE='ARPE_REPORT' and ans.ACCOUNT_ID=7
group by ans.SERVICE_TYPE, ans.SERVICE_SUB_TYPE, bi.BILL_DATE;
Is it possible to obtain two differents sum for each field (amount and volume) ?
I've resolved the query like this :
select distinct ans.SERVICE_TYPE, ans.SERVICE_SUB_TYPE,
sum(Case when bi.BILL_DATE<'01/05/2016' then bi.ACTUAL_AMOUNTVAT ELSE 0 END) as amount_m1,
sum(Case when bi.BILL_DATE>='01/05/2016' then bi.ACTUAL_AMOUNTVAT ELSE 0 END) as amount_m,
sum(Case when bi.BILL_DATE<'01/05/2016' then bi.ACTUAL_VOLUME ELSE 0 END) as volume_m1,
sum(Case when bi.BILL_DATE>='01/05/2016' then bi.ACTUAL_VOLUME ELSE 0 END) as volume_m
--,bi.BILL_DATE
from bill_item bi left outer join ANALYTIC_SECTION ans on ans.TREE_PATH=bi.REPORT_SECTION
where bi.account_id=7
and bi.BILL_DATE<='06/05/2016' and bi.BILL_DATE>='06/04/2016'
and ans.REPORT_TYPE='ARPE_REPORT' and ans.ACCOUNT_ID=7
group by ans.SERVICE_TYPE, ans.SERVICE_SUB_TYPE;
Thank's All for your help
it looks to me like you want to summarize by each date and then return the value for the months in ascending fields?
It might work with a subquery and a ranking option, although this may not be the most efficient route.
Select t.Id
, Max(Case When t.BillRank=1 Then t.SumVolume Else Null End) As Volume1
, Max(Case When t.BillRank=2 Then t.SumVolume Else Null End) As Volume2
, Max(Case When t.BillRank=1 Then t.SumVAT Else Null End) As Vat1
, Max(Case When t.BillRank=2 Then t.SumVAT Else Null End) As Vat2
From ( Select ans.SERVICE_TYPE
, ans.SERVICE_SUB_TYPE
, Sum(bi.ACTUAL_AMOUNTVAT) As SumVAT
, Sum(bi.ACTUAL_VOLUME) As SumVolume
, bi.BILL_DATE
, bi.Id
, Rank() Over ( Partition By bi.Id Order By bi.BILL_DATE Asc ) As BillRank
From bill_item As bi
Left Outer Join ANALYTIC_SECTION as ans
On ans.TREE_PATH = bi.REPORT_SECTION
Group By ans.SERVICE_TYPE
, ans.SERVICE_SUB_TYPE
, bi.BILL_DATE
, bi.Id
) t
Group By t.Id;

SQL group by and where on each group

I have a table with columns like sourceId (guid), state (1:Deactivated, 2:Activated, 3:Dead), modifiedDate.
I am writing a query to group by sourceId and see if ALL the records in a group have the state as 2 (activated) and also get the MAX of modifiedDate of the rows which have state as 2 (activated) in each group.
result table should be something like sourceId, IsAllActivated, MaxModifiedForActivatedRecords.
I tried a lot of options like Partition By, Cross over etc. which are giving me either one of the column and not both. Options which have self joins were costly, so looking for any other efficient way of forming the query.
Data :
SourceId | State | modifiedDate
s1 | 1 | 01/01
s1 | 2 | 01/02
s2 | 3 | 02/03
s2 | 3 | 03/03
s1 | 3 | 10/10
Ouput:
sourceId | IsAllActivated | MaxModifiedForActivatedRecords
s1 | 0 | 02/03
s2 | 1 | 03/03
What i had tried :
SELECT
[SourceID]
,CASE
WHEN COUNT(DISTINCT State) = 1 AND
SUM(DISTINCT State) = 3
THEN 1
ELSE 0
END AS IsAllActivated
FROM ThreadActivation
GROUP BY SourceID
SELECT
[SourceID]
,MAX(modifiedDate) AS MaxModifiedForActivatedRecords
FROM ThreadActivation
GROUP BY SourceID
HAVING State = 3
I am able to get them separately, but not together in a single query.
I tried ranking with row number :
WITH ThreadActivationTransaction AS (
select
*
,ROW_NUMBER() over(PARTITION BY SourceId order by modifiedDate desc) AS rk
from ThreadActivation)
select
[sourceID]
,CASE
WHEN COUNT(DISTINCT State) = 1 AND SUM(DISTINCT State) = 3
THEN 1
ELSE 0
END AS IsAllActivated
,[SourceId]
from ThreadActivation s
GROUP by SourceId --where s.rk =1
All these were not giving me a break through.
You can do this with aggregation and case:
select sourceId,
(case when max(state) = min(state) and max(state) = 2
then 1 else 0
end) as IsAllActivated,
max(case when state = 2 then modifiedDate end) as MaxModifiedForActivatedRecords
from t
group by sourceId;
This assumes that state is not NULL. The logic is only slightly more complicated if that is possible.

SQL: Count() based on column value

I have a table as follows:
CallID | CompanyID | OutcomeID
----------------------------------
1234 | 3344 | 36
1235 | 3344 | 36
1236 | 3344 | 36
1237 | 3344 | 37
1238 | 3344 | 39
1239 | 6677 | 37
1240 | 6677 | 37
I would like to create a SQL script that counts the number of Sales outcomes and the number of all the other attempts (anything <> 36), something like:
CompanyID | SalesCount | NonSalesCount
------------------------------------------
3344 | 3 | 1
6677 | 0 | 2
Is there a way to do a COUNT() that contains a condition like COUNT(CallID WHERE OutcomeID = 36)?
You can use a CASE expression with your aggregate to get a total based on the outcomeId value:
select companyId,
sum(case when outcomeid = 36 then 1 else 0 end) SalesCount,
sum(case when outcomeid <> 36 then 1 else 0 end) NonSalesCount
from yourtable
group by companyId;
See SQL Fiddle with Demo
Something like this:
SELECT companyId,
COUNT(CASE WHEN outcomeid = 36 THEN 1 END) SalesCount,
COUNT(CASE WHEN outcomeid <> 36 THEN 1 END) NonSalesCount
FROM
yourtable
GROUP BY
companyId
should work -- COUNT() counts only not null values.
Yes. Count doesn't count NULL values, so you can do this:
select
COUNT('x') as Everything,
COUNT(case when OutcomeID = 36 then 'x' else NULL end) as Sales,
COUNT(case when OutcomeID <> 36 then 'x' else NULL end) as Other
from
YourTable
Alternatively, you can use SUM, like bluefeet demonstrated.
SELECT
companyId, SalesCount, TotalCount-SalesCount AS NonSalesCount
FROM
(
select
companyId,
COUNT(case when outcomeid = 36 then 1 else NULL end) SalesCount,
COUNT(*) AS TotalCount
from yourtable
group by companyId
) X;
Using this mutually exclusive pattern with COUNT(*)
avoids a (very small) overhead of evaluating a second conditional COUNT
gives correct values if outcomeid can be NULL
Using #bluefeet's SQLFiddle with added NULLs
Knowing COUNT() and SUM() only count non-null values and the following rule:
true or null = true
false or null = null
For fiddling around, you can take Taryn's answer and circumvent CASE altogether in a super-dirty and error-prone way!
select companyId,
sum(outcomeid = 36 or null) SalesCount,
sum(outcomeid <> 36 or null) NonSalesCount
from yourtable
group by companyId;
Forget to add an or null and you'll be counting everything!