Where if there's a match - c#

I have a table I'm selecting from, which logs "Signatures" from logins.
The table is like so:
| Int| VARCHAR | Guid | Bit | Int | NVARCHAR(MAX)
| ID | UserName | UserId | Signed | Rec | additional info etc...
| -- | -------- | ------ | ------ | --- | ---------------------
| 1 | Bob | 12 | 0 | 100 | sasdd
| 2 | Steve | 14 | 1 | 100 | asretg
| 3 | GROUP: 2 | 76 | 0 | 101 | This is a group of logins
| 4 | Bob | 12 | 1 | 101 | asdfasd
So column 5 is the target ID to which it's been signed.
To build a list of unsigned items for a specific user (Bob) was pretty straight forward:
SELECT Rec FROM tbl_Sigs WHERE Signed = 0 And UserId = '12'
Now, I've added login groups to this list, as in item 3 - In this example, the group has both logins in it and I'm able in code to pull from the login, which group it has access to, so the statement becomes:
SELECT Rec FROM tbl_Sigs WHERE (Signed = 0 And UserId = '12')
OR UserId IN (76,77,78)
This works, but in the example will select record 100 and 101.
What I would like is to only select record 100 for Bob, because I have a match for 101 because of "GROUP: 2" (Which will always be unsigned), however "Bob" has signed record 101 already, hence doesn't need it in his list.
What I can't figure out if how to put that condition into the where clause, I could do it in C# once I have the dataset but would prefer a pure SQL way if possible.

I think you just need a NOT EXISTS clause?
DECLARE #tbl_Sigs TABLE (
ID INT,
UserName VARCHAR(50),
UserId INT, --Not a GUID!
Signed BIT,
Rec INT,
AdditionalInfo VARCHAR(MAX));
INSERT INTO #tbl_Sigs VALUES (1, 'Bob', 12, 0, 100, 'sasdd');
INSERT INTO #tbl_Sigs VALUES (2, 'Steve', 14, 1, 100, 'asretg');
INSERT INTO #tbl_Sigs VALUES (3, 'GROUP: 2', 76, 0, 101, 'This is a group of logins');
INSERT INTO #tbl_Sigs VALUES (4, 'Bob', 12, 1, 101, 'asdfasd');
--So column 5 is the target ID to which it's been signed.
--To build a list of unsigned items for a specific user (Bob) was pretty straight forward:
SELECT Rec FROM #tbl_Sigs WHERE Signed = 0 And UserId = '12';
--Now, I've added login groups to this list, as in item 3 - In this example, the group has both logins in it and I'm able in code to pull from the login, which group it has access to, so the statement becomes:
SELECT Rec FROM #tbl_Sigs r1 WHERE (Signed = 0 And UserId = '12') OR UserId IN (76, 77, 78)
AND NOT EXISTS (SELECT * FROM #tbl_Sigs r2 WHERE r2.Rec = r1.Rec AND r2.UserId = '12' AND r2.Signed = 1);

Related

is there a way to use OFFSET and FETCH in MS access or anything similiar in a query, set query has to be used in ASP.NET [duplicate]

Is it possible to emulate the following MySQL query:
SELECT * FROM `tbl` ORDER BY `date` DESC LIMIT X, 10
(X is a parameter)
in MS Access?
While the Access/JET TOP keyword does not directly provide an OFFSET capability, we can use a clever combination of TOP, a subquery, and a "derived table" to obtain the same result.
Here is an example for getting the 10 rows starting from offset 20 in a Person table in ORDER BY Name and Id...
SELECT Person.*
FROM Person
WHERE Person.Id In
(
SELECT TOP 10 A.Id
FROM [
SELECT TOP 30 Person.Name, Person.Id
FROM Person
ORDER BY Person.Name, Person.Id
]. AS A
ORDER BY A.Name DESC, A.Id DESC
)
ORDER BY Person.Name, Person.Id;
Essentially, we query the top 30, reverse the order, query the top 10, and then select the rows from the table that match, sorting in forward order again. This should be fairly efficient, assuming the Id is the PRIMARY KEY, and there is an index on Name. It might be that a specific covering index on Name, Id (rather than one on just Name) would be needed for best performance, but I think that indexes implicitly cover the PRIMARY KEY.
Another way - Let say you want from 1000 to 1999 records in a table called table1 (of course if you have that many records) you can do something like this.
MSSQL
SELECT *
FROM table1 LIMIT 1000, 1999;
MS Access
SELECT TOP 1000 *
FROM table1
Where ID NOT IN (SELECT TOP 999 table1.ID FROM table1);
To break this down
SELECT TOP NumA *
FROM table1
Where ID NOT IN (SELECT TOP NumB table1.ID FROM table1);
UpperLimit = 1999
LowerLimit = 1000
NumA = UpperLimit - LowerLimit + 1
ex. 1000 = 1999 - 1000 + 1
NumB = LowerLimit -1
ex. 999 = 1000 - 1
A better query would be:
SELECT Users.*
FROM Users
WHERE Users.id In
(
SELECT TOP X A.id
FROM [
SELECT TOP Y Users.*
FROM Users
ORDER BY Users.reg_date DESC
]. AS A
ORDER BY A.reg_date ASC
)
ORDER BY Users.reg_date DESC
Where
if((totalrows - offset) < limit) then
X = (totalrows - offset)
else
X = limit
And:
Y = limit + offset
For example, if total_rows = 12, and we set the limit to 10 (show 10 users per page), and the offset is calculated as p * limit - (limit) where p is the number of the current page, hence in the first page (p = 1) we will get: X = 12 and Y = 10, on the second X = 2 and Y = 20. The list of users is ordered by registration date (descending).
Simple and fastest solution.
myTable {ID*, Field2, Filed3...}
Assume your SortOrder contain primary KEY only
SELECT TOP PageItemsCount tb01.*
FROM myTable AS tb01
LEFT JOIN (
SELECT TOP OffsetValue ID FROM myTable ORDER BY ID ASC
) AS tb02
ON tb01.ID = tb02.ID
WHERE ISNULL(tb02.ID)
ORDER BY tb01.ID ASC
SortOrder based on other fields with duplicated values, in this case you must include your primary key in SortOrder as last one.
For exemple, myTable
+-------+--------+--------+
| ID | Field2 | Filed3 |
+-------+--------+--------+
| 1 | a1 | b |
| 2 | a | b2 |
| 3 | a1 | b2 |
| 4 | a1 | b |
+-------+--------+--------+
SELECT TOP 2 * From myTable ORDER BY FIELD2;
+-------+--------+--------+
| ID | Field2 | Filed3 |
+-------+--------+--------+
| 2 | a | b2 |
| 4 | a1 | b |
| 3 | a1 | b2 |
| 1 | a1 | b |
+-------+--------+--------+
SELECT TOP 2 * From myTable ORDER BY FIELD2, FIELD3;
+-------+--------+--------+
| ID | Field2 | Filed3 |
+-------+--------+--------+
| 2 | a | b2 |
| 4 | a1 | b |
| 1 | a1 | b |
+-------+--------+--------+
But if we add ID to sort order [AS LAST IN FIELDS LIST]
SELECT TOP 2 * From myTable ORDER BY FIELD2, ID;
+-------+--------+--------+
| ID | Field2 | Filed3 |
+-------+--------+--------+
| 2 | a | b2 |
| 1 | a1 | b |
+-------+--------+--------+
Final request
SELECT TOP PageItemsCount tb01.*
FROM myTable AS tb01
LEFT JOIN (
SELECT TOP OffsetValue ID FROM myTable ORDER BY Field2 ASC, ID
) AS tb02
ON tb01.ID = tb02.ID
WHERE ISNULL(tb02.ID)
ORDER BY tb01.Field2 ASC, tb01.ID
You can definitely get the the equivalent of "Limit" using the top keyword. See:
Access Database LIMIT keyword
No, JET SQL does not have a direct equivalent. As a workaround, you could add a WHERE clause that selects an ordered/id column between two values.
If possible, you can also use pass-through queries to an existing MySQL/other database.
While TOP in MS-Access can limit records returned, it does not take two parameters as with the MySQL LIMIT keyword (See this question).

how can i check if value exists before the date specified in sql server

I have the data below in a sql table,
ID | supplier | Supplier_Due | Date |
1 | S-0003 | 14850 |2020-11-09
2 | S-0003 | 850 |2020-11-09
3 | S-0003 | 21750 |2020-11-13
4 | S-0003 | 975 |2020-11-15
5 | S-0003 | 75 |2020-11-17
let assume the user wants to get data of 2020-11-13 which is
3 | S-0003 | 21750 |2020-11-13
but i'd like to get the previous supplier due as well before the date specified which is
850
along with
3 | S-0003 | 21750 |2020-11-13
so the actual query i wanna get is this
ID | supplier | Supplier_Due | Date | Previous Due
3 | S-0003 | 21750 |2020-11-13 | 850
and if there is no previous due i wanna return
ID | supplier | Supplier_Due | Date | Previous Due
3 | S-0003 | 21750 |2020-11-13 | 0.00
i couldn't even figure out how to write the query because i dont understand how to go about it
You can use window functions. Assuming that date can be used to consistently order the records of each supplier:
select *
from (
select t.*,
lag(supplier_due, 1, 0) over(partition by supplier order by date) as previous_due
from mytable t
) t
where date = '2020-11-13' and supplier = 'S-0003'
A typical alternative is a subquery, or a lateral join:
select t.*, coalesce(t1.supplier_due, 0) as previous_due
from mytable t
outer apply (
select top (1) supplier_due
from mytable t1
where t1.supplier = t.supplier and t1.date < t.date
order by t1.date desc
) t1
where date = '2020-11-13' and supplier = 'S-0003'
DECLARE #Suppliers table
(
ID integer PRIMARY KEY CLUSTERED,
Supplier char(6) NOT NULL,
Supplier_Due smallmoney NOT NULL,
[Date] date NOT NULL
);
INSERT #Suppliers
(ID, Supplier, Supplier_Due, [Date])
VALUES
(1, 'S-0003', 14850, '2020-11-09'),
(2, 'S-0003', 850, '2020-11-09'),
(3, 'S-0003', 21750, '2020-11-13'),
(4, 'S-0003', 975, '2020-11-15'),
(5, 'S-0003', 75, '2020-11-17');
SELECT
S.ID,
S.Supplier,
S.Supplier_Due,
S.[Date],
[Previous Due] =
LAG(S.Supplier_Due, 1, 0) OVER (
PARTITION BY S.Supplier
ORDER BY S.[Date] ASC)
FROM #Suppliers AS S
WHERE
S.[Date] = CONVERT(date, '2020-11-13', 121);
db<>fiddle demo
Documentation: LAG (Transact-SQL)

Find out every column sum by groupwise as row Using Pivot

I have a table tbTest like this:
q1 | q2 | q3 | type
--------------------
2 | 1 | 3 | student
3 | 2 | 1 | alumni
2 | 1 | 3 | alumni
1 | 1 | 3 | student
Now I want a new table which is based on the first table and finds the sum of every question by GroupWise convert it into like this:
q | student | alumni
---------------------
q1 | 3 | 5
q2 | 2 | 3
q3 | 6 | 3
SELECT Student,
Alumni
FROM
(SELECT q1, userType FROM tbTest2) tb1
PIVOT
(
SUM(q1)
FOR userType IN (Student, Alumni)
) AS tb2;
But using(above SQL) Pivot I can manage only one row like this:
student | alumni
---------------------
3 | 5
You can unpivot the data and aggregate. Based on the C# tag, I am assuming the database is SQL Server, in which case you can use apply:
select v.question,
sum(case when t.type = 'student' then val else 0 end) as student,
sum(case when t.type = 'alumni' then val else 0 end) as alumni
from t cross apply
(values ('q1', t.q1), ('q2', t.q2), ('q3', t.q3)) v(question, val)
group by v.question;
In other database, you can do something similar using a lateral join or union all.

Creating a constraint on two columns to support specific rule

I have a table storing Device details. For simplicity, the columns are:
Id (Primary Key)
Name (varchar)
StatusId (Foreign Key to Status table).
The Status table has two columns:
Id (Primary Key)
State (varchar)
and two rows:
[Id | State]
1 | Active
2 | Inactive
I would like to allow multiple devices in the Devices table with the same Name, but only one of them can have status Active at any time.
That is to say, this should be allowed in the Devices table:
[Id | Name | StatusId]
10 | Mobile001 | 1
11 | Mobile001 | 2
12 | Mobile001 | 2
20 | Tablet001 | 1
21 | Tablet002 | 2
22 | Tablet002 | 1
23 | Tablet003 | 2
But this should not be allowed:
[Id | Name | StatusId]
10 | Mobile001 | 1 <-- wrong
11 | Mobile001 | 1 <-- wrong
12 | Mobile001 | 2
20 | Tablet001 | 1
21 | Tablet002 | 1 <-- wrong
22 | Tablet002 | 1 <-- wrong
23 | Tablet003 | 2
Is there a way how to create a constraint in T-SQL to reject inserts and updates that violate this rule? And is there a way how to do it in EF code first using EntityTypeConfigurations and Fluent API, possibly via IndexAnnotation or IndexAttributes?
Thanks.
One method, as #ZoharPeled just commented is using a filtered unique index.
As you are only allowed one Active Device of a specific name, this can be implemented as below:
USE Sandbox;
GO
--Create sample table
CREATE TABLE Device (ID int IDENTITY(1,1),
[name] varchar(10),
[StatusID] int);
--Unique Filtered Index
CREATE UNIQUE INDEX ActiveDevice ON Device ([name], [StatusID]) WHERE StatusID = 1;
GO
INSERT INTO Device ([name], StatusID)
VALUES ('Mobile1', 1); --Works
GO
INSERT INTO Device ([name], StatusID)
VALUES ('Mobile1', 0); --Works
GO
INSERT INTO Device ([name], StatusID)
VALUES ('Mobile2', 1); --Works
GO
INSERT INTO Device ([name], StatusID)
VALUES ('Mobile1', 1); --Fails
GO
UPDATE Device
SET StatusID = 1
WHERE ID = 2; --Also fails
GO
SELECT *
FROM Device;
GO
DROP TABLE Device;
Any questions, please do ask.
In EF CF You could achieve it by setting an unique index like described in this answer.
modelBuilder.Entity<Device>()
.HasIndex(d => new { d.Name, d.StatusId })
.IsUnique();

Version Control Algorithm

I have a database where I store objects. I have the following (simplified) schema
CREATE TABLE MyObjects
(
UniqueIdentifier Id;
BigInt GenerationId;
BigInt Value;
Bit DeleteAction;
)
Each object has a unique identifier ("Id"), and a (set of) property ("Value"). Each time the value of the property for an object is changed, I enter a new row into this table with a new generation id ("GenerationId", which is monotonically increasing). If an object is deleted, then I record this fact by setting the "DeleteAction" bit to true.
At any point in time (generation), I would like to retrieve the state of all of my active objects!
Here's an example:
Id GenerationId Value DeleteAction
1 1 99 false
2 1 88 false
1 2 77 false
2 3 88 true
Objects in generations are:
1: 1 {99}, 2 {88}
2: 1 {77}, 2 {88}
3: 1 {77}
The key is: how can I find out the row for each unique object who's generation id is closest (but not exceeding) to a given generation id? I can then do a post-filter step to remove all rows where the DeleteAction field is true.
This works in MS SQL
SELECT id,value
FROM Myobjects
INNER JOIN (
SELECT id, max(GenerationID) as LastGen
FROM MyObjects
WHERE GenerationID <= #Wantedgeneration
Group by ID)
On GenerationID = LastGen
WHERE DelectedAction = false
My version uses a joint of the table MyObjects against a
subset of itself, created by a subquery, and containing only the last
generation for each object:
SELECT O.id,generation,value FROM
MyObjects O,
(SELECT id,max(generation) AS max_generation FROM MyObjects
WHERE generation <= $GENERATION_ID GROUP BY id) AS TheMax WHERE
TheMax.max_generation = generation AND O.deleted is False
ORDER BY generation DESC;
In the above query, the GENERATION_ID is hardwired. A way to
parametrize it is to write a function:
CREATE OR REPLACE FUNCTION generation_objects(INTEGER) RETURNS SETOF MyObjects AS
'SELECT O.id,generation,value,deleted FROM
MyObjects O,
(SELECT id,max(generation) AS max_generation FROM MyObjects
WHERE generation <= $1 GROUP BY id) AS TheMax WHERE
TheMax.max_generation = generation AND O.deleted is False;'
LANGUAGE SQL;
Now, it works. With this table:
> SELECT * FROM MyObjects;
id | generation | value | deleted
----+------------+-------+---------
1 | 1 | 99 | f
2 | 2 | 88 | f
1 | 3 | 77 | f
2 | 4 | 88 | t
3 | 5 | 33 | f
4 | 6 | 22 | f
3 | 7 | 11 | f
2 | 8 | 11 | f
I get:
> SELECT * FROM generation_objects(1) ORDER by generation DESC;
id | generation | value | deleted
----+------------+-------+---------
1 | 1 | 99 | f
> SELECT * FROM generation_objects(2) ORDER by generation DESC;
id | generation | value | deleted
----+------------+-------+---------
2 | 2 | 88 | f
1 | 1 | 99 | f
> SELECT * FROM generation_objects(3) ORDER by generation DESC;
id | generation | value | deleted
----+------------+-------+---------
1 | 3 | 77 | f
2 | 2 | 88 | f
And then, at the following generation, object 2 is deleted:
> SELECT * FROM generation_objects(4) ORDER by generation DESC;
id | generation | value | deleted
----+------------+-------+---------
1 | 3 | 77 | f
Here's the working version:
SELECT MyObjects.Id,Value
FROM Myobjects
INNER JOIN
(
SELECT Id, max(GenerationId) as LastGen
FROM MyObjects
WHERE GenerationId <= #TargetGeneration
Group by Id
) T1
ON MyObjects.Id = T1.Id AND MyObjects.GenerationId = LastGen
WHERE DeleteAction = 'False'
Not sure whether that's standard SQL, but in Postgres, you can use the LIMIT flag:
select GenerationId,Value,DeleteAction from MyObjects
where Id=1 and GenerationId < 3
order by GenerationId
limit 1;

Categories

Resources