Merging different column values in a single row in Oracle 9i [duplicate] - sql

Consider the following table,
Name | Subject_1 | Marks_1 | Subject_2 | Marks_2 |
Tom | Maths | 90 | | |
Tom | | | Science | 50 |
Jon | | | Science | 70 |
Jon | Maths | 60 | | |
How do I get the following result
Name | Subject_1 | Marks_1 | Subject_2 | Marks_2 |
Tom | Maths | 90 | Science | 50 |
Jon | Maths | 60 | Science | 70 |
Tried forms of GROUP BY but did not get correct result, Maths will always come under Subject_1 and Science under SUbject_2.

Use:
MAX
GROUP BY
SQL> SELECT NAME,
2 MAX(subject_1) subject_1,
3 MAX(marks_1) marks_1,
4 MAX(subject_2) subject_2,
5 MAX(marks_2) marks_2
6 FROM t
7 GROUP BY name;
NAME SUBJECT_1 MARKS_1 SUBJECT_2 MARKS_2
---- --------- ---------- --------- ----------
Jon Maths 60 Science 70
Tom Maths 90 Science 50
SQL>
On a side note, you need to think about your table design. You could only have 3 columns, name, subject, marks.
If you want to have separate columns in the same table, then you should have them as a single row for each student. And when you have a new subject for the student, then update the row for that student, instead of adding a new row.

Try this :
select maths.name,maths.subject_1,maths.marks_1,sci.subject_1,sci.marks_2
from mytable maths,(select * from mytable where subject_2 is not null) sci
where maths.name=sci.name
and maths.subject_1 is not null;

Related

How do I insert multiple rows from one table into a struct column of a single row of another table?

I have 2 source tables at the moment.
Table #1: sourceTableMain
|EmployeeNumber| DepartmentNumber | CostCenterNumber |
| -------------| ---------------- |------------------|
| 1 | 100 | 1001 |
| 2 | 200 | 1001 |
| 3 | 100 | 1002 |
Table #2: sourceTableEmployee
|EmployeeNumber| EmployeeFirstName | EmployeeLastName | EmployeeAddress |
| -------------| ---------------- |------------------|---------------- |
| 1 | Michael | Scott | 110 ABC Ln |
| 1 | Michael | Scott | 450 XYZ Ln |
| 2 | Dwight | Schrute | 321 PQR St |
| 3 | Jim | Halpert | 678 LMN Blvd |
I am trying to insert the combine the rows into a 3rd table named targetTableCombined which has the following schema:
FieldName
Type
Mode
employeeNumber
INTEGER
NULLABLE
employeeDetails
(struct)
RECORD
REPEATED
employeeFirstName
STRING
NULLABLE
employeeLastName
STRING
NULLABLE
employeeAddress
STRING
NULLABLE
Within the target table (targetTableCombined), I am trying to make sure that for each employeeNumber, all of the First Names, Last Names and Addresses are repeated under a single struct array. For example, EmployeeNumber 1 should have only 1 row in the target table, with the first name, last name and different addresses as part of the second column (struct), each in a separate row.
I wrote an insert script to do this, but I am going wrong:
insert into `dev.try_sbx.targetTableCombined`
select
main.employeeNumber,
array(
select as struct
emp.employeeFirstName,
emp.employeeLastName,
emp.employeeAddress
)
from
`dev.try_sbx.sourceTableMain` as main
inner join `dev.try_sbx.sourceTableEmployee` as emp
on main.EmployeeNumber = emp.EmployeeNumber;
This is the result I am getting when running the query above:
| EmployeeNumber | EmployeeDetails |
| ------------- | ------------------------------ |
| 1 | [Michael, Scott, 110 ABC Ln] |
| 1 | [Michael, Scott, 450 XYZ Ln] |
| 2 | [Dwight, Schrute, 321 PQR St] |
| 3 | [Jim, Halpert, 678 LMN Blvd] |
(Sorry about not being able to share screenshots - I don't have enough rep. But to elaborate, I am expecting only 3 rows on the insert (employee 1 should have had a single array containing both addresses). I am instead, getting 4 rows after the insert.)
Where am I going wrong with my script?
It's because ARRAY() is not an aggregation function. You should ARRAY_AGG() along with GROUP BY to group details for each employee into an array.
SELECT EmployeeNumber,
ARRAY_AGG((SELECT AS STRUCT EmployeeFirstName, EmployeeLastName, EmployeeAddress)) AS employeeDetails
FROM `dev.try_sbx.sourceTableEmployee`
GROUP BY 1;
More preferred way is :
SELECT EmployeeNumber,
ARRAY_AGG(STRUCT(EmployeeFirstName, EmployeeLastName, EmployeeAddress)) AS employeeDetails
FROM `dev.try_sbx.sourceTableEmployee`
GROUP BY 1;
output:

SQL - joining 3 tables and choosing newest logged entry per id

I got rather complicated riddle to solve. So far I'm unlocky.
I got 3 tables which I need to join to get the result.
Most important is that I need highest h_id per p_id. h_id is uniqe entry in log history. And I need newest one for given point (p_id -> num).
Apart from that I need ext and name as well.
history
+----------------+---------+--------+
| h_id | p_id | str_id |
+----------------+---------+--------+
| 1 | 1 | 11 |
| 2 | 5 | 15 |
| 3 | 5 | 23 |
| 4 | 1 | 62 |
+----------------+---------+--------+
point
+----------------+---------+
| p_id | num |
+----------------+---------+
| 1 | 4564 |
| 5 | 3453 |
+----------------+---------+
street
+----------------+---------+-------------+
| str_id | ext | name |
+----------------+---------+-------------+
| 15 | | Mein st. 33 | - bad name
| 11 | | eck st. 42 | - bad name
| 62 | abc | Main st. 33 |
| 23 | efg | Back st. 42 |
+----------------+---------+-------------+
EXPECTED RESULT
+----------------+---------+-------------+-----+
| num | ext | name |h_id |
+----------------+---------+-------------+-----+
| 3453 | efg | Back st. 42 | 3 |
| 4564 | abc | Main st. 33 | 4 |
+----------------+---------+-------------+-----+
I'm using Oracle SQL. Tried using query below but result is not true.
SELECT num, max(name), max(ext), MAX(h_id) maxm FROM history
INNER JOIN street on street.str_id = history._str_id
INNER JOIN point on point.p_id = history.p_id
GROUP BY point.num
In Oracle, you can use keep:
SELECT p.num,
MAX(h.h_id) as maxm,
MAX(s.name) KEEP (DENSE_RANK FIRST ORDER BY h.h_id DESC) as name,
MAX(s.ext) KEEP (DENSE_RANK FIRST ORDER BY h.h_id DESC) as ext
FROM history h INNER JOIN
street s
ON s.str_id = h._str_id INNER JOIN
point p
ON p.p_id = h.p_id
GROUP BY p.num;
The keep syntax allows you to do "first()" and "last()" for aggregations.

SQL on Self Table Join

I am trying to do a simple self-join SQL and a join to a 2nd table and for the life of me I can't figure it out. I've done some research and can't seem to glean the answer from similar questions. This query is for MS-Access running in VB.NET.
I have 2 tables:
TodaysTeams
-----------
TeamNum PlayerName PlayerID
------- ---------- --------
1 Mark 100
1 Brian 101
2 Mike 102
2 Mike 102
(Note the last 2 rows above are not a typo. In this case a player can be paired with themselves to form a team)
TodaysTeamsPoints
-----------------
TeamNum Points
------- ------
1 90
2 85
The result I want is (2 rows, 1 for each team):
TeamNum PlayerName1 PlayerName2 Points
------- ----------- ----------- ------
1 Mark Brian 90
2 Mike Mike 85
Here is my SQL:
SELECT DISTINCT A.TeamNum, A.PlayerName as PlayerName1, B.PlayerName AS PlayerName2, C.Points
FROM ((TodaysTeams A INNER JOIN
TodaysTeamsPoints C ON A.TeamNum = C.TeamNum) INNER JOIN
TodaysTeams B ON A.TeamNum = B.TeamNum)
ORDER BY C.Points DESC
I know I am missing another join as I'm returning a cartesian produce (i.e. too many rows).
I would appreciate help as to what I am missing here.
Thank you.
Whilst Gordon's suggested method will work well providing that there are at most two players per team, the method breaks down if ever you add another team member and wish to display them in a separate column.
The difficulty in displaying the data in a manner that you can describe logically but cannot easily produce using a query usually implies that the database structure is sub optimal.
For your particular setup, I would personally recommend the following structure:
+---------------+ +----------+------------+
| Players | | PlayerID | PlayerName |
+---------------+ +----------+------------+
| PlayerID (PK) | | 100 | Mark |
| PlayerName | | 101 | Brian |
+---------------+ | 102 | Mike |
+----------+------------+
+-------------+ +--------+----------+
| Teams | | TeamID | TeamName |
+-------------+ +--------+----------+
| TeamID (PK) | | 1 | Team1 |
| TeamName | | 2 | Team2 |
+-------------+ +--------+----------+
+-------------------+ +--------+--------------+----------+
| TeamPlayers | | TeamID | TeamPlayerID | PlayerID |
+-------------------+ +--------+--------------+----------+
| TeamID (PK) | | 1 | 1 | 100 |
| TeamPlayerID (PK) | | 1 | 2 | 101 |
| PlayerID (FK) | | 2 | 1 | 102 |
+-------------------+ | 2 | 2 | 102 |
+--------+--------------+----------+
Using this method, you can use condition aggregation or a crosstab query pivoting on the TeamPlayerID to produce each of your columns, and you would not be limited to two columns.
You can use aggregation:
SELECT ttp.TeamNum, MIN(tt.PlayerName) as PlayerName1,
MAX(tt.PlayerName) as PlayerName2,
ttp.Points
FROM TodaysTeamsPoints as ttp INNER JOIN
TodaysTeams as tt
ON tt.TeamNum = ttp.TeamNum
GROUP BY ttp.TeamNum, ttp.Points
ORDER BY ttp.Points DESC;

Make one-to-many relationship look like one-to-one

I'm using postgres 9.3.5.
Given the following data:
select * from department;
id | name
----+-----------
1 | sales
2 | marketing
3 | HR
and
select * from people;
id | department_id | first_name | last_name
----+---------------+------------+-----------
1 | 1 | Tom | Jones
2 | 1 | Bill | Cosby
3 | 2 | Jessica | Biel
4 | 1 | Rachel | Hunter
5 | 2 | John | Barnes
I'd like to return a result set like this:
id | name | first_name-1 | last_name-1 | first_name-2 | last_name-2 | first_name-3 | last_name-3
----+-----------+--------------+-------------+--------------+-------------+--------------+------------
1 | sales | Tom | Jones | Bill | Cosby | Rachel | Hunter
2 | marketing | Jessica | Biel | John | Barnes
3 | HR |
Is this possible?
The answer provided here by Max Shawabkeh using the GROUP_CONCAT is close - but its not returning as extra fields in the dataset, its concatenating them into a single field.
You need cross-tabulation (sometimes called pivot).
Could look like this in your case:
SELECT * FROM crosstab(
$$SELECT d.id, d.name,'p' AS dummy_cat
,concat_ws(' ', p.first_name, p.last_name) AS person
FROM department d
LEFT JOIN people p ON p.department_id = d.id
ORDER BY d.department_id, p.id$$
)
AS ct (id int, department text, person_1 text, person_2 text, person_3 text);
Returns:
id department person_1 person_2 person_3
--------------------------------------------------------
1 sales Tom Jones Bill Cosby Rachel Hunter
2 marketing Jessica Biel John Barnes <NULL>
3 HR <NULL> <NULL> <NULL>
Very similar to this related case (explanation for special difficulties there):
Postgres - Transpose Rows to Columns
But this case is simpler: since you do not seem to care about the order in which persons are listed, you can use the basic one-parameter form of crosstab().
Also, according to your comment, you want all departments, even if no people are assigned. Adjusted the LEFT JOIN accordingly.
Basic details in this related answer:
PostgreSQL Crosstab Query

Using Group By on a non numeric field

If I have a table like this:
ID | Name | Age
1 | Bill | 30
2 | Jim | 20
3 | Bill | 30
4 | Bill | 30
5 | Bob | 25
I want to return this:
ID | Name | Age
1 | Bill | 30
2 | Jim | 20
5 | Bob | 25
I tried this but it doesn't work:
SELECT ID,Max(Name),Age FROM TABLE
GROUP BY ID,Age
What do I got to do?
This should work:
select MIN(ID), NAME, AGE from TABLE group by NAME, AGE
Grouping by ID has no sense, because ID is already unique.
SELECT Max(Name), AGE
FROM TABLE
GROUP BY Age