SQL Cross Join with null values - sql

I have a table AddressTypes with 4 records (home,office,vacation,hotel) and a table Address that share the filed addresstypeid.
In address table I have 1 record of type "home" and I want a query where I get 4 rows like this:
Type Address1 Address2 City State
home piping 1232 Austin Tx
office null null null null
vacation null null null null
hotel null null null null
Here is an image of tables: http://tinypic.com/view.php?pic=28078xv&s=6
I'm sure is something very easy maybe using cross join but don't get it. Hope someone can guide me. Appreciate in advance.

Left joining AdddressTypes to Addresses will produce desired result:
select at.Type,
a.Address1,
a.Address2,
a.City,
a.State
from AddressTypes at
left join Address a
on at.AddressTypeID = a.AddressTypeID
-- For this query to make sense
-- Filter one person only
and a.PersonID = #PersonID
----------- THIS PART WAS ADDED BY VAAA ------------------------
Nikola, I change to this:
select at.description,
a.Address1,
a.Address2,
a.City
from address_types at
left join Address a
on 1 = 1
-- For this query to make sense
-- Filter one person only
and a.addressid = 24
And then I get the 4 rows, but all of them have the same address info and just the "home" type address is the one with data. So its close...

Use the LEFT OUTER JOIN, which returns one copy of matching records from both the left and right tables and non-matching records from both the tables. Here is the test code I tried:
CREATE TABLE AddressTypes(
AddressType SMALLINT NOT NULL,
[Description] NVARCHAR(50))
ALTER TABLE AddressTypes
ADD CONSTRAINT PK_AddressTypes_AddressType
PRIMARY KEY (AddressType)
CREATE TABLE [Address] (
AddressID INT NOT NULL,
AddressType SMALLINT,
Address1 NVARCHAR(50),
Address2 NVARCHAR(50),
City NVARCHAR(50),
State CHAR(2))
ALTER TABLE [Address]
ADD CONSTRAINT PK_Address_AddressID
PRIMARY KEY (AddressID)
ALTER TABLE [Address]
ADD CONSTRAINT FK_address_addresstypes_addresstype
FOREIGN KEY (AddressType) REFERENCES AddressTypes(AddressType)
INSERT INTO AddressTypes VALUES (1, 'home'), (2, 'office'),
(3, 'vacation'), (4, 'hotel')
INSERT INTO address VALUES (1, 1, 'piping', '1232', 'Austin', 'TX')
-- Here is the query that outputs the result set.
SELECT AT.AddressType AS [Type], A.Address1, A.Address2, A.City, A.State
FROM AddressTypes AT
LEFT OUTER JOIN address A
ON AT.AddressType = A.AddressType

Related

Select multiple columns from another table depending on value of column

I'm not even sure this is possible, but I have an order table (online store). Within this table, the shipping and billing address columns are ID's which correspond with the address table.
For E.g.
OrderID ShippingAddressID BillingAddressID
201800194 21183 21182
The Address table then lists the address information.
AddressID Address1 City RegionCode
21182 123 Somewhere Dr Hometown1 Florida
21183 456 Elsewhere Rd Hometown2 Florida
I'd like the resulting listing to show something similar to this:
OrderID BillingAddress1 BillingCity BillingRegionCode ShippingAddress1 ShippingCity ShippingRegionCode
201800194 123 Somewhere Dr Hometown1 Florida 456 Elsewhere Rd Hometown2 Florida
Is this even possible?
Thanks..
Join the address table twice. Example with your data below.
CREATE TABLE #Orders (OrderID int, ShippingAddressId int, BillingAddressId int)
CREATE TABLE #Address (AddressID int, Address1 varchar(100), City varchar(100), RegionCode varchar(100))
INSERT INTO #Orders (OrderID, ShippingAddressId, BillingAddressId) VALUES
(201800194 , 21183, 21182)
INSERT INTO #Address (AddressID, Address1, City, RegionCode) VALUES
(21182, '123 Somewhere Dr', 'Hometown1', 'Florida'),
(21183, '456 Elsewhere Rd', 'Hometown2', 'Florida')
SELECT
ORD.OrderID,
BILL.Address1 AS 'BillingAddress1',
BILL.City AS 'BillingCity',
BILL.RegionCode AS 'BillingRegionCode',
SHIP.Address1 AS 'ShippingAddress1',
SHIP.City AS 'ShippingCity',
SHIP.RegionCode AS 'ShippingRegionCode'
FROM
#Orders AS ORD
LEFT OUTER JOIN #Address AS SHIP
ON ORD.ShippingAddressId = SHIP.AddressID
LEFT OUTER JOIN #Address AS BILL
ON ORD.BillingAddressId = BILL.AddressID
DROP TABLE #Orders
DROP TABLE #Address
Lets assume your Tables are Order, Address.
Select A.OrderID, B1.Address1 As 'BillingAddress1', B1.City As 'BillingCity', B1.RegionCode As 'BillingRegionCode', B2.Address1 As 'ShippingAddress1', B2.City As 'ShippingCity', B2.RegionCode As 'ShippingRegionCode'
FROM Order AS A
Left Join Address AS B1 On A.BillingAddressID = B1.AddressID
Left Join Address As B2 On A.ShippingAddressID = B2.AddressID
Hope this Helps
Yes you can achieve it using join like this -
SELECT OrderId, Billing.Address1 As BillingAddress1, Shipping.Address1 AS
ShippingAddress1, ... other column
FROM dbo.Order
LEFT JOIN dbo.Address AS Billing ON Order.BillingAddressID = Billing.AddressID
LEFT JOIN dbo.Address AS Shipping ON Order.ShippingAddressID = Shipping.AddressID

Sybase SQL join RESULT SET

I have a problem with a result set of two tables.
I create two tables.
create table person
(ID int not null PRIMARY KEY,
NAME varchar (50)
)
create table address
( ID int NOT NULL PRIMARY KEY,
PERSON_ID int not null,
ADDRESS VARCHAR (20) null,
ZIP int)
insert into person (ID, NAME) values ( 1,'Hans')
insert into person values (2,'Peter')
insert into address values (1,1, 'Andernach',56626)
insert into address values (2,2,'Koblenz',56000)
insert into address values (3,3,'Neuwied',56100)
I would like to get a result set of these tables.
If I use this query with p.ID = a.PERSON_ID
I get right result.
BUT if I use query with p.ID != a.PERSON_ID
I get a lot of results
select a.ADDRESS,
a.ZIP,
a.PERSON_ID,
p.NAME
from address a,
person p
where a.PERSON_ID != p.ID
My question: Why do I get a lot of results with !=?
Thanks
:-)
Because lots of rows in the first table don't match lots of rows in the second. In fact, almost all rows in the second will match each in the first. Presumably you want:
select a.ADDRESS,
a.ZIP,
a.PERSON_ID,
p.NAME
from person p left join
address a
on a.PERSON_ID = p.ID
where p.id is null;
This gets rows in person that don't have an address. (Putting the tables in the other order or using right join would get addresses with no person.)

SELECT value from a second level foreign key table

Greetings fellow Earthlings,
I have a problem. Let me start by laying out my table structure:
CREATE TABLE Person
(
id varchar(50) NOT NULL PRIMARY KEY,
name varchar(50) NOT NULL,
adress varchar(50) NOT NULL references Adress(id)
)
CREATE TABLE Adress
(
id varchar(50) NOT NULL PRIMARY KEY,
addressName varchar(50),
city varchar(50),
aState varchar(50),
linkToCountry varchar(50) references Country(id)
)
CREATE TABLE Country
(
id varchar(50) NOT NULL PRIMARY KEY,
countryName varchar(50)
)
What I want to achieve is: select a person 'name' along with their 'addressName' and the 'countryName' they're from.
I know that this is a joining related issue but I can't seem to figure this one out.
So any help from people who are well versed on SQL?
Would appreciate it very very much any one has links to advance sql joining so I can familiarize myself with it.
You can get the result using simple join as below. This will retrun the person name with address name, and country name. However it returns only those person names which has an address record in the address table and country record in the country table. If you want to retrieve all the persons irrespective of whether address/country exists or not, you need to use left join.
SELECT Person.Name, Address.addressname,Country.countryName
FROM Person
JOIN Address on Person.address = Address.Id
JOIN Country ON Address.linkToCountry = Country.id
Try this:
SELECT p.name, a.addressName, c.countryName
FROM Person p
INNER JOIN Adress a ON p.adress = a.id
LEFT OUTER JOIN Country c ON a.linkToCountry = c.id

How to transform vertical table into horizontal table?

I have one table Person:
Id Name
1 Person1
2 Person2
3 Person3
And I have its child table Profile:
Id PersonId FieldName Value
1 1 Firstname Alex
2 1 Lastname Balmer
3 1 Email some_email#test.com
4 1 Phone +1 2 30004000
And I want to get data from these two tables in one row like this:
Id Name Firstname Lastname Email Phone
1 Person1 Alex Balmer some_email#test.com +1 2 30004000
What is the most optimized query to get these vertical (key, value) values in one row like this? Now I have a problem that I done four joins of child table to parent table because I need to get these four fields. Some optimization is for sure possible.
I would like to be able to modify this query in easy way when I add new field (key,value). What is the best way to do this? To create some stored procedure?
I would like to have strongly types in my DB layer (C#) and using LINQ (when programming) so it means when I add some new Key, Value pair in Profile table I would like to do minimal modifications in DB and C# if possible. Actually I am trying to get some best practices in this case.
Select
P.ID
, P.Name
, Case When C.FieldName = 'FirstName' Then C.Value Else NULL END AS FirstName
, Case When C.FieldName = 'LastName' Then C.Value Else NULL END AS LastName
, Case When C.FieldName = 'Email' Then C.Value Else NULL END AS Email
, Case When C.FieldName = 'Phone' Then C.Value Else NULL END AS Phone
From Person AS P
Inner JOIN Child AS C
ON P.ID = C.PersonID
You could use PIVOT; not sure which one would be the easiest for you to add a new column.
best optimized way with strongly typed fields, is to do it this way:
CREATE TABLE Persons
(PersonID int identity(1,1) primary key
,Firstname varchar(...)
,Lastname varchar(...)
,Email varchar(...)
,Phone varchar(...)
,....
)
then the most optimized query would be:
SELECT
PersonID,Firstname,Lastname,Email,Phone
FROM Persons
WHERE ...
Add all main columns into the persons table. if you need to specialize create additional tables:
--one person can play many instruments with this table
CREATE TABLE PersonMusicians
(PersonID int --pk fk to Persons.PersonID
,InstrumentCode char(1) --pk
,...
)
--only one row per person with this table
CREATE TABLE PersonTeachers
(PersonID int --pk fk to Persons.PersonID
,FavoriteSubjectCode char(1)
,SchoolName varchar(...)
)
if you have to have unlimited dynamic attribute fields, then I would create the above structure as fully as possible (as many common fields as possible) and then have an "AdditionalInfo" table where you store all the info like:
AdditionalInfoFields
FieldID int identity(1,1) primary key
FieldName varchar(...)
AdditionalInfo
AdditionalInfoID int identity(1,1) primary key
PersonID int fk to Persons.PersonID
FieldID int fk to AdditionalInfoFields.FieldID
FieldValue varchar(..) or you can look into sql_variant
have an index on AdditionalInfo.PersonID+FieldID and if you will search for all people that have attribute X, then also another like AdditionalInfo.FieldID+PersonID
short of any of the above, you will need to use the four left outer joins like you have mentioned in your option #1:
SELECT
P.ID, p.Name
, p1.Value AS Firstname
, p2.value AS Lastname
, p3.Value AS Email
, p4.Value AS Phone
FROM Persons p
LEFT OUTER JOIN Profile p1 ON p.PersonID=p1.PersonID AND p1.FieldName='Firstname'
LEFT OUTER JOIN Profile p1 ON p.PersonID=p1.PersonID AND p1.FieldName='Lastname'
LEFT OUTER JOIN Profile p1 ON p.PersonID=p1.PersonID AND p1.FieldName='Email'
LEFT OUTER JOIN Profile p1 ON p.PersonID=p1.PersonID AND p1.FieldName='Phone'
WHERE ....
you could always make a materialized view with an index out of this 4 left join query and have the data precalculated for you which should speed it up.

What's the best way to get related data from their ID's in a single query?

I have a table where each row has a few fields that have ID's that relate to some other data from some other tables.
Let's say it's called people, and each person has the ID of a city, state and country.
So there will be three more tables, cities, states and countries where each has an ID and a name.
When I'm selecting a person, what's the easiest way to get the names of the city, state and country in a single query?
Note: I know this is possible with joins, however as there are more related tables, the nested joins makes the query hard to read, and I'm wondering if there is a cleaner way. It should also be possible for the person to have those fields empty.
Assuming the following tables:
create table People
(
ID int not null primary key auto_increment
,FullName varchar(255) not null
,StateID int
,CountryID int
,CityID int
)
;
create table States
(
ID int not null primary key auto_increment
,Name varchar(255) not null
)
;
create table Countries
(
ID int not null primary key auto_increment
,Name varchar(255) not null
)
;
create table Cities
(
ID int not null primary key auto_increment
,Name varchar(255) not null
)
;
With the Following Data:
insert into Cities(Name) values ('City 1'),('City 2'),('City 3');
insert into States(Name) values ('State 1'),('State 2'),('State 3');
insert into Countries(Name) values ('Country 1'),('Country 2'),('Country 3');
insert into People(FullName,CityID,StateID,CountryID) values ('Has Nothing' ,null,null,null);
insert into People(FullName,CityID,StateID,CountryID) values ('Has City' , 1,null,null);
insert into People(FullName,CityID,StateID,CountryID) values ('Has State' ,null, 2,null);
insert into People(FullName,CityID,StateID,CountryID) values ('Has Country' ,null,null, 3);
insert into People(FullName,CityID,StateID,CountryID) values ('Has Everything', 3, 2, 1);
Then this query should give you what you are after.
select
P.ID
,P.FullName
,Ci.Name as CityName
,St.Name as StateName
,Co.Name as CountryName
from People P
left Join Cities Ci on Ci.ID = P.CityID
left Join States St on St.ID = P.StateID
left Join Countries Co on Co.ID = P.CountryID
JOINS are the only way to really do this.
You might be able to change your schema, but the problem will be the same regardless.
(A City is always in a State, which is always in a Country - so the Person could just have a reference to the city_id rather than all three. You still need to join the 3 tables though).
There is no cleaner way than joins. If the fields are allowed to be empty, use outer joins
SELECT c.*, s.name AS state_name
FROM customer c
LEFT OUTER JOIN state s ON s.id = c.state
WHERE c.id = 10
According to the description of the schema that you have given you will have to use JOINS in a single query.
SELECT
p.first_name
, p.last_name
, c.name as city
, s.name as state
, co.name as country
FROM people p
LEFT OUTER JOIN city c
ON p.city_id = c.id
LEFT OUTER JOIN state s
ON p.state_id = s.id
LEFT OUTER JOIN country co
ON p.country_id = co.id;
The LEFT OUTER JOIN will allow you to fetch details of person even if some IDs are blank or empty.
Another way is to redesign your lookup tables. A city is always in a state and a state in a country. Hence your city table will have columns : Id, Name and state_id. Your state table will be : Id, Name and country_id. And country table will remain the same : Id and Name.
The person table will now have only 1 id : city_id
Now your query will be :
SELECT
p.first_name
, p.last_name
, c.name as city
, s.name as state
, co.name as country
FROM people p
LEFT OUTER JOIN city c
ON p.city_id = c.id
LEFT OUTER JOIN state s
ON c.state_id = s.id
LEFT OUTER JOIN country co
ON s.country_id = co.id;
Notice the difference in the last two OUTER JOINS
If the tables involved are reference tables (i.e. they hold lookup data that isn't going to change during the life time of a session), depending on the nature of your application, you could pre-load the reference data during you application start up. Then your query doesn't need to do the joins, instead it returns the id values, and in your application you do a decode of the ids when you need to display the data.
The easiest solution is to use the names as the primary keys in city, state, and country. Then your person table can reference them by the name instead of the pseudokey "id". That way, you don't need to do joins, since your person table already has the needed values.
It does take more space to store a string instead of a 4-byte pseudokey. But you may find the tradeoff worthwhile, if you are threatened by joins as much as you seem to be (which, by the way, is like a PHP programmer being reluctant to use foreach -- joins are fundamental to SQL in the same way).
Also there are many city names that appear in more than one state. So your city table should reference the state table and use these two columns as the primary key.
CREATE TABLE cities (
city_name VARCHAR(30),
state CHAR(2),
PRIMARY KEY (city_name, state),
FOREIGN KEY (state) REFERENCES states(state)
);
CREATE TABLE persons (
person_id SERIAL PRIMARY KEY,
...other columns...
city_name VARCHAR(30),
state CHAR(2),
country_name VARCHAR(30),
FOREIGN KEY (city_name, state) REFERENCES cities(city_name, state),
FOREIGN KEY (country_name) REFERENCES countries(country_name)
);
This just an example of the technique. Of course it's more complex than this, because you may have city names in more than one country, you may have countries with no states, and so on. The point is SQL doesn't force you to use integer pseudokeys, so use CHAR and VARCHAR keys where appropriate.
A disadvantage of standard SQL is the the return data needs to be in tabular format.
However some database vendors have added features that makes it possible to select data in non-tabular format. I don't know whether MySQL knows such features.
Create a view that does the Person, City, State, and Country joins for you. Then just reference the View in all other joins.
Something like:
CREATE VIEW FullPerson AS
SELECT Person.*, City.Name, State.Name, Country.Name
FROM
Person LEFT OUTER JOIN City ON Person.CityId = City.Id
LEFT OUTER JOIN State ON Person.StateId = State.Id
LEFT OUTER JOIN Country ON Person.CountryId = Country.Id
Then in other queries, you can
SELECT FullPerson.*, Other.Value
FROM FullPerson LEFT OUTER JOIN Other ON FullPerson.OtherId = Other.Id
All great answers but the questioner specified they didn't want to use joins. As one respondent demonstrated, assuming your Cities, States, and Countries tables have an Id and a Description field you might be able to do something like this:
SELECT
p.Name, c.Description, s.Description, ct.Description
FROM
People p, Cities c, States s, Countries ct
WHERE
p.Id = value AND
c.Id = value AND
s.Id = value AND
ct.Id = value;
Joins are the answer. With practise they will become more readable to you.
There may be special cases where creating a function would help you, for example you could do the following (in Oracle, I don't know any mysql):
You could create a function to return a formatted address given the city state and country codes, then your query becomes
SELECT first_name, last_name, formated_address(city_id, state_id, country_id)
FROM people
WHERE some_where_clause;
where formated_address does individual lookups on the city state and country tables and puts separators between the decoded values, or returns "no address" if they are all empty, etc