Getting NULL values in JOINED table with LIMIT - postgresql

There are many similar questions which I've learned from, but my result set isn't returning the expected results.
My Objective:
Build a query that will return a result set containing all rows in table demo1 with user_id = "admin", and the only row of table demo2 with user_id = "admin". Each row in demo2 has a unique user_id so there's always only one row with "admin" as user_id.
However, I don't want demo2 data to wastefully repeat on every subsequent row of demo1. I only want the first row of the result set to contain demo2 data as non-null values. Null values for demo2 columns should only be returned for rows 2+ in the result set.
Current Status:
Right now my query is returning the appropriate columns (all demo1 and all demo2) but
all the data returned from demo2 is null.
Demo1:
id user_id product quantity warehouse
1 admin phone 3 A
2 admin desk 1 D
3 k45 chair 5 B
Demo2:
id user_id employee job country
1 admin james tech usa
2 c39 cindy tech spain
Query:
SELECT *
from demo1
left join (SELECT * FROM demo2 WHERE demo2.user_id = 'X' LIMIT 1) X
on (demo1.user_id = x.user_id)
WHERE demo1.user_id = 'admin'
Rationale:
The subquery's LIMIT 1 was my attempt to retrieve demo2 values for row 1 only, thinking the rest would be null. Instead, all values are null.
Current Result:
id user_id product quantity warehouse id employee job country
1 admin phone 3 A null null null null
2 admin desk 1 D null null null null
Desired Result:
id user_id product quantity warehouse id employee job country
1 admin phone 3 A 1 james tech usa
2 admin desk 1 D null null null null
I've tried substituting left join for left inner join, right join, full join, but nothing returns the desired result.

Your join is going to bring through ANY records that satisfies the join condition for your two tables. There is no changing that.
But you could suppress subsequent records in your result set from displaying the matching demo2 record that satisfied the join condition AFTER it's joined:
SELECT demo1.id ,
demo1.user_id,
demo1.product,
demo1.quantity,
demo1.warehouse
CASE WHEN ROW_NUMBER() OVER (PARTITION BY demo1.user_id ORDER BY demo1.id) = 1 THEN demo2.id END as demo2_id,
CASE WHEN ROW_NUMBER() OVER (PARTITION BY demo1.user_id ORDER BY demo1.id) = 1 THEN demo2.employee END AS demo2_employee,
CASE WHEN ROW_NUMBER() OVER (PARTITION BY demo1.user_id ORDER BY demo1.id) = 1 THEN demo2.job END as demo2_job,
CASE WHEN ROW_NUMBER() OVER (PARTITION BY demo1.user_id ORDER BY demo1.id) = 1 THEN demo2.country END as demo2_country
from demo1
left join demo2
on demo1.user_id = demo2.user_id
AND demo2.user_id = 'X'
WHERE demo1.user_id = 'admin'
That's just a quick rewrite of your original sql with the addition CASE expressions included.
That being said, this sql will produce no results for demo2 since the demo2.user_id can't satisfy both conditions in this query:
The join condition demo1.user_id = demo2.user_id with the where predicate of demo1.user_id = 'admin'
Also hold the value X.
It's either admin and satisfies your first join condition, but fails your second. Or it's X and satisfies your second condition, but nor your first.

Here is another nice approach:
sqlfiddle

Related

How to include and exclude ids in once query postgresql

I use PostgreSQL 13.3
I'm trying to think how I can make include/exclude in query at the same time
I have include_system_ids [1,5] and exclude_system_ids [3]
There's one big table - records
system_records table
record
system_id
1
1
1
5
1
3
2
1
2
5
If a record contains an exclusive identifier, then it should not be included in the final selection. I had some several tries, but I didn't get a necessary result
Awaiting result: record with id 2
Fact result: 1, 2
My variants
select r.id from records r
left join (select record_id from system_records
where system_id in (1,5)
) include_ids on r.id = include_ids
left join (select record_id from system_records
where system_id not in (3)
) exclude_ids on r.id = exclude_ids.id
Honestly, I don't understand how I can do it((
Is there anyone who can help me
Maybe this query could be a solution (result here)
with x as (select record,string_agg(system_id::varchar,',') as sys_id from records group by record)
select records.*
from records,x
where records.record = x.record
and x.sys_id = '1,5'

Query users on filter applied to a one-to-many relationship table postgresql

We currently have a users table with a one-to-many relationship on a table called steps. Each user can have either four steps or seven steps. The steps table schema is as follows:
id | user_id | order | status
-----------------------------
# | # |1-7/1-4| 0 or 1
I am trying to query all of the users who have a status of 1 on all of their steps. So if they have either 4 or 7 steps, they must all have a status of 1.
I tried a join with a check on step 4 (since a step cannot be complete without the previous one being complete as well) but this has issues if someone with 7 steps completed step 4 but not 7.
select u.first_name, u.last_name, u.email, date(s.updated_at) as completed_date
from users u
join steps s on u.id = s.user_id
where s.order = 4 and s.status = 1;
The bool_and aggregate function should help you to identify the users with all their steps at status = 1 whatever the number of steps.
Then the array_agg aggregate function can help to find the updated_at date associated to the last step for each user by ordering the dates according to order DESC and selecting the first value in the resulting array [1] :
SELECT u.first_name, u.last_name, u.email
, s.completed_date
FROM users u
INNER JOIN
( SELECT user_id
, (array_agg(updated_at ORDER BY order DESC))[1] :: date as completed_date
FROM steps
GROUP BY user_id
HAVING bool_and(status :: boolean) -- filter the users with all their steps status = 1
) AS s
ON u.id = s.user_id

Merge two tables in Postgresql giving preference to one particular table

I have two tables, Users and Masters. Users are having User specific settingkey-value. Masters is having master settingkey-value. I want to display key-value from the two tables, where
if users do not have that particular key, need to take it from masters
2 if the users do not exists in the table, need to display all from masters key-value
if users having key-value, have to display users key-value
Example:
Inputs being - UserID and appID = 1.
I tried with left join combination, but not getting desired result if Users do not exists at all in the Users table.
Could you please give me some advise.
step-by-step demo:db<>fiddle
SELECT
COALESCE(m.app_id, u.app_id) as app_id,
COALESCE(m.setting_key, u.setting_key) as setting_key,
COALESCE(u.setting_value, m.setting_value) as setting_value -- 2
FROM
master_table m
FULL OUTER JOIN -- 1
user_table u
ON m.app_id = u.app_id AND m.setting_key = u.setting_key
WHERE COALESCE(m.app_id, u.app_id) = 1 -- 3
AND (u.user_id = 1 OR u.user_id IS NULL)
You need a FULL OUTER JOIN to join also data set that the other table does not contain
COALESCE(a, b) gives you the first non-null value. So, if a (here the user value) is available, it will be returned. Otherwise b (here the master value)
Filter by app_id and user_id; second needs to be filtered by user_id == NULL too, to get all setting_keys. Of course, you could use here COALESCE as well: COALESCE(u.user_id, 1) whereas the last 1 is the specific user_id you're asking
Edit: If User does not exist, give out the Masters values for app_id:
step-by-step demo:db<>fiddle:
SELECT DISTINCT ON (app_id, setting_key) -- 3
*
FROM (
SELECT
COALESCE(user_app_id, master_app_id) AS app_id, -- 2
COALESCE(user_setting_key, master_setting_key) AS setting_key,
COALESCE(user_setting_value, master_setting_value) AS setting_value,
user_id
FROM (
SELECT
app_id as master_app_id,
setting_key as master_setting_key,
setting_value as master_setting_value,
null as user_id,
null as user_app_id,
null as user_setting_key,
null as user_setting_value
FROM
master_table m
UNION -- 1
SELECT
*
FROM
master_table m
FULL OUTER JOIN
user_table u
ON m.app_id = u.app_id AND m.setting_key = u.setting_key
) s
) s
WHERE app_id = 1
AND (user_id = 2 OR user_id IS NULL)
ORDER BY app_id, setting_key, user_id NULLS LAST -- 3
This is a little more complicated. You need a separate data set for user_id == NULL which could be fetched. So, the NULL user represents the unknown user.
You can achieve this by adding the Master table with NULL values using an UNION.
Now you can create the expected columns with the COALESCE() functions as described above.
The third trick is using the DISTINCT ON clause on the app_id and the setting_key columns. When you ordered the NULL columns from the default UNION part in (1) last, then the DISTINCT ON will fetch the user record. However, when the user didn't exist, then the DISTINCT ON will fetch the default Master record.

How to fill the null values using last fill for the same id in PSQL?

I have a data frame in PostgreSQL as follows and I want the latest record for each id, if any latest record for each id contains a NULL value in any column, I want to replace it with the next latest value within the same column
data
id ingdt code gender address
1 27-10-2018 NULL NULL street1
1 24-10-2018 1234 NULL street2
1 20-08-2017 3245 M street2
2 24-09-2018 NULL F Astreet
2 24-10-2018 2857 F Bstreet
3 24-08-2018 3489 M NULL
3 22-08-2018 5802 M Cstreet
Expected Output
final_output
id ingdt code gender address
1 27-10-2018 1234 M street1
2 24-10-2018 2857 F Bstreet
3 24-08-2018 3489 M Cstreet
Tried
insert into final_output select * from (
(select code, id from data where code != null order by ingdt limit 1) x join
(select gender, id from data where gender != null order by ingdt limit 1) y join
(select address, id from data where address != null order by ingdt limit 1)z on y.id=x.id)
demo:db<>fiddle
Using window functions can help you:
SELECT DISTINCT
id,
max(ingdt) OVER (PARTITION BY id),
first_value(code) OVER (PARTITION BY id ORDER BY code IS NULL, ingdt DESC) AS code,
first_value(gender) OVER (PARTITION BY id ORDER BY gender IS NULL, ingdt DESC) AS gender,
first_value(address) OVER (PARTITION BY id ORDER BY address IS NULL, ingdt DESC) AS address
FROM mytable
ORDER BY id
Explaining first_value(...) OVER (...):
A window function can group your rows into separate frames. This is done by the key word PARTITION BY. In this case I am generating frames for each id.
Now I am checking whether or not the value of a column is NULL. This gives me true or false. I am sorting this result like any boolean column with false first (meaning NOT NULL). If there are many NOT NULL rows, the latest row is taken (ingdt DESC). This ordering is done for every single frame separately as well.
first_value() calculates the first value of the ordered frame.

Finding exact matches to a requested set of values

Hi I'm facing a challenge. There is a table progress.
User_id | Assesment_id
-----------------------
1 | Test_1
2 | Test_1
3 | Test_1
1 | Test_2
2 | Test_2
1 | Test_3
3 | Test_3
I need to pull out the user_id who have completed only Test_1 & test_2 (i.e User_id:2). The input parameters would be the list of Assesment id.
Edit:
I want those who have completed all the assessments on the list, but no others.
User 3 did not complete Test_2, and so is excluded.
User 1 completed an extra test, and is also excluded.
Only User 2 has completed exactly those assessments requested.
You don't need a complicated join or even subqueries. Simply use the INTERSECT operator:
select user_id from progress where assessment_id = 'Test_1'
intersect
select user_id from progress where assessment_id = 'Test_2'
I interpreted your question to mean that you want users who have completed all of the tests in your assessment list, but not any other tests. I'll use a technique called common table expressions so that you can follow step by step, but it is all one query statement.
Let's say you supply your assessment list as rows in a table called Checktests. We can count those values to find out how many tests are needed.
If we use a LEFT OUTER JOIN then values from the right-side table will be null. So the test_matched column will be null if an assessment is not on your list. COUNT() ignores null values, so we can use this to find out how many tests were taken that were on the list, and then compare this to the number of all tests the user took.
with x as
(select count(assessment_id) as tests_needed
from checktests
),
dtl as
(select p.user_id,
p.assessment_id as test_taken,
c.assessment_id as test_matched
from progress p
left join checktests c on p.assessment_id = c.assessment_id
),
y as
(select user_id,
count(test_taken) as all_tests,
count(test_matched) as wanted_tests -- count() ignores nulls
from dtl
group by user_id
)
select user_id
from y
join x on y.wanted_tests = x.tests_needed
where y.wanted_tests = y.all_tests ;