Query returns no results - sql-server

I am running a query that counts emails sent by customers, based on their subject.
DECLARE #LastMonthNo varchar(2)
DECLARE #LastMYear varchar(4)
SET #LastMonthNo = DATEPART(m,DATEADD(m,-1,GetDate()))
SET #LastMYear = DATEPART(yyyy,DATEADD(m,-1,GetDate()));
SELECT
CustID, CustName, CustEmail,
ISNULL(SUM(CASE WHEN EmailSubject LIKE 'KeyWord' THEN 1 END),0) AS TotalEmail
FROM
TableEmails
WHERE
DATEPART(M, DATESENT) = #LastMonthNo
AND DATEPART(YYYY, DATESENT) = #LastYearNo
GROUP BY CustID, CustName, CustEmail
For some customers, the query returns no results. I do not mean NULL, I mean there is no record at all. However, I need to identify those customers.
What can I do to get the query to generate some sort of results? A 0 would be perfect.

Try something like this..
SELECT CustID, CustName, CustEmail,
SUM(CASE WHEN EmailSubject LIKE 'KeyWord'
AND DATEPART(YYYY,DATESENT)=#LastYearNo
AND DATEPART(YYYY,DATESENT)=#LastYearNo
THEN 1 ELSE 0 END) AS TotalEmail,
FROM TableEmails
GROUP BY CustID, CustName, CustEmail
What is the difference?
WHERE part executes before GROUP BY. So, with your query, you are grouping your results after other customers are filtered out. If you move that condition to CASE statement, you will check that condition on each record in the table regardless of dates. Hope that makes sense.

Related

How to get the latest not null value from multiple columns in SQL or Azure Synapse

I have data like in the below format
I want output in the below format
Please help me with the SQL code. Thanks !
Like I mention in the comments, you need to fix whatever it is that's inserting the data and not lose the values so that they become NULL in "newer" rows.
To get the results you want, you'll going to have to use row numbering and conditional aggregation, which is going to get messy the more columns you have; and why you need to fix the real problem. This will look something like this:
WITH CTE AS(
SELECT GroupingColumn,
NullableCol1,
NullableCol2,
DateColumn,
CASE WHEN NullableCol1 IS NOT NULL THEN ROW_NUMBER() OVER (PARTITION BY GroupingColumn, CASE WHEN NullableCol1 IS NULL THEN 1 ELSE 0 END ORDER BY DateColumn DESC) AS NullableCol1RN,
CASE WHEN NullableCol2 IS NOT NULL THEN ROW_NUMBER() OVER (PARTITION BY GroupingColumn, CASE WHEN NullableCol2 IS NULL THEN 1 ELSE 0 END ORDER BY DateColumn DESC) AS NullableCol2RN
FROM dbo.YourTable)
SELECT GroupingColumn,
MAX(CASE NullableCol1RN WHEN 1 THEN NullableCol1 END) AS NullableCol1,
MAX(CASE NullableCol2RN WHEN 1 THEN NullableCol2 END) AS NullableCol2,
MAX(DateColumn) AS DateColumn
FROM CTE;

What is the difference between my 2 SUM(CASE()) examples?

So I have 2 queries, 1 works like I would expect and the other one doesn't. Here's the one that works like I expect, it's a SUMIF using a CASE statement:
SELECT
PartNo,
SUM(ActualPcsGood) AS Pcs,
SUM(CASE WHEN Status = 'Current' THEN ActualPcsGood END) AS [Current],
SUM(CASE WHEN Status = 'Pending' THEN ActualPcsGood END) AS [Pending],
SUM(CASE WHEN Status = 'Future' THEN ActualPcsGood END) AS [Future],
SUM(CASE WHEN Status = 'Finished' THEN ActualPcsGood END) AS [Finished]
FROM OrderRouting
WHERE PartNo LIKE '20004%'
GROUP BY PartNo;
Output:
Now I have this other query that is confusing me, here's the code:
SELECT
JobNo,
UnitPrice,
SUM(CASE WHEN JobNo LIKE '10426%' THEN UnitPrice END) AS [OrderTotal]
FROM OrderDet
WHERE UnitPrice > 0
AND JobNo LIKE '10426%'
GROUP BY JobNo, UnitPrice;
Output:
My question is why is the 3rd column exactly the same as the second one? It's my intention that the third column is supposed to total the entire thing, meaning that the value for the 3rd column would be exactly the same for all rows. Why is it not? What is the major difference between my 2 examples?
This isn't tested but here are some ideas:
select dtl.JobNo, dtl.UnitPrice, tot.UnitPrice SumPrice, (dtl.UnitPrice/tot.UnitPrice)*100 pctTot
from
(SELECT
JobNo,
UnitPrice
FROM OrderDet
WHERE UnitPrice > 0
AND JobNo LIKE '10426%') dtl
cross join
(SELECT sum(UnitPrice) unitPrice
FROM OrderDet
WHERE UnitPrice > 0
AND JobNo LIKE '10426%') tot
OR
SELECT
JobNo,
UnitPrice,
(select sum(UnitPrice) from OrderDet where UnitPrice > 0 and JobNo like '10426%') totPrice
FROM OrderDet
WHERE UnitPrice > 0
AND JobNo LIKE '10426%'
GROUP BY JobNo;
At first look everything looks fine. I would say that your number of records per unit price is one per job for the this record set.
Add a COUNT(*) to see how many records are being summed up.
The other thought is that you have a quantity field on the record and your case statement should really be:
SUM(CASE WHEN JobNo LIKE '10426%' THEN UnitPrice * Quantity END) AS [OrderTotal]
Hope that helps.
Group by sums the values within the grouping columns. Your grouping columns are Job and UnitPrice. Because you have a unique JobNo, UnitPrice, it's hard to see what it's doing. Try adding a duplicate UnitPrice, JobNo row in your data source so you can see what's actually doing.
I'm not sure why you would want to show the sum total in this way though. I would use a rollup which would show the total at the bottom.
Your first query groups by only PartNo. So you SUM with Case statement work for each unique PartNo.
Your second query however groups by JobNo and UnitPrice. so your SUM runs for each group of JobNo and UnitPrice, which is only single row. Hence same result as UnitPrice. Assuming each jobid as unique unit price try query below. You don't need CASE inside SUM as WHERE clause will take care of it.
SELECT
JobNo,
MIN(UnitPrice) as UnitPrice,
SUM(UnitPrice) AS [OrderTotal]
FROM OrderDet
WHERE UnitPrice > 0
AND JobNo LIKE '10426%'
GROUP BY JobNo;
But why are you adding a case when you are already filtering by '10426%' condition?
your query will return only those records with '10426%' as Job No. Grouping by both job and Unitprice will give you single row for different unit prices of same job. Below query should be enough. If you are having different unit price for '10426%', we cannot get unit price in a single row.
SELECT
JobNo,
SUM(UnitPrice) AS [OrderTotal]
FROM OrderDet
WHERE UnitPrice > 0
AND JobNo LIKE '10426%'
GROUP BY JobNo;

Filtering values based on the date ranges

Request your help in acheiving the following result from the date set below
I have the below result set
CampaignName Matchfrom MatchTo
a 08-09-2013 07-11-2013
a 10-09-2013 10-11-2013
a 08-11-2013 07-01-2014
a 09-11-2013 08-01-2014
above set is sorted on matchfrom date column. First row will be considered as a master
now the query should filter out the rows in which matchfrom lies in the date range of the master.
This, I achieved using a self join. But now the third row is completely out of range of the master(1st row). This should now be considered as the master and it should filter out the 4th row.
Final result set will be like the below, marked as pass and fail
CampaignName Matchfrom MatchTo
a 08-09-2013 07-11-2013 PASS
a 10-09-2013 10-11-2013 FAIL
a 08-11-2013 07-01-2014 PASS
a 09-11-2013 08-01-2014 FAIL
Can someone advise me on this
With you data you'll have to do a bit more scrubbing but the code below should get you in the right direction. You have to be careful because your MatchFrom and MatchTo in your "Master Record"go opposite directions than all of your other data.
CREATE TABLE #tmpCampaign(
CampaignName varchar(1),
Matchfrom Date,
MatchTo Date
)
INSERT INTO #tmpCampaign VALUES
('a','08-09-2013','07-11-2013'),
('a','10-09-2013','10-11-2013'),
('a','08-11-2015','07-01-2014'),
('a','09-11-2013','08-01-2014')
;WITH Campaign AS(
SELECT *,
ROW_NUMBER() OVER (PARTITION BY campaignName ORDER BY MatchFrom) as CampRank
FROM #tmpCampaign)
SELECT c1.*, c2.MatchFrom as MasterFrom, c2.MatchTo as MasterTo,
CASE WHEN c1.Matchfrom >= c2.MatchFrom AND c1.Matchfrom <= c2.MatchTo THEN 'Pass'
ELSE 'Fail' END as PassFail
FROM Campaign as c1
JOIN Campaign as c2
ON c1.CampaignName = c2.CampaignName and c2.CampRank = 1
may be this is create problem when date duplication happens but as for your result set i have picked the datekey and done the partition according to that to achieve results
;With Cte as
(select Campaignname,
matchfrom,
matchto,
ROW_number()OVER(PARTITION BY right(matchfrom, len(matchfrom) - charindex('-', matchfrom) - 3)ORDER BY Campaignname)RN
from #tmpCampaign )
select Campaignname,
matchfrom,
matchto,
Case when RN = 1 then 'Pass' ELSE 'Fail' END
from Cte

Use OVER (PARTITION BY ) instead of Group By

right now I am using temp table in my sql query but I want to use Partition By function instead.
My temp table query is given below:
drop table #Temp;
create table #Temp
(
NAME varchar(50),
EMPID varchar(50),
SS MONEY,
PP MONEY
);
insert into #Temp
select * From
(
select
p1.NAME,
p1.EMPID,
case when p1.AmtPayer = 'SELF' then sum(p1.Salary) else 0 end as S,
case when p1.AmtPayer = 'MANAGER' then sum(p1.Salary) else 0 end as P
from Candidate p1
group by p1.Name, p1.EMPID, p1.AmtPayer
) as P;
select
t.NAME,
t.EMPID,
sum(t.SS) as 'SELF PAID',
sum(t.PP) as 'PARTY PAID'
from #Temp t
group by t.NAME, t.EMPID;
I am getting the expected result as well but I want to perform this operation using Partition function , I tried for it but result is not accurate -
select
NAME,
EMPID,
sum(Salary) over (partition by AmtPayer) as Total
from dbo.Candidate
Output is:
Vivek 0001 300.00
Vivek 0001 300.00
Vivek 0001 6200.00
Vivek 0001 6200.00
Vivek 0001 6200.00
But I need:
Vivek 0001 6200.00 300.00
To do exactly what you want, try this:
select
Name, EmpId,
sum(case when AmtPayer = 'SELF' then Salary else 0 end) as [Self],
sum(case when AmtPayer = 'MANAGER' then Salary else 0 end) as [Manager]
from dbo.Candidate
group by Name, EmpId;
You can use case statements in aggregate functions, which enables you to do a lot of crazy stuff :)
However, as noted in my comments to your question, this is only useful if you have a fixed number of AmtPayer variants that you know in advance.
To elaborate more: partition by is explicitly designed not to reduce the result set. It will still return one row per row, and there's nothing you can do to change that - if you do want to reduce the result set, you use group by instead. Combined with all the complex stuff you can do with aggregate functions, this is actually a very powerful tool - and that applies to both partition by and group by. Also note that partition by can be much slower than group by. In fact, I found out that using partition by to get result count (ie. count over (partition by NULL) or something similar) is much slower than simply doing two queries, one just for the count, and the other for the actual results.
Don't assume your way is better because it looks smarter - always measure. Profiling is your friend. Systems like SQL Server are doing a lot of optimizations that try all the time to give you great performance for seemingly stupid queries :)
I used below query:
DROP TABLE #Temp
CREATE TABLE #Temp(
NAME VARCHAR(50),
EMPID VARCHAR(50),
SS MONEY,
PP MONEY
)
INSERT INTO #Temp
Select * From(
SELECT DISTINCT
NAME,EMPID,
SUM(CASE WHEN AmtPayer='SELF' then Salary ELSE 0 end) OVER (PARTITION BY AmtPayer) AS SS ,
SUM(CASE WHEN AmtPayer='MANAGER' THEN Salary ELSE 0 end) OVER (PARTITION BY AmtPayer) AS PP
FROM dbo.Candidate
)AS P
SELECT DISTINCT t.NAME ,t.EMPID ,SUM(t.SS) OVER(PARTITION BY t.NAME,t.EMPID) AS 'SELF PAID',
SUM(t.PP) OVER(PARTITION BY t.NAME,t.EMPID) AS 'PARTY PAID' FROM #Temp t
--GROUP BY t.NAME ,t.EMPID

SSRS 2008 R2 - evaluating running total only on change of group

I have a report where I capture patient information, some of which is stored in the patient table and some of which is stored in the observations table. Taking date of birth as my example, if I count all the records for which the DOB has been supplied, I get significantly more than the total number of patients, because of the join to the observations table. How do I evaluate the running total only once for each group?
Edit: some sample data over at http://sqlfiddle.com/#!3/27b91/1/0. If I count birthdates from that query, I want 2 as the answer; same for race and ethnicity.
The following may or may not be the right approach for your specific situation, but it can be a useful technique to have at your disposal.
You can add some code to your select statement to help yourself answer questions like these 'downstream' (either via added criteria or via SSRS). See this modification of your SQL Fiddle:
select pid, firstName, lastName, dateOfBirth, obsName, obsValue, obsDate,
rowRank, CASE rowRank WHEN 1 THEN 1 ELSE 0 END AS countableRow
from
(
select Person.pid, Person.firstName, Person.lastName, Person.dateOfBirth
, Obs.obsName, Obs.obsValue, Obs.obsDate,
ROW_NUMBER() OVER (PARTITION BY Person.pid, Person.firstName, Person.lastName, Person.dateOfBirth ORDER BY Obs.obsDate) AS rowRank
from Person
join Obs on Person.pId = Obs.pId
) rankedData
The rowRank field will create a group-relative ranking number, which may or may not be useful to you downstream. The countableRow field will be either 1 or 0 such that each group will have one and only one row with a 1 in it. Doing SUM(countableRow) will give you the proper number of groups in your data.
Now, you can extend this functionality (if you wish) by dumping out actual field values instead of a constant scalar like 1 in the first row of each group. So, if you had CASE rowRank WHEN 1 THEN dateOfBirth ELSE NULL END AS countableDOB, you could then, for example, get the total number of people with each distinct birthday using just this dataset.
Of course, you can do all those things using methods like #Russell's with SQL anyway, so this would be most relevant with specific downstream requirements that may not match your situation.
EDIT
Obviously the countableRow field there isn't a one-size-fits-all solution to the types of queries you want. I have added a few more examples of the PARTITION BY strategy to another SQL Fiddle:
select pid, firstName, lastName, dateOfBirth, obsName, obsValue, obsDate,
rowRank, CASE rowRank WHEN 1 THEN 1 ELSE 0 END AS countableRow,
valueRank, CASE valueRank WHEN 1 THEN 1 ELSE 0 END AS valueCount,
dobRank, CASE WHEN dobRank = 1 AND dateOfBirth IS NOT NULL THEN 1 ELSE 0 END AS dobCount
from
(
select Person.pid, Person.firstName, Person.lastName, Person.dateOfBirth
, Obs.obsName, Obs.obsValue, Obs.obsDate,
ROW_NUMBER() OVER (PARTITION BY Person.pid, Person.firstName, Person.lastName, Person.dateOfBirth ORDER BY Obs.obsDate) AS rowRank,
ROW_NUMBER() OVER (PARTITION BY Obs.obsName, Obs.obsValue ORDER BY Obs.obsDate) AS valueRank,
ROW_Number() OVER (PARTITION BY Person.dateOfBirth ORDER BY Person.pid) AS dobRank
from Person
join Obs on Person.pId = Obs.pId
) rankedData
Lest anyone misunderstand me as suggesting this is always appropriate, it obviously isn't. This isn't a better solution to getting specific answers using additional SQL queries. What it allows you to do is encode enough information to simply answer such questions in the consuming code all in a single result set. That's where it can come in handy.
SECOND EDIT
Since you were wondering whether you can do this if race data is stored in more than one place, the answer is, absolutely. I have revised the code from my previous SQL Fiddle, which is now available in a new one:
select pid, firstName, lastName, dateOfBirth, obsName, obsValue, obsDate,
rowRank, CASE rowRank WHEN 1 THEN 1 ELSE 0 END AS countableRow,
valueRank, CASE valueRank WHEN 1 THEN 1 ELSE 0 END AS valueCount,
dobRank, CASE WHEN dobRank = 1 AND dateOfBirth IS NOT NULL THEN 1 ELSE 0 END AS dobCount,
raceRank, CASE WHEN raceRank = 1 AND (race IS NOT NULL OR obsName = 'RACE') THEN 1 ELSE 0 END AS raceCount
from
(
select Person.pid, Person.firstName, Person.lastName, Person.dateOfBirth, Person.[race]
, Obs.obsName, Obs.obsValue, Obs.obsDate,
ROW_NUMBER() OVER (PARTITION BY Person.pid, Person.firstName, Person.lastName, Person.dateOfBirth ORDER BY Obs.obsDate) AS rowRank,
ROW_NUMBER() OVER (PARTITION BY Obs.obsName, Obs.obsValue ORDER BY Obs.obsDate) AS valueRank,
ROW_NUMBER() OVER (PARTITION BY Person.dateOfBirth ORDER BY Person.pid) AS dobRank,
ROW_NUMBER() OVER (PARTITION BY ISNULL(Person.race, CASE Obs.obsName WHEN 'RACE' THEN Obs.obsValue ELSE NULL END) ORDER BY Person.pid) AS raceRank
from Person
left join Obs on Person.pId = Obs.pId
) rankedData
As you can see, in the new Fiddle, this properly counts the number of Races as 3, with 2 being in the Obs table and the third being in the Person table. The trick is that PARTITION BY can contain expressions, not just raw column output. Note that I changed the join to a left join here, and that we need to use a CASE to only include obsValue WHERE obsName is 'RACE'. It is a little complicated, but not overwhelmingly so, and it handles even fairly complex cases gracefully.
It turned out that Jeroen's pointer to RunningValue was more on-target than I thought. I was able to get the results I wanted with the following code:
=RunningValue(Iif(Not IsNothing(Fields!DATEOFBIRTH.Value)
, Fields!PATIENTID.Value
, Nothing)
, CountDistinct
, Nothing
)
Thanks particularly to Dominic P, whose technique I'll keep in mind for next time.
This will only pull one record per patient, unless they reported different DOBs:
SELECT P.FOO,
P.BAR,
(etc.),
O.DOB
FROM Patients P
INNER JOIN Observations O
ON P.PatientID = O.PatientID
GROUP BY P.FOO, P.BAR, (P.etc), O.DOB

Resources