Joining a table to itself to compare two different types of fields - sql

sorry for the confusing title.
I have two tables, individu and orgcont. individu looks something like:
individu
-------------
ind_id
org_id
email
orgcont looks something like:
orgcont
-------------
ind_id
function_code
I have many users that can belong to the same org_id and they each of their own ind_id. The 'leaders' of the org_id have a function code of 'PRIM', everyone else's are irrelevant. I am trying to return a list of org_id and email of all of the users who have a 'PRIM' function code who have users that have NULL email addresses that are not 'PRIM'.
I have something like this, but the results are not correct. Any help would be appreciated:
select
i.email,
i.org_id
from
individu i,
orgcont o,
individu i2,
orgcont o2
where
i.ind_id = o.ind_id
and i.org_id = i2.org_id
and i2.ind_id = o2.ind_id
and o.function_code = 'PRIM'
and o2.function_code != 'PRIM'
and i2.email is NULL

Try it with exists:
select
i.email,
i.org_id
from
individu i
inner join orgcont o on
i.ind_id = o.ind_id
where
o.function_code = 'PRIM'
and exists (
select
1
from
individu i2
inner join orgcont o2 on
i2.ind_id = o2.ind_id
where
i2.org_id = i.org_id
and o2.function_code != 'PRIM'
and i2.email is null
)
This will only return you one row for each leader with folks in their org with null email addresses, as opposed to your original query, which will return duplicate rows.
Also note the join syntax--you want to do an inner join here, not a cross join, so do so explicitly.
Additionally, you could have just done a select distinct, but that's a little more expensive than the exists.

This should work, the INNER JOIN to the sub query will act as the filter
SELECT DISTINCT i.email, i.org_id
FROM individu i
INNER JOIN orgcont o ON i.ind_id = o.ind_id
INNER JOIN
( SELECT i.id
FROM individu i
INNER JOIN orgcont o ON i.ind_id = o.ind_id
WHERE o.function_code != 'PRIM' AND i.email IS NULL
) t ON t.id = i.id
WHERE o.function_code = 'PRIM'

Related

Nesting queries

My query from the attached schema is asking me to look for the same location of where the people who tested positive went and were in the same people as the untested people. (Untested means the people not there in the testing table.
--find the same locations of where the positive people and the untested people went
select checkin.LocID, checkin.PersonID
from checkin join testing on checkin.personid = testing.personid
where results = 'Positive'
and (select CheckIn.PersonID
from checkin join testing on checkin.PersonID = testing.PersonID where CheckIn.PersonID
not in (select testing.PersonID from testing));
In my view the query is stating the following
To select a location and person from joining the checking and testing table and the result is positive and to select a person from the check in table who is not there in the testing table.
Since the answer I am getting is zero and I know manually there are people. What am I doing wrong?
I hope this makes sense.
You can get the people tested 'Positive' with this query:
select personid from testing where results = 'Positive'
and the untested people with:
select p.personid
from person p left join testing t
on t.personid = p.personid
where t.testingid is null
You must join to each of these queries a copy of checkin and these copies joined together:
select l.*
from (select personid from testing where results = 'Positive') p
inner join checkin cp on cp.personid = p.personid
inner join checkin cu on cu.lid = cp.lid
inner join (
select p.personid
from person p left join testing t
on t.personid = p.personid
where t.testingid is null
) pu on pu.personid = cu.personid
inner join location l on l.locationid = cu.lid
If what you want are the positive people who are at a location where there is also someone who is not tested, you might consider:
select ch.LocID,
group_concat(case when t.results = 'positive' then ch.PersonID end) as positive_persons
from checkin ch left join
testing t
on ch.personid = t.personid
group by ch.LocId
having sum(case when t.results = 'positive' then 1 else 0 end) > 0 and
count(*) <> count(t.personid); -- at least one person not tested
With this structure, you can get the untested people using:
group_concat(case when t.personid is null then ch.personid)
You have several mistakes (missing exists, independent subquery in exists). I believe that this should do the work
select ch1.LocID, ch1.PersonID
from checkin ch1
join testing t1 on ch1.personid = t1.personid
where results = 'Positive'
and exists (
select 1
from checkin ch2
where ch1.LocID = ch2.LocID and ch2.PersonID not in (
select testing.PersonID
from testing
)
);

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);

List of users who do not have transactions after 'x' date

I have a query I need to create that pulls data from three different tables. Essentially the end result is to pull data for users that have not had any activity on a users' account since 07/01/2018, but they have to have an account plan as "x." Is there a way to manipulate this query I've created to get it to display what I need it to? Maybe somehow correlate it to a count of 0 on the TRANDATE column? Very lost here and could use some help!
select p.ID as ID, p.LAST as LastName, p.FIRST as FirstName
From gl
inner join p
on p.ID = gl.PID
left join psp
on psp.PLANNUM = gl.ACCNUM
where gl.ACCNUM = 'x'
and psp.ACTIVE = 1
and gl.TRANDATE <= to_date('07/01/2018', 'MM/DD/YYYY')
;
commit;
Thank you all very much and please let me know if there's anything else I can provide here.
You don't really specify your table structure, so I'm kinda guessing here. Does your "gl" table have more than one record per PID+ACCNUM? What are the primary keys? There are a lot of extra details you could provide.
select p.ID as ID, p.LAST as LastName, p.FIRST as FirstName, g.maxdate
From (select gl.PID, gl.ACCNUM, max(gl.TRANDATE) as maxdate
from gl
where gl.ACCNUM = 'x'
group by gl.PID, gl.ACCNUM) g
inner join p
on p.ID = g.PID
inner join psp
on psp.PLANNUM = g.ACCNUM
and psp.ACTIVE = 1
where g.maxdate <= to_date('07/01/2018', 'MM/DD/YYYY')
;
Also, (a) you don't need a commit on a select, and (b) a left/outer join is an inner join when you have its column in the WHERE clause (psp.ACTIVE = 1).
I would simply use aggregation and max() in the having clause:
select p.ID as ID, p.LAST as LastName, p.FIRST as FirstName
from gl inner join
p
on p.ID = gl.PID inner join
psp
on psp.PLANNUM = gl.ACCNUM
where gl.ACCNUM = 'x' and psp.ACTIVE = 1
group by p.ID, p.LAST, p.FIRST
having max(gl.TRANDATE) <= date '2018-07-01';
Note that the where condition on psp.ACTIVE turns the outer join into an inner join, so I changed the join type for readability.
Since all your display fields are from p, you can do grouping in inner queryfor the p.id which occur more than once. All the rest of ids(not in) are of your interest.
select p.ID as ID, p.LAST as LastName, p.FIRST as FirstName
From p where p.ID not in (
select p.ID From gl
inner join p on p.ID = gl.PID
left join psp on psp.PLANNUM = gl.ACCNUM
where gl.ACCNUM = 'x'
and psp.ACTIVE = 1
and gl.TRANDATE <= to_date('07/01/2018', 'MM/DD/YYYY')
group by p.ID
having count(1) >= 1)

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;

Conditional Inner Join

I want to be able to inner join two tables based on the result of an expression.
What I've been trying so far:
INNER JOIN CASE WHEN RegT.Type = 1 THEN TimeRegistration ELSE DrivingRegistration AS RReg
ON
RReg.RegistreringsId = R.Id
RegT is a join I made just before this join:
INNER JOIN RegistrationTypes AS RegT ON R.RegistrationTypeId = RegT.Id
This SQL-script does not work.
So all in all, if the Type is 1, then it should join on the table TimeRegistration else it should join on DrivingRegistration.
Solution:
In my select statement I performed the following joins:
INNER JOIN RegistrationTypes AS RegT ON R.RegistrationTypeId = RegT.Id
LEFT OUTER JOIN TimeRegistration AS TReg ON TReg.RegistreringsId = R.Id AND RegT.Type = 1
LEFT OUTER JOIN DrivingRegistration AS DReg ON DReg.RegistreringsId = R.Id AND RegT.Type <>1
Then I edited my where-clause to output the correct, depending on the RegType, like this:
WHERE (CASE RegT.Type WHEN 1 THEN TReg.RegistreringsId ELSE DReg.RegistreringsId END = R.Id)
Try putting both tables in the query using LEFT JOIN's
LEFT JOIN TimeRegistration TR ON r.rid = TR.Id AND RegT.type =1
LEFT JOIN DrivingRegistration DR ON r.rid = DR.Id AND RegT.type <>1
Now, in you select clause, use
CASE RegType.Type WHEN 1 THEN TR.SomeField ELSE DR.someField END as SomeField
The other option is to use dynamic SQL
You probably need to perform two left joins, one onto TimeRegistration and one onto DrivingRegistration, and return the fields you want from the appropriate join table something like this:
LEFT JOIN TimeRegistration ON TimeRegistration.RegistreringsId = R.Id
LEFT JOIN DrivingRegistration ON DrivingRegistration.RegistreringsId = R.Id
and you select statement would be something like this:
SELECT CASE WHEN RegT.Type = 1 THEN TimeRegistration.Foo ELSE DrivingRegistration.Bar END
I like what you're trying to do, but I don't think SQL is that clever.
SELECT
R.foo, tr.bar
FROM
SomeTable AS R
INNER JOIN RegistrationTypes AS RegT ON R.RegistrationTypeId = RegT.Id
AND RegT1.Type = 1
INNER JOIN TimeRegistration AS tr ON /* whatever */
UNION
SELECT
R.foo, dr.bar
FROM
SomeTable AS R
INNER JOIN RegistrationTypes AS RegT ON R.RegistrationTypeId = RegT.Id
AND RegT1.Type = 2
INNER JOIN DrivingRegistration AS dr ON /* whatever */
So I had a scenario where there were three email columns in one table (don't ask why) and any of them could be null (or empty). In this example code I will just deal with a case where it is null.
I had to join it to another table by any of the emails to retrieve the users firstname.
Here is what worked
select
m.email1,
m.email2,
m.email3,
m2.firstName
from MyTable m
left join MyOtherTable m2 on m2.Email =
case when m.email1 is null then
case when m.email2 is null then
case when m.email3 null then
'nonexistent#mydomain.com' -- i stopped here
else m.email3 end
else wm.email2 end
else m.email1 end
Obviously you would include further conditions like
case when m.email1 is null or m.email1 = '' then ...
To cover for empty values.