Nested result sets as with dynamic names - sql

I'm trying to join the result of two referencing tables to get row values which are referencing different table names, which rows are selectable by their uuid.
my tables look like this:
table entry
table map
table cats
table dogs
nrrefInt
id name mapRef breed
mapRef breed
1 123
123'dogs'
456 'bengal'
123 'sheepdog'
2 456
456 'cats'
888 'birma' 999 'poodle'
3 789
789'dogs'
4 123
refInt of entry is referencing to map. the name of map is the reference to tables in addition with the field id which is also applied on the tables cats/dogs (dynamic tables loading).
// subset 1: list of numbers that needs to be loaded from entry table (1-4)
SELECT DISTINCT refInt FROM entry WHERE nr in (1,2,3,4)
// subset 2: get all names from map that have the same id like refInt from subset1
SELECT name FROM map WHERE id in subset1
// main query: load all rows from table with the given name
// from map table that have the same mapRef value on it
SELECT * FROM (subset2.names) WHERE mapRef IN (subset2.ids)
result should be the rows:
1) 456 bengal
2) 123 sheepdog
I also made a SQLFiddle of it.
Is there a way to combine this to one query?

It's going to look something like:
SqlFiddle
select
sub.nr,
sub.breed
from (
select e.nr, e.refInt,
case
when c.breed is not null then c.breed
when d.breed is not null then d.breed
else null
end as breed
from (
select e.nr, e.refInt, m.name
from entry e
inner join map m on e.refInt = m.id
) e
left join cats c on e.refInt = c.mapRef and e.name = 'cats'
left join dogs d on e.refInt = d.mapref and e.name = 'dogs'
) sub
where sub.breed is not null
This is going to be very poor in performance.
Now the IMO the correct schema would be:
table entry
nr refint
1 123
2 456
3 789
4 124 (duplicate?)
table breed
mapRef breed species
123 sheepdog 1
999 poodle 1
456 bengal 2
888 birma 2
table species
id species
1 dogs
2 cats
This is normalized and has very good performance.

Note how the following query fully achieves the desired result set and fully demonstrates how the tables cats and dogs are truly just partitions of a single entity animals. The schema should be reworked to reflect this new understanding. This query is also efficient because the inclusion test id pushed to the depths of the innermost CTE's, at the level where actual table rows are being read, without relying on the engine to discover this potential optimization (which can be problematic with UNIONs).
with
cats2 as (
select species='cat', mapref, breed
from cats animals
join entry on entry.refint = animals.mapref
where entry.nr in (1,2,3,4)
),
dogs2 as (
select species='dog', mapref, breed
from dogs animals
join entry on entry.refint = animals.mapref
where entry.nr in (1,2,3,4)
),
animals as (
select species, mapref, breed from cats2
union all
select species, mapref, breed from dogs2
)
select species, mapref, breed
from animals
group by species, mapref, breed
This test script:
declare #entry table (nr int, refint int );
declare #map table (id int, name varchar(20) );
declare #cats table (mapRef int, breed varchar(20));
declare #dogs table (mapRef int, breed varchar(20));
insert #entry(nr,refint) values
(1,123)
,(2,456)
,(3,789)
,(4,123);
insert #map(id,name) values
(123,'dogs')
,(456,'cats')
,(789,'dogs');
insert #cats(mapRef,breed) values
(456,'bengal'),(888,'burma');
insert #dogs(mapRef,breed) values
(123,'sheepdog'), (999,'poodle');
with
cats2 as (
select species='cat', mapref, breed
from #cats animals
join #entry entry on entry.refint = animals.mapref
where entry.nr in (1,2,3,4)
),
dogs2 as (
select species='dog', mapref, breed
from #dogs animals
join #entry entry on entry.refint = animals.mapref
where entry.nr in (1,2,3,4)
),
animals as (
select species, mapref, breed from cats2
union all
select species, mapref, breed from dogs2
)
select species, mapref, breed
from animals
group by species, mapref, breed
yields as desired:
species mapref breed
------- ----------- --------------------
cat 456 bengal
dog 123 sheepdog

Related

How to denormalized data in SQL query

I have a table that has Clinic Names and Doctor Names. one clinic can have many doctors. I need to split this data into two tables. one with clinic info and the other with Doctor info
trying to do this in a SQL query
Table CLINIC_DOC:
ID ClinicName Doctor
------------------------
1 xyz Dr Joe
2 xyz Dr Bob
3 abc Dr Mary
4 abc Dr John
I want to split the data into the following tables like this:
Table ClinicsData:
ClinicID ClinicName
----------------------
1 xyz
2 abc
Table DoctorData:
DocId ClinicID Doctor
--------------------------
1 1 Dr Joe
2 1 Dr Bob
3 2 Dr Mary
4 2 Dr John
Assuming that the ID columns (ClinicID and DocID) are automatically generated and that the clinic names are unique (i.e there are no two clinics with the same name in the portion of the real world your data represents) you can try:
INSERT INTO clinicsdata
(clinicname)
SELECT DISTINCT
cd.clinicname
FROM clinic_doc cd;
INSERT INTO doctordata
(clinicid,
doctor)
SELECT c.clinicid,
cd.doctor
FROM clinic_doc cd
INNER JOIN clinicsdata c
ON c.clinicname = cd.clinicname;
First, you'll probably want to create the tables you're going to populate. Here's my best guess at dataypes:
CREATE TABLE ClinicsData
(
ClinicID INT IDENTITY(1,1),
ClinicName varchar(100)
)
CREATE TABLE DoctorData
(
DocID INT IDENTITY(1,1),
ClinicID INT,
Doctor VARCHAR(100)
)
Notice that I've made ClinicsData.ClinicID an IDENTITY column. This will help us to populate DoctorData later.
Next, let's populate ClinicsData with all the distinct clinic names.
INSERT INTO ClinicsData
(
ClinicName
)
SELECT DISTINCT
ClinicName
FROM CLINIC_DOC;
Now, we can utilize ClinicsData to populate DoctorData, using an INNER JOIN.
INSERT INTO DoctorData
(
ClinicID
,Doctor
)
SELECT DISTINCT
cd.ClinicID,
c_d.Doctor
FROM CLINIC_DOC c_d
INNER JOIN ClinicsData cd ON cd.ClinicName = c_d.ClinicName

Selecting the amount of users that have no 'thing' assigned to it

I have a simplified table that shows my problem
Name Animal
Bill Dog
Bill Cat
Bill Fish
John Cat
John Fish
Sara Dog
Sara Cat
Mark Fish
I want the number of people that have no dog. I tried this query.
select count(distinct Name) from Table
where Animal <> 'Dog'
But it returns 4 and not the expected 2. What am I doing wrong?
Your query returned the count of names that have any animal other than dog.
select distinct name
from table t1
where not exists
(
select 1 from table t2 where t1.name=t2.name and t2.Animal='Dog'
)
Use not exists :
select count(distinct t.name) as counts
from table t
where not exists (select 1 from table where name = t.name and animal = 'Dog');
For your current query you are filtering single record based on animal name which would not produced desired result as it should be along with name column.
your where condition "Animal <> 'dog'" is filtering the rows not the names.
So try the below query
Select count(distinct Name) from Table where name not in (
select Name from Table where Animal = 'Dog')

How can I insert data from two tables into one?

I have three tables , both with the same fields as in the example below:
Table:
dog
-------------
name, date
Table :
cat
-------------
name, date
Table:
animal
-------------
name, date
as I transfer the dog and cat data for animal table ? I tried the select into but could not do it with two tables.
Table value:
CAT
name date
Garfield 2015-08-03
DOG
name date
Spike 2015-08-03
Source:
insert into animal values ((select * from cat,dog))
Expected result
ANIMAL
name date
Garfield 2015-08-03
Spike 2015-08-03
Try this:
insert into animal
select name, date from dog
union all
select name, date from cat
It can be done with the execution of a query ie:Join operation.Table 1, Table 2,table 3. We have the same attribute in all these three tables.Just join the attributes with table1.fieldname JOIN table2.fieldname JOIN table3.fieldname
There is no need to do a JOIN as there is no relationship between them.
Insert Into animal (name, [date])
Select name, [date]
from dog, cat

Doing a SQL insert without having to look up a value in another table first

Imagine I have two tables:
PETS
SPECIES
The SPECIES table has two columns:
ID - Species ID Number
DESCRIPTION - Description of the species (DOG, CAT, TURTLE, etc.)
The PETS table has two columns:
NAME - Name of the pet
SPECIES a link to the ID field of the SPECIES table
If I want a list of all pets named FLUFFY who are cats, I can do the following in a single SQL statement:
select *
from NAME, SPECIES
where PETS.NAME = 'FLUFFY'
and SPECIES.DESCRIPTION = 'CATS'
and PETS.SPECIES = SPECIES.ID
Now, I have a new pet, BOWSER, who's a dog. I want to be able to say something like this:
INSERT INTO PETS
(PETS.NAME, SPECIES.DESCRIPTION)
VALUES
('BOWSER', 'DOG')
Yes, I know I can first find the SPECIES.ID for DOG, then do the insert:
my $species = $sql.execute(select ID from SPECIES where description = 'DOG');
insert into PETS (NAME, SPECIES)
values ('BOWSER', '$species')
But, I want to do the insert with in a single SQL statement much the way I could do my query. Is that possible?
What about:
INSERT INTO Pets(Name, Species)
SELECT 'Bowser' AS Name, ID AS Species
FROM Species
WHERE Description = 'DOG';
You can use a subselect like:
INSERT INTO Pets (
Name,
Species
) VALUES (
'Bowser',
(SELECT Id FROM Species WHERE Description='Dog')
)
INSERT INTO PETS
(
NAME,
SPECIES
)
SELECT
'BOWSER',
ID
FROM
SPECIES
WHERE
DESCRIPTION='DOG'

Add or delete repeated row

I have an output like this:
id name date school school1
1 john 11/11/2001 nyu ucla
1 john 11/11/2001 ucla nyu
2 paul 11/11/2011 uft mit
2 paul 11/11/2011 mit uft
I would like to achieve this:
id name date school school1
1 john 11/11/2001 nyu ucla
2 paul 11/11/2011 mit uft
I am using direct join as in:
select distinct
a.id, a.name,
b.date,
c.school
a1.id, a1.name,
b1.date,
c1.school
from table a, table b, table c,table a1, table b1, table c1
where
a.id=b.id
and...
Any ideas?
We will need more information such as what your tables contain and what you are after.
One thing I noticed is you have a school and then school1. 3nf states that you should never duplicate fields and append numbers to them to get more information even if you think that the relationship will only be 1 or 2 additional items. You need to create a second table that stores a user associated with 1 to many schools.
I agree with everyone else that both your source table and your desired output are poor design. While you probably can't do anything about your source table, I recommend the following code and output:
Select id, name, date, school from MyTable;
union
Select id, name, date, school1 from MyTable;
(repeat as necessary)
This will give you results in the format:
id name date school
1 john 11/11/2001 nyu
1 john 11/11/2001 ucla
2 paul 11/11/2011 mit
2 paul 11/11/2011 uft
(Note: in my version of SQL, union queries automatically select distinct records so the distinct flag isn't needed)
With this format, you could easily count the number of schools per student, number of students per school, etc.
If processing time and/or storage space is a factor here, you could then split this into 2 tables, 1 with the id,name & date, the other with the id & school (basically what JonH just said). But if you're just working up some simple statistics, this should suffice.
This problem was just too irresistable, so I just took a guess at the data structures that we are dealing with. The technology wasn't specified in the question. This is in Transact-SQL.
create table student
(
id int not null primary key identity,
name nvarchar(100) not null default '',
graduation_date date not null default getdate(),
)
go
create table school
(
id int not null primary key identity,
name nvarchar(100) not null default ''
)
go
create table student_school_asc
(
student_id int not null foreign key references student (id),
school_id int not null foreign key references school (id),
primary key (student_id, school_id)
)
go
insert into student (name, graduation_date) values ('john', '2001-11-11')
insert into student (name, graduation_date) values ('paul', '2011-11-11')
insert into school (name) values ('nyu')
insert into school (name) values ('ucla')
insert into school (name) values ('uft')
insert into school (name) values ('mit')
insert into student_school_asc (student_id, school_id) values (1,1)
insert into student_school_asc (student_id, school_id) values (1,2)
insert into student_school_asc (student_id, school_id) values (2,3)
insert into student_school_asc (student_id, school_id) values (2,4)
select
s.id,
s.name,
s.graduation_date as [date],
(select max(name) from
(select name,
RANK() over (order by name) as rank_num
from school sc
inner join student_school_asc ssa on ssa.school_id = sc.id
where ssa.student_id = s.id) s1 where s1.rank_num = 1) as school,
(select max(name) from
(select name,
RANK() over (order by name) as rank_num
from school sc
inner join student_school_asc ssa on ssa.school_id = sc.id
where ssa.student_id = s.id) s2 where s2.rank_num = 2) as school1
from
student s
Result:
id name date school school1
--- ----- ---------- ------- --------
1 john 2001-11-11 nyu ucla
2 paul 2011-11-11 mit uft