Get a unique row based on a column item in SQL query - sql

I have a sql query to return IT tickets and their satisfaction scores however because of the way our ticketing system works this sometimes returns 2 rows for a ticket reference - 1 with a "Not Rated" rating and one with the real rating.
My question is, is there a way to get the query to only return a single row for each Ticket Reference and only return the "Not Rated" rating if another rating does not exist. i.e. when there are 2 rows with the same ticket reference and one has a rating of "Excellent" and one has a rating of "Not Rated" that it only returns the row with the "Excellent" rating. Any with only 1 row that is "Not Rated" should be returned.
The query looks like this so far -
SELECT DISTINCT
t.Rating_Date,
t.id AS 'Ticket Reference',
[Rating]
= CASE
When tt.[rating_id] = 20 then 'Poor'
When tt.[rating_id] = 15 then 'Average'
When tt.[rating_id] = 17 then 'Good'
When tt.[rating_id] = 6 then 'Excellent'
else 'Not Rated'
END,
[subject],
[priority],
[status],
uu.name,
[assignee_id],
[Location],
[technology]
FROM
[DB1].[dbo].[table1] t
INNER JOIN [DB1].[dbo].[table2] tt
ON t.id=tt.ticket_id
LEFT JOIN [DB1].[dbo].[table3] uu
ON t.assignee_id=uu.id
WHERE
t.rating_date > '2013-07-01'
AND status = 'closed'
AND location = 'UK'
AND technology <> 'Not Known'
AND group_id = '5678912'
ORDER BY
t-rating_date

I think you can wrap the first query, order it by rating_id (if Not rated value is 0 you should order DESC, if Not rated is the highest you should order ASC) and then select the first record using the TOP function of sql-server.

I suppose that the following logic can be implemented here. Please, be aware of that I am using sample data and in order to implement the solution you should understand the idea.
Let's say we have the following data:
Note: I am using id "0" for "Not Rated" status.
If I have understand your need correctly, the output data should be something like this:
As you can see from the screenshot above, for Tickets with IDs 1 and 4 we have "Not Rated" records, but we are not showing them. The "Not Rated" state is displayed only for Ticket with ID 3.
The solution:
;WITH DataSource AS
(
SELECT ROW_NUMBER() OVER (PARTITION BY [TicketID] ORDER BY [TicketRateID] DESC) AS [RowNumber]
,[TicketID]
,[TicketRateID]
FROM #DataSource
)
SELECT [TicketID]
,[RowNumber]
,[TicketRateID]
FROM DataSource
The SQL statement above is using ROW_NUMBER function in order to create a unique ID for each set of records for given tickets. We are sorting the records with DESC directive in order to be sure the "0"/"Not rated" records will have bigger ID.
The output of previous statement is:
As you can see from the screenshot above, we need to display only this records with RowNumber equal to 1. This is done simple with WHERE clause.
Follows, full working example:
SET NOCOUNT ON
GO
DECLARE #DataSource TABLE
(
[TicketID] TINYINT
,[TicketRateID] TINYINT
)
INSERT INTO #DataSource ([TicketID],[TicketRateID])
VALUES (1,6)
,(1,0)
,(2,20)
,(3,0)
,(4,0)
,(4,15)
;WITH DataSource AS
(
SELECT ROW_NUMBER() OVER (PARTITION BY [TicketID] ORDER BY [TicketRateID] DESC) AS [RowNumber]
,[TicketID]
,[TicketRateID]
FROM #DataSource
)
SELECT [TicketID]
,[RowNumber]
,[TicketRateID]
FROM DataSource
WHERE [RowNumber] = 1
SET NOCOUNT OFF
GO

Related

SQL - Count new entries based on last date

I have a table with the follow structure
ID ReportDate Object_id
What I need to know, is the count of new and count of old (Object id's)
For example: If I have the data below:
I want the following output grouped by ReportDate:
I thought a way doing it using a Where clause based on date, however i need the data for all the dates I have in the table. To see the count of what already existed in the previous report and what is new at that report. Any Ideas?
Edit: New/Old definition- New would be the records that never appeared before that report run date and appeared on this one, whereas old is the number of records that had at least one match in previous dates. I'll edit the post to include this info.
managed to do it using a left join. Below is my solution in case it helps anyone in the future :)
SELECT table.ReportRunDate,
-1*sum(table.ReportRunDate = new_table.init_date) as count_new,
-1*sum(table.ReportRunDate <> new_table.init_date) as count_old,
count(*) as count_total
FROM table LEFT JOIN
((SELECT Object_ID, min(ReportRunDate) as init_date
FROM table
GROUP By OBJECT_ID) as new_table)
ON table.Object_ID = new_table.Object_ID
GROUP BY ReportRunDate
This would work in Oracle, not sure about ms-access:
SELECT ReportDate
,COUNT(CASE WHEN rnk = 1 THEN 1 ELSE NULL END) count_of_new
,COUNT(CASE WHEN rnk <> 1 THEN 1 ELSE NULL END)count_of_old
FROM (SELECT ID
,ReportDate
,Object_id
,RANK() OVER (PARTITION BY Object_id ORDER BY ReportDate) rnk
FROM table_name)
GROUP BY ReportDate
Inner query should rank each occurence of object_id based on the ReportDate so the 1st occurrence of certain object_id will have rank = 1, the next one rank = 2 etc.
Then the outer query counts how many records with rank equal/not equal 1 are the within each group.
I assumed that 1 object_id can appear only once within each reportDate.

Modify my SQL Server query -- returns too many rows sometimes

I need to update the following query so that it only returns one child record (remittance) per parent (claim).
Table Remit_To_Activate contains exactly one date/timestamp per claim, which is what I wanted.
But when I join the full Remittance table to it, since some claims have multiple remittances with the same date/timestamps, the outermost query returns more than 1 row per claim for those claim IDs.
SELECT * FROM REMITTANCE
WHERE BILLED_AMOUNT>0 AND ACTIVE=0
AND REMITTANCE_UUID IN (
SELECT REMITTANCE_UUID FROM Claims_Group2 G2
INNER JOIN Remit_To_Activate t ON (
(t.ClaimID = G2.CLAIM_ID) AND
(t.DATE_OF_LATEST_REGULAR_REMIT = G2.CREATE_DATETIME)
)
where ACTIVE=0 and BILLED_AMOUNT>0
)
I believe the problem would be resolved if I included REMITTANCE_UUID as a column in Remit_To_Activate. That's the REAL issue. This is how I created the Remit_To_Activate table (trying to get the most recent remittance for a claim):
SELECT MAX(create_datetime) as DATE_OF_LATEST_REMIT,
MAX(claim_id) AS ClaimID,
INTO Latest_Remit_To_Activate
FROM Claims_Group2
WHERE BILLED_AMOUNT>0
GROUP BY Claim_ID
ORDER BY Claim_ID
Claims_Group2 contains these fields:
REMITTANCE_UUID,
CLAIM_ID,
BILLED_AMOUNT,
CREATE_DATETIME
Here are the 2 rows that are currently giving me the problem--they're both remitts for the SAME CLAIM, with the SAME TIMESTAMP. I only want one of them in the Remits_To_Activate table, so only ONE remittance will be "activated" per Claim:
enter image description here
You can change your query like this:
SELECT
p.*, latest_remit.DATE_OF_LATEST_REMIT
FROM
Remittance AS p inner join
(SELECT MAX(create_datetime) as DATE_OF_LATEST_REMIT,
claim_id,
FROM Claims_Group2
WHERE BILLED_AMOUNT>0
GROUP BY Claim_ID
ORDER BY Claim_ID) as latest_remit
on latest_remit.claim_id = p.claim_id;
This will give you only one row. Untested (so please run and make changes).
Without having more information on the structure of your database -- especially the structure of Claims_Group2 and REMITTANCE, and the relationship between them, it's not really possible to advise you on how to introduce a remittance UUID into DATE_OF_LATEST_REMIT.
Since you are using SQL Server, however, it is possible to use a window function to introduce a synthetic means to choose among remittances having the same timestamp. For example, it looks like you could approach the problem something like this:
select *
from (
select
r.*,
row_number() over (partition by cg2.claim_id order by cg2.create_datetime desc) as rn
from
remittance r
join claims_group2 cg2
on r.remittance_uuid = cg2.remittance_uuid
where
r.active = 0
and r.billed_amount > 0
and cg2.active = 0
and cg2.billed_amount > 0
) t
where t.rn = 1
Note that that that does not depend on your DATE_OF_LATEST_REMIT table at all, it having been subsumed into the inline view. Note also that this will introduce one extra column into your results, though you could avoid that by enumerating the columns of table remittance in the outer select clause.
It also seems odd to be filtering on two sets of active and billed_amount columns, but that appears to follow from what you were doing in your original queries. In that vein, I urge you to check the results carefully, as lifting the filter conditions on cg2 columns up to the level of the join to remittance yields a result that may return rows that the original query did not (but never more than one per claim_id).
A co-worker offered me this elegant demonstration of a solution. I'd never used "over" or "partition" before. Works great! Thank you John and Gaurasvsa for your input.
if OBJECT_ID('tempdb..#t') is not null
drop table #t
select *, ROW_NUMBER() over (partition by CLAIM_ID order by CLAIM_ID) as ROW_NUM
into #t
from
(
select '2018-08-15 13:07:50.933' as CREATE_DATE, 1 as CLAIM_ID, NEWID() as
REMIT_UUID
union select '2018-08-15 13:07:50.933', 1, NEWID()
union select '2017-12-31 10:00:00.000', 2, NEWID()
) x
select *
from #t
order by CLAIM_ID, ROW_NUM
select CREATE_DATE, MAX(CLAIM_ID), MAX(REMIT_UUID)
from #t
where ROW_NUM = 1
group by CREATE_DATE

Get the highest date with combination of day,month and year

I am working on a game , where i have a table called punishment which have following schema
CREATE TABLE Punishment
(
PunishmentId int identity(1,1) not null ,
PunishmentDay int ,
PunishmentMonth int ,
PunishmentYear int ,
GameId int
)
PunishmentDay ,PunishmentMonth ,PunishmentYear are numbers which can be either zero or null or any number.
GameId can be repeat in this table , means i can get multiple times punishment for the same game.
Now my question is i have to get the punishmentId in which user get the highest punishment.
I have tried following way but not able to get the max record ..
SELECT PunishmentId, DATEADD(DD,PunishmentDay,DATEADD(MM,PunishmentMonth,(DATEADD(YY,PunishmentYear,GETDATE()))))
FROM Punishment
You can use ROW_NUMBER() instead of a correlated subquery to find the max year/month/day. ROW_NUMBER() will allow you assign an incrementing row number based on an order by clause. You can then select only rows where that rownumber = 1. Try something like this:
SELECT * FROM
( SELECT PunishmentId,PunishmentDay,PunishmentMonth,PunishmentYear, DATEADD(DD,PunishmentDay,DATEADD(MM,PunishmentMonth,(DATEADD(YY,PunishmentYear,GETDATE())))) TotalDays, ROW_NUMBER() OVER(PARTITION BY GameId ORDER BY PunishmentYear, PunishmentMonth, PunishmentDay DESC) RowNumber
FROM Punishment
WHERE GameId = #GameId
) OrderedPunishment
WHERE RowNumber = 1
Note: I haven't checked this for syntax, and I based the statement off your statement (pretty much ignored your nested dateadds, maybe there is a better way to do that too). I also only just now noticed your second table name ConvictCases_G... I didn't see that that is supposed to be Punishment.
This should work
SELECT TOP 1 PunishmentId
FROM
(
SELECT TOP 100 PERCENT
PunishmentId ,
SUM(PunishmentDay + PunishmentMonth*30 + PunishmentYear*360) AS MaxPunishment
FROM #p
GROUP BY PunishmentId
ORDER BY SUM(PunishmentDay + PunishmentMonth*30 + PunishmentYear*360) DESC
)
AS X
I have solved this by following sql
SELECT PunishmentId,PunishmentDay,PunishmentMonth,PunishmentYear, DATEADD(DD,PunishmentDay,DATEADD(MM,PunishmentMonth,(DATEADD(YY,PunishmentYear,GETDATE()))))
FROM Punishment
WHERE GameId=#GameId and
DATEADD(DD,PunishmentDay,DATEADD(MM,PunishmentMonth,(DATEADD(YY,PunishmentYear,GETDATE()))))
= (SELECT MAX(DATEADD(DD,PunishmentDay,DATEADD(MM,PunishmentMonth,(DATEADD(YY,PunishmentYear,GETDATE()))))) FROM Punishment where GameId=#GameId)
but still waiting if there any better solution can be done ..
You could also use:
SELECT TOP 1 WITH TIES
PunishmentId,PunishmentDay,PunishmentMonth,PunishmentYear,
DATEADD(DD,PunishmentDay,DATEADD(MM,PunishmentMonth,(DATEADD(YY,PunishmentYear,GETDATE())))) AS PunishmentEndDate
FROM Punishment
WHERE GameId=#GameId
ORDER BY PunishmentEndDate DESC

Create View which removes multiple slices of data from table based on different criteria

The below table has PC asset information and I need to remove slices of data from it based on different criteria.
I need to create a View in SQL Server 2005 which returns my results.
I tried to accomplish my goals using temporary tables until I realized that I could not use temporary tables in a View.
I then tried to use a CTE until I realized that deleting data from a CTE would also delete data from the actual table.
I cannot delete data from the actual table. I cannot create another table in the database either.
The table has 160,000 records.
The table:
TABLE dsm_hardware_basic
(
[UUID] binary(16) -- Randomly generated 16 digit key that is unique for each record, only column with no duplicate rows.
[HostUUID] binary(16) -- Randomly generated 16 digit key, column has duplicate rows.
[Name] nvarchar(255) -- Column that contains hostnames of computer assets. Example of record: PCASSET001. Column has duplicate rows.
[LastAgentExecution] datetime -- The last time that the software agent that collects asset information ran on the PC.
[HostName] nvarchar(255) -- The fully qualified domain name of the PC. Example of record: PCASSET001.companydomain.com. Column has duplicate rows.
)
I will explain what I want to accomplish:
1) Read in all the information from the table dbo.dsm_hardware_basic. Lets call this: dsm_hardware_basic_copy.
2) Query dbo.dsm_hardware_basic and remove data that fits the following criteria from dsm_hardware_basic_copy.
This basically removes the duplicate [HostUUID] with the oldest [LastAgentExecution] time.:
SELECT ,dsm_hardware_basic.[HostUUID]
,MIN(dsm_hardware_basic.[LastAgentExecution]) AS [LastAgentExecution]
FROM dsm_hardware_basic
WHERE dsm_hardware_basic.[HostUUID] <> ''
GROUP BY dsm_hardware_basic.[HostUUID]
HAVING COUNT(*) = 2 -- The tiny amount of rows where this count is >2 will be left alone.
3) Additionaly query dbo.dsm_hardware_basic and remove data that fits the following criteria from dsm_hardware_basic_copy:
This basically removes the duplicate [HostName] with the oldest [LastAgentExecution] time.:
SELECT ,dsm_hardware_basic.[HostName]
,MIN(dsm_hardware_basic.[LastAgentExecution]) AS [LastAgentExecution]
FROM dsm_hardware_basic
WHERE dsm_hardware_basic.[HostName] <> ''
GROUP BY dsm_hardware_basic.[HostName]
HAVING COUNT(*) > 1
I wasn't sure how to do this in the above select, but not only should the COUNT of [HostName] be > 1, but [Name] should equal everything in [HostName] before the first period in [HostName]. Example [Name]: PCASSET001. Example [HostName]: PCASSET001.companydomain.com. I know this sounds strange considering the kind of PC data we are talking about in these two columns, but it is something I actually need to contend with.
3) Additionally query dbo.dsm_hardware_basic and remove data that fits the following criteria from dsm_hardware_basic_copy:
This basically removes the duplicate [Name] with the oldest [LastAgentExecution] time.:
SELECT ,dsm_hardware_basic.[Name]
,MIN(dsm_hardware_basic.[LastAgentExecution]) AS [LastAgentExecution]
FROM dsm_hardware_basic
WHERE dsm_hardware_basic.[Name] <> ''
GROUP BY dsm_hardware_basic.[Name]
HAVING COUNT(*) = 2 -- The tiny amount of rows where this count is >2 will be left alone.
You've actually asked several different questions here and I'm not sure I completely follow the logic of the query, however, constructing it should not be too difficult.
To start with, you can work dsm_hardware_basic directly rather than a copy:
SELECT
*
FROM dsm_hardware_basic
Now the part that
removes the duplicate [HostUUID] with the oldest [LastAgentExecution]
time
SELECT
dsm_hardware_basic.*
FROM dsm_hardware_basic
INNER JOIN
(
SELECT [UUID], ROW_NUMBER() OVER
(PARTITION BY [HostUUID]
ORDER BY [LastAgentExecution] DESC) AS host_UUID_rank
FROM dsm_hardware_basic
WHERE
[HostUUID] <> ''
) AS
duplicate_host_UUID_filtered ON dsm_hardware_basic.UUID = duplicate_host_UUID_filtered.UUID
AND duplicate_host_UUID_filtered.host_UUID_rank = 1
What we've done is partitioned your table by HostUUID sorted by newest LastAgentExecution and removed every UUID from the query that matches our result using a JOIN.
We can now apply the same logic to your HostName:
SELECT
dsm_hardware_basic.*
FROM dsm_hardware_basic
INNER JOIN
(
SELECT [UUID], ROW_NUMBER() OVER
(PARTITION BY [HostUUID]
ORDER BY [LastAgentExecution] DESC) AS host_UUID_rank
FROM dsm_hardware_basic
WHERE
[HostUUID] <> ''
) AS
duplicate_host_UUID_filtered ON dsm_hardware_basic.UUID = duplicate_host_UUID_filtered.UUID
AND duplicate_host_UUID_filtered.host_UUID_rank = 1
INNER JOIN
(
SELECT [UUID], ROW_NUMBER() OVER
(PARTITION BY [HostName]
ORDER BY [LastAgentExecution] DESC) AS host_UUID_rank
FROM dsm_hardware_basic
WHERE
[HostName] <> ''
) AS
duplicate_HostName_filtered ON dsm_hardware_basic.UUID = duplicate_HostName_filtered.UUID
AND duplicate_HostName_filtered.host_UUID_rank = 1
I'll leave the final part to you as an exercise. Finally, after you've done debugging, just add CREATE VIEW to this.

How to write this SQL Order By Clause

I have a SQL Query which I am trying to write and am now a bit stuck on how to write the order by clause.
Basically the table I am selecting from has items with a severity value. I want to select these items and order them so that the Severity column is ordered Severity 1-4 then 0 and the log date is descending for each.
Severity 1 is highest 4 is lowest and 0 respresents an unassigned severity, I need to display these items Highest severity, oldest item first, lowest severity, newest item last.
My query so far:
SELECT
[TicketID],
[type],
[Product],
[Description],
[LoggedBy],
[LogDate],
[Department],
[AssignedTo],
[Severity],
[Status],
[LastUpdatedBy],
[LastUpdatedDate]
FROM
SupportTicketsTbl
WHERE
TicketID NOT IN
(
SELECT
tck.ticketID
FROM
SupportTicketsTbl tck
JOIN
tblTicketsInEvents tie
ON
tck.TicketID = tie.ticketID
JOIN
tblSupportEvent ev
ON
tie.eventID = ev.id
where
ev.type = 1
)
AND
Status <> 'Complete'
I guess the easiest way is to create a table variable and select all the Items that are not 0 into it in the order I want, then select all the 0 items into my table variable, and finally just select everything back out of the table variable, but this seems a bit messy so im wondering if there is a more elegant solution?
Thanks
Since you didn't like the UNION answer, and I'm not sure if UNION is guaranteed to preserve order...
ORDER BY CASE WHEN severity = 0 THEN 999 ELSE Severity END, date
You can order by a case statement like this:
ORDER BY CASE Severity WHEN 0 THEN 1 ELSE 2 END, Severity
First, Select all of the ones with severity levels 1-4 using a standard orderby clause, then union the results with a second query that selects only the ones with severity level 0.
Unless I'm very much mistaken, it's something like this:
ORDER BY severity DESC, date DESC
Insert that line into your SQL.
This will sort the data by Severity first, and if they have the same severity, then sort it according to date.