This is going to be a little complicated. Let me start with my tables.
clients [src = 0]
---------
clientID code company
--------- ------- ---------
1 ABC ABC Corp
2 DEF DEF Corp
carriers [src = 1]
---------
clientID code company
--------- ------- -------
1 ABC ABC Inc.
2 JHI JHI Inc.
link
--------
contactID uID src
--------- ----- ----
1 1 0
1 1 1
1 2 0
contact info
--------------
contactID fname lname
--------- ------- --------
1 John Smith
2 Quincy Jones
So, i'm trying to do a search for say "ABC" on the link table. The link table needs to basically join to either the carriers or clients table depending on the link.src column. It should find two matches, one in the clients and one in the carriers, but since both resolve to contactID (links table) of 1, i should then query the contact info table and return
Found 1 record(s):
John Smith
I hope this makes sense. Any help is greatly appreciated!
Here is one approach using left join:
select co.*
from link l left join
clients cl
on l.src = 0 and l.uid = cl.code left join
carriers ca
on l.src = 1 and l.uid = ca.code left join
contacts co
on l.contactid = co.contactid
where 'ABC' in (co.code, cl.code)
Here is another approach. First, you UNION the Clients and Carriers tables and add a new column ContactType to differentiate one from the other. Use 0 for Clients and 1 for Carriers, the same as src. Then you perform a LEFT JOIN to get the desired result.
;WITH Clients(ClientID, Code, Company) AS(
SELECT 1, 'ABC', 'ABC Corp' UNION ALL
SELECT 2, 'DEF', 'DEF Corp'
)
,Carriers(ClientID, Code, Company) AS(
SELECT 1, 'ABC', 'ABC Inc.' UNION ALL
SELECT 2, 'JHI', 'JHI Inc.'
)
,Link(ContactId, UID, Src) AS(
SELECT 1, 1, 0 UNION ALL
SELECT 1, 1, 1 UNION ALL
SELECT 1, 2, 0
)
,ContactInfo(ContactID, FName, LName) AS(
SELECT 1, 'John', 'Smith' UNION ALL
SELECT 2, 'Quincy', 'Jones'
)
-- START
,Contact(ContactID, ContactType, Code, Company) AS(
SELECT
ClientID, 0, Code, Company
FROM Clients
UNION ALL
SELECT
ClientID, 1, Code, Company
FROM Carriers
)
SELECT DISTINCT
ci.FName,
ci.LName
FROM Link l
LEFT JOIN Contact c
ON c.ContactID = l.UID
AND c.ContactType = l.src
LEFT JOIN ContactInfo ci
ON ci.ContactID = c.ContactID
WHERE
c.Code = 'ABC'
Look at this from a modeling pov. You have two tables with the same type of data in each one, the entity company. The only difference between them is their role or relationship to your company. So why not keep them all in the same bucket?
Companies:
ID code Name
-- ---- ---------
1 ABC ABC Corp
2 DEF DEF Corp
3 JHI JHI Inc.
If a particular company could only be a client or a carrier, that designation could be placed in the Companies table. Since obviously one company can be both, the designation goes into a separate table. The following shows that company 1, 'ABC', is both a client ('L') and carrier ('R'), company 2 is only a client and company 3 is only a carrier.
CompanyRoles:
CompanyID Type
--------- ----
1 'L'
1 'R'
2 'L'
3 'R'
There is no need to keep multiple copies of the same data just because a company can play multiple roles. If there is role-dependent data, data that is maintained on client but not for carriers, or vice versa, then subtables can keep that.
As for the contacts, if a company has one contact no matter the role, the contact reference can be added to the Companies table. If the contact is role dependent, it is added to the CompanyRoles table.
CompanyRoles:
CompanyID Type ContactID
--------- ---- ---------
1 'L' 1
1 'R' 2
2 'L' 3
3 'R' 4
Want to see a list of clients?
select c.ID as ClientID, c.Code as ClientCode, c.Name as ClientName,
ci.ContactName
from Companies c
join CompanyRoles cr
on cr.CompanyID = c.ID
and cr.Type = 'L'
left join Contacts ct -- In case no contact is currently defined
on ct.ContactID = cr.ContactID
join ClientSpecificData csd
on csd.ClientID = c.ID;
Want to see a list of carriers?
select c.ID as CarrierID, c.Code as CarrierCode, c.Name as CarrierName,
ci.ContactName
from Companies c
join CompanyRoles cr
on cr.CompanyID = c.ID
and cr.Type = 'R'
left join Contacts ct -- In case no contact is currently defined
on ct.ContactID = cr.ContactID
join CarrierSpecificData csd
on csd.ClientID = c.ID;
You can create views on the last two queries to provide a single data source for those apps that deal with only Clients or only Carriers. Triggers on the views can deal with incoming DML statements as needed to route the data to the appropriate tables.
As you can see, the queries are clean and simple. Data integrity is easy and scalability is not an issue. What more could you want?
Related
I have a requirement to write a query that finds records related to a record in another table that aren't related to another record.
Below is an example of what I mean. I will happily rewrite this question and title if I can express the question in a better way (advice welcome).
Table company
id
1
2
3
Table company_partner
id company_id company_name
1 1 Nike
2 1 Reebok
3 2 Nike
4 3 Nike
In the above example, I would like all companies partnered with Nike but not if they are also partnered with Reebok. Using the above example that would be companies 2 and 3.
I can write a query that gives me all companies partnered with Nike:
SELECT c.id
FROM company c
INNER JOIN company_partner cp ON c.id = cp.company_id
WHERE
cp.company_name = 'Nike'
-- ignore cp.company_name = 'Reebok' ???
I am unclear how I can ignore companies that are also partnered with Reebok?
You should be able to use not in - like this:
SELECT c.id
FROM company c
INNER JOIN company_partner cp ON c.id = cp.company_id
WHERE cp.company_name = 'Nike'
AND c.id not in (
select id from company_partner where company_name = 'Reebok'
)
Aggregation provides one straightforward option:
SELECT company_id
FROM company_partner
GROUP BY company_id
HAVING COUNT(CASE WHEN company_name = 'Nike' THEN 1 END) > 0 AND
COUNT(CASE WHEN company_name = 'Reebok' THEN 1 END) = 0;
We have two tables for "Physical Location" and "Stores". One Physical Location can have multiple stores. Each Store can be either Active or Inactive.
How would we write a query to find those 'Physical Locations' that has no active Stores associated to it?
PhysicalLocation
LocationId LocationAddress
100 Address1
101 Address2
102 Address3
Store
StoreID LocationId Status
100A 100 Active
100B 100 Inactive
101C 101 Inactive
102D 101 Inactive
I have tried something like the below.
Select * from PhysicalLocation where LocationId IN
(Select LocationId from Store where Status <> 'Active')
My expected result is
LocationId LocationAddress
101 Address2
Since this location has only inactive stores
select l.LocationId,l.LocationAddress
from locations as l
where not exists
(
select 1 from stores as s
where l.LocationId=s.LocationId
and s.Status='Active'
)
Could you please try this one if it is suitable for you.
By the way, location 102 also does not contain active stores
Use Joins
SELECT * FROM PhysicalLocation
INNER JOIN Store ON Store.LocationId = PhysicalLocation.LocationId
WHERE Store.Status = 'Inactive'
I have used INNER JOIN with the assumption that no store locations e.g 102 Address3, is as good as the location with inactive store(s).
If the assumption is incorrect and you want all inactive along with locations where there are no stores, then go for LEFT OUTER JOIN
The query will be like this.
SELECT * FROM PhysicalLocation AS Ph INNER JOIN Store AS St ON Ph.LocationId = St.LocationId WHERE St.Status = 'Inactive';
Use Join to make relation between two tables. This is will give you resultant values you are looking for.
Output:
LocationId LocationAddress StoreID LocationId Status
100 Address1 100B 100 Inactive
101 Address2 101C 101 Inactive
101 Address2 102D 101 Inactive
You may use the following which uses a having clause with a case expression in a count to filter out locations that have active stores. The join is used to retrieve additional location details.
SELECT
P.*
FROM
PhysicalLocation P
INNER JOIN (
SELECT LocationId
FROM Store
GROUP BY LocationId
HAVING COUNT(CASE WHEN Status='Active' THEN 1 END) =0
) SA ON p.LocationId=SA.LocationId
LocationId
LocationAddress
101
Address2
If you are interested in all locations with no active stores regardless of whether a store entry has ever been created then you may try the following which uses a left join and where clause to apply the filter instead of an inner join as above.
SELECT
P.*
FROM
PhysicalLocation P
LEFT JOIN (
SELECT
LocationId,
COUNT(CASE WHEN Status='Active' THEN 1 END) as no_active_locations
FROM Store
GROUP BY LocationId
) SA ON p.LocationId=SA.LocationId
WHERE SA.no_active_locations=0 OR SA.no_active_locations IS NULL
LocationId
LocationAddress
101
Address2
102
Address3
View working demo db fiddle
You need to select what locations have active stores and filter out those locations in the WHERE clause.
SELECT S.LocationId
,LocationAddress
FROM #Store S
INNER JOIN #PhysicalLocation PL
ON S.StoreID = PL.LocationId
WHERE S.LocationId NOT IN (SELECT LocationId FROM #Store WHERE [status] = 'active')
OUTPUT:
This is slightly different than the desired output you specified, but I suspect you did not account for the additional address. There is still only 1 location with no active stores.
Here is the simplest solution :
select LocationId, LocationAddress from PhysicalLocation
where LocationId in
(
select Distinct (LocationId) from Store
where LocationId not in (select LocationId from Store where Status = 'Active')
)
I am wondering how to use 2 instances of the same table in the following example , I know how to do it but I just cant make it work for my task.
I have the following tables :
Agency(id_agency,name)
Space(id_space,address)
Offer(id_agency,id_space)
Task: Find out the address,agency name 1 ,agency name 2 for spaces offered by two different agencies(the combination of 2 agencies is unique).
What I tried:
1)
SELECT ADDRESS,A.NAME,B.NAME
FROM AGENCY A
INNER JOIN OFFER O ON A.ID_AGENCY=O.ID_AGENCY
INNER JOIN SPACE S ON O.ID_SPACE=S.ID_SPACE
WHERE S.ID_SPACE=ANY(SELECT S.ID_SPACE FROM AGENCY B
INNER JOIN OFFER O ON B.ID_AGENCY=O.ID_AGENCY
INNER JOIN SPACE S ON O.ID_SPACE=S.ID_SPACE
);
2)
SELECT ADDRESS
FROM AGENCY A
INNER JOIN OFFER O ON A.ID_AGENCY=O.ID_AGENCY
INNER JOIN SPACE S ON O.ID_SPACE=S.ID_SPACE
INTERSECT
SELECT ADDRESS
FROM AGENCY B
INNER JOIN OFFER O ON B.ID_AGENCY=O.ID_AGENCY
INNER JOIN SPACE S ON O.ID_SPACE=S.ID_SPACE
WHERE A.ID_AGENCY<>B.ID_AGENCY;
In the second example I have no idea how to make it show A.name and b.name, since intersect won't work if I try to add them...
I tried to do this for the last 3 hours , sadly, I guess I can`t do it with the skills I have so far. :(
Thanks in advance
Edit 1: I hope you understand, thats how it should look.
Agency
id_agency name
---------- -------
1 Agency1
2 Agency2
3 Agency3
Space
id_space address
--------- --------
1 address1
2 address2
3 address3
Offer
id_agency id_space
----------- --------
1 1
2 1
3 2
Expected output:
Address Name1 Name2
----------- -------- -------
address1 Agency1 Agency2
To have the results in 1 row, each pair of agencies per row, as you asked for:
select S.address as address, A1.name as agency_1, A2.name as agency_2
from offer O1
join offer O2
on O2.id_space = O1.id_space
and O2.id_agency != O1.id_agency
join space S
on S.id_space = O1.id_space
join agency A1
on A1.id_agency = O1.id_agency
join agency A2
on A2.id_agency = O2.id_agency
;
The "core functionality" here is the join of offer no.1 (O1 alias) to offer no.2 (O2 alias) on equality of id_space but difference of id_agency.
An interesting exercise: To have the results in multiple rows, one agency per row:
select S.address, A.name as agency, X.number_of_agencies_per_space
from (
select id_space, id_agency, count(1) over (partition by id_space) as number_of_agencies_per_space
from offer
) X
join space S
on S.id_space = X.id_space
join agency A
on A.id_agency = X.id_agency
where X.number_of_agencies_per_space > 1
;
id1 id2 year State Gender
==== ====== ====== ===== =======
1 A 2008 ca M
1 B 2008 ca M
3 A 2009 ny F
3 A 2008 ny F
4 A 2009 tx F
select
state, gender, [year],
count (distinct(cast(id1 as varchar(10)) + id2))
from
tabl1
group by state, gender, [year]
i could find the distinct count through statewise.
now i need to find distinct count through city wise. like in CA - 3 cities.. sfo,la,sanjose. i have a look up table that i could find the state and the city.
table2 - city
====
cityid name
==== ====
1 sfo
2 la
3 sanjose
table 3 - state
====
stateid name
==== ====
1 CA
2 Az
table 4 lookup state city
====
pk_cityId pk_state_id
1 1
2 1
select state,city,gender, [year],
count (distinct(cast(id1 as varchar(10)) + id2))
from
tabl1 p
group by state, gender, [year],city
this query to find city and state name.
select c.city,s.state from city_state sc
inner join (select * from state)s on sc.state_id = s.state_id
inner join (select * from city)c on sc.city_id = c.city_id
i did similar to this query using the look up table but the problem is that i get the distinct count throughout the states and the same no of count is repeating for each city in the state.
ex: for count for ca : 10 then the count for cities should be like La - 5, sanjose - 4, sfo-1.
but with my query i get as sfo - 10,la-10, sanjose-10.. i couldnt find the count for the lower level. any help would be appreciated.
UPDATE:
i have updated the query and the lookup tables.
Your implied schema seems to have a flaw:
You're trying to get city level aggregates but you are joining your data table (table1) to your city table (table2) based on the state. This will cause EVERY city in the same state to have the same aggregate values; in your case: all California states having count of 10.
Can you provide actual DDL statements for your two tables? Perhaps you have other columns there (city_id?) that might provide the necessary data for you to correct your query.
I think you need something like the following, but can't be sure w/o further information:
;WITH DistinctState AS
(
SELECT DISTINCT
id1
, id2
, [year]
, [State]
, Gender
FROM tab1
)
SELECT s.state
, c.city
, gender
, [year]
, count(*)
FROM DistinctState s
INNER JOIN
tab2 c
ON s.id1 = c.id1
AND s.id2 = c.id2
GROUP BY
s.state
, c.city
, gender
, [year]
should be simple enough but it's causing me a couple of issues.
I have a data set similar to the following:
User
UserID
Name
Age
UserPropertyValues
UserID
PropertyCodeValueID
PropertyCodes
PropertyCodeID
PropertyCodeName
PropertyCodeValues
PropertyCodeValueID
PropertyCodeID
PropertValue
Now let's assume the tables contain the following data:
1 John 25
2 Sarah 34
1 2
1 3
2 1
2 3
1 FavColour
2 CarMake
3 PhoneType
1 1 Blue
2 1 Yellow
3 2 Ford
4 3 Mobile
5 3 Landline
Now from this I'm looking to create a view to return the User details, as well as the property values for Property code 1 and 2 like so:
John 25 Yellow Ford
Sarah 34 Blue Ford
The queries I have tried so far tend to return repeating rows of data :
John 25 Yellow
John 25 Ford
Sarah 34 Blue
Sarah 34 Ford
Any help is appreciated, thank you all in advance.
Input data:
DECLARE #User TABLE (UserID INT, Name VARCHAR(10), Age INT)
INSERT INTO #User
SELECT 1, 'John', 25 UNION
SELECT 2, 'Sarah', 34
DECLARE #UserPropertyValues TABLE(UserID INT, PropertyCodeValueID INT)
INSERT INTO #UserPropertyValues
SELECT 1, 2 UNION
SELECT 1, 3 UNION
SELECT 2, 1 UNION
SELECT 2, 3
DECLARE #PropertyCodes
TABLE (PropertyCodeID INT, PropertyCodeName VARCHAR(10))
INSERT INTO #PropertyCodes
SELECT 1, 'FavColour' UNION
SELECT 2, 'CarMake' UNION
SELECT 3, 'PhoneType'
DECLARE #PropertyCodeValues TABLE (PropertyCodeValueID INT,
PropertyCodeID INT, PropertValue VARCHAR(10))
INSERT INTO #PropertyCodeValues
SELECT 1, 1, 'Blue' UNION
SELECT 2, 1, 'Yellow' UNION
SELECT 3, 2, 'Ford' UNION
SELECT 4, 3, 'Mobile' UNION
SELECT 5, 3, 'Landline'
If two properties is all that you need in result, and each user have those properties, then try this:
SELECT U.Name, U.Age, PCVFC.PropertValue, PCVCM.PropertValue
FROM #User U
INNER JOIN #UserPropertyValues UPVFC ON U.UserID = UPVFC.UserID
INNER JOIN #PropertyCodeValues PCVFC
ON UPVFC.PropertyCodeValueID = PCVFC.PropertyCodeValueID
AND PCVFC.PropertyCodeID = 1
INNER JOIN #UserPropertyValues UPVCM ON U.UserID = UPVCM.UserID
INNER JOIN #PropertyCodeValues PCVCM
ON UPVCM.PropertyCodeValueID = PCVCM.PropertyCodeValueID
AND PCVCM.PropertyCodeID = 2
[edit] But to handle possible NULL values better use this:
SELECT U.Name, U.Age, FC.PropertValue, CM.PropertValue
FROM #User U
LEFT JOIN (
SELECT UserID, PropertValue FROM #UserPropertyValues UPV
INNER JOIN #PropertyCodeValues PCV
ON UPV.PropertyCodeValueID = PCV.PropertyCodeValueID
AND PCV.PropertyCodeID = 1
) FC ON U.UserID = FC.UserID
LEFT JOIN (
SELECT UserID, PropertValue FROM #UserPropertyValues UPV
INNER JOIN #PropertyCodeValues PCV
ON UPV.PropertyCodeValueID = PCV.PropertyCodeValueID
AND PCV.PropertyCodeID = 2
) CM ON U.UserID = CM.UserID
What you really need to is abandon this type of database design as soon as humanly possible. It will never be either effective of efficient. To get three types of values you have to join to the table three times. Once you have 30 or forty differnt types of information, you will need to join to the table that many times (and left joins at that). Further everytime you want any information you will need to join to this table. I see this as creating a major locking issue in your database. The people who originally designed one of the databases I work with did this and caused a huge performance issue when the company grew from having one or two customers to the largest in our industry.
If the properties are ones that will likely only have one realted records per person, put them into the user table. If they will have multiple records then create a separate table for each type of information (one for hones, one for email, one for cartype, etc.) Since the information you will eventually want to collect will usually be more than the simple value and differnt for each type of information they must be in separate tables. Then when you only need to see one value (say phone number but not email) you join to just that table and you aren't interfeing with people trying to access email but not phone number. And if you have a yellow ford or white Honda, it will be stored in only one record in the auto table rather than two property records in your design.
SELECT
u.[Name],
u.Age,
pcv1.PropertValue,
pcv2.PropertValue
FROM
Users u
LEFT JOIN
( UserPropertyValues upv1
JOIN PropertyCodeValues pcv1 ON
upv1.PropertyCodeValueID = pcv1.PropertyCodeValueID
AND pcv1.PropertyCodeID = 1
)
ON upv1.UserID = u.UserID
LEFT JOIN (
UserPropertyValues upv2
JOIN PropertyCodeValues pcv2 ON
upv2.PropertyCodeValueID = pcv2.PropertyCodeValueID
AND pcv2.PropertyCodeID = 2
)
ON upv2.UserID = u.UserID
Edit : I renamed user to users
Edit2 : Allow for null (not entered values)