Join query with not equal - sql

I want to get all unpaid male customers those who are not in any plan
SELECT cr.id, cr.type FROM mydb.customer cr
JOIN mydb.plan1 p1 on cr.id != p1.id
JOIN mydb.plan2 p2 on cr.id != p2.id
JOIN mydb.plan3 p3 on cr.id != p3.id
WHERE cr.type = 'male'
is this query correct?

You could use a series of three left joins along with IS NULL:
SELECT cr.id, cr.type
FROM mydb.customer cr
LEFT JOIN mydb.plan1 p1
ON cr.id = p1.id
LEFT JOIN mydb.plan2 p2
ON cr.id = p2.id
LEFT JOIN mydb.plan3 p3
ON cr.id = p3.id
WHERE p1.id IS NULL AND p2.id IS NULL AND p3.id iS NULL AND
cr.type = 'male'

Since all you seem to need is the id, EXCEPT should be a good choice here:
SELECT id FROM mydb.customer WHERE type = 'male'
EXCEPT ALL SELECT id FROM mydb.plan1
EXCEPT ALL SELECT id FROM mydb.plan2
EXCEPT ALL SELECT id FROM mydb.plan3;
To be precise: EXCEPT ALL:
Using EXCEPT clause in PostgreSQL
Basic techniques:
Select rows which are not present in other table
Multiple joins may not perform as fast if each table can have multiple related rows due to multiplication of rows in the intermediary derived table. Just test performance with EXPLAIN ANALYZE.

Related

Access Subquery On mulitple conditions

This SQL query needs to be done in ACCESS.
I am trying to do a subquery on the total sales, but I want to link the sale to the province AND to product. The below query will work with one or the other: (po.product_name = allp.all_products) AND (p.province = allp.all_province); -- but it will no take both.
I will be including every month into this query, once I can figure out the subquery on with two criteria.
Select
p.province as [Province],
po.product_name as [Product],
all_price
FROM
(purchase_order po
INNER JOIN person p
on p.person_id = po.person_id)
left join
(
select
po1.product_name AS [all_products],
sum(pp1.price) AS [all_price],
p1.province AS [all_province]
from (purchase_order po1
INNER JOIN product pp1
on po1.product_name = pp1.product_name)
INNER JOIN person p1
on po1.person_id = p1.person_id
group by po1.product_name, pp1.price, p1.province
)
as allp
on (po.product_name = allp.all_products) AND (p.province = allp.all_province);
Make the first select sql into a table by giving it an alias and join table 1 to table 2. I don't have your table structure or data to test it but I think this will lead you down the right path:
select table1.*, table2.*
from
(Select
p.province as [Province],
po.product_name as [Product]
--removed this ,all_price
FROM
(purchase_order po
INNER JOIN person p
on p.person_id = po.person_id) table1
left join
(
select
po1.product_name AS [all_products],
sum(pp1.price) AS [all_price],
p1.province AS [all_province]
from (purchase_order po1
INNER JOIN product pp1
on po1.product_name = pp1.product_name)
INNER JOIN person p1
on po1.person_id = p1.person_id
group by po1.product_name, pp1.price, p1.province --check your group by, I dont think you want pp1.price here if you want to aggregate
) as table2 --changed from allp
on (table1.product = table2.all_products) AND (table1.province = table2.all_province);

How can I join 3 tables, limit the query based on a condition, and return empty columns if condition not met?

I have the following query currently:
select * from people
LEFT JOIN addresses
ON people.id = addresses.id
LEFT JOIN pers
ON people.id = pers.pers_id
WHERE people.id =:id
AND addresses.is_primary = 'Y'
Of course if there is no address where is_primary = 'Y' for a given person, the query doesn't return any results.
Without is_primary='Y', the query returns multiple addresses.
Is there any way, instead, to return null columns for all of the address fields in the event where there is no record for the id where is_primary = 'Y'?
You can do something like this -
select *
from people
LEFT JOIN addresses
ON people.pidm = addresses.pidm
and addresses.is_primary = 'Y'
RIGHT JOIN pers
ON people.id = pers.pers_id
WHERE people.id = :id
use case when
select *,case when addresses.is_primary not in('Y') then 'primary address different' else addresses.is_primary end as is_primary from people
LEFT JOIN addresses
ON people.pidm = addresses.pidm
RIGHT JOIN pers
ON people.id = pers.pers_id
WHERE people.id =:id
I strongly recommend that you not mix left join and right join. The query is just so hard to follow.
Instead, start with the table where you want to keep all the rows. Then only use left join. Sometimes, you may need to put conditions in the on clause.
In your case:
select *
from people p left join
addresses a
on p.pidm = a.pidm and a.is_primary = 'Y' left join
pers
on p.id = pers.pers_id
where p.id = :id;

how to select a column in postgres?

My Query:
select c.id, sum(case when r.paid_out>='1' then 0 else 1 end) as all_paids
from people p1, houses h, policies p2, receipts r
where p1.id = h.id
and h.code = p2.code
and p2.code_policy = r.code_policy
group by p1.id
having all_paids= 0;
Postesql return me the following error:
ERROR : There is no column 'all_paids'
I have tried several things but nothing, any help is appreciated
First, learn to use proper join syntax.
Second, you can use the expression for the having clause or a subquery:
select p1.id
from people p1 join
houses h
on p1.id = h.id join
policies p2
on h.code = p2.code join
receipts r
on p2.code_policy = r.code_policy
group by p1.id
having sum(case when r.paid_out >= '1' then 0 else 1 end) = 0;
Note: there is no c.id defined, so I assume the select should be p1.id based on the group by.
I should say that this logic would often be expressed using not exists:
select p1.*
from people p1
where not exists (select 1
from houses h
policies p2
on h.code = p2.code join
receipts r
on p2.code_policy = r.code_policy
where p1.id = h.id and
r.paid_out >= '1'
);
This eliminates the aggregation.

Distinct on id with ordering by possible duplicate names

I have the following requisites for a query:
Needs to ordered on a inner joined table (see from_products_products below),
Allow duplicates names on from_products_products
It cannot return duplicates records on the origin table (distinct on products.id).
The following query will eliminate the duplicate names, which is not desired, as I had to put a distinct on from_products_products.name because of the use in order by:
SELECT DISTINCT ON (from_products_products.name, products.id) "products".* FROM "products"
INNER JOIN "suppliers_plugin_source_products" ON "suppliers_plugin_source_products"."to_product_id" = "products"."id"
INNER JOIN "products" "from_products_products" ON "from_products_products"."id" = "suppliers_plugin_source_products"."from_product_id"
INNER JOIN "suppliers_plugin_source_products" "sources_from_products_products_join" ON "sources_from_products_products_join"."to_product_id" = "products"."id"
INNER JOIN "suppliers_plugin_suppliers" ON "suppliers_plugin_suppliers"."id" = "sources_from_products_products_join"."supplier_id"
WHERE "products"."profile_id" = 45781 AND (("products"."type" IN ('SuppliersPlugin::DistributedProduct') OR "products"."type" IS NULL)) AND (products.archived <> true)
ORDER BY from_products_products.name ASC, products.id
Using GROUP BY has the same effect and also don't remove duplicates;
The original query that gives duplicate products when the INNER JOIN doesn't match any product:
SELECT "products".* FROM "products"
INNER JOIN "suppliers_plugin_source_products" ON "suppliers_plugin_source_products"."to_product_id" = "products"."id"
INNER JOIN "products" "from_products_products" ON "from_products_products"."id" = "suppliers_plugin_source_products"."from_product_id"
INNER JOIN "suppliers_plugin_source_products" "sources_from_products_products_join" ON "sources_from_products_products_join"."to_product_id" = "products"."id"
INNER JOIN "suppliers_plugin_suppliers" ON "suppliers_plugin_suppliers"."id" = "sources_from_products_products_join"."supplier_id"
WHERE "products"."profile_id" = 45781 AND (("products"."type" IN ('SuppliersPlugin::DistributedProduct') OR "products"."type" IS NULL)) AND (products.archived <> true)
ORDER BY from_products_products.name ASC
So, how to overcome this on PostgreSQL?
PS: This is part of open-source software Noosfero-ecosol
Does this do what you want?
with t as (
SELECT DISTINCT ON (products.id) "products".*,
from_products_products.name as from_products_name
FROM "products"
INNER JOIN "suppliers_plugin_source_products" ON "suppliers_plugin_source_products"."to_product_id" = "products"."id"
INNER JOIN "products" "from_products_products" ON "from_products_products"."id" = "suppliers_plugin_source_products"."from_product_id"
INNER JOIN "suppliers_plugin_source_products" "sources_from_products_products_join" ON "sources_from_products_products_join"."to_product_id" = "products"."id"
INNER JOIN "suppliers_plugin_suppliers" ON "suppliers_plugin_suppliers"."id" = "sources_from_products_products_join"."supplier_id"
WHERE "products"."profile_id" = 45781 AND (("products"."type" IN ('SuppliersPlugin::DistributedProduct') OR "products"."type" IS NULL)) AND (products.archived <> true)
ORDER BY products.id
)
select t.*
from t
order by from_products_name
It seems to meet your requirements.
EDIT:
If the above does what you want, I can think of five options:
The above using a CTE.
Basically the same logic, using a subquery.
Using window functions, which is structurally very similar.
Using group by.
Using a where clause for the filtering logic.
Here is the group by method:
SELECT "products".*,
MIN(from_products_products.name) as from_products_name
FROM "products"
INNER JOIN "suppliers_plugin_source_products" ON "suppliers_plugin_source_products"."to_product_id" = "products"."id"
INNER JOIN "products" "from_products_products" ON "from_products_products"."id" = "suppliers_plugin_source_products"."from_product_id"
INNER JOIN "suppliers_plugin_source_products" "sources_from_products_products_join" ON "sources_from_products_products_join"."to_product_id" = "products"."id"
INNER JOIN "suppliers_plugin_suppliers" ON "suppliers_plugin_suppliers"."id" = "sources_from_products_products_join"."supplier_id"
WHERE "products"."profile_id" = 45781 AND (("products"."type" IN ('SuppliersPlugin::DistributedProduct') OR "products"."type" IS NULL)) AND (products.archived <> true)
GROUP BY products.id
ORDER BY from_products_name;
This form depends on products.id being declared as a primary key. Alternatively, you can put all the columns from that table in the group by.
Rewriting (simplifying the aliases) yields:
SELECT p1.*
FROM products p1
INNER JOIN suppliers_plugin_source_products spsp
ON spsp.to_product_id = p1.id
INNER JOIN products p2
ON p2.id = spsp.from_product_id
INNER JOIN suppliers_plugin_source_products spsp2
ON spsp2.to_product_id = p1.id -- <<-- Huh?
INNER JOIN suppliers_plugin_suppliers sps
ON sps.id = spsp2.supplier_id
WHERE p1.profile_id = 45781
AND (p1."type" IN ('SuppliersPlugin::DistributedProduct') OR p1."type" IS NULL)
AND p1.archived <> true
ORDER BY p2.name ASC -- <<-- Huh?
;
The outer query only refers to the product tables p1 and p2.
Assuming that JOINing the "suppliers_plugin_source_products" table twice was unintentional, this can be reduced to:
SELECT p1.*
FROM products p1
JOIN products p2
ON EXISTS (
SELECT * FROM suppliers_plugin_source_products spsp
-- the next line might not be necessary ...
INNER JOIN suppliers_plugin_suppliers sps ON sps.id = spsp.supplier_id
WHERE spsp.to_product_id = p1.id
AND spsp.from_product_id = p2.id
)
WHERE p1.profile_id = 45781
AND (p1."type" IN ('SuppliersPlugin::DistributedProduct') OR p1."type" IS NULL)
AND p1.archived <> true
ORDER BY p2.name ASC
;

Finding unique matches in 2 separate databases

I have 2 databases that have the same structure, but different data. Both are SQL 2005.
I am trying to find which of the Persons in Database A, exist in Database B. My best opportunity for match is to match on FirstName and LastName.
I only want to bring back a list of:
DatabaseA.Person
DatabaseB.Person
Where:
1. I want all records from DatabaseA, even if there is not a match in Database B.
2. I only want records from DatabaseB where the FirstName/LastName match only one record in DatabaseB.
I have written a query, where I group by, but since I need to see more data than FirstName and LastName, I cannot bring it back without grouping it - which gives me many duplicates. What kind of query should I be using? Do I need to use a cursor?
Here is my query now, which sort of works - except I'm getting results for duplicates in DatabaseB and all I want to know about Database B is when FirstName/LastName matches to one distinct record and no others. My objective is to get a list of people that I know are the same person in 2 databases so that I can build a dictionary list of department code mappings between employees.
select
count(DatabaseAEmployee.id) as matchcount
, DatabaseAPerson.id as DatabaseAPersonid
, DatabaseAEmployee.DeptCode DatabaseADeptCode
, DatabaseAPerson.firstname as DatabaseAfirst
, DatabaseAPerson.lastname as DatabaseAlast
, DatabaseBPerson.id as DatabaseBPersonid
, DatabaseBEmployee.DeptCode as DatabaseBDeptCode
, DatabaseBPerson.firstname as DatabaseBfirst
, DatabaseBPerson.lastname as DatabaseBlast
, DatabaseAPerson.ssn as DatabaseAssn
, DatabaseBPerson.ssn as DatabaseBssn
, DatabaseAPerson.dateofbirth as DatabaseAdob
, DatabaseBPerson.dateofbirth as DatabaseBdob
FROM [DatabaseA].[dbo].Employee DatabaseAEmployee
LEFT OUTER JOIN [DatabaseA].[dbo].Person DatabaseAPerson
ON DatabaseAPerson.id = DatabaseAEmployee.id
LEFT OUTER JOIN [DatabaseB].[dbo].Person DatabaseBPerson
ON
DatabaseAPerson.firstname = DatabaseBPerson.firstname
AND
DatabaseAPerson.lastname = DatabaseBPerson.lastname
LEFT OUTER JOIN [DatabaseB].[dbo].Employee DatabaseBEmployee
on DatabaseBEmployee.id = DatabaseBPerson.id
group by
DatabaseAPerson.firstname
, DatabaseAPerson.lastname
, DatabaseAPerson.id
, DatabaseAEmployee.DeptCode
, DatabaseBPerson.id
, DatabaseBEmployee.DeptCode
, DatabaseBPerson.firstname
, DatabaseBPerson.lastname
, DatabaseBPerson.ssn
, DatabaseAPerson.ssn
, DatabaseBPerson.dateofbirth
, DatabaseAPerson.dateofbirth
Here's what I'm trying now, but I'm getting duplicates on the left side:
with UniqueMatchedPersons (Id, FirstName, LastName)
as (
select
p2.ID, p2.FirstName, p2.LastName
from
[DatabaseA].[dbo].[Employee] p1
INNER JOIN [DatabaseA].[dbo].[Person] p2 on p1.id = p2.id
inner join [DatabaseB].[dbo].[Person] p3
on p2.FirstName = p3.FirstName and p2.LastName = p3.LastName
INNER JOIN [DatabaseB].[dbo].[Employee] p4
on p3.id = p4.id
group by p2.ID, p2.FirstName, p2.LastName
having count(p2.ID) = 1
)
select p1.*, p2.*
from DatabaseA.dbo.Person p1
inner join UniqueMatchedPersons on p1.ID = UniqueMatchedPersons.ID
left outer join DatabaseB.dbo.Person p2
on p1.FirstName = p2.FirstName and p1.LastName = p2.LastName
Try this:
SELECT id,FirstName,Lastname
FROM dba.Persons
UNION
SELECT b.id,b.FirstName,b.LastName
FROM dbb.Persons as b
INNER JOIN dba.Persons as a
ON b.FirstName = a.FirstName AND b.LastName = a.LastName
If you want to get all from A and only those from B that DON'T have a match (which would make more sense to me) i'd use this:
SELECT id,FirstName,Lastname
FROM dba.Persons
UNION
SELECT b.id,b.FirstName,b.LastName
FROM dbb.Persons as b
LEFT OUTER JOIN dba.Persons as a
ON b.FirstName = a.FirstName AND b.LastName = a.LastName
WHERE a.id is null
Try something like:
Select dta.LastName, dta.FirstName, dta.[otherColumns] dtb.LastName, dtb.FirstName
dtb.[otherColumns]
From [databaseA].[table] as dta
LEFT OUTER JOIN [databaseB].[table] as dtb
on dta.Lastname = dtb.LastName and dta.FirstName = dtb.FirstName
That should get you: 1) everyone in table A, and 2) everyone in table B who is has a Lastname/Firstname match in table A.
Works when SQL Server (at least it should)
SELECT
A.*
, B.*
FROM
DatabaseA.dbo.Person A
LEFT JOIN DatabaseB.dbo.Person B
ON A.FirstName = B.FirstName AND A.LastName = B.LastName
Edit: You mention you receive duplicates from DatabaseB where you only need the match on first and lastname. But you also request other data (then first/lastname) this is the problem. If you distinct data they you only request that data.
Using transact-sql, the following untested query should allow you to view unique matches only:
select
p1.ID, p1.FirstName, p1.LastName
from
[DatabaseA].[dbo].[Persons] p1
left outer join [DatabaseB].[dbo].[Persons] p2
on p1.FirstName = p2.FirstName and p1.LastName = p2.LastName
group by p1.ID, p1.FirstName, p2.LastName
having count(p1.ID) = 1
If using Sql Server, this can then be encapsulated within a common table expression, to which you can perform a join.
with UniqueMatchedPersons (Id, FirstName, LastName)
as (
--query in previous code snippet
)
select persons.*
from Persons
inner join UniqueMatchedPersons on Persons.ID = UniqueMatchedPersons.ID
Update:
If you wish to select fields from both tables, you can simply respecify the original join condition that evaluated name matching before; this is because duplicated matches on the left hand side of the join have been filtered out by the having aggregate condition.
Modifying the select portion of the above snippet to read the following will allow you to select fields from either side of the join:
select p1.*, p2.*
from [DatabaseA].[dbo].[Persons] p1
inner join UniqueMatchedPersons on p1.ID = UniqueMatchedPersons.ID
left outer join [DatabaseB].[dbo].[Persons] p2
on p1.FirstName = p2.FirstName and p1.LastName = p2.LastName
Update 2:
To filter out duplicates on the left hand side (which will also cause duplicates on the right) you'll have to remove the grouping on [DatabaseA].[dbo].[Persons].[ID].
When I refer to duplicates, I mean names in adjacent rows that are identical in terms of characters and padding. If you have diacritic variations of first and last names, then the results of the name comparison will be subject to the database collation (unless you explicity declare a collation on a join expression). Likewise if you have variations in spacing, padding or punctuation between names, you may have to consider a different approach than a direct equality operator for name matching.
Try the following:
with UniqueMatchedPersons (FirstName, LastName)
as (
select
p1.FirstName, p1.LastName
from
[DatabaseA].[dbo].[Person] p1
left outer join [DatabaseB].[dbo].[Person] p2
on p2.FirstName = p3.FirstName and p2.LastName = p3.LastName
group by p1.FirstName, p1.LastName
having count(p1.FirstName) = 1
)
select p1.*, p2.*, e1.*, e2.*
from [DatabaseA].[dbo].[Person] p1
inner join UniqueMatchedPersons ump
on p1.FirstName = ump.FirstName and p1.LastName = ump.LastName
left outer join [DatabaseB].[dbo].[Person] p2
on p1.FirstName = p2.FirstName and p1.LastName = p2.LastName
inner join [DatabaseA].[dbo].[Employee] e1 on p1.ID = e1.ID
inner join [DatabaseB].[dbo].[Employee] e2 on e2.ID = p2.ID
order by p1.id asc