postgreSQL - how to filter by json_agg value - sql

I have join table between t_table and s_table.
there are many to many relationships between them.
s_table
id
s_value
1
1
2
2
3
3
t_table
id
t_value
1
100
2
200
3
300
t_id_s_id_table
s_id
t_id
1
1
1
2
2
2
2
3
3
1
3
3
First, I aggregated t_value group by s_table id by this query
SELECT
t_id_s_id_table.s_id,
JSON_AGG(t_value) AS json_agg
FROM
t_id_s_id_table
LEFT JOIN
t_table
ON
t_table.id = t_id_s_id_table.t_id
GROUP BY
t_id_s_id_table.s_id
And I got this result.
s_id
json_agg
1
100, 200
2
200, 300
3
100, 300
What I would like to do
I want to obtain all s_ids whose associated json_agg value includes 100.
(It means s_id = 1 and 3)
I tried the following query
SELECT *
FROM (
SELECT
t_id_s_id_table.s_id,
JSON_AGG(t_value) AS json_agg
FROM
t_id_s_id_table
LEFT JOIN
t_table
ON
t_table.id = t_id_s_id_table.t_id
GROUP BY
t_id_s_id_table.s_id
)
WHERE COUNT(json_agg = 100) > 0
but it doesn't work for me.
I got error operator does not exist: json = integer.
How can I make SQL in order to obtain get this result?
I am using PostgreSQL 11.2.
Thank you in advance.

Regardless of your query's business logic - as you need to count how many json_agg array elements are equal to 100, your where clause shall be
WHERE (
select count(*)
from json_array_elements_text(json_agg) jae
where jae::integer = 100
) > 0
or simpler, whether an array element equal to 100 exists
WHERE exists (
select from json_array_elements_text(json_agg) jae
where jae::integer = 100
)
And btw better do not use the name of a function (json_agg) as a column name.

The easy way would be to use HAVING instead of WHERE to act on each group, and use the BOOL_OR operator to compare if any item in the group is equal to 100;
SELECT
t_id_s_id_table.s_id,
JSON_AGG(t_value) AS json_agg
FROM
t_id_s_id_table
LEFT JOIN
t_table
ON
t_table.id = t_id_s_id_table.t_id
GROUP BY
t_id_s_id_table.s_id
HAVING BOOL_OR(t_value=100)
A DBfiddle to test with.

Related

How to check the possibility of groups union in a sequence order

I have a table with the following columns:
ID_group, ID_elements
For example with the following records:
1, 1
1, 2
2, 2
2, 4
2, 5
2, 6
3, 7
And I have sets of the elements, for example: 1,2,5; 1,5,2; 1,2,4; 2,7;
I need to check (true or false) that exist a common group for the pairs of adjacent elements.
For example elements:
1,2,5 -> true [i.e. elements 1,2 has common group 1 and elements 2,5 has common group 2]
1,5,2 -> false [i.e. 1,5 do not have a common group unlike 5,2 (but the result is false due to 1,5 - false)]
1,2,4 -> true
2,7 -> false
First, we need a list of pairs. We can get this by taking your set as an array, turning each element into a row with unnest and then making pairs by matching each row with its previous row using lag.
with nums as (
select *
from unnest(array[1,2,5]) i
)
select lag(i) over() a, i b
from nums
offset 1;
a | b
---+---
1 | 2
2 | 5
(2 rows)
Then we join each pair with each matching row. To avoid counting duplicate data rows twice, we count only the distinct rows.
with nums as (
select *
from unnest(array[1,2,5]) i
), pairs as (
select lag(i) over() a, i b
from nums
offset 1
)
select
count(distinct(id_group,id_elements)) = (select count(*) from pairs)
from pairs
join foo on foo.id_group = a and foo.id_elements = b;
This works on any size array.
dbfiddle
Your query to check if elements in a set evaluate to true or not can be done via procedures/function. Set representation can be taken as a string and then splitting it to substring then returning the required result can use a record for multiple entries. For sql query, below is a sample that can be used as a workaround, you can try changing the below query based on your requirement.
select case when ( Select count(*)
from ( SELECT
id_group, count(distinct id_elements)
from table where
id_group
in (1,2,5)
group by ID_group having
id_elements
in (1,2,5)) =3 ) then "true" else "false"
end) from table;
#Schwern, thank you, it helped. But I have changed the condition join foo on foo.id_group = a, because as I understand, a is element's ID, not group's. I have changed the following section:
join foo fA on fA.id_elements = a
join foo fB on fB.id_elements = b and fA.group_id = fB.group_id;

SQL Complex Counting

I want to get all the possible type from a table and then count the rows under a group who has this type.
To better illustrate consider the following table.
Object (o)
id name group_id type
-----------------------------------------------------------
1 Computer 100 A
2 Monitor 100 A
3 Chair 100 B
4 Table 100 B
5 Telephone 100 C
6 Notebook 200 D
7 Pen 200 D
8 Wire 100 E
What I want to get is the following result:
group_id type total
-------------------------------
100 A 2
100 B 2
100 C 1
100 D 0
100 E 1
The group_id doesn't have to be included in the result, I just wanted to let you see here that I'm using a WHERE clause in my query. Basically I only want the results for group | 100 but I want the counts for all type.
Using my query below
SELECT o.group_id, o.type, COUNT(*) AS total
FROM object o
WHERE o.group_id = 100
GROUP BY o.type
I only get the following result:
group_id type total
-------------------------------
100 A 2
100 B 2
100 C 1
100 E 1
You will notice that I am missing the row for type | D. I also want to include that.
You can use the following query:
SELECT 100 AS group_id,
o.type,
COUNT(CASE WHEN o.group_id = 100 THEN 1 END) AS total
FROM object o
GROUP BY o.type
This query groups by type and uses conditional aggregation so as to count the rows under each group who have type = 100.
The problem is that Type D is not returned in the query for that ID, so it is not included in the groupings. To do this, we need to get the list of ALL Types in the table, then do the counts for your ID in the table. Something like this:
SELECT o_list.type, COUNT(o.id) AS total
FROM object o
RIGHT OUTER JOIN ( SELECT DISTINCT type from object ) o_list on o_list.type = o.type
WHERE o.group_id = 100
GROUP BY o_list.type
Giorgos's version is, arguably, cleaner to read and will always run in one full scan of the table. Mine is more complex, but if there are indexes on group_id and type, will run on index scans and so may be significantly faster on a large data set.
And you get to see that there are always options on how to solve a problem. :)
Since Type isn't in group 100 I first get a distinct set of types, then join back to objects since you want 100 for type D, I always show 100 when the type doesn't exist.
SELECT coalesce(O.Group_ID,100) as Group_ID, T.Type, count(O.Group_ID) as Total
FROM (Select Distinct Type from object) T
LEFT JOIN Object O
on T.Type = O.Type
GROUP BY coalesce(O.Group_ID,100), T.Type

How can I create this conditional grouped field on SQL Server 2008?

Sorry for this question, but i cannot resolve this simple query.
I have this table:
ID_Type Item
-----------------
A 1
P 2
P 3
A 4
P 5
A 6
I need to calculate a "group" incremental counter based on ID_Type Field where This field has an "A" Value. This is the expected result:
ID_Type Item Counter
-----------------------------
A 1 1
P 2 1
P 3 1
A 4 2
P 5 2
A 6 3
So every time a record with ID_Type='A' appear, I need to increment the counter. Any help will be apreciated.
In SQL Server 2012+, you can use a cumulative sum:
select t.*,
sum(case when id_type = 'A' then 1 else 0 end) over (order by item) as counter
from t;
This will be much more efficient than a correlated subquery approach, particularly on larger data sets.
One way is a subquery:
SELECT ID_Type, Item, (
SELECT COUNT(*) FROM MyTable t2
WHERE t2.Item <= t1.Item
AND t2.ID_Type='A'
) AS Counter
FROM MyTable t1
ORDER BY Item ASC
This will work on any version of SQL Server.

DB2 SQL filter query result by evaluating an ID which has two types of entries

After many attempts I have failed at this and hoping someone can help. The query returns every entry a user makes when items are made in the factory against and order number. For example
Order Number Entry type Quantity
3000 1 1000
3000 1 500
3000 2 300
3000 2 100
4000 2 1000
5000 1 1000
What I want to the query do is to return filter the results like this
If the order number has an entry type 1 and 2 return the row which is type 1 only
otherwise just return row whatever the type is for that order number.
So the above would end up:
Order Number Entry type Quantity
3000 1 1000
3000 1 500
4000 2 1000
5000 1 1000
Currently my query (DB2, in very basic terms looks like this ) and was correct until a change request came through!
Select * from bookings where type=1 or type=2
thanks!
select * from bookings
left outer join (
select order_number,
max(case when type=1 then 1 else 0 end) +
max(case when type=2 then 1 else 0 end) as type_1_and_2
from bookings
group by order_number
) has_1_and_2 on
type_1_and_2 = 2
has_1_and_2.order_number = bookings.order_number
where
bookings.type = 1 or
has_1_and_2.order_number is null
Find all the orders that have both type 1 and type 2, and then join it.
If the row matched the join, only return it if it is type 1
If the row did not match the join (has_type_2.order_number is null) return it no matter what the type is.
A "common table expression" [CTE] can often simplify your logic. You can think of it as a way to break a complex problem into conceptual steps. In the example below, you can think of g as the name of the result set of the CTE, which will then be joined to
WITH g as
( SELECT order_number, min(type) as low_type
FROM bookings
GROUP BY order_number
)
SELECT b.*
FROM g
JOIN bookings b ON g.order_number = b.order_number
AND g.low_type = b.type
The JOIN ON conditions will work so that if both types are present then low_type will be 1, and only that type of record will be chosen. If there is only one type it will be identical to low_type.
This should work fine as long as 1 and 2 are the only types allowed in the bookings table. If not then you can simply add a WHERE clause in the CTE and in the outer SELECT.

Select values in SQL that do not have other corresponding values except those that i search for

I have a table in my database:
Name | Element
1 2
1 3
4 2
4 3
4 5
I need to make a query that for a number of arguments will select the value of Name that has on the right side these and only these values.
E.g.:
arguments are 2 and 3, the query should return only 1 and not 4 (because 4 also has 5). For arguments 2,3,5 it should return 4.
My query looks like this:
SELECT name FROM aggregations WHERE (element=2 and name in (select name from aggregations where element=3))
What do i have to add to this query to make it not return 4?
A simple way to do it:
SELECT name
FROM aggregations
WHERE element IN (2,3)
GROUP BY name
HAVING COUNT(element) = 2
If you want to add more, you'll need to change both the IN (2,3) part and the HAVING part:
SELECT name
FROM aggregations
WHERE element IN (2,3,5)
GROUP BY name
HAVING COUNT(element) = 3
A more robust way would be to check for everything that isn't not in your set:
SELECT name
FROM aggregations
WHERE NOT EXISTS (
SELECT DISTINCT a.element
FROM aggregations a
WHERE a.element NOT IN (2,3,5)
AND a.name = aggregations.name
)
GROUP BY name
HAVING COUNT(element) = 3
It's not very efficient, though.
Create a temporary table, fill it with your values and query like this:
SELECT name
FROM (
SELECT DISTINCT name
FROM aggregations
) n
WHERE NOT EXISTS
(
SELECT 1
FROM (
SELECT element
FROM aggregations aii
WHERE aii.name = n.name
) ai
FULL OUTER JOIN
temptable tt
ON tt.element = ai.element
WHERE ai.element IS NULL OR tt.element IS NULL
)
This is more efficient than using COUNT(*), since it will stop checking a name as soon as it finds the first row that doesn't have a match (either in aggregations or in temptable)
This isn't tested, but usually I would do this with a query in my where clause for a small amount of data. Note that this is not efficient for large record counts.
SELECT ag1.Name FROM aggregations ag1
WHERE ag1.Element IN (2,3)
AND 0 = (select COUNT(ag2.Name)
FROM aggregatsions ag2
WHERE ag1.Name = ag2.Name
AND ag2.Element NOT IN (2,3)
)
GROUP BY ag1.name;
This says "Give me all of the names that have the elements I want, but have no records with elements I don't want"