Conversion of columns to rows - google-bigquery

ID Name M E H S
1 Sally 78 85 91 76
2 Edward 87 90 82 87
convert to
ID Name Subject Marks
1 Sally M 78
1 Sally E 85
1 Sally H 91
1 Sally S 76
2 Edward M 87
2 Edward E 90
2 Edward H 82
2 Edward S 87

The unpivot function will perform the action you're looking for, try the following:
with sample_data as (
SELECT 1 as id, 'Sally' as name, 78 as M, 85 as E, 91 as H, 76 as S UNION ALL
SELECT 2, 'Edward', 87, 90, 82, 87
)
SELECT id, name, subject, marks
from sample_data
unpivot(marks for subject in (M,E,H,S));
for more information on unpivot see the docs here:
https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax#unpivot_operator

One simple approach uses a series of unions:
SELECT ID, Name, 'M' AS Subject, M AS Marks, 1 AS pos FROM yourTable UNION ALL
SELECT ID, Name, 'E', E, 2 FROM yourTable UNION ALL
SELECT ID, Name, 'H', H, 3 FROM yourTable UNION ALL
SELECT ID, Name, 'S', S, 4 FROM yourTable
ORDER BY ID, pos;

In many practical case number of columns are big enough to enlist in the query or even unknown in advance - so below approach is covering more generic cases - you don't need to know in advance number and name of columns
select id, name, key, value
from your_table t,
unnest([to_json_string((select as struct * except(id, name) from unnest([t])))]) json,
unnest(bqutil.fn.json_extract_keys(json)) key with offset
join unnest(bqutil.fn.json_extract_values(json)) value with offset
using (offset)
if applied to sample data in your question - output is

Related

Add information to one table from table contains duplicates

I have the following table:
In Table_1, (ID, Name) pairs can repeat and have any combination
Table_1:
ID
Name
Value1
Value2
1
John
34
45
1
John
15
78
2
Randy
67
12
2
Randy
40
46
1
Randy
23
85
2
Holmes
10
100
I want to find all information for all unique pairs. So the output should be:
ID
Name
Value1
Value2
1
John
34
45
2
Randy
67
12
1
Randy
23
85
2
Holmes
10
100
When I do SELECT DISTINCT(ID, Name) I get the unique pairs correctly. But how do I add value1, value2 columns to this. Because adding value1, value2 causes the pairs to repeat.
You may use DISTINCT ON here:
SELECT DISTINCT ON (ID, Name) *
FROM yourTable
ORDER BY ID, Name;
Demo
This will arbitrarily return one record from each (ID, Name) combination. Note that if you wanted to choose which of the duplicate pair (or more) records gets retained, you could add another level to the ORDER BY clause. For example, to choose the duplicate record with the highest Value2 value, you could use:
SELECT DISTINCT ON (ID, Name) *
FROM yourTable
ORDER BY ID, Name, Value2 DESC;
try row_number and partition by.
SELECT *
FROM (
select *,
row_number() over(partition by Name order by Name desc) rn
from Table_1) as a
where rn = 1;

Conditional Summing in SQL with special output

I need to create a SQL select statement but got some problems with it. Maybe someone can help out. My table looks something like this:
Article
Name
Amount
Location
Count
A0
Name0
10
99
1
A0
Name0
50
44
1
A0
Name0
20
44
1
A1
Name1
300
44
1
A1
Name1
250
110
1
A2
Name2
10
99
0
A3
Name3
20
1000
1
A4
Name4
NULL
NULL
1
I need to do a select statement that will sum up the amount per article in certain locations, if count = 1. My idea was to do something like this:
SELECT article, name, sum(amount), location
FROM test
WHERE count = 1
AND (location IN (44, 99)
OR location IS NULL)
GROUP BY article, name, location;
This will result in something like this:
ARTICLE
NAME
SUM(AMOUNT)
LOCATION
A0
Name0
70
44
A0
Name0
10
99
A4
Name4
-
-
A1
Name1
300
44
The only problem is, I would also need article A3 with an amount and location of 0 or NULL in the output table. So if there are no articles found in the location 44 or 99, the row must not be ommited and contains amount 0 and location 0. But if there are products in locations 99 or 44, there must not be a extra line with amount 0 and location 0.
Is this even possible with SQL? Thank you so much for your answers!
How about UNION of two SELECT statements?
1st returns "normal" data (rows that satisfy conditions you mentioned; count = 1 and location is 44 or 99)
2nd returns rows whose count is 1, but location doesn't match (NOT EXISTS clause)
Something like this:
SQL> select t.article, t.name, sum(t.amount) amount, t.location
2 from test t
3 where t.count = 1
4 and t.location in (44, 99)
5 group by t.article, t.name, t.location
6 union all
7 select t.article, t.name, sum(t.amount) amount, null
8 from test t
9 where t.count = 1
10 and not exists (select null from test a
11 where a.name = t.name
12 and a.location in (44, 99)
13 )
14 group by t.article, t.name, t.location
15 order by article, name, location;
ARTICLE NAME AMOUNT LOCATION
---------- ----- ---------- ----------
A0 Name0 70 44
A0 Name0 10 99
A1 Name1 300 44
A3 Name3 20
A4 Name4
SQL>
You could use conditional aggregation:
SELECT article, name,
sum(case when count = 1 and (location in (44, 99) or location is null) then amount end),
location
FROM test
GROUP BY article, name, location;

Re-Organize Access Table by converting Rows to Columns

I'm pretty new to access and SQL and need some help re-organizing a table. I have the following table (sorry for the table below - having trouble posting):
ID GroupID Distance Code Start_Finish
1 44 7 A S1
2 44 14 A F1
3 45 12 B S1
4 45 16 B F1
5 45 31 C S2
6 45 36 C F2
7 45 81 B S3
8 45 88 B F3
And need for the table to be transformed into:
GroupID Code Start_Distance Finish_Distance
44 A 7 14
45 B 12 16
45 C 31 36
45 B 81 88
try something like this
Select GroupID, Code, min(distance) as Start_distance, max(distance) as Finish_distance
from Table
group by GroupID, Code
If the min and max functions don't give you what you need, try it with First() and Last() instead.
Oops - just noticed you have 2 different entries in the output for GroupID 45 Code B - is that a requirement? With that data structure and requirement, the problem gets much more difficult.
Now I see the final column in the 1st table - I think that can be used to get the output you want:
Select GroupID, Code, mid(start_finish,2) as T, min(distance) as Start_distance, max(distance) as Finish_distance
from Table
group by GroupID, Code, T
You can use conditional aggregation for this.
select GroupID
, CODE
, max(case when Left(Start_Finish, 1) = 'S' then Distance end) as Start_Distance
, max(case when Left(Start_Finish, 1) = 'F' then Distance end) as Finish_Distance
from SomeTable
group by GroupID
, CODE

How to change column name and value depending on condition?

I want to change column name and value depending on condition.
My table is..
Roll Name Mark
3 Chaity 87
1 Anis 75
4 Unknown 30
2 Badol 0
And I want to get like this
Roll Name Grade
3 Chaity A+
1 Anis A
4 Unknown F
2 Badol F
Where Mark and Grade mapping is as follows:
0 to 60 is F
61 to 79 is A
80 and above is A+
A simple CASE expression will do it:
SELECT
Roll,
Name,
Grade = CASE
WHEN Mark <= 60 THEN 'F'
WHEN Mark <= 79 THEN 'A'
ELSE 'A+'
END
FROM tbl
ONLINE DEMO
you can use CASE WHEN ... or create a Grade table
SELECT Roll, Name, Grade = case when Mark >= 80 then 'A+'
when Mark between 61 and 79 then 'A'
else 'F'
end
FROM yourtable
using a Grade table
SELECT t.Roll, t.Name, g.Grade
FROM yourtable t
outer apply
(
select x.Grade
from GradeTable x
where t.Mark between x.Mark_from and x.Mark_to
) g
But isn't the grading system a bit drastic ? You either get an A or failed :(

Database Query Help required in MySQL

I am looking for help in writing a query of retrieving the values from 2 tables in MySQL.
The scenario is
Table A
ID Name Marks
===================
23 John 67
45 Mark 45
12 Ram 87
Table B has the following Structure
ID Name Evaluation Marks
==============================
45 Mark 34
78 Chris 09
98 Nancy 10
23 John 12
I am trying to write a query, where if I execute the following query
Select "SOMETHING" from Table A where Id=45
I should get Marks Column as 45+34=79, which should fetch and sum from the both the Tables A and Table B.
If I execute the query with the Id=12.
Since the Id=12, does not exists in the Table B, I should get the Marks as 87.
What would a query for the above?
I assume that the id occurs only once in your tables table a, but could be missing in both. If it always exists in table a, you can use a LEFT JOIN instead of the UNION.
SELECT COALESCE(SUM(marks), 0)
FROM
(
SELECT marks FROM a WHERE id = 45
UNION ALL
SELECT SUM(evaluation_marks) AS marks FROM b WHERE id = 45
) x
Edit
If you have all users in table a, then use
SELECT a.marks + COALESCE( SUM( b.evaluation_marks ), 0 )
FROM a
LEFT OUTER JOIN b ON ( b.id = a.id )
WHERE a.id = 45
GROUP BY a.id, a.marks
You should consider changing your table model though. Why do you store name and id twice? Can't you do it like that:
id name marks evaluation marks
=======================================
12 Ram 87 0
23 John 67 12
45 Mark 45 34
78 Chris 0 9
98 Nancy 0 10