Related
Im updating a system, trying to create a 'shadow' table of an orignal table, reciving changes through triggers, but not using the orginal table, since I really don't like the way the orignal program is designed.
One of them is a team configuration table, holding all the users & their team id. Users with the same team id are of the same team. I am trying to create trigger here to intercept data into a new team history log. (1 row / 1 team id)
But since after a team setup, multiple rows of the table get updated, and update is not done in batch, it's hard to tell when a new team setup is over. Fortunately there is this member_count column, which can be used to count the members and determine when all the nencessry records related to this new team_id are updated.
| team id | uid | member_count |
|201701010800A| 1 | 3 |
|201701010800A| 2 | 3 |
|201701010800A| 3 | 3 |
My question is how should I specify this condtition in after-update trigger.
here is what I do for now. But it seems so hacky. I guess there are more sensible ways to express it.
if exists(
select 1 from ( values (1) ) t(c)
outer apply (
select count(team_id) tc, max(member_count) mx, min(member_count) mm
from old_teamtable
where team_id = ( select top 1 team_id from inserted )
) e
where tc = mx and mx = mm
)
Does this work for you?
if exists (
select 1
from old_teamtable
where team_id = (select top 1 team_id from inserted)
having count(*) = max(member_count)
and max(member_count) = min(member_count)
)
The following code demonstrates one way to manage a team history table based on inserts to a teams and users table. The trigger fires after each insert to TeamsAndUsers. For new teams, not already in the TeamHistory, appropriate rows are added and marked IsComplete = 0. The TeamHistory is then updated by checking all the teams affected by the insert to see if they have the correct number of users. If so, the IsComplete bit is set to 1 for the corresponding rows in the TeamHistory table.
-- Create sample tables.
create table TeamsAndUsers ( Id Int Identity, TeamId Int, UserId Int, TeamMembers Int );
create table TeamHistory ( Id Int Identity, TeamId Int, IsComplete Bit );
go
-- Create the trigger.
create trigger UpdateTeamHistory
on TeamsAndUsers
after Insert
as
set nocount on
-- Handle any teams we've not heard of before.
insert into TeamHistory ( TeamId, IsComplete )
select distinct TeamId, 0
from inserted
where TeamId not in ( select TeamId from TeamHistory );
-- See if any teams have been completed, i.e. all of their members are present.
update TeamHistory
set IsComplete = 1
where TeamId in (
select TAU.TeamId from (
-- TeamId values touched by the INSERT ...
select distinct TeamId from inserted ) as i inner join
-- ... matched with corresponding incomplete teams in the team history ...
TeamHistory as TH on TH.TeamId = i.TeamId and TH.IsComplete = 0 inner join
-- ... matched with users currently assigned to the teams.
TeamsAndUsers as TAU on TAU.TeamId = i.TeamId
group by TAU.TeamId
having Count( TAU.UserId ) = Max( TAU.TeamMembers ) );
go
-- Test the trigger.
insert into TeamsAndUsers ( TeamId, UserId, TeamMembers ) values
( 101, 1, 3 ), ( 101, 2, 3 );
select * from TeamsAndUsers;
select * from TeamHistory;
insert into TeamsAndUsers ( TeamId, UserId, TeamMembers ) values
( 101, 3, 3 );
select * from TeamsAndUsers;
select * from TeamHistory;
insert into TeamsAndUsers ( TeamId, UserId, TeamMembers ) values
( 102, 4, 1 );
select * from TeamsAndUsers;
select * from TeamHistory;
insert into TeamsAndUsers ( TeamId, UserId, TeamMembers ) values
( 103, 10, 2 );
select * from TeamsAndUsers;
select * from TeamHistory;
insert into TeamsAndUsers ( TeamId, UserId, TeamMembers ) values
( 103, 11, 2 );
select * from TeamsAndUsers;
select * from TeamHistory;
Aside: The select in the update looks rather clumsy, but it's late here. It can probably be tidied up a little.
In my application there is a table to store text and another table to store it's respective images..
My table structure goes as follows (tbl_article):
article_id | Page_ID | article_Content
-----------+---------+-----------------
1 | 1 | hello world
2 | 1 | hello world 2
where article_id is the pk and auto incremented.
Now in my other table (tbl_img):
image_id| image_location|article_id | page_id
--------+---------------+-----------+---------
1 | imgae locat | 1 | 1
2 | image loc2 | 2 | 1
where image_id is the pk and auto incremented.
In both table I am inserting data through table valued parameter, and in second table article_id is referencing article_id of the first table.
To get auto incremented column value I am using output clause:
DECLARE #TableOfIdentities TABLE
(
IdentValue INT,
PageId INT
)
INSERT INTO tbl_article(page_id, article_content)
OUTPUT Inserted.article_id, #pageId INTO #TableOfIdentities (IdentValue, PageId)
SELECT page_id, slogan_body_header
FROM #dtPageSlogan
INSERT INTO tbl_img(page_id, image_location)
SELECT page_id, image_location
FROM #dtPageImageContent
But now I have to insert values from #TableOfIdentities into article_id of tbl_img - how to do that?
You need an additional column , a temporary article id generated from your code to link images and related articles properly. So you can use MERGE with OUTPUT, because with merge you can refer to columns from both the target and the source and build your TableOfIdentities tvp properly, then join it with dtPageImageContent to insert on tbl_img.
CREATE TABLE tbl_article (
article_id INT IDENTITY(1, 1) PRIMARY KEY
, Page_ID INT
, article_Content NVARCHAR(MAX)
);
CREATE TABLE tbl_img (
image_id INT IDENTITY(1, 1) PRIMARY KEY
, image_location VARCHAR(256)
, article_id INT
, Page_ID INT
);
DECLARE #TableOfIdentities TABLE
(
IdentValue INT,
PageId INT,
tmp_article_id INT
);
DECLARE #dtPageSlogan TABLE(
tmp_article_id INT -- generated in your code
, page_id INT
, slogan_body_header NVARCHAR(MAX)
);
DECLARE #dtPageImageContent TABLE (
page_id INT
, image_location VARCHAR(256)
, tmp_article_id INT -- needed to link each image to its article
)
-- create sample data
INSERT INTO #dtPageSlogan(tmp_article_id, page_id, slogan_body_header)
VALUES (10, 1, 'hello world');
INSERT INTO #dtPageSlogan(tmp_article_id, page_id, slogan_body_header)
VALUES (20, 1, 'hello world 2');
INSERT INTO #dtPageImageContent(page_id, image_location, tmp_article_id)
VALUES (1, 'image loc1', 10);
INSERT INTO #dtPageImageContent(page_id, image_location, tmp_article_id)
VALUES (1, 'image loc2', 20);
-- use merge to insert tbl_article and populate #TableOfIdentities
MERGE INTO tbl_article
USING (
SELECT ps.page_id, ps.slogan_body_header, ps.tmp_article_id
FROM #dtPageSlogan as ps
) AS D
ON 1 = 2
WHEN NOT MATCHED THEN
INSERT(page_id, article_content) VALUES (page_id, slogan_body_header)
OUTPUT Inserted.article_id, Inserted.page_id, D.tmp_article_id
INTO #TableOfIdentities (IdentValue, PageId, tmp_article_id)
;
-- join using page_id and tmp_article_id fields
INSERT INTO tbl_img(page_id, image_location, article_id)
-- select the "IdentValue" from your table of identities
SELECT pic.page_id, pic.image_location, toi.IdentValue
FROM #dtPageImageContent pic
-- join the "table of identities" on the common "page_id" column
INNER JOIN #TableOfIdentities toi
ON pic.page_Id = toi.PageId AND pic.tmp_article_id = toi.tmp_article_id
;
You can try it on fiddle
You need to join the #dtPageImageContent table variable with the #TableOfIdentities table variable on their common page_id to get those values:
-- add the third column "article_id" to your list of insert columns
INSERT INTO tbl_img(page_id, image_location, article_id)
-- select the "IdentValue" from your table of identities
SELECT pic.page_id, pic.image_location, toi.IdentValue
FROM #dtPageImageContent pic
-- join the "table of identities" on the common "page_id" column
INNER JOIN #TableOfIdentities toi ON pic.page_Id = toi.page_id
I want to insert a row into a SQL server table at a specific position. For example my table has 100 rows and also I have a field named LineNumber,I want to insert a new row after line number 9. But the ID column which is PK for the table already has a row with LineNumber 9. So now I need a new row with the line number 9 or 10 so that the ID field has to be updated automatically. How can I insert a row at this position so that all the rows after it shift to next position?
Don't modify the primary key, that is not a good way to modify the order of your output now that you have a new record you want to insert.
Add a new column on to the table to hold your order. Then you can copy the primary key values in to that column if that's your current order before making the required changes for the new row.
Sample that you should be able to copy and paste and run as is:
I've added orderid column, which you will need to do with default null values.
DECLARE #OrderTable AS TABLE
(
id INT ,
val VARCHAR(5) ,
orderid INT
)
INSERT INTO #OrderTable
( id, val, orderid )
VALUES ( 1, 'aaa', NULL )
,
( 2, 'bbb', NULL )
,
( 3, 'ddd', NULL )
SELECT *
FROM #OrderTable
-- Produces:
/*
id val orderid
1 aaa NULL
2 bbb NULL
3 ddd NULL
*/
-- Update the `orderid` column to your existing order:
UPDATE #OrderTable
SET orderid = id
SELECT *
FROM #OrderTable
-- Produces:
/*
id val orderid
1 aaa 1
2 bbb 2
3 ddd 3
*/
-- Then you want to add a new item to change the order:
DECLARE #newVal AS NVARCHAR(5) = 'ccc'
DECLARE #newValOrder AS INT = 3
-- Update the table to prepare for the new row:
UPDATE #OrderTable
SET orderid = orderid + 1
WHERE orderid >= 3
-- this inserts ID = 4, which is what your primary key would do by default
-- this is just an example with hard coded value
INSERT INTO #OrderTable
( id, val, orderid )
VALUES ( 4, #newVal, #newValOrder )
-- Select the data, using the new order column:
SELECT *
FROM #OrderTable
ORDER BY orderid
-- Produces:
/*
id val orderid
1 aaa 1
2 bbb 2
4 ccc 3
3 ddd 4
*/
What makes this difficult is that the column is a primary key. If you can interact with the database when no one else is, then you can do this:
Make the column no longer a primary key.
Run a command like this:
UPDATE MyTable
SET PrimaryColumnID = PrimaryColumnID + 1
WHERE PrimaryColumnID > 8
Insert the row with the appropriate PrimaryColumnID (9).
Restore the column to being the primary key.
Obviously, this probably wouldn't be good with a large table. You could create a new primary key column and switch it, then fix the values, then switch it back.
Two steps, first update LineNumber
UPDATE
table
SET
LineNumber = LineNumber + 1
WHERE
LineNumber>9
Then do your insert
INSERT INTO table
(LineNumber, ...) VALUES (10, .....)
I need to model groups of persons and I can't find a way to design tabels to do it efficiently.
Groups can be thought as sets, unordered collections of one or more persons, each group should be uniquely identified by its components.
Edit: and a person can be part of more than one group.
My first attempt looks like this.
A table which contains all "persons" managed by the system.
table Persons(
id int,
name varchar,
(other data...)
)
a table that contains groups and all group properties:
table Groups(
group_id int,
group_name varchar,
(other data...)
)
and a table with the association between persons and groups
table gropus_persons (
person_id int,
group_id in
)
This design doesn't fit well with this requirements because it is hard to write the query to retrieve the group id from a list of components.
The only query I could come up to find the group composed by persons (1, 2, 3) looks like this:
select *
from groups g
where
g.group_id in (select group_id from gropus_persons where person_id = 1)
and g.group_id in (select group_id from gropus_persons where person_id = 2)
and g.group_id in (select group_id from gropus_persons where person_id = 3)
and not exists (select 1 from gropus_persons where group_id = g.group_id and person_id not in (1,2,3))
the problem is that the number of components is variable so I can only use a dynamically generated query and add a subquery for each component each time I need to find a new group.
Is there a better solution?
Thank you in advice for the help!
You need to group by the "group" and count how many hits you receive. For this, you only need the intersection table:
select GroupID, count(*) as MemberCount
from GroupsPersons
where PersonID in( 1, 2, 3 )
group by GroupID
having count(*) = 3;
The problem comes with making this query suitable for a varying list of person id values. As you seem to already realize this will require dynamic SQL, the pseudo-code will look something like this:
stmt := 'select GroupID, count(*) as MemberCount '
|| 'from GroupsPersons '
|| 'where PersonID in( ' || CSVList || ' ) '
|| 'group by GroupID '
|| 'having count(*) = ' || length( CSVList );
The one potential bug you have to be wary of is if the same id repeats in the list. For example: CSVList := '1, 2, 3, 2';
This will generate a correct count(*) value of 3, but the having clause will be looking for 4.
Another solution to consider is to pivot/xpath the set of person IDs in alpha sequence and store it in your groups table and compare that string with your target.
For your example, you'd use Select group_id from groups where personIDs = '1,2,3,'
How about this, I think the schema is the same as yours, not sure:
create table Groups(
group_id int primary key,
group_name varchar(100)
);
create table Persons(
person_id int primary key,
name varchar
);
create table Membership(
group_id int REFERENCES Groups (group_id),
person_id int REFERENCES Persons (person_id)
);
INSERT INTO Persons
VALUES (1, 'p1'),
(2, 'p2'),
(3, 'p2'),
(4, 'p2');
INSERT INTO Groups
VALUES (1, 'group1'),
(2, 'group2');
INSERT INTO Membership
VALUES (1, 1),
(1, 2),
(2, 2),
(1, 3);
Then select:
select p.name, g.group_name
from Persons as p
join Membership as m on p.person_id = m.person_id
join Groups as g on g.group_id = m.group_id
where m.group_id in (1, 2);
Obviously data would need to be adjusted to suit yours.
In my MVC 4 application I was looking for optgroup for dropdownlist and found this very helpful.
I followed the steps and achieved that as well but currently I am facing some issues.My controller code looks like:
Model.ddlProject = db.Projects.OrderBy(t => t.Type)
.Select(t => new GroupedSelectListItem
{
GroupName = t.Type,
Text = t.Name,
Value = SqlFunctions.StringConvert((decimal)t.Id)
});
The grouping is done here but I want it in a different manner.With this I am able to bind the dropdown as shown below:
Now let me tell about my table structure.I have one table with following structure:
Here I have one hierarchy such that Customer -> Client -> Projects . Thus, I need to group by Clients such that all the projects should be listed under the clients.I want something like this:
Hence I have been stuck with query over here,the query to achieve this.I need to show all the client names in the group name and for clients I need to list all the respective projects.I guess I am clear with my requirement.Kind of got stuck here so any help would be great.Thanks in advance.
As Stephen Muecke said in the comment section that Projects and Clients are 2 different things. You should have a Client table and a separate Project table containing a column with foreign key to the Client table. So below solution is according to your existing table structure.
Dummy Schema
DECLARE #clientprj TABLE ([id] int identity(1,1), [name] varchar(30), Desc1 varchar(30), ParentId int, pType varchar(30));
INSERT #clientprj ([name], Desc1, ParentId, pType) VALUES ('client1', '', 0, 'clt'), ('prj1', '', 1, 'prj'), ('prj2', '', 1, 'prj'), ('client2', '', 0, 'clt'), ('prj n', '', 4, 'prj')
and Here is the query
SELECT GroupName, GroupKey, ProjName, ProjId
FROM
(
(
SELECT NAME AS GroupName
,Id AS GroupKey
FROM #clientprj m
WHERE ParentId = 0
) m
FULL OUTER JOIN
(
SELECT NAME AS ProjName
,Id AS ProjId
,ParentId
FROM #clientprj
)
t ON m.GroupKey = t.ParentId
)
WHERE ParentId <> 0
Which return the following output.
GroupName GroupKey ProjName ProjId
client1 1 prj1 2
client1 1 prj2 3
client2 4 prj n 5
and your controller method which calls this query like this -
Model.ddlProject = db.NewMethod
.Select(t => new GroupedSelectListItem
{
GroupName = t.GroupName,
GroupKey = t.GroupKey.ToString(),
Text = t.ProjName,
Value = t.ProjId.ToString()
});
and then bind your dropdownlist. Best of luck...