Showing only max value of case statement in group by statement in SQL Server Management Studio - sql

With a query I'd like to check for all the loans in our database, whether they main credit taker has an e-mail address filled out or not.
The problem I am encountering is that the Address table stores various types of addresses (also physical/fax addresses etc.). I can't simply filter on e-mail addresses as I would still like to see all loans (also those without e-mail), with the result whether or not they have an e-mail.
My current query still shows multiple rows for loans who have both mail and another form of address.
select l.ApplicationNumber, p1.Name AS Credittaker, a1.EAddress AS 'Mail
credit taker',
SUM(case
when c1.ContactChannelTypeID = 2 AND a1.ElectronicAddressTypeID = 1 then 1
else 0 end) as MailID
from Line l
JOIN Role r1 on (l.ObjID = r1.ObjID_Businessobject)
JOIN Party p1 on (p1.ObjID = r1.ObjID_Party)
JOIN ContactChannel c1 on (p1.ObjID = c1.ObjID_Party)
JOIN Address a1 on (a1.ObjID = c1.ObjID_Address)
WHERE r1.RDNameID = '17' AND r1.Enddate IS NULL AND c1.EndDate IS NULL
GROUP BY l.ApplicationNumber, p1.Name, a1.EAddress
order by l.ApplicationNumber
What I want is for the query to show 1 row per application number, if there is an e-mail (i.e. a row with MailID 1), to show only this value for the application number. If no e-mail is found (i.e. only row with MailID 0), to show this value.
How would I do this?
Example of results vs desired results:
11650 and 11651 only have non e-mail addresses so their value of MailID = 0 is correct. 11652 and 11653 both have an e-mail address and other types of addresses, and should therefore only display MailID = 1. Adding MAX(MailID) to the Group by clause doesn't work because it doesn't recognize the column name. If I put the case statement in a subquery and refer to it afterwards, I get the error that the an aggregate function can't contain another aggregate function.

I think this will work:
select top (1) with ties . . .
. . .
order by row_number() over (partition by l.ApplicationNumber order by MailId desc);
I'm not 100% sure you can use a column alias like this in the order by. If not, then :
with t as (
<your query here with no order by>
)
select
from (select t.*,
row_number() over (partition by applicationNumber order by mailid desc) as seqnum
from t
) t
where seqnum = 1;

Related

Last message sent logic issue

I'm attempting to identify the last message sent in a conversation for a set of conversations in SQL. I can get back the correct users and ID's no problem, but the last message and the time it was sent are coming back as the same for both conversations, whereas I need the last message sent in that particular conversation.
Here's the SQL I'm using:
SELECT ConversationId,
(SELECT Username FROM dbo.[User] WHERE (UserId = dbo.Conversation.FromUser)) AS FromUser,
(SELECT Username
FROM dbo.[User] User_1
WHERE (UserId = dbo.Conversation.ToUser)) AS ToUser,
(SELECT TOP (1)
MessageBody
FROM dbo.ConversationMessage
WHERE (ConversationId = ConversationId)
ORDER BY MessageDateTime DESC) AS LastMessageBody,
(SELECT TOP (1)
CONVERT(varchar(5), MessageDateTime, 114) AS Expr1
FROM dbo.ConversationMessage ConversationMessage_1
WHERE (ConversationId = ConversationId)
ORDER BY MessageDateTime DESC) AS LastMessageTime
FROM dbo.Conversation;
And here's the result set I'm getting back, just for clarity:
LastMessageBody and LastMessageTime, should both be unique and since the ConversationId's are both unique I can't see why the same message should be returned each time. My understanding was that the individual row's ConversationId would be used in the where clause just like the UserId in the other columns?
Honestly, this is a massive stab in the dark, however, it might be what you're after. There's no sample data or expected results here, so if this isn't what you need, then provide both.
I've also got rid of those awful subqueries and replaced them with JOINs:
SELECT TOP 1 WITH TIES
C.ConversationId,
Uf.Username AS FromUser,
Ut.Username AS ToUser,
CM.MessageBody AS LastMessageBody,
CONVERT(time(0),CM.MessageDateTime) AS LastMessageTime
FROM dbo.[Conversation] C
JOIN dbo.[User] Uf ON C.FromUser = Uf.UserId
JOIN dbo.[User] Ut ON C.ToUser = Ut.UserId
JOIN dbo.ConversationMessage CM ON C.ConversationId = CM.ConversationId
ORDER BY ROW_NUMBER() OVER (PARTITION BY C.ConversationId ORDER BY CM.MessageDateTime DESC);
Whenever you have multiple tables in a query, always use table aliases and qualified column names. This is your problem.
Here is an example of how to fix it:
SELECT . . .
(SELECT TOP (1) cm.MessageBody
FROM dbo.ConversationMessage cm
WHERE cm.ConversationId = c.ConversationId
ORDER BY cm.MessageDateTime DESC
) AS LastMessageBody,
. . .
FROM dbo.Conversation c;
When all column names are properly qualified, your query should work as you intend.

How to select only one row that satisfies one of multiple ordered conditions

I have customers that have multiple addresses. Each customer/address combination has its own line in the database (Oracle) table.
I am trying to achieve a query in which, if the customer has a 'Main Address', I display only the Main Address,
otherwise if he has a Shipment Address, I display only the Shipment Address,
otherwise if he has a 'Secondary Address', I display the Secondary Address,
otherwise I display nothing.
This order is important, and the problem is that the entries in the database are in no specific order, meaning that the same customer might be found to have a Shipment Address first, and a Main Address later on. Therefore I don't simply need the first row that satisfies one of the conditions...
I tried this, but it returns a result for each line, e.g. multiple results for one person:
CASE WHEN ADR = 'MAIN' THEN 'MAIN'
WHEN ADR = 'SHIPMENT' THEN 'SHIPMENT'
WHEN ADR = 'SECONDARY' THEN 'SECONDARY'
ELSE null
END AS Adressart
To clarify, the input looks as follows:
CUSTOMER_NR ADDRESS_TYPE
1 SHIPMENT
1 MAIN
2 SHIPMENT
3 SECONDARY
3 SHIPMENT
4 SECONDARY
The results would look like this:
CUSTOMER_NR ADDRESS_TYPE
1 MAIN
2 SHIPMENT
3 SHIPMENT
4 SECONDARY
I think you could use multiple joins to the address table and by using coalesce return only the one address from the order
The query would look something like:
SELECT customer_nr, coalesce(a1.address, a2.address, a3,address) AS address
FROM customer AS c
LEFT JOIN address AS a1 where a1.customer_nr = c.customer_nr and a1.address_type = 'MAIN'
LEFT JOIN address AS a2 where a2.customer_nr = c.customer_nr and a2.address_type = 'SHIPMENT'
LEFT JOIN address AS a3 where a3.customer_nr = c.customer_nr and a3.address_type = 'SECONDARY'
The following query would get you the desired result:
SELECT
CUSTOMER_NR,
ADDRESS_TYPE
FROM
(
select
customer_nr,
address_type,
row_number () over (partition by customer_nr order by
case address_type
when 'MAIN' then 1
when 'SHIPMENT' then 2
when 'SECONDARY' THEN 3
else 4
end) rn
from addresses
)
WHERE rn = 1;
First, the addresses are sorted using the priority with a CASE statement. Then, only the address types that have rn = 1 (the address type of highest priority) are selected.
SQL Fiddle demo

removing non-matching rows based on order in SQL within a WITH statement

I've got a many-to-many setup where there are items and item names(based on languageID)
I want to retrieve all names for a set id, where the name is replace with an alternate name (same itemID, but different languageID) when name is NULL.
I've set up a table that receives all combinations of itemids and itemnames, even the missing ones, and have the name ordered by an hasName flag, that is set based on name existing to 0,1 or 2. 0 means languageId and name exist, 1 means only name exists, and 2 means neither. I then sort the results: ORDER BY itemId, hasName, languageId this works well enough, because the top 1 row of every itemid meats the critera, and I can just pull that.
However I still need to process other queries using the result, so this doesn't work well, because as soon as I use a WITH statement, the order cannot be used, so it breaks the functionality
What I'm using instead is a join, where I select the top 1 matching row on the ordered table
the problem there is that the time to execute goes up 10x
any ideas what else I could try?
using SQL server 10.50
the slow query:
SELECT
*,
(SELECT top 1 ItemName FROM ItemNameMultiLang x WHERE x.ItemId = tc.ItemId ORDER BY ItemID, hasName, LangID) AS ItemName
FROM ItemCategories tc
ORDER BY ItemId
One way to approach this is with row_number(), so you can get the first row from itemNameMultiLang, which is what you want:
SELECT tc.*, inml.ItemName
FROM ItemCategories tc left outer join
(select inml.*, row_number() over (partition by inml.ItemId order by hasname, langId) as seqnum
from ItemNameMultiLang
) inml
on tc.ItemItem = inml.ItemId and
inml.seqnum = 1
ORDER BY tc.ItemId;

How can I find records where a field appears once in a column or multiple times in the column but only once between a set of dates?

I am trying to write a query that brings up the details (invoiceNumber, name, phone & email) from a table but only where the entries match these specific requirements:
Return the rows where the email address appears only once in the whole table from any date
AND
Return the rows where the email address multiple times WHERE the email has only been entered ONCE within the last 3 months.
I am fairly sure that this requires nested statements but I have no idea how to go about setting it up.
Any help would be greatly appreciated!
Approach this by thinking about the email addresses. You can determine the frequency of appearance by doing a group by:
select emailaddress, count(*) as TotalCnt,
sum(case when invoicedate >= sysdate - 90 then 1 else 0 end) as LastThreeMonths
from t
group by emailaddress;
This gives you the information you need. The following query joins this back to the original table to get the rows you are looking for:
select t.*
from t join
(select emailaddress, count(*) as TotalCnt,
sum(case when invoicedate >= sysdate - 90 then 1 else 0 end) as LastThreeMonths
from t
group by emailaddress
) e
on t.emailaddress = e.emailaddress
where e.TotalCnt = 1 or e.LastThreeMonths = 1

SQL Count the numbers in a returned query?

I have a query to retrieve the email addresses and the names (sometimes more than 1) associated with the email address, where the account status is not closed, in descending order:
SELECT sbg.contact_email, COUNT(DISTINCT sbg.contact_name) Num_Contact_Names
FROM SummaryBillGroup sbg
INNER JOIN Account a
ON sbg.Customer_number = a.Customer_number
WHERE a.account_status_code <> 'c'
GROUP BY sbg.contact_email
ORDER BY Num_Contact_Names DESC
This returns a list of the email addresses and the number of names associated with each email address. What I would like to do now is use that query to count up all of the returned numbers, so that I have a list of the 3's, the 2's, the 1's, etc.
You could use that same query as a derived table for another one. Like this:
select num_contact_names, count(*)
from (SELECT sbg.contact_email, COUNT(DISTINCT sbg.contact_name) Num_Contact_Names
FROM SummaryBillGroup sbg
INNER JOIN Account a
ON sbg.Customer_number = a.Customer_number
WHERE a.account_status_code 'c'
GROUP BY sbg.contact_email) as t
group by t.num_contact_names
order by 2
First row would give you 1's, second row the 2's and so on. Cheers.