Left outer join of 3 tables - sql

I am trying to get the count of distinct people of the shaded region.
Table structure is as follows:
customer key
A234 1
A345 4
A12 5
A989 6
HIVE Query:
select count(distinct(a.customer))
from (
select *
from cust
where key in (1,2,3)) c
left outer join (
select *
from cust
where key in (4,5)) a on a.customer= c.customer where c.customer is null
join
(select *
from cust
where key in (6,7,8,9)) d on c.customer = d.customer and d.customer is null;
Error:
missing EOF at 'join' near 'null'

You have a syntax problem because where follows the from clause, and a given select has only one where.
I would just use group by and having. To get the customers:
select c.customer
from cust c
group by c.customer
having sum(case when key in (1, 2, 3) then 1 else 0 end) > 0 and
sum(case when key in (4, 5, 6, 7, 8, 9) then 1 else 0 end) = 0;
You can then count them with a subquery:
select count(*)
from (select c.customer
from cust c
group by c.customer
having sum(case when key in (1, 2, 3) then 1 else 0 end) > 0 and
sum(case when key in (4, 5, 6, 7, 8, 9) then 1 else 0 end) = 0
) c

Related

How to change value if multiple duplicate rows more then 3 in Oracle SQL

I am trying to select if A.POLICY_NO or A.POLICY_TITLE values duplicate rows more than 3, want to change C.SMALL_CATEGORY_TITLE values to "Z" and also want to cahge B.SMALL_CATEGORY_SID values to null.
I used 3 tables that YIP.YOUTH_POLICY as A, YIP.YOUTH_POLICY_AREA as B and YIP.YOUTH_SMALL_CATEGORY as C. the PK and FK is POLICY_NO.
ForExample if the table
A.POLICY_NO
B.SMALL_CATEGORY_SID
C.SMALL_CATEGORY_TITLE
A.POLICY_TITLE
1
80
A
VALUE1
1
90
B
VALUE1
1
95
C
VALUE1
2
80
A
VALUE2
2
90
B
VALUE2
2
95
C
VALUE2
3
80
A
VALUE3
3
90
B
VALUE3
4
80
A
VALUE4
I wnat to select like
A.POLICY_NO
B.SMALL_CATEGORY_SID
C.SMALL_CATEGORY_TITLE
A.POLICY_TITLE
1
NULL
Z
VALUE1
2
NULL
Z
VALUE2
3
80
A
VALUE3
3
90
B
VALUE3
4
80
A
VALUE4
this is the query select except when duplicate values more then 3,
SELECT
A.POLICY_NO
, B.SMALL_CATEGORY_SID
, C.SMALL_CATEGORY_TITLE
, A.POLICY_TITLE
, COUNT(*) OVER() AS TOTAL_COUNT
FROM
YIP.YOUTH_POLICY A
LEFT JOIN
YIP.YOUTH_POLICY_AREA B
ON A.POLICY_NO = B.POLICY_NO
LEFT JOIN
YIP.YOUTH_SMALL_CATEGORY C
ON B.SMALL_CATEGORY_SID = C.SMALL_CATEGORY_SID
WHERE A.POLICY_NO IN (SELECT
F.POLICY_NO
FROM YIP.YOUTH_POLICY F
LEFT JOIN
YIP.YOUTH_POLICY_AREA G
ON F.POLICY_NO = G.POLICY_NO
LEFT JOIN
YIP.YOUTH_SMALL_CATEGORY H
ON G.SMALL_CATEGORY_SID = H.SMALL_CATEGORY_SID
GROUP BY F.POLICY_NO
HAVING COUNT(*) < 3)
ORDER BY A.POLICY_NO;
and I was tried to change C.SMALL_CATEGORY_TITLE values when POLICY_NO values duplicated more then 3
SELECT
A.POLICY_NO
--, B.SMALL_CATEGORY_SID
, SUM(CASE WHEN C.SMALL_CATEGORY_TITLE IN (SELECT
F.POLICY_NO
FROM YIP.YOUTH_POLICY F
LEFT JOIN
YIP.YOUTH_POLICY_AREA G
ON F.POLICY_NO = G.POLICY_NO
LEFT JOIN
YIP.YOUTH_SMALL_CATEGORY H
ON G.SMALL_CATEGORY_SID = H.SMALL_CATEGORY_SID
GROUP BY F.POLICY_NO
HAVING COUNT(*) > 2) THEN 1 ELSE NULL END) AS 'Z'
, A.POLICY_TITLE
, COUNT(*) OVER() AS TOTAL_COUNT
FROM
YIP.YOUTH_POLICY A
LEFT JOIN
YIP.YOUTH_POLICY_AREA B
ON A.POLICY_NO = B.POLICY_NO
LEFT JOIN
YIP.YOUTH_SMALL_CATEGORY C
ON B.SMALL_CATEGORY_SID = C.SMALL_CATEGORY_SID
ORDER BY A.POLICY_NO;
I got SQL Error [42000]: JDBC-8006:Missing FROM keyword. ¶at line 17, column 59 of null:¶ HAVING COUNT(*) > 2) THEN 1 ELSE NULL END) AS 'Z'¶
Is there any way to fix it? I was thinking more then 5 hours but i could't fix it
The trick is to use the windowing function COUNT(*) OVER to get a count over the entire rowset in order to later make decisions about each individual row. You can finally collapse it down with DISTINCT or GROUP BY.
SELECT DISTINCT
policy_no,
CASE WHEN (policy_count >= 3 OR policy_title_count>= 3) THEN NULL
ELSE small_category_sid
END AS small_category_sid,
CASE WHEN (policy_count >= 3 OR policy_title_count>= 3) THEN 'Z'
ELSE small_category_title
END AS small_category_title,
policy_title
FROM (SELECT x.*,
COUNT(*) OVER (PARTITION BY policy_no) policy_count,
COUNT(*) OVER (PARTITION BY policy_title) policy_title_count
FROM yip.youth_policy x)

Oracle SQL: How to select only ID‘s which are member in specific groups?

I want to select only those ID‘s which are in specific groups.
For example:
ID GroupID
1 11
1 12
2 11
2 12
2 13
Here I want to select the ID's which are in the groups 11 and 12 but in no other groups.
So the result should show just the ID 1 and not 2.
Can someone provide a SQL for that?
I tried it with
SELECT ID FROM table
WHERE GroupID = 11 AND GroupID = 12 AND GroupID != 13;
But that didn't work.
You can use aggregation:
select id
from mytable
group by id
having min(groupID) = 11 and max(groupID) = 12
This having condition ensures that the given id belongs to groupIDs 11 and 12, and to no other group. This works because 11 and 12 are sequential numbers.
Other options: if you want ids that belong to group 11 or 12 (not necessarily both), and to no other group, then:
having sum(case when groupId in (11, 12) then 1 end) = count(*)
If numbers are not sequential, and you want ids in both groups (necessarily) and in no other group:
having
max(case when groupID = 11 then 1 end) = 1
and max(case when groupID = 12 then 1 end) = 1
and max(case when groupID in (11, 12) then 0 else 1 end) = 0
SELECT t.id FROM table t
where exists(
SELECT * FROM table
where group = 11
and t.id = id
)
and exists(
SELECT * FROM table
where group = 12
and t.id = id
)
and not exists(
SELECT * FROM table
where group = 13
and t.id = id
)
group by t.id
One method is conditional aggregation:
select id
from t
group by id
having sum(case when groupid = 1 then 1 else 0 end) > 0 and
sum(case when groupid = 2 then 1 else 0 end) > 0 and
sum(case when groupid in (1, 2) then 1 else 0 end) = 0 ;
You can use GROUP BY with HAVING and a conditional COUNT:
SELECT id
FROM table_name
GROUP BY ID
HAVING COUNT( CASE Group_ID WHEN 11 THEN 1 END ) > 0
AND COUNT( CASE Group_ID WHEN 12 THEN 1 END ) > 0
AND COUNT( CASE WHEN Group_ID NOT IN ( 11, 12 ) THEN 1 END ) = 0
Or you can use collections:
CREATE TYPE int_list IS TABLE OF NUMBER(8,0);
and:
SELECT id
FROM table_name
GROUP BY id
HAVING int_list( 11, 12 ) SUBMULTISET OF CAST( COLLECT( group_id ) AS int_list )
AND CARDINALITY( CAST( COLLECT( group_id ) AS int_list )
MULTISET EXCEPT int_list( 11, 12 ) ) = 0
(Using collections has the advantage that you can pass the collection of required values as a single bind parameter whereas using conditional aggregation is probably going to require dynamic SQL if you want to pass a variable number of items to the query.)
Both output:
| ID |
| -: |
| 1 |
db<>fiddle here
Use joins:
SELECT DISTINCT c11.ID
FROM (SELECT ID FROM WORK_TABLE WHERE GROUPID = 11) c11
INNER JOIN (SELECT ID FROM WORK_TABLE WHERE GROUPID = 12) c12
ON c12.ID = c11.ID
LEFT OUTER JOIN (SELECT ID FROM WORK_TABLE WHERE GROUPID NOT IN (11, 12)) co
ON co.ID = c11.ID
WHERE co.ID IS NULL;
The INNER JOIN between the first two subqueries ensures that rows exist for both GROUPID 11 and 12, and the LEFT OUTER JOIN and WHERE verify that there are no rows for any other GROUPIDs.
dbfiddle here

Getting distinct rows on SQL query with multiple IIF columns

In SQL Server I am creating a view that shows if a record in table CONTACTS has certain tags in a different table TAGS.
This is my query:
SELECT DISTINCT
contacts.ID, contacts.NAME,
IIF(tags.tag = 'A', 1, 0) as A,
IIF(tags.tag = 'B', 1, 0) as B,
IIF(tags.tag = 'C', 1, 0) as C,
IIF(tags.tag = 'D', 1, 0) as D
FROM
contacts
LEFT JOIN
TAGS ON contacts.ID = TAGS.CONTACT_ID
I would like the results like this:
ID NAME A B C D
------------------------------------
1 BOB 1 0 0 1
1 Charlie 1 0 1 0
but I get
ID NAME A B C D
------------------------------------
1 BOB 1 0 0 0
1 BOB 0 0 0 1
1 Charlie 1 0 0 0
1 Charlie 0 0 1 0
Must be something I overlook, but I can't find it.
You need simply use GROUP BY clause + MAX aggreate instead of DISTINCT
SELECT contacts.ID, contacts.NAME
, MAX(IIF(tags.tag = 'A', 1, 0)) as A
, MAX(IIF(tags.tag = 'B', 1, 0)) as B
, MAX(IIF(tags.tag = 'C', 1, 0)) as C
, MAX(IIF(tags.tag = 'D', 1, 0)) as D
FROM contacts LEFT JOIN
TAGS ON contacts.ID = TAGS.CONTACT_ID
group by
contacts.ID, contacts.NAME
As Gordon Linoff says, use of standard SQL case when is more compatible with all the database (postgres, oracle, sql server ...)
IIF(tags.tag = 'A', 1, 0)
is equivalent to
CASE WHEN tags.tag = 'A' THEN 1 ELSE 0 END
Use group by:
SELECT c.ID, c.NAME,
MAX(CASE WHEN t.tag = 'A' THEN 1 ELSE 0 END) as A,
MAX(CASE WHEN t.tag = 'B' THEN 1 ELSE 0 END) as B,
MAX(CASE WHEN t.tag = 'C' THEN 1 ELSE 0 END) as C,
MAX(CASE WHEN t.tag = 'D' THEN 1 ELSE 0 END) as D
FROM contacts c LEFT JOIN
TAGS t
ON c.ID = t.CONTACT_ID
GROUP BY c.ID, c.NAME;
Note changes the query:
Introduced table aliases. These make the query easier to write and to read.
Removed the SELECT DISTINCT, because you really want a GROUP BY.
Changed IIF() to CASE. I see no reason to use a function designed for backward compatibility to MS Access over the SQL standard function.
SELECT contacts.ID, contacts.NAME
, MAX(IIF(tags.tag = 'A', 1, 0)) as A
, MAX(IIF(tags.tag = 'B', 1, 0)) as B
, MAX(IIF(tags.tag = 'C', 1, 0)) as C
, MAX(IIF(tags.tag = 'D', 1, 0) as D
FROM contacts LEFT JOIN
TAGS ON contacts.ID = TAGS.CONTACT_ID
GROUP BY contacts.ID, contacts.NAME

SQL - SUM within subquery

I have the following code that looks at the SalesVol of different products and groups it by transaction_week
SELECT a.transaction_week,
SUM(CASE WHEN record_type IN (6,37,13) THEN quantity ELSE 0 END) as SalesVol
FROM table 1 a
LEFT JOIN table 2 b ON b.Date = a.transaction_date
LEFT JOIN table 3 c ON c.sku = a.product
WHERE series in (62,236,501,52)
GROUP BY a.transaction_week
ORDER BY a.transaction_week
| tw | SalesVol |
| 1 | 4768 |
| 2 | 4567 |
| 3 | 4354 |
| 4 | 4678 |
I want to be able to have multiple subqueries where I change the series numbers for example.
SELECT a.transaction_week,
(SELECT SUM(CASE WHEN record_type IN (6,37,13) THEN quantity ELSE 0 END) as SalesVol
FROM table 1 a
LEFT JOIN table 2 b ON b.Date = a.transaction_date
LEFT JOIN table 3 c ON c.sku = a.product
WHERE series in (62,236,501,52)) as personal care
(SELECT SUM(CASE WHEN record_type IN (6,37,13) THEN quantity ELSE 0 END) as SalesVol
FROM table 1 a
LEFT JOIN table 2 b ON b.Date = a.transaction_date
LEFT JOIN table 3 c ON c.sku = a.product
WHERE series in (37,202,203,456)) as white goods
FROM table 1 a
LEFT JOIN table 2 b ON b.Date = a.transaction_date
LEFT JOIN table 3 c ON c.sku = a.product
GROUP BY a.transaction_week
ORDER BY a.transaction_week
I can't get the subqueries at work as it is giving me the overall sum value and not grouping it by transaction_week
Instead of using subqueries, add series to the condition of the CASE statements:
SELECT a.transaction_week,
sum(CASE WHEN series IN (62,236,501,52) AND record_type IN (6,37,13)
THEN quantity ELSE 0 END) as personal_care,
sum(CASE WHEN series IN (37,202,203,456) AND record_type IN (6,37,13)
THEN quantity ELSE 0 END) as white_goods
FROM table 1 a
LEFT JOIN table 2 b ON b.Date = a.transaction_date
LEFT JOIN table 3 c ON c.sku = a.product
GROUP BY a.transaction_week
ORDER BY a.transaction_week;
You just miss the a.transaction_week in you subquery. The JOIN in outer query is unneccessary.
SELECT a.transaction_week,
(
SELECT SUM(CASE WHEN record_type IN (6,37,13) THEN quantity ELSE 0 END) as SalesVol
FROM table 1 a2
LEFT JOIN table 2 b ON b.Date = a2.transaction_date
LEFT JOIN table 3 c ON c.sku = a2.product
WHERE series in (62,236,501,52) AND a2.transaction_week = a.transaction_week
) as personal care,
(
SELECT SUM(CASE WHEN record_type IN (6,37,13) THEN quantity ELSE 0 END) as SalesVol
FROM table 1 a 2
LEFT JOIN table 2 b ON b.Date = a2.transaction_date
LEFT JOIN table 3 c ON c.sku = a2.product
WHERE series in (37,202,203,456) AND a2.transaction_week = a.transaction_week
) as white goods
FROM table 1 a
GROUP BY a.transaction_week
ORDER BY a.transaction_week
Try this it would work fast as well as up to your requirement:
SELECT a.transaction_week ,
whitegoods.SalesVol AS 'White Goods' ,
personalcare.SalesVol1 AS 'Personal Care'
FROM table1 a
LEFT JOIN table2 b ON b.[Date] = a.transaction_date
LEFT JOIN table3 c ON c.sku = a.product
CROSS APPLY ( SELECT SUM(CASE WHEN record_type IN ( 6, 37, 13 )
THEN quantity
ELSE 0
END) AS SalesVol
FROM table1 a2
WHERE b.[Date] = a2.transaction_date
AND c.sku = a2.product
AND series IN ( 37, 202, 203, 456 )
AND a2.transaction_week = a.transaction_week
) whitegoods
CROSS APPLY ( SELECT SUM(CASE WHEN record_type IN ( 6, 37, 13 )
THEN quantity
ELSE 0
END) AS SalesVol1
FROM table1 a2
WHERE b.[Date] = a2.transaction_date
AND c.sku = a2.product
AND series IN ( 62, 236, 501, 52 )
AND a2.transaction_week = a.transaction_week
) personalcare
GROUP BY a.transaction_week
ORDER BY a.transaction_week
You should use the UNION operator. Please refer to the query below:
select a.transaction_week, SalesVol from
(SELECT a.transaction_week as transaction_week,
SUM(CASE WHEN record_type IN (6,37,13) THEN quantity ELSE 0 END) as SalesVol
FROM table 1 a
LEFT JOIN table 2 b ON b.Date = a.transaction_date
LEFT JOIN table 3 c ON c.sku = a.product
WHERE series in (62,236,501,52)
UNION
SELECT a.transaction_week as transaction_week,
SUM(CASE WHEN record_type IN (6,37,13) THEN quantity ELSE 0 END) as SalesVol
FROM table 1 a
LEFT JOIN table 2 b ON b.Date = a.transaction_date
LEFT JOIN table 3 c ON c.sku = a.product
WHERE series in (37,202,203,456)
) AS tbl1
GROUP BY tbl1.transaction_week
ORDER BY tbl1.transaction_week

Grouping and counting issue

I have a table like this:
Id,Code, (some more columns)
1, c
1, a
1, b
1, b
1, b
2, a -- the desired row
3, b
3, c
3, a
3, a
I want to get one Id (or all) which have only been associated with 'a' and not 'b' and 'c'. How do I do this ?
What i tried just now:
select *
from
(
select Id, count(case when Code='a' then 0 else 1 end) c
from tbl
group by Id
)
where c = 0
Why does this not work ?
This will get you the list of Id values which are associated only with codes of 'a'.
select Id
from tbl
group by Id
having max(case when Code='a' then 0 else 1 end) = 0
See this fiddle for a live demo.