SQL Server Pre and Post Deployment Scripts - sql

Bit of a SQL newbie so hopefully explain this ok:
So I have a USCars and a USCars_AUD (for Audit) table. Then I have a Cars and a Cars_AUD table.
In my USCars and USCars_AUD table I had a column Description. I am deleting this column from the table and adding it to the Main Cars and Cars_AUD table.
We have a database project to do this and there is Pre and Post Deployment scripts. The Id column in the USCars and USCars_AUD will be the same Id as the Cars and Cars_AUD table has.
So I have updated my schema objects in the database project to reflect that USCars and a USCars_AUD will have description deleted and that it is added to Cars and Cars_AUD.
I am struggling a bit with the query to run pre-deployment and post-deployment. Ideally I want an IF type condition - to say only run if USCars table contains description column. (not sure if that is possible)
I then need to copy all the data from both tables into temp tables in pre-deploy
-- need an If conditional to only run if USCars table contains Description
-- then begin /end
print 'Moving USCars and Cars and Audit tables and related data to temp tables'
exec sp_executesql N'
select * into Upgrade_USCars from USCars
select * into Upgrade_USCars_AUD from USCars_AUD
'
Now Cars table has some extra info in it that is not in USCars so would I need to select everything from it into a temp table joined by the Id? So the result will be I want my final Cars and Cars_AUD table to contain everything it had in it originally but where it had a Description in the USCars and USCars_AUD table I want to copy across the description to the row with the same Id
In the Post Deploy then would I insert the values from this Joined temp table back into Cars Table and then Drop the temp tables

Syntax to check for column existence is:
IF EXISTS (SELECT * FROM sys.columns
WHERE object_id = OBJECT_ID('YourTableName')
AND name = 'YourColumnName')
BEGIN
-- your logic goes here
END

Related

SQL insert from select, but set some values by hand

Let's say that I have table with A LOT of columns. I have one column with primary key that has autoincrement set to 1. I want to insert a new row and in this new row I have following requirements:
The row must have generated ID
All non-specified columns have to be copied from row with id='9999'
I have to be able to set some values of columns by hand (for example columns name and age
I have tried:
Insert Into demo_table
Select * From demo_table Where id='9999';
However, I get this error:
An explicit value for the identity column in table 'demo_table' can only be specified when a column list is used and IDENTITY_INSERT is ON.
What do I need:
I want to duplicate a row -> let the id be set by database (I have PK and autoincrement configured) -> set some columns by hand -> have other column's values duplicated, without specifying column names (as I have a lot of columns and their names could change in future.)
Form of solution:
I would prefer if I was able to achive this using only one query. If necessary, I have stored procedures available.
My question:
Is this even possible? How could I achive such query/procedure?
There is a way to build sql query by table schema:
USE <databaseName>
DECLARE
#SourceTableName NVARCHAR(255) = <TableName>,
#SqlQuery NVARCHAR(MAX)
IF OBJECT_ID('tempdb.dbo.#IdentityCols', 'U') IS NOT NULL
DROP TABLE #IdentityCols;
CREATE TABLE #IdentityCols(
ColumnName NVARCHAR(255)
)
INSERT INTO #IdentityCols
SELECT
--TABLE_NAME,
COLUMN_NAME
FROM
INFORMATION_SCHEMA.COLUMNS
WHERE
COLUMNPROPERTY(object_id(TABLE_SCHEMA+'.'+TABLE_NAME), COLUMN_NAME, 'IsIdentity') = 1 AND TABLE_NAME = #SourceTableName
UNION
SELECT
--o.name,
c.name
FROM
sys.objects o inner join
sys.columns c on o.object_id = c.object_id
WHERE
c.is_identity = 1 AND o.name = #SourceTableName
--STRING_AGG in SQL SERVER 2017 and greater. As aproach for early versions is cursor or loop
SELECT #SqlQuery = 'SELECT ' + STRING_AGG(COLUMN_NAME, ',') + ' FROM ' + #SourceTableName
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME=#SourceTableName AND COLUMN_NAME NOT IN (Select ColumnName FROM #IdentityCols)
exec sp_executesql #SqlQuery
For more information you can see this questions:
How can I show the table structure in SQL Server query?
How do you determine what SQL Tables have an identity column programmatically
How to concatenate text from multiple rows into a single text string in SQL Server
SQL Server Loop through Table Rows without Cursor
SQL Server loop - how do I loop through a set of records
For anyone interested, how I've solved this problem:
After I've read your comments (thanks btw) and some threads online, I've realized why I cannot do what I asked. However, I've come seen solution to similar problem somewhere, where they wanted to select * except one specific column, they solved it like this:
copied the entire table
selected from there.
I've come up with similar solution to my problem
-- check data, remove after testing
select * from demo_table order by ID desc;
-- create table with one column I want to duplicate
select * into Temp_demo_table from demo_table where ID=9999;
-- drop id, so it does not get included in the inter-table insert
alter table Temp_demo_table
drop column ID;
-- update columns that I need to modify, doesn't have to have WHERE clause, becuase there's only one row there
update Temp_demo_table set MyCustomProperty='abc', name=NULL, age=NULL
-- insert the desired and edited row
insert into demo_table
select * from Temp_demo_table;
-- drop the temp table
drop table Temp_demo_table;
-- check data, remove after testing
select * from demo_table order by ID desc;
I realize how inefficient this is, however the function (on my api) executing this command will not be called so often (max 100 times per day). I believe that this query could be optimized, however I do not have sufficient knowledge to do it at this moment (100% going to put it in my TODO :D).
Edit-1:
Just found out that you can write queries in oracle db like this:
(select * from demo_table) - (select name, age from demo_table)
I currentlly don't know if I can apply this to sql server, however as soon as I have an access to mssql, I'll try it out and keep this answear updated!

DROP TABLE by CONCAT table name with VALUE from another SELECT [SQLite]

I was wondering how can I drop table with concat by selecting value from other table.
This is what I am trying to figure out:
DROP TABLE SELECT 'table' || (select value from IncrementTable)
So basically table name is table6 for example.
Goal is: eg.. DROP TABLE table6
You can't do this directly. Table and column names have to be known when the statement is byte-compiled; they can't be generated at runtime. You have to figure out the table name and generate the appropriate statement string in the program using the database, and execute it.

Need help to optimize my stored procedure

I need help optimizing my stored procedure. This is for our fact table, and currently the stored procedure truncates the table, and then loads the data back in. I want to get rid of truncating and instead append new rows or delete rows by a last_update column which currently does not exist. There also is a last_update table with one column, which changes at every stored procedure run, but I'd rather the last_update be a column in the table itself, rather than a separate column.
I've created a trigger that should update the last_updated column with the current date when the stored procedure runs, but I would also like to get rid of truncating and instead append/delete rows as well. The way the stored procedure is currently structured is making it difficult for me to figure out how best to do it.
The stored procedure begins by adding data into 2 temp tables, then adds the data from the two temp tables into a 3rd temp table, then truncates the current FACT TABLE and then the 3rd temp table finally inserts into the FACT table.
--CLEAR LAST UPDATE TABLE
TRUNCATE TABLE ADM.LastUpdate;
--SET NEW LAST UPDATE TIME
INSERT INTO ADM.LastUpdate(TABLE_NAME, UPDATE_TIME)
VALUES('FactBP', CONVERT(VARCHAR, GETDATE(), 100)+' (CST)');
--CHECK TO SEE IF TEMP TABLES EXISTS THEN DROP
IF OBJECT_ID('tempdb.dbo.#TEMP_CARTON', 'U') IS NOT NULL
DROP TABLE #TEMP_CARTON;
IF OBJECT_ID('tempdb.dbo.#TEMP_ORDER', 'U') IS NOT NULL
DROP TABLE #TEMP_ORDER;
--CREATE TEMP TABLES
SELECT *
INTO #TEMP_CARTON
FROM [dbo].[FACT_CARTON_V];
SELECT *
INTO #TEMP_ORDER
FROM [dbo].[FACT_ORDER_V];
--CHECK TO SEE IF DATA EXISTS IN #TEMP_CARTON AND #TEMP_ORDER
IF EXISTS(SELECT * FROM #TEMP_CARTON)
AND EXISTS(SELECT * FROM #TEMP_ORDER)
--CODE HERE joins the data from #TEMP_CARTON and #TEMP ORDER and puts it into a 3rd temp table #TEMP_FACT.
--CLEAR ALL DATA FROM FACTBP
TRUNCATE TABLE dbo.FactBP;
--INSERT DATA FROM TEMP TABLE TO FACTBP
INSERT INTO dbo.FactBP
SELECT
[SOURCE]
,[DC_ORDER_NUMBER]
,[CUSTOMER_PURCHASE_ORDER_ID]
,[BILL_TO]
,[CUSTOMER_MASTER_RECORD_TYPE]
,[SHIP_TO]
,[CUSTOMER_NAME]
,[SALES_ORDER]
,[ORDER_CARRIER]
,[CARRIER_SERVICE_ID]
,[CREATE_DATE]
,[CREATE_TIME]
,[ALLOCATION_DATE]
,[REQUESTED_SHIP_DATE]
,[ADJ_REQ_SHIP]
,[CANCEL_DATE]
,[DISPATCH_DATE]
,[RELEASED_DATE]
,[RELEASED_TIME]
,[PRIORITY_ORDER]
,[SHIPPING_LOAD_NUMBER]
,[ORDER_HDR_STATUS]
,[ORDER_STATUS]
,[DELIVERY_NUMBER]
,[DCMS_ORDER_TYPE]
,[ORDER_TYPE]
,[MATERIAL]
,[QUALITY]
,[MERCHANDISE_SIZE_1]
,[SPECIAL_PROCESS_CODE_1]
,[SPECIAL_PROCESS_CODE_2]
,[SPECIAL_PROCESS_CODE_3]
,[DIVISION]
,[DIVISION_DESC]
,[ORDER_QTY]
,[ORDER_SELECTED_QTY]
,[CARTON_PARCEL_ID]
,[CARTON_ID]
,[SHIP_DATE]
,[SHIP_TIME]
,[PACKED_DATE]
,[PACKED_TIME]
,[ADJ_PACKED_DATE]
,[FULL_CASE_PULL_STATUS]
,[CARRIER_ID]
,[TRAILER_ID]
,[WAVE_NUMBER]
,[DISPATCH_RELEASE_PRIORITY]
,[CARTON_TOTE_COUNT]
,[PICK_PACK_METHOD]
,[RELEASED_QTY]
,[SHIP_QTY]
,[MERCHANDISE_STYLE]
,[PICK_WAREHOUSE]
,[PICK_AREA]
,[PICK_ZONE]
,[PICK_AISLE]
,EST_DEL_DATE
FROM #TEMP_FACT;
Currently, since I've added the last_updated column into my FACT TABLE and created a trigger, I don't actually pass any value via the stored procedure for it, so I get an error
An object or column name is missing or empty.
I am not sure as to where I'm supposed to pass any value for the LAST_UPDATED column.
Here is the trigger I've created for updating the last_updated column:
CREATE TRIGGER last_updated
ON dbo.factbp
AFTER UPDATE
AS
UPDATE dbo.factbp
SET last_updated = GETDATE()
FROM Inserted i
WHERE dbo.factbp.id = i.id
The first thing I would try is to create primary keys on the two temp tables #TEMP_CARTON and #TEMP_ORDER and use the intersect command to get the rows that are common to both tables:
select * from #TEMP_CARTON
intersect
SELECT * FROM #TEMP_ORDER
Figured out the answer. I just had to put "null" for the last_updated value during Insert, and then the Trigger took care of adding the timestamp on its own.

SQL update records with join table

Currently I need to move three columns from table A to table B. And I am using the update join table script to copy the existing data to the new columns. Afterwards the old column at table A will be drop.
Alter table NewB add columnA integer
Alter table NewB add columnB integer
Update NewB
Set NewB.columnA = OldA.columnA, NewB.columnB = OldA.columnB
From NewB
Join OldA on NewB.ID = OldA.ID
Alter table OldA drop column columnA
Alter table OldA drop column columnB
These script will add new columns and update the existing data from the old table to the newly created columns. Then remove the old columns.
But due to system structure, I will required to run SQL Script for more than one times to makes sure the database is up to date.
Although I did If (Columns Exist) Begin (Alter Add, Update, Alter Drop) End to ensure the existence of columns required. But when the script runs at the next time, it will hit error that says the columns was not found from the old table in the "update" query. Because the columns were dropped when the script run at the first time.
Is there other ways to solve?
you will not be able to update using join, But you can do like this :
Update NewB set NewB.columnA = (select OldA.columnA from OldA where NewB.ID = OldA.ID);
Update NewB set NewB.columnB = (select OldA.columnB from OldA where NewB.ID = OldA.ID);
I don't know which database you are using, in database there are some system tables, from where you can get whether the column does exist in table or not, like in oracle, All_TAB_COLUMNS contains the information of all the columns of tables, so you can hit that table like below :
select 1 from ALL_TAB_COLUMNS where TABLE_NAME='OldA' and COLUMN_NAME in ('columnA','columnB');
if resulting records are empty that means specified columns are not present in the table so you can skip your queries.
There must be something wrong with your is column exists check. I have similar DDL and DML operations many times. As you did not show how you are checking column existence I am not able to tell you what's wrong.
Anyway, you are adding a new column to a table. We can check if such column exists, if not - run the script, if yes- skip the script. And here is the check:
IF EXISTS(SELECT 1 FROM [sys].[columns] WHERE OBJECT_ID('[dbo].[NewB]') = [object_id] AND [name] = 'columnA')
BEGIN
BEGIN TRANSACTION;
....
COMMIT TRANSACTION;
END;

Create table with checksum of all tables in a database?

I'm trying to figure out how to determine if a table has been affected by a number of processes that run in sequence, and need to know what the state of the table is before and after each runs. What I've been trying to do is run some SQL before all the processes run that saves a before checksum of every table in the db to a table, then running it again when each ends and updating the table row with an after checksum. After all the processes are over, I compare the checksums and get all rows where before <> after.
Only problem is that I'm not the best guy for SQL, and am a little lost. Here's where I'm at right now:
select checksum_agg(binary_checksum(*)) from empcomp with (nolock)
create table Test_CheckSum_Record ( TableName varchar(max), CheckSum_Before int, CheckSum_After int)
SELECT name into #TempNames
FROM sys.Tables where is_ms_shipped = 0
And the pseudocode for what I want to do is something like
foreach(var name in #TempNames)
insert into Test_CheckSum_Record(name, ExecuteSQL N'select checksum_agg(binary_checksum(*)) from ' + name + ' with (nolock)', null)
But how does one do this?
Judging by the comments you need to create a trigger that handles all CRUD operations and just places a flag
Syntax is
Create TRIGGER [TriggerName] ON [TableName]
AFTER UPDATE, AFTER Delete, AFTER UPDATE
In the trigger you can do a
select CHECKSUM_AGG([Columns you want to compare against])
from [ParentTable] store that value in a variable and check it against the checksum table before column. If it does not exist you add a new entry with the DELETED tables checksum_AGG value as the before entry
Please note the choice not to use the inserted table is just preference for me on calculated columns
I will edit later when I have more time to add code