tsql UNION without nulls - sql-server

I have the following query
SELECT MONTH, COUNT(DISTINCT VISITS) AS BRAND_VISITS, NULL AS NONB_VISITS
FROM Table1
WHERE KEYWORD_TYPE = BRAND(
AND DATE >= '2013-01-01'
GROUP BY MONTH
UNION ALL
SELECT MONTH, NULL, COUNT(DISTINCT VISITS) AS NONB_VSTS
FROM Table1
WHERE KEYWORD_TYPE = NON-BRAND
AND DATE >= '2013-01-01'
GROUP BY MONTH
I get the following results:
1 352540 NULL
2 309834 NULL
3 228764 NULL
4 236054 NULL
5 218096 NULL
6 172527 NULL
1 NULL 5337
2 NULL 14120
3 NULL 9954
4 NULL 23755
5 NULL 19771
6 NULL 30797
However, what I want is inline results without NULLS
1 352540 5337
2 309834 14120
3 228764 9954
4 236054 23755
5 218096 19771
6 172527 30797

You can do this with using a single statement with CASE or with an JOIN on month instead of a UNION. If you take the join approach you may need to account for null values (no visist for a keyword in a month). You will want to profile them to see which is faster with your data and table structure. It is really all about the indexes and the amount of data you need to aggregate.
Assuming you don't have to worry about nulls based on the counts in your example, here is what you want.
SELECT brand.month, brand.brand_visits,nonbrand.non_brand_visits
FROM (SELECT month, COUNT(visits) AS brand_visits
FROM Table1
WHERE keyword_type = 'BRAND'
AND date >= '2013-01-01'
GROUP BY month) brand
INNER JOIN
(SELECT month, COUNT(visits) AS non_brand_visits
FROM Table1
WHERE keyword_type = 'NON-BRAND'
AND date >= '2013-01-01'
GROUP BY month) nonbrand
ON brand.month=nonbrand.month
Here is the CASE approach. You should profile based on your actual data you are aggregating and your indexes to see which method is faster.
SELECT month,
SUM(CASE WHEN keyword_type = 'BRAND' THEN 1 ELSE 0 END) AS brand_visits,
SUM(CASE WHEN keyword_type = 'NON-BRAND' THEN 1 ELSE 0 END) AS non_brand_visits
FROM Table1
WHERE date >= '2013-01-01'
GROUP BY month
Finally, you did not provide table structure or example data so I made some assumptions above. I strongly believe you did not need the COUNT(DISTINCT in your original statement. I have removed it and verified the two statement above yield the same results. If COUNT(DISTINCT is required then the CASE approach will not work but the join approach will still work fine.

Using your columns:
SELECT month,
count(distinct CASE WHEN keyword_type = 'BRAND' THEN visits END) AS BRAND_VISITS,
count(distinct CASE WHEN keyword_type = 'NON-BRAND' THEN visits END) AS NONB_VSTS
FROM Table1
WHERE date >= '2013-01-01'
and keyword_type in ('BRAND','NON-BRAND')
GROUP BY month
Am tempted to believe that month is simply the month from the date column, I would prefer this solution, it con cover more years than 1 and the same query will still be valid in the year 2014
SELECT cast(dateadd(month, datediff(month, 0, date), 0) as date) month,
count(distinct CASE WHEN keyword_type = 'BRAND' THEN visits END) AS BRAND_VISITS,
count(distinct CASE WHEN keyword_type = 'NON-BRAND' THEN visits END) AS NONB_VSTS
FROM Table1
WHERE date >= '2013-01-01'
and keyword_type in ('BRAND','NON-BRAND')
GROUP BY datediff(month, 0, date)
If you want to stick with your old script, you can fix it this way:
SELECT MONTH, max(BRAND_VISITS) BRAND_VISITS, max(NONB_VISITS) NONB_VISITS
FROM
(
SELECT MONTH, COUNT(DISTINCT VISITS) AS BRAND_VISITS, NULL AS NONB_VISITS
FROM Table1
WHERE KEYWORD_TYPE = 'BRAND'
AND DATE >= '2013-01-01'
GROUP BY MONTH
UNION ALL
SELECT MONTH, NULL, COUNT(DISTINCT VISITS) AS NONB_VSTS
FROM Table1
WHERE KEYWORD_TYPE = 'NON-BRAND'
AND DATE >= '2013-01-01'
GROUP BY MONTH
) a
GROUP BY MONTH

Related

Select Earliest Date Within a Range of Dates Before a Break Occurs

I have been trying to find a solution for getting the most recent start date from a series of date ranges. I have found similar topics on StackOverflow as well as other websites, but none of worked for my specific scenario.
Here are two examples of the data in my database:
Example 1
Start Date | End Date
-----------|-----------
8/26/2006 | 5/31/2016
6/1/2016 | 12/31/2017
1/1/2018 | NULL
For this example, I'm expecting the result of the query to be: 8/26/2006. This is because the start and end dates are continuous all the way back to the original start date.
Example 2
Start Date | End Date
-----------|-----------
7/6/2014 | 11/30/2014
1/1/2019 | NULL
For this example, I'm expecting the result of the query to be: 1/1/2019. This is because there is a break between 11/30/2014 and 1/1/2019.
I don't need a list of all of the dates or even the end dates returned. I just need the earliest start date before a break in the date ranges.
I'm guessing what I need is a recursive CTE to loop through the records, such as this:
WITH CTE AS
(
SELECT
T1.StartDate
,T1.EndDate
FROM
ExampleTable AS T1
LEFT JOIN
ExampleTable AS T2
ON
T1.EmployeeID = T2.EmployeeID
AND T1.StartDate - 1 = T2.EndDate
WHERE
T1.EmployeeID = #EmployeeID
UNION ALL
SELECT
C.EmployeeID
,C.StartDate
,T2.EndDate
FROM
CTE AS C
JOIN
ExampleTable AS T2
ON
C.EmployeeID = T2.EmployeeID
AND T2.StartDate - 1 = C.EndDate
)
SELECT
StartDate
,NULLIF(MAX(ISNULL(EndDate, '32121231')), '32121231') AS EndDate
FROM
CTE
GROUP BY
StartDate;
But no luck. It always returns all of the date ranges I listed in examples 1 or 2. Can anyone help please?
This seems the simplest method to get the result:
SELECT TOP 1 StartDate
FROM YourTable
ORDER BY CASE WHEN LAG(EndDate) OVER (ORDER BY StartDate) = DATEADD(DAY,-1,StartDate) THEN 1 ELSE 0 END,
StartDate DESC;
So, for your data:
WITH VTE AS(
SELECT CONVERT(date, StartDate,101) AS StartDate,
CONVERT(date, EndDate,101) AS EndDate
FROM (VALUES('7/6/2014','11/30/2014'),
('1/1/2019',NULL)) V(StartDate, EndDate))
SELECT TOP 1 StartDate
FROM VTE
ORDER BY CASE WHEN LAG(EndDate) OVER (ORDER BY StartDate) = DATEADD(DAY,-1,StartDate) THEN 1 ELSE 0 END,
StartDate DESC;
WITH VTE AS(
SELECT CONVERT(date, StartDate,101) AS StartDate,
CONVERT(date, EndDate,101) AS EndDate
FROM (VALUES('8/26/2006','5/31/2016'),
('6/1/2016 ','12/31/2017'),
('1/1/2018 ',NULL)) V(StartDate, EndDate))
SELECT TOP 1 StartDate
FROM VTE
ORDER BY CASE WHEN LAG(EndDate) OVER (ORDER BY StartDate) = DATEADD(DAY,-1,StartDate) THEN 1 ELSE 0 END,
StartDate DESC;

Select all days of the current week

Good Day! I am working on a chart where I need to display all the days of the current week to show the sales per Week. So far, I am able to display all the days of the current week, I'm just having a trouble in displaying the sales for each day of the week.Since there are no records in the database for the days of the week, it the TOTAL_SALES column should all return a Null value. Instead, it returns the total sales recorded in the database. Here is my Stored Procedure query so far.
WITH DAYSOFTHEWEEK AS
(
SELECT 0 DAY
UNION ALL
SELECT DAY + 1 FROM DAYSOFTHEWEEK WHERE DAY < 6
)
SELECT DATEADD(DAY, DAY, DATEADD(DAY, 2-DATEPART(WEEKDAY, CONVERT (date, GETDATE())), CONVERT (date, GETDATE()))) AS DAY_OF_THE_WEEK,
SUM([ORDER].NET_AMOUNT) AS TOTAL_SALES
FROM DAYSOFTHEWEEK, [ORDER]
GROUP BY DAYSOFTHEWEEK.DAY
I tried adding this condition statement,
WHERE DAYSOFTHEWEEK.DAY IN ([ORDER].ORDER_DATE)
But it returns this error
Operand type clash: date is incompatible with int
Can someone help me out on this?Is there a work around with the code that I already have? Thanks in advance!
What I think you're after is a SUM of each day's sales for the current week with NULL if there are no sales. The secret is to left join your date list onto your data:
-- Setup some fake sales data
WITH TestData(N, Order_Date, Net_Amount) AS (
SELECT 1 N, CAST(GETDATE() AS DATE) Order_Date, RAND() * 100 Net_Amount
UNION ALL
SELECT N+1 N, CAST(GETDATE()-N/5 AS DATE) Order_Date, RAND(CHECKSUM(NEWID())) * 100 Net_Amount FROM TestData
WHERE N < 20
)
SELECT TestData.Order_Date, TestData.Net_Amount INTO #Order FROM TestData
--Set the first day of the week (if required)
SET DATEFIRST 7 --Sunday
;WITH Days(N,DayOfTheWeek) AS (
SELECT 1 N, DATEADD(DAY, 1-DATEPART(WEEKDAY, GETDATE()), CONVERT(DATE,GETDATE())) DayOfTheWeek
UNION ALL
SELECT N+1 N,DATEADD(DAY, 1, DayOfTheWeek) DayOfTheWeek FROM Days
WHERE N < 7
)
SELECT d.DayOfTheWeek, SUM(Net_Amount) TotalAmount
FROM Days d
LEFT JOIN #Order ON d.DayOfTheWeek = Order_Date
GROUP BY d.DayOfTheWeek
DayOfTheWeek TotalAmount
------------ ----------------------
2016-08-07 219.036784917497
2016-08-08 273.319570812461
2016-08-09 271.148114731087
2016-08-10 194.780039228967
2016-08-11 NULL
2016-08-12 NULL
2016-08-13 NULL
Here is every day this week, starting at your datefirst date, which can be temporarily varied for the query with SET DATEFIRST if you need to have some other week start date
I think you have some sales table there that you haven't shown us, you need to join to that on date, then group by
WITH DAYSOFTHEWEEK AS
(
SELECT cast(dateadd(
day,
-datepart(weekday,getdate()) + 1 ,
GETDATE()
)
as date) [DAY], 0 as cnt
UNION ALL
SELECT dateadd(day,1,[DAY]), cnt + 1 FROM DAYSOFTHEWEEK WHERE cnt < 6
)
select DAYSOFTHEWEEK.[day], SUM([ORDER].NET_AMOUNT) AS TOTAL_SALES from daysoftheweek
JOIN
SalesTable on
CAST(SalesTable.SalesDate date) = DAYSOFTHEWEEK.[day]
GROUP BY DAYSOFTHEWEEK.[day]
A little over complicated for me:
To get name of the week use, for example
SELECT DATENAME(dw,getdate())
But you really need something like this:
SELECT ProductName,Sum(Sales) From NameOfTable GROUP BY
DATENAME(ww,salesDate)

Last 2 month record in SQL

I try this SQL query to get current total and also last 2 months records and current month record means total 3 months..
Select distinct
tblRv.Owner,
(Select Count(*) as total from tblvv WHERE MasterID =tblRv.ID and Name <> '')
as currentdata
from tblRe
inner join tblRv
On tblRe.RID = tblRv.RID
WHERE
tblRe.StartDate between dateadd(m, -2, getdate()) and getdate() and
//tblRe.StartDate >= '2016-07-01 00:00:00' AND
//tblRe.EndDate <= '2016-07-08 23:59:59'
and tblRe.Region = 'uk' and
tblRv.Owner='Roh'
order by tblRv.Owner
when i exe this show me like this
OwnerName currentdata
Roh 1
Roh 2
Roh 3
Roh 5
and when i check individually write query and check from date 2016-07-01 and todate 2016-07-30 dates then this show me data 3 and 2016-06-01 00:00:00 and 2016-06-31 23:59:59 show me data 1 and 2016-05-01 00:00:00 ,2016-05-31 23:59:59 show me data 0
so i want data like this
owner july june may
roh 3 1 0
also when there will be current month i.e. aug then data must display last 2 months i.e.june july
Select
tblRv.Owner
,DATENAME(MONTH,tblRe.StartDate) as [Month]
,ISNULL(SUM(total),0) as currentdata
from tblRe
INNER JOIN tblRv ON tblRe.RID = tblRv.RID
LEFT JOIN (
Select Count(*) as total ,MasterID
from tblvv
WHERE Name <> ''
GROUP BY MasterID
) tblvv
ON tblvv.MasterID =tblRv.ID
WHERE tblRe.StartDate >= DATEADD(MONTH, -2, GETDATE())
AND tblRe.EndDate <= GETDATE()
AND tblRe.Region = 'uk'
AND tblRv.[Owner] ='Roh'
GROUP BY tblRv.Owner
,DATENAME(MONTH,tblRe.StartDate)
order by tblRv.[Owner]
You can try doing a pivot query where the three columns to be pivoted are the current, previous, and previous previous month in your data set. Note that I had to rewrite your query to remove the subqueries in the SELECT clause, because this makes it impossible to do aggregation of those columns.
SELECT tblRv.Owner,
SUM(CASE WHEN tblRe.StartDate = GETDATE() THEN t.total ELSE 0 END) AS currMonth,
SUM(CASE WHEN tblRe.StartDate = DATEADD(m, -1, GETDATE())
THEN t.total ELSE 0 END) AS prevMonth,
SUM(CASE WHEN tblRe.StartDate = DATEADD(m, -2, GETDATE())
THEN t.total ELSE 0 END) AS lastPrevMonth
FROM tblRe
INNER JOIN tblRv
ON tblRe.RID = tblRv.RID
INNER JOIN
(
SELECT MasterID, COUNT(*) AS total
FROM tblvv
GROUP BY MasterID
WHERE Name <> ''
) AS t
ON tblRv.ID = t.MasterID
WHERE tblRe.StartDate BETWEEN DATEADD(m, -2, GETDATE()) AND GETDATE() AND
tblRe.Region = 'uk' AND
tblRv.Owner = 'Roh'
GROUP BY tblRv.Owner
ORDER BY tblRv.Owner

SQL SMS 2008 -Count column ids and count duplicate ids if createddate is greater than 3 months between ids

*Edit (Hopefully to be more clear)
Table below, I would like to count ids and count duplicate ids where the createddate has a gap of 3 months or more for that ID.
Query I have so far...
if object_id('tempdb..#temp') is not null
begin drop table #temp end
select
top 100
a.id, a.CreatedDate
into #temp
from tbl a
where 1=1
--and year(CreatedDate) = '2015'
if object_id('tempdb..#temp2') is not null
begin drop table #temp2 end
select t.id, count(t.id) as Total_Cnt
into #temp2
from #temp t
group by id
select distinct #temp2.Total_Cnt, #temp2.id, #temp.CreatedDate, DENSE_RANK() over (partition by #temp.id order by createddate) RK
from #temp2
inner join #temp on #temp2.id = #temp.id
where 1=1
order by Total_Cnt desc
Results:
Total_cnt id createddate rk
3 1 01-01-2015 1
3 1 03-02-2015 2
3 1 01-02-2015 3
2 2 05-01-2015 1
2 2 05-02-2015 2
1 3 06-01-2015 1
1 4 07-01-2015 1
Count ids and only count duplicate ids when the createddate from the id is greater than 3 months.
Something like this...
Total_cnt id Countwith3monthgap
3 1 2
2 2 1
1 3 1
1 4 1
You can use a cte and ROW_NUMBER to get your order and self join the cte based on the order..
WITH cte AS
( SELECT
*,
ROW_NUMBER() OVER (PARTITION BY ID ORDER BY CreatedDate) Rn
FROM
Test
)
SELECT
c1.ID,
COUNT(CASE WHEN c2.CreatedDate IS NULL THEN 1
WHEN c1.CreatedDate >= DATEADD(month,3,c2.CreatedDate) THEN 1
END)
FROM
cte c1
LEFT JOIN cte c2 ON c1.ID = c2.ID
AND c1.RN = c2.RN + 1
GROUP BY
c1.ID
You also need to use a conditional count where the Previous CreatedDate is null or if the Current CreatedDate is >= the Previous CreatedDate + 3 months
If you happen to be using SQL 2012+ you can also use LAG here to get the same result
SELECT
ID,
COUNT(*)
FROM
(SELECT
ID,
CreatedDate CurrentDate,
LAG(CreatedDate) OVER (PARTITION BY ID ORDER BY CreatedDate) PreviousDate
FROM
Test
) T
WHERE
PreviousDate IS NULL
OR CurrentDate >= DATEADD(month, 3, PreviousDate)
GROUP BY
ID
You can use a lag to get the previous date, Null for the first in the list
SELECT
id,
lag(CreatedDate,1) OVER (PARTITION BY Id ORDER BY CreatedDate) AS PreviousCreateDate,
CreatedDate
FROM #t
You can use that as a subquery and get the difference in months using DATEDIFF
SELECT sub.id,DATEDiff(month, sub.PreviousCreateDate ,sub.CreatedDate)
FROM (SELECT
id,
lag(CreatedDate,1) OVER (PARTITION BY Id ORDER BY CreatedDate) AS PreviousCreateDate,
CreatedDate
FROM #t) sub
WHERE DATEDiff(month, sub.PreviousCreateDate ,sub.CreatedDate) >=3
OR sub.PreviousCreateDate IS NULL
You can then take your totals
SELECT sub.id,COUNT(sub.id) as cnt
FROM (SELECT
id,
lag(CreatedDate,1) OVER (PARTITION BY Id ORDER BY CreatedDate) AS PreviousCreateDate,
CreatedDate
FROM #t) sub
WHERE DATEDIFF(month, sub.PreviousCreateDate ,sub.CreatedDate) >=3
OR sub.PreviousCreateDate IS NULL
GROUP BY sub.id
Note that using datediff the last day of january is three months before the first day of march. That appears to be the logic you were after.
You might want to define your three month gap criteria as
WHERE sub.PreviousCreateDate <= DATEADD(month, -3, sub.CreatedDate)
OR sub.PreviousCreateDate IS NULL
or
WHERE sub.CreatedDate >= DATEADD(month, +3, sub.PreviousCreateDate )
OR sub.PreviousCreateDate IS NULL
I'm guessing that your desired definition of three-month gap doesn't coincide with datediff()'s. Most of the logic here is to look back at the previous date and decide if the gap is big enough to qualify.
When datediff() counts three months difference we still need to make sure the day of month is later than the first one (per example and ID 5). If difference is more than three months then we're good automatically.
But I'm also assuming that you would want to treat the distance from November 30th to February 28th (or 29th in a leap year) as a full three months because the end date falls on the final day of the month. By adjusting the end date by an extra day this is an easy scenario to snag as it will bump the date into the following month and increase the month difference by one as well. If that's not what you want then just remove the dateadd(day, 1, ...) portion and use only the raw CreatedDate value.
You sample data is limited so I'm also making the assumption that the gaps are measure between consecutive dates. If you're wanting to find blocks of runs that don't span more than three months across the set, then that's a different problem and you should clarify with more information.
Since you've indicated that you're probably on SQL Server 2008 you'll have to do without the lag() function. Although the first query could be adjusted for that it's likely easier to go with the second approach at the end.
with diffs as (
select
ID,
row_number() over (partition by ID order by CreatedDate) as RN,
case when
datediff(
month,
lag(CreatedDate, 1) over (partition by ID order by CreatedDate),
CreatedDate
) = 3
and
datepart(
day,
lag(CreatedDate, 1) over (partition by ID order by CreatedDate)
) <= datepart(day, CreatedDate)
or
datediff(
month,
lag(CreatedDate, 1) over (partition by ID order by CreatedDate),
/* adding one day to handle gaps like Nov30 - Feb28/29 and Jan31 - Apr30 */
dateadd(day, 1, CreatedDate)
) >= 4
then 1
else 0
end as GapFlag
from <T> /* <--- your table name here */
), gaps as (
select
ID, RN,
sum(1 + GapFlag) over (partition by ID order by RN) as Counter
from diffs
)
select ID, count(distinct Counter - RN) as "Count"
from gaps
group by ID
The rest of the logic is a typical gaps and islands scenario looking for holes in the sum(1 + GapCount) sequence with the offset of 1 acting pretty much like row_number().
http://sqlfiddle.com/#!6/61b12/3
JamieD77's approach is also valid. I was originally thinking your problem involved more than looking at the rows in sequence. Here's how I would tweak it for the gap definition I've been running with:
with data as (
select ID, CreatedDate, row_number() over (partition by ID order by CreatedDate) as RN
from T
)
select ID, count(*) as "Count"
from data d1 left outer join data d0
on d0.ID = d1.ID and d0.RN = d1.RN - 1 /* connect to the one before */
where
datediff(month, d0.CreatedDate, d1.CreatedDate) = 3
and datepart(day, d0.CreatedDate) <= datepart(day, d0.CreatedDate)
or datediff(month, d0.CreatedDate, dateadd(day, 1, d0.CreatedDate)) >= 4
or d0.ID is null
group by ID
Edit: You have changed the question since yesterday.
Change this line in the first query to include the total count:
...
select count(*) as TotalCnt, ID, count(distinct Counter - RN) as GapCount
...
Second would look like:
with data as (
select ID, CreatedDate, row_number() over (partition by ID order by CreatedDate) as RN
from T
)
select
count(*) as TotalCnt, ID,
count(case when
datediff(month, d0.CreatedDate, d1.CreatedDate) = 3
and datepart(day, d0.CreatedDate) <= datepart(day, d0.CreatedDate)
or datediff(month, d0.CreatedDate, dateadd(day, 1, d0.CreatedDate)) >= 4
or d0.ID is null then 1 end
) as GapCount
from data d1 left outer join data d0
on d0.ID = d1.ID and d0.RN = d1.RN - 1 /* connect to the one before */
where
group by ID

how to query two column of same table with two condition with groupby

Table :tbl_user
dateofregistration ID registrationstate
6-03-11 3 0
6-03-11 1 0
6-03-11 2 1
7-03-11 2 1
7-03-11 1 1
how can I display result like this for sql server 2008 express
date TotalID(count) Total State(0 only)
6-03-11 3 2
7-03-11 2 0
I have tried with this
SELECT CONVERT(varchar(10), dateofregistration, 103) AS Date,
(select COUNT(ID)) AS Subbase,
(Select Count(ID)from tbl_User where (registrationstate='0')) AS Totalchurn
FROM tbl_User
GROUP BY CONVERT(varchar(10), dateofregistration, 103);
but wrong result.Any help plz.
How about;
select
cast(dateofregistration as date),
count(distinct id), --or * for all
sum(
case registrationstate when '0' then 1 else 0 end
)
from tbl_user
group by cast(dateofregistration as date)
order by 1
2011-06-03 3 2
2011-07-03 2 0
SELECT CONVERT(varchar(10), dateofregistration, 103) AS Date,
COUNT(1) AS Subbase,
SUM(CASE WHEN registrationstate='0' THEN 1 ELSE 0 END) AS Totalchurn
FROM tbl_User
GROUP BY CONVERT(varchar(10), dateofregistration, 103)
ORDER BY 1
You were nearly there. You don't need a subselect for COUNT(ID) since that is handled by the GROUP BY. You group by date, and so the count will be the number of IDs within each date. I've made the count distinct, just in case you can have multiple registrations of the same ID on one day.
Your subquery was almost right - it needs to be correlated with the main query by selecting rows with the same registration date.
SELECT CONVERT(varchar(10), dateofregistration, 103) AS Date,
COUNT(DISTINCT ID) AS TotalID,
(Select Count(*) from tbl_User t2 where (registrationstate='0') AND t2.registrationdate=t1.registrationdate) AS Totalchurn
FROM tbl_User t1
GROUP BY CONVERT(varchar(10), dateofregistration, 103);

Resources