Create an instance of a table in a single row? - sql

I'm having trouble figuring out how to create a snapshot (or instance) of a table when the user updates the table in the UI.
The problem is:
I have a UI for "Amendments." In the UI, there is a dropdown list for "Amendment Type." Amendment Type also has its own UI, and the user can check/un-check the list of available Amendment Types here. When the user un-checks an Amendment Type, that type gets removed from the dropdown list for all Amendments. The client asks that changes to the Amendment Type ONLY affect NEW Amendments, and not ones that existed before the change.
So, if the Amendment Type values for "New, Draft, Closed" were checked previously, and "Draft" was un-checked, I would still need to display all three values in the UI for existing Amendments, and then only display "New" and "Closed" for new Amendments. But then, they can go back in and reopen the "Draft" and have that display once again, but only for Amendments created after they reopened it.
To me, this means that I need to create a table for "Amendment Type History." The table for "Amendment Type" has an AmendmentTypeID column where all of the IDs are displayed as rows. I would display those as columns, with the row defined by the Effective Date--the date that the values were changed. Then I would link the Amendment to the AmendmentTypeHistoryID and get the values to display by looking up the AmendmentTypeIDs.
e.g.
dbo.AmendmentType
AmendmentTypeID Name CreationDate IsActive
1 New 6/2/2019 1
2 Draft 6/2/2019 1
3 Closed 6/2/2019 1
dbo.AmendmentTypeHistory
AmendmentTypeHistoryID EffectiveDate AmendmentTypeID AmendmentTypeID AmendmentTypeID
1 6/3/2019 1 (New) 2 (Draft) 3 (Closed)
Then you change it...
dbo.AmendmentType
AmendmentTypeID Name CreationDate IsActive
1 New 6/2/2019 1
3 Closed 6/2/2019 1
dbo.AmendmentTypeHistory
AmendmentTypeHistoryID EffectiveDate AmendmentTypeID AmendmentTypeID AmendmentTypeID
1 6/3/2019 1 (New) 2 (Draft) 3 (Closed)
2 6/3/2019 1 (New) 3 (Closed) NULL
There are 77 Amendment Type possibilities total. It's not something I can hard code, so I was hoping I could do it dynamically somehow.
Does anyone know how I could do this? Is there an easier or better way to do what I'm trying to do?

This is a very difficult and confusing requirement. What happens if the Amendment Types which IsActive are different between the moment you load them into the drop down and the moment the user saves their entry?
Ignoring this, I would recommend a table which would map combinations in an ordered comma-seperated list. It is very rare that my answer breaks Normal Forms, but it would be difficult otherwise.
Make a table called AmendmentTypeGroup:
create table AmendmentTypeGroup
(
AmendmentTypeGroupID int identity
,AmendmentTypes nvarchar(max) not null
,constraint PK_AmendmentTypeGroup primary key clustered(AmendmentTypeGroupID)
)
Add a column to your Amendments (not amendments type) table:
alter table Amendmends add AmendmentTypeGroupID int null
Each time you insert a row in Amendments, use a trigger or proc to ensure you first insert into the AmendmentTypeGroup a group of all the IDs of all AmendmentTypes who are IsActive at that moment, get the group ID, and then insert the Amendments row with the group value you inserted. If there were already a group with the specific combination, just grab its id and use that instead.
declare #amendmentCombination nvarchar(max)
declare #groupID int
select amendmentCombination=string_agg(convert(nvarchar(max),AmendmentTypeID),',') WITHIN GROUP ( ORDER BY AmendmentTypeID asc)
from AmendmentType
where IsActive = 1 -- will only work for sql server version 2017. Search XML path concatenation if you don't have it
select #groupID=AmendmentTypeGroupID
from AmendmentTypeGroup
where AmendmentTypes=#amendmentCombination
if #groupID is null
begin
insert AmendmentTypeGroup(AmendmentTypes) select #amendmentCombination
set #groupID=SCOPE_IDENTITY() -- last id entered
end
insert Amendments(..your_other_columns...,AmendmentTypeGroup)
select ..your_other_columns...,#groupID
This way, for each Amendment row, you can join it with AmendmentTypeGroup to get the comma seperated list of available AmendmentTypes at that time. You will have to split it to take in in a tabular form.
To make your data more robust, you ought to 1) Manually create all AmendmentTypeGroups which were used for already existing rows in amendments, and insert their IDs in Amendment.AmendmentTypeGroup, so that you can then 2) create a FK on Amendment.AmendmentTypeGroup referenceing AmendmentTypeGroup.AmendmentTypeGroupID 3) create a unique constraint on AmendmentTypeGroup.AmendmentTypes
As you can see, this is difficult. Tread carefully.

After you comments I think I have a solution for you.
Create a table AmendmentTypeVersions like this
ID | VERSION_NUMBER | AmendmentTypeId
and insert all active AmendmentTypes using version 1
INSERT INTO AmendmentTypeVersions (VERSION_NUMBER , AmendmentTypeId)
SELECT 1, AmendmentTypeID
FROM AmendmentType
WHERE isactive = 1
Every time you have save the AmendmentType, get the max version number and insert the records of the new version
DECLARE #LastVersion = (SELECT MAX(VERSION_NUMBER) FROM AmendmentTypeVersions)
INSERT INTO AmendmentTypeVersions (VERSION_NUMBER , AmendmentTypeId)
SELECT #LastVersion + 1, AmendmentTypeID
FROM AmendmentType
WHERE isactive = 1
Add to column to Amendments with the VERSION_NUMBER and set it to 1
Now all you have to do is this:
When you are creating an Amendment you need to get then set the Amendment VERSION_NUMBER to the #lastVersion (On the edit operation you already have Amendment VERSION_NUMBER set)
always filter the AmendmentTypes by the VERSION_NUMBER of the Amendment (you have to join amendmentTypes with the AmendmentTypeVersions by the VERSION_NUMBER of the Amendment)

Related

Inserting data from one table to another table values based on column condition

I want to know if there is a way to achieve this.
I have two table, I want to move data from one table to another table.
TableA:
ID Name Option
----------------------
1 First Add into box
2 Second Don't Add
3 Third Add into box
TableB
ID Name Option Status
--------------------------------
1 First Add into box Approved
2 Second Don't Add Reject
3 Third Add into box Approved
I want to insert data from TableA to tableB but tableB have one more column, where the value of it depends on option column data.
If column value is Add into box then it should be inserted as approved into TableB else Reject has to be inserted.
If you are using MSSQL, the below query would work.
Insert into TableB (Id,Name, Option, Status)
select Id,Name, Option, CASE when Option = 'Add into box' then 'Approved' ELSE 'Rejected' END
From TableA

Left join with multiple matches on right table with conditions

I am trying to do a SQL Server query where in my left table (database 1 - Db1), I have some Data that need to be checked some times.
I can`t write in this table (Db1, read only) so I have created other database Db2 that when I check the information related to some record in Db1, I save the date of this check at Db2.
Then, I have a list on the left table that shows me all the records that have to be check. When I create the check record in db2, for some time, 3-4 month, the list does not have to show me this record again.
So the query must handle that, if there is not record in db2, if is null, the record must be showed in the list, then if I create one check, for a time (3-4 month) does not have to show this record, when the 3-4 months have passed, the record have to be again in the list to remember to do a new check, then if I create a new check in Db2 with new date, the record at left has to hide again for 3-4 month more and so on.
I have tried this, that works if there is only one record at Db2, but as soon as I create another check, the old record, the first one, does not allow to hide the left record on the list.
I hope some one can give me a clue.
Thank you in advance
SELECT uno.fecha_ini, uno.fecha_res, uno.fecha_fin, uno.n_contrato, uno.n_propied, tres.n_contrato
FROM [GI].[dbo].[alq03] uno
LEFT JOIN [GI_impuestos].[dbo].[checkar] tres ON uno.n_contrato = tres.n_contrato
WHERE tres.n_contrato IS NULL
/*checks*/
AND (tres.fecha_checkar IS NULL)
OR (tres.fecha_checkar NOT BETWEEN (GETDATE() - 120) AND (GETDATE()))
GROUP BY uno.fecha_ini, uno.fecha_res, uno.fecha_fin, uno.n_contrato, uno.n_propied, tres.n_contrato
ORDER BY uno.fecha_fin asc
Here is a simple example of what you are asking for. You will have to modify it to suit your needs.
create table readOnly(
id int primary key,
info varchar(100) not null);
insert into readOnly values
(1,'info to check'),
(2,'more info'),
(3,'not finished yet!');
create table chk (
rid int primary key,
checked date);
insert into chk values
(1,'2021-07-01'),
(2,'2021-12-01')
select
id,
coalesce(cast(checked as char),'never') last_check,
info info_to_check
from
readOnly
left join
chk on id = rid
where
datediff(m,checked,getdate())>4
or checked is null
id | last_check | info_to_check
-: | :----------- | :----------------
1 | 2021-07-01 | info to check
3 | never | not finished yet!
db<>fiddle here

How can I write a Stored Procedure that will reorder numbered data according to a Status Column?

I have a table that has a numeric column that orders the data, a text field that holds info, and a status column.
item_num item status
1 Plate new
2 Cup continued
3 Fork new
4 Spoon revised
When an item is updated with a status to "discontinued", I need the list to reorder the item_num to put all items with a "discontinued" status having the highest number, and reorder the other items accordingly. So, if I discontinue the Cup, I need a stored procedure to update the table resulting in this:
item_num item status
1 Plate new
2 Fork new
3 Spoon revised
4 Cup discontinued
I just need some direction on how to update the data and reorder by status.
I am using Microsoft SQL Server. The item_num is not the Primary Key. The problem I have is that I'm limited on what I can do because I can't make any changes to the table itself, it HAS to be a Stored Procedure, and it HAS to change those numbers. This is for Treatment Plan Goals in an EHR. When the Treatment Plan document generates, it lists the goals by the Goal Number (item_num). So if a user discontinues a goal, the numbering is wrong. The Treatment Plan doesn't show "discontinued" goals. So the Goals go from 1,2,3,4 to 1,2,4 (if I discontinued the 3rd goal).
I am a developer for the front end of an Electronic Health Records system. This Stored Procedure needs to fire when a user clicks a button. This site has been very helpful to me, but is my first post - I know I probably mangled it. Apologies.
You can use ROW_NUMBER() to achieve this, as long as all rows are distinct. If they are not, you will need a key of some kind to handle the join.
First put everything in a temp table and create your new item_num
SELECT item_num, item, status,
ROW_NUMBER() over (ORDER BY CASE WHEN STATUS = 'Discontinued'
THEN 2 ELSE 1 END) as 'newnum'
INTO #TEMP
FROM TABLE
Then JOIN your temp to the main, and replace the old item_num with the newnum
UPDATE ta
SET ta.item_num = t.newnum
FROM table ta
JOIN #temp t on t.item_num = ta.item_num
AND t.item = ta.item
AND t.status = ta.status

CDC in sql server

i have enabled CDC feature on one of my database. now i have below table data in cdc tables
MemberID LastName __$operation
1 David 4
1 Dave 4
2 Jimmy 4
2 Test 4
Now my problem is that i have to query the cdc table and get all the rows that are the latest one for all the members (most recent updated value). for example the query would return
MemberID LastName __$operation
1 Dave 4
2 Test 4
In addition to the _$operation column, there are also the _$start_lsn and __$seq_val columns. Ordering by those two should get you there.
You can not only determine by _$operations for CDC. If you want to do it correct use other column fields that are:
__$start_lsn
__$end_lsn
__$seqval
__$update_mask
So I'm not 100% sure I understand what you are asking for, but if you need the latest values for all the members in the table then ignore the CDC table and just query the table itself as this is where all the latest values are afterall.
If you need to see the latest values for all the members that have been changed within a certain time period, then you should use the cdc.fn_cdc_get_net_changes_(capture_instance) function, detailed here:
cdc.fn_cdc_get_net_changes
This allows you to specify a start and end date for the capture period (via the sys.fn_cdc_map_time_to_lsn function which allows you to map the LSNs to actual times) and it will then output the net changes to the table within this period.
The cdc.fn_cdc_get_net_changes_(capture_instance) changes is generated depending on your table name, so as you have not specified what this is, I have called it dbo_members, please change as required, here is an example of how you can get a list of the latest values for all changed members within the last day using the functions detailed above:
DECLARE #begin_time DATETIME ,
#end_time DATETIME ,
#begin_lsn BINARY(10) ,
#end_lsn BINARY(10);
SELECT #begin_time = GETDATE() - 1 ,
#end_time = GETDATE();
SELECT #begin_lsn = sys.fn_cdc_map_time_to_lsn('smallest greater than',
#begin_time);
SELECT #end_lsn = sys.fn_cdc_map_time_to_lsn('largest less than or equal',
#end_time);
SELECT [MemberID] ,
[LastName]
FROM cdc.fn_cdc_get_net_changes_dbo_members(#begin_lsn, #end_lsn, 'all')
GO
As per steoleary you can simply check the data table for the latest values and ignore CDC altogether, but if you are looking to what changed with values from and to, then you will need to refer to the _$operation values 3 (deleted) and 4 (inserted) values in conjunction with the __$start_lsn. The inserted and deleted values correspond to those tables you would use when writing triggers btw.
To just see what column values changes as a precursor to actually evaluating those values, then you can use the __$update_mask column, tied into the cdc.captured_columns table which will provide you the actual column names, by implementing the sys.fn_cdc_is_bit_set(captured_columns.column_ordinal, __$update_mask) function where the result = 1.
Welcome to the wacky world of CDC and the copious amounts of late nights and caffeine hits required to even attempt to master it!
If your cdc system table name is cdc.dbo_demo_ct then with following query you will get desired result:
SELECT *
FROM (SELECT Row_number() OVER (partition BY a.MemberID ORDER BY b.tran_end_time DESC) t,
*
FROM cdc.dbo_demo_ct a
INNER JOIN cdc.lsn_time_mapping b
ON a.__$start_lsn = b.start_lsn) T
WHERE T.t = 1

TSQL Inserting records and track ID

I would like to insert records in a table below (structure of table with example data). I have to use TSQL to achieve this:
MasterCategoryID MasterCategoryDesc SubCategoryDesc SubCategoryID
1 Housing Elderly 4
1 Housing Adult 5
1 Housing Child 6
2 Car Engine 7
2 Car Engine 7
2 Car Window 8
3 Shop owner 9
So for example if I enter in a new record with MasterCategoryDesc = 'Town' it will insert '4' in MasterCategoryID with the respective SubCategoryDesc + ID.
CAN I SIMPLIFY THIS QUESTION BY REMOVING THE SubCategoryDesc and SubCategoryID columns. How can I achieve this now just with the 2 columns MasterCategoryID and MasterCategoryDesc
INSERT into Table1
([MasterCategoryID], [MasterCategoryDesc], [SubCategoryDesc], [SubCategoryID])
select TOP 1
case when 'Town' not in (select [MasterCategoryDesc] from Table1)
then (select max([MasterCategoryID])+1 from Table1)
else (select [MasterCategoryID] from Table1 where [MasterCategoryDesc]='Town')
end as [MasterCategoryID]
,'Town' as [MasterCategoryDesc]
,'owner' as [SubCategoryDesc]
,case when 'owner' not in (select [SubCategoryDesc] from Table1)
then (select max([SubCategoryID])+1 from Table1)
else (select [SubCategoryID] from Table1 where [SubCategoryDesc]='owner')
end as [SubCategoryID]
from Table1
SQL FIDDLE
If you want i can create a SP too. But you said you want an T-SQL
This will take three steps, preferably in a single Stored Procedure. Make sure it's within a transaction.
a) Check if the MasterCategoryDesc you are trying to insert already exists. If so, take its ID. If not, find the highest MasterCategoryID, increase by one, and save it to a variable.
b) The same with SubCategoryDesc and SubCategoryID.
c) Insert the new record with the two variables you created in steps a and b.
Create a table for the MasterCategory and a table for the SubCategory. Make an ___ID column for each one that is identity (1,1). When loading, insert new rows for nonexistent values and then look up existing values for the INSERT.
Messing around with finding the Max and looking up data in the existing table is, in my opinion, a recipe for failure.