How would you find the 'GOOD' ID when cancellation is involved? - sql

Suppose you have the following schema:
CREATE TABLE Data
(
ID INT,
CXL INT
)
INSERT INTO Data (ID, CXL)
SELECT 1, NULL
UNION
SELECT 2, 1
UNION
SELECT 3, 2
UNION
SELECT 5, 3
UNION
SELECT 6, NULL
UNION
SELECT 7, NULL
UNION
SELECT 8, 7
The column CXL is the ID that cancels a particular ID. So, for example, the first row in the table with ID:1 was good until it was cancelled by ID:2 (CXL column). ID:2 was good until it was cancelled by ID:3. ID:3 was good until it was cancelled by ID:5 so in this sequence the last "GOOD" ID was ID:5.
I would like to find all the "GOOD" IDs So in this example it would be:
Latest GOOD ID
5
6
8
Here's a fiddle if you want to play with this:
http://sqlfiddle.com/#!6/68ac48/1

SELECT D.ID
FROM Data D
WHERE NOT EXISTS(SELECT 1
FROM Data WHERE D.ID = CXL)

select Id
from data
where Id not in (select cxl from data where cxl is not null)

Related

Find whether given conditions based records are matching or not in sql

I have a table and want to find out whether the given condition matches or not with existing table records. I tried with not exists but that doesn't work. What expression would fill the is_exists column like in the expected output below?
create table user1(id number, name varchar2(20), age number);
insert into user1 values(1, 'user1', 1);
insert into user1 values(1, 'user11', 11);
insert into user1 values(2, 'user2', 2);
insert into user1 values(2, 'user22', 22);
insert into user1 values(3, 'user3', 3);
insert into user1 values(4, 'user4', 4);
select id, age, is_exists
from user1
where id = 1 and age in (1, 11, 111);
Expected result:
id age is_exists
-- --- ---------
1 1 true
1 11 true
1 111 false
You need some kind of table for the three set of values; which you can build using select ... union all. Then use left join or exists:
with cte as (
select 1 as id, 1 as age from dual union all
select 1, 11 from dual union all
select 1, 111 from dual
)
select cte.*, case when exists (
select 1
from user1
where user1.id = cte.id and user1.age = cte.age
) then 'yes' else 'no' end is_exists
from cte
SQL is designed to return a subset or transformed set of data from the underlying database. For example, FROM specifies that you want all columns and rows from a particular table. The expression list in the SELECT statement narrows down the number of columns you need from this table. The WHERE statement narrows down the number of rows you receive.
Every row in the result set is based on one or more existing rows in the original data set. In either case the maximum result is always the data that exists in the table.
Because you want one row for the age in the result set, you need one row for the age in the query source. If you don't have a table, creating a temporary one with a series of UNION is a good approach.
This is the same as 'Salman A', but using LEFT JOIN whose result is CASE tested in Select clause. Basicaly, if there is not a join then there is not a matching record...
WITH
fltr AS
(
Select 1 "ID", 1 "AGE" From DUAL UNION
Select 1 "ID", 11 "AGE" From DUAL UNION
Select 1 "ID", 111 "AGE" From DUAL
)
SELECT
f.ID "ID", f.AGE "AGE",
CASE WHEN u.ID Is Not Null THEN 'YES' ELSE 'NO' END "IS_EXISTS"
FROM
fltr f
LEFT JOIN
USER1 u ON(u.ID = f.ID And u.AGE = f.AGE)
--
-- Result
--
-- ID AGE IS_EXISTS
-- 1 1 YES
-- 1 11 YES
-- 1 111 NO

Check Constraint in Oracle SQL Developer

need your help
I have a table in Oracle SQL Developer of this kind:
Issue
ID|Subscriber_ID|Book_ID| Taken |Returned
--+-------------+-------+--------+--------
1 | 1 | 2 |01-06-16|05-06-16
2 | 3 | 5 |07-05-16| (null)
3 | 2 | 2 |06-06-16| (null)
4 | 1 | 3 |17-05-16|26-05-16
It's some sort of library book issuing where (null) in Returned column means that this book wasn't returned yet. I need to create validation rule to avoid issuing book that wasn't returned (e.g. I can't take the book #5 at the moment). How can I implement it?
Hmmm. You can't do this with a check constraint, because those only apply to values in one row.
What you want to ensure is that you do not have two returned values for a book. Some databases support filtered unique indexes:
create unique index on unq_issue_bookid on issue(book_id) where returned is null;
But not Oracle. You can do something very similar with a function-based index:
create unique index on unq_issue_bookid_returned
on issue(book_id,
(case when returned is not null then id else -1 end)
);
This will have the same effect of allowing only one NULL value per book.
You can do it with :
CREATE TABLE table_name ( ID, Subscriber_ID, Book_ID, Taken, Returned ) AS
SELECT 1, 1, 2, DATE '2016-06-01', DATE '2016-06-05' FROM DUAL UNION ALL
SELECT 2, 3, 5, DATE '2016-05-07', NULL FROM DUAL UNION ALL
SELECT 3, 2, 2, DATE '2016-06-06', NULL FROM DUAL UNION ALL
SELECT 4, 1, 3, DATE '2016-05-17', DATE '2016-05-26' FROM DUAL;
ALTER TABLE table_name ADD is_borrowed
GENERATED ALWAYS AS ( CASE WHEN returned IS NULL THEN 1 END ) VIRTUAL;
ALTER TABLE TABLE_NAME ADD CONSTRAINT is_borrowed__u
UNIQUE( book_id, is_borrowed );
Then:
INSERT INTO table_name ( ID, Subscriber_ID, Book_ID, Taken )
VALUES ( 5, 2, 5, DATE '2016-06-06' );
Will fail with:
SQL Error: ORA-00001: unique constraint (TEST.IS_BORROWED__U) violated

SQL: how to select common lines of one table on several column

I have a table :
create table a (page int, pro int)
go
insert into a select 1, 2
insert into a select 4, 2
insert into a select 5, 2
insert into a select 9, 2
insert into a select 1, 3
insert into a select 2, 3
insert into a select 3, 3
insert into a select 4, 3
insert into a select 9, 3
insert into a select 1, 4
insert into a select 9, 4
insert into a select 12, 4
insert into a select 1, 5
insert into a select 9, 5
insert into a select 12, 5
insert into a select 13, 5
insert into a select 14, 5
insert into a select 15, 5
go
(here is the SQLfiddle of this table and queries I began to write )
Common value of page on ALL lines
I'm looking to extract the common column "page" for each column "pro" from this table.
here is what we expect :
1
9
I tried to use:
SELECT DISTINCT a.page
FROM a
WHERE a.page IN (
SELECT b.page FROM a as b
WHERE b.pro <> a.pro
)
but this query returns every "page" that have at least one common values which is not what we need to have. see below :
1
4
9
12
The opposite query aka different value at least one but not all time
I'm looking to extract the "page" linked to one or more "pro" but without being common to all of them (it's the exact opposite of the previous query)
Here is what we expect :
2
3
4
5
12
13
14
15
I can't manage to find a solution to those 2 queries :'(
Could anyone help me on those ones?
Best regards
edit: the SQLfiddle url
Just a bit of reversed thinking - group by page and count distinct pro values for each. Return rows that matches the total of distinct pro values
SELECT [page]
FROM a
GROUP BY [page]
HAVING COUNT(DISTINCT pro) = (SELECT COUNT(DISTINCT pro) FROM a)
SQLFiddle
EDIT: for the second problem, just replace = with '<' in the final line -> SQLFiddle
Fot the first part of the question, try this query:
SELECT DISTINCT t1.page FROM a t1
WHERE (SELECT COUNT(DISTINCT t2.pro) FROM a t2 WHERE
t2.page = t1.page) =
(SELECT COUNT(DISTINCT t3.pro) FROM a t3)
And the second query is the simple substraction from all page values:
SELECT DISTINCT t4.page FROM a t4
EXCEPT
SELECT DISTINCT t1.page FROM a t1
WHERE (SELECT COUNT(DISTINCT t2.pro) FROM a t2 WHERE
t2.page = t1.page) =
(SELECT COUNT(DISTINCT t3.pro) FROM a t3)

SQL Server 2008, how to check if multi records exist in the DB?

I have 3 tables:
recipe:
id, name
ingredient:
id, name
recipeingredient:
id, recipeId, ingredientId, quantity
Every time, a customer creates a new recipe, I need to check the recipeingredient table to verify if this recipe exists or not. If ingredientId and quantity are exactly the same, I will tell the customer the recipe already exists. Since I need to check multiple rows, need help to write this query.
Knowing your ingredients and quantities, you can do something like this:
select recipeId as ExistingRecipeID
from recipeingredient
where (ingredientId = 1 and quantity = 1)
or (ingredientId = 8 and quantity = 1)
or (ingredientId = 13 and quantity = 1)
group by recipeId
having count(*) = 3 --must match # of ingeredients in WHERE clause
I originally thought that the following query would find pairs of recipes that have exactly the same ingredients:
select ri1.recipeId, ri2.recipeId
from RecipeIngredient ri1 full outer join
RecipeIngredient ri2
on ri1.ingredientId = ri2.ingredientId and
ri1.quantity = ri2.quantity and
ri1.recipeId < ri2.recipeId
group by ri1.recipeId, ri2.recipeId
having count(ri1.id) = count(ri2.id) and -- same number of ingredients
count(ri1.id) = count(*) and -- all r1 ingredients are present
count(*) = count(ri2.id) -- all r2 ingredents are present
However, this query doesn't count things correctly, because the mismatches don't have the right pairs of ids. Alas.
The following does do the correct comparison. It counts the ingredients in each recipe before the join, so this value can just be compared on all matching rows.
select ri1.recipeId, ri2.recipeId
from (select ri.*, COUNT(*) over (partition by recipeid) as numingredients
from #RecipeIngredient ri
) ri1 full outer join
(select ri.*, COUNT(*) over (partition by recipeid) as numingredients
from #RecipeIngredient ri
) ri2
on ri1.ingredientId = ri2.ingredientId and
ri1.quantity = ri2.quantity and
ri1.recipeId < ri2.recipeId
group by ri1.recipeId, ri2.recipeId
having max(ri1.numingredients) = max(ri2.numingredients) and
max(ri1.numingredients) = count(*)
The having clause guarantees that each recipe that the same number of ingredients, and that the number of matching ingredients is the total. This time, I've tested it on the following data:
insert into #recipeingredient select 1, 1, 1
insert into #recipeingredient select 1, 2, 10
insert into #recipeingredient select 2, 1, 1
insert into #recipeingredient select 2, 2, 10
insert into #recipeingredient select 2, 3, 10
insert into #recipeingredient select 3, 1, 1
insert into #recipeingredient select 4, 1, 1
insert into #recipeingredient select 4, 3, 10
insert into #recipeingredient select 5, 1, 1
insert into #recipeingredient select 5, 2, 10
If you have a new recipe, you can modify this query to just look for the recipe in one of the tables (say ri1) using an additional condition on the on clause.
If you place the ingredients in a temporary table, you can substitute one of these tables, say ri1, with the new table.
You might try something like this to find if you have a duplicate:
-- Setup test data
declare #recipeingredient table (
id int not null primary key identity
, recipeId int not null
, ingredientId int not null
, quantity int not null
)
insert into #recipeingredient select 1, 1, 1
insert into #recipeingredient select 1, 2, 10
insert into #recipeingredient select 2, 1, 1
insert into #recipeingredient select 2, 2, 10
-- Actual Query
if exists (
select *
from #recipeingredient old
full outer join #recipeingredient new
on old.recipeId != new.recipeId -- Different recipes
and old.ingredientId = new.ingredientId -- but same ingredients
and old.quantity = new.quantity -- and same quantities
where old.id is null -- Match not found
or new.id is null -- Match not found
)
begin
select cast(0 as bit) as IsDuplicateRecipe
end
else begin
select cast(1 as bit) as IsDuplicateRecipe
end
Since this is really only searching for a duplicate, you might want to substitute a temp table or pass a table variable for the "new" table. This way you wouldn't have to insert the new records before doing your search. You could also insert into the base tables, wrap the whole thing in a transaction and rollback based upon the results.

create view for items not in list

I have two tables. Table 1 is a master list of equipment with equipment_id and equipment_description. So, let's say for this table I have ten equipment_id's. 1,2,3....10 each with some description attached.
Table 2 logs when the equipment has been inspected:
equipment_id|inspection_date
1 | '1-22-2012'
2 | '1-22-2012'
4 | '1-22-2012'
2 | '1-23-2012'
3 | '1-23-2012'
I've created a view, v_dates which pulls out of table 2 all of the distinct inspection dates - not sure if I needed it but did it anyway.
I would like to create another view which shows all equipment that was NOT inspected for each date in the v_dates. So it would show:
3 | '1-22-2012'
5 | '1-22-2012'
and so on.
Rookie here and just not sure how to join these tables correctly. Can't get it to work and would appreciate any help.
Untested, but I think this should give the desired result:
SELECT i.id,d.date FROM
( SELECT DISTINCT inspection_date AS date FROM inspections ORDER BY 1 ) d
LEFT JOIN
inspections i
ON d.date=i.date
WHERE i.date IS NULL
GROUP BY 1,2
ORDER BY 1,2
Like mentioned in the comments would a table with inspection dates really help.
The following appears to work based on my test data using SQL SERVER 2005. I am using a CROSS JOIN of distinct dates along with a LEFT JOIN to throw out EQUIPMENT_ID records that exist for those dates.
Sorry, I am having problems getting my code formatting correct with tabs and spaces...
IF OBJECT_ID('tempdb..#EQUIPMENT') IS NOT NULL
DROP TABLE #EQUIPMENT
CREATE TABLE #EQUIPMENT
( EQUIPMENT_ID smallint,
EQUIPMENT_DESC varchar(32)
)
INSERT INTO #EQUIPMENT
( EQUIPMENT_ID, EQUIPMENT_DESC )
SELECT 1, 'AAA'
UNION SELECT 2, 'BBB'
UNION SELECT 3, 'CCC'
UNION SELECT 4, 'DDD'
UNION SELECT 5, 'EEE'
UNION SELECT 6, 'FFF'
UNION SELECT 7, 'GGG'
UNION SELECT 8, 'HHH'
UNION SELECT 9, 'III'
UNION SELECT 10, 'JJJ'
IF OBJECT_ID('tempdb..#INSPECTION') IS NOT NULL
DROP TABLE #INSPECTION
CREATE TABLE #INSPECTION
( EQUIPMENT_ID smallint,
INSPECTION_DATE smalldatetime
)
INSERT INTO #INSPECTION
( EQUIPMENT_ID, INSPECTION_DATE )
SELECT 1, '1-22-2012'
UNION SELECT 1, '1-27-2012'
UNION SELECT 3, '1-27-2012'
UNION SELECT 5, '1-29-2012'
UNION SELECT 7, '1-22-2012'
UNION SELECT 7, '1-27-2012'
UNION SELECT 7, '1-29-2012'
SELECT E.EQUIPMENT_ID, D.INSPECTION_DATE
FROM #EQUIPMENT E
CROSS JOIN ( SELECT DISTINCT INSPECTION_DATE
FROM #INSPECTION
) D
LEFT JOIN #INSPECTION I2
ON E.EQUIPMENT_ID = I2.EQUIPMENT_ID
AND D.INSPECTION_DATE = I2.INSPECTION_DATE
WHERE I2.EQUIPMENT_ID IS NULL
ORDER BY E.EQUIPMENT_ID, D.INSPECTION_DATE
As per my comment to the question, you really need a table of valid inspection dates. It makes the sql much more sensible, and besides it's the only way to do it if you want to see all items listed for dates when inspections were supposed to be done, but no inspections were done.
So, assuming the two tables:
create table inspections (equipment_id int, inspection_date date);
create table inspection_dates (id int, inspection_date date);
Then a join to get all the equipment that does not have an inspection on a date when an inspection should have taken place would be:
select i.equipment_id, id.inspection_date
from inspection_dates id,
(select distinct equipment_id from inspections) i
where not exists (select * from inspections i2
where i2.inspection_date = id.inspection_date
and i2.equipment_id = i.equipment_id);
You want the combos that do not exist. Thus the not exists predicate.
Note again, that presumably you would have a table for all the unique equipment_ids, but not knowing that I had to construct it myself in place.