Select minimal count of grouping by result without window functions - sql

As always, want to do with one sql request. Have a table of send attempts:
ID TIMESTAMP TASK_ID
1 2019-01-30 15:29:38 1
2 2019-01-30 15:29:39 1
3 2019-01-30 15:29:40 2
4 2019-01-30 15:29:41 3
Task table:
ID EMAIL
1 boxOne#test.com
2 boxOne#test.com
3 boxTwo#test.com
Purpose is to get task ids for unique emails that has minimal count of attempts (in our case is 2 and 3). Problem number one is that i want make some tests using H2 that not supports window functions. Problem two is that several tasks can have same email.
Tried this :
SELECT TASK.id, TASK.EMAIL, count(att.TASK_ID)
FROM TASK
JOIN ATTEMPTS on TASK.id = ATTEMPTS.TASK_ID
GROUP BY ATTEMPTS.TASK_ID
and have such result:
TASK.id EMAIL count(TASK.id)
1 boxOne#test.com 2
2 boxOne#test.com 1
3 boxTwo#test.com 1
but i need minimal count for each unique email like this:
TASK.id EMAIL count(TASK.id)
2 boxOne#test.com 1
3 boxTwo#test.com 1
min(count(TASK.id)) didn't work for me result is always zero.
Can this be done without window functions or i should accept temp result and process it in my code ?

Try to use a correlated subquery, HAVING and ALL
SELECT t.id, t.email, count(a.task_ID) cnt
FROM task t
JOIN attempts a on t.id = a.task_ID
GROUP BY t.id, t.email
HAVING count(a.task_ID) <= ALL
(
SELECT count(a.task_ID)
FROM task t2
JOIN attempts a on t2.id = a.task_ID
WHERE t2.email = t.email
GROUP BY t2.id
)
DEMO

you can try by using correlated subquery
select distinct t1.* from
(
SELECT TASK.id, TASK.EMAIL, count(att.TASK_ID) cnt
FROM TASK
JOIN ATTEMPTS on TASK.id = ATTEMPTS.TASK_ID
group by TASK.id, TASK.EMAIL
) t1 where t1.cnt= (select min(cnt) from
(SELECT TASK.id, TASK.EMAIL, count(att.TASK_ID) cnt
FROM TASK
JOIN ATTEMPTS on TASK.id = ATTEMPTS.TASK_ID
group by TASK.id, TASK.EMAIL
) t2 where t2.EMAIL=t1.EMAIL)

Related

SQL Query to fetch a user without an associated service

I have the below data in a table. I am trying to fetch all the "Modem" users, who do not have an associated telephone service.
UserID DeviceNumber DeviceType DeviceRole
1 A Telephone SingleUser
1 A Modem MultiUser
1 B Modem MultiUser
2 C Telephone SingleUser
2 C Modem MultiUser
2 D Modem MultiUser
select distinct t.* from table t
join table t1 on t1.UserID= v.UserID
and t1.DeviceNumber <> t.DeviceNumber
and t.DeviceType = 'Modem';
I want to see DeviceNumber B and D in my output. But above query is not returning expected results.
Hmmm . . . One method would be:
select t.*
from t
where t.devicetype = 'Modem' and
not exists (select 1
from t t2
where t2.userid = t.userid and t2.devicenumber = t.devicenumber and
t2.devicetype = 'Telephone'
);
You can do by count. Here is the demo.
select
UserID,
DeviceNumber,
DeviceType,
DeviceRole
from
(
select
yt.*,
count(*) over (partition by DeviceNumber) as cnt
from yourTable yt
) val
where cnt = 1
and DeviceType = 'Modem'
Output:

select distinct from join

My Tables look like this
Table 1 Table 2
Users Options
id name id user_id option
------- --- -------- --------
1 Donald 1 1 access1
2 John 2 1 access2
3 Bruce 3 1 access3
4 Paul 4 2 access1
5 Ronald 5 2 access3
6 Steve 6 3 access1
Now, i want to select join these to find a user which has only access1
If i do something like
select t1.id,t1.name,t2.id,t2.user_id,t2.option
from table1 t1, table2 t2
where t1.id=t2.user_id
and option='access1';
This does not give me unique results, as in the example i need only user_id=3 my data has has these in hundreds
I also tried something like
select user_id from table2 where option='access1'
and user_id not in (select user_id from table2 where option<>'access1')
There have been other unsuccessful attempts too but i am stuck here
You can do this using a EXISTS subquery (technically, a left semijoin):
SELECT id, name
FROM table1
WHERE EXISTS(
SELECT * FROM table2
WHERE table1.id = table2.user_id
AND table2.option = 'access1'
)
If you want only users that have access1 and not any other access, add NOT EXISTS (a left anti-semi-join; there's a term to impress your colleagues!):
AND NOT EXISTS (
SELECT * FROM table2
WHERE table1.id = table2.user_id
AND table2.option <> 'access1'
)
bool_and makes it simple
with users (id,name) as ( values
(1,'donald'),
(2,'john'),
(3,'bruce'),
(4,'paul'),
(5,'ronald'),
(6,'steve')
), options (id,user_id,option) as ( values
(1,1,'access1'),
(2,1,'access2'),
(3,1,'access3'),
(4,2,'access1'),
(5,2,'access3'),
(6,3,'access1')
)
select u.id, u.name
from
users u
inner join
options o on o.user_id = u.id
group by 1, 2
having bool_and(o.option = 'access1')
;
id | name
----+-------
3 | bruce
If you want the user that has only access1, I would use aggregation:
select user_id
from table2
group by user_id
having min(option) = max(option) and min(option) = 'access1';
WITH users(id,name) AS ( VALUES
(1,'Donald'),
(2,'John'),
(3,'Bruce'),
(4,'Paul'),
(5,'Ronald'),
(6,'Steve')
), options(id,user_id,option) AS ( VALUES
(1,1,'access1'),
(2,1,'access2'),
(3,1,'access3'),
(4,2,'access1'),
(5,2,'access3'),
(6,3,'access1')
), user_access_count AS (
SELECT op.user_id,count(op.option) AS access_count
FROM options op
WHERE EXISTS(
SELECT 1 FROM options
WHERE option = 'access1'
)
GROUP BY op.user_id
)
SELECT u.id,u.name
FROM users u
INNER JOIN user_access_count uac ON uac.user_id = u.id
WHERE uac.access_count = 1;

Over lapping in SQL

I have a table with following data:
User# App
1 A
1 B
2 A
2 B
3 A
I want to know overlapping between Apps by distinct Users, so my end result with look like this
App1 App2 DistinctUseroverlapped
A A 3
A B 2
B B 2
So what result means is there are 3 users using app A only , there are 2 users who use App A and App B both , and there are 2 users who use App B only.
Remember there lot of app and users how can I do this in SQL?
My solution starts by generating all possible pairs of applications that are of interest. This is the driver subquery.
It then joins in the original data for each of the apps.
Finally, it uses count(distinct) to count the distinct users that match between the two lists.
select pairs.app1, pairs.app2,
COUNT(distinct case when tleft.user = tright.user then tleft.user end) as NumCommonUsers
from (select t1.app as app1, t2.app as app2
from (select distinct app
from t
) t1 cross join
(select distinct app
from t
) t2
where t1.app <= t2.app
) pairs left outer join
t tleft
on tleft.app = pairs.app1 left outer join
t tright
on tright.app = pairs.app2
group by pairs.app1, pairs.app2
You could move the conditional comparison in the count to the joins and just use count(distinct):
select pairs.app1, pairs.app2,
COUNT(distinct tleft.user) as NumCommonUsers
from (select t1.app as app1, t2.app as app2
from (select distinct app
from t
) t1 cross join
(select distinct app
from t
) t2
where t1.app <= t2.app
) pairs left outer join
t tleft
on tleft.app = pairs.app1 left outer join
t tright
on tright.app = pairs.app2 and
tright.user = tleft.user
group by pairs.app1, pairs.app2
I prefer the first method because it is more explicit on what is being counted.
This is standard SQL, so it should work on Vertica.
this works in vertica 6
with tab as
( select 1 as user,'A' as App
union select 1 as user,'B' as App
union select 2 as user,'A' as App
union select 2 as user,'B' as App
union select 3 as user,'A' as App
)
, apps as
( select distinct App from tab )
select apps.app as APP1,tab.app as APP2 ,count(distinct tab.user) from tab,apps
where tab.app>=apps.app
group by 1,2
order by 1

CASE for joining sql tables

I need a help on sql database side. And i have
table 1 : ENTITY_TYPE
entity_type_id entity_name
1 Task
2 Page
3 Project
4 Message
5 User
and table 2 : MESSAGE , that contains message from each entity values like
message_id entity_type owner_tableid message
1 1 12 A message on task level
2 3 14 A message on project level
and I want select these message according to each entity type and details from its owner table using 'owner_tableid' ie a query like....
select * from MESSAGE JOIN
case entity_type when 1 then taskTable
when 2 then pageTable
when 3 then projectTable
when 4 then MessageTable
when 5 then UserTable
Which is best method to solve this issue on single procedure. Any idea ?? Now I am using IF clause for each entity...
You can't parameterise the tables involved in a query (so you can't put a table name in a variable and expect that to be used either).
One way to do it is as a chain of left joins:
select
* /* TODO - Pick columns */
from
MESSAGE m
left join
taskTable tt
on
m.entity_type = 1 and
m.owner_entity_id = tt.id
left join
pageTable pt
on
m.entity_type = 2 and
m.owner_entity_id = pt.id
left join
projectTable prt
on
m.entity_type = 3 and
m.owner_entity_id = prt.id
left join
MessageTable mt
on
m.entity_type = 4 and
m.owner_entity_id = mt.id
left join
UserTable ut
on
m.entity_type = 5 and
m.owner_entity_id = ut.id
If you want values from these tables to appear in a single column in the result, use a COALESCE across all of the values, e.g.
COALESCE(tt.Value,pt.Value,prt.Value,mt.Value,ut.Value) as Value
Use Union Clause with your individual entity_type
SELECT * FROM Message
JOIN pageTable ON ....
WHERE entity_type = 1
UNION ALL
..........
entity_type = 2
UNION ALL
..........
entity_type = 3
Select ...
From Message
Join (
Select 1 As entity_type, id
From taskTable
Union All
Select 2, id
From pageTable
Union All
Select 3, id
From projectTable
Union All
Select 4, id
From messageTable
Union All
Select 5, id
From userTable
) As Z
On Z.entity_type = Message.entity_type
And Z.id = Message.owner_tableid
If you need to return several entity_types details in one query, than UNION might help:
SELECT interesting_columns FROM Message
JOIN pageTable ON (joinPredicate)
WHERE entity_type = 1
UNION ALL
SELECT interesting_columns FROM Message
JOIN pageTable ON (joinPredicate)
WHERE entity_type = 2
-- ...
But if you only need details of certain entity_type than you original solution with IF would be much better.

SQL JOIN Statement

Lets say I have a table e.g
Request No. Type Status
---------------------------
1 New Renewed
and then another table
Action ID Request No LastUpdated
------------------------------------
1 1 06-10-2010
2 1 07-14-2010
3 1 09-30-2010
How can I join the second table with the first table but only get the latest record from the second table(e.g Last Updated DESC)
SELECT T1.RequestNo ,
T1.Type ,
T1.Status,
T2.ActionId ,
T2.LastUpdated
FROM TABLE1 T1
JOIN TABLE2 T2
ON T1.RequestNo = T2.RequestNo
WHERE NOT EXISTS
(SELECT *
FROM TABLE2 T2B
WHERE T2B.RequestNo = T2.RequestNo
AND T2B.LastUpdated > T2.LastUpdated
)
Using aggregates:
SELECT r.*, re.*
FROM REQUESTS r
JOIN REQUEST_EVENTS re ON re.request_no = r.request_no
JOIN (SELECT t.request_no,
MAX(t.lastupdated) AS latest
FROM REQUEST_EVENTS t
GROUP BY t.request_no) x ON x.request_no = re.request_no
AND x.latest = re.lastupdated
Using LEFT JOIN & NOT EXISTS:
SELECT r.*, re.*
FROM REQUESTS r
JOIN REQUEST_EVENTS re ON re.request_no = r.request_no
WHERE NOT EXISTS(SELECT NULL
FROM REQUEST_EVENTS re2
WHERE re2.request_no = r2.request_no
AND re2.LastUpdated > re.LastUpdated)
SELECT *
FROM REQUEST, ACTION
WHERE REQUEST.REQUESTNO = ACTION.REQUESTNO --Joining here
AND ACTION.LastUpdated = (SELECT MAX(LastUpdated) FROM ACTION WHERE REQUEST.REQUESTNO = ACTION.REQUESTNO);
A sub-query is used to get the last updated record's date and matches against itself to prevent the other records being joined.
Granted, depending on how precise the LastUpdated field is, it can have problems with two records being updated on the same date, but that is a problem encountered in any other implementation, so the precision would have to be increased or some other logic would have to be in place or another distinguishing characteristic to prevent multiple rows being returned.
SELECT r.RequestNo, r.Type, r.Status, a.ActionID, MAX(a.LastUpdated)
FROM Request r
INNER JOIN Action a ON r.RequestNo = a.RequestNo
GROUP BY r.RequestNo, r.Type, r.Status, a.ActionID
We can use the operation Top 1 with ORDER BY clause. For instance, if your tables are RequestTable(ID,Type,Status) and ActionTable(ActionID,RequestID,LastUpdated), the query will be like this:
Select Top 1 rq.ID, rq.Status, at.ActionID
From RequestTable as rq
JOIN ActionTable as at ON rq.ID = at.RequestID
Order by at.LastUpdated DESC