Combine two rows with just 2 different column values into one - sql

I have a table that keeps records of Workplace Accidents. Each IncidentNo is supposed to be unique. The organization that provided me the data in such a way that each IncidentNo has two entries, one for each gender. Format of the table is something like this. (Table has around 110 columns, I just showed the ones related to the question. All of the columns are varchar)
+------------+--------+----------------+
| IncidentNo | Gender | PersonnelCount |
+------------+--------+----------------+
| 123456 | M | 150 |
| 123456 | F | 100 |
| 789012 | M | 31 |
| 789012 | F | 42 |
+------------+--------+----------------+
What I need is to combine these columns in such a way that table (Doesn't matter if it is on the same table or inserted into a new one) is something like this:
+------------+----------------------+--------------------+
| IncidentNo | FemalePersonnelCount | MalePersonnelCount |
+------------+----------------------+--------------------+
| 123456 | 100 | 150 |
| 789012 | 42 | 31 |
+------------+----------------------+--------------------+
I thought to use Left Outer Join to insert data to a new table but couldn't figure out how.

Just use conditional aggregation:
select incidentno, sum(PersonnelCount) as total_PersonnelCount,
sum(case when gender = 'M' then PersonnelCount else 0 end) as nummales,
sum(case when gender = 'F' then PersonnelCount else 0 end) as numfemales,
from t
group by incidentno;
I would calculate the total as well, just to be sure that the total matches the sum of 'M' and 'F'.

In its rawest form, you can self join:
select x.IncidentNo,
sum(a.personelcount) as Female,
sum(b.personelcount) as Male
from Accidents x
left join Accidents a
on a.incidentno = x.incidentno
and a.Gender = 'F'
left join Accidents b
on b.incidentno = x.incidentno
and b.Gender = 'M'
group by x.IncidentNo -- I left this out originally because I'm an idiot

Related

SQL query on conditional expressions

I am starting an SQL crash course and I have this problem it's bugging me I can't find a solution and with lockdown I have no one I can ask. It's about conditional expressions. In the problem some people play different instruments and they play in duets occasionally in school. I need to find a query that retrieves a table with name and guitar (yes or no) if they play that instrument. I have to work with the following tables:
Table students
| id | name | grade | gender
+-----+-------------+---------+------
| 1 | John | 12 | M
| 2 | Andrew | 11 | F
| 3 | Abigail | 11 | F
| 4 | Simon | 10 | M
Table duets
| id | idStudent1 | idStudent2 | duetOf
+----+------------+------------+------
| 20 | 1 | 2 | Piano
| 35 | 2 | 4 | Piano
| 36 | 3 | 2 | Drums
| 37 | 4 | 1 | Drums
| 35 | 4 | 2 | Guitar
| 36 | 4 | 2 | Flute
| 37 | 4 | 3 | Piano
Query:
select s.name, Coalesce (d.duets, '(none)')
from students s, duet d
where s.id = d.idStudent1
and s.id = d.idStudeant2
and d.instrument = 'guitar'
Any tips on how I can tackle this kind of problem?
You can do conditional aggregation:
select
s.id,
s.name,
max(case when d.duetOf = 'Piano' then 'Yes' else 'No' end) piano,
max(case when d.duetOf = 'Guitar' then 'Yes' else 'No' end) guitar
from students s
left join duets d
on s.id in (d.idStudent1, d.idStudent2)
group by s.id, s.name
This generates one record for each record in students, with two columns that indicate whether the student belongs to a duet that plays guitar or piano.
If you need to display more columns from the students table, you can add them in both the select and group by clauses.
Check this
select s.name, case when duetOf = 'Guitar' then 'Yes' Else 'No' End as Guitar
from duet d
inner join students s on d.idStudent1 = s.Id or d.idStudent2 = s.Id

Transposing lines containing Text to columns

I have a table just like this one:
+----+---------+-------------+------------+
| ID | Period | Total Units | Source |
+----+---------+-------------+------------+
| 1 | Past | 400 | Competitor |
| 1 | Present | 250 | PAWS |
| 2 | Past | 3 | BP |
| 2 | Present | 15 | BP |
+----+---------+-------------+------------+
And I'm trying to transpose the lines into columns, so that for each ID, I have one unique line that compares past and present numbers and attributes. Like following :
+----+------------------+---------------------+-------------+----------------+
| ID | Total Units Past | Total Units Present | Source Past | Source Present |
+----+------------------+---------------------+-------------+----------------+
| 1 | 400 | 250 | Competitor | PAWS
|
| 2 | 3 | 15 | BP | BP |
+----+------------------+---------------------+-------------+----------------+
Transposing the total units is not a problem, as I use a SUM(CASE WHEN Period = Past THEN Total_Units ELSE 0 END) AS Total_Units.
However I don't know how to proceed with text columns. I've seen some pivot and unpivot clause used but they all use an aggregate function at some point.
You can do conditional aggregation :
select id,
sum(case when period = 'past' then units else 0 end) as unitspast,
sum(case when period = 'present' then units else 0 end) as unitpresent,
max(case when period = 'past' then source end) as sourcepast,
max(case when period = 'present' then source end) as sourcepresent
from table t
group by id;
Assuming you only have two rows per ID, you could also join:
Select a.ID, a.units as UnitsPast, a.source as SourcePast
, b.units as UnitsPresent, b.source as SourcePresent
from MyTable a
left join MyTable b
on a.ID = b.ID
and b.period = 'Present'
where a.period = 'Past'

Insert zero value rows for missing column value Postgres

I have this table (elev_table) :
country | cat | elev
--------------+-----+----------
USA | 0 | 41.46
USA | 1 | 878.90
USA | 2 | 78.01
CAN | 0 | 46.00
CAN | 1 | 78.92
CAN | 2 | 78.01
CAN | 4 | 667.77
CAN | 5 | 10.80
How can I create a query to return the same table, filled with missing cat values? The cat column has to values from (-1 to 5) but not every country value contains every cat value.
Essentially, for every country in my table I need rows with elevation values for every cat value of -1 to 5.....
For example I want the resulting table to look like:
country | cat | elev
--------------+-----+----------
USA | -1 | 0
USA | 0 | 41.46
USA | 1 | 878.90
USA | 2 | 78.01
USA | 3 | 0
USA | 4 | 0
USA | 5 | 0
CAN | -1 | 0
CAN | 0 | 46.00
CAN | 1 | 78.92
CAN | 2 | 78.01
CAN | 3 | 0
CAN | 4 | 667.77
CAN | 5 | 10.80
I believe 0 would be a good representation, but maybe NULL would be better?
I have tried the following query, but this doesn't create missing row values for each country... I know I am missing something but can't seem to figure out what!!!
with cat_series as(
select generate_series(-1,5) as fullcat
)
select fullcat, coalesce(sum(t.elev),0), t.country
from cat_series
left join elev_table t on cat_series.fullcat=t.cat
group by fullcat, t.country
order by fullcat, t.country;
Still quite new to postgres, sql. Any help or pointers would be appreciated!
You can use a cross join followed by a left join:
select c.country, g.cat, coalesce(sum(t.elev), 0)
from (select distinct country from elev_table) c cross join
generate_series(-1, 5) as g(cat) left join
elev_table et
on et.country = c.country and et.cat = g.cat
group by c.country, g.cat;
I left the group by in the query, although based on the sample data, this is sufficient:
select c.country, g.cat, coalesce(t.elev, 0)
from (select distinct country from elev_table) c cross join
generate_series(-1, 5) as g(cat) left join
elev_table et
on et.country = c.country and et.cat = g.cat;
You can remove the coalesce() if you prefer NULL to 0.

Inserting results of two sums together to a table

From my previous question, I've managed to get things to work using the Microsoft SQL Server and importing the excel file to a new database. Now, my question is dealing with writing the right sql commands. I'm trying to sum up the same column of a table twice using different criteria, and then input those numbers to a different table. I know how to insert a sum to a table, but I wonder how I can insert two sums that came from the same column simultaneously (since each time I insert, a new row is created) to the same row of a table.
Additional question: what is the way I should organize my results are dependent on values from a third table? Sample data as follows.
Some sample data:
DeptID Department
15 Eng
16 Eng
17 Mkt
18 Mkt
| Person | DeptID | Type | Number |
+--------+------------+------+---------+
| A | 15 | p1 | 1 |
| B | 18 | p2 | 5 |
| C | 16 | p2 | 10 |
| D | 17 | p1 | 7 |
| E | 18 | p1 | 11 |
| F | 16 | p2 | 12 |
So the result I should give is as such:
| Department | Sum of p1 | Sum of p2 |
+------------+------------+-----------+
| Eng | 1 | 22 |
| Mkt | 18 | 5 |
What I've tried is as follows:
select sum(Amount) as engsump1 from Sheet1$
where Department = 'Eng' and Type = 'p1'
select sum(Amount) as engsump2 from Sheet1$
where Department = 'Eng' and Type = 'p2'
select sum(Amount) as mktsump1 from Sheet1$
where Department = 'Mkt' and Type = 'p1'
select sum(Amount) as mktsump2 from Sheet1$
where Department = 'Mkt' and Type = 'p2'
Run it once and after I see the results, perform the insert into function. I'm just wondering if I can do this two steps in one step.
To sum the 'number' column in each group, you can use the SUM() function; Just put a case statement inside there, so you can sum the cases when type is p1, and sum the cases when type is p2.
To separate the departments, just use a GROUP BY clause. Try this:
SELECT department,
SUM(CASE WHEN type = 'p1' THEN number ELSE 0 END) AS totalP1,
SUM(CASE WHEN type = 'p2' THEN number ELSE 0 END) AS totalP2
FROM myTable
GROUP BY department;
Here is an SQL Fiddle example.

SQL MAX & MIN on same column around given date

I have a table structure like the following:
+------+------+-------------+
|Person| Code | DateOccur |
+------+------+-------------+
| 1545 | A | 1/1/2014 |
| 2324 | K | 3/4/2014 |
| 2324 | j | 3/8/2014 |
| 1545 | B | 3/6/2014 |
| 5663 | J | 3/2/2014 |
+------+------+-------------+
The Primary key could be seen as (Person, DateOccur), that is, there would never be two values with the same date for the same employee.
I want to form a query, given a particular date SomeDate, where I return the MAX(DateOccur) LESS than SomeDate as well as the MIN(DateOccur) GREATER than SomeDate, as well as the two associated codes.
An example with the table provided would be (w/ SomeDate = 3/3/2014):
+--------+------------+---------------+-----------+--------------+
| Person | CodeBefore | MaxDateBefore | CodeAfter | MinDateAfter |
+--------+------------+---------------+-----------+--------------+
| 1545 | A | 1/1/2014 | B | 3/6/2014 |
| 2324 | K | 3/4/2014 | j | 3/8/2014 |
| 5663 | j | 3/2/2014 | null | null |
+--------+------------+---------------+-----------+--------------+
What would be the simplest method of accomplishing this? I read a number of MAX/MIN StackOverflow questions, but I specifically need values around a specific date. I ideally just don't want to end up with something hacked together when I know there has to be a smooth way to do this.
Do this with conditional aggregation and then joining in the results. Here is the code to get the dates:
select person, max(case when DateOccur < #TheDate then DateOccur end) as DateBefore,
min(case when DateOccur > #TheDate then DateAfter end) as DateAfter
from table t
group by person;
And to get the rest:
select x.person, x.DateBefore, tbefore.code, x.DateAfter, tafter.code
from (select person, max(case when DateOccur < #TheDate then DateOccur end) as DateBefore,
min(case when DateOccur > #TheDate then DateOccur end) as DateAfter
from table t
group by person
) x left outer join
table tbefore
on x.person = tbefore.person and x.DateBefore = tbefore.DateOccur left outer join
table tafter
on x.person = tafter.person and x.DateAfter = tafter.DateOccur;
I assume you want to avoid using min/max -based subqueries? Perhaps you can conceive of a solution around the ROW_NUMBER() OVER ranking function? What do you want to happen if you have 2 rows in the first table with the same DateOccur value?
http://msdn.microsoft.com/en-us/library/ms186734.aspx