update multiple records with different where conditions - sql

I have two tables which are used to deal with identifier changes.
So the table below is where identifiers are logged.
tblNewIds
DateFrom OldId NewId
2017-06-02 ABC ABB
2017-04-21 XYZ JHG
The next table is where all the daily sales are stored.
tblSales
DateSale Id
2017-01-01 ABC
2017-01-01 XYZ
2017-01-02 ABC
2017-01-02 XYZ
...
2017-06-20 ABC
2017-06-20 XYZ
I want a query to update tblSales such that from 2017-04-21 any Id that equals XYZ changes to JHG & for from 2017-06-02 change ABC to ABB.
I know how I can do this for one record at a time with the update statement below but I would like to know how to do both at once?
update tblSales
set Id = 'ABB'
where Id = 'ABC' and DateSale >= '2017-06-02'

Assuming that ids are not chained, then you can do:
update s
set id = ni.NewId
from tblSales s join
tblNewIds ni
on s.id = ni.oldId and s.DateSale >= ni.DateFrom;
I would be cautious about making the change in the data, though. Losing the information about the original id could have unexpected side-effects.
If the ids can change more than once, I would suggest just running the update until there are no more changes. Although you can construct the correct id at a given point in time using a recursive CTE, it is a lot more work for a one-time effort.

You might be able to slightly modify your current update to use a CASE expression which can cover both types of update in a single statement.
update tblSales
set Id = case when Id = 'ABC' and DateSale >= '2017-06-02' then 'ABB'
when Id = 'XYZ' and DateSale >= '2017-04-21' then 'JHG' END
where (Id = 'ABC' and DateSale >= '2017-06-02') or
(Id = 'XYZ' and DateSale >= '2017-04-21')

UPDATE tblSales
SET id= CASE
WHEN (Id = 'ABC' and DateSale >= '2017-06-02') THEN 'ABB'
WHEN (Id = 'XYZ' and DateSale >= '2017-04-21') THEN 'JHG'
END ;

Related

Is there SQL Logic to reduce type 2 table along a dimension

I have a slowly changing type 2 price change table which I need to reduce the size of to improve performance. Often rows are written to the table even if no price change occurred (when some other dimensional field changed) and the result is that for any product the table could be 3-10x the size it needs to be if it were including only changes in price.
I'd like to compress the table so that it only has contains the first effective date and last expiration date for each price until that price changes that can also
Deal with an unknown number of rows of the same price
Deal with products going back to an old price
As an example if i have this raw data:
Product
Price Effective Date
Price Expiration Date
Price
123456
6/22/18
9/19/18
120
123456
9/20/18
11/8/18
120
123456
11/9/18
11/29/18
120
123456
11/30/18
12/6/18
120
123456
12/7/18
12/19/18
85
123456
12/20/18
1/1/19
85
123456
1/2/19
2/19/19
85
123456
2/20/19
2/20/19
120
123456
2/21/19
3/19/19
85
123456
3/20/19
5/22/19
85
123456
5/23/19
10/10/19
85
123456
10/11/19
6/19/19
80
123456
6/20/20
12/31/99
80
I need to transform it into this:
Product
Price Effective Date
Price Expiration Date
Price
123456
6/22/18
12/6/18
120
123456
12/7/18
2/19/19
85
123456
2/20/19
2/20/19
120
123456
2/21/19
10/10/19
85
123456
10/11/19
12/31/99
80
You can first find the intervals where the price does not change, and then group on those intervals:
with to_r as (select row_number() over (order by (select 1)) r, t.* from data_table t),
to_group as (select t.*, (select sum(t1.r < t.r and t1.price != t.price) from to_r t1) c from to_r t)
select t.product, min(t.effective), max(t.expiration), max(t.price) from to_group t group by t.c order by t.r;
Output:
Product
Price Effective Date
Price Expiration Date
Price
123456
6/22/18
12/6/18
120
123456
12/7/18
2/19/19
85
123456
2/20/19
2/20/19
120
123456
2/21/19
10/10/19
85
123456
10/11/19
12/31/99
80
This is a type of gaps-and-islands problem. I would recommend reconstructing the data, saving it in a temporary table, and then reloading the existing table.
The code to reconstruct the data is:
select product, price, min(effective_date), max(expiration_date)
from (select t.*,
sum(case when prev_expiration_date = effective_date - interval '1 day' then 0 else 1 end) over (partition by product order by effective_date) as grp
from (select t.*,
lag(expiration_date) over (partition by product, price order by effective_date) as prev_expiration_date
from t
) t
) t
group by product, price, grp;
Note that the logic for date arithmetic varies depending on the database.
Save this result into a temporary table, temp_t or whatever, using select into, create table as, or whatever your database supports.
Then empty the current table and reload it:
truncate table t;
insert into t
select product, price, effective_date, expiration_date
from temp_t;
Notes:
Validate the data before using truncate_table!
If there are triggers or columns with default values, you might want to be careful.
It sounds like you are asking for a temporal schema? Where for a given date you can know the price of an asset?
This is done with two tables; price_current and price_history.
price_id
item_id
price
rec_created
1
1
100
'2015-04-18'
price_id
item_id
from
to
price
1
1
'2001-01-01'
'2004-05-01'
114
1
1
'2004-05-01'
'2015-04-18'
102
i.e. for any item, you can ascertain the date it was set without polluting your "current" table. For this to work effectively you will need to have UPDATE triggers on your current_table. When you update a record you insert into the history table the details and the period it was valid from.
CREATE OR REPLACE TRIGGER trg_price_current_update
AS
BEGIN
INSERT INTO price_history(price_id, item_id, from, to, price)
SELECT price_id, item_id, rec_created, GETDATE(), price
FROM rows_updated
END
Now you have a distinction between current and historical, without your current table (presumably the busier table) getting out of hand because of maintaining historical state. Hope i understood the question.
To ignore 'dummy' updates, just alter the trigger to ignore empty changes (if that's not handled by the DBMS anyway). Tbh, this should and could be done application side easily enough, but to manage it via the trigger:
CREATE OR REPLACE TRIGGER trg_price_current_update
AS
BEGIN
INSERT INTO price_history(price_id, item_id, from, to, price)
SELECT price_id, item_id, rec_created, GETDATE(), price
FROM rows_updated u
INNER JOIN price_current ON u.price_id = p.price_id
WHERE u.price <> p.price
END
i.e. rows_updated contains the record from the update, we insert into the history table the previous row, providing the previous row's price is different from the current row's price.
(edited to include new trigger. I also changed the date held in rec_created, this must be the date the row is created, not the first instance that product had a price assigned to it. that was a mistake. Regarding the dates, I am lazy to put the full DD-MM-YYYY hh:mm:ss:zzz, but that would generally be useful in between queries)
What you are asking for is a versioning system. Many RDBMS platforms implement support for this out of the box (it's a SQL standard), which may be suitable, depending on your requirements.
You have not tagged a specific platform so it's not possible to be specific to your situation. I use the concept of system versioning regularly in MS Sql Server, where you would implement it thus:
assuming schema "history" exists,
alter table dbo.MyTable add
ValidFrom datetime2 generated always as row start hidden constraint DF_MyTableSysStart default sysutcdatetime(),
ValidTo datetime2 generated always as row end hidden constraint DF_MyTableSysEnd default convert(datetime2, '9999-12-31 23:59:59.9999999'),
period for system_time (ValidFrom, ValidTo);
end
alter table MyTable set (system_versioning = on (history_table = History.MyTable));
create clustered index ix_MyTable on History.MyTable (ValidTo, ValidFrom) with (data_compression = page,drop_existing=on) on History;
A number of syntax extensions exist to aid querying the temporal data for example to find historical data at a point in time.
Alternatively, to utilise a single table but handle the duplication, you could create an instead of trigger.
the idea here is that the trigger gets to intercept the data before it is inserted, where you can check to see of the value is different to the last value and discard or insert as appropriate.
something along the lines of:
WITH keeps AS
(
SELECT p.product_id, p.effective, p.expires, p.price, CASE WHEN EXISTS(SELECT 1 FROM prices p1 WHERE p1.effective = DATEADD(DAY, p.exires, 1) AND p1.price <> p.price) THEN 1 ELSE 0 END AS has_after, CASE WHEN EXISTS(SELECT 1 FROM prices p1 WHERE p1.expires = DATEADD(DAY, p.effective, -1) AND p1.price <> p.price) THEN 1 ELSE 0 END AS has_before
FROM prices p
)
SELECT * FROM keeps
WHERE has_after = 1
OR has_before = 1
UNION ALL
SELECT p.product_id, p.effective, p.exires, p.price
FROM prices p
WHERE p.effective = (SELECT MIN(effective) FROM prices p1 WHERE p1.product_id = p.product_id)
What's it doing:
Find all the entries where there exists another entry whose effective date is that of the previous entry's expiry date + 1, and the price of that new entry is different. This gives us all the actual changes in price. But we miss the first price entry, so we simply include that in the results.
e.g.:
product_id
effective
expires
price
has_before
has_after
123456
6/22/18
9/19/18
120
0
0
123456
9/20/18
11/8/18
120
0
0
123456
11/9/18
11/29/18
120
0
0
123456
11/30/18
12/6/18
120
0
1
123456
12/7/18
12/19/18
85
1
0
123456
12/20/18
1/1/19
85
0
0
123456
2/1/19
2/19/19
85
0
1
123456
2/20/19
2/20/19
120
1
1
123456
2/21/19
3/19/19
85
1
0

id where column has two conditions

I have a table of customers that join and leave a company
ID ActiveFrom ActiveTo
I have for example a where clause that has
where ActiveFrom <= '20170101'
and Activeto < '20170201'
but some times a customer has decided to re-join the company a few days later because of a deal.
example customer:
ID ActiveFrom ActiveTo
1 2000-01-01 2017-01-03
1 2017-01-28 Null
2 2000-01-01 2017-01-06
I want to exclude the customers that do this, from the customers that leave the company but come back
so I want id 2 to be returned only
please help
You can try this.
SELECT * FROM Customer C1
WHERE C1.ActiveFrom <= '20170101'
AND C1.Activeto < '20170201'
AND NOT EXISTS ( SELECT * FROM Customer C2
WHERE C1.ID = C2.ID
AND C2.ActiveTo IS NULL )
You could use a subquery on WHERE statement:
SELECT *
FROM tablename
WHERE (SELECT id FROM tablename WHERE activeTo is null) <> id
GROUP By id
You can find more information here:
https://www.techonthenet.com/sql_server/subqueries.php

sas/sql logic needed

I have a data with SSN and Open date and have to calculate if a customer has opened 2 or more accounts within 120 days based on the open_date field. I know to use INTCK/INTNX functions but it requires 2 date fields, not sure how to apply the same logic on a single field for same customer.Please suggest.
SSN account Open_date
xyz 000123 12/01/2015
xyz 112344 11/22/2015
xyz 893944 04/05/2016
abc 992343 01/10/2016
abc 999999 03/05/2016
123 111123 07/16/2015
123 445324 10/12/2015
You can use exists or join:
proc sql;
select distinct SSN
from t
where exists (select 1
from t t2
where t2.SSN = t.SSN and
t2.open_date between t.open_date and t.open_date + 120
);
I'd do it using JOIN :
proc sql;
create table want as
select *
from have
where SSN in
(select a.SSN
from have a
inner join have b
on a.SSN=b.SSN
where intck('day', a.Open_date, b.Open_Date)+1 < 120)
;
quit;
Just a slightly different solution here - use the dif function which calculates the number of days between accounts being open.
proc sort data=have;
by ssn open_date;
run;
data want;
set have;
by ssn;
days_between_open = dif(open_date);
if first.ssn then days_between_open = .;
*if 0 < days_between_open < 120 then output;
run;
Then you can filter the table above as required. I've left it commented out at this point because you haven't specified how you want your output table.

SQL query where clause for reporting

Let's say I have this table with the following data:
Service_ID Cust_ID Service_Date Next_Service_Date
-----------------------------------------------------
1 15 2016-01-1 2016-01-31
2 21 2016-01-1 2016-01-31
3 15 2016-01-31 2016-03-1
I need a condition to check if Next_Service_Date is found in Service_Date for each customer not the whole table.
For example customer with id = 15 and Service_ID = 3, you can see Service_Date was made on 2016-01-31
Same as the Next_Service_Date with Service_ID = 1
So output of the query should be
Service_ID Cust_ID Service_Date Next_Service_Date
------------------------------------------------------
2 21 2016-01-1 2016-01-31
I hope I made everything clear.
Note why I want to show record # 2 because that customer has no records in Service_Date that matches the date in Next_Service_Date
If I understand correctly, you want not exists:
select bt.*
from belowtable bt
where not exists (select 1
from belowtable bt2
where bt2.cust_id = bt.cust_id and
(bt2.next_service_date = bt.service_date or
bt2.service_date = bt.next_serice_date
)
);
Edited -- I think I understand your question now -- you want to find all records that should have a next service date scheduled but do not? If so, a combination semi-join and anti-join would do it.
If I'm off the mark, please let me know where I misunderstood.
select
t1.*
from
MyTable t1
where exists (
select null
from MyTable t2
where
t1.Next_Service_Date = t2.Service_Date
)
and not exists (
select null
from MyTable t2
where
t1.Cust_Id = t2.Cust_Id and
t1.Next_Service_Date = t2.Service_Date
)

SQL Server 2008 - need help on a antithetical query

I want to find out meter reading for given transaction day. In some cases there won’t be any meter reading and would like to see a meter reading for previous day.
Sample data set follows. I am using SQL Server 2008
declare #meter table (UnitID int, reading_Date date,reading int)
declare #Transactions table (Transactions_ID int,UnitID int,Transactions_date date)
insert into #meter (UnitID,reading_Date,reading ) values
(1,'1/1/2014',1000),
(1,'2/1/2014',1010),
(1,'3/1/2014',1020),
(2,'1/1/2014',1001),
(3,'1/1/2014',1002);
insert into #Transactions(Transactions_ID,UnitID,Transactions_date) values
(1,1,'1/1/2014'),
(2,1,'2/1/2014'),
(3,1,'3/1/2014'),
(4,1,'4/1/2014'),
(5,2,'1/1/2014'),
(6,2,'3/1/2014'),
(7,3,'4/1/2014');
select * from #meter;
select * from #Transactions;
I expect to get following output
Transactions
Transactions_ID UnitID Transactions_date reading
1 1 1/1/2014 1000
2 1 2/1/2014 1010
3 1 3/1/2014 1020
4 1 4/1/2014 1020
5 2 1/1/2014 1001
6 2 3/1/2014 1001
7 3 4/1/2014 1002
Your SQL Query to get your desired out put will as following:
SELECT Transactions_ID, T.UnitID, Transactions_date
, (CASE WHEN ISNULL(M.reading,'') = '' THEN
(
SELECT MAX(Reading) FROM #meter AS A
JOIN #Transactions AS B ON A.UnitID=B.UnitID AND A.UnitID=T.UnitID
)
ELSE M.reading END) AS Reading
FROM #meter AS M
RIGHT OUTER JOIN #Transactions AS T ON T.UnitID=M.UnitID
AND T.Transactions_date=M.reading_Date
I can think of two ways to approach this - neither of them are ideal.
The first (and slightly better) way would be to create a SQL Function that took the Transactions_date as a parameter and returned the reading for Max(Reading_date) where reading_date <= transactions_date. You could then use this function in a select statement against the Transactions table.
The other approach would be to use a cursor to iterate through the transactions table and use the same logic as above where you return the reading for Max(Reading_date) where reading_date <= transactions_date.
Try the below query:
Please find the result of the same in SQLFiddle
select a.Transactions_ID, a.UnitID, a.Transactions_date,
case when b.reading IS NULL then c.rd else b.reading end as reading
from
Transactions a
left outer join
meter b
on a.UnitID = b.UnitID
and a.Transactions_date = b.reading_Date
inner join
(
select UnitID,max(reading) as rd
from meter
group by UnitID
) as C
on a.UnitID = c.UnitID