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.
Related
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.
Posted a question yesterday about SQL append distinct values from two columns and two tables that resulted in the following code:
with cte1 as
(Select Distinct "Tra.".cus_outnum As "account number" from table1 "Tra." Where "Tra.".invdat >= DATEADD(year, -1, GETDATE())
union
Select Distinct "Sal.".outnum As "account number" From table2 "Sal." Where "Sal.".deldat>= DATEADD(year, -1, GETDATE()))
,cte2 as
(Select Distinct "Tra.".artnum As "article number" from tabel1 “Tra.” Where "Tra.".invdat >= DATEADD(year, -1, GETDATE())
union
Select Distinct ISNULL("Sal.".org_artnum, "Sal.".artnum) As "article number" From table2 "Sal." Where "Sal.".deldat>= DATEADD(year, -1, GETDATE()))
Select * from cte1 cross join cte2
It produces a table with all distinct combination of account numbers and article numbers from two different tables where orders are done later than one year ago.
Next object that I'm struggling with is to include the latest and second latest purchase date in two new columns. If no purchase is done for any combination of account and article, it should result in a null(blank) value. And the latest/second latest date (i.e max date) should come from either one or the other of the tables. End result should be along the lines of:
<table><tbody><tr><th>Account</th><th>Article</th><th>Latest Date</th><th>Second to latest Date</th></tr><tr><td>1</td><td>1</td><td>26.06.2018</td><td>13.03.2018</td></tr><tr><td>1</td><td>2</td><td>15.05.2018</td><td>Blank (no purchase)</td></tr><tr><td>2</td><td>1</td><td>23.06.2018</td><td>30.06.2017</td></tr><tr><td>2</td><td>2</td><td>Blank (no purchase)</td><td>Blank (no purchase)</td></tr></tbody></table>
Many thanks for all the help, it is much appreciated!
Partially pseudeo. To get more detailed help please offer a db-fiddle.
SELECT
a.Account as Account,
b.Article as Article,
(<SUBSELECT> MAX(DATE) FROM ... WHERE ... ) as LatestDate,
(<SUBSELECT> MAX(DATE) FROM ... WHERE ... < LatestDate ) as 'SecondToLatestDate')
FROM
accounts a JOIN articles b
+ some IFNULL's
Is there a way to run a query for a specified amount of time, say the last 5 months, and to be able to return how many records were created each month? Here's what my table looks like:
SELECT rID, dateOn FROM claims
SELECT COUNT(rID) AS ClaimsPerMonth,
MONTH(dateOn) AS inMonth,
YEAR(dateOn) AS inYear FROM claims
WHERE dateOn >= DATEADD(month, -5, GETDATE())
GROUP BY MONTH(dateOn), YEAR(dateOn)
ORDER BY inYear, inMonth
In this query the WHERE dateOn >= DATEADD(month, -5, GETDATE()) ensures that it's for the past 5 months, the GROUP BY MONTH(dateOn) then allows it to count per month.
And to appease the community, here is a SQL Fiddle to prove it.
Unlike the other two answers, this will return all 5 months, even when the count is 0. It will also use an index on the onDate column, if a suitable one exists (the other two answers so far are non-sargeable).
DECLARE #nMonths INT = 5;
;WITH m(m) AS
(
SELECT TOP (#nMonths) DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE())-number, 0)
FROM master.dbo.spt_values WHERE [type] = N'P' ORDER BY number
)
SELECT m.m, num_claims = COUNT(c.rID)
FROM m LEFT OUTER JOIN dbo.claims AS c
ON c.onDate >= m.m AND c.onDate < DATEADD(MONTH, 1, m.m)
GROUP BY m.m
ORDER BY m.m;
You also don't have to use a variable in the TOP clause, but this might make the code more reusable (e.g. you could pass the number of months as a parameter).
SELECT
count(rID) as Cnt,
DatePart(Month, dateOn) as MonthNumber,
Max(DateName(Month, dateOn)) as MonthName
FROM claims
WHERE dateOn >= DateAdd(Month, -5, getdate())
GROUP BY DatePart(Month, dateOn)
I am having to write quite a complicated query at the moment but I am getting stuck. The table structure is as follows
Inquiry is linked to Timelog by a field called Inquiry_ID. My current code which brings back total minutes but for the entire table and not per company. What I am basically after:
Two Columns one for company name (dbo.inquiry.concom) and another for total minutes. The table INQUIRY holds say 100 entries for the same company, I want a row to return the company name once and the total amount of minutes counted for that company name from TIMELOG.LOGMINS
So for example there are 50 entries in dbo.inquiry that have the same company name, I want it to display a distinct company but I need it to total the amount of minutes that is in another table. I am completely lost!
DECLARE #StartDate DATETIME, #EndDate DATETIME
SET #StartDate = dateadd(mm, - 1, getdate())
SET #StartDate = dateadd(dd, datepart(dd, getdate()) * - 1, #StartDate)
SET #EndDate = dateadd(mm, 1, #StartDate)
SELECT DISTINCT TOP 100 PERCENT dbo.INQUIRY.CONCOM, TIMELOG_1.LOGMINS, dbo.INQUIRY.ESCDATE, dbo.INQUIRY.INQUIRY_ID,
(SELECT SUM(LOGMINS) AS Expr1
FROM dbo.TIMELOG
WHERE dbo.INQUIRY.ESCDATE BETWEEN #Startdate AND #EndDate) AS TOTALMINUTES
FROM dbo.INQUIRY INNER JOIN
dbo.TIMELOG AS TIMELOG_1 ON dbo.INQUIRY.INQUIRY_ID = TIMELOG_1.INQUIRY_ID INNER JOIN
dbo.PROD ON dbo.INQUIRY.PROD_ID = dbo.PROD.PROD_ID INNER JOIN
dbo.CATEGORY ON dbo.PROD.CATEGORY_ID = dbo.CATEGORY.CATEGORY_ID
WHERE dbo.INQUIRY.ESCDATE BETWEEN #Startdate AND #EndDate
ORDER BY dbo.INQUIRY.CONCOM
EDIT: The reason the category and product tables are there is because I will need to exclude the count based on whether a product is in a certain category.
SELECT i.concom, COALESCE(SUM(t.logmins), 0)
FROM inquiry i
LEFT JOIN
timelog t
ON t.inquiry_id = i.inquiry_id
GROUP BY
i.concom
I have tried but i am not able to figure this out. I have a table transactions (transaction_ID, transaction_Person_ID, Transaction_Date etc).
What i want is to return all the transaction_person_ID's that have more than 3 transactions per week for the last year. That means i have to check for 1-1-10 to 7-1-10 to see if someone had more than 3 transactions for that 7 day period, then for 2-1-10 to 8-1-10 then 3-1-10 to 9-1-10 etc etc.
I now i need to use a recursive select but i what i have writen does not produce the correct time frame.
What i have written so far is this
WITH Dates AS (
SELECT
[Date] = CONVERT(DATETIME,'01/01/2010')
UNION ALL SELECT
[Date] = DATEADD(DAY, 1, [Date])
FROM
Dates
WHERE
Date < '12/31/2010'
)
SELECT transaction_person_Id FROM transactions
JOIN DATES
ON transactions.transaction_date = dates.date
where transactions.Transaction_Date between dateadd(DAYOFYEAR,-7,dates.date) and dates.date
group by transaction_person_Id
having count(transaction_person_ID) >= 4
OPTION (MAXRECURSION 2000)
Thanks a lot
PS:
in simple words what i need to do is this
select transaction_person_ID from transactions
where Transaction_Date between '2010-01-01' and '2010-01-07'
group by transaction_person_Id
having count(transaction_person_ID) >= 4
then
select transaction_person_ID from transactions
where Transaction_Date between '2010-01-02' and '2010-01-08'
group by transaction_person_Id
having count(transaction_person_ID) >= 4
.
.
.
.
.
until it goes
select transaction_person_ID from transactions
where Transaction_Date between '2010-12-25' and '2010-12-31'
group by transaction_person_Id
having count(transaction_person_ID) >= 4
i need to have the results of these 365 queries
This will give one result set with person and weeks, rather then 360 results sets
WITH Weeks
AS (
SELECT
CONVERT(DATETIME,'01 Jan 2010') AS WeekStartMidnight,
CONVERT(DATETIME,'08 Jan 2010') AS WeekEndMidnight
UNION ALL
SELECT
DATEADD(day, 1, WeekStartMidnight),
DATEADD(day, 1, WeekEndMidnight)
FROM
Weeks
WHERE
WeekEndMidnight < '31 Dec 2010'
)
SELECT
t.transaction_person_Id,
w.WeekStartMidnight, w.WeekEndMidnight
FROM
weeks w
JOIN
transactions t ON t.Transaction_Date >= w.WeekStartMidnight AND t.Transaction_Date < w.WeekEndMidnight
GROUP BY
t.transaction_person_Id
HAVING
count(*) >= 4 --COUNT(t.transaction_person_Id) = same
OPTION
(MAXRECURSION 365)
If you want 360 results sets, it's a loop using WHILE or a CURSOR per row in the "weeks" derived table