Help with Complex SQL Query - sql

I'm still a neophyte when it comes to SQL queries, so I was hoping someone could lend me a hand.
Assume I have 3 tables, Skill, Set, and Contact, and 2 linking tables, SkillSet and ContactSet. (the 3 tables have an "ID" column that is being used as a primary key)
Contacts can have any number of sets, and vice versa (many-to-many)
Sets can have any number of skills, and vice versa (also many-to-many)
What I want is, when presented with a skill's ID, to return every contact with a set containing that skill.
Still with me?
Here's what I've got so far:
SELECT Set.ID as expr1
FROM SkillSet
WHERE Skill.ID = #SkillID
//this selects every set containing the Skill.
SELECT Contact.ID
FROM ContactSet
WHERE SET.ID = ?
//this is where I run into problems. I want to use the records returned from
//the previous SELECT query (expr1) but I'm unsure how.
//Would it be expr1.ID or expr1.Set.ID?
Thank you.

this should work just fine or at least lead you to the final solution, you only need the one query:
declare #skillid int
set #skillid = 1
SELECT C.*
FROM contact c
inner join SetContact SC on (sc.contactId = C.contactId)
inner join SkillSet SS on (ss.SetId = SC.SetId)
WHERE (SS.SkillId = #SkillId)

Related

Display correct results in many to many relatonship tables

I currently have three tables.
master_tradesmen
trades
master_tradesmen_trades (joins the previous two together in a many-to-many relationship). The 'trade_id' and 'master_tradesman_id' are the foreign keys.
Here is what I need to happen. A user performs a search and types in a trade. I need a query that displays all of the information from the master_tradesmen table whose trade in the master_tradesmen_trade table matches the search. For example, if 'plumbing' is typed in the search bar (trade_id 1), all of the columns for Steve Albertsen (master_tradesman_id 6) and Brian Terry (master_tradesman_id 8) would be displayed from the master_tradesmen table. As a beginner to SQL, trying to grasp the logic of this is about to make my head explode. I'm hoping that someone with more advanced SQL knowledge can wrap their head around this much easier than I can. Note: the 'trades' column in master_tradesmen is for display purposes only, not for querying. Thank you so much in advance!
You have a catalog for the tradesmen, & another catalog for the trades.
The trades should only appear once in the trades catalog in order to make your DB more consistent
Then you have your many-to-many table which connects the trades & master tradesmen tables.
If we want to get the tradesmen according to the given trade in the input, we should first
know the id of that trade which has to be unique, so in your DB you would have something
like the img. below :
Now we can make a query to select the id of trade :
DECLARE #id_trade int = SELECT trade_id FROM trades WHERE trade_name LIKE '%plumbing%'
Once we know the trading id, we can redirect to the 'master_tradesmen_trades' table to know the name of the people how work that trade :
SELECT * FROM master_tradesmen_trades WHERE trade_id = #id_trade
You will get the following result :
You may say, 'But there is still something wrong with it, as i am still not able to see the tradesmen', this is the moment when we make an inner join:
SELECT * FROM master_tradesmen_trades trades_and_tradesmen
INNER JOIN master_tradesman tradesmen
ON tradesmen.id = trades_and_tradesmen.master_tradesmen_id
WHERE trade_id = #id_trade
IF you need to see specific columns, you can do :
SELECT first_name, last_name, city, state FROM master_tradesmen_trades trades_and_tradesmen
INNER JOIN master_tradesman tradesmen
ON tradesmen.id = trades_and_tradesmen.master_tradesmen_id
WHERE trade_id = #id_trade

New to SQL and need some advice with queries

I have recently started learning SQL but can't seem to get my head around creating SQL statements that form relevant results from multiple tables/relations.
Given the following schema:
Account(accNumber, balance, type)
Branch(BSB, phone, streetAddress, town)
registered(accNumber*, BSB*)
I am trying to formulate some outputs:
List all the accNumber registered with a specific BSB (123) and show its listed town (Sydney).
I have tried the following statement for the first query:
SELECT accNumber, BSB, town
FROM ACCOUNT, BRANCH
WHERE BSB = 123;
However, I get every account listed even if they don't belong to the BSB, so I tried:
SELECT accNumber, BSB, town
FROM ACCOUNT, BRANCH
WHERE BSB = 123
AND Town = 'Sydney'
AND account.accNumber=registered.accNumber
AND branch.bsb=registered.bsb;
This time I get column ambiguously defined because they have the same name in the "registered" table.
I've tried making alias in the select statment i.e. accNumber AS ACCOUNT_NUMBER etc, but still getting ambiguously defined errors.
I tried just listing what was in the registered table but then I do not get the town name, just the accNumber and the BSB passed in as a foreign key.
I can't seem to understand how to pull data from other tables and display them correctly and would greatly appreciate any advice!
This might help you start.
SELECT a.ccNumber, b.BSB, c.town
FROM ACCOUNT as a
inner join registered as b on b.accNumber=a.accNumber
inner join BRANCH as c on c.bsb = b.bsb
WHERE b.BSB = 123
AND c.Town = 'Sydney'
So this sounds like a generic SQL question. For your query here is what you're looking at:
select a.account_number
from account a, brance b, registered r
where a.account_number = r.account_number and
a.bsb = b.bsb and
b.bsb = 123;
This will get you all account numbers from the account table that are in BSB 123. When you have multiple tables that have the same column, you need to tell Oracle (and any database for that matter) which "account_number" column you're referring to (as otherwise it's ambiguous as there are multiple tables that contain the column account_number).
SQL is about tables and joins. You sometimes have to join several tables to get what you need, as above. If you don't join tables, as you did originally, you'll get a "cross product", which is not what you want.
I know this is very "light", but hopefully from the above answer to your question, you'll get some idea of how to do this.
I'd be happy to help you more if you have questions. Everyone is new to SQL at some point. Don't feel bad about that. It takes practice and then it becomes much easier.
-Jim

Is there a more efficient way to execute this nested SQL query?

I am writing a query to fill a select box on a user form and it works fine but when I look at the SQL, I feel like there should be a better, more efficient way to write it. Notice the two nested SELECTs below are from the same table.
I am wondering if there is an SQL trick I can add to my repertoire since I have to do queries like this from time to time. Or perhaps my approach is sloppy by some standards? Any feedback is appreciated.
Here is the SQL:
SELECT c.id, c.county_name, adr.county
FROM (SELECT id, county_name FROM counties WHERE state = (SELECT state FROM members WHERE id = 53)) AS c
LEFT JOIN (SELECT county FROM members WHERE id = 53) AS a ON c.id = a.county;
Here are the results (partial):
http://i.stack.imgur.com/Jawfa.png
The "3022" in the right column is member 53's county.
If needed, here is an explanation of the query and my intentions:
I have a members table where the county field is stored as an integer linking to a lookup table called counties. I also want to filter the county results to the member's state.
The query is filling an HTML select element and I want to pre-select the county of the member. The only information my PHP has available (without running a second query to get more) is the member id.
So the query needs to return the county id, the county name and some kind of identifier to let me know which record is matching for the member (I just went with the linking county field and my code will check for that).
The database is an MDB file accessed from a PDO ODBC connection.
You may need not need any subqueries but possibly a self-join:
SELECT counties.id, counties.county_name, members_1.county
FROM counties
INNER JOIN members ON counties.state = members.state
LEFT JOIN members_1 ON counties.id = members_1.county
WHERE members.id = 53 AND members_1.id = 53

SQL WHERE <from another table>

Say you have these tables:
PHARMACY(**___id_pharmacy___**, name, addr, tel)
PHARMACIST(**___Insurance_number___**, name, surname, qualification, **id_pharmacy**)
SELLS(**___id_pharmacy___**, **___name___**, price)
DRUG(**___Name___**, chem_formula, **id_druggistshop**)
DRUGGISTSHOP(**___id_druggistshop___**, name, address)
I think this will be more specific.
So, I'm trying to construct an SQL statement, in which I will fetch the data from id_pharmacy and name FROM PHARMACY, the insurance_number, name, and surname columns from PHARMACIST, for all the pharmacies that sell the drug called Kronol.
And that's basically it. I know I'm missing the relationships in the code I wrote previously.
Note: Those column names which have underscores left and right to them are underlined(Primary keys).
The query you've written won't work in any DBMS that I know of.
You'll most likely want to use some combination of JOINs.
Since the exact schema isn't provided, consider this pseudo code, but hopefully it will get you on the right track.
SELECT PH.Ph_Number, PH.Name, PHCL.Ins_Number, PHCL.Name, PHCL.Surname
FROM PH
INNER JOIN PHCL ON PHCL.PH_Number = PH.Ph_Number
INNER JOIN MLIST ON MLIST.PH_Number = PH.PH_Number
WHERE MLIST.Name = "Andy"
I've obviously assumed some relationships between tables that may or may not exist, but hopefully this will be pretty close. The UNION operator won't work because you're selecting different columns and a different number of columns from the various tables. This is the wrong approach all together for what you're trying to do. It's also worth mentioning that a LEFT JOIN may or may not be a better option for you, depending on the exact requirements you're trying to meet.
Ok, try this query:
SELECT A.id_pharmacy, A.name AS PharmacyName, B.Insurance_number,
B.name AS PharmacistName, B.surname AS PharmacistSurname
FROM PHARMACY A
LEFT JOIN PHARMACIST B
ON A.id_pharmacy = B.id_pharmacy
WHERE A.id_pharmacy IN (SELECT id_pharmacy FROM SELLS WHERE name = 'Kronol')

SQL: selective subqueries

I'm having an SQL query (MSSQLSERVER) where I add columns to the resultset using subselects:
SELECT P.name,
(select count(*) from cars C where C.type = 'sports') AS sportscars,
(select count(*) from cars C where C.type = 'family') AS familycars,
(select count(*) from cars C where C.type = 'business') AS businesscars
FROM people P
WHERE P.id = 1;
The query above is just from a test setup that's a bit nonsense, but it serves well enough as example I think. The query I'm actually working on spans a number of complex tables which only distracts from the issue at hand.
In the example above, each record in the table "people" also has three additional columns: "wantsSportscar", "wantsFamilycar" and "wantsBusinesscar". Now what I want to do is only do the subselect of each additional column if the respective "wants....." field in the people table is set to "true". In other words, I only want to do the first subselect if P.wantsSportscar is set to true for that specific person. The second and third subselects should work in a similar manner.
So the way this query should work is that it shows the name of a specific person and the number of models available for the types of cars he wants to own. It might be worth noting that my final resultset will always only contain a single record, namely that of one specific user.
It's important that if a person is not interested in a certain type of cars, that the column for that type will not be included in the final resultset. An example to be sure this is clear:
If person A wants a sportscar and a familycar, the result would include the columns "name", "sportscars" and "familycars".
If person B wants a businesscar, the result would include the columns "name" and "businesscar".
I've been trying to use various combinations with IF, CASE and EXISTS statements, but so far I've not been able to get a syntactically correct solution. Does anyone know if this is even possible? Note that the query will be stored in a Stored Procedure.
In your case, there are 8 column layouts possible and to do this, you will need 8 separate queries (or build your query dynamically).
It's not possible to change the resultset layout within a single query.
Instead, you may design your query as follows:
SELECT P.name,
CASE WHEN wantssport = 1 THEN (select count(*) from cars C where C.type = 'sports') ELSE NULL END AS sportscars,
CASE WHEN wantsfamily = 1 THEN (select count(*) from cars C where C.type = 'family') ELSE NULL END AS familycars,
CASE WHEN wantsbusiness = 1 THEN (select count(*) from cars C where C.type = 'business') ELSE NULL END AS businesscars
FROM people P
WHERE P.id = 1
which will select NULL in appropriate column if a person doesn't want it, and parse these NULL's on client side.
Note that relational model answers the queries in terms of relations.
In your case, the relation is as follows: "this person needs are satisifed with this many sport cars, this many business cars and this many family cars".
Relational model always answers this specific question with a quaternary relation.
It doesn't omit any of the relation members: instead, it just sets them to NULL which is the SQL's way to show that the member of a relation is not defined, applicable or meaningful.
I'm mostly an Oracle guy but there's a high chance the same applies. Unless I've misunderstood, what you want is not possible at that level - you will always have a static number of columns. Your query can control if the column is empty but since in the outer-most part of the query you have specified X number of columns, you are guaranteed to get X columns in your resultset.
As I said, I am unfamiliar with MS SQL Server but I'm guessing there will be some way of executing dynamic SQL, in which case you should research that since it should allow you to build a more flexible query.
You may be able to do what you want by first selecting the values as separate rows into a temp table, then doing a PIVOT on that table (turning the rows into columns).
It's important that if a person is not
interested in a certain type of cars,
that the column for that type will not
be included in the final resultset. An
example to be sure this is clear:
You will not be able to do it in plain SQL. I suggest you just make this column NULL or ZERO.
If you want the query to be dynamically expand when new cars are added, then PIVOTing could help you somewhat.
There are three fundamentals you want to learn to make this work easy. The first is data normalization, the second is GROUP BY, and the third is PIVOT.
First, data normalization. Your design of the people table is not in first normal form. The columns "wantsports", "wantfamily", "wantbusiness" are really a repeating group, although they may not look like one. If you can modify the table design, you will find it advantageous to create a third table, lets call it "peoplewant", with two key columns, personid and cartype. I can go into detail about why this design will be more flexible and powerful if you like, but I'm going to skip that for now.
On to GROUP BY. This allows you to produce a result that summarizes each group in one row of the result.
SELECT
p.name,
c.type,
c.count(*) as carcount
FROM people p,
INNER JOIN peoplewant pw ON p.id = pw.personid
INNER JOIN cars c on pw.cartype = c.type
WHERE
p.id = 1
GROUP BY
p.name,
c.type
This (untested) query gives you the result you want, except that the result has a separate row for each car type the person wants.
Finally, PIVOT. The PIVOT tool in your DBMS allows you to turn this result into a form where there is just one row for the person, and there is a separate column for each of the cartypes wanted by that person. I haven't used PIVOT myself, so I'll let somebody else edit this response to provide an example using PIVOT.
If you use the same technique to retrieve data for multiple people in one sweep, keep in mind that a column will appear for each wanted type that any person wants, and zeroes will appear in the PIVOT result for persons who do not want a car type that is in the result columns.
Just came across this post through a google search, so I realize I'm late to this party by a bit, but .. sure this really is possible to do... however, I wouldn't suggest actually doing it this way because it's usually considered a Very Bad Thing (tm).
Dynamic SQL is your answer.
Before I say how to do it, I want to preface this with, Dynamic SQL is a very dangerous thing, if you aren't sanitizing your input from the application.
So, therefore, proceed with caution:
declare #sqlToExecute nvarchar(max);
declare #includeSportsCars bit;
declare #includeFamilyCars bit;
declare #includeBusinessCars bit;
set #includeBusinessCars = 1
set #includeFamilyCars = 1
set #includeSportsCars = 1
set #sqlToExecute = 'SELECT P.name '
if #includeSportsCars = 1
set #sqlToExecute = #sqlToExecute + '(select count(*) from cars C where C.type = ''sports'') AS sportscars, ';
if #includeFamilyCars = 1
set #sqlToExecute = #sqlToExecute + '(select count(*) from cars C where C.type = ''family'') AS familycars, ';
if #includeBusinessCars = 1
set #sqlToExecute = #sqlToExecute + '(select count(*) from cars C where C.type = ''business'') AS businesscars '
set #sqlToExecute = #sqlToExecute + ' FROM people P WHERE P.id = 1;';
exec(#sqlToExecute)