Creating a constraint on two columns to support specific rule - c#

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();

Related

C# WPF DataGrid Group view of related tables

I have my data arranged in two related tables (SQLite). Let's say table Families and People. Table structure looks like below.
CREATE TABLE families (
FamilyID INTEGER PRIMARY KEY AUTOINCREMENT,
FamilyName VARCHAR,
FamilyPower INTEGER,
FamilyStatus Boolean
);
CREATE TABLE people (
PersonID INTEGER PRIMARY KEY AUTOINCREMENT,
FamilyID INTEGER REFERENCES families (FamilyID),
Name Varchar,
Age INTEGER
);
I'd like to display data from this table in grouped DataGrid. I can arrange this data as a DataSet, or as a custom class'es Objects or a netsted Objects (people could be a list of objects of class Person inside of the Family object). But I'm not sure which option is the best.
The worst option (I think) is doing one DataTable containing joined tables like SELECT * FROM families LEFT JOIN people ON families.FamilyID = people.PersonID. But I may be wrong and this is actually the best option to achieve what i want.
What I want is to display grouped data with group header containing full info about family and all its member as a data rows. So it would looks like below:
PersonID | Name | Age |
FamilyID: 1 Name: Stark Power: 10 Members: 3 Status: [x] <-- This is checkbox
1 | Eddard | 60 |
2 | Robb | 30 |
3 | Arya | 15 |
FamilyID: 2 Name: Lannister Power: 15 Members: 4 Status: [x]
4 | Cersei | 30 |
5 | Tyrion | 35 |
6 | Jaimie | 30 |
7 | Tywin | 30 |
FamilyID: 1 Name: Targaryen Power: 10 Members: 1 Status: [ ]
1 | Daenerys | 30 |
Is it even possible to achieve that? Please help me to choose best way to store data and solve how to bind and group it in the DataGrid?

How to delete Category and SubCateogries?

I have little Confusion that when we delete Category is it necessary to delete Subcategory related to that if so please help by writing Sql query?Similarly if delete subcategory then deleting Category will also should be deleted as well?
+-------+------------+
| CatID | CatName |
+-------+------------+
| 1 | Seeds |
| 2 | Pesticides |
| 3 | Fertilizer |
+-------+------------+
+----------+---------------+-----------+
| SubCatID | SubCatName | CatID |
+----------+---------------+-----------+
| 1 | Red Seed | 1 |
| 2 | Red Seed | 1 |
| 3 | Red Seed | 1 |
| 4 | Red Seed | 1 |
| 5 | National Worm | 2 |
+----------+---------------+-----------+
You need ON DELETE CASCADE by altering current schema :
ALTER TABLE SubCategory
ADD CONSTRAINT fk_SubCat_CatID FOREIGN KEY (CatID) REFERENCES category(CatID )
ON DELETE CASCADE;
By this when you delete category from category table, the reference data would auto delete. Just make sure drop your current constraint before create new one with ON DELETE CASCADE.
Given you have a Foreign Key defined on this relation, then yes, you have to delete (because childs cannot be orphans) or set null (if you column CatId allows null values) on the CatID column on the Subcategories table because the relational databases uses the referential integrity. You can define the CASCADE option to do it for you automatically.
ALTER TABLE SubCategory
ADD CONSTRAINT FK_SubCategory_Category
FOREIGN KEY (CatId)
REFERENCES Category (CatId)
ON DELETE CASCADE
Check this article for more details.
If you have a FK and want more control what you are deleting, you must provide two sql statements to make it, for sample.
delete from subcategory where catId = 1
delete from category where catId = 1

Cassandra Update - 'Where' with timestamp clustering key

I have a table in cassandra with following structure:
CREATE TABLE answers (
Id uuid,
Name text,
Description text,
LastVersion boolean,
CreationDate timestamp,
EditionDate timestamp,
PRIMARY KEY(Id, EditionDate)
)WITH CLUSTERING ORDER BY (EditionDate DESC);
The problem is when I need to update the value of the LastVersion column to false. In this case a new line is inserted only with the values ​​of the Primary Key (Id, EditionDate) + the value of the LastVersion column.
In this order:
INSERT:
insert into answers
(id, name, description, lastversion, creationdate, editiondate)
values
(uuid(), 'Test 1', 'Description 1', true, dateof(now()), dateof(now()));
RESULT:
id | editiondate | creationdate | description | lastversion | name
--------------------------------------+---------------------------------+---------------------------------+---------------+-------------+--------
ac4f9ec1-8737-427c-8a63-7bdb62c93932 | 2018-08-01 19:54:51.603000+0000 | 2018-08-01 19:54:51.603000+0000 | Description 1 | True | Test 1
UPDATE:
update answers
set lastversion = false
where id = ac4f9ec1-8737-427c-8a63-7bdb62c93932
and editiondate = '2018-08-01 19:54:51';
RESULT:
id | editiondate | creationdate | description | lastversion | name
--------------------------------------+---------------------------------+---------------------------------+---------------+-------------+--------
ac4f9ec1-8737-427c-8a63-7bdb62c93932 | 2018-08-01 19:54:51.603000+0000 | 2018-08-01 19:54:51.603000+0000 | Description 1 | True | Test 1
ac4f9ec1-8737-427c-8a63-7bdb62c93932 | 2018-08-01 19:54:51.000000+0000 | null | null | False | null
What is wrong? Actually the EditionTime field seems to be different but, I spend the same value on UPDATE query.
your update is using a different value for editionDate than you inserted so your update is not finding the original row. And Cassandra updates and inserts are really upserts so a new row with the new key is being inserted.
Notice the EditionDate has millisecond precision but your update is only specifying it to the nearest second.

Where if there's a match

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);

How do a make a query to count which string appears the most in a certain column?

I'm making a program and I need to make a query to the database asking for the string that appears most often in a given column. In this example, its "stringONE".
----------------------------
| ID | Column (string) |
----------------------------
| 1 | stringONE |
----------------------------
| 2 | stringTWO |
----------------------------
| 3 | stringONE |
----------------------------
| 4 | stringONE |
----------------------------
Now I need to take the name of the string that appears the most and put it into a variable string, for example:
string most_appeared_string = sql.ExecuteScalar();
Also, what happens if there is no string that appears the most, rather 2 or more strings that appear the same amount of times, like this:
----------------------------
| ID | Column (string) |
----------------------------
| 1 | stringONE |
----------------------------
| 2 | stringTWO |
----------------------------
| 3 | stringTWO |
----------------------------
| 4 | stringONE |
----------------------------
Thanks ahead.
#KeithS
Do you have an sql-server version of the query because I'm getting some errors when trying it there. Here's a table example of what I'd like to do precisely.
------------------------------------------------
| ID | column1 (string) | author (string) |
------------------------------------------------
| 1 | string-ONE | John |
------------------------------------------------
| 2 | string-TWO | John |
------------------------------------------------
| 3 | string-ONE | Martin |
------------------------------------------------
| 4 | string-ONE | John |
------------------------------------------------
SELECT TOP (1) column1, COUNT(*) FROM table WHERE author='John' ORDER BY ID
It should return "string-ONE" since it appears the most (2) times for the author John. When trying the query in MS-SQL Management Studio though, this is the error I'm getting:
Column 'table.column1' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
Nevermind the edit. Thank you.
This is a pretty easy query (in T-SQL at least):
select top 1 Column, Count(*) from Table group by Column order by Count(*) desc
ExecuteScalar, by an implementation detail, will return the string value because it's the first column of the only row in the result set, even though there are two columns. You could also use ExecuteReader to access the number of times that string occurs.
select top (1) SomeCol, count(*) as Row_Count
from YourTable
group by SomeCol
order by Row_Count desc
Also, what happens if there is no string that appears the most, rather
2 or more strings that appear the same amount of times, like this:
In that case, using the above query, you will get one arbitrary row. You can add with ties to get all rows that has the same highest value.
select top (1) with ties SomeCol, count(*) as Row_Count
from YourTable
group by SomeCol
order by Row_Count desc
SELECT max(counted) AS max_counted FROM (
SELECT count(*) AS counted FROM counter GROUP BY date
)
This could do the trick

Categories

Resources