here is what i would like to accomplish: I have two (or more) tables in join, and I would like to get a boolean output if some conditions are met, e.g.
Table1:
customer_id
customer_name
status
1
Google
waiting
2
Facebook
working
3
Salesforce
waiting
Table2:
customer_id
agent
outcome
1
John
failure
1
Mike
success
2
John
success
I would like to get for all customer ids true if status in Table1 is "waiting" and there is no Table2 record for that customer
Desired output:
customer_id
waiting_and_no_record_in_table_2
1
false
2
false
3
true
Any idea on how to reach this goal?
Thanks in advance
As there can be multiple rows per customer_id in table2, to achieve the desired result requires some simplification be applied on table2. For this I simply chose select distinct which is sufficient for the example, but alternatives do exist.
select
table1.customer_id
, case when table1.status = 'waiting' and t2.customer_id IS NULL then true else false end as waiting_and_no_record_in_table_2
from table1
left join (select distinct customer_id from table2) as t2 on table1.customer_id = t2.customer_id
order by
table1.customer_id
and alternative might be to join only successful rows from t2 although this may still produce more than one row per customer_id
select
table1.customer_id
, case when table1.status = 'waiting' and t2.customer_id IS NULL then true else false end as waiting_and_no_record_in_table_2
from table1
left join table2 as t2 on table1.customer_id = t2.customer_id
and t2.outcome = 'success'
order by
table1.customer_id
or these 2 might be combined to also ensure only one row per customer_id
select
table1.customer_id
, case when table1.status = 'waiting' and t2.customer_id IS NULL then true else false end as waiting_and_no_record_in_table_2
from table1
left join (select distinct customer_id from table2 where outcome = 'success') as t2 on table1.customer_id = t2.customer_id
order by
table1.customer_id
Related
In a Rails app with Postgres I have a users, jobs and followers join table. I want to select jobs that are not followed by a specific user. But also jobs with no rows in the join table.
Tables:
users:
id: bigint (pk)
jobs:
id: bigint (pk)
followings:
id: bigint (pk)
job_id: bigint (fk)
user_id: bigint (fk)
Data:
sandbox_development=# SELECT id FROM jobs;
id
----
1
2
3
(3 rows)
sandbox_development=# SELECT id FROM users;
id
----
1
2
sandbox_development=#
SELECT id, user_id, job_id FROM followings;
id | user_id | job_id
----+---------+--------
1 | 1 | 1
2 | 2 | 2
(2 rows)
Expected result
# jobs
id
----
2
3
(2 rows)
Can I create a join query that is the equivalent of this?
sandbox_development=#
SELECT j.id FROM jobs j
WHERE NOT EXISTS(
SELECT 1 FROM followings f
WHERE f.user_id = 1 AND f.job_id = j.id
);
id
----
2
3
(2 rows)
Which does the job but is a PITA to create with ActiveRecord.
So far I have:
Job.joins(:followings).where(followings: { user_id: 1 })
SELECT "jobs".* FROM "jobs"
INNER JOIN "followings"
ON "followings"."job_id" = "jobs"."id"
WHERE "followings"."user_id" != 1
But since its an inner join it does not include jobs with no followers (job id 3). I have also tried various attempts at outer joins that either give all the rows or no rows.
In Rails 5, You can use #left_outer_joins with where not to achieve the result. Left joins doesn't return null rows. So, We need to add nil conditions to fetch the rows.
Rails 5 Query:
Job.left_outer_joins(:followings).where.not(followings: {user_id: 1}).or(Job.left_outer_joins(:followings).where(followings: {user_id: nil}))
Alternate Query:
Job.left_outer_joins(:followings).where("followings.user_id != 1 OR followings.user_id is NULL")
Postgres Query:
SELECT "jobs".* FROM "jobs" LEFT OUTER JOIN "followings" ON "followings"."job_id" = "jobs"."id" WHERE "followings"."user_id" != 1 OR followings.user_id is NULL;
I'm not sure I understand, but this has the output you want and use outer join:
SELECT j.*
FROM jobs j LEFT JOIN followings f ON f.job_id = j.id
LEFT JOIN users u ON u.id = f.user_id AND u.id = 1
WHERE u.id IS NULL;
I'm going to use the group by clause...max().
I'm using Informix version 10.
This is example table: table_a
col_a col_b col_c
1 20181010 3
1 20181030 4
I want to retrieve data with a recent date.
I want result :
col_a col_b col_c
1 20181030 4
When I use this query
#query 1: select col_a, max(col_b), col_c from table_a group by col_a
#result : The column (col_c) must be in the GROUP BY list.
#query 2: select col_a, max(col_b), col_c from table_a group by col_a, col_c
#result :
col_a col_b col_c
1 20181010 3
1 20181030 4
I think can I use MS SQL of row_num(partition by col_b)? but
Informix version 10 can't use row_num...
So, I use a join query
select a.col_a,a.col_b,a.col_c from table_a a
inner join (select col_a, max(col_b) as col_b from table_a group by col_a) b
on a.col_a = b.col_a and a.col_b = b.col_b
I got the results I wanted.
Is there a way to use join?
I want to get the details of each condition result as true or false used in a join. For example below is the case
select emp.emp_id,emp.emp_name,hr.department,hr.salary
from employee emp left outer join HR
on emp.empId=HR.emp_id
and emp.emp_name=HR.emp_name
and emp.department=hr.department
and emp.salary=hr.salary;
now lets say first join condition emp_id is true and remaining are false. I want to get an output as
emp.emp_id,emp.emp_name,hr.department,hr.salary
true,false,false,false
and if first two conditions are true the output should be
true,true,false,false
and so on. Please ignore the database design here as it is taken just as an example. Thanks
Jafery
This might be along the lines of what you want to do here:
SELECT
CASE WHEN EXISTS (SELECT 1 FROM HR WHERE emp.empId = HR.empID)
THEN 'true' ELSE 'false' END AS emp_id,
CASE WHEN EXISTS (SELECT 1 FROM HR WHERE emp.emp_name = HR.emp_name)
THEN 'true' ELSE 'false' END AS emp_name,
CASE WHEN EXISTS (SELECT 1 FROM HR WHERE emp.department = HR.department)
THEN 'true' ELSE 'false' END AS department,
CASE WHEN EXISTS (SELECT 1 FROM HR WHERE emp.salary = HR.salary)
THEN 'true' ELSE 'false' END AS salary
FROM employee emp;
Each of the CASE with EXISTS statements probes, for record in the employee table, the HR table to see if it can find at least one matching record, for that particular column.
Note that this answer might not reflect an actual join, where a record in employee could potentially match multiple records in HR. But, in this case, it is not well defined what it would mean for a single column alone to match or not match.
Maybe you can try :
SELECT CASE WHEN emp.emp_id = HR.emp_id THEN 'True' ELSE 'False' as [emp_id],
CASE WHEN emp.emp_name = HR.emp_name THEN 'True' ELSE 'False' as [emp_name],
CASE WHEN emp.department = HR.department THEN 'True' ELSE 'False' as [department],
CASE WHEN emp.salary = HR.salary THEN 'True' ELSE 'False' as [salary],
from employee emp left outer join HR
on emp.empId=HR.emp_id
and emp.emp_name=HR.emp_name
and emp.department=hr.department
and emp.salary=hr.salary
I found a similar question but it didn't satisfy my answer: SQL: Select records where ALL joined records satisfy some condition
I have two tables, orders and shipments
orders have_many shipments
shipments have attribute status open/closed
I would like to query orders, where all of its shipments are closed:
Assuming table of:
order1, 2 shipments: 1open, 1closed
order2, 3 shipments: 1open, 2closed
order3, 1 shipments: 0open, 1closed
order4, 2 shipments: 0open, 2closed
Running the query returns records for order3 and order4
I am currently doing this with N+1 using application code, I'd like to just implement in SQL.
Let's look for all orders for which there does not exist any shipment (matching that order) with a status other than closed.
Order.where("NOT EXISTS(SELECT 1 FROM shipments WHERE shipments.order_id = orders.id AND status != 'closed')"
demo:db<>fiddle
Aggregate the status and then you can filter with the ALL operator which checks if all array elements fit the condition.
SELECT order_id
FROM (
SELECT order_id, array_agg(status) status
FROM shipments
GROUP BY order_id
) s
WHERE 'closed' = ALL(status)
I need to produce a report of all records (businesses) created by a particular user each month over last months. I produced the following query and expect it to provide me with a row for each month. However, this user didn't create any records (businesses) these months so I get an empty result [].
I'm still expecting to receive a row for each month, since I'm selecting a generate_series column using RIGHT OUTER JOIN but it doesn't happen.
start = 3.months.ago
stop = Time.now
new_businesses = Business.select(
"generate_series, count(id) as new").
joins("RIGHT OUTER JOIN ( SELECT
generate_series(#{start.month}, #{stop.month})) series
ON generate_series = date_part('month', created_at)
").
where(created_at: start.beginning_of_month .. stop.end_of_month).
where(author_id: creator.id).
group("generate_series").
order('generate_series ASC')
How can I change my query to get a row for each month instead of an empty result? I'm using PosgreSQL.
UPDATE
This code works:
new_businesses = Business.select(
"generate_series as month, count(id) as new").
joins("RIGHT OUTER JOIN ( SELECT
generate_series(#{start.month}, #{stop.month})) series
ON (generate_series = date_part('month', created_at)
AND author_id = #{creator.id}
AND created_at BETWEEN '#{start.beginning_of_month.to_formatted_s(:db)}' AND
'#{stop.end_of_month.to_formatted_s(:db)}'
)
").
group("generate_series").
order('generate_series ASC')
Your problem is in the where part which is breaks any outer joins. Consider the example:
select *
from a right outer join b on (a.id = b.id)
It will returns all rows from b and linked values from a, but:
select *
from a right outer join b on (a.id = b.id)
where a.some_field = 1
will drops all rows where a is not present.
The right way to do such sings is to place the filter into the join query part:
select *
from a right outer join b on (a.id = b.id and a.some_field = 1)
or use subquery:
select *
from (select * from a where a.some_field = 1) as a right outer join b on (a.id = b.id)