Select JSON value from a EAV structure result set - postgresql

Given a result set which is in the EAV structure such as :
id | attributeName | stringValue | intValue | BooleanValue
---------------------------------------------------------------
1 stringFoo v1
1 stringFooList v2
1 stringFooList v3
1 intFoo 10
1 intFooList 10
1 intFooList 20
1 booleanFoo true
1 booleanFooList true
1 booleanFooList true
How can I select all the attributes and value pair as a single value in a JSON/JSONB format , which are something likes:
{
"stringFoo" : "v1" ,
"stringFooList" : ["v2","v3"] ,
"intFoo" : 10 ,
"intFooList" : [10,20],
"booleanFoo" : true,
"booleanFooList" : [true,true]
}
If there are multiple attribute value for an attribute such as stringFooList , it will format it as JSON array.
I am using PostgreSQL 9.6

You can do something like this:
select id, jsonb_object_agg(att, value)
from (
select id,
attributename as att,
case
when count(*) > 1 then
jsonb_agg(coalesce(stringvalue,intvalue::text,booleanvalue::text))
else
to_jsonb(min(coalesce(stringvalue,intvalue::text,booleanvalue::text)))
end as value
from eav
group by id, attributename
) t
group by id;
The inner select aggregates multiple values into an JSON array, single values into JSON scalar values. And the outer query then builds a single JSON value of all rows.
Online example: https://rextester.com/TLCRN79815

#a_horse_with_no_name 's answer gives me a good start. I extend his/her answer and come up the following query such that the elements in the JSON array have the same data type of what defined in PostgreSQL.
select id, jsonb_object_agg(att,
case
when strval is not null then strval
when intvalue is not null then intvalue
else boolVal
end
)
from (
select id,
attributename as att,
case when count(*) > 1 then
jsonb_agg(stringvalue) filter (where stringvalue is not null)
else
to_jsonb(min(stringvalue) filter (where stringvalue is not null))
end as strVal,
case when count(*) > 1 then
jsonb_agg(intvalue) filter (where intvalue is not null)
else
to_jsonb(min(intvalue) filter (where intvalue is not null))
end as intvalue,
case when count(*) > 1 then
jsonb_agg(booleanvalue) filter (where booleanvalue is not null)
else
to_jsonb(bool_and(booleanvalue) filter (where booleanvalue is not null))
end as boolVal
from eav
group by id, attributename
) t
group by id;

Related

Postgres query how to club date and time if time is not null

with data as(SELECT c."id",c."accountId",c."name",c."campaignType",c."status",
(CASE WHEN cb."executionDetails"->>'initiatedAt' IS NULL THEN csr."startDate"
ELSE cast(cb."executionDetails"->>'initiatedAt' as TIMESTAMP)
END) as "startDate",
CASE WHEN cb."executionDetails"->>'initiatedAt' IS NOT NULL THEN NULL
ELSE csr."timeSlot"->>'type' END as "timeSlotType",
(CASE WHEN cb."executionDetails"->>'initiatedAt' IS not NULL THEN Null ELSE
-- CASE WHEN csr."timeSlotType"->>'startTime' IS NULL THEN NULL
CASE WHEN csr."timeSlot"->>'type'='MORNING' THEN '07:00'
WHEN csr."timeSlot"->>'type'='AFTERNOON' THEN '12:00'
WHEN csr."timeSlot"->>'type'='EVENING' THEN '17:00'
WHEN csr."timeSlot"->>'type'='CUSTOM' THEN (csr."timeSlot"->>'startTime')::json->>'hour'||':'||((csr."timeSlot"->>'startTime')::json->>'minute')
ELSE csr."timeSlot"->>'startTime' END END )::TIME as "startTime",
split_part(cb."batchRunId", '-',6)::decimal as batchNumber,
'CAMPAIGN' as type
FROM "Campaigns" c
LEFT JOIN "CampaignScheduleRequests" csr
ON c."id"=csr."campaignId"
LEFT JOIN "CampaignBatches" cb
ON csr."id"=cb."requestId")
SELECT * FROM data as d
WHERE d."status" IN ('ACTIVATED')
OUTPUT of the above query
Required o/p
Start time column should be concatenation of start date and startTime
with data as(
SELECT c."id",
c."accountId",
c."name",
c."campaignType",
c."status",
coalesce((cb."executionDetails"->>'initiatedAt')::timestamp,
csr."startDate")
) as "startDate",
CASE WHEN cb."executionDetails" ? 'initiatedAt' THEN NULL
ELSE csr."timeSlot"->>'type'
END as "timeSlotType",
(CASE WHEN cb."executionDetails" ? 'initiatedAt' THEN NULL
ELSE CASE csr."timeSlot"->>'type'
WHEN 'MORNING' THEN '07:00'
WHEN 'AFTERNOON' THEN '12:00'
WHEN 'EVENING' THEN '17:00'
WHEN 'CUSTOM' THEN (csr."timeSlot"->'startTime')->>'hour'
||':'
||(csr."timeSlot"->'startTime')->>'minute'
ELSE csr."timeSlot"->>'startTime' --invalid format could cause problems with ::time
END
END )::TIME as "startTime",
split_part(cb."batchRunId", '-',6)::decimal as batchNumber,
'CAMPAIGN' as type
FROM "Campaigns" c
LEFT JOIN "CampaignScheduleRequests" csr ON c."id"=csr."campaignId"
LEFT JOIN "CampaignBatches" cb ON csr."id"=cb."requestId"
WHERE c."status" IN ('ACTIVATED')
)
SELECT *,
"startDate"+coalesce("startTime",'00:00'::time) as "newStartTimestamp"
FROM data;
Use coalesce() to shorten the null replacements:
CASE WHEN cb."executionDetails"->>'initiatedAt' IS NULL
THEN csr."startDate"
ELSE cast(cb."executionDetails"->>'initiatedAt' as TIMESTAMP)
END
is the same as
coalesce((cb."executionDetails"->>'initiatedAt')::timestamp, csr."startDate")
In CASE you can do a single expression evaluation:
CASE expression
WHEN value1 THEN...
WHEN value2 THEN...
instead of a series of checks
CASE
WHEN expression=value1 THEN...
WHEN expression=value2 THEN...
Instead of casting back to json after using the ->> operator that gives you text: (jsonb->>'key1')::json->>'key2', you can just use -> to keep json output the first time.
? operator lets you check the presence of a key json?'key1' without having to check for null in an attempted read json->>'key1' is null.
You can add time to date or timestamp directly, the same how you'd add an interval. And to avoid nullifying your intitiatedAt-based startDate when adding a null-valued startTime, you can use coalesce() again - which I think was your main question.

Determine Group By from Parameter -

How is it that this is valid:
ALTER PROCEDURE [StoredProcedure]
#abcID int = null -- optional param
SELECT columnJ, columnK, Count(eID) AS Num, Sum(OutXYZ) as TotalProdXYZ, Sum(RawXYZ) as TotalRawXYZ
FROM [v_ViewTable]
WHERE (#abcID IS NULL OR (abcID = #abcID))
GROUP BY columnJ, columnK
But then this is throwing a "columnJ is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause."
ALTER PROCEDURE [StoredProcedure]
#abcID int = null -- optional param
-- if 0: Group by columnJ, columnK
-- else: Group by columnK, columnJ
, #Grouping int = null
SELECT columnJ, columnK, Count(eID) AS Num, Sum(OutXYZ) as TotalProdXYZ, Sum(RawXYZ) as TotalRawXYZ
FROM [v_ViewTable]
WHERE (#abcID IS NULL OR (abcID = #abcID))
GROUP BY
CASE WHEN #Grouping = 0 THEN columnJ ELSE columnK END
,CASE WHEN #Grouping = 0 THEN columnK ELSE columnJ END
What's wrong with my CASE statement? Basically, if #Grouping = 0, I want the group by to be J, K if anything else, it should be K, J
Thanks in advance for any help!
As the message says columnJ and columnK are not contained in either an aggregate function or the GROUP BY clause in the second case. You should use field or the exact expression in select list AS IS in the GROUP BY section.
So the following statement will be ok:
SELECT CASE WHEN #Grouping = 0 THEN columnJ ELSE columnK END,
CASE WHEN #Grouping = 0 THEN columnK ELSE columnJ END,
Count(eID) AS Num, Sum(OutXYZ) as TotalProdXYZ, Sum(RawXYZ) as TotalRawXYZ
FROM [v_ViewTable]
WHERE (#abcID IS NULL OR (abcID = #abcID))
GROUP BY
CASE WHEN #Grouping = 0 THEN columnJ ELSE columnK END
,CASE WHEN #Grouping = 0 THEN columnK ELSE columnJ END

Counting Null values in JPQL

i want to count null values in JPQL but count (case when "column" is null then.. end) doesn't work it work only on MYSQL ,i don't want to use count(*) what is the solution ??
String jpql ="select c.commande.user.login ,count(CASE WHEN c.commande.commandeTms is Null THEN 1 else 0 END) AS count1 from Designation c GROUP BY c.commande.user.login";
here my database
Here you go : I have used CriteriaBuilder to count them ... as a result, you get a List of Tuple containing the user login and count elements ... you may want to change names and "count cases" according to your needs :
public List<Tuple> test() {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> cq = cb.createQuery(Tuple.class);
Root<Designation> designation = cq.from(Designation.class);
Join<Designation, Commande> commande = designation.join("commande");
Join<Commande, User> user = commande.join("user");
Expression expr = commande.get("commandeTms");
cq.multiselect(user.get("login").alias("login"),
cb.sum(cb.<Long>selectCase().when(expr.isNotNull(), 1L).otherwise(0L)).alias("NotNull"),
cb.sum(cb.<Long>selectCase().when(expr.isNull(), 1L).otherwise(0L)).alias("Null"));
cq.groupBy(user.get("login"));
Query query = entityManager.createQuery(cq);
return query.getResultList();
}
You can use SUM aggregate function for this case
String jpql ="SELECT c.commande.user.login, SUM(CASE WHEN c.commande.commandeTms IS NULL THEN 1 ELSE 0 END) AS count1 FROM Designation c GROUP BY c.commande.user.login";

sql: case when (with in param value)

could you please help?
SELECT
(some columns),
SortOrder = CASE WHEN City = #inParamCity THEN 0 ELSE 1 END
FROM
dbo.addressBook
ORDER BY
SortOrder
I tried this and got:
Incorrect syntax near '=' ' –
SELECT
(some columns)
FROM
dbo.addressBook
ORDER BY
CASE
WHEN City = #inParamCity THEN 0
ELSE 1
END
Shouldn't it be like this?
SELECT (some columns),
CASE City WHEN #inParamCity THEN 0 ELSE 1 END As SortOrder
FROM dbo.addressBook
ORDER BY SortOrder

How to conditionally filter on a column in a WHERE clause?

OK, the umpteenth conditional column question:
I'm writing a stored proc that takes an input parameter that's mapped to one of several flag columns. What's the best way to filter on the requested column? I'm currently on SQL2000, but about to move to SQL2008, so I'll take a contemporary solution if one's available.
The table queried in the sproc looks like
ID ... fooFlag barFlag bazFlag quuxFlag
-- ------- ------- ------- --------
01 1 0 0 1
02 0 1 0 0
03 0 0 1 1
04 1 0 0 0
and I want to do something like
select ID, name, description, ...
from myTable
where (colname like #flag + 'Flag') = 1
so if I call the sproc like exec uspMyProc #flag = 'foo' I'd get back rows 1 and 4.
I know I can't do the part in parens directly in SQL. In order to do dynamic SQL, I'll have to stuff the entire query into a string, concatenate the #flag param in the WHERE clause and then exec the string. Aside from the dirty feeling I get when doing dynamic SQL, my query is fairly large (I'm selecting a couple dozen fields, joining 5 tables, calling a couple of functions), so it's a big giant string all because of a single line in a 3-line WHERE filter.
Alternately, I could have 4 copies of the query and select among them in a CASE statement. This leaves the SQL code directly executable (and subject to syntax hilighting, etc.) but at the cost of repeating big chunks of code, since I can't use the CASE on just the WHERE clause.
Are there any other options? Any tricky joins or logical operations that can be applied? Or should I just get over it and exec the dynamic SQL?
There are a few ways to do this:
You can do this with a case statement.
select ID, name, description, ...
from myTable
where CASE
WHEN #flag = 'foo' then fooFlag
WHEN #flag = 'bar' then barFlag
END = 1
You can use IF.
IF (#flag = 'foo') BEGIN
select ID, name, description, ...
from myTable
where fooFlag = 1
END ELSE IF (#flag = 'bar') BEGIN
select ID, name, description, ...
from myTable
where barFlag = 1
END
....
You can have a complicated where clause with a lot of parentheses.
select ID, name, description, ...
from myTable
where (#flag = 'foo' and fooFlag = 1)
OR (#flag = 'bar' and barFlag = 1) OR ...
You can do this with dynamic sql:
DECLARE #SQL nvarchar(4000)
SELECT #SQL = N'select ID, name, description, ...
from myTable
where (colname like ''' + #flag + 'Flag'') = 1'
EXECUTE sp_ExecuteSQL #SQL, N''
There are more, but I think one of these will get you going.
"Alternately, I could have 4 copies of the query and select among them in a CASE statement."
You don't need to copy your entire query 4 times, just add all the possibilities into the where clauses in your single copy of the query:
select ID, name, description, ...
from myTable
where (#flag = 'foo' and fooFlag = 1) OR (#flag = 'bar' and barFlag = 1) OR ...
What I would do is CASE some variables at the beginning. Example:
DECLARE
#fooFlag int,
#barFlag int,
#bazFlag int,
#quuxFlag int
SET #fooFlag = CASE WHEN #flag = 'foo' THEN 1 ELSE NULL END
SET #barFlag = CASE WHEN #flag = 'bar' THEN 1 ELSE NULL END
SET #bazFlag = CASE WHEN #flag = 'baz' THEN 1 ELSE NULL END
SET #quuxFlag = CASE WHEN #flag = 'quux' THEN 1 ELSE NULL END
SELECT ID, name, description, ...
FROM myTable
WHERE (fooFlag >= ISNULL(#fooFlag, 0) AND fooFlag <= ISNULL(#fooFlag, 1))
AND (barFlag >= ISNULL(#barFlag, 0) AND barFlag <= ISNULL(#barFlag, 1))
AND (bazFlag >= ISNULL(#bazFlag, 0) AND bazFlag <= ISNULL(#bazFlag, 1))
AND (quuxFlag >= ISNULL(#quuxFlag, 0) AND quuxFlag <= ISNULL(#quuxFlag, 1))
The good thing about this query is that, because the possible values for "flags" are bounded, you can calculate all your conditionals as prerequisites instead of wrapping columns in them. This guarantees a high-performance index seek on whichever columns are indexed, and doesn't require writing any dynamic SQL. And it's better than writing 4 separate queries for obvious reasons.
You could have a parameter for each possible flag column, then check if the parameter is null or the value in the column is equal to the parameter. Then you pass in a 1 for the flags that you want to check and leave the others null.
select id, name, description, ...
from myTable
where (#fooFlag is null or fooFlag = #fooFlag) AND
(#barFlag is null or barFlag = #barFlag) AND
...
Honestly, though, this seems like an ideal candidate for building a dynamic LINQ query and skipping the SPROC once you get to SQL2008.
int should be accepted as varchar value
declare #CompanyID as varchar(10) = '' -- or anyother value
select * from EmployeeChatTbl chat
where chat.ConversationDetails like '%'+#searchKey+'%'
and
(
(0 = CASE WHEN (#CompanyID = '' ) THEN 0 ELSE 1 END)
or
(chat.CompanyID = #CompanyID)
)
working
when the companyID is present , then filtration based on it is done, other wise , filtration is skipped.
where
case when #value<>0 then Field else 1 end
=
case when #value<>0 then #value else 1 end