(probably) very simple SQL query needed - sql

Having a slow day....could use some assistance writing a simple ANSI SQL query.
I have a list of individuals within families (first and last names), and a second table which lists a subset of those individuals. I would like to create a third table which flags every individual within a family if ANY of the individuals are not listed in the second table. The goal is essentially to flag "incomplete" families.
Below is an example of the two input tables, and the desired third table.
As I said...very simple...having a slow day. Thanks!

I think you want a left join and case expression:
select t1.*,
(case when t2.first_name is null then 'INCOMPLETE' else 'OK' end) as flag
from table1 t1 left join
table2 t2
on t1.first_name = t2.first_name and t1.last_name = t2.last_name;
Of course, this marks "Diane Thomson" as "OK", but I think that is an error in the question.
EDIT:
Oh, I see. The last name defines the family (that seems like a pretty big assumption). But you can do this with window functions:
select t1.*,
(case when count(t2.first_name) over (partition by t1.last_name) =
count(*) over (partition by t1.last_name)
then 'OK'
else 'INCOMPLETE'
end) as flag
from table1 t1 left join
table2 t2
on t1.first_name = t2.first_name and t1.last_name = t2.last_name;

That's not simple, at least not in SAS :-)
Standard SQL, when Windowed Aggregates are supported:
select ft.*,
-- counts differ when st.first_name is null due to the outer join
case when count(*) over (partition by ft.last_name)
= count(st.first_name) over (partition by ft.last_name)
then 'OK'
else 'INCOMPLETE'
end
from first_table as ft
left join second_table as st
on ft.first_name = st.first_name
and ft.last_name = ft.last_name
Otherwise you need to a standard aggregate and join back:
select ft.*, st.flag
from first_table as ft
join
(
select ft.last_name,
case when count(*)
= count(st.first_name)
then 'OK'
else 'INCOMPLETE'
end as flag
from first_table as ft
left join second_table as st
on ft.first_name = st.first_name
and ft.last_name = st.last_name
group by ft.last_name
) as st
on ft.last_name = st.last_name

It is pretty easy to do in SAS if you want to take advantage of its non-ANSI SQL feature of automatically re-merging aggregate function results back onto detail records.
select
a.first
, a.last
, case when 1=max(missing(b.last)) then 'INCOMPLETE'
else 'OK'
end as flag
from table1 a left join table2 b
on a.last=b.last and a.first=b.first
group by 2
order by 2,1
;

Related

JOIN AND CASE MORE AN TABLE

I have 2 tables; the first one ORG contains the following columns:
ORG_REF, ARB_REF, NAME, LEVEL, START_DATE
and the second one WORK contains these columns:
ARB_REF, WORK_STREET - WORK_NUM, WORK_ZIP
I want to do the following: write a select query that search in work and see if the WORK_STREET, WORK_ZIP are duplicate together, then you should look at WORK_NUM. If it is the same then output value ' ok ', but if WORK_NUM is not the same, output 'not ok'
I wrote this SQL query:
select
A.ARB_REF, A.WORK_STREET, A.WORK_NUM, A.WORK_ZIP
case when B.B = 1 then 'OK' else 'not ok' end
from
work A
join
(select
WORK_STREET, WORK_ZIP count(distinct , A.WORK_NUM) B
from
WORK
group by
WORK_STREET, WORK_ZIP) B on B.WORK_STREET = A.WORK_STREET
and B.WORK_ZIP = A.WORK_ZIP
Now I want to join the table ORG with this result I want to check if every address belong to org if it belong I should create a new column result and set it to yes in it (RESULT) AND show the "name" column otherwise set no in 'RESULT'.
Can anyone help me please?
While you can accomplish your result by adding a left outer join to the query you've already started, it might be easiest to just use count() over....
with org_data as (
-- do the inner join before the left join later
select * from org1 o1 inner join org2 o2 on o2.orgid = o1.orgid
)
select
*,
count(*) over (partition by WORK_STREET, WORKZIP) as cnt,
case when o.ARB_REF is not null then 'Yes' else 'No' end as result
from
WORK w left outer join org_data o on o.ARB_REF = w.ARB_REF

Adding CASE expression in the correct spot?

I want to change this query:
select
t.AccountA
,t.AccountB
,t.totalNumber
,a.Category
from TableA t
left join Accounts a
on t.ActNum = a.ActNum
left join
(select distinct
s.col1
from (
select ....
from Table
group by...
) st
left join (select S....
group by..
) g on...
left join (select... on ...
) t on ...
where...
)
so that c.AccountB displays "X" if it was a "Y". So I want to do something like
CASE WHEN c.AccountB = 'Y' THEN 'X' ELSE 'c.AccountB END
Except I'm having a problem where some data (a.Category) is coming from the table a, and table a doesn't have a record in it equal to "Y", so the join doesn't get the category data from a. That field is therefore blank. I'm trying to avoid adding it to that table and would rather change the query. How can I do this? What I think would work is:
select
t.AccountA
,t.AccountB
,t.totalNumber
,a.Category
from TableA t
left join ****** (Select CASE WHEN t.AccountB = 'Y' THEN 'X' ELSE 't.AccountB END Accounts a)
on t.ActNum = a.ActNum
left join
(select distinct
col1
from (
select ....
from Table
group by...
) sta
left join (select S....
group by..
) g on...
left join (select... on ...
) t on ...
where...
)
Where I put the CASE expression in the 7th line here by the asterisks ***
Will this return exactly the same records? This is a really long running query and difficult to test so I'm trying to run it as few times as possible, would like some input to help me so this doesn't turn into a 6 hour project.
EDIT: I had a typo, the first columns selected were supposed to reference the first table - I changed it (table "t")
First, this might be as simple as getting rid of the single quote before c.AccountB CASE WHEN c.AccountB = 'Y' THEN 'X' ELSE c.AccountB END Otherwise I'm not quite sure I understand what you want but I'll try:
If you just want to select then:
select
c.AccountA
,CASE WHEN c.AccountB = 'Y' THEN 'X' ELSE c.AccountB END AccountB
,totalNumber
,a.Category
from TableA t
left join Accounts a
on t.ActNum = a.ActNum
left join
...
If instead you want to use this as part of a join you'll have to use it in your join. Since you don't show how "c" is joined, nor how "c" and "a" are related I will try to give an example:
select
c.AccountA
,CASE WHEN c.AccountB = 'Y' THEN 'X' ELSE c.AccountB END AccountB
,totalNumber
,a.Category
from CheckRegister c
left join Accounts a
on a.ActNum = c.AccountA
left join Accounts b
on b.ActNum = CASE WHEN c.AccountB = 'Y' THEN 'X' ELSE c.AccountB END

Query result not working with array type in PostgreSQL

I have two tables which contains a column with data type array in PostgreSQL. The structure is like below:
tbl_tour_packages
tbl_header_images
I have a query which contains several joins. The query is working fine with other joins and showing no error. But missing the values from tbl_header_images.
The query is:
SELECT
t1.tour_id AS pid,
t1.tour_name AS title,
t1.tour_duration AS nights,
t1.tour_price_full AS price,
t1.discount AS discount,
t1.tour_seo_title AS seo,
t3.category AS category,
t4.image_names[1] AS image_url,
CASE WHEN max(s.state_name) IS NULL THEN NULL ELSE array_agg(s.state_name) END AS state,
CASE WHEN max(o.destination) IS NULL THEN NULL ELSE array_agg(o.destination) END AS destinations
FROM tbl_tour_packages t1
LEFT JOIN tbl_countries t2 ON t1.tour_country_iso = t2.iso
LEFT JOIN tbl_categories t3 on t1.tour_category_id = t3.id
LEFT JOIN tbl_header_images t4 ON t1.tour_id = t4.package_id
LEFT JOIN tbl_states AS s ON (t1.tour_state #> array[s.state_code])
LEFT JOIN tbl_destinations AS o ON (t1.tour_destination #> array[o.id])
WHERE t1.tour_status = 1
GROUP BY 1,7,8
ORDER BY view_count ASC LIMIT 6
I want to get the 'image_name' from tbl_header_images. Any quick help or suggestion will be appreciated.
before WHERE clause you should be able to do something like:
, unnest(image_names) _image_names
and then in select statement aggregate that back into an array
array_agg(_image_names) AS image_names
I don't quite get the t4.image_names[1] AS image_url attempt, but I'm sure you can pick it up from here.
so the whole query would be something like:
edit: I've stripped extra groupping
SELECT
t1.tour_id AS pid,
t1.tour_name AS title,
t1.tour_duration AS nights,
t1.tour_price_full AS price,
t1.discount AS discount,
t1.tour_seo_title AS seo,
t3.category AS category,
(array_agg(_image_names))[1] AS image_url,
CASE WHEN max(s.state_name) IS NULL THEN NULL ELSE array_agg(s.state_name) END AS state,
CASE WHEN max(o.destination) IS NULL THEN NULL ELSE array_agg(o.destination) END AS destinations
FROM tbl_tour_packages t1
LEFT JOIN tbl_countries t2 ON t1.tour_country_iso = t2.iso
LEFT JOIN tbl_categories t3 on t1.tour_category_id = t3.id
LEFT JOIN tbl_header_images t4 ON t1.tour_id = t4.package_id
LEFT JOIN tbl_states AS s ON (t1.tour_state #> array[s.state_code])
LEFT JOIN tbl_destinations AS o ON (t1.tour_destination #> array[o.id])
, unnest(t4.image_names) AS _image_names
WHERE t1.tour_status = 1
GROUP BY 1,7
ORDER BY view_count ASC LIMIT 6
alternatively I'd go with subselect:
SELECT t1.*,
(SELECT image_names[1] FROM tbl_header_images WHERE package_id = t1.tour_id) AS image_url
FROM t1, t2, t3
WHERE ...

Using select case on a left join?

I have used a left join on two of my tables. Now I want to use case to identify the records from my left table who don't have a match in the right table. Such records exist and have a null value in the 'id_zeus' column of my join, however when I execute the case, it is as these fields don't exist. Where am I going wrong ? I get "Present" in all my column Disturbance. I am using Oracle SQL developer.
SELECT
CASE DP.ID_PRB
WHEN NULL
THEN 'Absence'
ELSE 'Present' END as Disturbance,
FROM
FIRE.WSITE WI
LEFT JOIN
(SELECT DISTINCT
DPL.ID_PERT as ID_PRB
FROM FIRE.DEPPLAN DPL
GROUP BY DPL.ID_PERT
) DPL
ON WI.ID_PERT = DP.ID_PERT
What is const? You don't seem to need it. The SELECT DISTINCT and GROUP BY are redundant, so use only one of them. And your alias on the subquery is incorrect.
But your problem is the comparison to NULL. It doesn't even match when doing a comparison as you are doing in CASE. You need to use IS NULL:
SELECT (CASE WHEN DP.ID_PRB IS NULL THEN 'Absence' ELSE 'Present'
END) as Disturbance,
FROM FIRE.WSITE WI LEFT JOIN
(SELECT DISTINCT DPL.ID_PERT as ID_PRB
FROM FIRE.OSI_DEVIATION_PLANS DP
) DP
ON WI.ID_PERT = DP.ID_PERT;
This query would commonly be written as:
SELECT (CASE WHEN NOT EXISTS (SELECT 1
FROM FIRE.OSI_DEVIATION_PLANS DP
WHERE WI.ID_PERT = DP.ID_PERT
)
THEN 'Absence' ELSE 'Present'
END) as Disturbance,
FROM FIRE.WSITE WI ;
This offers more opportunities for optimization.

Eliminating "not in" for SQL command

I would like to take a sample of an Oracle table, but not include entries from another table. I have a query that currently works, but I'm pretty sure it will blow-up when the sub-select gets more than 1000 records.
select user_key from users sample(5)
where active_flag = 'Y'
and user_key not in (
select user_key from user_validation where validation_state <> 'expired'
);
How could this be re-written without the not in. I thought of using minus, but then my sample size would keep going down as new entries were added to the user_validation table.
You can do this with a left outer join:
select *
from (select u.user_key,
count(*) over () as numrecs
from users u left outer join
user_validation uv
on u.user_key = uv.user_key and
uv.validation_state <> 'expired'
where u.active_flag = 'Y' and uv.user_key is null
) t
where rownum <= numrecs * 0.05
You are using the sample clause. It is not clear if you just want the non-matches in the 5% you choose or if you want 5% of the data that is non-matches. This is the latter.
EDIT: Added example based on author's comment:
select user_key from (
select u.user_key, row_number() over (order by dbms_random.value) as randval
from users u
left outer join user_validation uv
on u.user_key = uv.user_key
and uv.validation_state <> 'expired'
where u.active_flag = 'Y'
and uv.user_key is null
) myrandomjoin where randval <=100;
select us.user_key
from users us -- sample(5)
where us.active_flag = 'Y'
and NOT EXISTS (
SELECT *
from user_validation nx
where nx.user_key = us.user_key
AND nx.validation_state <> 'expired'
);
BTW: I commented-out the sample(5) because I don't know what it means. (I strongly believe that it is not relevant, though)
select u.user_key from users u, user_validation uv
where u.active_flag = 'Y'
and u.user_key=uv.user_key
uv.validation_state= 'expired';
This was a double negation query, x not in list of non expired ids, which is equivalent to x is in the list of expired IDs, which is what I did, in addition to changing the subquery to a join.