SQL CASE in GROUP BY - postgresql

I have a query that is meant to get the count of each reaction on a comment and whether the user is one of the people who made that reaction. However, my case statement returns includesMe:1 for EVERY reaction when the user has made at least one reaction on the comment.
Eg, if the user only reacted to 🥰, my query returns that they reacted to every reaction:
SELECT count(*) as "numReacts",
reaction,
case when exists(
select *
from "CommentReaction" as i
where i."userId" = 'b8b660c9-c416-42b6-9142-19112a9ff811'
and i."commentId" = 'c142787b-4422-4128-8357-58d36c177307'
and i.reaction = reaction
)
then 1
else 0
end as "includesMe"
FROM "CommentReaction"
WHERE "commentId" = 'c142787b-4422-4128-8357-58d36c177307'
GROUP BY reaction;

The condition:
and i.reaction = reaction
is evaluated as TRUE because the unqualified column reaction is considered to be the column of the inner subquery's table.
You could alias the outer table, for example
FROM "CommentReaction" AS t
and change to
and i.reaction = t.reaction
but I believe that you can solve your problem with a simpler query if you use conditional aggregation:
SELECT COUNT(*) AS "numReacts",
reaction,
MAX(CASE WHEN "userId" = 'b8b660c9-c416-42b6-9142-19112a9ff811' THEN 1 ELSE 0 END) AS "includesMe"
FROM "CommentReaction"
WHERE "commentId" = 'c142787b-4422-4128-8357-58d36c177307'
GROUP BY reaction;
or:
SELECT COUNT(*) AS "numReacts",
reaction,
MAX(("userId" = 'b8b660c9-c416-42b6-9142-19112a9ff811')::int) AS "includesMe"
FROM "CommentReaction"
WHERE "commentId" = 'c142787b-4422-4128-8357-58d36c177307'
GROUP BY reaction;

the database interprets reaction as belonging to the alias i.
you need to use the original name (or in my case the alias c) to get te rigt column
SELECT
count(*) as "numReacts",
reaction,
case
when exists (
select *
from "CommentReaction" as i
where i."userId" = 'b8b660c9-c416-42b6-9142-19112a9ff811' and i."commentId" = 'c142787b-4422-4128-8357-58d36c177307' and i.reaction = c.reaction
) then 1
else 0
end as "includesMe"
FROM "CommentReaction" as c WHERE "commentId" = 'c142787b-4422-4128-8357-58d36c177307' GROUP BY reaction;

Related

At least one of the duplicate records has 'x'. Postgres sql EXISTS or INNER JOIN

I have the below postgres query that finds duplicate records in a database but I'm hoping to add in another condition so that I can say AT LEAST ONE of the duplicated records has the values of v.varfield_type_code = 's' AND v.field_content ~ 'Greendale student cards%' (from a table called sierra_view.varfield v ON p.record_id = v.record_id).
I tried an INNER JOIN and am looking into EXISTS. Does anyone have any insight? Thank you.
SELECT
p.birth_date_gmt, 'p' || rm2.record_num || 'a' AS "patron",
n.last_name || ' ' || n.first_name || ' ' || n.middle_name as name,
count(*) as cnt
FROM
sierra_view.patron_record p
JOIN sierra_view.patron_record_fullname n ON p.record_id =
n.patron_record_id
JOIN sierra_view.record_metadata rm2 on p.record_id = rm2.id
/* JOIN sierra_view.varfield v on p.record_id =v.record_id */
WHERE p.birth_date_gmt BETWEEN '01-01-2001' AND '12-31-2017'
GROUP BY 1,2, 3
HAVING COUNT(1) > 1
ORDER BY 2,1
You can put these conditions in the HAVING section:
JOIN sierra_view.varfield v on p.record_id = v.record_id
WHERE ...
GROUP BY ...
HAVING COUNT(*) > 1 AND COUNT(CASE WHEN v.varfield_type_code = 's' AND v.field_content ~ 'Greendale student cards%' THEN 1 END) > 0
So:
COUNT(*) > 1 = Only include duplicate records (you already do this)
COUNT(CASE WHEN v.varfield_type_code = 's' AND v.field_content ~ 'Greendale student cards%' THEN 1 END) > 0 = Count the grouped records based on those two conditions; if a record matches, it gets a 1, otherwise a NULL (implicit), and NULLs are not counted. So if at least one of the grouped records matches the criteria, the whole "group" will be included in the results; if not, they won't be included.
Also worth double checking whether ~ 'Greendale student cards%' is correct; ~ is for a regex check, while % is a wildcard symbol for LIKE, unless of course you do mean to search for a literal % character.

How to conditionally group into column without using FULL OUTER JOIN

I want to turn
TABLEA:
id type amount
A 'Customer' 100
A 'Parter' 10
A 'Customer' 200
A 'Parter' 20
B 'Parter' 555
I can hardcode the type, don't need to be dynamic, these types are enum
RESULT:
id customer_array customer_sum partner_array partner_sum
A [100, 200] 300 [10, 20] 30
B [] 0 [555] 555
Right now
I am using two aggregate function
WITH customer AS (
SELECT
table_A,
json_agg(row_to_json(amount)) AS customer_array,
sum(amount) AS customer_sum
FROM table_A WHERE type='Customer'
GROUP BY id
), partner AS (
SELECT
table_A,
json_agg(row_to_json(amount)) AS partner_array,
sum(amount) AS partner_sum
FROM table_A WHERE type='Partner'
GROUP BY id
) SELECT
id,
COALESCE(customer_array, '[]') AS customer_array,
COALESCE(customer_sum, 0) AS customer_sum,
COALESCE(partner_array, '[]') AS partner_array,
COALESCE(partner_sum, 0) AS partner_sum
FROM customer FULL OUTER JOIN partner USING (id)
I am wondering if there is a way to achieve what I want without querying twice?
This is a simple conditional aggregation as far as I can tell:
select id,
array_agg(amount) filter (where type = 'Customer') as customer_array,
sum(amount) filter (where type = 'Customer') as customer_sum,
array_agg(amount) filter (where type = 'Partner') as partner_array,
sum(amount) filter (where type = 'Partner') as partner_sum
from table_a
group by id;
If you want an empty array instead of a NULL value, wrap the aggregation functions into a coalesce():
select id,
coalesce((array_agg(amount) filter (where type = 'Customer')),'{}') as customer_array,
coalesce((sum(amount) filter (where type = 'Customer')),0) as customer_sum,
coalesce((array_agg(amount) filter (where type = 'Partner')),'{}') as partner_array,
coalesce((sum(amount) filter (where type = 'Partner')),0) as partner_sum
from table_a
group by id;
You can try using the case statement.
https://www.postgresql.org/docs/8.2/static/functions-conditional.html
I don't have a postgres server to try this. But overall the syntax should be as below.
SELECT
table_A,
case
when Type='Customer'
then json_agg(row_to_json(amount))
else []
end AS customer_array,
case
when Type='Customer'
sum(amount)
else 0
end
AS customer_sum,
case
when Type='Partner'
then json_agg(row_to_json(amount))
else []
end AS partner_array
case
when Type='Partner'
sum(amount)
else 0
end
From table_A
GROUP BY id

Using the result of a subquery in a CASE expression with T-SQL

I'm writing a query with some CASE expressions and it outputs helper-data columns which help me determine whether or not a specific action is required. I would like to know if I can somehow use the result of a subquery as the output without having to perform the same query twice (between WHEN (subquery) THEN and as the result after THEN)
The dummy code below describes what I'm after. Can this be done? I'm querying a MS2005 SQL database.
SELECT 'Hello StackOverflow'
,'Thanks for reading this question'
,CASE
WHEN
(
SELECT count(*)
FROM sometable
WHERE condition = 1
AND somethingelse = 'value'
) > 0 THEN
-- run the query again to get the number of rows
(
SELECT count(*)
FROM sometable
WHERE condition = 1
AND somethingelse = 'value'
)
ELSE 0
END
SELECT 'Hello StackOverflow'
,'Thanks for reading this question'
,CASE
WHEN
(
SELECT count(*)
FROM sometable
WHERE condition = 1
AND somethingelse = 'value'
) AS subqry_count > 0 THEN
-- use the subqry_count, which fails... "Incorrect syntax near the keyword 'AS'"
subqry_count
ELSE 0
END
Just use the subquery as the source you are selecting from:
SELECT 'Hello StackOverflow'
,'Thanks for reading this question'
,CASE subqry_count.Cnt
WHEN 0 THEN 0
ELSE subqry_count.Cnt
END
FROM ( SELECT count(*) AS Cnt
FROM sometable
WHERE condition = 1
AND somethingelse = 'value'
) subqry_count
As an aside, if you are just going to return 0 if the output from COUNT is 0, then you don't even need to use a CASE statement.

PostgreSQL: case when using alias column

I would like to do something like this:
select
case when (select count(*) as score from users t1 ) >5 THEN score else 0 end
When i try it i get error:
column score doesn't exists.
Can i do this in some other way? I need it to set a LIMIT value. I would like to do it of course in this way:
select
case when (select count(*) as score from users t1 ) >5 THEN (select count(*) as score from users) else 0 end
but than I need execute two times this same query.
Have someone some ideas?
You can use WITH clause:
with a as (select count(*) score from t)
select case when score > 5 then score else 0 end from a;
Or subquery (inline-view):
select case when score > 5 then score else 0 end
from (select count(*) score from t) t;

How to match records for two different groups?

I have one main table called Event_log which contains all of the records that I need for this query. Within this table there is one column that I'm calling "Grp". To simplify things, assume that there are only two possible values for this Grp: A and B. So now we have one table, Event_log, with one column "Grp" and one more column called "Actual Date". Lastly I want to add one more Flag column to this table, which works as follows.
First, I order all of the records in descending order by date as demonstrated below. Then, I want to flag each Group "A" row with a 1 or a 0. For all "A" rows, if the previous record (earlier in date) = "B" row then I want to flag 1. Otherwise flag a 0. So this initial table looks like this before setting this flag:
Actual Date Grp Flag
1-29-13 A
12-27-12 B
12-26-12 B
12-23-12 A
12-22-12 A
But after these calculations are done, it should look like this:
Actual Date Grp Flag
1-29-13 A 1
12-27-12 B NULL
12-26-12 B NULL
12-23-12 A 0
12-22-12 A 0
How can I do this? This is simpler to describe than it is to query!
You can use something like:
select el.ActualDate
, el.Grp
, Flag = case
when el.grp = 'B' then null
when prev.grp = 'B' then 1
else 0
end
from Event_log el
outer apply
(
select top 1 prev.grp
from Event_log prev
where el.ActualDate > prev.ActualDate
order by prev.ActualDate desc
) prev
order by el.ActualDate desc
SQL Fiddle with demo.
Try this
;with cte as
(
SELECT CAST('01-29-13' As DateTime) ActualDate,'A' Grp
UNION ALL SELECT '12-27-12','B'
UNION ALL SELECT '12-26-12','B'
UNION ALL SELECT '12-23-12','A'
UNION ALL SELECT '12-22-12','A'
)
, CTE2 as
(
SELECT *, ROW_NUMBER() OVER (order by actualdate desc) rn
FROM cte
)
SELECT a.*,
case
when A.Grp = 'A' THEN
CASE WHEN b.Grp = 'B' THEN 1 ELSE 0 END
ELSE NULL
END Flag
from cte2 a
LEFT OUTER JOIN CTE2 b on a.rn + 1 = b.rn