SQL Server : update boolean column based on conditions from 2 other columns - sql

I need to set all rows to 1 (true) in column PrimaryInvoiceFile when my InvoiceFileId only has one InvoiceId.
However, if InvoiceId has multiple has multiple InvoiceFileId, I need to set all of the PrimaryInvoiceFile rows to 0 (false) except for the most recent InvoiceFileId added based on the date added.
For example it should look like this:
|CreatedDate|InvoiceId|InvoiceFileId|PrimaryInvoiceFile|
+-----------+---------+-------------+------------------+
|2019-01-16 | 1 | 1 | 1 |
|2019-01-17 | 2 | 2 | 1 |
|2019-01-18 | 3 | 3 | 0 |
|2019-01-19 | 3 | 4 | 0 |
|2019-01-20 | 3 | 5 | 1 |
|2019-01-21 | 4 | 6 | 1 |
I just added the PrimaryInvoiceFile column migration and set the default value to 0.
Any help with this would be greatly appreciated! I have been racking my head with this trying to get my update statements to perform this update.

You can make use of rownumber while doing your update to achieve your desired results. Also, order by descending so that you get the most recent date.
Lets create a table keeping PrimaryInvoiceFile as null and then updating later.
select '2019-01-16' as CreatedDate, 1 as invoiceID, 1 as Invoicefield, null
as PrimaryInvoiceFile
into #temp
union all
select '2019-01-17' as CreatedDate, 2 as invoiceID, 2 as Invoicefield, null
as Primaryinvoicefile union all
select '2019-01-18' as CreatedDate, 3 as invoiceID, 3 as Invoicefield, null
as Primaryinvoicefile union all
select '2019-01-19' as CreatedDate, 3 as invoiceID, 4 as Invoicefield, null
as Primaryinvoicefile union all
select '2019-01-20' as CreatedDate, 3 as invoiceID, 5 as Invoicefield, null
as Primaryinvoicefile union all
select '2019-01-21' as CreatedDate, 4 as invoiceID, 6 as Invoicefield, null
as Primaryinvoicefile
update t
set Primaryinvoicefile = tst.Rownum
from #temp t
join
(Select invoiceID, Invoicefield,CreatedDate,
case when ROW_NUMBER() over (partition by invoiceID order by createddate desc) = 1
then 1 else 0 end as Rownum from #temp) tst
on tst.CreatedDate = t.CreatedDate
and tst.invoiceID = t.invoiceID
and tst.Invoicefield = t.Invoicefield
Case statement would make sure that you are value as 1 for only the rows where you have 1 row for invoice ID or for the most recent data.
select * from #temp
Output:
CreatedDate invoiceID Invoicefield PrimaryInvoiceFile
2019-01-16 1 1 1
2019-01-17 2 2 1
2019-01-18 3 3 0
2019-01-19 3 4 0
2019-01-20 3 5 1
2019-01-21 4 6 1

Please try this:
;WITH Data AS (
SELECT t.CreatedDate,t.InvoiceId,t.InvoiceFieldId,t.PrimaryInvoiceFile
,COUNT(*)OVER(PARTITION BY t.InvoiceId) AS [cnt]
FROM [YourTableName] t
)
UPDATE d SET d.PrimaryInvoiceFile = CASE WHEN d.cnt = 1 THEN 1 ELSE 0 END
FROM Data d
;
Query to play around:
DROP TABLE IF EXISTS #YourTableName;
CREATE TABLE #YourTableName(CreatedDate DATETIME2,InvoiceId INT, InvoiceFieldId INT,PrimaryInvoiceFile BIT);
INSERT INTO #YourTableName(CreatedDate,InvoiceId,InvoiceFieldId)VALUES
('2019-01-16',1,1),('2019-01-17',2,2),('2019-01-18',3,3),('2019-01-19',3,4),('2019-01-20',3,5),('2019-01-21',4,6)
;WITH Data AS (
SELECT t.CreatedDate,t.InvoiceId,t.InvoiceFieldId,t.PrimaryInvoiceFile,COUNT(*)OVER(PARTITION BY t.InvoiceId) AS [cnt]
FROM #YourTableName t
)
UPDATE d SET d.PrimaryInvoiceFile = CASE WHEN d.cnt = 1 THEN 1 ELSE 0 END
FROM Data d
;
SELECT t.CreatedDate,t.InvoiceId,t.InvoiceFieldId,t.PrimaryInvoiceFile
FROM #YourTableName t
;
DROP TABLE IF EXISTS #YourTableName;

If the default PrimaryInvoiceFile is set to 0, you need to update the PrimaryInvoiceFile of the maximum CreatedDate grouped by InvoiceId.
UPDATE inv1
SET inv1.PrimaryInvoiceFile = 1
FROM invoices inv1 JOIN
(SELECT max(CreatedDate) as maxDate, InvoiceId
FROM invoices
GROUP BY InvoiceId ) as inv2
WHERE inv1.CreatedDate=inv2.maxDate and inv1.InvoiceId= inv2.InvoiceId

Related

Selecting only if on column value is distinct by other column

I have a linking table with rule_id to sub_rule_id as such:
rule_id | sub_rule_id
---------------------
1 | 1
2 | 1
2 | 2
2 | 3
3 | 3
3 | 4
I want to be able to get all the sub_rule_ids which are linked to only one rule_is by rule_id. So if my rule_id = 1 then I expected no rows. And if rule_id = 2 then I should get just one. Tried to play with distinct and having and would not trouble you with a bad query.. I am sure there is an easy elegant way to do it.
Thanks in advance
You can group by sub_rule_id amd set the condition in the having clause:
select sub_rule_id
from tablename
group by sub_rule_id
having count(distinct rule_id) = 1
Or with NOT EXISTS if you want full rows:
select t.* from tablename t
where not exists (
select 1 from tablename
where sub_rule_id = t.sub_rule_id
and rule_id <> t.rule_id
)

Change Position of Serial Number in SQL

I have a table named students. the structure is given below
______________________________
AdmissionNo RollNo Name
______________________________
1001 1 A
1003 2 B
1005 3 C
1006 4 D
1008 5 E
Now i want to change rollno 4 to 2 and increment forthcoming numbers
so the result should be like below
-------------------------------
AdmissionNo RollNo Name
-------------------------------
1001 1 A
1006 2 D
1003 3 B
1005 4 C
1008 5 E
--------------------------------
How to attain this using sql Query.
Note: Question Edited as per 'The Impaler' said.Admission number is not changing.only Roll no change. The values in table are examples actual values are hundreds of records.
With the omission of a dialect, I have answered this in T-SQL, as I wanted a stab at this.
This isn't pretty, however, I use a couple of updatable CTE's to find the offset for the specific rows, and then update the needed rows accordingly:
USE Sandbox;
GO
CREATE TABLE dbo.YourTable (AdmissionNo int, Rollno tinyint, [Name] char(1));
INSERT INTO dbo.YourTable
VALUES(1001,1,'A'),
(1003,2,'B'),
(1005,3,'C'),
(1006,4,'D'),
(1008,5,'E');
GO
DECLARE #NewPosition tinyint = 2,
#MovingName char(1) = 'D';
WITH Offsetting AS(
SELECT *,
COUNT(CASE Rollno WHEN #NewPosition THEN 1 END) OVER (ORDER BY RollNo ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING) -
COUNT(CASE [Name] WHEN #MovingName THEN 1 END) OVER (ORDER BY RollNo ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING) AS LagOffset
FROM dbo.YourTable),
NewNames AS(
SELECT *,
CASE RollNo WHEN #NewPosition THEN #MovingName
ELSE LAG([Name],LagOffset) OVER (ORDER BY RollNo)
END AS NewName
FROM Offsetting)
UPDATE NewNames
SET [Name] = NewName;
GO
SELECT *
FROM dbo.YourTable;
GO
DROP TABLE dbo.YourTable;
Not pretty but you could use some sub queries
DROP TABLE IF EXISTS T;
create table t
(AdmissionNo int, RollNo int, Name varchar(1));
insert into t values
(1001 , 1 , 'A'),
(1003 , 2 , 'B'),
(1005 , 3 , 'C'),
(1006 , 4 , 'D'),
(1008 , 5 , 'E');
select t.*,
case when rollno = 2 then (select name from t where rollno = 4)
when rollno > 2 and
rollno <> (select max(rollno) from t) then (select name from t t1 where t1.rollno < t.rollno order by t1.rollno desc limit 1)
else name
end
from t;
+-------------+--------+------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| 1001 | 1 | A | A |
| 1003 | 2 | B | D |
| 1005 | 3 | C | B |
| 1006 | 4 | D | C |
| 1008 | 5 | E | E |
+-------------+--------+------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
5 rows in set (0.001 sec)
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(admission_no INT NOT NULL PRIMARY KEY
,roll_no INT NOT NULL
,name CHAR(1) NOT NULL
);
INSERT INTO my_table VALUES
(1001,1,'A'),
(1003,2,'B'),
(1005,3,'C'),
(1006,4,'D'),
(1008,5,'E');
SELECT *
, CASE WHEN roll_no = 4 THEN 2
WHEN roll_no >= 2 AND roll_no < 4 THEN roll_no + 1
ELSE roll_no END x FROM my_table;
+--------------+---------+------+---+
| admission_no | roll_no | name | x |
+--------------+---------+------+---+
| 1001 | 1 | A | 1 |
| 1003 | 2 | B | 3 |
| 1005 | 3 | C | 4 |
| 1006 | 4 | D | 2 |
| 1008 | 5 | E | 5 |
+--------------+---------+------+---+
5 rows in set (0.00 sec)
...or, as an update...
UPDATE my_table x
JOIN
( SELECT *
, CASE WHEN roll_no = 4 THEN 2
WHEN roll_no >= 2 AND roll_no < 4 THEN roll_no + 1
ELSE roll_no END n
FROM my_table
) y
ON y.admission_no = x.admission_no
SET x.admission_no = y.n;
You'd probably want to extend this idea to deal with the fact that rows can be dragged up and down the list, so something like this...
SET #source = 1, #target = 5;
SELECT *
, CASE WHEN roll_no = GREATEST(#source,#target) THEN LEAST(#source,#target)
WHEN roll_no >= LEAST(#source,#target) AND roll_no < GREATEST(#source,#target) THEN roll_no + 1
ELSE roll_no END x
FROM my_table;
Try this below query
; with cte as (select a.AdmissionNo, a.RollNo, b.Name from student a
join student b on a.RollNo=b.RollNo+1
where a.RollNo between 3 and 4
union all
select a.AdmissionNo, a.RollNo, b.Name from student a
left join student b on a.RollNo+2=b.RollNo
where a.RollNo=2)
update a set a.Name = b.name
from student a
join cte b on a.rollno=b.rollno

MSSQL: How to increment a int-column grouped by another column?

Given the following table:
UserId | Idx
1 | 0
1 | 1
1 | 3
1 | 5
2 | 1
2 | 2
2 | 3
2 | 5
And I want to update the Idx column that it is correctly incremented grouped by UserId column:
UserId | Idx
1 | 0
1 | 1
1 | 2
1 | 3
2 | 0
2 | 1
2 | 2
2 | 3
I know its possible with T-SQL (with Cursor), but is it also possible with a single statement?
Thank you
You can use correlated subquery :
update t
set idx = coalesce((select count(*)
from table as t1
where t1.userid = t.userid and t1.idx < t.idx
), 0
);
Use ROW_NUMBER() with Partition
update tablex set Idx=A.Idx
from tablex T
inner join
(
select UserID ,ID,ROW_NUMBER() OVER (PARTITION BY UserID ORDER By UserID)-1 Idx
from tablex
) A on T.ID=A.ID
Use an updatable CTE:
with toupdate as (
select t.*,
row_number() over (partition by user_id order by idx) - 1 as new_idx
from t
)
update toupdate
set idx = new_idx
where new_idx <> new_idx;
This should be the fastest method for solving this problem.

SQL Server - Select Distinct of two columns, where the distinct column selected has a maximum value based on two other columns

I have 2 tables - TC and T, with columns specified below. TC maps to T on column T_ID.
TC
----
T_ID,
TC_ID
T
-----
T_ID,
V_ID,
Datetime,
Count
My current result set is:
V_ID TC_ID Datetime Count
----|-----|------------|--------|
2 | 1 | 2013-09-26 | 450600 |
2 | 1 | 2013-12-09 | 14700 |
2 | 1 | 2014-01-22 | 15000 |
2 | 1 | 2014-01-22 | 15000 |
2 | 1 | 2014-01-22 | 7500 |
4 | 1 | 2014-01-22 | 1000 |
4 | 1 | 2013-12-05 | 0 |
4 | 2 | 2013-12-05 | 0 |
Using the following query:
select T.V_ID,
TC.TC_ID,
T.Datetime,
T.Count
from T
inner join TC
on TC.T_ID = T.T_ID
Result set I want:
V_ID TC_ID Datetime Count
----|-----|------------|--------|
2 | 1 | 2014-01-22 | 15000 |
4 | 1 | 2014-01-22 | 1000 |
4 | 2 | 2013-12-05 | 0 |
I want to write a query to select each distinct V_ID + TC_ID combination, but only with the maximum datetime, and for that datetime the maximum count. E.g. for the distinct combination of V_ID = 2 and TC_ID = 1, '2014-01-22' is the maximum datetime, and for that datetime, 15000 is the maximum count, so select this record for the new table. Any ideas? I don't know if this is too ambitious for a query and I should just handle the result set in code instead.
One method uses row_number():
select v_id, tc_id, datetime, count
from (select T.V_ID, TC.TC_ID, T.Datetime, T.Count,
row_number() over (partition by t.V_ID, tc.tc_id
order by datetime desc, count desc
) as seqnum
from t join
tc
on tc.t_id = t._id
) tt
where seqnum = 1;
The only issue is that some rows have the same maximum datetime value. SQL tables represent unordered sets, so there is no way to determine which is really the maximum -- unless the datetime really has a time component or another column specifies the ordering within a day.
It is possible to solve this using CTEs. First, extracting the data from your query. Second, get the maxdates. Third, get the highest count for each maxdate.:
;WITH Dataset AS
(
select T.V_ID,
TC.TC_ID,
T.[Datetime],
T.[Count]
from T
inner join TC
on TC.T_ID = T._ID
),
MaxDates AS
(
SELECT V_ID, TC_ID, MAX(t.[Datetime]) AS MaxDate
FROM Dataset t
GROUP BY t.V_ID, t.TC_ID
)
SELECT t.V_ID, t.TC_ID, t.[Datetime], MAX(t.[Count]) AS [Count]
FROM Dataset t
INNER JOIN MaxDates m ON t.V_ID = m.V_ID AND t.TC_ID = m.TC_ID AND m.MaxDate = t.[Datetime]
GROUP BY t.V_ID, t.TC_ID, t.[Datetime]
Just to keep it simple:
You need to group by T.V_ID,TC.TC_ID,
with selecting the max of date and then to get the maximum count, you must use a sub query as follows,
select T.V_ID,
TC.TC_ID,
max(T.Datetime) as Date_Time,
(select max(Count) from T as tb where v_ID = T.v_ID and DateTime = max(T.DateTime)) as Count
from T
inner join TC
on TC.T_ID = T._ID
group by T.V_ID,TC.TC_ID,

SQL Query using Partition By

I have following table name JobTitle
JobID LanaguageID
-----------------
1 1
1 2
1 3
2 1
2 2
3 4
4 5
5 2
I am selecting all records from table except duplicate JobID's for which count > 1. I am selecting only one record/first row from the duplicate JobID's.
Now I am passing LanguageID as paramter to stored procedure and I want to select duplicate JobID for that languageID along with the other records Also.
If I have passed languageID as 1 then output should come as follows
JobID LanaguageID
-----------------
1 1
2 1
3 4
4 5
5 2
I have tried using following query.
with CTE_RN as
(
SELECT ROW_NUMBER() OVER(PARTITION BY JobTitle.JobID ORDER BY JobTitle.JobTitle) AS RN
FROM JobTitle
INNER JOIN JobTitle_Lang
ON JobTitle.JobTitleID = JobTitle_Lang.JobTitleID
)
But I am unable to use WHERE clause in the above query.
Is any different approch should be followed. Or else how can i modify the query to get the desired output
with CTE_RN as
(
SELECT
JobID, LanaguageID,
ROW_NUMBER() OVER(PARTITION BY JobTitle.JobID ORDER BY JobTitle.JobTitle) AS RN
FROM JobTitle
INNER JOIN JobTitle_Lang ON JobTitle.JobTitleID = JobTitle_Lang.JobTitleID
)
select
from CTE_RN
where RN = 1 or LanguageID = #LanguageID
update
simplified a bit (join removed), but you'll get the idea:
declare #LanguageID int = 2
;with cte_rn as
(
select
JobID, LanguageID,
row_number() over(
partition by JobTitle.JobID
order by
case when LanguageID = #LanguageID then 0 else 1 end,
LanguageID
) as rn
from JobTitle
)
select *
from cte_rn
where rn = 1
sql fiddle demo
SELECT b.[JobID], b.[LanaguageID]
FROM
(SELECT a.[JobID], a.[LanaguageID],
ROW_NUMBER() OVER(PARTITION BY a.[JobID] ORDER BY a.[LanaguageID]) AS [row]
FROM [JobTitle] a) b
WHERE b.[row] = 1
Result
| JOBID | LANAGUAGEID |
--------|-------------|
| 1 | 1 |
| 2 | 1 |
| 3 | 4 |
| 4 | 5 |
| 5 | 2 |
See a demo