sql check if join table has rows by mutliple criterias - sql

I have two tables, assets and asset_params.
assets
|asset_id| some_asset_data |
-----------------------------
| 1 | 'some data' |
| 2 | 'some other data'|
asset_params
|asset_param_id|asset_id|param_name|param_value|
------------------------------------------------
| 1 | 1 | 'Memory' | '4096' |
| 2 | 1 | 'OS' | 'Windows' |
| 3 | 2 | 'Memory' | '4096' |
| 4 | 2 | 'OS' | 'Linux' |
Now, how can i find assets which have parameters where they have a param called 'Memory' with value '4096' AND a param 'OS' with value 'Windows'.
Desired result is that i find one asset row, with id 1, in the example case.
I cant find a reasonable solution. Any help is appreciated.

select * from assets a where
exists (select 1 from asset_params p
where name = "OS" and value = "Win" and a.asset_id=p.asset_id)
and exists (select 1 from asset_params p
where name = "memory" and value = "4096" and a.asset_id=p.asset_id)

You can do this with aggregation and a having clause:
select asset_id
from asset_params
where param_name = 'Memory' and param_value = '3096' or
param_name = 'OS' and param_value = 'Windows'
group by asset_id
having count(*) = 2;
Note: if you can have multiple parameters with the same name, you should use count(distinct param_name) = 2.
This easily generalized. A more Postgres'ish way of writing it is:
select asset_id
from asset_params
where (param_name, param_value) in (('Memory', '3096'), ('OS', 'Windows'))
group by asset_id
having count(*) = 2;

Something like this should work:
select * from assets where
assets_id in (select assets_id from asset_params
where name = "OS" and value = "Win")
and
assets_id in (select assets_id from asset_params
where name = "memory" and value = "4096")
If your tables are huge, this needs proper indexes. But you should always have good indexes anyway. ;)

Related

How can I get join results as JSON in a PostgreSQL query?

I have the query below that almost works: It returns 3 rows one of which should have first_nation populated (other two should be NULL). But they all get the same data for first_nation. What I need is the person.id from the outer where to be a part of the WHERE in the inner query but I don't think that's doable. Any help would be appreciated.
Or another way, I'd like the results of the JOIN to be JSON rather than appearing as additional columns.
SELECT person.id,
(
SELECT row_to_json(x)
FROM (
SELECT ref_first_nations_gov.id
FROM ref_first_nations_gov JOIN person ON person.first_nation_id = ref_first_nations_gov.id
WHERE person.application_id = 1 AND person.archived = false
) x
) AS first_nation
FROM person
WHERE application_id = 1 AND archived = false;
EDIT: Sample Data
SELECT id, application_id, first_nation_id FROM person WHERE application_id = 1;
id | application_id | first_nation_id
----+----------------+-----------------
4 | 1 |
1 | 1 |
2 | 1 |
3 | 1 | 1
What the query above gives me:
id | first_nation
----+--------------
4 | {"id":1}
1 | {"id":1}
3 | {"id":1}
What I want
id | first_nation
----+--------------
4 |
1 |
3 | {"id":1}
Even though I don't have how to test this right now, I don't think you need a subquery.
Try something like this.
SELECT p.id, row_to_json(r.id) FROM person p
FULL OUTER JOIN ref_first_nations_gov r on r.id = p.first_nation_id
WHERE p.application_id = 1 AND p.archived = false;

PosgtreSQL join tables on json field

Let's say we have 2 tables-
Employee:
id integer (pk)
name char
code char
Status:
id integer (pk)
key char
data jsnob
Now here is the sample data of above tables:
Employee
+----+--------+------+
| id | name | code |
+----+--------+------+
| 1 | Brian | BR1 |
| 2 | Andrew | AN1 |
| 3 | Anil | AN2 |
| 4 | Kethi | KE1 |
| 5 | Smith | SM1 |
+----+--------+------+
Status
+----+---------+---------------------------------------+
| id | key | data |
+----+---------+---------------------------------------+
| 1 | Admin | {'BR1':true, 'AN1':true,'KE1':false} |
| 2 | Staff | {'SM1':true, 'AN2':true,'KE1':false} |
| 3 | Member | {'AN2':false, 'AN1':true,'KE1':false} |
| 4 | Parking | {'BR1':true, 'AN1':true,'KE1':false, |
| | | 'AN2':true,'SM1':true} |
| 5 | System | {'AN2':false, 'AN1':true,'KE1':true} |
| 6 | Ticket | {'AN2':false, 'AN1':true,'KE1':false} |
+----+---------+---------------------------------------+
Now my goal is to get status and name of failure keys, employee code wise. For ex:-
I am not an expert in sql complex queries, so any help is much appreciate.
Note: Above are just sample tables (name and data changed), but design is similar to original tables.
you can use the function jsonb_each_text yo get key and value from jsonb type, and if we mix with sql and ... voilĂ , the following query is a example for you case :
select employee.code,
case
when dat2.count is null then 'TRUE'
else
'FALSE'
end as status,
case
when dat2.count is null then 0
else
dat2.count
end as failures, string_agg as key from employee left join
(
select key, count(*), string_agg(code,',') from (
select key code , (jsonb_each_text(data)).key,(jsonb_each_text(data)).value
from status) as dat
where value='false'
group by 1 ) dat2 on (employee.code=dat2.key)
As far as I can tell, you don't need the employee table for this (because you only want the code column which is also present in the JSON values of the status table). It's enough to unnest the JSON value and then aggregate on the code from that.
select d.code,
bool_and(d.flag::boolean) as status_flag,
count(*) filter (where not d.flag::boolean) as failures,
coalesce(string_agg(key, ', ') filter (where not d.flag::boolean), 'N/A') as keys
from status st
join lateral jsonb_each_text(st.data) as d(code, flag) on true
group by d.code
order by d.code;
The filter() option is used to only include rows in the aggregate that comply with the where condition. In this case those where the value for the code is false.
bool_and is an aggregate function for boolean values that returns true if all input values are true (and false otherwise)
Online example: https://rextester.com/PEKCZ52605
Use a left-join query between those tables, and apply jsonb_each_text() function for jsonb type column.
The trick is to use conditionals as case when (js).value = 'false' then .. else .. end for the aggregated columns :
select e.id, e.code,
min(case when (js).value = 'false' then 'FALSE' else 'TRUE' end ) as status,
count(case when (js).value = 'false' then 1 end) as failures,
coalesce(
string_agg(case when (js).value = 'false' then s.key end, ',' ORDER BY s.id),'NA'
) as key
from Employee e
left join
(
select *, jsonb_each_text(data) as js
from Status
) s on e.code = (js).key
group by e.id, e.code
order by e.id;
where (js).value is extracted from jsonb type Status.data column
Demo

SQL Query on 3 Tables Get Column from One and Use it for the Other

I have a table, commission, that is being called from a function. It is selected by user_id and it will have certain columns named sale_product_id. This column is tied to another table: sale_product, which has another column, sale_id. Now there is one last table, sale that has ids.
Currently, here is what I have:
SELECT
c.amount
FROM
commission AS c,
sale_product AS sp,
sale AS s
WHERE
c.user_id = '[id]' AND
c.sale_product_id = sp.id AND
s.is_paid = 1 AND
(s.upline_user_id = c.user_id OR sp.sale_id = s.id)
I should have returning 6 rows, but there are 21. What am I doing wrong?
OUTPUT expectation (if I do SELECT c.* ...):
id | sale_product | user_id | amount |
# | 466 | 3 | 3.99 |
# | 123 | 3 | 2.99 |
What I am getting are duplicates (I think because the other tables see sale product id and do something funky)
id | sale_product | user_id | amount |
# | 466 | 3 | 3.99 |
# | 123 | 3 | 2.99 |
# | 466 | 3 | 3.99 |
# | 123 | 3 | 2.99 |
# | 466 | 3 | 3.99 |
# | 123 | 3 | 2.99 |
I have made some changes to the SQL and I'm almost done.
Here is what I have:
SELECT
DISTINCT c.id, c . * , s . *
FROM
commission AS c, sale_product AS sp, sale AS s
WHERE
sp.id = c.sale_product_id
AND s.id = sp.sale_id
AND s.is_paid = '1'
AND sp.cron_executed = '1'
AND s.upline_user_id = c.user_id
AND c.user_id = '3'
When I do this query, particularly the s.upline_user_id = c.user_id line, it gives me EXACTLY 1 query. This is correct. When I remove that line, I get all 6 rows. This is also correct. However, when I try to do this:
SELECT
DISTINCT c.id, c . * , s . *
FROM
commission AS c, sale_product AS sp, sale AS s
WHERE
sp.id = c.sale_product_id
AND s.id = sp.sale_id
AND s.is_paid = '1'
AND sp.cron_executed = '1'
AND s.upline_user_id <> c.user_id
AND c.user_id = '3'
I get a null result. How come? Even if I change the line to s.upline_user_id <> '3', (which btw should give me 5 rows), gives me null.
Anyone?
Your OR operator on the join is likely causing the duplicate data because both are being met. If you are using the OR as a fallback when the condition is not met, think about using a CASE statement instead, if possible. Test the existence of the most likely condition first, ELSE is your fallback.
AND CASE
WHEN (a IS NOT NULL AND b IS NOT NULL)
THEN (a = b)
ELSE (c = d)
END
If you are testing strings, test for NULLs and lengths of zero.
Hope it helps!

SQL SELECT multiple keys/values

I've got a table PERSON_PROPERTIES that resembles the following :
| ID | KEY | VALUE | PERSON_ID |
| 1 | fname | robert | 1 |
| 2 | lname | redford | 1 |
| 3 | fname | robert | 2 |
| 4 | lname | de niro | 2 |
| 5 | fname | shawn | 3 |
| 6 | nname | redford | 3 |
I would like to SELECT (in JPQL or in PSQL) the PERSON_ID that matches the given fname and lname.
I've tried
`SELECT DISTINCT *
FROM PERSON_PROPERTIES t0
WHERE ((((t0.key = 'fname')
AND (t0.value = 'robert'))
AND ((t0.key = 'lname')
AND (t0.value = 'redford'))))`
but it returns me no value.
I've also tried
`SELECT DISTINCT *
FROM PERSON_PROPERTIES t0
WHERE ((((t0.key = 'fname')
AND (t0.value = 'robert'))
OR ((t0.key = 'lname')
AND (t0.value = 'redford'))))`
but this way it returns me all values. I don't know how to turn the query properly for it to give me only value 1.
SELECT PERSON_ID
FROM PERSON_PROPERTIES
group by PERSON_ID
having sum(case when key = 'fname' and value = 'robert' then 1 else 0 end) > 0
and sum(case when key = 'lname' and value = 'redford' then 1 else 0 end) > 0
Groupy by the person and select only those having both values.
Another approach would be with subselect (caution, it's MS SQL 2012)
SELECT PERSON_ID
FROM PERSON_PROPERTIES
WHERE [Key] = 'fname' AND value = 'robert'
AND PERSON_ID in
(SELECT PERSON_ID FROM PERSON_PROPERTIES WHERE [Key] = 'lname' AND value = 'redford')
Fiddle Demo
Along with some colleagues we came to this answer :
SELECT p.PERSON_ID
FROM PERSON_PROPERTIES p
WHERE (p.key = 'fname' AND p.value = 'robert')
OR (p.key = 'lname' AND p.value = 'redford')
GROUP BY p.PERSON_ID
HAVING count(*) = 2
What do you think about it?
SELF JOIN also does the trick. DISTINCT for duplicate person_id:
SELECT DISTINCT a.PERSON_ID
FROM PERSON_PROPERTIES a JOIN PERSON_PROPERTIES b ON a.PERSON_ID = b.PERSON_ID
WHERE a.the_key = 'fname' AND a.value = 'robert'
AND b.the_key = 'lname' AND b.value = 'redford';
Demo
OK I will be marking this as the correct answer. The only thing I did was modified it a bit
SELECT Y.*, M.* FROM wp_postmeta as Y JOIN wp_postmeta AS M USING (`post_id`)
WHERE (Y.meta_key = 'agam_post_options_year' AND Y.meta_value = 2013)
AND (M.meta_key = 'agam_post_options_month' AND M.meta_value BETWEEN 0 AND 12 )
GROUP BY Y.meta_value, M.meta_value ORDER BY M.meta_value+0 DESC
So I get that DESC order.. however.. I noticed that it does not duplicates results... I had two posts with the same year and same month... now I don't see it... is there anything there that's preventing this ?

divide a column into two based on another column value - ORACLE

First, hope the title expresses the issue. Otherwise, any suggest is welcomed. My issue is I have the following table structure:
+----+------+------------------+-------------+
| ID | Name | recipient_sender | user |
+----+------+------------------+-------------+
| 1 | A | 1 | X |
| 2 | B | 2 | Y |
| 3 | A | 2 | Z |
| 4 | B | 1 | U |
| | | | |
+----+------+------------------+-------------+
Whereby in the column recipient_sender the value 1 means the user is recipient, the value 2 means the user is sender.
I need to present data in the following way:
+----+------+-----------+---------+
| ID | Name | recipient | sender |
+----+------+-----------+---------+
| 1 | A | X | Z |
| 2 | B | U | Y |
+----+------+-----------+---------+
I've tried self-join but it did not work. I cannot use MAX with CASE WHEN, as the number of records is too big.
Note: Please ignore the bad table design as it's just a simplified example of the real one
Please try:
SELECT
MIN(ID) ID
Name,
max(case when recipient_sender=1 then user else null end) sender,
max(case when recipient_sender=2 then user else null end) recipient
From yourTable
group by Name
maybe you can try this:
select min(id) id,
name,
max(decode(recipient_sender, 1, user, '')) sender,
max(decode(recipient_sender, 2, user, '')) recipient
from t
group by name
You can check a demo here on SQLFiddle.
You can select values with this query
SELECT t.id,
t.name,
case
when t.recipient_sender = 1 then
t.user
ELSE
t2.user
END as recipient,
case
when t.recipient_sender = 2 then
t.user
ELSE
t2.user
END as sender
FROM your_table t
JOIN your_table t2
ON t.name = t2.name
AND t.id != t2.id
after this query you can add DISTINCT keyword or GROUP them ...
this query is used to join tables with column NAME but if you have some identity for message , join tables using that ,
Create new Table (with better struct):
insert into <newtable> as
select distinct
id,
name,
user as recipient,
(select user from <tablename> where id = recip.id and name = recip.name) as sender
from <tablename> recip
sorry, have no oracle here.