Optimizing code with multple conditions on multiple tables? - sql

I want to check whether these customers have LEAD action or SELL action which both stay in another tables. However, It takes like forever to finish it.
create table ct_nguyendang.visitor
as
select user_id, updated_at::date,
case
when user_id in (select distinct d_visitor_id from xiti.lead_detail) then 'lead'
else 'None'
end as lead_action,
case
when user_id in (select distinct account_id from ct_nguyendang.daily_listor) then 'sell'
else 'None'
end as sell_action

I think you can use union all and aggregation:
select user_id, max(is_lead) as has_lead, max(is_sale) as has_sale
from ((select d_visitor_id as user_id, 1 as is_lead, 0 as is_sale
from xiti.lead_detail
) union all
(select account_id, 0, 1
from ct_nguyendang.daily_listor
)
) ls
group by user_id;
If you have a table of users, then you can use correlated subqueries:
select u.*,
(case when exists (select 1
from xiti.lead_detail l
where u.user_id = l.d_visitor_id
)
then 1 else 0
end) as has_lead,
(case when exists (select 1
from ct_nguyendang.daily_listor s
where u.user_id = s.account_id
)
then 1 else 0
end) as has_sale
from users u;
Note that I prefer using 1 for "true" and 0 for "false". Of course, you can use string values if you prefer.
To optimize this query, you want indexes on xiti.lead_detail(d_visitor_id) and ct_nguyendang.daily_listor(account_id).

Related

Aggregate Function on an Expression Containing A Subquery

With the following t-sql query:
select u.userid
into #temp
from user u
where u.type = 1;
select top 50
contentid,
count(*) as all_views,
sum(case when hc.userid in (select userid from #temp) then 1 else 0 end) as first_count,
sum(case when hc.userid in (40615, 40616) then 1 else 0 end) as another_count
from hitcounts hc
inner join user u on u.userid = hc.userid
group by hc.contentid
order by count(*) desc;
I get an error message
Cannot perform an aggregate function on an expression containing an aggregate or a subquery.
However, if just include the column 'another_count' (with the hard-coded list of identifiers), everything works as I expected. Is there a way I should go about only getting the count for userids contained within a subquery? I plan to have multiple columns, each counting up a set/subquery of different userids.
Performance is not a concern at this point and I appreciate any guidance.
You don't need a temporary table for this purpose. Just use a conditional aggregation:
select top 50 contentid,
count(*) as all_views,
sum(case when u.type = 1 then 1 else 0 end) as first_count,
sum(case when hc.userid in (40615, 40616) then 1 else 0 end) as another_count
from hitcounts hc join
user u
on u.userid = hc.userid
group by hc.contentid
order by count(*) desc;

CASE or IF statement in WHERE clause

How can I use CASE statement or IF statement in WHERE clause ?
I am trying to apply a check on the basis of COUNT
SELECT * FROM sometable
WHERE CASE WHEN (SELECT COUNT(*) FROM sometable s WHERE SP = 2 AND sometable.id = s.id) > 2 THEN sometable.SP IS NOT NULL END
So basically if the count of rows is more than 1 it should apply IS NOT NULL condition else it should not.
Your logic suggests something like:
SELECT s.*
FROM (SELECT s.*,
SUM(CASE WHEN sp = 2 THEN 1 ELSE 0 END) OVER (PARTITION BY id) as cnt_2
FROM sometable s
) s
WHERE cnt_2 <= 2 OR s.sp is not null;
That seems equivalent. The logic doesn't seem particularly useful though.

sql case statement IN with group by

I have a 2 column table with the columns : "user_name" and "characteristic". Each user_name may appear multiple times with a different characteristic.
The values in characteristic are:
Online
Instore
Account
Email
I want to write a sql statement that goes like this - but obviously this isn't working:
SELECT user_name,
case
when characteristic in ("online","instore") then 1
else 0
END as purchase_yn,
case
when characteristic in ("online","instore") and
characteristic in ("email",'account') then 1
else 0
END as purchaser_with_account
FROM my_table
GROUP BY user_name;
Essentially the first is a flag where I check for the presence of either value for that user_name.
The Second field is that they meet this criteria AND that they meet the criteria for having either 'email' or 'account'
An example the structure of your data would help better understand what you are trying to accomplish. But I think I get what you are trying to do.
You have to use an aggregate function in order to use a group by.
Something like SUM or AVG.
But you need first to build a pivot of your data and then you could use that pivot to check for your criterias:
This would create a table pivot that shows for each record what criterias are met:
SELECT
user_name,
case when characteristic = "online" then 1 else 0 end as online_yn,
case when characteristic = "instore" then 1 else 0 end as instore_yn,
case when characteristic = "account" then 1 else 0 end as account_yn,
case when characteristic = "email" then 1 else 0 end as email_yn,
FROM my_table
Now what you might wanted to do is to create an averaged version of these entries grouped by user_name and use those averages to create the fields you wanted. For that you need to use the same statement created earlier as an inline table :
Select
user_name,
case when avg(online_yn + instore_yn) >= 1 then 1 else 0 end as purchase_yn,
case when avg(online_yn + instore_yn) >= 1 and avg(email_yn + account_yn) >= 1 then 1 else 0 end as purchaser_with_account
From
(SELECT
user_name,
case when characteristic = "online" then 1 else 0 end as online_yn,
case when characteristic = "instore" then 1 else 0 end as instore_yn,
case when characteristic = "account" then 1 else 0 end as account_yn,
case when characteristic = "email" then 1 else 0 end as email_yn,
FROM my_table) avg_table
group by
user_name;
This should help.
It may not be efficient in terms of performance but you'll get what you want.
You just have to enclose the CASE expressions in COUNT aggregates:
SELECT user_name,
COUNT(case when characteristic in ("online","instore") then 1 END) as purchase_yn,
COUNT(case when characteristic in ("email",'account') then 1 END) as user_with_account
FROM my_table
GROUP BY user_name
If purchase_yn > 0 then you first flag is set. If purchase_yn > 0 and user_with_account > 0 then you second flag is set as well.
Note: You have to remove ELSE 0 from the CASE expressions because COUNT takes into account all not null values.
You haven't mentioned a specific RDBMS, but if SUM(DISTINCT ...) is available the following is quite nice:
SELECT
username,
SUM(DISTINCT
CASE
WHEN characteristic in ('online','instore') THEN 1
ELSE 0
END) AS purchase_yn,
CASE WHEN (
SUM(DISTINCT
CASE
WHEN characteristic in ('online','instore') THEN 1
WHEN characteristic in ('email','account') THEN 2
ELSE 0 END
)
) = 3 THEN 1 ELSE 0 END as purchaser_with_account
FROM
my_table
GROUP BY
username
If I correctly understand, if user have 'online' or 'instore', then for this user you want 1 as purchase_yn column, and if user also have 'email' or 'account', then 1 as purchaser_with_account column.
If this is correct, then one way is:
with your_table(user_name, characteristic) as(
select 1, 'online' union all
select 1, 'instore' union all
select 1, 'account' union all
select 1, 'email' union all
select 2, 'account' union all
select 2, 'email' union all
select 3, 'online'
)
-- below is actual query:
select your_table.user_name, coalesce(max(t1.purchase_yn), 0) as purchase_yn, coalesce(max(t2.purchaser_with_account), 0) as purchaser_with_account
from your_table
left join (SELECT user_name, 1 as purchase_yn from your_table where characteristic in('online','instore') ) t1
on your_table.user_name = t1.user_name
left join (SELECT user_name, 1 as purchaser_with_account from your_table where characteristic in('email', 'account') ) t2
on t1.user_name = t2.user_name
group by your_table.user_name

How to add where condition if result count is greater than one

I want to build SQL query that returns unique id.
My problem is that i need to add another condition to query if i have more than one result.
select u.id
from users u
where u.id in ('1','2','3')
and u.active = 'Y'
if i get more than one result i need to add:
and u.active_contact = 'Y'
I tried to build this query
select * from (
select u.id, count(u.id) as results
from users u
where u.id in ('1','2','3')
and u.active = 'Y'
group by u.id
) tab
If(tab.results > 1) then
where tab.u.active_contact = 'Y'
end
Thanks in advanced.
Hope i explained my self good enough.
Here's a different approach:
SELECT id
FROM (SELECT id, (CASE WHEN active ='Y' THEN 1 ELSE 0 END) + (CASE WHEN active_contact ='Y' THEN 1 ELSE 0 END) as actv FROM users ORDER BY actv DESC)
WHERE actv > 0
LIMIT 1
The subquery adds a column which aggregates active and active_contact. The main SELECT then optimizes the combination of these two fields, requiring at least one of them. I believe this provides the intended result.
Among the possible ways to solve this, here are two.
1) Use the active_contact id. If there is none use another id.
select coalesce( max(case when active_contact = 'Y' then id end), max(id) ) as id
from users
where id in ('1','2','3')
and active = 'Y';
2) Sort with active_contact coming first. Then get the first record.
select id
from
(
select id
from users
where id in ('1','2','3')
and active = 'Y'
order by case when active_contact = 'Y' then 1 else 2 end
) where rownum = 1;
A method using Analytic functions
SELECT id
FROM (SELECT u.id
, u.active_contact
, count(*) OVER () actives
FROM users u
WHERE u.id IN ('1','2','3')
AND u.active = 'Y')
WHERE ( actives = 1
OR ( actives > 1
AND active_contact = 'Y'))
If there is more than one record where active = 'Y' AND active_contact = 'Y' it will return them all. If only one of these is required you will need to identify the criteria for choosing that one.

SQL Aggreate Functions

I have table which list a number of cases and assigned primary and secondary technicians. What I am trying to accomplish is to aggregate the number of cases a technician has worked as a primary and secondary tech. Should look something like this...
Technician Primary Secondary
John 4 3
Stacy 3 1
Michael 5 3
The table that I am pulling that data from looks like this:
CaseID, PrimaryTech, SecondaryTech, DOS
In the past I have used something like this, but now my superiors are asking for the number of secondary cases as well...
SELECT PrimaryTech, COUNT(CaseID) as Total
GROUP BY PrimaryTech
I've done a bit of searching, but cant seem to find the answer to my problem.
Select Tech,
sum(case when IsPrimary = 1 then 1 else 0 end) as PrimaryCount,
sum(case when IsPrimary = 0 then 1 else 0 end) as SecondaryCount
from
(
SELECT SecondaryTech as Tech, 0 as IsPrimary
FROM your_table
union all
SELECT PrimaryTech as Tech, 1 as IsPrimary
FROM your_table
) x
GROUP BY Tech
You can group two subqueries together with a FULL JOIN as demonstrated in this SQLFiddle.
SELECT Technician = COALESCE(pri.Technician, sec.Technician)
, PrimaryTech
, SecondaryTech
FROM
(SELECT Technician = PrimaryTech
, PrimaryTech = COUNT(*)
FROM Cases
WHERE PrimaryTech IS NOT NULL
GROUP BY PrimaryTech) pri
FULL JOIN
(SELECT Technician = SecondaryTech
, SecondaryTech = COUNT(*)
FROM Cases
WHERE SecondaryTech IS NOT NULL
GROUP BY SecondaryTech) sec
ON pri.Technician = sec.Technician
ORDER By Technician;
SELECT COALESCE(A.NAME, B.NAME) AS NAME, CASE WHEN A.CASES IS NOT NULL THEN A.CASES ELSE 0 END AS PRIMARY_CASES,
CASE WHEN B.CASES IS NOT NULL THEN B.CASES ELSE 0 END AS SECONDARY_CASES
FROM
(
SELECT COUNT(*) AS CASES, PRIMARYTECH AS NAME FROM YOUR_TABLE
GROUP BY PRIMARYTECH
) AS A
FULL OUTER JOIN
(
SELECT COUNT(*) AS CASES, SECONDARYTECH AS NAME FROM YOUR_TABLE
GROUP BY SECONDARYTECH
) AS B
ON A.NAME = B.NAME