Having case multiple conditions in SQL Server 2014 [duplicate] - sql

This question already has answers here:
Case with multiple conditions in SQL Server 2014
(1 answer)
How to check a SQL CASE with multiple conditions?
(4 answers)
Closed 5 years ago.
I have a table 'FinancialTrans' where only 3 of those fields are needed.
AcctID TransTypeCode DateOfTrans Field 4 Field 5 Field 6....
123 TOLL 2016-06-06
123 TOLL 2016-06-02
123 TOLL 2016-04-28
123 PYMT 2016-03-11
123 TOLL 2015-12-22
123 TOLL 2015-12-22
What I need:
I only need account numbers where there are No Tolls AND No Pymt in the last 2 years.
My attempt at the code:
I know I need a Having clause but not quite sure how to write it.
Perhaps, a NOT Exist?
SELECT [AcctID]
,[TransTypeCode]
,[TransDate]
FROM [FinancialTrans]
WHERE (
(TransTypeCode = 'TOLL' AND Max(TransDate) <= DATEADD(year, -2, GETDATE()))
OR (TransTypeCode = 'PYMT' AND Max(TransDate) <= DATEADD(year, -2, GETDATE()))
)
GROUP BY AcctID, TransTypeCode, TransDate
The challenge I'm facing is that I want account numbers where there is NEITHER a toll NOR a payment in the past two years.
I'm getting account numbers that have no tolls in the past two years but has a payment in the past two years.
Question: How do I ensure I get account numbers that doesn't have BOTH in the past two years?
This question is different from an earlier question asked because the requirements have now changed.

Not exists would work also.
Select AcctID,
TransTypeCode,
TransDate
From FinancialTrans ft1
Where Not Exists (Select 1
From FinancialTrans ft2
Where ft1.AcctID = ft2.AcctID
and ft2.TransTypeCode IN ('TOLL','PYMT')
and ft2.DateOfTrans > DATEADD(year, -2, getdate()))

You can use group by and having:
SELECT [AcctID]
FROM [FinancialTrans]
GROUP BY [AcctID]
HAVING MAX(CASE WHEN TransTypeCode = 'TOLL' THEN TransDate END) <= DATEADD(year, -2, GETDATE()) AND
MAX(CASE WHEN TransTypeCode = 'PYMT' THEN TransDate END) <= DATEADD(year, -2, GETDATE()) ;
That above actually requires that there be both types of transactions. It might be better to do:
SELECT [AcctID]
FROM [FinancialTrans]
GROUP BY [AcctID]
HAVING SUM(CASE WHEN TransTypeCode IN ('TOLL', 'PYMT') AND TransDate > DATEADD(year, -2, GETDATE())
THEN 1 ELSE 0
END) = 0;

Related

Selecting fields that are not in GROUP BY [duplicate]

This question already has answers here:
Get top 1 row of each group
(19 answers)
Closed last year.
I am working with group by in SQL Server. I want to select fields that shouldn't be part of group by.
I want to select DishId but it shouldn't be in group by clause. If I add the DishId into group by it repeats the range as shown following.
SELECT v.range, COUNT(*) AS 'occurrences',COUNT(DishID) * SUM(OrderQty) AS [TotalOrders],po.DishID
FROM ( SELECT PgrId,CASE WHEN DATEDIFF(YEAR, p.PgrDOB, GETDATE()) >= 0 AND DATEDIFF(YEAR, p.PgrDOB, GETDATE()) < 10 THEN '0-9'
WHEN DATEDIFF(YEAR, p.PgrDOB, GETDATE()) >= 10 AND DATEDIFF(YEAR, p.PgrDOB, GETDATE()) < 20 THEN '10-19'
WHEN DATEDIFF(YEAR, p.PgrDOB, GETDATE()) >= 20 AND DATEDIFF(YEAR, p.PgrDOB, GETDATE()) < 30 THEN '20-29'
WHEN DATEDIFF(YEAR, p.PgrDOB, GETDATE()) >= 30 AND DATEDIFF(YEAR, p.PgrDOB, GETDATE()) < 40 THEN '30-39'
ELSE '40+'
END AS 'range'
FROM Passenger p
) v inner join PassengerOrder po on v.PgrId = po.PgrID
GROUP BY v.range,po.DishID
Sounds like you want the highest dish in each age range, ordered by total sales descending. There are many other improvements that could be made here but the simplest way is to just generate a row number per range ordered by total sales descending, and wrap that in a CTE, filtered by the first row number.
;WITH cte AS
(
SELECT v.range,
COUNT(*) AS occurrences,
COUNT(DishID) * SUM(OrderQty) AS TotalOrders,
po.DishID,
rn = ROW_NUMBER() OVER (PARTITION BY v.range
ORDER BY COUNT(DishID) * SUM(OrderQty) DESC)
FROM
( SELECT PgrId,
CASE WHEN p.PgrDOB < DATEADD(YEAR, -40, GETDATE()) THEN '40+'
WHEN p.PgrDOB < DATEADD(YEAR, -30, GETDATE()) THEN '30-39'
WHEN p.PgrDOB < DATEADD(YEAR, -20, GETDATE()) THEN '20-29'
WHEN p.PgrDOB < DATEADD(YEAR, -10, GETDATE()) THEN '10-19'
ELSE '0-9'
END AS range
FROM dbo.Passenger p
) v inner join dbo.PassengerOrder po on v.PgrId = po.PgrID
GROUP BY v.range,po.DishID
)
SELECT range, occurrences, TotalOrders, DishID
FROM cte
WHERE rn = 1
ORDER BY TotalOrders DESC;
Output (shown in this db<>fiddle):
range
occurrences
TotalOrders
DishID
40+
2
220
dsh0000001
30-39
2
84
dsh0000001
10-19
1
11
dsh0000001
20-29
1
1
dsh0000001
0-9
1
1
dsh0000001
I fixed a couple of other things, too:
Always use schema name for objects.
Don't use 'single quotes' to delimit column aliases; this makes them too easy to confuse with string literals. Use [square brackets] instead, but don't delimit at all unless you need to. (I talk a little about that here.)
Your DATEDIFF calculation was not accurate to a person's birthday, nor is mine, but if you flip the order you can make a much less complex CASE expression, and when you move functions away from the column, you make it more likely that the query could benefit from a current (or future) index. Lots more at Dating Responsibly.

DateDiff function adding an extra year for the customer dob

How can I alter the SQL query to not return a 3 for customer Mike since he is not 3 years old yet. As you can see the customer turns 3 in December of 2021. But my query is giving him the age of 3. Is there anyways to alter or make a query that gives him the correct age?
SELECT
id,
name,
dob,
DATEDIFF(YYYY, dob, GETDATE())
FROM
customer
WHERE
DATEDIFF(YYYY, dob, GETDATE()) >= 2
AND DATEDIFF(YYYY, dob, GETDATE()) <= 4
Results:
id name dob datediff
-------------------------------
1 Mike 2018-12-05 3
There are many varied solutions to this issue on SQL Server. This answer is based on a Stack Overflow answer from a question where the accepted answer is not the best answer imo. When it comes to calculating "age from birth" there are advantages to using date format 112. You could try calculating the customer age something like this
declare
#as_of datetime=getdate(),
#bday datetime='2018-12-05';
select
(0 + Convert(Char(8),#as_of,112) - Convert(Char(8),#bday,112))/10000 age;
age
2
If you want an integer number of years (no fractional part)...
SELECT
*,
CASE
WHEN DATEADD(year, diff.years, customer.dob) > GETDATE()
THEN diff.years - 1
ELSE diff.years
END
AS age
FROM
customer
CROSS APPLY
(
SELECT DATEDIFF(year, customer.dob, GETDATE()) AS years
)
AS diff
WHERE
customer.dob > DATEADD(year, -5, CAST(GETDATE() AS DATE)) -- Born more recently than 5 years ago, so at most 4 years 11 month and 30 days old
AND customer.dob <= DATEADD(year, -2, CAST(GETDATE() AS DATE)) -- Born two years ago or earlier, so at least 2 years and 0 days old
The cross apply is just so that I can write the DATEDIFF() once and refer to it as many times as I like (Don't Repeat Yourself).
But then I also refactored the where clause. By moving the calculation to be on GETDATE() rather than on the dob column, I both make the maths simpler (than the case statement), but also make it so that any index on dob can be used (SARGable).
CAST(GETDATE() AS DATE) just removes the time part of today's date, on the assumption your dob (and calculations) don't account for the exact time they were born ;)

Don't have the answer to attached query?

My data is in two tables. The format of the two tables is below :
I had to figure out for all customers aged between 25 to 35 years find what is the net total revenue generated by these customers in last 30 days of transactions from max transaction date available in the data ?
I wrote below code
SELECT
TOP 1 YEAR(T2.TRAN_DATE)[TRAN_YEAR] ,MONTH(T2.TRAN_DATE)[TRAN_Month],
SUM(T2.Total_amt)[REVENUE]
FROM TRANSACTIONS T2
RIGHT JOIN CUSTOMER T1
ON T1.CUSTOMER_ID = T2.CUST_ID
WHERE DATEDIFF(YY, T1.DOB, GETDATE()) BETWEEN 25 AND 35
GROUP BY YEAR(T2.TRAN_DATE),MONTH(T2.TRAN_DATE)
ORDER BY YEAR(T2.TRAN_DATE) DESC, MONTH(T2.TRAN_DATE) DESC
My query works but when i calculated the same thing on excel it gave a different answer.
I am not able to figure out my mistake.
I am expecting a query like this:
SELECT SUM(T.Total_amt) as REVENUE]
FROM TRANSACTIONS T JOIN
CUSTOMER c
ON c.CUSTOMER_ID = t.CUST_ID
WHERE c.DOB >= DATEADD(YEAR, -35, GETDATE()) AND
c.DOB < DATEADD(YEAR, -24, GETDATE()) AND
t.TRAN_DATE > DATEADD(DAY, -30, GETDATE());
Note that this uses direct date comparisons rather than DATEDIFF(). These are usually more accurate.

How to show 'o' value for month and year, if no record exists for that month/year in table [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
SQL Group and Sum By Month - Default to Zero
In my table, i am not having record for some months and year, so i need to put '0' for that records and need to show all months and year from '2006' to '2012' in result, please suggest me
Thanks in advance
dummy is your real table ...
WITH DummyDates (Date) AS (
SELECT DATEADD(DAY, DATEDIFF(DAY, 0, '20121231') - DATEDIFF(DAY, '20060101', '20121231'), 0)
UNION ALL SELECT DATEADD(DAY, 1, Date)
FROM DummyDates
WHERE DATEADD(DAY, 1, Date) <= '20121231')
SELECT DatePart(yy,DummyDates.Date) as [Year],DATEPART(MM,DummyDates.Date) as [Month],SUM(Case when dummy.date is null then 0 else 1 end) as cnt
FROM DummyDates
LEFT Join dummy on dummy.Date=DummyDates.Date
Group by DatePart(yy,DummyDates.Date),DATEPART(MM,DummyDates.Date)
Order by DatePart(yy,DummyDates.Date),DATEPART(MM,DummyDates.Date)
OPTION (MAXRECURSION 0)

Calculate items, loop by month, adding month each time through

I have a table of tickets. I am trying to calculate how many tickets were "open" at each month end over the course of the current year. As well, I am pushing this to a bar chart and I am needing out put this into an array through LINQ.
My SQL query to get my calculation is:
SELECT
(SELECT COUNT(*) FROM tblMaintenanceTicket t WHERE (CreateDate < DATEADD(MM, 1, '01/01/2012')))
-
(SELECT COUNT(*) FROM tblMaintenanceTicket t WHERE (CloseDate < DATEADD(MM, 1, '01/01/2012'))) AS 'Open #Month End'
My logic is the following: Count all tickets open between first and end of the month. Subtract that count from the tickets closed before the end of the month.
UPDATED:
I have updated my query with the comments below and it is not working with errors in the GROUP, but I am not truly understanding the logic I guess, my lack of skill in SQL is to blame.
I have added a SQL Fiddle example to show you my query: http://sqlfiddle.com/#!3/c9b638/1
Desired output:
-----------
| Jan | 3 |
-----------
| Feb | 4 |
-----------
| Mar | 0 |
-----------
Your SQL has several erros . . . are grouping by CreateDate but you don't have it as a column from the subqueries. And, you don't have a column alias on the count(*).
I think this is what you are trying to do:
select DATENAME(MONTH,CreateDate), DATEPART(YEAR,CreateDate),
(sum(case when CreateDate < DATEADD(MM, 1, '01/01/2012') then 1 else 0 end) -
sum(case when CloseDate < DATEADD(MM, 1, '01/01/2012') then 1 else 0 end)
)
from tblMaintenanceTicket
group by DATENAME(MONTH,CreateDate), DATEPART(YEAR,CreateDate)
Your comment seems to elucidate what you want clearer than your question (the explanation in the question is a bit buried). What you need is a driver table of months and then join this to your table. Something like:
select mons.yr, mons.mon, count(*) as OpenTickets
from (select month(CreateDate) as mon, year(CreateDate) as yr,
cast(min(CreateDate) as date) as MonthStart,
cast(max(CreateDate) as date) as monthEnd
from tblMaintenanceTicket
group by month(CreateDate), year(CreateDate)
) mons left outer join
tblMaintenanceTicket mt
on mt.CreateDate <= mons.MonthEnd and
(mt.CloseDate > mons.MonthEnd or mt.CloseDate is null)
group by mons.yr, mons.mon
I am assuming records are created on every day. This is a convenience so I don't have to think about getting the first and last day of each month using other SQL functions.
If your query is returning what you need, then simply use DATENAME(MONTH, yourDate) to retrieve the month and group by Month,Year:
SELECT SUM(*), DATENAME(MONTH,yourDate), DATEPART(YEAR,yourDate)
FROM
(
your actual query here
)
GROUP BY DATENAME(MONTH,yourDate), DATEPART(YEAR,yourDate)