Bitwise operation in SQL Server - sql-server

I have a column CategoryId that will store more than one value at the time, some of them have 1 (BA), 2 (SA) or 3 (both). I'm not sure if this is the right way.
For example the query down brings all records because 3 includes 1 and 2. If I want rows that have both categories then bitwise does not work. I believe I'm confusing terms.
Sample data and query:
CREATE TABLE #Payment (Id INT, Name NVARCHAR(50), CategoryId INT)
INSERT #Payment (Id, Name, CategoryId) VALUES(1, N'A', 1) --BA
INSERT #Payment (Id, Name, CategoryId) VALUES(1, N'B', 2) --SA
INSERT #Payment (Id, Name, CategoryId) VALUES(1, N'C', 3) --BA and SA
INSERT #Payment (Id, Name, CategoryId) VALUES(1, N'D', 2) --SA
DECLARE #Value INT = 3
SELECT *
FROM #Payment
WHERE (CategoryId & #Value) = CategoryId

There is a subtle correction needed in the WHERE clause. It should be:
WHERE (CategoryID & 3) = 3 -- bits 1 and 2 are set (matches 3, 7, 11, 15, ...)
For completeness here are the other variations:
WHERE (CategoryID & 3) <> 0 -- bits 1 or 2 are set (matches 1, 2, 3, 5, 6, 7, 9, 10, 11, ...)
WHERE (CategoryID & 3) = 0 -- bits 1 and 2 are not set (matches 0, 4, 8, 12, ...)
WHERE (CategoryID & 3) = CategoryID -- bits other than 1 and 2 are not set (matches 0, 1, 2, 3)

Related

How to create a columns based on other columns SQL Server 2012

I have 2 tables #Claims and #ClaimsActivity:
Query:
declare #Claims table (ClaimID int)
insert into #Claims
values (6070), (6080)
declare #ClaimsActivity table
(
Activityid int,
ClaimID int,
Activity int,
ActivityDate datetime,
ClaimStatus int
)
insert into #ClaimsActivity
values (1, 6070, 0, '2017-11-05 20:23:16.640', 0),
(3, 6070, 6, '2017-11-06 13:50:28.203', 0),
(4, 6070, 9, '2017-11-07 13:39:28.410', 0),
(5, 6070, 10, '2017-11-07 13:40:49.980', 0),
(7, 6070, 8, '2017-11-07 15:46:18.367', 1),
(8, 6070, 8, '2017-11-07 16:50:49.543', 1),
(9, 6070, 9, '2017-11-07 16:50:54.733', 0),
(10, 6070, 4, '2017-11-07 16:55:22.135', 0),
(11, 6070, 6, '2017-11-08 18:32:15.101', 0),
(12, 6080, 0, '2017-11-12 11:15:17.199', 0),
(13, 6080, 8, '2017-11-13 09:12:23.203', 1)
select *
from #Claims
select *
from #ClaimsActivity
order by ActivityDate
I need to add 2 columns based on data in #ClaimsActivity: IsReopened and DateReopened
The logic is:
If the last ClaimStatus (based on ActivityDate) = 1 then IsReopened = 0
But if the last ClaimStatus = 0 then it need to go and check whether one of the Activity is = 9 (Claim Reopened)
and if one of the Activity = 9 then IsReopened should = 1 and DateReopened should be the last date when it was reopened
I brought column StatusOfClaim, but I also need IsReopened and DateReopened
select
Claimid,
isnull((select top 1
case when al.ClaimStatus = 1
then 'Closed'
else 'Open'
end
from
#ClaimsActivity al
where
C.ClaimID = al.ClaimID
order by
al.ActivityDate desc), 'Open') as 'Status of Claim',
NULL as 'isReopen',
NULL as 'DateReopened'
from
#Claims c
Desired output should be like this:
There are many different ways you can accomplish this, but here is an example using CROSS APPLY and OUTER APPLY:
SELECT
ClaimID,
CASE WHEN tmp.IsOpen = 1 THEN 'Open' ELSE 'Closed' END AS 'Status of Claim',
CASE WHEN tmp.IsOpen = 1 AND lastReopen.Activityid IS NOT NULL THEN 1 ELSE 0 END AS 'isReopen',
lastReopen.ActivityDate AS 'DateReopened'
FROM #Claims c
CROSS APPLY (
SELECT ISNULL((
SELECT TOP 1 CASE WHEN al.ClaimStatus = 1 THEN 0 ELSE 1 END
FROM #ClaimsActivity al
WHERE c.ClaimID = al.ClaimID
ORDER BY al.ActivityDate DESC
), 1) AS IsOpen
) tmp
OUTER APPLY (
SELECT TOP 1
al.Activityid,
al.ActivityDate
FROM #ClaimsActivity al
WHERE c.ClaimID = al.ClaimID AND al.Activity = 9
ORDER BY al.ActivityDate DESC
) lastReopen
The CROSS APPLY is just used to produce a column that tells us whether a claim is open or closed, and we can reuse this throughout the rest of the query.
The OUTER APPLY is used to grab to the last "reopen" activity for each claim, of which you want the date.
I can't attest to the performance of this query, but this should at least give you the correct results.

Sort results based on order of entries in the WHERE's IN clause

I have a query
select *
from qtable
where qid in (15,555,2,3,4,5,36,27,18,9)
here qid is the primary key.
I get the following results sorted out by qid as it is the primary key but my requirement is to get the results as mentioned in the in (15, 555, 2, 3, 4, 5, 36, 27, 18, 9) statement.
Is there a way to unsort the records?
select qtable.*
from qtable join (values (15),(55),(2),(3),(4))a(id)
on a.Id = qtable.qID
SQL Server and other ANSI SQL compliant database require an ORDER BY clause to return data in a particular order. One approach is to use a row constructor that includes the desired sequence:
SELECT qtable.*
FROM qtable
JOIN (VALUES
(1, 15)
,(2, 555)
,(3, 2)
,(4, 3)
,(5, 4)
,(6, 5)
,(7, 36)
,(8, 27)
,(9, 18)
,(10, 9)
) AS list(seq, value) ON qtable.quid = list.qid
ORDER BY list.seq;
A similar technique can be used for variable list of values passed as a table-valued parameter or table variable:
DECLARE #list TABLE (
seq int
, qid int);
INSERT INTO #list VALUES
(1, 15)
,(2, 555)
,(3, 2)
,(4, 3)
,(5, 4)
,(6, 5)
,(7, 36)
,(8, 27)
,(9, 18)
,(10, 9);
SELECT qtable.*
FROM qtable
JOIN #list AS list ON qtable.quid = list.qid
ORDER BY list.seq;
You can do it like this:
select qid,case when qid = 15 then 1
when qid = 555 then 2
when qid = 2 then 3
when qid = 3 then 4
when qid = 4 then 5
when qid = 5 then 6
when qid = 36 then 7
when qid = 27 then 8
when qid = 18 then 9
when qid = 9 then 10
end as qidflag
from qtable where qid in (15,555,2,3,4,5,36,27,18,9)
order by qidflag
Here's an option where you can pass the desired ID/Sequence as a delimited string.
The Sequence will be maintained.
Example
Declare #List varchar(max)='15,555,2,3,4,5,36,27,18,9'
Select A.*
From qtable A
Join (
Select RetSeq = Row_Number() over (Order By (Select null))
,RetVal = v.value('(./text())[1]', 'int')
From (values (convert(xml,'<x>' + replace(#List,',','</x><x>')+'</x>'))) x(n)
Cross Apply n.nodes('x') node(v)
) B on A.quid = B.RetVal
Order By B.RetSeq
If it Helps - the sub-query Returns
RetSeq RetVal
1 15
2 555
3 2
4 3
5 4
6 5
7 36
8 27
9 18
10 9
I would use something similar to the following:
DECLARE #qidList TABLE (sort INT NOT NULL IDENTITY(1,1), qid INT);
INSERT #qidList VALUES
(15),
(555),
(2),
(3),
(4),
(5),
(36),
(27),
(18),
(9);
select qtable.*
from qtable
join #qidList list on list.qid=qtable.qid
order by list.sort;

Filter a table's records based on its other records

First, I apologize if the title won't make sense but below is the detailed scenario.
Say I have a document_revision table
id document_id phase_id user_id
1 1 3 1
2 1 2 1
3 1 1 1
4 2 3 2
5 2 2 2
where phase_id is: transcribe = 3; proof = 2; and submit = 1.
I would like to write a query where I can filter the revision records where I will disregard a proof phase if the same user did the transcribe and proof. So the output would be:
id document_id phase_id user_id
1 1 3 1
3 1 1 1
4 2 3 2
I've been struggling for hours figuring out a query for this but no luck so far.
Assuming you only want the phase 3 for any case where a user_id was involved in phase 2 and 3, then one way you could do this is with ROW_NUMBER(), e.g.:
DECLARE #T TABLE (ID INT IDENTITY(1, 1), Document_ID INT, Phase_ID INT, [User_ID] INT);
INSERT #T (Document_ID, Phase_ID, [User_ID]) VALUES
(1, 1, 1), (1, 2, 1), (1, 3, 1), (2, 3, 2), (2, 2, 2), (3, 1, 1), (3, 2, 1), (3, 3, 2);
SELECT ID, Document_ID, Phase_ID, [User_ID]
FROM
(
SELECT *, RN = ROW_NUMBER() OVER (PARTITION BY Document_ID, [User_ID], CASE WHEN Phase_ID IN (2, 3) THEN 2 ELSE Phase_ID END ORDER BY Phase_ID DESC)
FROM #T
) AS T
WHERE RN = 1;
DECLARE #document_revision TABLE (
id INT IDENTITY(1,1),
document_id INT,
phase_id INT,
user_id INT
);
INSERT INTO #document_revision
(document_id, phase_id, user_id)
VALUES
(1, 3, 1),
(1, 2, 1),
(1, 1, 1),
(2, 3, 2),
(2, 2, 2),
-- To test a scenario where there is a proof and a submit with no transcribe phases and same document
(3, 2, 3),
(3, 1, 3),
-- To test a scenario where there is a transcribe and a submit with no proof phases and same document
(4, 3, 4),
(4, 1, 4),
-- To test a scenario where there is a proof and a submit with no transcribe phase (for document_id 5) but different document and same user as above
(5, 2, 4);
SELECT dr.id
, dr.document_id
, dr.phase_id
, dr.user_id
FROM #document_revision AS dr
WHERE NOT EXISTS ( SELECT 1
FROM #document_revision AS temp
-- Same user
WHERE temp.user_id = dr.user_id
-- Same document
AND temp.document_id = dr.document_id
-- To check if there is already a transcribe phase_id with the same user_id and document_id
AND temp.phase_id = 3
-- -- To check if there is already a proof phase_id with the same user_id and document_id
AND dr.phase_id = 2 )
results:
id document_id phase_id user_id
1 1 3 1
3 1 1 1
4 2 3 2
6 3 2 3
7 3 1 3
8 4 3 4
9 4 1 4
10 5 2 4

How do you validate that range doesn't overlap in a list of data?

I have a list of data :
Id StartAge EndAge Amount
1 0 2 50
2 2 5 100
3 5 10 150
4 6 9 160
I have to set Amount for various age group.
The age group >0 and <=2 need to pay 50
The age group >2 and <=5 need to pay 100
The age group >5 and <=10 need to pay 150
But
The age group >6 and <=9 need to pay 160 is an invalid input because >6 and <=9 already exist on 150 amount range.
I have to validate such kind of invalid input before inserting my data as a bulk.Once 5-10 range gets inserted anything that is within this range should not be accepted by system. For example: In above list, user should be allowed to insert 10-15 age group but any of the following should be checked as invalid.
6-9
6-11
3-5
5-7
If Invalid Input exists on my list I don't need to insert the list.
You could try to insert your data to the temporary table first.
DECLARE #TempData TABLE
(
[Id] TINYINT
,[StartAge] TINYINT
,[EndAge] TINYINT
,[Amount] TINYINT
);
INSERT INTO #TempData ([Id], [StartAge], [EndAge], [Amount])
VALUES (1, 0, 2, 50)
,(2, 2, 5, 100)
,(3, 5, 10, 150)
,(4, 6, 9, 160);
Then, this data will be transferred to your target table using INSERT INTO... SELECT... statement.
INSERT INTO <your target table>
SELECT * FROM #TempData s
WHERE
NOT EXISTS (
SELECT 1
FROM #TempData t
WHERE
t.[Id] < s.[Id]
AND s.[StartAge] < t.[EndAge]
AND s.[EndAge] > t.[StartAge]
);
I've created a demo here
We can use recursive CTE to find how records are chained by end age and start age pairs:
DECLARE #DataSource TABLE
(
[Id] TINYINT
,[StartAge] TINYINT
,[EndAge] TINYINT
,[Amount] TINYINT
);
INSERT INTO #DataSource ([Id], [StartAge], [EndAge], [Amount])
VALUES (1, 0, 2, 50)
,(2, 2, 5, 100)
,(3, 5, 10, 150)
,(4, 6, 9, 160)
,(5, 6, 11, 160)
,(6, 3, 5, 160)
,(7, 5, 7, 160)
,(9, 10, 15, 20)
,(8, 7, 15, 20);
WITH PreDataSource AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY [StartAge] ORDER BY [id]) as [pos]
FROM #DataSource
), DataSource AS
(
SELECT [Id], [StartAge], [EndAge], [Amount], [pos]
FROM PreDataSource
WHERE [id] = 1
UNION ALL
SELECT R.[Id], R.[StartAge], R.[EndAge], R.[Amount], R.[pos]
FROM DataSource A
INNER JOIN PreDataSource R
ON A.[Id] < R.[Id]
AND A.[EndAge] = R.[StartAge]
AND R.[pos] =1
)
SELECT [Id], [StartAge], [EndAge], [Amount]
FROM DataSource;
This is giving us, the following output:
Note, that before this, we are using the following statement to prepare the data:
SELECT *, ROW_NUMBER() OVER (PARTITION BY [StartAge] ORDER BY [id]) as [pos]
FROM #DataSource;
The idea is to find records with same start age and to calculated which one is inserted first. Then, in the CTE we are getting only the first.
Assuming you are bulk inserting the mentioned data into a temp table(#tmp) or table variable (#tmp).
If you are working on sql server 2012 try the below.
select *
from(select *,lag(endage,1,0)over(order by endage) as [col1]
from #tmp)tmp
where startage>=col1 and endage>col1
The result of this query should be inserted into your main table.

SQL Server: How to write a query to select id having all values less than 0(zero)

Consider the following table,
Table 1:
id (int): 1 1 2 2 2
value (int): -1 0 -1 -3 -8
How to write a query to select the id from the table which has all the values of column value less than 0?
Try this one -
DECLARE #temp TABLE
(
id INT,
value INT
)
INSERT INTO #temp (id, value)
VALUES (1, -1), (1, 0), (2, -1), (2, -3), (2, -8)
SELECT id
FROM #temp
GROUP BY id
HAVING MAX(value) < 0
Output -
id
-----------
2

Resources