query without using subquery or explicit join - sql

I need to re-write this without using subquery and explicit join..help please been looking around for a while
SELECT snum, pnum, shipdate
FROM supply as b
WHERE EXISTS (SELECT pname, pnum FROM parts as a WHERE b.pnum = a.pnum);

I believe you've been given a trick question. The answer is this.
SELECT snum, pnum, shipdate
FROM supply
The reason is that the condition you're checking for should be impossible in a well designed database.
Let's have a look at what the original query is doing.
SELECT snum, pnum, shipdate
FROM supply as b
WHERE EXISTS (
SELECT pname, pnum FROM parts as a WHERE b.pnum = a.pnum
);
It's getting every row in supply where there's a corresponding part in parts. How do you do this in a query without a join? You shouldn't have to do it in the first place. Instead you should rely on referential integrity.
Referential integrity is a property of good table design that says all references are valid. There should be no need to check that each part in supply exists in parts because such a condition should be impossible. You do this with a well designed schema with appropriate use of foreign key and not null constraints.
(My examples are done in Postgres. The syntax for your database may vary.)
create table parts(
pnum integer primary key,
pname text not null
);
create table supply(
snum integer primary key,
pnum integer references parts(pnum) not null,
shipdate date not null
);
By declaring supply.pnum as references parts(pnum) we have told the database this is a foreign key and there must be a corresponding row in parts. Adding not null guarantees each row in supply must supply a valid part. The database enforces these constraints automatically.
(Note that MySQL takes a little more convincing to enforce a foreign key constraint. Because MySQL is so non-standard one can pick up bad habits learning on it. Use Postgres or even SQLite instead.)
You can also add the constraints to an existing table using alter table.
test=> alter table supply alter pnum set not null;
ALTER TABLE
test=> alter table supply add constraint pnum_fk foreign key (pnum) references parts(pnum);
ALTER TABLE
For example, let's say we have these parts.
test=> select * from parts;
pnum | pname
------+---------
1 | flange
2 | thingy
3 | whatsit
We can insert a row into supply for one of those parts.
test=> insert into supply (pnum, shipdate) values (3, '2018-02-03');
INSERT 0 1
But if we try to insert a part that doesn't exist, we get an error.
test=> insert into supply (pnum, shipdate) values (99, '2018-02-03');
ERROR: insert or update on table "supply" violates foreign key constraint "supply_pnum_fkey"
DETAIL: Key (pnum)=(99) is not present in table "parts".
Or one with a null part number...
test=> insert into supply (pnum, shipdate) values (null, '2018-02-03');
ERROR: null value in column "pnum" violates not-null constraint
DETAIL: Failing row contains (1, null, 2018-02-03).
The condition you're testing for is now impossible. There's no need for it. So the answer is:
SELECT snum, pnum, shipdate
FROM supply

One way is INTERSECT(column list is limited to common ones):
SELECT pnum
FROM supply
INTERSECT
SELECT pnum
FROM parts;
Using SEMIJOIN:
SELECT b.snum, b.pnum, b.shipdate
FROM supply as b
SEMIJOIN parts as a
ON b.pnum = a.pnum;

The subquery can be replaced by an INNER JOIN, as follows :
SELECT b.snum, b.pnum, b.shipdate
FROM
supply as b
INNER JOIN parts as a ON b.pnum = a.pnum
GROUP BY b.snum, b.pnum, b.shipdate
You could also go for implicit join, but I would not recommend it as it is less readable and out of favor as of now :
SELECT b.snum, b.pnum, b.shipdate
FROM
supply as b,
parts as a
WHERE b.pnum = a.pnum
GROUP BY b.snum, b.pnum, b.shipdate

Related

Create a table with a foreign key referencing to a temporary table generated by a query

I need to create a table having a field, which is a foreign key referencing to another query rather than existing table. E.g. the following statement is correct:
CREATE TABLE T1 (ID1 varchar(255) references Types)
but this one throws a syntax error:
CREATE TABLE T2 (ID2 varchar(255) references SELECT ID FROM BaseTypes UNION SELECT ID FROM Types)
I cannot figure out how I can achieve my goal. In the case it’s needed to introduce a temporary table, how can I force this table being updated each time when tables BaseTypes and Types are changed?
I am using Firebird DB and IBExpert management tool.
A foreign key constraint (references) can only reference a table (or more specifically columns in the primary or unique key of a table). You can't use it to reference a select.
If you want to do that, you need to use a CHECK constraint, but that constraint would only be checked on insert and updates: it wouldn't prevent other changes (eg to the tables in your select) from making the constraint invalid while the data is at rest. This means that at insert time the value could meet the constraint, but the constraint could - unnoticed! - become invalid. You would only notice this when updating the row.
An example of the CHECK-constraint could be:
CREATE TABLE T2 (
ID2 varchar(255) check (exists(
SELECT ID FROM BaseTypes WHERE BaseTypes.ID = ID2
UNION
SELECT ID FROM Types WHERE Types.ID = ID2))
)
For a working example, see this fiddle.
Alternatively, if your goal is to 'unite' two tables, define a 'super'-table that contains the primary keys of both tables, and reference that table from the foreign key constraint. You could populate and update (eg insert and delete) this table using triggers. Or you could use a single table, and replace the existing views with an updatable view (if this is possible depends on the exact data, eg IDs shouldn't overlap).
This is more complex, but would give you the benefit that the foreign key is also enforced 'at rest'.

How to reference foreign key from more than one column (Inconsistent values)

I Have table three tables:
The first one is emps:
create table emps (id number primary key , name nvarchar2(20));
The second one is cars:
create table cars (id number primary key , car_name varchar2(20));
The third one is accounts:
create table accounts (acc_id number primary key, woner_table nvarchar2(20) ,
woner_id number references emps(id) references cars(id));
Now I Have these values for selected tables:
Emps:
ID Name
-------------------
1 Ali
2 Ahmed
Cars:
ID Name
------------------------
107 Camery 2016
108 Ford 2012
I Want to
Insert values in accounts table so its data should be like this:
Accounts:
Acc_no Woner_Table Woner_ID
------------------------------------------
11013 EMPS 1
12010 CARS 107
I tried to perform this SQL statement:
Insert into accounts (acc_id , woner_table , woner_id) values (11013,'EMPS',1);
BUT I get this error:
ERROR at line 1:
ORA-02291: integrity constraint (HR.SYS_C0016548) violated - parent key not found.
This error occurs because the value of woner_id column doesn't exist in cars table.
My work require link tables in this way.
How Can I Solve This Problem Please ?!..
Mean: How can I reference tables in previous way and Insert values without this problem ?..
One-of relationships are tricky in SQL. With your data structure here is one possibility:
create table accounts (
acc_id number primary key,
emp_id number references emps(id),
car_id number references car(id),
id as (coalesce(emp_id, car_id)),
woner_table as (case when emp_id is not null then 'Emps'
when car_id is not null then 'Cars'
end),
constraint chk_accounts_car_emp check (emp_id is null or car_id is null)
);
You can fetch the id in a select. However, for the insert, you need to be explicit:
Insert into accounts (acc_id , emp_id)
values (11013, 1);
Note: Earlier versions of Oracle do not support virtual columns, but you can do almost the same thing using a view.
Your approach should be changed such that your Account table contains two foreign key fields - one for each foreign table. Like this:
create table accounts (acc_id number primary key,
empsId number references emps(id),
carsId number references cars(id));
The easiest, most straightforward method to do this is as STLDeveloper says, add additional FK columns, one for each table. This also bring along with it the benefit of the database being able to enforce Referential Integrity.
BUT, if you choose not to do, then the next option is to use one FK column for the the FK values and a second column to indicate what table the value refers to. This keeps the number of columns small = 2 max, regardless of number of tables with FKs. But, this significantly increases the programming burden for the application logic and/or PL/SQL, SQL. And, of course, you completely lose Database enforcement of RI.

Sql server relationship using between value

I have a database that contains 2 tables (sql server 2008):
LenderCommission
ID int
Commission decimal
CommissionTier
ID int
MinCommission decimal
MaxCommission decimal
I want to create a relationship between the 2 tables. The obvious way would be to add CommissionTierId to the LenderCommission table, however this is then fixed. If the commission structure of a tier changed all LenderCommissions would need to be revaluated.
I'm no expert but I'm guessing that it's not possible to create a relationship where Commission is between MinCommission and MaxCommission. However, is there any way in which this relationship can be made more fluid?
I'm not really sure what you're trying to do - I've put together a demo of one way you might be wanting things to work. We actually create a table that contains more columns and then hide that behind a view with a couple of triggers to make sure that everything works well.
The tables:
create table dbo.CommissionTier (
ID int not null,
MinCommission decimal not null,
MaxCommission decimal not null,
constraint PK_CommissionTier PRIMARY KEY (ID),
constraint UQ_CommissionTier_RangeValidation UNIQUE (ID,MinCommission,MaxCommission) --This is to support our FK below
)
go
create table dbo._LenderCommission (
ID int not null,
Commission decimal not null,
CommissionTier int not null,
_MinCommission decimal not null,
_MaxCommission decimal not null,
constraint PK__LenderCommission PRIMARY KEY (ID),
constraint CK__LenderCommission_InRange CHECK (
_MinCommission < Commission and --Inclusive or exclusive bound? <= or <
Commission < _MaxCommission --Ditto?
),
constraint FK__LenderCommission_Tier
FOREIGN KEY (CommissionTier)
references dbo.CommissionTier (ID), --This FK isn't actually needed
constraint FK__LenderCommission_Tier_Range
FOREIGN KEY (CommissionTier,_MinCommission,_MaxCommission)
references dbo.CommissionTier(ID,MinCommission,MaxCommission) on update cascade
)
The view (which from now on you'll treat as your original table):
create view dbo.LenderCommission
with schemabinding
as
select
ID,
Commission,
CommissionTier
from
dbo._LenderCommission
go
You probably don't need this, but I tend to always put on an index when the view is masquerading as a table, giving it the same name as the original primary key would have been:
create unique clustered index PK_LenderCommission on dbo.LenderCommission(ID)
Now a couple of triggers that do some necessary house cleaning:
create trigger dbo.T_LenderCommission_I
on dbo.LenderCommission
instead of insert
as
set nocount on
insert into dbo._LenderCommission (ID,Commission,CommissionTier,_MinCommission,_MaxCommission)
select i.ID,i.Commission,i.CommissionTier,ct.MinCommission,ct.MaxCommission
from
inserted i
inner join
dbo.CommissionTier ct
on
i.CommissionTier = ct.ID
go
create trigger dbo.T_LenderCommission_U
on dbo.LenderCommission
instead of update
as
set nocount on
update lc
set
Commission = i.Commission,
CommissionTier = i.CommissionTier,
_MinCommission = ct.MinCommission,
_MaxCommission = ct.MaxCommission
from
dbo._LenderCommission lc
inner join
inserted i
on
lc.ID = i.ID
inner join
dbo.CommissionTier ct
on
i.CommissionTier = ct.ID
And now we populate them:
insert into CommissionTier(ID,MinCommission,MaxCommission) values
(1,0,1000),
(2,1000,2000),
(3,2000,3000)
go
insert into LenderCommission(ID,Commission,CommissionTier) values
(1,500,1),
(2,750,1),
(3,1500,2)
Now for some errors:
--This produces an error:
insert into LenderCommission(ID,Commission,CommissionTier) values
(4,500,2)
go
--This produces an error:
update LenderCommission set Commission = 2500 where ID=2
go
--This *doesn't* produce an error because the new value matches the new tier
update LenderCommission set Commission = 2500,CommissionTier = 3 where ID=2
go
--This is okay, we're adjusting one of the tiers
update CommissionTier set MaxCommission = 1550 where ID = 2
go
--This produces an error, because the cascading foreign key updated the values:
update LenderCommission set Commission = 1600 where ID = 3
As an alternative to the above, rather than having CK__LenderCommission_InRange, you might instead create a computed column that performs the same checks and adopts suitable values:
CommissionOutOfRange AS
CASE WHEN Commission < MinCommission THEN -1
CASE WHEN Commission > MaxCommission THEN 1
ELSE 0 END
And now you can quickly search your table for values that are out of range (but nothing stops them from being created
I don't fully understand your goal. There seems to already be a logical relationship between the tables; presumably, the commission can fall between the min and max values. You would do the join as:
select <whatever>
from LenderCommission lc join
CommissionTier ct
on lc.commission between ct.MinCommission and ct.MaxCommission;
What are you trying to do?
One possibility is that you want to make this join more efficient by converting it to an equi-join rather than a "between" join. Having the id would definitely do this. If the CommissionTier table has only a few dozen rows, then this level of efficiency is probably unimportant.
Perhaps you are trying to enforce a constraint between the table, so new commissions can only be added with a commission tier. If so, you can do the checking via a trigger.
Really converting this relationship into a single foreign key will be complicated. You will need to take into account things such as:
Preventing overlapping commission tiers.
Changing the foreign key relationship when a commission tier changes (that is, modifying the data in eaderCommission.
Maintaining historical data on the commission tiers, if you ever need to see "what it was" at a different point in time.
These are all possible, but are perhaps much more complicated than you intend.

Can I write an insert query using data from two joined tables?

I have a SELECT query like this:
SELECT id_default_value, id_type FROM ntg_attribute, ntg_module_attribute
WHERE ntg_attribute.id_module_attribute = ntg_module_attribute.id;
This returns 2 columns, id_default_value and id_type. I'd like to then use this data as the basis of an INSERT query into another table, ntg_default_value, using id_default_value as the key, and id_type as the inserted value.
The following is nearly there, but not quite:
INSERT INTO ntg_default_value (id, id_type)
SELECT id_default_value, id_type FROM ntg_attribute, ntg_module_attribute
WHERE ntg_attribute.id_module_attribute = ntg_module_attribute.id;
This gives me:
ERROR: duplicate key value violates unique constraint "pk_ntg_default_value"
Is what I'm trying to do actually possible? If so, how do I construct the query?
(PostgreSQL 8.4.6)
The name of the constraint 'pk_ntg_default_value' probably means you are violating the primary key constraint of the table ntg_default_value.
Depending on your requirements you can either take away the primary key constraint. Or you can expand it to include both id & id_type if it doesn't already and add a GROUP BY to your query, if necessary, to prevent duplicate id_devault_value & id_type pairs. Your query becomes then :
INSERT INTO ntg_default_value (id, id_type)
SELECT id_default_value, id_type
FROM ntg_attribute, ntg_module_attribute
WHERE ntg_attribute.id_module_attribute =
ntg_module_attribute.id
GROUP BY id_default_value, id_type

How to create a unique index on a NULL column?

I am using SQL Server 2005. I want to constrain the values in a column to be unique, while allowing NULLS.
My current solution involves a unique index on a view like so:
CREATE VIEW vw_unq WITH SCHEMABINDING AS
SELECT Column1
FROM MyTable
WHERE Column1 IS NOT NULL
CREATE UNIQUE CLUSTERED INDEX unq_idx ON vw_unq (Column1)
Any better ideas?
Using SQL Server 2008, you can create a filtered index.
CREATE UNIQUE INDEX AK_MyTable_Column1 ON MyTable (Column1) WHERE Column1 IS NOT NULL
Another option is a trigger to check uniqueness, but this could affect performance.
The calculated column trick is widely known as a "nullbuster"; my notes credit Steve Kass:
CREATE TABLE dupNulls (
pk int identity(1,1) primary key,
X int NULL,
nullbuster as (case when X is null then pk else 0 end),
CONSTRAINT dupNulls_uqX UNIQUE (X,nullbuster)
)
Pretty sure you can't do that, as it violates the purpose of uniques.
However, this person seems to have a decent work around:
http://sqlservercodebook.blogspot.com/2008/04/multiple-null-values-in-unique-index-in.html
It is possible to use filter predicates to specify which rows to include in the index.
From the documentation:
WHERE <filter_predicate> Creates a filtered index by specifying which
rows to include in the index. The filtered index must be a
nonclustered index on a table. Creates filtered statistics for the
data rows in the filtered index.
Example:
CREATE TABLE Table1 (
NullableCol int NULL
)
CREATE UNIQUE INDEX IX_Table1 ON Table1 (NullableCol) WHERE NullableCol IS NOT NULL;
Strictly speaking, a unique nullable column (or set of columns) can be NULL (or a record of NULLs) only once, since having the same value (and this includes NULL) more than once obviously violates the unique constraint.
However, that doesn't mean the concept of "unique nullable columns" is valid; to actually implement it in any relational database we just have to bear in mind that this kind of databases are meant to be normalized to properly work, and normalization usually involves the addition of several (non-entity) extra tables to establish relationships between the entities.
Let's work a basic example considering only one "unique nullable column", it's easy to expand it to more such columns.
Suppose we the information represented by a table like this:
create table the_entity_incorrect
(
id integer,
uniqnull integer null, /* we want this to be "unique and nullable" */
primary key (id)
);
We can do it by putting uniqnull apart and adding a second table to establish a relationship between uniqnull values and the_entity (rather than having uniqnull "inside" the_entity):
create table the_entity
(
id integer,
primary key(id)
);
create table the_relation
(
the_entity_id integer not null,
uniqnull integer not null,
unique(the_entity_id),
unique(uniqnull),
/* primary key can be both or either of the_entity_id or uniqnull */
primary key (the_entity_id, uniqnull),
foreign key (the_entity_id) references the_entity(id)
);
To associate a value of uniqnull to a row in the_entity we need to also add a row in the_relation.
For rows in the_entity were no uniqnull values are associated (i.e. for the ones we would put NULL in the_entity_incorrect) we simply do not add a row in the_relation.
Note that values for uniqnull will be unique for all the_relation, and also notice that for each value in the_entity there can be at most one value in the_relation, since the primary and foreign keys on it enforce this.
Then, if a value of 5 for uniqnull is to be associated with an the_entity id of 3, we need to:
start transaction;
insert into the_entity (id) values (3);
insert into the_relation (the_entity_id, uniqnull) values (3, 5);
commit;
And, if an id value of 10 for the_entity has no uniqnull counterpart, we only do:
start transaction;
insert into the_entity (id) values (10);
commit;
To denormalize this information and obtain the data a table like the_entity_incorrect would hold, we need to:
select
id, uniqnull
from
the_entity left outer join the_relation
on
the_entity.id = the_relation.the_entity_id
;
The "left outer join" operator ensures all rows from the_entity will appear in the result, putting NULL in the uniqnull column when no matching columns are present in the_relation.
Remember, any effort spent for some days (or weeks or months) in designing a well normalized database (and the corresponding denormalizing views and procedures) will save you years (or decades) of pain and wasted resources.