sum case when with subquery - sql

I need to find the amount of customers in specific segment
and the amount of customers that have valid mail or cellphone and specific brand from specific segment.
Tables are:
"source" that holds customers+brands
"customers" that holds customers, email, cellphone
"segment" that holds email (that ref to customer)
I have some constrains: I build matrix query so I need to use the case when structure.
This is what I wrote - not good
SELECT
count(*) as CountCustomers,
sum(case when Customerid in
(select distinct customerid from source
where brand like '%abc%')
and (Cellphone not in ('0' ,'-1') or email not like '%#NoEmail.com')
then 1 else 0 end
) as EmailOrSms
from customer
where email in(select distinct email from segment where p=1)
My problem is in the case when I don't know how to write it correctly.
This is the error:
"Cannot perform an aggregate function on an expression containing an aggregate or a subquery"
Hope you understand my question and could help me
thank you very much for your time and effort.

Maybe you can move the subquery to a left join, so there are no issues with aggregate clauses
select
count (*) as CountCustomers,
sum(case when source.customerid is null then 0 else 1 end) as EmailOrSms
from customer
left join source on source.customerid = customer.Customerid and brand like '%abc'
and (Cellphone not in ('0' ,'-1') or email not like '%#NoEmail.com')
where email in(select distinct email from segment where p=1)
this is pseudo code, check the proper syntax

Move that subquery to outer apply/left join
SELECT
count(*) as CountCustomers,
sum(case when s.customerid is not null
and (Cellphone not in ('0' ,'-1') or email not like '%#NoEmail.com')
then 1 else 0 end
) as EmailOrSms
from customer c
left join (
select distinct customerid
from source
where brand like '%abc%'
) s on s.customerid = c.customerid
inner join (
select distinct email
from segment where p=1
) g on g.email = c.email
Both distincts look ugly but perhaps scans are more suitable here than loops. And I don't like this join by email very much.

Related

How can I exclude users who have only purchase with car?

There is this SQL(from Django) query:
SELECT "id", "name"
FROM "polls_client" INNER JOIN "polls_purchases"
ON ("id" = "client_id")
WHERE "polls_purchases"."product" IN (car, bike)
We need to select from query users who have purchase records only 'car'. I want to do this in one select to the database. How do I do this?
You can group by client and set the condition in the HAVING clause:
SELECT pc.id, pc.name
FROM polls_client pc INNER JOIN polls_purchases pp
ON pc.id = pp.client_id
GROUP BY pc.id, pc.name
HAVING SUM(CASE WHEN pp.product <> 'car' THEN 1 ELSE 0 END) = 0
We need to select from query users who have purchase records only 'car'.
The simplest, most efficient method should be not exists:
SELECT c.*
FROM "polls_client" c
WHERE NOT EXISTS (SELECT 1
FROM "polls_purchases" pp
WHERE c."id" = pp."client_id" AND pp."product" <> 'car'
);
In particular, this can take advantage of an index on polls_purchases(client_id, product).
I would also dissuade your from using double quotes for identifies. They only serve to clutter queries.

Select one record from a nested sql select

Help - I have puzzeled on this for some time and am going bats.
In this cutdown query I want to select a single Email address from a contacts list that may contain multiple contacts for that customer.
My problem is that it does not find the Contacts email if I specify TOP 1
(showing NULL for CC.Email)
Note: it returns two records with Contact emails if I specify TOP 5
I think the TOP 1 must be restricting the records to 1 before determining a match?
SELECT C.Code as Customer, C.Name as CustomerName, C. Email, CC.Email, IIF(CC.Email<>'',CC.Email,C.Email) as Email
FROM [dr].[Customer] C
LEFT OUTER JOIN (Select TOP 1 CustomerId, Email
from dr.Contact CC
INNER JOIN dr.ContactDocumentOption CDO
on CDO.TransactionTypeId = 102
AND CDO.ContactId = CC.ContactId
Order by CC.ContactId) CC
on CC.CustomerId = C.CustomerId
WHERE C.Code = 'B82'
Well, it's a bit hard to understand your question, but i guess you have multiple rows of the same customer, but not every row has values in the email column, am I right?
For this you should try including WHERE Email IS NOT NULL in your SQL string, the part with TOP 1 is good, but try to make the SQL statement return values only if it finds an email, using Email IS NOT NULL.
I hope I understood your question and that this helps you.
Have you tried to LEFT JOIN directly the second request instead of using a nested request? And then you take TOP 1 on the main request?
It would be easier to help if you had a SQL fiddle.
You can combine the data from Contacts with [SQL Row_Number() function][1] and additional Partition By clause as follows
Please note instead of TOP, I used Row_number function and used the result in WHERE clause with rn=1 for choosing the top 1
;with cte as (
select
c.Code as Customer, c.Name as CustomerName, c.Email CustomerEmail, cc.Email ContactEmail,
rn = ROW_NUMBER() over (partition by c.CustomerId order by cc.Email desc)
from Customer c
left join Contact cc on c.CustomerId = cc.CustomerId
left join ContactDocumentOption o on o.TransactionTypeId = 102
and o.ContactId = cc.ContactId
where c.Code = 'B82'
)
select Customer, CustomerName, ISNULL(ContactEmail,CustomerEmail) Email
from cte where rn = 1
ISNULL() function is a better way to compare two fields according to one of the field's nullable case
Output is as follows

T-SQL Query Join on different column depending on case

I have a legacy system that has a sales table and a customer table, CMS and CUST respectively. I need to query for the shipped to address based on different criteria. The customer table treats each address as its own customer. So if I have a billing address, then a shipping address, those will both be different CUSTNUM's. The CMS table has columns CUSTNUM and SHIPNUM. If the sales order uses the billing address as the shipping address, SHIPNUM = 0. If those 2 address are different, SHIPNUM = a different customer number than CUSTNUM. I'm trying to write a query that joins CUST to CMS based on the case of SHIPNUM being > 0 or not. My original query just used CUSTNUM, and ignored the SHIPNUM. My new query is syntactically correct and executes, but the row count returned is 2860 vs 3590 for the old query. The old join statement is just the commented out line :ON CMS.CUSTNUM = CUST.CUSTNUM.
from
KGI_LOTNOS as LOT
INNER JOIN CMS
ON LOT.ORDERNO = CMS.ORDERNO
JOIN CUST
ON CUST.CUSTNUM =
CASE
WHEN CMS.SHIPNUM > 0
THEN CMS.SHIPNUM
Else CMS.CUSTNUM
END
-- ON CMS.CUSTNUM = CUST.CUSTNUM
INNER JOIN COUNTRY as C
ON CUST.COUNTRY = C.COUNTRY
Here is an example from the CMS table;
CUSTNUM SHIPNUM ORDERNO
41863 77394 828509 <--Different billing and shipping address
43242 69291 776888 <--Different billing and shipping address
2356 0 765022 <--Same billing and shipping address
Any thoughts on how to make this work?
PS Here is the original query in its entirety.
select
CUST.CUSTNUM as Customer,
CMS.CUSTNUM,
CMS.SHIPNUM,
CUST.CTYPE,
CMS.ORDERNO,
CMS.ODR_DATE,
LTRIM(RTRIM(CUST.FIRSTNAME)) as First,
LTRIM(RTRIM(CUST.LASTNAME)) as Last,
LTRIM(RTRIM(CUST.COMPANY)) as Company,
LTRIM(RTRIM(CUST.PHONE)) as Phone,
LTRIM(RTRIM(CUST.EMAIL)) as Email,
LTRIM(RTRIM(CUST.ADDR)) as ADDR1,
LTRIM(RTRIM(CUST.ADDR2)) as ADDR2,
LTRIM(RTRIM(CUST.ADDR3)) as ADDR3,
LTRIM(RTRIM(CUST.CITY)) as City,
LTRIM(RTRIM(CUST.State)) as State,
LTRIM(RTRIM(CUST.ZIPCODE)) as Zip,
LTRIM(RTRIM(C.NAME)) as Country,
LOT.ITEMNO,
LOT.LOTNO,
COUNT(LOT.ITEMNO) as Quantity
from
KGI_LOTNOS as LOT
INNER JOIN CMS
ON LOT.ORDERNO = CMS.ORDERNO
LEFT JOIN CUST
ON CMS.CUSTNUM = CUST.CUSTNUM
INNER JOIN COUNTRY as C
ON CUST.COUNTRY = C.COUNTRY
where
(
CUST.CTYPE IN ('P','W','Z')
)
AND
(
LOT.LOTNO IN ('1000001','20001','300001')
)
GROUP BY
CMS.ORDERNO,
CUST.CUSTNUM,
CMS.CUSTNUM,
CMS.SHIPNUM,
CUST.CTYPE,
CUST.FIRSTNAME,
CMS.ODR_DATE,
CUST.LASTNAME,
CUST.COMPANY,
CUST.PHONE,
CUST.EMAIL,
CUST.ADDR,
CUST.ADDR2,
CUST.ADDR3,
LOT.ITEMNO,
CUST.CITY,
CUST.STATE,
CUST.ZIPCODE,
C.NAME,
LOT.LOTNO
ORDER BY
Customer,
CMS.ORDERNO,
LOT.ITEMNO,
LOT.LOTNO
If you use INNER JOIN you have risk to exclude raws which have no reference in another table. This could be caused by any of 2 another joins in your expression - comment them and try again. If you still receive less records you should check consistency of your data - one table has values which not correspond to values in another table.
BTW, I don't like CASE in JOIN expression simply because it looks ugly. What do you thinK about this expression which seemed to do the job too:
LEFT JOIN CUST
ON CUST.CUSTNUM = COALESCE(NULLIF(CMS.SHIPNUM, 0), CMS.CUSTNUM)
You could use a CTE like this.
WITH cte (ORDERNO, SHIPNUM) AS
(
SELECT ORDERNUM, SHIPNUM = CASE
WHEN CMS.SHIPNUM > 0
FROM CMS
Fewer records join using your altered criteria, there are some CMS.SHIPNUM values that don't have matching CUSTNUM in the CUST table.
To find the problematic entries change from INNER to OUTER join and add WHERE criteria, something like:
LEFT JOIN CUST
ON CUST.CUSTNUM = CASE WHEN CMS.SHIPNUM > 0 THEN CMS.SHIPNUM
ELSE CMS.CUSTNUM
END
WHERE CUST.CUSTNUM IS NULL
AND CMS.SHIPNUM > 0
Edit: You'll have to remove the INNER JOIN to COUNTRY to see the unmatched from your updated JOIN since it joins on a field from the customer table, and make sure to have the SHIPNUM field in your SELECT.
your query looks correct but not sure why it is not working, try left joinCUST table twiceone on shipping and the other on billing and then write the case statement for each customer column.
select
LTRIM(RTRIM(case when CMS.SHIPNUM > 0 THEN CUST.FIRSTNAME else CUST_BILL.FIRSTNAME end )) as First,
from
KGI_LOTNOS as LOT
INNER JOIN CMS
ON LOT.ORDERNO = CMS.ORDERNO
left JOIN CUST CUST
ON CUST.CUSTNUM = CMS.SHIPNUM
left JOIN CUST CUST_BILL
ON CMS.CUSTNUM = CUST_BILL.CUSTNUM
INNER JOIN COUNTRY as C
ON CUST.COUNTRY = C.COUNTRY
if it still outputs less rows then something else is wrong

SQL query for join with condition

I have these two tables:
Customers: Id, Name
Orders: Id, CustomerId, Time, Status
I want to get a list of customers for which the LAST order does not have a status of 'Wrong'.
I know how to use a LEFT JOIN to get a count of orders for each customer, but I don't know how I can use this statement for what I want. Maybe a JOIN is not the right thing to use too, I'm not sure.
It's possible that customers do not have any order, and they should be returned.
I'm abstracting the real tables here, but the scenario is for a windows phone app sending notifications. I want to get all clients for which their last notification does not have a 'Dropped' status. I can sort their notifications (orders) by the 'Time' field. Thanks for the help, while I continue experimenting with subqueries in the where clause.
Select ...
From Customers As C
Where Not Exists (
Select 1
From Orders As O1
Join (
Select O2.CustomerId, Max( O2.Time ) As Time
From Orders As O2
Group By O2.CustomerId
) As LastOrderTime
On LastOrderTime.CustomerId = O1.CustomerId
And LastOrderTime.Time = O1.Time
Where O1.Status = 'Dropped'
And O1.CustomerId = C.Id
)
There are obviously alternatives based on the actual database product and version. For example, in SQL Server one could use the TOP command or a CTE perhaps. However, without knowing what specific product is being used, the above solution should produce the results you want in almost any database product.
Addition
If you were using a product that supported ranking functions (which database product and version isn't mentioned) and common-table expressions, then an alternative solution might be something like so:
With RankedOrders As
(
Select O.CustomerId, O.Status
, Row_Number() Over( Partition By CustomerId Order By Time Desc ) As Rnk
From Orders As O
)
Select ...
From Customers
Where Not Exists (
Select 1
From RankedOrders As O1
Where O1.CustomerId = C.Id
And O1.Rnk = 1
And O1.Status = 'Dropped'
)
Assuming Last order refers to the Time column here is my query:
SELECT C.Id,
C.Name,
MAX(O.Time)
FROM
Customers C
INNER JOIN Orders O
ON C.Id = O.CustomerId
WHERE
O.Status != 'Wrong'
GROUP BY C.Id,
C.Name
EDIT:
Regarding your table configuration. You should really consider revising the structure to include a third table. They would look like this:
Customer
CustomerId | Name
Order
OrderId | Status | Time
CompletedOrders
CoId | CustomerId | OrderId
Now what you do is store the info about a customer or order in their respective tables ... then when an order is made you just create a CompletedOrders entry with the ids of the 2 individual records. This will allow for a 1 to Many relationship between customer and orders.
Didn't check it out, but something like this?
SELECT c.CustmerId, c.Name, MAX(o.Time)
FROM Customers c
LEFT JOIN Orders o ON o.CustomerId = c.CustomerId
WHERE o.Status <> 'Wrong'
GROUP BY c.CustomerId, C.Name
You can get list of customers with the LAST order which has status of 'Wrong' with something like
select customerId from orders where status='Wrong'
group by customerId
having time=max(time)

SQL COUNT() / LEFT JOIN?

I have three tables: calls, attachments and notes and I want to display everything that's in the calls table, but also display whether a call has attachments and whether the call has notes. - by determining if there is an attachment or note record with a call_id in it. There could be notes and attachments, or there may not be but I would need to know.
Tables structure:
calls:
call_id | title | description
attachments:
attach_id | attach_name | call_id
notes:
note_id | note_text | call_id
If I write:
SELECT c.call_id
, title
, description
, count(attach_id)
FROM calls c
LEFT JOIN attachments a ON c.call_id = a.call_id
GROUP BY c.call_id
, title
, description
to give me a list of all calls and the number of attachments.
How can I also add in a column with the number of notes or a column which indicates that there is notes?
Any ideas?
Thanks.
For the count
SELECT
c.call_id,
title,
description,
count(DISTINCT attach_id) AS attachment_count ,
count(DISTINCT note_id) AS notes_count
FROM calls c
LEFT JOIN attachments a ON c.call_id = a.call_id
LEFT JOIN notes n ON n.call_id = c.call_id
GROUP BY c.call_id,title,description
Or for existence (will be more efficient if this is all you need)
SELECT
c.call_id,
title,
description,
count(attach_id) AS attachment_count ,
case
when exists (select * from notes n WHERE n.call_id = c.call_id) then
cast(1 as bit)
else
cast(0 as bit)
end as notes_exist
FROM calls c
LEFT JOIN attachments a ON c.call_id = a.call_id
GROUP BY c.call_id,title,description
SELECT c.call_id, title, description, a.call_id, n.call_id
FROM calls c
LEFT JOIN attachments a ON c.call_id = a.call_id
LEFT JOIN notes n ON c.call_id = n.call_id
GROUP BY c.call_id,title,description, a.call_id, n.call_id
If call id is present in fiels 4 or 5, you know you have an attachement or a note
If you need to number of attachement or note, look at other answers, look at AtaTheDev's post.
Use distinct in counts
You have to use distinct in counts because your groups have grown by two different entities. So you have to only count distinct values of each. This next query will return both counts as well as bit values whether there are any attachments and notes.
select
c.call_id, c.title, c.description,
count(distinct a.attach_id) as attachments_count,
count(distinct n.note_id) as notes_count,
/* add these two if you need to */
case when count(distinct a.attach_id) > 0 then 1 else 0 end as has_attachments,
case when count(distinct n.note_id) > 0 then 1 else 0 end as has_notes
from calls c
left join attachments a
on (a.call_id = c.call_id)
left join notes n
on (n.call_id = c.call_id)
group by c.call_id, c.title, c.description
I think it should be something like this
SELECT c.call_id, title, description, count(distinct attach_id) , count(distinct note_id)
FROM calls c
LEFT JOIN attachments a ON c.call_id = a.call_id
LEFT JOIN notes n ON n.call_id = a.call_id
GROUP BY c.call_id,title,description
This also works:
SELECT
cl.*,
(SELECT count(1) FROM attachments AS at WHERE at.call_id = cl.id) as num_attachments,
(SELECT count(1) FROM notes AS nt WHERE nt.call_id = cl.id) as num_notes,
FROM calls AS cl
I have used this simple query. This query allows you to use main tables columns easily without group by.
Select StudentName,FatherName,MotherName,DOB,t.count from Student
left JOIN
(
Select StudentAttendance.StudentID, count(IsPresent) as count
from StudentAttendance
group by StudentID, IsPresent
) as t
ON t.StudentID=Student.StudentID