Left join grouped data(with min/max) without losing rows - sql

Hello and thank you in advance! I have the following query that retrieves the table i need (highlighted in green). The top select statement gets the min(eventdate) and the bottom select statement (that is Unioned) gets a concatenation of min(eventdate) and max(eventdate). This is most important to maintain. This returns the data just fine, but now i'm stuck having to join the rest of the data to this table (highlighted in red).
How can I join without having to group all my columns and lose some data. If you look at the image you can see the data returned because event time doesn't need to be grouped, it's all the same. However, this is not always the case, event time changes even for the same ID. Any help on this would be greatly appreciated.
(Select id,__DisplayName, LEFT(cast(min(EventDate) as varchar), len(min(eventdate)) -7)Eventdate from SP_HS_Marketing_ExtEvents WHERE __ApprovalStatus = 'Approved' and __EventType not in ('1','-1','-2')
group by ID, __DisplayName
Union ALL
Select id,__DisplayName,
LEFT(cast(min(EventDate) as varchar), len(min(eventdate)) -7) + ' - ' + LEFT(cast(max(EventDate) as varchar), len(max(eventdate)) -7) Eventdate from SP_HS_Marketing_ExtEvents WHERE __ApprovalStatus = 'Approved' and __EventType = -2
group by ID, __DisplayName)
Thank you,
Adam

to achieve this you can use a CTE to find the min and max dates, then join back to the original table and concat + cast the date.
With cte as (
Select min(eventdate) as minDate, max(eventdate) as maxDate, Id
from SP_HS_Marketing_ExtEvents
WHERE __ApprovalStatus = 'Approved' and __EventType not in ('1','-1','-2')
group by id
)
Select Distinct s.EventTitle,
DATENAME(MONTH,cte.minDate) + ' ' + DATENAME(DAY,cte.minDate) + ' ' + RIGHT('00' + CAST(YEAR(cte.minDate) AS VARCHAR),2)
+ ' - ' +
DATENAME(MONTH,cte.maxDate) + ' ' + DATENAME(DAY,cte.maxDate) + ' ' + RIGHT('00' + CAST(YEAR(cte.maxDate) AS VARCHAR),2) AS EventDate
,EventTime
,Location
from SP_HS_Marketing_ExtEvents as s
join cte as cte
on cte.ID = s.id
Rextester

Related

Add a leading zero to months less than 10 and trim 4 digit years to 2 digits

I need to print manipulate month and string which I fetch from a table and display in the format like '12/20', '11/20', 09/20'
For this I need to trim the last 2 digits from year and also a leading zero to months which are less than 10.
SELECT
CAST(MONTH(O.AddDate) AS VARCHAR(2)) + '/' + CAST(YEAR(O.AddDate) AS VARCHAR(4)) AS TimeStamp
FROM
[Order] O
WHERE
O.CountryCode = 9009
GROUP BY
CAST(MONTH(O.AddDate) AS VARCHAR(2)) + '/' + CAST(YEAR(O.AddDate) AS VARCHAR(4))
This provided output in the format of '10/2020', '8/2020' but I require it to be like '08/20', '10/20'
You can use format():
select format(O.AddDate, 'MM/yy') as timestamp
from [Order] o
group by format(O.AddDate, 'MM/yy')
Obviously that's not your entire query; otherwise, if you have no aggregation function in the select clause, you can use select distinct instead of group by.
IMHO formatting dates is a presentation layer concern. SQL Server should provide the data and presentation layer should show the data to user as required. So I would write the query like this:
SELECT DISTINCT
YEAR(O.AddDate) AS AddDateYear, MONTH(O.AddDate) AS AddDateMonth
FROM
[Order] O
WHERE
O.CountryCode = 9009
Or like this:
SELECT
YEAR(O.AddDate) AS AddDateYear, MONTH(O.AddDate) AS AddDateMonth
FROM
[Order] O
WHERE
O.CountryCode = 9009
GROUP BY
YEAR(O.AddDate), MONTH(O.AddDate)
However, if you insist in formatting the date like you requested, then here is the query:
SELECT
RIGHT('0' + CAST(T.AddDateMonth AS varchar(2)), 2) + '/'
+ RIGHT(CAST(T.AddDateYear) AS varchar(4), 2) AS Y2kVulnerableTimestamp
FROM
(
SELECT DISTINCT
YEAR(O.AddDate) AS AddDateYear, MONTH(O.AddDate) AS AddDateMonth
FROM
[Order] O
WHERE
O.CountryCode = 9009
) T

Group only specific values and get min and max dates

Hello and thank you in advance, not sure my title will suffice but hoping the image with the desired output will help. Essentially, the data where eventtype=2 needs to be grouped by ID. Once grouped, I still need to concatenate the Min(eventdate) and max(eventdate).
The First image is just the simple select statment(select * from TABLE).
Any help would be greatly appreciated.
SELECT __DisplayName,
EventDate,
RIGHT(CONVERT(varchar(20),DATEADD(HOUR, -5 , EventDate), 100),7)
+ ' to ' +
RIGHT(CONVERT(varchar(20), DATEADD(HOUR, -5 , EndDate), 100),7),
location,
__EventType
FROM (SELECT *,
ROW_NUMBER() OVER
( PARTITION BY Id
ORDER BY eventdate asc
) AS ROWNUM
FROM TABLE
) x
WHERE ROWNUM = 1
AND __ApprovalStatus = 'Approved'
AND __EventType not in ('1','-1');
Thank you all for your input. YOu led me to the right answer. SO what I did is essentially union two queries. One that gets all data where eventtype is not in -1,1, and -2 and then another query that essentially concatenates the min and max but only where event type = -2
(Select __DisplayName, cast(min(EventDate)as varchar) Eventdate from TABLE WHERE __ApprovalStatus = 'Approved' and __EventType not in ('1','-1','-2')
group by ID, __DisplayName
Union ALL
Select __DisplayName,
cast(min(EventDate)as varchar) + ' - ' + cast(max(EventDate) as varchar) Eventdate from TABLE WHERE __ApprovalStatus = 'Approved' and __EventType = -2 group by ID, __DisplayName)
order by Eventdate

SQL Server : remove duplicates from count()

I'm creating a report in a SQL Server database. I will show it's code first and then describe what it does and where is problem.
SELECT
COUNT(e.flowid) AS [count],
t.name AS [process],
CAST(DATEPART(YEAR, e.dtcr) AS VARCHAR) + '-' + CAST(RIGHT('0' + RTRIM(DATEPART(MONTH, e.dtcr)), 2) AS VARCHAR) + '-' + CAST(RIGHT('0' + RTRIM(DATEPART(DAY, e.dtcr)), 2) AS VARCHAR) AS [day]
FROM
dbo.[Event] e
JOIN
dbo.Flow f ON e.flowid = f.id
JOIN
dbo.WorkOrder o ON f.workorderno = o.number
AND o.treenodeid IN (26067, 26152, 2469, 1815, 1913) -- only from requested processes
JOIN
dbo.TreeNode t ON o.treenodeid = t.id -- for process name in select statement
JOIN
dbo.Product p ON f.productid = p.id
AND p.materialid NOT IN (26094, 27262, 27515, 27264, 28192, 28195, 26090, 26092, 26093, 27065, 26969, 27471, 28351, 28353, 28356, 28976, 27486, 29345, 29346, 27069, 28653, 28654, 26735, 26745, 28686) -- exclude unwanted family codes
WHERE
e.pass = 1 -- only passed units
AND e.treenodeid IN (9036, 9037, 9038, 9039, 12594, 26330) -- only from requested events
AND e.dtcr BETWEEN '2015-12-01 00:00:00.000' AND '2016-05-31 23:59:59.999' -- only from requested time interval
GROUP BY
DATEPART(YEAR, e.dtcr), DATEPART(MONTH, e.dtcr), DATEPART(DAY, e.dtcr), t.name
ORDER BY
[day]
What query does is count units that passed specific events in a time periods (with some filters).
Important tables are:
Event - basically log for units passing specific events.
Product - list of units.
Output is something like this:
COUNT PROCESS DAY
71 Process-1 2015-12-01
1067 Process-2 2015-12-01
8 Process-3 2015-12-01
3 Process-4 2015-12-01
15 Process-1 2015-12-02
276 Process-2 2015-12-02
47 Process-3 2015-12-02
54 Process-4 2015-12-02
It does well but there is an issue. In some specific cases unit can pass same event several times and this query counts every such passing. I need to count every unit only once.
"Duplicated" records are in Event table. They have different dates and ids. Same for all records I need to count only once is flowid. Is there any simple way to achieve this?
Thank you for your time and answers!
To count each flowid only once, do count(distinct flowid), i.e.
SELECT
COUNT(distinct e.flowid) AS [count],
t.name AS [process],
CAST(DATEPART(YEAR, e.dtcr) AS VARCHAR) + '-' + CAST(RIGHT('0' + RTRIM(DATEPART(MONTH, e.dtcr)), 2) AS VARCHAR) + '-' + CAST(RIGHT('0' + RTRIM(DATEPART(DAY, e.dtcr)), 2) AS VARCHAR) AS [day]
FROM
...
It sounds like you need the first time that something passes the threshold. You can get the first time using row_number(). This can be tricky with the additional conditions on the query. This modification might work for you:
select sum(case when seqnum = 1 then 1 else 0 end) as cnt,
. . .
from (select e.*,
row_number() over (partition by eventid order by e.dtcr) as seqnum
from event e
where e.pass = 1 and -- only passed units
e.treenodeid IN (9036, 9037, 9038, 9039, 12594, 26330) and
e.dtcr >= '2015-12-01' AND e.dtcr < '2016-06-01'
) e join
. . .
You don't specify how the same event is identified for the duplicates. The above uses eventid for this purpose.

Order by year in post date

My sql query is:
SELECT DISTINCT
SUBSTRING(DATENAME(MONTH, PostDate), 1, 3) + '-' + CAST(YEAR(PostDate) AS VARCHAR(4)) AS PostArchive,
Posts = COUNT(*)
FROM
Post WHERE Verified=1
GROUP BY
SUBSTRING(DATENAME(MONTH, PostDate), 1, 3) + '-' + CAST(YEAR(PostDate) AS VARCHAR(4)),
YEAR(PostDate), MONTH(PostDate)
ORDER BY PostArchive
Its gives a result like this:
PostArchive Posts
------------------------
Mar-2009 1
Mar-2010 1
May-2005 1
May-2011 1
May-2012 1
May-2013 1
But I want a result order by date(year) like this.
PostArchive Posts
------------------------
May-2005 1
Mar-2009 1
Mar-2010 1
May-2011 1
May-2012 1
May-2013 1
I search and found this link but unable to solve my problem.
I try :
ORDER BY CONVERT(DateTime, PostArchive,101) DESC
But it gives me a error:
Invalid column name 'PostArchive'.
Is there any way to do this or I am in wrong way.Thanks.
The reason for the error is that PostArchive is the name you've given to the column on the SELECT line, which is effectively the output of the query. The ORDER BY clause does not look at that, it looks at its input to the query, which in this case is PostDate
I assume that you didn't really mean that you want to order it by
year, but instead by year/month. The ordering issue that you have is
because you are ordering it as a character and not as a date.
You don't need DISTINCT, since you already GROUP BY.
Main problem is that you already converted to VARCHAR. Hence, months
are unsortable.
ssss
-- Create a CTE (inline view)
WITH T AS (
SELECT YEAR(PostDate) PostYear
, MONTH(PostDate) PostMM
, SUBSTRING(DATENAME(MONTH, PostDate),1,3) PostMonth
, COUNT(*) Posts
FROM Post
WHERE Verified = 1
GROUP BY YEAR(PostDate)
, MONTH(PostDate)
, DATENAME(MONTH, PostDate)
)
-- Build you date string
SELECT PostMonth + '-' + CAST(PostYear AS VARCHAR(4)) AS PostArchive
, Posts
FROM T
-- Sort it by the components separately
ORDER BY PostYear
-- Don't use the character, otherwise, Aug will come before Mar
, PostMM
I used CTE to get the result try this
with tempTable (PostArchiveMonth , PostArchiveYear , PostArchiveMonthName , Posts )
(
select month(PostDate) , YEAR(PostDate) , SUBSTRING(DATENAME(MONTH, PostDate), 1, 3)
COUNT(*)
FROM Post
WHERE Verified=1
group by MONTH(PostDate) ,YEAR( PostDate)
,SUBSTRING(DATENAME(MONTH, PostDate), 1, 3)
)
select PostArchiveMonthName +'-' + PostArchiveYear as PostArchive , Posts
from tempTable
order by PostArchiveYear , PostArchiveMonth
Try
SELECT DISTINCT
SUBSTRING(DATENAME(MONTH, PostDate), 1, 3) + '-' + CAST(YEAR(PostDate) AS VARCHAR(4)) AS PostArchive,
Posts = COUNT(*)
FROM
Post WHERE Verified=1
GROUP BY
SUBSTRING(DATENAME(MONTH, PostDate), 1, 3) + '-' + CAST(YEAR(PostDate) AS VARCHAR(4)),
YEAR(PostDate), MONTH(PostDate)
Order by Month(PostDate), Year(PostDate)
Try to change this:
ORDER BY PostArchive
...to this...
ORDER BY YEAR(PostDate)

Using SELECT result in another SELECT

So here is my query
SELECT
*
FROM
Score AS NewScores
WHERE
InsertedDate >= DATEADD(mm, -3, GETDATE());
SELECT
ROW_NUMBER() OVER( ORDER BY NETT) AS Rank,
Name,
FlagImg,
Nett,
Rounds
FROM (
SELECT
Members.FirstName + ' ' + Members.LastName AS Name,
CASE
WHEN MenuCountry.ImgURL IS NULL THEN
'~/images/flags/ismygolf.png'
ELSE
MenuCountry.ImgURL
END AS FlagImg,
AVG(CAST(NewScores.NetScore AS DECIMAL(18, 4))) AS Nett,
COUNT(Score.ScoreID) AS Rounds
FROM
Members
INNER JOIN
Score
ON Members.MemberID = Score.MemberID
LEFT OUTER JOIN MenuCountry
ON Members.Country = MenuCountry.ID
WHERE
Members.Status = 1
GROUP BY
Members.FirstName + ' ' + Members.LastName,
MenuCountry.ImgURL
) AS Dertbl
ORDER BY;
The query is to give a result set for a GridView based leaderboard and what I want is to only get the average of Scores that are less than 3 months old. I have this in 2 parts as you can see and obviously it gives an error like this.
Msg 4104, Level 16, State 1, Line 2
The multi-part identifier "NewScores.NetScore" could not be bound.
Which is because of this AVG(CAST(NewScores.NetScore AS DECIMAL(18, 4))) AS Nett
How do I make it so that I can use NewScores there so I'm only getting the average of the scores less than 3 months old?
EDIT: Using the answers people provided I've solved it by using a join in the correct place and here is the correct query:
SELECT ROW_NUMBER() OVER(ORDER BY NETT) AS Rank, Name, FlagImg, Nett, Rounds FROM (SELECT Members.FirstName + ' ' + Members.LastName AS Name, CASE WHEN MenuCountry.ImgURL IS NULL THEN '~/images/flags/ismygolf.png' ELSE MenuCountry.ImgURL END AS FlagImg, AVG(CAST(NewScores.NetScore AS DECIMAL(18, 4))) AS Nett, COUNT(NewScores.ScoreID) AS Rounds FROM Members INNER JOIN (SELECT * FROM Score WHERE InsertedDate >= DATEADD(mm, -5, GETDATE())) NewScores ON Members.MemberID = NewScores.MemberID LEFT OUTER JOIN MenuCountry ON Members.Country = MenuCountry.ID WHERE Members.Status = 1 GROUP BY Members.FirstName + ' ' + Members.LastName, MenuCountry.ImgURL) AS Dertbl ORDER BY Nett ASC
NewScores is an alias to Scores table - it looks like you can combine the queries as follows:
SELECT
ROW_NUMBER() OVER( ORDER BY NETT) AS Rank,
Name,
FlagImg,
Nett,
Rounds
FROM (
SELECT
Members.FirstName + ' ' + Members.LastName AS Name,
CASE
WHEN MenuCountry.ImgURL IS NULL THEN
'~/images/flags/ismygolf.png'
ELSE
MenuCountry.ImgURL
END AS FlagImg,
AVG(CAST(NewScores.NetScore AS DECIMAL(18, 4))) AS Nett,
COUNT(Score.ScoreID) AS Rounds
FROM
Members
INNER JOIN
Score NewScores
ON Members.MemberID = NewScores.MemberID
LEFT OUTER JOIN MenuCountry
ON Members.Country = MenuCountry.ID
WHERE
Members.Status = 1
AND NewScores.InsertedDate >= DATEADD(mm, -3, GETDATE())
GROUP BY
Members.FirstName + ' ' + Members.LastName,
MenuCountry.ImgURL
) AS Dertbl
ORDER BY;
What you are looking for is a query with WITH clause, if your dbms supports it. Then
WITH NewScores AS (
SELECT *
FROM Score
WHERE InsertedDate >= DATEADD(mm, -3, GETDATE())
)
SELECT
<and the rest of your query>
;
Note that there is no ; in the first half. HTH.
You are missing table NewScores, so it can't be found. Just join this table.
If you really want to avoid joining it directly you can replace NewScores.NetScore with SELECT NetScore FROM NewScores WHERE {conditions on which they should be matched}