How to update a table based on criteria from that same table - sql-server

In the image above you'll note the first 6 rows that shows 2 records for each StudentID. I am needing to UPDATE, for each group of StudentID's, the EndDate column value of the 2nd record with a value of ONE DAY LESS THAN the StatusEffectiveDate of the first record for the same StudentID. I am working with SQL Server 2014.
So the first record should look like:
Here is my SQL code:
SELECT *
FROM #TEMP_99RecordsNeedingEndDateUpdated
ORDER BY StudentID asc, StatusEffectiveDate desc
Any help/direction would be appreciated.

Using what ZLK stated with a CTE to update the rows.
;WITH cte AS
(SELECT
*
, DATEADD(DAY, -1, LAG(statuseffectivedate) OVER (PARTITION BY StudentID ORDER BY statuseffectivedate DESC)) AS NewDate
FROM #TEMP_99RecordsNeedingEndDateUpdated)
UPDATE cte
SET cte.enddate = cte.NewDate
WHERE cte.NewDate IS NOT NULL

Late to the party, but here's one to try as well:
BEGIN TRANSACTION
UPDATE [YourTable] SET EndDate = DATEADD(DAY, -1, t.MaxDate)
FROM
(
SELECT StudentID, MAX(StatusEffectiveDate) as MaxDate
FROM [YourTable]
GROUP BY StudentID
) t
WHERE [YourTable].StudentID = t.StudentID AND [YourTable].StatusEffectiveDate <> t.MaxDate
-- COMMIT TRANSACTION
-- ROLLBACK TRANSACTION
As others have stated, this has very limited constraints on your data.
Essentially, I'm updating the EndDate to a new value, based on a sub query for the MAX() date from the same student ID. Finally, to avoid updating all student records, I ensure that the StatusEffectiveDate is not the later date.

This has very limited constraints, notably always needing two rows - one 'Free' and one 'Other' - but if that is the case, you should be able to do a UNION query.
SELECT StudentID, Location, Status, EconDisCode, StatusEffDate, EndDate
FROM #TEMP_99RecordsNeedingEndDateUpdated
WHERE Status = 'Free'
UNION
SELECT StudentID, Location, Status, EconDisCode, StatusEffDate, (SELECT StatusEffDate-1 FROM #TEMP_99RecordsNeedingEndDateUpdated t2 WHERE Status = 'Free' AND t1.StudentID = t2.StudentID) AS EndDate
FROM #TEMP_99RecordsNeedingEndDateUpdated t1
WHERE Status = 'Other'
I think a fiddle example with the data would be helpful.

with cte as
( select *
, row_number over (partition by studentID order by StatusEffectiveDate desc ) as rnD
, row_number over (partition by studentID order by StatusEffectiveDate asc ) as rnA
from table t
)
select cte.StudentID, cte.Location, cte.Status, cte.EconDisCode, cte.StatusEffectiveDate
, cte.EndDate
from cte
where rnD == 1
union
select cteA.StudentID, cteA.Location, cteA.Status, cteA.EconDisCode, cteA.StatusEffectiveDate
, dateadd(d, -1, cteD.StatusEffectiveDate)
from cte cteA
join cte cteD
on cteA.StudentID = cteD.StudentID
and cteA.rnD = 1
and cteD.rnA = 1
order by studentID, StatusEffectiveDate desc
update cteA
set cteA.EndDate = dateadd(d, -1, cteD.StatusEffectiveDate)
from cte cteA
join cte cteD
on cteA.StudentID = cteD.StudentID
and cteA.rnD = 1
and cteD.rnA = 1
order by studentID, StatusEffectiveDate desc
Include actual text (not image) so can cut and paste

Related

SQL - Return a value sum only once when grouped

I want to count the unique record of a string but grouping by dates, and if the string already appeared previously on a group it shouldn't be counted anymore.
I've tried using distinct and it does show the unique count of the record but the record is counted again on every month.
Actual and minified SQL query:
select
date,
count(distinct d.name) as count
from ...
group by date
Sample and desired output
Image
Grab unique names and tag them with the earliest date. At that point it's just a matter of regrouping the resulting rows by date. Each name will uniquely correspond to only one date as desired:
with data as (select name, min("date") as dt from T group by name)
select dt, count(name) as cnt from data group by dt;
If you still need to see the original dates even when no names are counted, then flag each row according to whether it should be counted and then count the flags per date:
with data as (
select *,
case when "date" = min("date") over (partition by name)
then 1 end as flag
from T
)
select "date", count(flag) as cnt
from data
group by "date";
So you want the name only count once:
SELECT COUNT(u.name) as name_count, u.[date]
FROM (
SELECT d.name,MIN(d.date) AS [date]
FROM yourTable d
GROUP BY d.name) u
GROUP BY u.[date];
You can add a ROW_NUMBER() that is Partitioned by name and ordered by date and add a WHERE clause that only returns the rows with Row_Number = 1.
You can check this following option-
SELECT A.Date,COUNT(B.[Name]) Count
FROM
(
SELECT DISTINCT Date FROM your_table
)A
LEFT JOIN
(
SELECT * FROM
(
SELECT *,ROW_NUMBER() OVER(PARTITION BY [Name] ORDER BY Date) RN
FROM your_table
)A WHERE RN = 1
)B ON A.Date = B.Date
GROUP BY A.Date
But the best option if I modify a bit the concept from Shawnt00 is as below-
SELECT A.Date,COUNT(B.[Name]) Count
FROM
(
SELECT DISTINCT Date FROM your_table
)A
LEFT JOIN
(
SELECT [Name],MIN(Date) Date FROM your_table GROUP BY [Name]
)B ON A.Date = B.Date
GROUP BY A.Date
Both case the output will be-
Date Count
20190101 2
20190201 0
20190301 1

How to select highest common value across groups

`Suppose I have a set of data with 2 fields - Type and Date. I am interested in finding (if exists) the the max common date across the various types. Is this easier to do in SQL or LINQ?
Given the data below the result should be 2018-02-01 as this is the max common date for all types. It there is no such date then no data is returned.
Type, Date
---------
1,2018-03-01
1,2018-02-01
1,2018-01-01
2,2018-02-01
2,2018-05-01
2,2018-01-01
3,2018-01-01
3,2018-03-01
3,2018-02-01
You could use:
SELECT TOP 1 [Date], COUNT(*) OVER(PARTITION BY Date) AS cnt
FROM tab
ORDER BY cnt DESC, [Date] DESC
DBFiddle Demo
This'll work if you have an unlimited or indeterminable number of Types:
CREATE TABLE #Sample ([Type] int, [DAte] date);
INSERT INTO #Sample
VALUES
(1,'20180301'),
(1,'20180201'),
(1,'20180101'),
(2,'20180201'),
(2,'20180501'),
(2,'20180101'),
(3,'20180101'),
(3,'20180301'),
(3,'20180201');
GO
WITH EntryCount AS(
SELECT [Type], [Date],
COUNT(*) OVER (PARTITION By [Date]) AS Entries
FROM #Sample)
SELECT MAX(Date)
FROM EntryCount EC
WHERE Ec.Entries = (SELECT COUNT(DISTINCT sq.[Type]) FROM #Sample sq);
GO
DROP TABLE #Sample;
Not sure how quick it'll be either though.
Example
Select Top 1 [Date]
from YourTable
Group By [Date]
Order By count([Type]) desc,[Date] desc
Returns
2018-02-01
This is not going to be very efficient not matter how you slice it because you have to compare across three groups. Assuming you have 3 types you could use a self join. Something like this.
select MAX(YourDate)
from YourTable yt
join YourTable yt2 on yt2.YourType = 2 and yt.YourDate = yt2.YourDate
join YourTable yt3 on yt3.YourType = 3 and yt.YourDate = yt3.YourDate
where yt.YourType = 1

DB query for x day that no data

I have table contain multiple records for the name and DT, I need a query to check the Name don't have any new record in past 2 days based on the DT, how to create
Name DT
ABC 2017-09-17 06:02:23.000
ACD 2017-09-15 06:02:23.000
I think You need something like this:
SELECT Name,dt
from
(SELECT Name,MAX(dt) dt
FROM your_table
GROUP BY NAME) a
where dt < GETDATE()-2
Without knowing your table schema or sample data this is a wild guess at a query, but what you want should be doable with the GETDATE() function. If you want to use UTC time you can also use the GETUTCDATE() function.
Edit: Updated to include the ROW_NUMBER() function.
Edit 2: Replaced the GETDATE() where clause with a CTE to exclude names that have dt within the last 2 days.
WITH CTE AS (
SELECT
name
FROM table
WHERE dt > GETDATE()-2
)
SELECT
name,
dt
FROM (
SELECT
name,
dt,
ROW_NUMBER() OVER (PARTITION BY name ORDER BY dt desc) AS rn
FROM table
LEFT JOIN CTE ON
table.name = CTE.name
WHERE CTE.name IS NULL
) tbl
WHERE rn = 1
BEGIN TRAN
CREATE TABLE #CM (Name NVARCHAR(06), DT DATETIME)
INSERT INTO #CM
SELECT 'ABC','2017-09-17 06:02:23.000' UNION ALL
SELECT 'ACD','2017-09-15 06:02:23.000'
SELECT * FROM #CM
WHERE CONVERT(NVARCHAR(105),dt) < CONVERT(NVARCHAR(105),GETDATE()-2)
ROLLBACK TRAN

Subtract top two rows from one column using one id

does anyone know how can I subtract top two rows from one column only using one id? Here's my sample query:
SELECT top 2 a.consumption,
coalesce(a.consumption -
(SELECT b.consumption
FROM tbl_t_billing b
WHERE b.id = a.id + 1), a.consumption) AS diff
FROM tbl_t_billing a
WHERE a.customerId = '5'
ORDER BY a.dateCreated DESC
I want to know how to get the difference between the top 2 rows using one id from the consumption column using the customerId #5. I've tried but I can't get the right query for that. Can somebody help me please? Thanks!
try this:
;with cte as
(
select consumption, customerId,
row_number() over (partiton by customerid order by datecreated desc) rn
from tbl_t_billing where customerId = '5'
)
select a.customerId, a.consumption,
coalesce((a.consumption - b.consumption), a.consumption) consumption_diff
from cte a left outer join cte b on a.rn + 1 = b.rn
where a.rn = 1
declare #tbl_t_billing table(consumption int, customerId int, datecreated datetime)
insert into #tbl_t_billing
values
(10,5,'20100101'),
(7,5,'20000101'),
(9,4,'20100101'),
(5,4,'20000101'),
(8,3,'20100101'),
(3,3,'20000101'),
(7,2,'20100101'),
(3,2,'20000101'),
(4,1,'20100101'),
(2,1,'20000101')
-- get the difference between the last two consumption values for each customerId
select
customerId,
sum(consumption) diff
from(
select
customerId,
consumption *
case row_number() over(partition by customerId order by datecreated desc)
when 1 then 1 when 2 then -1
end consumption
from #tbl_t_billing
) t
group by customerId

select top 1 with a group by

I have two columns:
namecode name
050125 chris
050125 tof
050125 tof
050130 chris
050131 tof
I want to group by namecode, and return only the name with the most number of occurrences. In this instance, the result would be
050125 tof
050130 chris
050131 tof
This is with SQL Server 2000
I usually use ROW_NUMBER() to achieve this. Not sure how it performs against various data sets, but we haven't had any performance issues as a result of using ROW_NUMBER.
The PARTITION BY clause specifies which value to "group" the row numbers by, and the ORDER BY clause specifies how the records within each "group" should be sorted. So partition the data set by NameCode, and get all records with a Row Number of 1 (that is, the first record in each partition, ordered by the ORDER BY clause).
SELECT
i.NameCode,
i.Name
FROM
(
SELECT
RowNumber = ROW_NUMBER() OVER (PARTITION BY t.NameCode ORDER BY t.Name),
t.NameCode,
t.Name
FROM
MyTable t
) i
WHERE
i.RowNumber = 1;
select distinct namecode
, (
select top 1 name from
(
select namecode, name, count(*)
from myTable i
where i.namecode = o.namecode
group by namecode, name
order by count(*) desc
) x
) as name
from myTable o
SELECT max_table.namecode, count_table2.name
FROM
(SELECT namecode, MAX(count_name) AS max_count
FROM
(SELECT namecode, name, COUNT(name) AS count_name
FROM mytable
GROUP BY namecode, name) AS count_table1
GROUP BY namecode) AS max_table
INNER JOIN
(SELECT namecode, COUNT(name) AS count_name, name
FROM mytable
GROUP BY namecode, name) count_table2
ON max_table.namecode = count_table2.namecode AND
count_table2.count_name = max_table.max_count
I did not try but this should work,
select top 1 t2.* from (
select namecode, count(*) count from temp
group by namecode) t1 join temp t2 on t1.namecode = t2.namecode
order by t1.count desc
Here are to examples that you could use but the temp table use is more efficient than the view, but was done on a small data sample. You would want to check your own statistics.
--Creating A View
GO
CREATE VIEW StateStoreSales AS
SELECT t.state,t.stor_id,t.stor_name,SUM(s.qty) 'TotalSales'
,ROW_NUMBER() OVER (PARTITION BY t.state ORDER BY SUM(s.qty) DESC) AS 'Rank'
FROM [dbo].[sales] s
JOIN [dbo].[stores] t ON (s.stor_id = t.stor_id)
GROUP BY t.state,t.stor_id,t.stor_name
GO
SELECT * FROM StateStoreSales
WHERE Rank <= 1
ORDER BY TotalSales Desc
DROP VIEW StateStoreSales
---Using a Temp Table
SELECT t.state,t.stor_id,t.stor_name,SUM(s.qty) 'TotalSales'
,ROW_NUMBER() OVER (PARTITION BY t.state ORDER BY SUM(s.qty) DESC) AS 'Rank' INTO #TEMP
FROM [dbo].[sales] s
JOIN [dbo].[stores] t ON (s.stor_id = t.stor_id)
GROUP BY t.state,t.stor_id,t.stor_name
SELECT * FROM #TEMP
WHERE Rank <= 1
ORDER BY TotalSales Desc
DROP TABLE #TEMP

Resources