subtract data from two columns and result into one column.` - sql

I have data like below
id year marks id year marks
1 2017 80 1 2018 100
2 2017 60 2 2018 70
3 2017 500 3 2018 600
My result should be values as 20, 10, 100 in Difference column.
All this data should be in a single row.

Unless I'm missing something, you just want to take a difference of the two marks columns:
SELECT
t1.id, t1.year, t1.marks AS marks_2017, t2.id, t2.year, t2.marks AS marks_2018,
t2.marks - t1.marks AS diff
FROM yourTable t1
INNER JOIN yourTable t2
ON t1.id = t2.id AND t1.year = 2017 AND t2.year = 2018;

Somehow, I think you want:
select id, sum(case when year = 2018 then marks else - marks end) as diff
from t
where year in (2017, 2018)
group by id;

SELECT id, year, marks, id, year, new_marks, new_marks - marks AS diff
FROM yourTable where year = '2018'

select
t1.*, t2.year, t2.marks, t2.marks - t1.marks as diff
from
test t1
inner join
test t2
on t1.id = t2.id and t1.year = 2017 and t2.year = 2018
Demo

Related

SQL - How to add a column which shows the average of another column where record-specific criteria are met

I have this table:
PersonID
Class
Score
1
1
90
1
2
100
1
3
110
2
1
40
2
2
50
2
3
60
I need the new column to show the average of Score for each PersonID across all Scores for which class is less than or equal to that in the current record.
Here's what it should look like:
PersonID
Class
Score
Avg_Score_ClassLessThanOrEqual
1
1
90
90
1
2
100
95
1
3
110
100
2
1
40
40
2
2
50
45
2
3
60
50
Is this possible? I've tried partition by and sum(Case when), but I'm just starting out learning. I believe I need something like the pseudocode Partition by PersonID where PersonID = PersonID and Class <= Class
Try this:
SELECT t1.personid, t1.class, t1.score,
avg(t2.score) as average_score
FROM yourtable AS t1
LEFT JOIN yourtable AS t2
ON t1.personid = t2.personid
AND t2.class <= t1.class
GROUP BY t1.personid, t1.class, t1.score
EDIT:
I added the following part to match your request in the comments:
SELECT t1.personid, t1.class, t1.score,
avg(CASE WHEN t2.class <= t1.class THEN t2.score END) AS average_lower,
avg(CASE WHEN t2.class > t1.class THEN t2.score END) AS average_higher
FROM yourtable AS t1
LEFT JOIN yourtable AS t2
ON t1.personid = t2.personid
GROUP BY t1.personid, t1.class, t1.score
I think the simplest method is a correlated subquery:
select t.*,
(select avg(t2.score)
from t t2
where t2.personid = t.personid and t2.class <= t.class
) as avg_score_less_than
from t;
For performance, you want an index on (personid, class, score). Note: This uses class for the "less than or equal to part".
Hmmm . . . Actually, you can do this with window functions:
select t.*,
avg(score) over (partition by personid order by class) as avg_score_less_than
from t;

SQL: Same date but different values

I have a table that looks like this
Station
year
month
day
number
A1
1990
1
1
50
A1
1990
1
1
60
A1
1990
1
2
55
A1
1990
1
3
10
A1
1990
1
4
40
In example , the query result will like below table
for same station and date
Station
year
month
day
number
A1
1990
1
1
50
A1
1990
1
1
60
How to set a proper SQL for it?
If I understand correctly, you want rows where the first four columns are duplicated. A simple method uses count(*):
select t.*
from (select t.*,
count(*) over (partition by station, year, month, date) as cnt
from t
) t
where cnt > 1;
Assuming your table have a primary key column called id, we can also try using exists logic here:
SELECT t1.*
FROM yourTable t1
WHERE EXISTS (SELECT 1 FROM yourTable t2
WHERE t2.Station = t1.Station AND t2.year = t1.year AND
t2.month = t1.month AND t2.day = t1.day AND t2.id <> t1.id);
If you don't have such an id column, then we could also use aggregation here:
SELECT t1.*
FROM yourTable t1
INNER JOIN
(
SELECT Station, year, month, day
FROM yourTable
GROUP BY Station, year, month, day
HAVING COUNT(*) > 1
) t2
ON t2.Station = t1.Station AND t2.year = t1.year AND t2.month = t1.month AND
t2.day = t1.day;
You can use exists clause to find exact duplicate with different number as follows:
Select t.*
From your_table t
Where exists
(select 1 from your_table tt
Where tt.station = t.station
And tt.year = t.year
And tt.month = t.month
And tt.date = t.date
And tt.number <> t.number)

Update multiple records based on 1 record meeting a criteria

I am trying to Select all records from a dataset where 1 of the records meets a criteria. I have a table of data that contain the hours worked by a person by day. I need to select all the records for a person for that week, if one record within that week has the 'logged' flag set to 'N'. Here's a data example:
t_hours:
Name Week Weekday Hours Logged
===============================
Jim 1 Mon 8 Y
Jim 1 Wed 8 Y
Jim 1 Fri 8 Y
Jim 2 Mon 8 Y
Jim 2 Wed 8 Y
Bill 1 Mon 8 N
Bill 1 Tue 8 Y
Bill 1 Wed 8 Y
Bill 1 Thu 8 Y
Bill 2 Mon 8 Y
Bill 2 Tue 8 Y
I want to write a query that will update all the records for a persons workweek to Logged='N', if they have one day where Logged='N'. But I can't figure out how to even select the records. Here are the records that I want to update:
Name Week Weekday Hours Logged
===============================
Bill 1 Mon 8 N
Bill 1 Tue 8 Y
Bill 1 Wed 8 Y
Bill 1 Thu 8 Y
I have tried a normal select, but can't figure out how to have two correlated subqueries in the where clause:
SELECT * FROM t_hours
WHERE (Name = (SELECT t1.Name FROM t_hours t1
where t1.Name = t2.Name and t1.Week = t2.Week and
t1.Logged = 'N') and
Week = (SELECT t2.Week FROM t_hours t2
where t1.Name = t2.Name and t1.Week = t2.Week and
t2.Logged = 'N')
but this doesn't work, any help is greatly appreciated.
You can use an updatable CTE:
with toupdate as (
select t.*,
min(logged) over (partition by name, week) as min_logged
from t
)
update toupdate
set logged = min_logged
where min_logged = 'N' and min_logged <> logged;
The min(logged) expression will return 'N' if any of the logged values are 'N' (and the rest if any are 'Y') for the person/week.
Return data:
SELECT *
FROM t_hours t1
WHERE EXISTS (SELECT 1
FROM t_hours t2
WHERE t1.NAME = t2.NAME
AND t1.week = t2.week
AND t2.logged = 'N');
Update data:
UPDATE t_hours
SET logged = 'N'
WHERE EXISTS (SELECT 1
FROM t_hours t2
WHERE t1.NAME = t2.NAME
AND t1.week = t2.week
AND t2.logged = 'N');
If the data set is large enough, you could group by name, week, and logged type.
try like below-using exists
select t1.* from table_name t1
where exists( select 1 from table_name t2 where t1.week=t2.week
and t2.logged='N' and t1.name=t2.name
)
Is this what you want?
Update table set Logged='N' where
Exists (Select 1 from(Select
Name,Workweek group by Name,
Workweek having count(Logged='N')
>=1)t1 where Name=t1.name and
Workweek=t1.Workweek)

How to get the number of records from table 1 based on table 2 total count

Table1
Rowno name Date
------------------------------------
1 sathish Dec 21
2 kumar Dec 22
3 sathish Dec 21
4 sathish Dec 22
5 sathish Dec 21
5 sathish Dec 22
Table 2
Date NoofTran
-----------------------
Dec22 2
Dec21 1
I want to get the records from table1 based on the table2 nooftran values and randomly.
On Dec21st, there are 3 records and no of records to display is 2 and it should take it from randomly.
What SQL query should I use?
You can just join both the tables and order by the NoofTran
select Table1.*
from Table1 T1
inner join Table2 T2
on T1.[Date] = T2.[Date]
order by T2.NoofTran DESC
This will give you the result in NoofTran order
WITH CTE AS (
SELECT *, ROW_NUMBER()
OVER (
PARTITION BY T1.Date
order by NEWID()
) AS RN
FROM Table1 T1
)
SELECT CTE.RowNo,CTE.Date, CTE.Name
FROM CTE INNER JOIN Table2 T2
ON T2.date=cte.Date
WHERE RN <= T2.NoofTran

SQL Server - TSQL: cumulative count

So I have the following query:
Select Year(DATEADD(MONTH,3,date)) as Year, Month(DATEADD(MONTH,3,date)) as Month, location, COUNT (agent_login_id)
from Agents
where Location = 'syd'
Group by Location, Year(DATEADD(MONTH,3,date)), Month(DATEADD(MONTH,3,date))
it outputs:
Year Month location Count
2013 1 SYD 1
2013 3 SYD 11
2013 4 SYD 2
2013 5 SYD 2
2013 8 SYD 3
2013 9 SYD 1
2013 10 SYD 4
2013 11 SYD 7
2013 12 SYD 7
2014 1 SYD 3
2014 2 SYD 1
But I need to do a cumulative count so for example this is what I need it to look like
Year Month location Count Cumulative count
2013 1 SYD 1 1
2013 3 SYD 11 12
2013 4 SYD 2 14
How can I do this?
I'll use #t to represent your current query
For what I'll call 'full accumulation' (based on original question)
-- 2012
;WITH t AS
(
SELECT *, rn = ROW_NUMBER() OVER(PARTITION BY l ORDER BY y, m)
FROM #t
)
SELECT y, m, c, ISNULL(a + LAG(a,2) OVER(PARTITION BY l ORDER BY y, m), a) a
FROM (
SELECT t1.y, t1.l, t1.m, t1.c, SUM(t2.c) a
FROM t t1
JOIN t t2 ON t2.rn <= t1.rn AND t2.l = t1.l
GROUP BY t1.y, t1.l, t1.m, t1.c
) t
-- 2005+
;WITH t AS
(
SELECT *, rn = ROW_NUMBER() OVER(PARTITION BY l ORDER BY y, m)
FROM #t
)
SELECT t1.y, t1.l, t1.m, t1.c, ISNULL(SUM(t2.c)+SUM(t3.c), MIN(t1.c)) a
FROM t t1
JOIN t t2 ON t2.rn <= t1.rn AND t2.l = t1.l
LEFT JOIN t t3 ON t3.rn < t2.rn-1
GROUP BY t1.y, t1.l, t1.m, t1.c
Edit
For a normal running total (after question was corrected)
--2005+
;WITH t AS
(
SELECT *, rn = ROW_NUMBER() OVER(PARTITION BY l ORDER BY y, m)
FROM #t
)
SELECT t1.y, t1.l, t1.m, t1.c, SUM(t2.c) a
FROM t t1
JOIN t t2 ON t2.rn <= t1.rn AND t2.l = t1.l
GROUP BY t1.y, t1.l, t1.m, t1.c
Note: If in your current query you calculated the first day of the month instead of using YEAR() and MONTH() (leaving these calculations to the end if required) then you could avoid using ROW_NUMBER() which may see some performance improvements