Getting distinct values from table - sql-server-2008

Im having and issue where in my table FarmerGroups I have multiple records by BSI_Code and I am getting double results for GallonsIssued due to this inner join. Is there a way to get the unique value of GallonsIssued or a way to just get results by individual BSI_CODE
With Summary as (
Select B_NAME as Branch, LOC as Location
,SUM(payment) as Gallons
,SUM(case when printed = 1 THEN Fee ELSE NULL END) as FeeCollected
,SUM(case when printed = 0 THEN Fee ELSE NULL END) as FeeNotCollected
,SUM(case when printed = 1 THEN Payment ELSE NULL END) as GallonsIssued
,SUM(case when printed = 0 THEN Payment ELSE NULL END) as GallonsNotIssued
From SicbWeeklyDeliveriesFuel F Inner Join FarmerGroups G ON G.BSI_CODE = F.BSI_CODE AND G.CROP_SEASON = F.CROP_SEASON AND F.B_NAME = G.BRANCH
Where F.CROP_SEASON = #cropseason
Group By B_NAME, LOC
)
SELECT Branch
,Location
,Gallons
,GallonsIssued
,GallonsNotIssued
,FeeCollected
,FeeNotCollected
,((GallonsIssued/Gallons) * 100) as pct_GallonsCollected
FROM Summary
Order by Location, Branch
For SicbWeeklyDeliveriesFuel
BSI_CODE
Payment
LOC
CROP_SEASON
Fee
B_NAME
FNAME
66
125
CZ
5
12.5
DOUGLAS
John K
55
147
OW
5
14.7
CALEDONIA
Tim H
66
95
CZ
5
9.5
DOUGLAS
John K
For Farmer Groups
BSI_CODE
Farmer
CROP_SEASON
BRANCH
TEST_GROUP
66
John K
5
DOUGLAS
1A
55
Tim H
5
CALEDONIA
1B
66
John K
5
DOUGLAS
2A

Your selection for the JOIN of G.BSI_CODE = F.BSI_CODE AND G.CROP_SEASON = F.CROP_SEASON AND F.B_NAME = G.BRANCH does not uniquely define the rows.
You will need to also include F..FNAME = G.Farmer otherwise the first row of SicbWeeklyDeliveriesFuel (BSI_CODE = 66, CROP_SEASON = 5 and B_NAME = DOUGLAS) matches both the first and last rows of FarmerGroups. Likewise the third row also matches the same two rows in FarmerGroups.

The reason for the duplication is the field TEST_GROUP in FarmerGroups Table.
But you don't need this field in the Join.
First,a CTE to get the info you need in the join without duplicates.
then your old join to the new CTE.
Try this:
WITH FarmersGroup AS
(
SELECT DISTINCT
BSI_CODE
, CROP_SEASON
, BRANCH
FROM FarmerGroups
)
, Summary AS
(
SELECT
Branch = B_NAME
, Location = LOC
, Gallons = SUM(payment)
, FeeCollected = SUM(case when printed = 1 THEN Fee ELSE NULL END)
, FeeNotCollected = SUM(case when printed = 0 THEN Fee ELSE NULL END)
, GallonsIssued = SUM(case when printed = 1 THEN Payment ELSE NULL END)
, GallonsNotIssued = SUM(case when printed = 0 THEN Payment ELSE NULL END)
FROM SicbWeeklyDeliveriesFuel F
JOIN FarmerGroup G ON G.BSI_CODE = F.BSI_CODE
AND G.CROP_SEASON = F.CROP_SEASON
AND G.BRANCH = F.B_NAME
WHERE F.CROP_SEASON = #cropseason
GROUP BY
B_NAME, LOC
)
SELECT
Branch
, Location
, Gallons
, GallonsIssued
, GallonsNotIssued
, FeeCollected
, FeeNotCollected
, pct_GallonsCollected = ((GallonsIssued/Gallons) * 100)
FROM Summary
ORDER BY
Location
, Branch

You can use Andy's code above and it should do the job or you can just replace the table join in your current query
Change the following
Inner Join FarmerGroups G ON G.BSI_CODE = F.BSI_CODE AND G.CROP_SEASON = F.CROP_SEASON AND F.B_NAME = G.BRANCH
to
Inner join (select SELECT DISTINCT
BSI_CODE
, CROP_SEASON
, BRANCH
FROM FarmerGroups ) G on
ON G.BSI_CODE = F.BSI_CODE AND G.CROP_SEASON = F.CROP_SEASON AND F.B_NAME = G.BRANCH

Related

CASE statement in SQL giving not proper values

I am having abnormal values when I run this part in my sql code. SQL syntax wise, everything is okay with this?
select
COUNT(CASE WHEN bt.idBillingStatus = 2
THEN 1
ELSE NULL END) AS successfulbillinghits,
SUM(CASE WHEN bt.idBillingStatus = 2
THEN price
ELSE 0.0 END)
AS old_revenue
from table
Overall Query is this. The result of successfulbillinghits should be equal to timesbilled
SELECT
cs.idCustomerSubscription,
cs.msisdn,
pro.name AS promoterName,
c.name AS ClubName,
c.idClub AS ClubID,
o.name AS operatorName,
o.idOperator AS OperatorID,
co.name AS country,
-- cu.customerSince AS CustomerSince,
cs.subscribeddate AS subscribeddate,
-- cs.subscriptionNotificationSent AS SubNotificationSent,
-- cs.eventId AS EventId,
cs.unsubscribeddate AS unsubscribeddate,
cs.firstBillingDate AS FirstBillingDate,
cs.lastBilledDate As LastBilledDate,
cs.lastAttemptDate AS LastAttemptDate,
-- smp.code AS packageName,
-- o.mfactor AS mmfactor,
-- cs.idSubscriptionSource AS SubscriptionChannel,
-- cs.idUnsubscribeSource AS UnsubscriptionChannel,
-- DATE(bt.creationDate) AS BillingCreationDate,
-- bt.price AS pricePerBilling,
-- cs.lastRetryDate As LastRetryDate,
-- cs.lastRenewalDate AS LastRenewalDate,
-- cs.isActive AS ActiveStatus,
-- COUNT(bt.idBillingTransaction) AS BillingAttempts,
curr.idcurreny_symbol AS CurrencyID,
curr.symbol AS currency,
date(bt.creationDate) AS BillingDate,
cs.lastBilledAmount As LastBilledAmount,
cs.timesbilled,
price,
-- sum(price),
-- revenueShareAmountLocal,
-- o.mfactor,
-- count(IFF (bt.idBillingStatus = 2,1,0)) as otherversion,
count(CASE WHEN bt.idBillingStatus = 2
THEN 1
ELSE 0 END) AS successfulbillinghits,
SUM(CASE WHEN bt.idBillingStatus = 2
THEN price
ELSE 0.0 END)
AS old_revenue
FROM
customersubscription cs
LEFT JOIN
billing_transaction bt
ON CONVERT(cs.msisdn USING latin1) = bt.msisdn
AND cs.idClub = bt.idClub
AND bt.creationDate BETWEEN cs.SubscribedDate AND COALESCE(cs.UnsubscribedDate, now())
INNER JOIN customer cu ON (cs.idCustomer = cu.idcustomer)
INNER JOIN operator o ON (o.idoperator = cu.idoperator)
INNER JOIN country co ON (co.`idCountry` = o.idCountry)
INNER JOIN curreny_symbol curr ON (curr.idcurreny_symbol = co.idCurrencySymbol)
LEFT JOIN Promoter pro ON cs.idPromoter = pro.id
INNER JOIN club_operator_relationships cor ON cor.clubId = cs.idClub
INNER JOIN club c ON c.idClub = cs.idClub
-- INNER JOIN operator op ON op.idOperator = cu.idOperator
WHERE
-- (cs.timesbilled > 0 and cs.subscribeddate < '2016-09-01 00:00:00' )
cs.subscribeddate between '2017-04-20 00:00:00' and '2017-04-21 00:00:00'
AND cs.idClub IN (39)
GROUP BY idCustomerSubscription, ClubName, operatorName, promoterName
Successfulbillinghits is much greater than timesbilled in the result
Instead of COUNTuse SUM, as count counts blanks or nulls also
select
SUM(CASE WHEN bt.idBillingStatus = 2
THEN 1
ELSE 0 END) AS successfulbillinghits,
SUM(CASE WHEN bt.idBillingStatus = 2
THEN price
ELSE 0.0 END)
AS old_revenue
from table
Instead of using CASE, you can use WHERE clause with these aggregate functions, e.g.:
SELECT COUNT(*) as `successfulbillinghits`, SUM(price) as `old_revenue`
FROM table bt
WHERE bt.idBillingStatus = 2;

How to find the row with the maximum matching columns?

If there are three tables, TableItem, TableAbcd and TablePqrs, as below
TableItem
ID item
1 item1
TableAbcd
ID Item ColA ColB ColC ColD
1 item1 A1 B1 C1 D1
TablePqrs
ID item ColA ColB ColC ColD ColValue
1 item1 A1 B1 null null 10000
2 item1 A1 B1 C1 D1 100
Here, for a given Item, There has to be just one record in the output which has the maximum columns matching in TableAbcd and TablePqrs.
Since row 1 of TableAbcd has maximum matching columns with TablePqrs row 2.
My output for join with above three tables should be,
item ColA ColB ColC ColD ColValue
item1 A1 B1 C1 D1 100
Code tried so far,
Select item, ColA, ColB, ColC, ColD, ColValue
FROM TableItem a
LEFT OUTER JOIN TableAbcd b
ON a.item = b.item
LEFT OUTER JOIN TablePqrs c
ON (b.ColA = c.ColA AND b.ColB = c.ColB AND b.ColC = c.ColC AND b.ColD = c.ColD)
OR (b.ColA = c.ColA AND b.ColB = c.ColB AND b.ColC = c.ColC)
OR (b.ColA = c.ColA AND b.ColB = c.ColB)
if fetch's me two records, i know there may be design issues, but we are getting data from third party legacy system, which has table structure as per its needs and sending this to another interface.
Please suggest.
Here the question is: How many columns match between B and C?
For the join clause you only need that at least one column of b matches the same column in c:
from c
left join b
on c.A = b.A or c.B = b.B or c.C = b.C or c.D = b.D
You can calc it by:
(case when c.A = b.A then 1 else 0 end)
+ (case when c.B = b.B then 1 else 0 end)
+ (case when c.C = b.B then 1 else 0 end)
+ (case when c.D = b.D then 1 else 0 end) as matches
Then simply order by matching rows (descendant) and limit the result to 1 row.
select
c.id, c.item, c.A, c.B, c.C, c.D, c.colValue,
(case when c.A = b.A then 1 else 0 end)
+ (case when c.B = b.B then 1 else 0 end)
+ (case when c.C = b.B then 1 else 0 end)
+ (case when c.D = b.D then 1 else 0 end) as matches
from c
left join b
on c.A = b.A or c.B = b.B or c.C = b.C or c.D = b.D
order by
((case when c.A = b.A then 1 else 0 end)
+ (case when c.B = b.B then 1 else 0 end)
+ (case when c.C = b.B then 1 else 0 end)
+ (case when c.D = b.D then 1 else 0 end)) desc
limit 1;
I've set up a rextester example just to check it: http://rextester.com/IPA67860
With TableAbcd called a and TablePqrs called p, the number of matches is (p.cola = a.cola) + (p.colb = a.colb) + (p.colc = a.colc) + (p.cold = a.cold), because in MySQL true is 1 and false is 0.
Now you are looking for the p records for which no other p record exists with a higher number of matches:
select *
from tablepqrs p1
where not exists
(
select *
from tablepqrs p2
join tableabcd a on a.item = p2.item
where p2.item = p1.item
and (p2.cola = a.cola) + (p2.colb = a.colb) + (p2.colc = a.colc) + (p2.cold = a.cold) >
(p1.cola = a.cola) + (p1.colb = a.colb) + (p1.colc = a.colc) + (p1.cold = a.cold)
);
In the code below you can see another option to filter. Its a similar approach to the one proposed by McNets, but using window functions.
The key is to compute a ranking which allows to determine the TablePqrs row with the best match. In the other hand, if two rows have the same ranking for the same item value, we have to use additional criteria to undo the tie. in the example, the criteria is the ID of the TableAbcd table. I'm not using outer joins so there will be no results for TableItems records without match ranking.
I'm not pretty sure if it really fits what you really want, just try it and get your own conclusions.
SELECT TableItem.id,
TableItem.item,
TablePqrs.colA,
TablePqrs.colB,
TablePqrs.colC,
TablePqrs.colD,
TablePqrs.value
FROM TableItem
INNER JOIN (SELECT DISTINCT
tableItemId,
FIRST_VALUE(tablePqrsId) OVER (PARTITION BY tableItemId ORDER BY ranking DESC, tablePqrsId DESC) tablePqrsId
FROM (SELECT rankTableItem.ID tableItemId,
rankTablePqrs.ID tablePqrsId,
CASE WHEN rankTablePqrs.colA IS NULL THEN 0 ELSE 1 END +
CASE WHEN rankTablePqrs.colB IS NULL THEN 0 ELSE 1 END +
CASE WHEN rankTablePqrs.colC IS NULL THEN 0 ELSE 1 END +
CASE WHEN rankTablePqrs.colD IS NULL THEN 0 ELSE 1 END ranking
FROM TableItem rankTableItem
INNER JOIN TableAbcd rankTableAbcd ON rankTableItem.item = rankTableAbcd.item
INNER JOIN TablePqrs rankTablePqrs ON rankTablePqrs.item = rankTableAbcd.item
AND (rankTableAbcd.colA = rankTablePqrs.colA
OR rankTableAbcd.colB = rankTablePqrs.colB
OR rankTableAbcd.colC = rankTablePqrs.colC
OR rankTableAbcd.colD = rankTablePqrs.colD))) pivotTable ON pivotTable.tableItemId = TableItem.Id
INNER JOIN TablePqrs ON TablePqrs.Id = pivotTable.tablePqrsId
I tried the below thing and it worked, the coalesce helps me prioritise which value to pick depending upon the order i mention in it.
Select item, ColA, ColB, ColC, ColD, ColValue
FROM TableItem a
LEFT OUTER JOIN (
SELECT item,
COALESCE(c1.ColValue,c2.ColValue,c3.ColValue) ColValue
FROM abc b
LEFT OUTER JOIN pqr c1
ON b.ColA = c1.ColA AND b.ColB = c1.ColB AND b.ColC = c1.ColC AND b.ColD = c1.ColD
LEFT OUTER JOIN pqr c2
ON b.ColA = c2.ColA AND b.ColB = c2.ColB AND b.ColC = c2.ColC
LEFT OUTER JOIN pqr c3
ON b.ColA = c3.ColA AND b.ColB = c3.ColB
GROUP BY item
) as Fact
ON Fact.item = a.item

How to split SQL query results into columns based on two WHERE conditions and two calculated COUNT fields?

I have the following (simplified) database schema:
Persons:
[Id] [Name]
-------------------
1 'Peter'
2 'John'
3 'Anna'
Items:
[Id] [ItemName] [ItemStatus]
-------------------
10 'Cake' 1
20 'Dog' 2
ItemDocuments:
[Id] [ItemId] [DocumentName] [Date]
-------------------
101 10 'CakeDocument1' '2016-01-01 00:00:00'
201 20 'DogDocument1' '2016-02-02 00:00:00'
301 10 'CakeDocument2' '2016-03-03 00:00:00'
401 20 'DogDocument2' '2016-04-04 00:00:00'
DocumentProcessors:
[PersonId] [DocumentId]
-------------------
1 101
1 201
2 301
I have also set up an SQL fiddle to play with: http://www.sqlfiddle.com/#!3/e6082
The relation logic is the following: every Person can work on zero or infinite number of ItemDocuments (many-to-many); each ItemDocument belongs to exactly one Item (one-to-many). Item has status 1 - Active, 2 - Closed
What I need is a report that fulfills the following requirements:
for each person in Persons table, display count of Items that have ItemDocuments related to this person
the counts should be split in two columns by ItemStatus
the query should be filterable by two optional date periods (using two BETWEEN conditions on ItemDocuments.Date field) and the Item counts should also be split into two periods
if a Person does not have any ItemDocuments assigned, it still should be shown in the results with all count values set to 0
if a Person has more than one ItemDocument for an Item, the Item still should be counted only once
Essentially, here is how the results should look like if I use both periods to NULL (to read all the data):
[PersonName] [Active Items for period 1] [Closed Items for period 1] [Active Items for period 2] [Closed Items for period 2]
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
'Peter' 1 1 1 1
'John' 1 0 1 0
'Anna' 0 0 0 0
While I can create an SQL query for each requirement separately, I have a problem to understand how to combine all of them together into one.
For example, I can split ItemStatus counts in two columns using
COUNT(CASE WHEN t.ItemStatus = 1 THEN 1 ELSE NULL END) AS Active,
COUNT(CASE WHEN t.ItemStatus = 2 THEN 1 ELSE NULL END) AS Closed
and I can filter by two periods (with max/min date constants from MS SQL server specification to avoid NULLs for optional period dates) using
between coalesce(#start1, '1753-01-01') and coalesce(#end1, '9999-12-31')
between coalesce(#start2, '1753-01-01') and coalesce(#end2, '9999-12-31')
but how to combine all of this together, considering also JOINs between tables?
Is there any technique, join or MS SQL Server specific approach to do this in efficient way?
My first attempt seems to work as required but it looks like ugly subquery duplications multiple times:
DECLARE #start1 DATETIME, #start2 DATETIME, #end1 DATETIME, #end2 DATETIME
-- SET #start2 = '2017-01-01'
SELECT
p.Name,
(SELECT COUNT(1)
FROM Items i
WHERE i.ItemStatus = 1 AND EXISTS(
SELECT 1
FROM DocumentProcessors AS dcp
INNER JOIN ItemDocuments AS idc ON dcp.DocumentId = idc.Id
WHERE dcp.PersonId = p.Id AND idc.ItemId = i.Id
AND idc.Date BETWEEN COALESCE(#start1, '1753-01-01') AND COALESCE(#end1, '9999-12-31')
)
) AS Active1,
(SELECT COUNT(*)
FROM Items i
WHERE i.ItemStatus = 2 AND EXISTS(
SELECT 1
FROM DocumentProcessors AS dcp
INNER JOIN ItemDocuments AS idc ON dcp.DocumentId = idc.Id
WHERE dcp.PersonId = p.Id AND idc.ItemId = i.Id
AND idc.Date BETWEEN COALESCE(#start1, '1753-01-01') AND COALESCE(#end1, '9999-12-31')
)
) AS Closed1,
(SELECT COUNT(1)
FROM Items i
WHERE i.ItemStatus = 1 AND EXISTS(
SELECT 1
FROM DocumentProcessors AS dcp
INNER JOIN ItemDocuments AS idc ON dcp.DocumentId = idc.Id
WHERE dcp.PersonId = p.Id AND idc.ItemId = i.Id
AND idc.Date BETWEEN COALESCE(#start2, '1753-01-01') AND COALESCE(#end2, '9999-12-31')
)
) AS Active2,
(SELECT COUNT(*)
FROM Items i
WHERE i.ItemStatus = 2 AND EXISTS(
SELECT 1
FROM DocumentProcessors AS dcp
INNER JOIN ItemDocuments AS idc ON dcp.DocumentId = idc.Id
WHERE dcp.PersonId = p.Id AND idc.ItemId = i.Id
AND idc.Date BETWEEN COALESCE(#start2, '1753-01-01') AND COALESCE(#end2, '9999-12-31')
)
) AS Closed2
FROM Persons p
I'm not absolutely sure if I really got what you want, but you might try this
WITH AllData AS
(
SELECT p.Id AS PersonId
,p.Name AS Person
,id.Date AS DocDate
,id.DocumentName AS DocName
,i.ItemName AS ItemName
,i.ItemStatus AS ItemStatus
,CASE WHEN id.Date BETWEEN COALESCE(#start1, '1753-01-01') AND COALESCE(#end1, '9999-12-31') THEN 1 ELSE 0 END AS InPeriod1
,CASE WHEN id.Date BETWEEN COALESCE(#start2, '1753-01-01') AND COALESCE(#end2, '9999-12-31') THEN 1 ELSE 0 END AS InPeriod2
FROM Persons AS p
LEFT JOIN DocumentProcessors AS dp ON p.Id=dp.PersonId
LEFT JOIN ItemDocuments AS id ON dp.DocumentId=id.Id
LEFT JOIN Items AS i ON id.ItemId=i.Id
)
SELECT PersonID
,Person
,COUNT(CASE WHEN ItemStatus = 1 AND InPeriod1 = 1 THEN 1 ELSE NULL END) AS ActiveIn1
,COUNT(CASE WHEN ItemStatus = 2 AND InPeriod1 = 1 THEN 1 ELSE NULL END) AS ClosedIn1
,COUNT(CASE WHEN ItemStatus = 1 AND InPeriod2 = 1 THEN 1 ELSE NULL END) AS ActiveIn2
,COUNT(CASE WHEN ItemStatus = 2 AND InPeriod2 = 1 THEN 1 ELSE NULL END) AS ClosedIn2
FROM AllData
GROUP BY PersonID,Person

GROUP BY on table returning incorrect counts in MySQL with LEFT JOIN

I am trying to return multiple counts and averages from multiple tables sorting by gender and am getting incorrect data. I understand that the following is incorrect, but I am unsure of how to fix it. (Edit: Problem with group by gender. See below.)
Here is the query:
SELECT c.gender AS 'Gender',
COUNT(DISTINCT mr.mailing_recipient_id) AS 'Mailing Recipients',
(SELECT COUNT(DISTINCT CASE WHEN mrc.mailing_recipient_click_type_id = 2 THEN 1 ELSE 0 END) ) AS 'Open Total',
AVG(CASE WHEN mrc.mailing_recipient_click_type_id = 2 THEN 1 ELSE 0 END) AS 'Avg Open',
(SELECT COUNT(DISTINCT CASE WHEN mrc.mailing_recipient_click_type_id = 1 THEN 1 ELSE 0 END) ) AS 'Click Total',
AVG(CASE WHEN mrc.mailing_recipient_click_type_id = 1 THEN 1 ELSE 0 END) AS 'Avg Click',
COUNT(DISTINCT ca.cons_action_contribution_id) AS Donations,
AVG(ca.transaction_amt) AS 'Avg Donation Amt'
FROM ((mailing m
LEFT JOIN mailing_recipient mr ON m.mailing_id = mr.mailing_id)
LEFT JOIN mailing_recipient_click mrc ON mr.mailing_recipient_id = mrc.mailing_recipient_id
LEFT JOIN cons_action_contribution ca ON mr.cons_id = ca.cons_id
LEFT JOIN cons c ON c.cons_id = ca.cons_id)
WHERE m.mailing_id = 1
AND gender IS NOT NULL
GROUP BY c.gender;
Here is the table which would be correct if the totals in the fields were correct:
GENDER Mailing Recipient Open Total Avg Open Click Total Avg Click Donations Avg Amt
F 105 2 0.5000 2 0.5000 105 22.5000
M 98 2 0.5000 2 0.5000 98 18.8780
EDIT: Here is an example of what I am hoping to achieve. I am certain that the above values are being repeated. The below values are just examples of what I am expecting:
GENDER Mailing Recipient Open Total Avg Open Click Total Avg Click Donations Avg Amt
F 105 8 0.0761 4 0.0380 2 22.5000
M 98 2 0.0204 1 0.0102 1 18.8000
Edit:
After playing around a bit, I thought that I had discovered that the joining the cons table was what is giving me problematic returns, but the problem is with GROUP BY when using gender. To illustrate, this query (which is grouped by mailing name instead of gender) works beautifully.
select m.mailing_name AS 'mailing',
COUNT(DISTINCT mr.mailing_recipient_id) AS 'Mailing Recipients',
SUM(CASE
when mrc.mailing_recipient_click_type_id = 2 THEN 1
END)
AS 'Open Total',
AVG(CASE
WHEN mrc.mailing_recipient_click_type_id = 2 THEN 1
ELSE 0
END) AS 'Avg Open',
SUM(CASE
WHEN mrc.mailing_recipient_click_type_id = 1 THEN 1
END)
AS 'Click Total',
AVG(CASE
WHEN mrc.mailing_recipient_click_type_id = 1 THEN 1
ELSE 0
END) AS 'Avg Click',
COUNT(ca.cons_action_contribution_id) AS Donations,
AVG(ca.transaction_amt) AS 'Avg Donation Amt'
FROM
mailing m
LEFT JOIN mailing_recipient mr ON m.mailing_id = mr.mailing_id
LEFT JOIN mailing_recipient_click mrc
ON mr.mailing_recipient_id = mrc.mailing_recipient_id
LEFT JOIN cons_action_contribution ca ON mr.cons_id = ca.cons_id
LEFT JOIN cons c ON mr.cons_id = c.cons_id
WHERE m.mailing_id = 1
GROUP BY m.mailing_name;
The statement is identical with the exception of the first and last lines.
Try this:
I'm not sure what you mean by Avg Open and Avg Click.
SELECT c.gender AS 'Gender',
COUNT(DISTINCT mr.mailing_recipient_id) AS 'Mailing Recipients',
SUM(CASE WHEN mrc.mailing_recipient_click_type_id = 2 THEN 1 ELSE 0 END) AS 'Open Total',
AVG(CASE WHEN mrc.mailing_recipient_click_type_id = 2 THEN 1 ELSE 0 END) AS 'Avg Open',
SUM(CASE WHEN mrc.mailing_recipient_click_type_id = 1 THEN 1 ELSE 0 END) AS 'Click Total',
AVG(CASE WHEN mrc.mailing_recipient_click_type_id = 1 THEN 1 ELSE 0 END) AS 'Avg Click',
COUNT(DISTINCT ca.cons_action_contribution_id) AS Donations,
AVG(ca.transaction_amt) AS 'Avg Donation Amt'
FROM mailing m
LEFT JOIN mailing_recipient mr ON m.mailing_id = mr.mailing_id
LEFT JOIN mailing_recipient_click mrc ON mr.mailing_recipient_id = mrc.mailing_recipient_id
LEFT JOIN cons_action_contribution ca ON mr.cons_id = ca.cons_id
LEFT JOIN cons c ON c.cons_id = ca.cons_id
WHERE m.mailing_id = 1
AND gender IS NOT NULL
GROUP BY c.gender;
I also think that mrc.mailing_recipient_click_type_id = 2 means open and mrc.mailing_recipient_click_type_id = 1 mean click seems strange to me. I would expect this data to be exclusive and stored in two different fields.

Concatenating row values sql server 2008 r2

I have two tables register and att_bottom and I want to display only the students at a certain building who have been tardy based on today's date with the periods separated by a comma.
This is the way the data is displayed when joining both tables:
Student ID | Building | Period | Grade
12345 2 1 11
12345 2 5 11
43210 2 1 12
I want this:
Student ID | <u>Building | Period | Grade
12345 2 1,5 11
43210 2 1 12
This is my query:
select r.STUDENT_ID,
r.BUILDING ,
(select ab.attendancePeriod + ','
from att_bottom ab
where ab.STUDENT_ID = r.student_id
and ab.building = '2'
and ab.attendance_c ='T'
and ab.SCHOOL_YEAR =2014
CONVERT(date,ab.attendance_date,102) = convert(date,getdate(),102)
FOR XML PATH ('') ) AS PERIODS,
r.GRADE
FROM register r
where r.CURRENT_STATUS = 'A'
and r.BUILDING ='2'
I'm getting all the students at building 2 and even if they don't have an attedance_c of T; a NULL value for Periods is being retrieved:
Student ID | Building | Period | Grade
12345 2 1 , 5 11
43210 2 1 , 12
95687 2 NULL 09
78417 2 NULL 10
20357 2 NULL 11
I have tried and ab.attendancePeriod is Not NULL and I still get the same results.
Any thoughts?
The outer query doesn't listen to any filters in the subquery; it will return NULL for any rows that aren't matched by the join conditions. You need to filter differently. Here is one way (this also eliminates the errant trailing comma, and avoids comparing dates by converting them expensively to strings):
;WITH x AS
(
SELECT DISTINCT s = r.Student_ID, r.building,
p = ab.attendancePeriod, r.grade
FROM dbo.Register AS r
INNER JOIN dbo.att_bottom AS ab
ON r.Student_ID = ab.Student_ID
AND r.building = ab.building
WHERE ab.building = '2'
AND ab.attendance_c = 'T'
AND ab.SCHOOL_YEAR = 2014
AND ab.attendance_date >= CONVERT(DATE, GETDATE())
AND ab.attendance_date < DATEADD(DAY, 1, CONVERT(DATE, GETDATE()))
AND r.building = '2'
AND r.CURRENT_STATUS = 'A'
)
SELECT DISTINCT
[Student ID] = x.s,
x.building,
Period = STUFF((SELECT ',' + x2.p FROM x AS x2 WHERE x2.s = x.s
FOR XML PATH(''),
TYPE).value(N'./text()[]',N'nvarchar(max)'),1,1,''),
x.grade
FROM x;
Another way:
SELECT DISTINCT
r.Student_ID,
r.building,
Period = STUFF(b.p.value(N'./text()[1]', N'nvarchar(max)'),1,1,''),
r.grade
FROM dbo.Register AS r
CROSS APPLY
(
SELECT p = ',' + ab.attendancePeriod
FROM dbo.att_bottom AS ab
WHERE ab.building = '2'
AND ab.attendance_c = 'T'
AND ab.SCHOOL_YEAR = 2014
AND ab.attendance_date >= CONVERT(DATE, GETDATE())
AND ab.attendance_date < DATEADD(DAY, 1, CONVERT(DATE, GETDATE()))
AND ab.student_id = r.student_id
AND ab.building = r.building
FOR XML PATH(''),TYPE
) AS b(p)
WHERE b.p IS NOT NULL
AND r.building = '2'
AND r.CURRENT_STATUS = 'A';
Move the AS PERIODS select to be an inner join to r.