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
Related
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 this postgresql function:
CREATE OR REPLACE FUNCTION public.test_q1()
RETURNS TABLE(schemaname text)
LANGUAGE plpgsql
AS $function$
BEGIN
return Query (select "content" from public.post where id='863550630468626253');
-- return Query (select count(*) from public.comments WHERE post_id='863550630468626253');
END
$function$
;
How can I return both select statement result from this function?
Is it possible to return the result set of two select statement from function such that when i call public.test_q1 it will return two values first resultset will be the values of content and other columns inside first Select and second return value will be the count ?
Return two values in one query?
select p."content",
(select count(*)
from public.comments c
where c.post_id = '863550630468626253'
) as num_comments
from public.post p
where p.id = '863550630468626253'
);
EDIT:
I don't think you can guarantee the order of results in the returned set of a function, but you can use union all to return the two values. Presumably, content is not a number. So, one method is to cast the values:
select p."content"
from public.post p
where p.id = '863550630468626253'
union all
select count(*)::text
from public.comments c
where c.post_id = '863550630468626253';
I believe in practice this will return the content first and the count second. However, I don't think Postgres guarantees the ordering.
I'm currently building a query and apparently, it doesn't work. This is my current query (this is the full one)
IF NOT EXISTS(SELECT * FROM invite WHERE uid=$1::uuid)
BEGIN
INSERT INTO invite (uid, link) VALUES ($1::uuid, $2::text);
END
ELSE
BEGIN
SELECT * FROM invite WHERE uid=$1::uuid;
END
In my console, i'm getting syntax error at or near "IF"
Many errors in your code:
plpgsql code can return result only from function (not from DO)
IF clause must have THEN
you do not need BEGIN / END in each statement inside IF ... THEN ... ELSE ... END (not error also has no sense)
Probably is better to do this by pure SQL:
WITH q AS (
SELECT *
FROM invite
WHERE uid=$1::uuid
), i AS (
INSERT INTO invite (uid, link)
SELECT $1::uuid, $2::text
WHERE (SELECT count(*) = 0 FROM q)
RETURNING *
) SELECT * FROM q
UNION SELECT * FROM i
I need makes this function to load teams from pl_raw to new table named game.
In pl_raw to be all data. Now.. this function in order to order data for tables...
CREATE OR REPLACE FUNCTION loadGames() RETURNS void AS $$
BEGIN
IF (SELECT * from pldata.pl_raw where venue = 'Away') THEN
INSERT INTO definitiu.game (date_game, home_team, away_team)
SELECT game_date, opposition_id,team_id from pldata.pl_raw;
ELSIF (SELECT * from pldata.pl_raw where venue = 'Home') THEN
INSERT INTO definitiu.game (date_game, home_team, away_team)
SELECT game_date, team_id, opposition_id from pldata.pl_raw;
END IF;
END; $$
LANGUAGE PLPGSQL;
The function compile, but, Postgres return this error when I execute this function:
ERROR: subquery must return only one column
LINE 1: SELECT (SELECT * from pldata.pl_raw where venue = 'Away')
^
QUERY: SELECT (SELECT * from pldata.pl_raw where venue = 'Away')
CONTEXT: PL/pgSQL function loadgames() line 4 at IF
********** Error **********
ERROR: subquery must return only one column
SQL state: 42601
Context: PL/pgSQL function loadgames() line 4 at IF
Use EXISTS:
IF EXISTS (SELECT * from pldata.pl_raw where venue = 'Away') THEN
I am trying to extract the count of records corresponding to a specific date and user_ids which do not have corresponding user_ids for the next later date in the database. This is the way I am trying to accomplish it (using plpgsql but not defining a function:
DO
$BODY$
DECLARE
a date[]:= array(select distinct start_of_period from monthly_rankings where balance_type=2);
res int[] = '{}';
BEGIN
FOR i IN array_lower(a,1) .. array_upper(a,1)-1
LOOP
res:=array_append(res,'SELECT COUNT(user_id) from (select user_id from monthly_rankings where start_of_period=a[i] except select user_id from monthly_rankings where start_of_period=a[i+1]) as b');
i:=i+1;
END LOOP;
RETURN res;
$BODY$ language plpgsql
I get an Error: could not Retrieve the result : ERROR: RETURN cannot have a parameter in function returning void
LINE 11: RETURN res;
I am new to this procedural language and cannot spot why the function is returning void. I do assign the values to variables , and I declared empty - not NULL - arrays. Is there a syntax or a more significant reasoning mistake?
1.) You cannot RETURN from a DO statement at all. You would have to CREATE FUNCTION instead.
2.) You don't need any of this. Use this query, which will be faster by an order of magnitude:
WITH x AS (
SELECT DISTINCT start_of_period
,rank() OVER (ORDER BY start_of_period) AS rn
FROM monthly_rankings
WHERE balance_type = 2
)
SELECT x.start_of_period, count(*) AS user_ct
FROM x
JOIN monthly_rankings m USING (start_of_period)
WHERE NOT EXISTS (
SELECT 1
FROM x x1
JOIN monthly_rankings m1 USING (start_of_period)
WHERE x1.rn = x.rn + 1
-- AND m1.balance_type = 2 -- only with matching criteria?
AND m1.user_id = m.user_id
)
-- AND balance_type = 2 -- all user_id from these dates?
GROUP BY x.start_of_period
ORDER BY x.start_of_period
This includes the last qualifying start_of_period, you may want to exclude it like in your plpgsql code.