BigQuery SQL same column multiple expressions - sql

I'm using Google's Big Query service to do some data processing...my database looks like:
value
-----
'a'
'b'
'a'
'a'
'a'
'b'
I want to write a query to count the occurrences of the various values.
Example:
Count('a') Count('b')
---------- ----------
4 3
I'd normally use Case to solve this; but BQ doesn't support Case.
Anyone have any ideas?
Thanks!

The first thing I would suggest is a group by:
select value, count(*)
from t
group by value
But you seem to want the values in one row. According to this documentation, it does support case. If you prefer, you can use if:
select sum(if(value = 'A', 1, 0)) as A, sum(if(value = 'B', 1, 0)) as B
from t

Related

Is there a function in PostgreSQL that counts string match across columns (row-wise)

I want to overwrite a number based on a few conditions.
Intended overwrite:
If a string (in the example I use is just a letter) occurs across 3 columns at least 2 times and the numerical column is more than some number, overwrite the numerical value OR
If another string occurs across 3 columns at least 2 times and the numerical column is more than some other number, overwrite the numerical value, else leave the numerical value unchanged.
The approach I thought of first, works but only if the table has one row. Could this be extended somehow so it could work on more rows? And if my approach is wrong, would you please direct me to the right one?
Please, see the SQL Fiddle
Any help is highly appreciated!
if letter a repeats at least 2 times among section_1,section_2,section_3 and number >= 3 then overwrite number with 3 or if letter b repeats at least 2 times among section_1,section_2,section_3 and number >= 8 write 8, else leave number unchanged
CREATE TABLE sections (
id int,
section_1 text,
section_2 text,
section_3 text,
number int
);
INSERT INTO sections VALUES
( 1, 'a', 'a', 'c', 5),
( 2, 'b', 'b', 'c', 9),
( 3, 'b', 'b', 'c', 4);
expected result:
id number
1 3
2 8
3 4
Are you looking for a case expression?
select (case when (section_1 = 'a')::int + (section_2 = 'a')::int + (section_3 = 'a')::int >= 2 and
other_col > threshold
then 'special'
end)
You can have additional when conditions. And include this in an update if you really wand to change the value.
A typical solution uses a lateral join to unpivot:
select s.*, x.number as new_number
from sections s
cross join lateral (
select count(*) number
from (values (s.section_1), (s.section_2), (s.section_3)) x(section)
where section = 'a'
) x;
This is a bit more scalable than repeating conditional expression, since you just need to enumerate the columns in the values() row constructor of the subquery.

Returning several values within a CASE expression in subquery and separate columns again in main query

My test table looks like this:
# select * from a;
source | target | id
--------+--------+----
1 | 2 | 1
2 | 3 | 2
3 | 0 | 3
My query is this one:
SELECT *
FROM (
SELECT
CASE
WHEN id<>1
THEN source
ELSE 0
END
AS source,
CASE
WHEN id<>1
THEN target
ELSE 0
END
AS target
FROM a
) x;
The query seems a bit odd because the CASE expression with the same criteria is repeated for every column. I would like to simplify this and tried the following, but it doesn't work as expected.
SELECT *
FROM (
SELECT
CASE
WHEN id<>1
THEN (source, target)
ELSE (0, 0)
END
AS r
FROM a
) x;
It yields one column with a row value, but I would rather get the two original columns. Separating them with a (r).* or similar doesn't work, because the "record type has not been registered".
I found several questions here with solutions regarding functions returning RECORD values, but none regarding this example with a sub-select.
Actually, there is a quite long list of columns, so repeating the same CASE expression many times makes the whole query quite unreadable.
Since the real problem - as opposed to this simplified case - consists of several CASE expressions and several column groups, a solution with a UNION won't help, because the number of UNIONs would be large and make it unreadable as well as several CASEs.
My actual question is: How can I get the original columns from the row value?
This answers the original question.
If I understood your needs, you want 0 and 0 for source and target when id = 1:
SELECT
0 AS source,
0 AS target
FROM tablename
WHERE id = 1
UNION ALL
SELECT
source,
target
FROM tablename
WHERE id <> 1
Revised answer: You can make your query work (fixing the record type has not been registered issue) by creating a TYPE:
CREATE TYPE stpair AS (source int, target int);
And cast the composite value column to that type:
SELECT id, (cv).source, (cv).target
FROM (
SELECT id, CASE
WHEN id <> 1 THEN (source, target)::stpair
ELSE (0, 0)::stpair
END AS cv
FROM t
) AS x
Having said that, it should be far more convenient to use arrays:
SELECT id, av[1] AS source, av[2] AS target
FROM (
SELECT id, CASE
WHEN id <> 1 THEN ARRAY[source, target]
ELSE ARRAY[0, 0]
END AS av
FROM t
) AS x
Demo on db<>fiddle
Will this work for you?
select source,target,id from a where id <>1 union all select 0 as source,0 as target,id from a where id=1 order by id
I have used union all to included cases where multiple records may have ID=1

To get different row values in different columns in a single row

My table looks like this:
Param_id Param_value
------------------------
A 1
B 2
C 3
D 4
.... and so on. Now I want only the values of Param_id "A" and "B".
Now I want to get the param_value in two different columns instead of two different rows. But if I use IN clause it will return the result in two rows.
I want something like the below:
Param_value_1 Param_value_2
---------------------------------
1 2
I can't use listagg or pivot because they are not serving my purpose. Is there any other way to achieve this? I searched in Google but could not find any solution for this.
The old way of pivoting... Since you are looking for the parameter values for parameter_id in ('A', 'B'), it doesn't make much sense to name the resulting columns param_value_1 and param_value_2; why not param_value_a and param_value_b? (Otherwise what determines that 'A' is 1 and 'B' is 2, and not the other way around?)
So - back to the old way of pivoting (although I suspect PIVOT will work too, regardless of requirement - unless you are on Oracle 10 or lower):
select max(case when param_id = 'A' then param_value end) as param_value_a,
max(case when param_id = 'B' then param_value end) as param_value_b
from your_table;

How to categorize several columns in one statement in SQL/PLSQL

I've got a table with 20 columns which I like to categorize like;
0-25 --> 1
25-50 --> 2
50-75 --> 3
75-100 --> 4
I prefer not to use 20 case ... when statements. Anyone who knows how to do this more dynamically & efficiently? Can be SQL or PL/SQL.
I tried some PL/SQL, but I didn't see a simple method to use the column names as variables.
Many thanks.
Frans
Your example is a bit confusing, but assuming you want to put a certain value into those categories, the function width_bucket might be what you are after:
Something like this:
with sample_data as (
select trunc(dbms_random.value(1,100)) as val
from dual
connect by level < 10
)
select val, width_bucket(val, 0, 100, 4) as category
from sample_data;
This will assign the numbers 1-4 to the (random) values from sample_data. the 0, 100 defines the range from which to build the buckets, and the final parameter 4 says in how many (equally wide) buckets this should be distributed. The result of the function is the bucket into which the value val would fall.
SQLFiddle example: http://sqlfiddle.com/#!4/d41d8/10721
The case statement is probably the most efficient way of doing it. A more dynamic way would be to create a table using the with statement. Here is an example of the code:
with ref as (
select 0 as lower, 25 as higher 1 as val from dual union all
select 25, 59, 2 from dual union all
select 50, 75, 3 from dual union all
select 75, 100, 4 from dual
)
select ref.val
from t left outer join ref
on t.col >= ref.lower and t.col < ref.higher
That said, this particular lookup could be done with arithmetic:
select trunc((t.col - 1) / 25) + 1 as val
from t
And, if your problem is managing the different columns, you might consider unpivot. However, I think it is probably easier just to write the code and modify the column names in a text editor or Excel.

How to transform vertical data into horizontal data with SQL?

I have a table "Item" with a number of related items, like so:
ID Rel_ID Name RelRank
--- ------ ---- -------
1 1 foo 1
2 1 bar 2
3 1 zam 3
4 2 foo2 1
I'm trying to get a query so items with the same Rel_ID would appear in the same row, like so:
Rel_ID Name1 Name2 Name3
------ ----- ----- -----
1 foo bar zam
2 foo2
I've tried selecting the table multiple times:
SELECT k.Rel_ID, k.name 'Name1', k2.name 'Name2'
FROM item k, item k2
WHERE k.Rel_ID = k2.Rel_ID
But this fails. Surely there's a transformation or query that could drastically simplify the process, and I'm just missing it because I haven't used SQL in this way before. What am I missing?
[Edit: added RelRank column, which does appear in my data]
Regardless of the database you are using, the concept of what you are trying to achieve is called "Pivot Table".
Here's an example for mysql:
http://en.wikibooks.org/wiki/MySQL/Pivot_table
Some databases have builtin features for that, see the links below.
SQLServer:
http://msdn.microsoft.com/de-de/library/ms177410.aspx
Oracle:
http://www.dba-oracle.com/t_pivot_examples.htm
You can always create a pivot by hand. Just select all the aggregations in a result set and then select from that result set. Note, in your case, you can put all the names into one column using concat (i think that's group_concat in mysql), since you cannot know how many names are related to a a rel_id.
pseudo-select for your case (i don't know mysql):
select rel_id, group_concat(name) from item group by rel_id
I think you are looking for a mysql specific answer.
Keep in mind that the syntax could vary across different data stores.
MySQL has a feature that makes this easy.
SELECT Rel_ID, GROUP_CONCAT(Name SEPARATOR ' ') As Names FROM Item GROUP BY Rel_ID;
that should work :-)
if the names that you listed are static,my below query that i runned sucessfully in sqlfiddle will work
SELECT rel_id,
MAX (DECODE (rel_id, '1', DECODE (relrank, '1', name) , '2',DECODE (relrank, '1', name))) NAME1,
MAX (DECODE (rel_id, '1', DECODE (relrank, '2', name))) NAME2,
MAX (DECODE (rel_id, '1', DECODE (relrank, '3', name))) NAME3
FROM supportContacts
GROUP BY rel_id
heres the SQL fiddle
http://sqlfiddle.com/#!4/480e2/11