SQL divide data in rows - sql

I have the following table named
Table: activity
userid appid type
1 a imp
2 a imp
2 a click
3 a imp
4 a imp
4 a click
5 b imp
5 b click
I am trying to calculate the click-through rate for each appid. In this instance we define a click-through as the (number of clicks) / (number of impressions). I have written the following SQL:
SELECT appid, type, count(*) from activity group by appid, type
and got the following outcome:
Output:
appid type count(*)
a click 2
a imp 4
b click 1
b imp 1
The next step is to do a row-wise division. Ultimately, I would like to achieve the following:
Goal:
appid click-through
a .5 # 2/4 = .5
b 1 # 1/1 = 1
How is this achieved? Ideally I want this to be done in one query, is that possible?

You can use conditional aggregation to do this:
select appid
, SUM(CASE WHEN type = 'click' THEN 1 END)*1.0
/ SUM(CASE WHEN type = 'imp' THEN 1 END) AS click_through
from activity
group by appid
Demo: SQL Fiddle
If using MySQL you can further simplify with:
select appid
, SUM(type = 'click')*1.0
/ SUM(type = 'imp') AS click_through
from activity
group by appid

Just count the impressions and clicks in subqueries and join them together:
select appid,
num_impressions,
num_clicks,
cast(num_clicks as float) / num_impressions as ctr
from(
select appid, count(1) as num_impressions
from activity
where type = 'imp'
group by appid
)a
join(
select appid, count(1) as num_clicks
from activity
where type = 'click'
group by appid
)b
on (a.appid = b.appid);
Note the type cast on num_clicks in ctr to avoid integer division.

CREATE TABLE #activity
(
userid INT ,
appid VARCHAR(1),
[type] VARCHAR(5)
)
INSERT INTO #activity
VALUES
(1, 'a' , 'imp'),
(2, 'a', 'imp'),
(2, 'a', 'click'),
(3, 'a', 'imp'),
(4, 'a', 'imp'),
(4, 'a', 'click'),
(5, 'b', 'imp'),
(5, 'b', 'click')
SELECT A.appid, CAST(a.Clicks AS FLOAT)/B.Imp
FROM
( SELECT appid, COUNT(1) Clicks
FROM #activity
WHERE [type] ='CLICK'
GROUP BY appid
) A
INNER JOIN
( SELECT appid, COUNT(1) Imp
FROM #activity
WHERE [type] ='imp'
GROUP BY appid
) B ON A.appid = B.appid
DROP TABLE #activity

You could do this with sub-queries since you are dealing with two different aggregates using different criteria:
SELECT d1.appid, (
SELECT count(*)
FROM activity d2
WHERE d2.appid = d1.appid
d2.type = 'click'
) / (
SELECT count(*)
FROM activity d3
WHERE d3.appid = d1.appid
d3.type = 'imp'
) AS click_through
FROM activity d1;

Related

SQL to select the 'first' date a project was made inactive for all projects

I am trying to work out the SQL I would need to select certain records, here is an example of what I'm trying to do:
Project number
Active/Inactive
Date
1
A
1/1/20
1
I
3/1/20
1
A
5/1/20
1
I
7/1/20
1
I
9/1/20
2
I
1/1/19
2
A
5/1/19
3
A
1/3/20
3
I
3/3/20
3
I
5/3/20
Note: A=Active project, I=Inactive.
What I would like to do is for each project where the project is currently inactive (i.e. the latest date for the project in the above table is set to I), return the row of the longest time ago it was made inactive, but NOT before it was last active (hope this is understandable!). So for the above table the following would be returned:
Project number
Active/Inactive
Date
1
I
7/1/20
3
I
3/3/20
So proj number 1 is inactive and the earliest time it was made inactive (after the last time it was active) is 7/1/20. Project 2 is not selected as it is currently active. Project 3 is inactive and the earliest time it was made inactive (after the last time it was active) is 3/3/20.
Thanks.
You could use the 'row_number' function to help you.
create TABLE #PROJECT(ProjectNumber int, [Status] varcha(1), [Date] date)
INSERT INTO #PROJECT VALUES
(1 ,'A' ,'1/1/20'),
(1 ,'I' ,'3/1/20'),
(1 ,'A' ,'5/1/20'),
(1 ,'I' ,'7/1/20'),
(1 ,'I' ,'9/1/20'),
(2 ,'I' ,'1/1/19'),
(2 ,'A' ,'5/1/19'),
(3 ,'A' ,'1/3/20'),
(3 ,'I' ,'3/3/20'),
(3 ,'I' ,'5/3/20')
select * from
(SELECT
row_number() over (partition by projectNumber order by [date]) as [index]
,*
FROM
#PROJECT
WHERE
[STATUS] = 'I'
) as a where [index] = 1
Using some effective date joins, this should work. I am using SQL Server. Create your tables and set up the same data set you provided:
CREATE TABLE dbo.PROJECTS
(
PROJ_NUM int NULL,
STTS char(1) NULL,
STTS_DT date NULL
) ON [PRIMARY]
GO
INSERT INTO dbo.PROJECTS values (1, 'A', '1/1/20');
INSERT INTO dbo.PROJECTS values (1, 'I', '3/1/20');
INSERT INTO dbo.PROJECTS values (1, 'A', '5/1/20');
INSERT INTO dbo.PROJECTS values (1, 'I', '7/1/20');
INSERT INTO dbo.PROJECTS values (1, 'I', '9/1/20');
INSERT INTO dbo.PROJECTS values (2, 'I', '1/1/19');
INSERT INTO dbo.PROJECTS values (2, 'A', '5/1/19');
INSERT INTO dbo.PROJECTS values (3, 'A', '1/3/20');
INSERT INTO dbo.PROJECTS values (3, 'I', '3/3/20');
INSERT INTO dbo.PROJECTS values (3, 'I', '5/3/20');
Write a sub-query that filters out just to the projects that are INACTIVE:
-- sub-query that gives you projects that are inactive
SELECT PROJ_NUM, STTS, STTS_DT FROM dbo.PROJECTS CURRSTTS
WHERE STTS_DT = (SELECT MAX(STTS_DT) FROM dbo.PROJECTS ALLP WHERE ALLP.PROJ_NUM = CURRSTTS.PROJ_NUM)
AND CURRSTTS.STTS = 'I'
;
Write another sub-query that provides you the last active status date for each project:
-- sub-query that gives you last active status date for each project
SELECT PROJ_NUM, STTS, STTS_DT FROM dbo.PROJECTS LASTACTV
WHERE STTS_DT = (SELECT MAX(STTS_DT) FROM dbo.PROJECTS ALLP WHERE ALLP.PROJ_NUM = LASTACTV.PROJ_NUM AND ALLP.STTS = 'A')
;
Combine those two sub-queries into a query that gives you the list of inactive projects with their last active status date:
-- sub-query using the 2 above to show only inactive projects with last active stts date
SELECT CURRSTTS.PROJ_NUM, CURRSTTS.STTS, CURRSTTS.STTS_DT, LASTACTV.STTS_DT AS LASTACTV_STTS_DT FROM dbo.PROJECTS CURRSTTS
INNER JOIN
(SELECT PROJ_NUM, STTS, STTS_DT FROM dbo.PROJECTS LASTACTV
WHERE STTS_DT = (SELECT MAX(STTS_DT) FROM dbo.PROJECTS ALLP WHERE ALLP.PROJ_NUM = LASTACTV.PROJ_NUM AND ALLP.STTS = 'A'))
LASTACTV ON CURRSTTS.PROJ_NUM = LASTACTV.PROJ_NUM
WHERE CURRSTTS.STTS_DT = (SELECT MAX(STTS_DT) FROM dbo.PROJECTS ALLP WHERE ALLP.PROJ_NUM = CURRSTTS.PROJ_NUM)
AND CURRSTTS.STTS = 'I'
Add one more layer to the query that selects the MIN(STTS_DT) that is greater than the LASTACTV_STTS_DT:
-- final query that uses above sub-query
SELECT P.PROJ_NUM, P.STTS, P.STTS_DT
FROM dbo.PROJECTS P
INNER JOIN (
SELECT CURRSTTS.PROJ_NUM, CURRSTTS.STTS, CURRSTTS.STTS_DT, LASTACTV.STTS_DT AS LASTACTV_STTS_DT FROM dbo.PROJECTS CURRSTTS
INNER JOIN
(SELECT PROJ_NUM, STTS, STTS_DT FROM dbo.PROJECTS LASTACTV
WHERE STTS_DT = (SELECT MAX(STTS_DT) FROM dbo.PROJECTS ALLP WHERE ALLP.PROJ_NUM = LASTACTV.PROJ_NUM AND ALLP.STTS = 'A'))
LASTACTV ON CURRSTTS.PROJ_NUM = LASTACTV.PROJ_NUM
WHERE CURRSTTS.STTS_DT = (SELECT MAX(STTS_DT) FROM dbo.PROJECTS ALLP WHERE ALLP.PROJ_NUM = CURRSTTS.PROJ_NUM)
AND CURRSTTS.STTS = 'I'
) SUB ON SUB.PROJ_NUM = P.PROJ_NUM
WHERE P.STTS_DT = (SELECT MIN(STTS_DT) FROM dbo.PROJECTS ALLP WHERE ALLP.PROJ_NUM = P.PROJ_NUM AND ALLP.STTS_DT > SUB.LASTACTV_STTS_DT)
The result I get back matches your desired result:
"Greatest n-per group" is the thing to look up when you run accross a problem like this again. Here is a query that will get what you need in postgresSQL.
I realized I changed your column to a boolean, but you will get the gist.
with most_recent_projects as (
select project_number, max(date) date from testtable group by project_number
),
currently_inactive_projects as (
select t.project_number, t.date from testtable t join most_recent_projects mrp on t.project_number = mrp.project_number and t.date = mrp.date where not t.active
),
last_active_date as (
select project_number, date from (
select t.project_number, rank() OVER (
PARTITION BY t.project_number
ORDER BY t.date DESC), t.date
from currently_inactive_projects cip join testtable t on t.project_number = cip.project_number where t.active) t1 where rank = 1
)
-- oldest inactive -- ie, result
select t.project_number, t.active, min(t.date) from last_active_date lad join testtable t on lad.project_number = t.project_number and t.date > lad.date group by t.project_number, t.active;
This is a variation of "gaps and islands" problem.
The query may be like this
SELECT
num,
status,
MIN(date) AS date
FROM (
SELECT
*,
MAX(group_id) OVER (PARTITION BY num) AS max_group_id
FROM (
SELECT
*,
SUM(CASE WHEN status = prev_status THEN 0 ELSE 1 END) OVER (PARTITION BY num ORDER BY date) AS group_id
FROM (
SELECT
*,
LAG(status) OVER (PARTITION BY num ORDER BY date) AS prev_status
FROM projects
) groups
) islands
) q
WHERE status = 'I' AND group_id = max_group_id
GROUP BY num, status
ORDER BY num
Another approach using CTEs
WITH last_status AS (
SELECT
*
FROM (
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY num ORDER BY date DESC) AS rn
FROM projects
) rns
WHERE rn = 1
),
last_active AS (
SELECT
num,
MAX(date) AS date
FROM projects
WHERE status = 'A'
GROUP BY num
),
last_inactive AS (
SELECT
p.num,
MIN(p.date) AS date
FROM projects p
WHERE p.status = 'I'
AND (
EXISTS (
SELECT 1 FROM last_active la
WHERE la.num = p.num AND la.date < p.date
)
OR NOT EXISTS (
SELECT 1 FROM last_active la
WHERE la.num = p.num
)
)
GROUP BY num
)
SELECT
ls.num,
ls.status,
li.date
FROM last_status ls
JOIN last_inactive li ON li.num = ls.num
WHERE ls.status = 'I'
You can check a working demo with both queries here

How to display null values in IN operator for SQL with two conditions in where

I have this query
select *
from dbo.EventLogs
where EntityID = 60181615
and EventTypeID in (1, 2, 3, 4, 5)
and NewValue = 'Received'
If 2 and 4 does not exist with NewValue 'Received' it shows this
current results
What I want
Ideally you should maintain somewhere a table containing all possible EventTypeID values. Sans that, we can use a CTE in place along with a left join:
WITH EventTypes AS (
SELECT 1 AS ID UNION ALL
SELECT 2 UNION ALL
SELECT 3 UNION ALL
SELECT 4 UNION ALL
SELECT 5
)
SELECT et.ID AS EventTypeId, el.*
FROM EventTypes et
LEFT JOIN dbo.EventLogs el
ON el.EntityID = 60181615 AND
el.NewValue = 'Received'
WHERE
et.ID IN (1,2,3,4,5);

Group by absorb NULL unless it's the only value

I'm trying to group by a primary column and a secondary column. I want to ignore NULL in the secondary column unless it's the only value.
CREATE TABLE #tempx1 ( Id INT, [Foo] VARCHAR(10), OtherKeyId INT );
INSERT INTO #tempx1 ([Id],[Foo],[OtherKeyId]) VALUES
(1, 'A', NULL),
(2, 'B', NULL),
(3, 'B', 1),
(4, 'C', NULL),
(5, 'C', 1),
(6, 'C', 2);
I'm trying to get output like
Foo OtherKeyId
A NULL
B 1
C 1
C 2
This question is similar, but takes the MAX of the column I want, so it ignores other non-NULL values and won't work.
I tried to work out something based on this question, but I don't quite understand what that query does and can't get my output to work
-- Doesn't include Foo='A', creates duplicates for 'B' and 'C'
WITH cte AS (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY [Foo] ORDER BY [OtherKeyId]) rn1
FROM #tempx1
)
SELECT c1.[Foo], c1.[OtherKeyId], c1.rn1
FROM cte c1
INNER JOIN cte c2 ON c2.[OtherKeyId] = c1.[OtherKeyId] AND c2.rn1 = c1.rn1
This is for a modern SQL Server: Microsoft SQL Server 2019
You can use a GROUP BY expression with HAVING clause like below one
SELECT [Foo],[OtherKeyId]
FROM #tempx1 t
GROUP BY [Foo],[OtherKeyId]
HAVING SUM(CASE WHEN [OtherKeyId] IS NULL THEN 0 END) IS NULL
OR ( SELECT COUNT(*) FROM #tempx1 WHERE [Foo] = t.[Foo] ) = 1
Demo
Hmmm . . . I think you want filtering:
select t.*
from #tempx1 t
where t.otherkeyid is not null or
not exists (select 1
from #tempx1 t2
where t2.foo = t.foo and t2.otherkeyid is not null
);
My actual problem is a bit more complicated than presented here, I ended up using the idea from Barbaros Özhan solution to count the number of items. This ends up with two inner queries on the data set with two different GROUP BY. I'm able to get the results I need on my real dataset using a query like the following:
SELECT
a.[Foo],
b.[OtherKeyId]
FROM (
SELECT
[Foo],
COUNT([OtherKeyId]) [C]
FROM #tempx1 t
GROUP BY [Foo]
) a
JOIN (
SELECT
[Foo],
[OtherKeyId]
FROM #tempx1 t
GROUP BY [Foo], [OtherKeyId]
) b ON b.[Foo] = a.[Foo]
WHERE
(b.[OtherKeyId] IS NULL AND a.[C] = 0)
OR (b.[OtherKeyId] IS NOT NULL AND a.[C] > 0)

ORACLE sum inside a case statement

Hi I need the result of this. so if a entityID matches to a value I need the sum of certain column.I am getting an expression missing error. Can someone point me to where the error is?
Thanks.
SELECT
p.jobTitle,
p.department,
p.person,
ufr.meets,
ufr.exceeds,
CASE
WHEN ufr.entityid = 'AHT' THEN (AD.acdcalls + AD.daacdcalls)
WHEN ufr.entityid = 'ACW' THEN (AD.acdcalls + AD.daacdcalls)
WHEN ufr.entityid = 'Adherence' THEN SUM(AA.totalSched)
WHEN ufr.entityid = 'Conformance' THEN SUM(AS.minutes)
ELSE null
END as weight,
(weight * meets) AS weightedMeets,
(weight * exceeds) AS weightedExceeds
FROM M_PERSON p
JOIN A_TMP5408_UNFLTRDRESULTSAG ufr
ON ufr.department = p.department AND ufr.jobTitle = p.jobTitle
LEFT JOIN M_AvayaDAgentChunk AD
ON AD.person = p.person and ufr.split = AD.split
LEFT JOIN M_AgentAdherenceChunk AA
ON AA.person = p.person
LEFT JOIN M_AgentScheduleChunk AS
ON AS.person = p.person
GROUP BY
p.person,
p.department,
p.jobTitle,
ufr.meets,
ufr.exceeds,
weight,
weightedMeets,
weightedExceeds
As well as the issues mentioned by #GordonLinoff (that AS is a keyword) and #DCookie (you need entityid in the group-by):
you also need acdcalls and daacdcalls in the group-by (unless you can aggregate those);
you can't refer to a column alias in the same level of query, so (weight * meets) AS weightedMeets isn't allowed - you've just define what weight is, in the same select list. You need to use an inline view, or a CTE, if you don't want to repeat the case logic.
I think this does what you want:
SELECT
jobTitle,
department,
person,
meets,
exceeds,
weight,
(weight * meets) AS weightedMeets,
(weight * exceeds) AS weightedExceeds
FROM
(
SELECT
MP.jobTitle,
MP.department,
MP.person,
ufr.meets,
ufr.exceeds,
CASE
WHEN ufr.entityid = 'AHT' THEN (MADAC.acdcalls + MADAC.daacdcalls)
WHEN ufr.entityid = 'ACW' THEN (MADAC.acdcalls + MADAC.daacdcalls)
WHEN ufr.entityid = 'Adherence' THEN SUM(MAAC.totalSched)
WHEN ufr.entityid = 'Conformance' THEN SUM(MASC.minutes)
ELSE null
END as weight
FROM M_PERSON MP
JOIN A_TMP5408_UNFLTRDRESULTSAG ufr
ON ufr.department = MP.department AND ufr.jobTitle = MP.jobTitle
LEFT JOIN M_AvayaDAgentChunk MADAC
ON MADAC.person = MP.person and ufr.split = MADAC.split
LEFT JOIN M_AgentAdherenceChunk MAAC
ON MAAC.person = MP.person
LEFT JOIN M_AgentScheduleChunk MASC
ON MASC.person = MP.person
GROUP BY
MP.person,
MP.department,
MP.jobTitle,
ufr.meets,
ufr.exceeds,
ufr.entityid,
MADAC.acdcalls,
MADAC.daacdcalls
);
Your fist two case branches could be combined since the calculation is the same, but will work either way.
In addition to the alias issue identified by Gordon, I think you'll find you need to use an aggregate function in all the THEN clauses of your CASE statement, and that you need to GROUP BY ufr.entityid as well. Otherwise you'll start getting ora-00979 errors (not a GROUP BY expression). If you don't want the aggregate function in all clauses, then you'll have to group by the expressions you're summing as well.
Small illustration:
CREATE TABLE tt (ID varchar2(32), sub_id varchar2(32), x NUMBER, y NUMBER);
INSERT INTO tt VALUES ('ID1', 'A', 1, 6);
INSERT INTO tt VALUES ('ID1', 'B', 1, 7);
INSERT INTO tt VALUES ('ID2', 'A', 2, 6);
INSERT INTO tt VALUES ('ID2', 'B', 2, 7);
INSERT INTO tt VALUES ('ID3', 'A', 3, 6);
INSERT INTO tt VALUES ('ID3', 'B', 3, 7);
INSERT INTO tt VALUES ('ID3', 'C', 3, 8);
SELECT ID, CASE WHEN sub_id = 'A' THEN SUM(y)
WHEN sub_id = 'B' THEN SUM(x)
ELSE (x + y) END tst
FROM tt
GROUP BY ID
ORA-00979: not a GROUP BY expression (points at sub_id in WHEN)
SELECT ID, CASE WHEN sub_id = 'A' THEN SUM(y)
WHEN sub_id = 'B' THEN SUM(x)
ELSE (x + y) END tst
FROM tt
GROUP BY ID, sub_id
ORA-00979: not a GROUP BY expression (points at x in ELSE)
SQL> SELECT ID, CASE WHEN sub_id = 'A' THEN SUM(y)
2 WHEN sub_id = 'B' THEN SUM(x)
3 ELSE SUM(x + y) END tst
4 FROM tt
5 GROUP BY ID, sub_id;
ID TST
-------------------------------- ----------
ID1 6
ID3 6
ID3 3
ID1 1
ID2 6
ID2 2
ID3 11

i want to display data according to type in same query

i have following table "vehicle_data" :
ID ALERT_TYPE VALUE
58 2 1
58 1 1
104 1 1
104 2 1
Here alert_type = 2 is for GPS value and alert_type=1 is for engine_value .
so if alert_type=2 and its value is =1 then it means its value is correct.
when alert_type=2 and its value is =0 then it means its value is wrong.
same for alert_type=1
so now here i want the following output:
ID gps engine_value
58 1 1
104 1 1
how can i perform this query??
You can do it like this.
SELECT ID
,CASE WHEN [ALERT_TYPE]=2 and [value ]=1 THEN 1 ELSE 0 END as gps
,CASE WHEN [ALERT_TYPE]=1 and [value ]=1 THEN 1 ELSE 0 END as engine
FROM vehicle_data
SELECT ID, alert_type=2 AS gps, alert_type=1 AS [engine] FROM vehicle_data WHERE value=1;
EDITED to account for your explanation of VALUE.
Schema
CREATE TABLE table3 (id int, ALERT_TYPE int)
INSERT table3 VALUES (58, 1), (58, 2), (104, 1), (104, 2)
Query
SELECT *
FROM (
SELECT ID
,ROW_NUMBER() OVER (
PARTITION BY id ORDER BY id
) AS row_num
,gps = CASE
WHEN ALERT_TYPE = 2
THEN 1
ELSE 0
END
,engine = CASE
WHEN ALERT_TYPE = 1
THEN 1
ELSE 0
END
FROM table3
) a
WHERE a.row_num = 1
Output
ID gps engine
58 1 0
104 0 1
One possible way using subqueries :
select
Ids.ID
, gps.VALUE 'gps'
, engine_value.VALUE 'engine_value'
from (select distinct ID from vehicle_data) Ids
left join
(select ID, VALUE from vehicle_data where ALERT_TYPE = 2) gps
on gps.ID = Ids.ID
left join
(select ID, VALUE from vehicle_data where ALERT_TYPE = 1) engine_value
on engine_value.ID = Ids.ID
[SQL Fiddle demo]
I hope this should work for you,
Select ID,sum(gps) as gps ,sum(engine) as engine from
(SELECT ID
,CASE WHEN [ALERT_TYPE]=2 and [value ]=1 THEN 1 ELSE 0 END as gps
,CASE WHEN [ALERT_TYPE]=1 and [value ]=1 THEN 1 ELSE 0 END as engine
FROM vehicle_data
)a
group by id
select x.id,x.alert_type as GPS,x.value as engine_value from (
select ID,alert_type,value,ROW_NUMBER() over (partition by id order by alert_type ) as Rnk from mytable
)x
where Rnk=1
Please check this query in SQL :
create table mytable (id int, alert_type int, value int);
insert into mytable (id, alert_type, value)
values (58, 2, 1),
(58, 1, 1),
(104, 1, 1),
(104, 2, 1);
SELECT distinct ID
,(select count (id) from mytable mt where mt.id=mytable.id and mt.[ALERT_TYPE]=2 and mt.[value ]=1) as gps
,(select count (id) from mytable mt where mt.id=mytable.id and mt.[ALERT_TYPE]=1 and mt.[value ]=1) as engine
FROM mytable
BASED ON YOUR QUESTION I BELIEVE YOU WANT THE DATA IN COLUMN AND TO SUIT YOUR REQUIREMENT I HAVE MADE A SQL FIDDLE WORKING - CODE IS ALSO MENTIONED BELOW -
HERE YOU GO WITH THE WORKING FIDDLE -
WORKING DEMO
SQL CODE FOR REFERNECE -
CREATE TABLE ALERTS (ID INT, ALERT_TYPE INT, VALUE INT)
INSERT INTO ALERTS VALUES (58,2,1)
INSERT INTO ALERTS VALUES (58,1,0)
INSERT INTO ALERTS VALUES (104,1,1)
INSERT INTO ALERTS VALUES (104,2,0)
CREATE TABLE ALERTSVALUE (ID INT, gps INT,engine INT)
INSERT INTO ALERTSVALUE VALUES (58,1,0)
INSERT INTO ALERTSVALUE VALUES (104,0,1)
SELECT A.ID,
CASE A.ALERT_TYPE WHEN 2 THEN 1 ELSE 0 END AS GPS,
CASE A.ALERT_TYPE WHEN 1 THEN 1 ELSE 0 END AS ENGINE_VALUE,
A.VALUE FROM ALERTS A WHERE A.VALUE = 1
EDIT BASED ON COMMENT - TO MERGE THE ROWS FOR BOTH GPS AND ENGINE_VALUE:
SELECT X.ID,X.ALERT_TYPE as GPS,X.VALUE as ENGINE_VALUE
FROM (
SELECT ID,ALERT_TYPE ,VALUE ,ROW_NUMBER() OVER (PARTITION BY ID ORDER BY alert_type ) AS [Rank] FROM ALERTS
)X
WHERE [Rank]=1
SQL FIDDLE DEMO