I've encountered a rather unintuitive behaviour while unnesting array results returned FROM sub-SELECT.
SELECT unnest(c).*
FROM (SELECT chat_messages[0 : array_length(chat_messages, 1)]
FROM Chats WHERE chat_id = 2) c
This was my original query. Postgres doens't like it:
function unnest(record) does not exist
But this seemingly equivalent query works:
SELECT *
FROM unnest((SELECT chat_messages[0 : array_length(chat_messages, 1)]
FROM Chats WHERE chat_id = 2)) c
This query doesn't work either with the same error message:
SELECT *
FROM (SELECT chat_messages[0 : array_length(chat_messages, 1)]
FROM Chats WHERE chat_id = 2) c,
unnest(c) u
I'm pretty sure I'm missing something here. Why such behaviour? And how come subquery returns record type when it's defined composite type?
In the first and third queries c is formally a set of rows (of pseudo-type record), so you cannot unnest(c). You should use a value instead (I skipped slice as irrelevant):
Query #1:
SELECT (unnest(val)).*
FROM (
SELECT chat_messages
FROM Chats WHERE chat_id = 2
) c(val);
-- or
SELECT (unnest(val)).*
FROM (
SELECT chat_messages val
FROM Chats WHERE chat_id = 2
) c;
Query #3:
SELECT *
FROM (
SELECT chat_messages
FROM Chats WHERE chat_id = 2
) c(val),
unnest(val) u;
-- or
SELECT *
FROM (
SELECT chat_messages val
FROM Chats WHERE chat_id = 2
) c,
unnest(val) u;
In query #2 you extract a value by using additional brackets, so the result is not a row but a value (an array in this case). This query will raise an error if the inner query returns more than one row.
Related
I have a bigquery repeated table on created_date, as in the image below
I am trying to do a simply WHERE query but receiving the error No matching signature for operator = for argument types: ARRAY<STRING>, STRING. Supported signature: ANY = ANY
Desired output:
just 2022-03-03T00:00:00,company_1, compapnu_1.com (without the 2nd row)
What is the correct way to query a repeated field?
My attempt:
SELECT * FROM `project.dataset.table`
WHERE website = "company_1"
An array needs to be unnested before comparing.
with tbl as (select "2022" date, ["comp1","comp2"] company_name union all select "2000",["test","comp1","comp1","comp1"])
select *
from tbl
where (Select true from unnest(company_name) X where X = "comp1" limit 1)
or unnest the field direct to keep only these rows:
with tbl as (select "2022" date, ["comp1","comp2"] company_name, ["w1","w2"] as website union all select "2000",["test","comp1","comp1","comp1"],split("Wt w1 w2 w3 w4 w5", " "))
select * except(company_name,website),company_name_single
from tbl,
unnest(company_name) company_name_single with offset company_name_num,
unnest(website) website_single with offset website_num
where company_name_single = "comp1" and company_name_num=website_num
On the BigQuery SELECT syntax page it gives the following:
query_statement:
query_expr
query_expr:
[ WITH cte[, ...] ]
{ select | ( query_expr ) | set_operation }
[ ORDER BY expression [{ ASC | DESC }] [, ...] ]
[ LIMIT count [ OFFSET skip_rows ] ]
I understand how the (second line) select could be either:
{ select | set_operation }
But what is the ( query_expr ) in the middle for? For example, if it can refer to itself, wouldn't it make the possibility to construct a lisp-like query such as:
with x as (select 1 a)
(with y as (select 2 b)
(with z as (select 3 c)
select * from x, y, z))
Actually, I just tested it and the answer is yes. If so, what would be an actual use case of the above construction where you can use ( query_expr ) ?
And, is there ever a case where using a nested CTE can do something that multiple CTEs cannot? (For example, the current answer is just a verbose way or writing what would more properly be written with a single WITH expression with multiple CTEs)
The following construction might be useful to model how an ETL flow might work and to encapsulate certain 'steps' that you don't want the outer query to have access to. But this is quite a stretch...
WITH output AS (
WITH step_csv_query AS (
SELECT * FROM 'Sales.CSV'),
step_filter_csv AS (
SELECT * FROM step_csv_query WHERE Country='US'),
step_mysql_query AS (
SELECT * FROM MySQL1 LEFT OUTER JOIN MySQL2...),
step_join_queries AS (
SELECT * FROM step_filter_csv INNER JOIN step_mysql_query USING (id)
) SELECT * FROM step_join_queries -- output is last step
) SELECT * FROM output -- or whatever we want to do with the output...
This query might be useful in that case where a CTE is being referred to by subsequent CTEs.
For instance, you can use this if you want to join two tables, use expressions and query the resulting table.
with x as (select '2' as id, 'sample' as name )
(with y as ( select '2' as number, 'customer' as type)
(with z as ( select CONCAT('C00',id), name, type from x inner join y on x.id=y.number)
Select * from z))
The above query gives the following output :
Though there are other ways to achieve the same, the above method would be much easier for debugging.
Following article can be referred for further information on the use cases.
In nested CTEs, the same CTE alias can be reused which is not possible in case of multiple CTEs. For example, in the following query the inner CTE will override the outer CTEs with the same alias :
with x as (select '1')
(with x as (select '2' as id, 'sample' as name )
(with x as ( select '2' as number, 'customer' as type)
select * from x))
In a SQL function I can return a boolean if I do
with myquery as (delete from mytable where id = 'value1' returning 1)
select exists (select * from another_function('value2') where (select count(*) from myquery) > 0);
But in a plpgsql function it doesn't work and gives error query has no destination for result data.
In this function I want to execute another_function if any rows were actually deleted from mytable. The problem is I'm repeating the entire select exists part, because I'm using that in multiple functions.
Is it possible to move more of that logic into another_function? So that I can do something like this?
with myquery as (delete from mytable where id = 'value1' returning 1)
select * from another_function('value2', myquery)
How can I pass the CTE into a function so I don't need to repeat the select exists and where (select count(*) from myquery) > 0) every time I want to call another_function?
I would expect an auxiliary function to take an argument, such as the id being returned from the delete. It would then look like:
with d as (
delete from mytable
where id = 'value1'
returning id
)
select another_function('value2', d.id)
from d;
This operates one value at a time, but that is typically what one would want. If you wanted all ids passed in at once, you could use an array:
with d as (
delete from mytable
where id = 'value1'
returning id
)
select another_function('value2', array_agg(d.id))
from d;
I have a select query that returns multiple rows, and I want to check if all rows are the same. So something like this:
anything_other_than(123) in (select id from foo)
So, if select id from foo returns 111,222,123,333 the statement above is false, and if select id from foo returns 123,123,123 it's true. How can I achieve this?
A simple solution is to use the = ALL operator:
SELECT 123 = ALL (SELECT id FROM foo);
This solution also stop scanning the result as soon as the first non-matching value is found.
Another option is to use EXISTS with a where condition:
select not exists (select *
from the_table
where id <> 123);
Run this:
select count(distinct id) = 1 and count(*) > 1 from foo;
count(distinct id) will return 1 if all the rows are the same
and count(*) will return the total number of rows.
If this returns true then you have more than 1 rows and all the rows are the same.
You can use something like this -
select x, IF(x > 1, "FALSE", "TRUE") from
(SELECT count(distinct(A.id)) as x FROM foo as A) as B;
Do refer this https://www.w3schools.com/sql/func_mysql_if.asp
Is it possible to do something like the following with SQL, not PL/pgSQL (note if it's only possible with PL/pgSQL, then how)?
IF password = 'swordfish' THEN
SELECT a, b, c FROM users;
ELSE
SELECT -1; -- unauthorized error code
END IF;
Ideally, could I wrap the above in a function with TRUE being an argument?
Rather, is it possible to set the command status string to -1?
I'm asking this because I want the query to return an error code, like -1, if someone tries to get a list of all the users with the wrong password. This is for a web app with user accounts that each have a password. So, this is not something I want to manage with database roles/permissions.
Algorithm
Select 1 into a (authorized) if we find a user_id_1-session_id match.
Select 0, NULL, NULL into u (unauthorized) if we didn't find a match in step 1.
Select user_id, body, sent into s (select) if we did find a match in step 1.
Union u and s.
Code
-- List messages between two users with `user_id_1`, `session_id`, `user_id_2`
CREATE FUNCTION messages(bigint, uuid, bigint) RETURNS TABLE(i bigint, b text, s double precision) AS
$$
WITH a AS (
SELECT 1
FROM sessions
WHERE user_id = $1
AND id = $2
), u AS (
SELECT 0, NULL::text, NULL::double precision
WHERE NOT EXISTS (SELECT 1 FROM a)
), s AS (
SELECT user_id, body, trunc(EXTRACT(EPOCH FROM sent))
FROM messages
WHERE EXISTS (SELECT 1 FROM a)
AND chat_id = pair($1, $3)
LIMIT 20
)
SELECT * FROM u UNION ALL SELECT * FROM s;
$$
LANGUAGE SQL STABLE;
The PL/pgsql function below returns the messages sent between user_id & with_user_id if the user_id:key pair is authorized, as determined by the user-defined function (UDF) user_auth. Otherwise, it returns one row with from = -1 . The other UDF, pair, is a unique unordered pairing function that, given two user IDs, returns the chat_id to which the messages belong.
--- Arguments: user_id, key, with_user_id
CREATE FUNCTION messages(bigint, uuid, bigint)
RETURNS TABLE(from bigint, body text, sent double precision) AS $$
BEGIN
IF user_auth($1, $2) THEN
RETURN QUERY SELECT from, body, trunc(EXTRACT(EPOCH FROM sent))
FROM messages WHERE chat_id = pair($1, $3);
ELSE
i := -1;
RETURN NEXT;
END IF;
END;
$$ LANGUAGE plpgsql STABLE;
I don't know how to translate this to an SQL function or whether that would be better.
This will work, but it's not pretty:
WITH
u AS (SELECT * FROM user WHERE mail = '..'),
code AS (
SELECT
CASE (SELECT count(*) FROM u)
WHEN 0 THEN
'not found'
ELSE
CASE (SELECT count(*) FROM u WHERE password = '..')
WHEN 1 THEN
'right password'
ELSE
'wrong password'
END
END)
SELECT
code.*,
u.*
FROM code NATURAL LEFT OUTER JOIN u
I think you might want to look into creating a result set returning function instead.
There is a CASE expression in addition to the (pl/pgsql only) CASE control structure.
EDIT: CASE expression in sql context:
SELECT CASE
WHEN my_conditions_are_met THEN a
ELSE NULL
END AS a_or_null,
b,
c
FROM users;
EDIT 2: given your example that's how you can do it in pure SQL:
WITH params AS (
SELECT user_auth(:user_id, :key) AS user_auth,
pair(:user_id, :with_user_id) AS chat_id
), error_message AS (
SELECT -1 AS "from",
'auth error' AS "body",
EXTRACT(EPOCH FROM CURRENT_TIMESTAMP) AS "sent"
)
SELECT from, body, trunc(EXTRACT(EPOCH FROM sent))
FROM messages
JOIN params ON messages.chat_id = params.chat_id AND params.user_auth
UNION ALL
SELECT error_message.*
FROM error_message
JOIN params ON NOT params.user_auth