Group by and Having using the same Column - sql

I have the following code for a SSRS report using stored procedures, I want to filter the group by to display only the information concerning group level 2, but I'm not sure how to do so.
Here is the stored procedure I am using:
ALTER PROCEDURE CheckActiveMembers
AS
BEGIN
SELECT COUNT(MemberID), GroupLevel
FROM Member M
INNER JOIN [Policy] P ON P.PolicyID = M.PolicyID
INNER JOIN [Group] G ON P.GroupID = G.GroupID
WHERE GroupLevel = '2' AND M.CancellationDate IS NULL
GROUP BY GroupLevel
HAVING GroupLevel = '2'
END

Group by is used to group records and applying having works for within that specific group only on which it is applied whereas a where clause is valid for whole table itself like in your case
SELECT COUNT(MemberID),
GroupLevel
FROM Member M
INNER JOIN [Policy] P
ON P.PolicyID = M.PolicyID
INNER JOIN [Group] G
ON P.GroupID = G.GroupID
WHERE GroupLevel = '2' AND
M.CancellationDate IS NULL
GROUP BY GroupLevel
HAVING GroupLevel = '2'
The having is irrelevant here as you have already filtered records in your where clause having usually is used to check aggregation within that group

HAVING should only contain aggregated columns, non-aggregated (grouped) columns go with WHERE:
SELECT COUNT(MemberID), GroupLevel
FROM Member M
INNER JOIN [Policy] P
ON P.PolicyID = M.PolicyID
INNER JOIN [Group] G
ON P.GroupID = G.GroupID
WHERE GroupLevel = '2' AND M.CancellationDate IS NULL
GROUP BY GroupLevel
If you wanted ones where there was > 1 member, you could use the aggregated COUNT in the HAVING:
SELECT COUNT(MemberID), GroupLevel
FROM Member M
INNER JOIN [Policy] P
ON P.PolicyID = M.PolicyID
INNER JOIN [Group] G
ON P.GroupID = G.GroupID
WHERE GroupLevel = '2' AND M.CancellationDate IS NULL
GROUP BY GroupLevel
HAVING COUNT(MemberId) > 1

Related

SQL - SUM TotalValue returning a null with LEFT JOIN Clause

I'd to like SUM a TotalValue column based on Vendor and logged in user. I completely returning a right value of other info in logged user and the connected vendor, the problem is the SUM of TotalValue column is returning a null value. am I missing something?
This is what I've already tried:
SELECT ,v.VendorName ,
u.Product ,
v.[Description] ,
v.Status ,
SUM(cpm.TotalValue) AS TotalValue
FROM Vendor v
LEFT JOIN [ProductContract] c ON v.VendorId = c.VendorId
AND c.[Status] = 4
AND c.ProductContractId IN
(SELECT con.ProductContractId
FROM [ProductContract] con
INNER JOIN [ProductContractPermission] cp ON cp.ProductContractId = con.ProductContractId
WHERE cp.UserInfoId = #UserInfoId)
LEFT JOIN ProductContractPaymentMenu cpm ON c.ProductContractId = cpm.ProductContractId
AND c.[Status] = 4
AND c.VendorId = #VendorId
LEFT JOIN VendorContact vc ON v.VendorId = vc.VendorId
AND vc.[Type] = 1
LEFT JOIN UserInfo u ON vc.UserInfoId = u.UserInfoId
WHERE v.VendorId IN
(SELECT VendorId
FROM ClientVendor
WHERE ClientId = #VendorId)
GROUP BY v.VendorName,
u.Product,
v.[Description],
v.Status,
cpm.TotalValue
ORDER BY v.[Status],
v.CreatedOn
Seems that you want to apply an aggregate filter, this is the famous HAVING clause:
...
GROUP BY v.VendorName,
u.Product,
v.[Description],
v.Status,
cpm.TotalValue
HAVING
SUM(cpm.TotalValue) > 0
ORDER BY v.[Status],
v.CreatedOn
Hi i think you have to add ISNULL(value,defaultvalue) because cpm table was on left join and it can be null.
SELECT v.VendorName ,
u.Product ,
v.[Description] ,
v.Status ,
SUM(ISNULL(cpm.TotalValue,0)) AS TotalValue
FROM Vendor v
LEFT JOIN [ProductContract] c ON v.VendorId = c.VendorId
AND c.[Status] = 4
AND c.ProductContractId IN
(SELECT con.ProductContractId
FROM [ProductContract] con
INNER JOIN [ProductContractPermission] cp ON cp.ProductContractId = con.ProductContractId
WHERE cp.UserInfoId = #UserInfoId)
LEFT JOIN ProductContractPaymentMenu cpm ON c.ProductContractId = cpm.ProductContractId
AND c.[Status] = 4
AND c.VendorId = #VendorId
LEFT JOIN VendorContact vc ON v.VendorId = vc.VendorId
AND vc.[Type] = 1
LEFT JOIN UserInfo u ON vc.UserInfoId = u.UserInfoId
WHERE v.VendorId IN
(SELECT VendorId
FROM ClientVendor
WHERE ClientId = #VendorId)
GROUP BY v.VendorName,
u.Product,
v.[Description],
v.Status,
cpm.TotalValue
ORDER BY v.[Status],
v.CreatedOn
https://learn.microsoft.com/en-us/sql/t-sql/functions/isnull-transact-sql?view=sql-server-2017
Edit: other point you have miss ',' in your query after the SELECT.
Your left join on ProductContractPaymentMenu may not always get an item, so cpm.TotalValue can be NULL sometimes. When you use SUM and when one value is NULL then the result will be NULL. You might rewrite that part as:
SUM(ISNULL(cpm.TotalValue, 0)) AS TotalValue
In that case, it will treat non-existing records as value 0.

SQL: Linking a Count to a specific value through multiple tables

I'm trying to link a COUNT to a specific value across several tables in a SQL Server Database. In this case the tables only share values through correlation. I am returning the values I want but the COUNT is counting everything in a given project not just the ones linked to their work items.
SELECT
[d].[Id]
,COUNT([t].[ItemId]) AS ItemCount
,[d].[ItemName]
FROM
[dbo].[Project_Map] [rm] WITH (NOLOCK)
INNER JOIN
[dbo].[WorkProjects] [r] WITH (NOLOCK)
ON [r].[DomainId] = [rm].[DomainId]
AND [r].[ProjectId] = [rm].[ProjectId]
AND [r].[ReleaseId] = [rm].[ReleaseId]
INNER JOIN
[dbo].[Items] [d] WITH (NOLOCK)
ON [d].[DomainId] = [r].[DomainId]
AND [d].[ProjectId] = [r].[ProjectId]
AND [d].[ReleaseId] = [r].[ReleaseId]
INNER JOIN [dbo].[Projects] [p] with (NOLOCK)
ON r.DomainId = p.DomainId
AND r.ProjectId = p.ProjectId
INNER JOIN [dbo].[Tests] [t] with (NOLOCK)
ON p.DomainId = t.DomainId
AND p.ProjectId = t.ProjectId
INNER JOIN
(
SELECT [Id], MAX([LastModifiedDate]) AS MostRecent
FROM Items
Group By [Id]
) AS updatedItem
ON updatedItem.Id = d.Id
INNER JOIN
[dbo].[WorkItemStates] [ds] WITH (NOLOCK)
ON [ds].[ItemStateName] = [d].[ItemStatus]
WHERE
d.Id = 111111
AND d.UserCategory Like 'SOMESTRING'
GROUP BY d.Id, d.ItemName
RETURNS: In this case the count should be 1 but it returns the count for the entire project.
ID COUNT ITEMNAME
86 5169 SOME NAME
173 5169 SOME NAME
170 5169 SOME NAME
Am I missing a join somewhere?
Currently, your counts are counting all JOIN instances and not just distinct Item level records. Consider turning your Item unit level join into an aggregate query join and include the count field in outer grouping:
Specifically, change:
INNER JOIN [dbo].[Tests] [t] with (NOLOCK)
ON p.DomainId = t.DomainId
AND p.ProjectId = t.ProjectId
Into:
INNER JOIN
(SELECT t.DomaindId, t.ProjectId, Count(*) As ItemCount
FROM [dbo].[Tests] t
GROUP BY t.DomaindId, t.ProjectId) agg
ON p.DomainId = agg.DomainId
AND p.ProjectId = agg.ProjectId
And then the outer query structure becomes:
SELECT
[d].[Id]
,agg.ItemCount
,[d].[ItemName]
FROM
...
GROUP BY
[d].[Id]
,agg.ItemCount
,[d].[ItemName]
Interestingly, you already do such an aggregate query join but never use that derived table updateItem or the field MostRecent.

sql : query same column multiple times AS different columns, with multiple values returned 1 per row

I need to build a report that is structured as follows:
Name, Field_1, Field_2, Field_3
Jim, opt_1, y, 12
Jane, opt_2, n, 64
etcetera
I'm pulling from Moodle database tables, structure shown in following images. mdl_user_info_data, mdl_user_info_field, mdl_user has same columns as others, all I'm getting from it is name so I'm not bothering to include it here. That part works fine.
Here's the best I could come up with so far, so you can see how the tables join:
SELECT
CONCAT (u.firstname, u.lastname) Name,
(SELECT d.data FROM mdl_user_info_data d JOIN mdl_user_info_field f ON f.id = d.fieldid WHERE f.shortname = "Field_1" GROUP BY d.userid),
(SELECT d.data FROM mdl_user_info_data d JOIN mdl_user_info_field f ON f.id = d.fieldid WHERE f.shortname = "Field_2" GROUP BY d.userid),
(SELECT d.data FROM mdl_user_info_data d JOIN mdl_user_info_field f ON f.id = d.fieldid WHERE f.shortname = "Field_3" GROUP BY d.userid),
FROM mdl_user u
JOIN mdl_user_info_data d ON d.userid = u.id
JOIN mdl_user_info_field f ON d.fieldid = f.id
GROUP BY d.userid
ORDER BY `Name` ASC
I know I'm barking up the wrong tree with my sub-queries, but not sure what to do in place of the GROUP BYs. My current query will return all values of each given Field_x for in each row, when I need to return only the value that corresponds to the user named in column 1.
Any help much appreciated.
I'm not positive about moodle or it's sql syntax, but if common, you might get what you want by doing left-joins to the table multiple times, each with its criteria qualification, then just use the different ALIAS references to get your pieces...
SELECT
CONCAT (u.firstname, u.lastname) Name,
dFld1.Data as Field1Data,
dFld2.Data as Field2Data,
dFld3.Data as Field3Data
FROM
mdl_user u
LEFT JOIN mdl_user_info_data dFld1
ON u.id = dFld1.userid
LEFT JOIN mdl_user_info_field fFld1
ON dFld1.fieldid = fFld1.id
AND fFld1.shortname = "Field_1"
LEFT JOIN mdl_user_info_data dFld2
ON u.id = dFld2.userid
LEFT JOIN mdl_user_info_field fFld2
ON dFld2.fieldid = fFld2.id
AND fFld2.shortname = "Field_2"
LEFT JOIN mdl_user_info_data dFld3
ON u.id = dFld3.userid
LEFT JOIN mdl_user_info_field fFld3
ON dFld3.fieldid = fFld3.id
AND fFld3.shortname = "Field_3"
ORDER BY
`Name` ASC
I have this query with LEFT-JOINs if any such links may NOT have a value.
As for the group by, this would only be required if there are multiple entries for one or more of the underlying tables for a given user and field 1, 2 or 3 entries respectively.
An alternative solution.
SELECT u.id, u.firstname, u.lastname, f.fieldname1, f.fieldname2
FROM mdl_user u
JOIN (
SELECT d.userid,
MAX(CASE WHEN f.shortname = 'fieldname1' THEN d.data ELSE null END) AS fieldname1,
MAX(CASE WHEN f.shortname = 'fieldname2' THEN d.data ELSE null END) AS fieldname2
FROM mdl_user_info_field f
JOIN mdl_user_info_data d ON d.fieldid = f.id
WHERE f.shortname IN ('fieldname1', 'fieldname2')
GROUP BY d.userid) f ON f.userid = u.id
Concat will work for MySql, but you should use database compatible functions - https://docs.moodle.org/dev/Data_manipulation_API#SQL_compatibility_functions
So use the result of
$DB->sql_concat('u.firstname', 'u.lastname');

How to use group by only for some columns in sql Query?

The following query returns 550 records, which I am then grouping by some columns in the controller via linq. However, how can I achieve the "group by" logic in the SQL query itself? Additionally, post-grouping, I need to show only 150 results to the user.
Current SQL query:
SELECT DISTINCT
l.Id AS LoadId
, l.LoadTrackingNumber AS LoadDisplayId
, planningType.Text AS PlanningType
, loadStatus.Id AS StatusId
, loadWorkRequest.Id AS LoadRequestId
, loadStatus.Text AS Status
, routeIds.RouteIdentifier AS RouteName
, planRequest.Id AS PlanId
, originPartyRole.Id AS OriginId
, originParty.Id AS OriginPartyId
, originParty.LegalName AS Origin
, destinationPartyRole.Id AS DestinationId
, destinationParty.Id AS DestinationPartyId
, destinationParty.LegalName AS Destination
, COALESCE(firstSegmentLocation.Window_Start, originLocation.Window_Start) AS StartDate
, COALESCE(firstSegmentLocation.Window_Start, originLocation.Window_Start) AS BeginDate
, destLocation.Window_Finish AS EndDate
AS Number
FROM Domain.Loads (NOLOCK) AS l
INNER JOIN dbo.Lists (NOLOCK) AS loadStatus ON l.LoadStatusId = loadStatus.Id
INNER JOIN Domain.Routes (NOLOCK) AS routeIds ON routeIds.Id = l.RouteId
INNER JOIN Domain.BaseRequests (NOLOCK) AS loadWorkRequest ON loadWorkRequest.LoadId = l.Id
INNER JOIN Domain.BaseRequests (NOLOCK) AS planRequest ON planRequest.Id = loadWorkRequest.ParentWorkRequestId
INNER JOIN Domain.Schedules AS planSchedule ON planSchedule.Id = planRequest.ScheduleId
INNER JOIN Domain.Segments (NOLOCK) os on os.RouteId = routeIds.Id AND os.[Order] = 0
INNER JOIN Domain.LocationDetails (NOLOCK) AS originLocation ON originLocation.Id = os.DestinationId
INNER JOIN dbo.EntityRoles (NOLOCK) AS originPartyRole ON originPartyRole.Id = originLocation.DockRoleId
INNER JOIN dbo.Entities (NOLOCK) AS originParty ON originParty.Id = originPartyRole.PartyId
INNER JOIN Domain.LocationDetails (NOLOCK) AS destLocation ON destLocation.Id = routeIds.DestinationFacilityLocationId
INNER JOIN dbo.EntityRoles (NOLOCK) AS destinationPartyRole ON destinationPartyRole.Id = destLocation.DockRoleId
INNER JOIN dbo.Entities (NOLOCK) AS destinationParty ON destinationParty.Id = destinationPartyRole.PartyId
INNER JOIN dbo.TransportationModes (NOLOCK) lictm on lictm.Id = l.LoadInstanceCarrierModeId
INNER JOIN dbo.EntityRoles (NOLOCK) AS carrierPartyRole ON lictm.CarrierId = carrierPartyRole.Id
INNER JOIN dbo.Entities (NOLOCK) AS carrier ON carrierPartyRole.PartyId = carrier.Id
INNER JOIN dbo.EntityRoles (NOLOCK) AS respPartyRole ON l.ResponsiblePartyId = respPartyRole.Id
INNER JOIN dbo.Entities (NOLOCK) AS respParty ON respPartyRole.PartyId = respParty.Id
INNER JOIN Domain.LoadOrders (NOLOCK) lo ON lo.LoadInstanceId = l.Id
INNER JOIN Domain.Orders (NOLOCK) AS o ON lo.OrderInstanceId = o.Id
INNER JOIN Domain.BaseRequests (NOLOCK) AS loadRequest ON loadRequest.LoadId = l.Id
--Load Start Date
LEFT JOIN Domain.Segments (NOLOCK) AS segment ON segment.RouteId = l.RouteId AND segment.[Order] = 0
LEFT JOIN Domain.LocationDetails (NOLOCK) AS firstSegmentLocation ON firstSegmentLocation.Id = segment.DestinationId
LEFT JOIN dbo.Lists (NOLOCK) AS planningType ON l.PlanningTypeId = planningType.Id
LEFT JOIN dbo.EntityRoles (NOLOCK) AS billToRole ON o.BillToId = billToRole.Id
LEFT JOIN dbo.Entities (NOLOCK) AS billTo ON billToRole.PartyId = billTo.Id
WHERE o.CustomerId in (34236) AND originLocation.Window_Start >= '07/19/2015 00:00:00' AND originLocation.Window_Start < '07/25/2015 23:59:59' AND l.IsHistoricalLoad = 0
AND loadStatus.Id in (285, 286,289,611,290)
AND loadWorkRequest.ParentWorkRequestId IS NOT NULL
AND routeIds.RouteIdentifier IS NOT NULL
AND (planSchedule.EndDate IS NULL OR (planSchedule.EndDate is not null and CAST(CONVERT(varchar(10), planSchedule.EndDate,101) as datetime) > CAST(CONVERT(varchar(10),GETDATE(),101) as datetime))) ORDER BY l.Id DESC
linq:
//Get custom grouped data
var loadRequest = (from lq in returnList
let loadDisplayId = lq.LoadDisplayId
let origin = lq.OriginId //get this origin for route
let destination = lq.DestinationId // get this destination for route
group lq by new
{
RouteId = lq.RouteName,
PlanId = lq.PlanId,
Origin = lq.OriginId,
Destination = lq.DestinationId
}
into grp
select new
{
RouteId = grp.Key.RouteId,
PlanId = grp.Key.PlanId,
Origin = grp.Key.Origin,
Destination = grp.Key.Destination,
Loads = (from l in grp select l)
}).OrderBy(x => x.Origin).ToList();
I'm guessing you want to Group By column 1 but include columns 2 and 3 in your Select. Using a Group By you cannot do this. However, you can do this using a T-SQL Windowing function using the OVER() operator. Since you don't say how you want to aggregate, I cannot provide an example. But look at T-SQL Windowing functions. This article might help you get started.
One important thing you need to understand about GROUP BY is that you must assume that there are multiple values in every column outside of the GROUP BY list. In your case, you must assume that for each value of Column1 there would be multiple values of Column2 and Column3, all considered as a single group.
If you want your query to process any of these columns, you must specify what to do about these multiple values.
Here are some choices you have:
Pick the smallest or the largest value for a column in a group - use MIN(...) or MAX(...) aggregator for that
Count non-NULL items in a group - use COUNT(...)
Produce an average of non-NULL values in a group - use AVG(...)
For example, if you would like to find the smallest Column2 and an average of Column3 for each value of Column1, your query would look like this:
select
Column1, MIN(Column2), AVG(Column3)
from
TableName
group by
Column1

How to add a subquery to return count of related rows

I have a working SELECT that returns info on each company (i.e. subscriber). Certain descriptive data is returned from inner joins on "code" tables. My problem is how to add a new column to the result which is a count of the number of users belonging to each subscriber. SubscriberID in the user table is a foreign key:
ALTER TABLE dbo.UserInfo WITH CHECK ADD CONSTRAINT FK_UserInfo_SubscriberID
FOREIGN KEY(SubscriberID) REFERENCES dbo.Subscriber (SubscriberID)
ON UPDATE CASCADE
ON DELETE CASCADE
I added the LEFT OUTER JOIN below and the GROUP BY but I cannot figure out why the parser complains that U.SubscriberID is an invalid column name).
SELECT SubscriberID
,SubscriberName
,DaysUntilExpired
,SubscriptionStatus SubscriptionStatusCode
, C.Description SubscriptionStatus
,U.NumberUsers
FROM dbo.Subscriber S
JOIN dbo.CodeValue C ON S.SubscriptionStatus = C.Value
JOIN dbo.CodeNamespace N ON N.ID = C.CodeNamespaceID AND N.Name = 'SubscriptionStatus'
JOIN dbo.CodeValue V ON S.NotificationStatus = V.Value
JOIN dbo.CodeNamespace X ON X.ID = V.CodeNamespaceID AND X.Name = 'NotificationStatus'
LEFT OUTER JOIN (SELECT Count(*) AS NumberUsers FROM dbo.UserInfo) AS U
ON S.SubscriberID = U.SubscriberID
GROUP BY
S.SubscriberID
,SubscriberName
,DaysUntilExpired
,S.SubscriptionStatus
,SubscriptionStatus
You are probably looking for the following. You need to inline the subquery
SELECT SubscriberID
,SubscriberName
,DaysUntilExpired
,SubscriptionStatus SubscriptionStatusCode
, C.Description SubscriptionStatus
,(SELECT Count(*) AS NumberUsers FROM dbo.UserInfo where SubscriberID = S.SubscriberID) AS NumberUsers
FROM dbo.Subscriber S
JOIN dbo.CodeValue C ON S.SubscriptionStatus = C.Value
JOIN dbo.CodeNamespace N ON N.ID = C.CodeNamespaceID AND N.Name = 'SubscriptionStatus'
JOIN dbo.CodeValue V ON S.NotificationStatus = V.Value
JOIN dbo.CodeNamespace X ON X.ID = V.CodeNamespaceID AND X.Name = 'NotificationStatus'
GROUP BY
S.SubscriberID
,SubscriberName
,DaysUntilExpired
,S.SubscriptionStatus
,SubscriptionStatus
Sql complains because your LEFT OUTER JOIN subquery does not include SubscriberId in column list (and in a group by clause since you have a COUNT aggregate), so it can't make the join on this column.
You should do:
LEFT OUTER JOIN (SELECT SubscriberID , Count(*) AS NumberUsers FROM dbo.UserInfo GROUP BY SubscriberID ) AS U
ON S.SubscriberID = U.SubscriberID