BigQuery use 2 pivots for 2 columns - sql

I have this table:
key
value
team
number
score
35
team
5
and I want to create the above table:
score
team
35
5
meaning I want to make 2 pivots. Is there a way to do it in one query?
the following code doesn't work in bigquery:
with a as(
select 'score' as key, 35 as value, 'team' as team, 5 as number
)
select *
from a
pivot (any_value(value) for key in ('score'))
pivot (any_value(number) for team in ('team'))

See simple fix below
select * from (
select * from your_table
pivot (any_value(value) for key in ('score'))
)
pivot (any_value(number) for team in ('team'))
with output like below

Related

SQL Server: pivoting without aggregation on a table with two columns

This is a question on a test. I have a table with two columns. I want to pivot on one of them and output the other.
Table structure:
(Name varchar(10), Age int)
I need output with age values as columns and Names listed below each age value.
From searching, I only see examples where there is at least one other column that is used to "group by" for want of a better term. In other words, there is a common factor in each row of the output. My problem does not have this property.
I tried:
SELECT
[agevalue1], [agevalue2], [agevalue3], [agevalue4]
FROM
(SELECT Name, Age FROM MyClass) AS SourceTable
PIVOT
(MAX(Name)
FOR Age IN ([agevalue1], [agevalue2], [agevalue3], [agevalue4])
) AS PivotTable;
I specified agevalue* as a string, i.e. in quotes. I got the column headings alright but a row of NULLS below them.
P.S.: The solution does not need to use pivot but I couldn't think of an alternative approach.
Sample Data:
Name Age
Bob 11
Rick 25
Nina 30
Sam 11
Cora 16
Rachel 25
Desired output:
11 16 25 30
Bob Cora Rick Nina
Sam NULL Rachel NULL
Try this :
with tab as
(
Select 'A' Name, 10 Age union all
Select 'B',11 union all
Select 'c',10 union all
Select 'D',11 union all
Select 'E',11 union all
Select 'F',11
)
select distinct
Age
, stuff((
select ',' + g.Name
from tab g
where g.age = g1.age
order by g.age
for xml path('')
),1,1,'') as Names_With_Same_Age
from tab g1
group by g1.age,Name
To group these together in one row:
11 16 25 30
Bob Cora Rick Nina
and separate them from another set, like:
11 16 25 30
Sam NULL Rachel NULL
they must have something different between each row, since doing a MAX(Name) would get you only one Name for each Age.
This query creates a number that links a particular Age to a row number and then pivots the result. As you said, the PIVOT will group by all columns not referenced in the PIVOT function, so it will group by this row indexer, separating the values like you wanted.
;WITH IndexedClass AS
(
SELECT
M.Name,
M.Age,
-- The ordering will determine which person goes first for each Age
RowIndexer = ROW_NUMBER() OVER (PARTITION BY M.Age ORDER BY M.Name)
FROM
MyClass AS M
)
SELECT
P.[11],
P.[16],
P.[25],
P.[30]
FROM
IndexedClass AS I
PIVOT (
MAX(I.Name) FOR I.Age IN ([11], [16], [25], [30])
) AS P

SQL query with grouping and MAX

I have a table that looks like the following but also has more columns that are not needed for this instance.
ID DATE Random
-- -------- ---------
1 4/12/2015 2
2 4/15/2015 2
3 3/12/2015 2
4 9/16/2015 3
5 1/12/2015 3
6 2/12/2015 3
ID is the primary key
Random is a foreign key but i am not actually using table it points to.
I am trying to design a query that groups the results by Random and Date and select the MAX Date within the grouping then gives me the associated ID.
IF i do the following query
select top 100 ID, Random, MAX(Date) from DateBase group by Random, Date, ID
I get duplicate Randoms since ID is the primary key and will always be unique.
The results i need would look something like this
ID DATE Random
-- -------- ---------
2 4/15/2015 2
4 9/16/2015 3
Also another question is there could be times where there are many of the same date. What will MAX do in that case?
You can use NOT EXISTS() :
SELECT * FROM YourTable t
WHERE NOT EXISTS(SELECT 1 FROM YourTable s
WHERE s.random = t.random
AND s.date > t.date)
This will select only those who doesn't have a bigger date for corresponding random value.
Can also be done using IN() :
SELECT * FROM YourTable t
WHERE (t.random,t.date) in (SELECT s.random,max(s.date)
FROM YourTable s
GROUP BY s.random)
Or with a join:
SELECT t.* FROM YourTable t
INNER JOIN (SELECT s.random,max(s.date) as max_date
FROM YourTable s
GROUP BY s.random) tt
ON(t.date = tt.max_date and s.random = t.random)
In SQL Server you could do something like the following,
select a.* from DateBase a inner join
(select Random,
MAX(dt) as dt from DateBase group by Random) as x
on a.dt =x.dt and a.random = x.random
This method will work in all versions of SQL as there are no vendor specifics (you'll need to format the dates using your vendor specific syntax)
You can do this in two stages:
The first step is to work out the max date for each random:
SELECT MAX(DateField) AS MaxDateField, Random
FROM Example
GROUP BY Random
Now you can join back onto your table to get the max ID for each combination:
SELECT MAX(e.ID) AS ID
,e.DateField AS DateField
,e.Random
FROM Example AS e
INNER JOIN (
SELECT MAX(DateField) AS MaxDateField, Random
FROM Example
GROUP BY Random
) data
ON data.MaxDateField = e.DateField
AND data.Random = e.Random
GROUP BY DateField, Random
SQL Fiddle example here: SQL Fiddle
To answer your second question:
If there are multiples of the same date, the MAX(e.ID) will simply choose the highest number. If you want the lowest, you can use MIN(e.ID) instead.

SQL Remove Duplicates, save lowest of certain column

I've been looking for an answer to this but couldn't find anything the same as this particular situation.
So I have a one table that I want to remove duplicates from.
__________________
| JobNumber-String |
| JobOp - Number |
------------------
So there are multiples of these two values, together they make the key for the row. I want keep all distinct job numbers with the lowest job op. How can I do this? I've tried a bunch of things, mainly trying the min function, but that only seems to work on the entire table not just the JobNumber sets. Thanks!
Original Table Values:
JobNumber Jobop
123 100
123 101
456 200
456 201
780 300
Code Ran:
DELETE FROM table
WHERE CONCAT(JobNumber,JobOp) NOT IN
(
SELECT CONCAT(JobNumber,MIN(JobOp))
FROM table
GROUP BY JobNumber
)
Ending Table Values:
JobNumber Jobop
123 100
456 200
780 300
With SQL Server 2008 or higher you can enhance the MIN function with an OVER clause specifying a PARTITION BY section.
Please have a look at https://msdn.microsoft.com/en-us/library/ms189461.aspx
You can simply select the values you want to keep:
select jobOp, min(number) from table group by jobOp
Then you can delete the records you don't want:
DELETE t FROM table t
left JOIN (select jobOp, min(number) as minnumber from table group by jobOp ) e
ON t.jobob = e.jobob and t.number = e.minnumber
Where e.jobob is null
I like to do this with window functions:
with todelete as (
select t.*, min(jobop) over (partition by numbers) as minjop
from table t
)
delete from todelete
where jobop > minjop;
It sounds like you are not using the correct GROUP BY clause when using the MIN function. This sql should give you the minimum JobOp value for each JobNumber:
SELECT JobNumber, MIN(JobOp) FROM test.so_test GROUP BY JobNumber;
Using this in a subquery, along with CONCAT (this is from MySQL, SQL Server might use different function) because both fields form your key, gives you this sql:
SELECT * FROM so_test WHERE CONCAT(JobNumber,JobOp)
NOT IN (SELECT CONCAT(JobNumber,MIN(JobOp)) FROM test.so_test GROUP BY JobNumber);

SQL Server - Compare on multiple columns in differnet rows within same table

I have a table that holds the values read from 2 meters:
----------------------------
Date | MeterID | Value
----------------------------
1/2/14 A 1.3
2/2/14 A 1.8
2/2/14 B 3.8
3/3/14 A 1.2
4/3/14 A 1.8
4/3/14 B 2.9
I need a query that will count the number of days that a reading exists for BOTH meter types (A & B)?
In the example above this should yield 2 as the result.
Thanks.
You can use a temporary table to list [Date]s when there were occurrences in MeterID for both A and B and then COUNT() all this [Date]s :
SELECT COUNT(t.*)
FROM ( SELECT [Date]
FROM [table]
GROUP BY [Date]
HAVING COUNT(DISTINCT [MeterID]) = 2
) t
Another solution, using sets:
select count(*) from
(
select distinct date from table where meterid='A'
intersect
select distinct date from table where meterid='B'
) x

How do I aggregate numbers from a string column in SQL

I am dealing with a poorly designed database column which has values like this
ID cid Score
1 1 3 out of 3
2 1 1 out of 5
3 2 3 out of 6
4 3 7 out of 10
I want the aggregate sum and percentage of Score column grouped on cid like this
cid sum percentage
1 4 out of 8 50
2 3 out of 6 50
3 7 out of 10 70
How do I do this?
You can try this way :
select
t.cid
, cast(sum(s.a) as varchar(5)) +
' out of ' +
cast(sum(s.b) as varchar(5)) as sum
, ((cast(sum(s.a) as decimal))/sum(s.b))*100 as percentage
from MyTable t
inner join
(select
id
, cast(substring(score,0,2) as Int) a
, cast(substring(score,charindex('out of', score)+7,len(score)) as int) b
from MyTable
) s on s.id = t.id
group by t.cid
[SQLFiddle Demo]
Redesign the table, but on-the-fly as a CTE. Here's a solution that's not as short as you could make it, but that takes advantage of the handy SQL Server function PARSENAME. You may need to tweak the percentage calculation if you want to truncate rather than round, or if you want it to be a decimal value, not an int.
In this or most any solution, you have to count on the column values for Score to be in the very specific format you show. If you have the slightest doubt, you should run some other checks so you don't miss or misinterpret anything.
with
P(ID, cid, Score2Parse) as (
select
ID,
cid,
replace(Score,space(1),'.')
from scores
),
S(ID,cid,pts,tot) as (
select
ID,
cid,
cast(parsename(Score2Parse,4) as int),
cast(parsename(Score2Parse,1) as int)
from P
)
select
cid, cast(round(100e0*sum(pts)/sum(tot),0) as int) as percentage
from S
group by cid;