SQL: Removing rows from Select Statement on Insert - sql

I have a stored procedure that returns a table of items that are missing for a specified ID. For example: exec getMissingItems '1' will return a list of the items that are missing for ID = 1 such as : 'A', 'B', 'C'. Now I am tracking when these items are received so they are stored into the database as 'A Received' & B received and so forth. I want to be able to only show the items that have not yet been received, for instance if I call exec getMissingItems '1' now, it will only return 'C'.
All of the information is being stored into a database table
TABLE1
ID | Event
1 | A Missing
1 | B Missing
1 | C Missing
1 | A Received
1 | B Received
So currently getMissingItems is simply calling:
SELECT Event FROM TABLE1 WHERE Event LIKE '%Missing'
Which returns a table of the items missing, but still returns them even if they are missing
RETURNED TABLE
Event
A Missing
B Missing
C Missing

This should work for you. You need to left join based on the ID and the parsed identifier from the event. Then find the rows that are unmatched that have "Missing" in the event.
Here is a SQL fiddle link to this example -- http://sqlfiddle.com/#!3/2d668/1/0
create table #table1
(
ID int,
[Event] varchar(100)
);
go
insert into #table1 values (1, 'A Missing');
insert into #table1 values (1, 'B Missing');
insert into #table1 values (1, 'C Missing');
insert into #table1 values (1, 'A Received');
insert into #table1 values (1, 'B Received');
go
with cte as
(
select id, [Event], substring([Event], 1, patindex('% %', [Event]) -1) as ItemId
from #table1
)
select a.Event
from cte a
left join cte b on
a.id = b.id and -- IDs must match
a.ItemId = b.ItemId and -- the ItemId from the parsed Event must match on the left side
a.Event like '%Missing' and -- items that match have a 'Missing' on the "left"
b.Event like '%Received' -- items that match have a 'Received' on the "right"
where b.ID is null -- rows that did not match on the right
and a.Event like '%Missing' -- row has missing in the event on the left side
drop table #table1;
go

Edited answer
See if this works a little better for you...
CREATE TABLE #temp (id, event, missing, recieved)
INSERT INTO #temp
SELECT Id, Event, case when event like '%missing%' then 1 else 0 END
CASE WHEN event like '%recieved%' then 1 else 0
FROM TABLE1
SELECT Event from Table1 t join #temp tt on t.id = tt.id
WHERE missing =1 and recieved = 0

Related

Select UNIQUE, NOT DISTINCT values

I am trying to select values from a table that are not duplicates - for example, with the following input set, I would like to select only the values in Column 1 that don't have a duplicated value in Column 2
Column 1 Column 2
A X
B X
C Y
D Y
E Z
Resulting in
Column 1 Column 2
E Z
This is made harder by my having a character limit for my SQL statement, and my having to join a couple of tables in the same query.
My existing statement is here, and this is where I am stuck.
SELECT d.o_docguid, d.o_itemdesc
FROM dms_doc d
INNER JOIN
(SELECT s.o_itemno as si, s.o_projectno as sp, t.o_itemno as ti, t.o_projectno as tp
FROM env_bs1192_1 s, env_bs1192_2 t
WHERE s.TB_FILE_ID = t.TB_FILE_ID) as r
ON (si = d.o_itemno AND sp = d.o_projectno)
OR (ti = d.o_itemno AND tp = d.o_projectno)
Results look like
o_docguid o_itemdesc
aguid adescription
bguid adescription
cguid bdescription
I want to filter this list out such that all that remains are the unique descriptions and their associated guid (i.e. only the rows that have specifically a single unique entry in the description, or put another way, if there is a duplicate, throw both away - in this instance, cguid and bdescription should be the only results).
The last challenge, which I still haven't solved, is that this SQL statement needs to fit into a character limit of 242 characters.
Taking the first part as a question, the answer might be:
declare #Table table (Column1 char(1), Column2 char(1));
insert into #Table values
('A', 'X'),
('B', 'X'),
('C', 'Y'),
('D', 'Y'),
('E', 'Z');
select
Column1 = max(Column1),
Column2
from
#Table
group by
Column2
having
count(*) = 1;
How to do it with generic data.
DROP TABLE IF EXISTS #MyTable
CREATE TABLE #MyTable(Column1 VARCHAR(50),Column2 VARCHAR(50))
INSERT INTO #MyTable(Column1,Column2)
VALUES
('A','X'),
('B','X'),
('C','Y'),
('D','Y'),
('E','Z')
;WITH UniqueCol2 AS
(
SELECT Column2
FROM #MyTable
GROUP BY Column2
HAVING COUNT(*) = 1
)
SELECT
mt.*
FROM UniqueCol2
JOIN #MyTable mt ON mt.Column2 = UniqueCol2.Column2

Needing system defined function to select updated or unmatched new records from two tables

I am having a live data table in which the old values are placed,in a new table i am moving data from that live table to this one how to find updated or new records that are inserted or updated in new table with out using except,checksum(binary_checksum) and join ,i am looking for a solution using System Defined Function.
The requirement is interesting as the best solutions are to use EXCEPT or a FULL JOIN. What you are trying to do is what is referred to as an left anti semi join. Here's a good article about the topic.
Note this sample data and the solutions (note that my solution that does not use EXCEPT or a join is the last solution):
-- sample data
if object_id('tempdb.dbo.orig') is not null drop table dbo.orig;
if object_id('tempdb.dbo.new') is not null drop table dbo.new;
create table dbo.orig (someid int, col1 int, constraint uq_cl_orig unique (someid, col1));
create table dbo.new (someid int, col1 int, constraint uq_cl_new unique (someid, col1));
insert dbo.orig values (1,100),(2,110),(3,120),(4,2000)
insert dbo.new values (1,100),(2,110),(3,122),(5,999);
Here's the EXCEPT version
select someid
from
(
select * from dbo.new except
select * from dbo.orig
) n
union -- union "distict"
select someid
from
(
select * from dbo.orig except
select * from dbo.new
) o;
Here's a FULL JOIN Solution which will also tell you if the record was removed, changed or added:
select
someid = isnull(n.someid, o.someid),
[status] =
case
when count(isnull(n.someid, o.someid)) > 1 then 'changed'
when max(n.col1) is null then 'removed' else 'added'
end
from dbo.new n
full join dbo.orig o
on n.col1=o.col1 and n.someid = o.someid
where n.col1 is null or o.col1 is null
group by isnull(n.someid, o.someid);
But, because those efficient solutions are not an option - you will need to go with a NOT IN or NOT EXISTS subquery.... And because it has to be a function, I am encapsulating the logic into a function.
create function dbo.newOrChangedOrRemoved()
returns table as return
-- get the new records
select someid, [status] = 'new'
from dbo.new n
where n.someid not in (select someid from dbo.orig)
union all
-- get the removed records
select someid, 'removed'
from dbo.orig o
where o.someid not in (select someid from dbo.new)
union all
-- get the changed records
select someid, 'changed'
from dbo.orig o
where exists
(
select *
from dbo.new n
where o.someid = n.someid and o.col1 <> n.col1
);
Results:
someid status
----------- -------
5 new
4 removed
3 changed

How to update in order and query the updated fields when updating in SQL in a single statement

I need to calculate Dividend Factors in the DB and the basic calculation needed in a general way is row2 field2 = (row2's field1) * (row1's field2) where the field2 is the value I need to both update and query at the same time i.e. when I calculate it for one row, I need the calculated value of the previous row for this row.
Now I have a temp table with has all the values and now I need to calculate the final values, but when I tried this:
UPDATE
#temp
SET
field2 = IsNull(
(SELECT d2.field2 * d.field1 FROM #temp AS d2 WHERE d2.rowNr = d.rowNr - 1)
,d.field1
)
FROM
#temp as d
;
It always saw that the field2 was always NULL and went with the default action, with it should do only for the first row.
Now currently there are only two methods I know for doing this:
Loop through the #temp with a cursor
Use a while statement and loop through the table that way (I opted for this one, because I thought there is no point in using a cursor for a small table of 10-20 rows max)
But I still would like to get this into a single statement, but I have no idea how to do this. I am using MS SQL 2008 R2.
EDIT:
This is the actual data I am working with: (Note, that all field2 values are NULL prior to the calculation and the data type is money)
field1 field2(expected values)
------ ----------------------
1,033 1,033
1,0363 1,0705
1,0558 1,1302
1,0157 1,1479
1,0188 1,1695
1,026 1,1999
1,0286 1,2342
1,0323 1,2741
1,0319 1,3147
Okay if I'm understanding this, you want to find field2 which is based on previous rows of field2 that were just calculated so you need either some form of loop or recursion. Try this recursive solution out:
Setting Up Tables
IF OBJECT_ID('tempdb..#temp') IS NOT NULL
DROP TABLE #temp;
DECLARE #yourTable TABLE (ID INT,field1 INT, field2 INT);
INSERT INTO #yourTable(ID,field1,field2)
VALUES (1111,11,11),(2222,22,22),(3333,33,33);
SELECT ROW_NUMBER() OVER (ORDER BY ID) rowNr,
ID,
field1,
field2 INTO #temp
FROM #yourTable;
Calculating values
WITH cte_recursion
AS
(
SELECT TOP 1
rowNR,
ID,
field1,
field2,
field1 AS dividend_factor
FROM #temp A
ORDER BY rowNr
UNION ALL
SELECT B.rowNr,
B.ID,
B.field1,
B.field2,
B.field1 * A.dividend_factor
FROM cte_recursion A
INNER JOIN #temp B
ON A.rowNr = B.rowNr - 1
)
Actual Update
UPDATE #yourTable
SET field2 = B.dividend_factor
FROM #yourTable A
INNER JOIN cte_recursion B
ON A.ID = B.ID
OPTION (MAXRECURSION 0)
SELECT *
FROM #yourTable
Results:
ID field1 field2
----------- ----------- -----------
1111 11 11
2222 22 242
3333 33 7986
Personally I wouldn't use the update because you have to constantly make sure the data is update to date. I'd much rather use the CTE I used to calculate the values and put it in a view so that you know the values are ALWAYS up to date and you don't have to worry about running it. Either that or having a dividend_factor column in your actual table that will be NULL unless the value is updated. Just my two cents
UPDATE d1
SET d1.field2 = IsNull(d2.field2 * d1.field1, d1.field1)
FROM #temp AS d1
left outer join #temp AS d2
on d2.rowNr = d1.rowNr - 1
magic
select d1.field1, EXP(SUM(LOG(d2.field1)))
from #temp AS d1
join #temp AS d2
on d2.rowNr <= d1.rowNr
group by d1.field1
op claims wrong answer
test for youself
drop table #temp;
create table #temp (ID int, val money);
insert into #temp (ID, val) values
(1, 1.033)
, (2, 1.0363)
, (3, 1.0558)
, (4, 1.0157)
, (5, 1.0188)
, (6, 1.026)
, (7, 1.0286)
, (8, 1.0323)
, (9, 1.0319);
SELECT TOP 10 [t1].[ID], EXP(SUM(LOG([t2].[val])))
from #temp AS t1
join #temp AS t2
on t2.[ID] <= t1.[ID]
group by t1.[ID]
order by t1.[ID]

I am looking for a way for a trigger to insert into a second table only where the value in table 1 changes

I am looking for a way for a trigger to insert into a second table only where the value in table 1 changes. It is essentially an audit tool to trap any changes made. The field in table 1 is price and we want to write additional fields.
This is what I have so far.
CREATE TRIGGER zmerps_Item_costprice__update_history_tr ON [ITEM]
FOR UPDATE
AS
insert into zmerps_Item_costprice_history
select NEWID(), -- unique id
GETDATE(), -- CURRENT_date
'PRICE_CHANGE', -- reason code
a.ima_itemid, -- item id
a.ima_price-- item price
FROM Inserted b inner join item a
on b.ima_recordid = a.IMA_RecordID
The table only contains a unique identifier, date, reference(item) and the field changed (price). It writes any change not just a price change
Is it as simple as this? I moved some of the code around because comments after the comma between columns is just painful to maintain. You also should ALWAYS specify the columns in an insert statement. If your table changes this code will still work.
CREATE TRIGGER zmerps_Item_costprice__update_history_tr ON [ITEM]
FOR UPDATE
AS
insert into zmerps_Item_costprice_history
(
UniqueID
, CURRENT_date
, ReasonCode
, ItemID
, ItemPrice
)
select NEWID()
, GETDATE()
, 'PRICE_CHANGE'
, d.ima_itemid
, d.ima_price
FROM Inserted i
inner join deleted d on d.ima_recordid = i.IMA_RecordID
AND d.ima_price <> i.ima_price
Since you haven't provided any other column names I Have used Column2 and Column3 and the "Other" column names in the below example.
You can expand adding more columns in the below code.
overview about the query below:
Joined the deleted and inserted table (only targeting the rows that has changed) joining with the table itself will result in unnessacary processing of the rows which hasnt changed at all.
I have used NULLIF function to yeild a null value if the value of the column hasnt changed.
converted all the columns to same data type (required for unpivot) .
used unpivot to eliminate all the nulls from the result set.
unpivot will also give you the column name its has unpivoted it.
CREATE TRIGGER zmerps_Item_costprice__update_history_tr
ON [ITEM]
FOR UPDATE
AS
BEGIN
SET NOCOUNT ON ;
WITH CTE AS (
SELECT CAST(NULLIF(i.Price , d.Price) AS NVARCHAR(100)) AS Price
,CAST(NULLIF(i.Column2 , d.Column2) AS NVARCHAR(100)) AS Column2
,CAST(NULLIF(i.Column3 , d.Column3) AS NVARCHAR(100)) AS Column3
FROM dbo.inserted i
INNER JOIN dbo.deleted d ON i.IMA_RecordID = d.IMA_RecordID
WHERE i.Price <> d.Price
OR i.Column2 <> d.Column2
OR i.Column3 <> d.Column3
)
INSERT INTO zmerps_Item_costprice_history
(unique_id, [CURRENT_date], [reason code], Item_Value)
SELECT NEWID()
,GETDATE()
,Value
,ColumnName + '_Change'
FROM CTE UNPIVOT (Value FOR ColumnName IN (Price , Column2, Column3) )up
END
As I understand your question correctly, You want to record change If and only if The column Price value is changes, you dont need any other column changes to be recorded
here is your code
CREATE TRIGGER zmerps_Item_costprice__update_history_tr ON [ITEM]
FOR UPDATE
AS
if update(ima_price)
insert into zmerps_Item_costprice_history
select NEWID(), -- unique id
GETDATE(), -- CURRENT_date
'PRICE_CHANGE', -- reason code
a.ima_itemid, -- item id
a.ima_price-- item price
FROM Inserted b inner join item a
on b.ima_recordid = a.IMA_RecordID

How to make a range series in SQL

I have to improve a Stored Procedure, it uses a select query on a table as follows:
SELECT DISTINCT ProjectId FROM Project where Status ='P' Order by ProjectId
it give an output as follows:
1
2
3
7
8
11
12
13
I need to use these values in insert statement for another table as follow:
insert into Table values (othervalue, 1|1);
insert into Table values (othervalue, 2|2);
....
To decrease the number of inserts, we want to store as follows:
insert into Table values (othervalue, 1|3);
insert into Table values (othervalue, 7|8);
insert into Table values (othervalue, 11|13);
That is in range till the time there is no gap. I tried using CURSOR to loop through the resultset and have some logic to convert it and keep on inserting. But seems some error.
Can we do something in SELECTquery itself?
with t(a,en,bg) as
(
select a,case when [begin] is NULL then NULL else row_number() over(partition by [begin] order by a) end
,case when [end] is NULL then NULL else row_number() over(partition by [end] order by a) end
from (
select t.a, case when t1.a is NULL then 'end' else NULL end [end],
case when t2.a is NULL then 'begin' else NULL end [begin]
from Project as t left join Project as t1 on (t1.a=t.a+1 AND t.Status='P' AND t1.Status='P')
left join Project as t2 on (t2.a=t.a-1 AND t2.Status='P')
) as o )
select cast(t1.a as varchar)+'|'+cast(t.a as varchar) from t inner join t as t1 on t.en=t1.bg
This query will return you values from Project in '1|3' type.
It's not clear from your question whether you use plsql or sql-server. My solution will be work for MS SQL Server