Counting prefixes in a joined table in SQL - sql

I am trying to count how many makes of car a person owns. Car makes are only defined by a prefix in my Links table.
Table 1 (Person)
UniqueID Name
PER0001 Adrian
PER0002 Michael
Per0003 James
Table 2 (Links)
UniqueID LinkEnd1_ID LinkEnd2_ID
LIN0001 PER0001 FER02332
LIN0002 PER0001 FER02112
LIN0003 PER0001 POR12122
LIN0004 PER0002 FER12321
LIN0005 PER0003 MAS12382
LIN0006 PER0003 FER22982
LIN0006 PER0003 MAS12232
Output (option 1)
Name Car_Make Count
Adrian FER 2
Adrian POR 1
Michael FER 1
James MAS 2
James FER 1
Output (option 2 - preferred)
Name FER POR MAS
Adrian 1 2
Michael 1
James 1 2
The reason I am using a link table to count the number of car makes is because every car make has a different table I would need to join in.
I've tried
select count left(LinkEnd2_ID,3) which doesnt work, i've also tried group by which I cant seem to crack.
I guess what I want to be able to do is
select
count(left(LinkEnd2_ID,3)='FER'
,count(left(LinkEnd2_ID,3)='POR'
,count(left(LinkEnd2_ID,3)='MAS'
but thats a query in a select and I decipher how to code that properly.
Heres where I am starting from (or the base I keep going back to start afresh)-
SELECT
Person.Unique_ID
,Person.Name
,left(Link.LinkEnd2_ID,3) as Car_Make
FROM
Person
LEFT JOIN
Links as Link
on Person.Unique_ID = Link.LinkEnd1_ID
Any help you can offer would be appreciated.

Nearly there, you just need to add a group by, and change all the columns to aggregate functions.
Your option 1:
SELECT
max(Person.Name) as Person_Name
,left(Link.LinkEnd2_ID,3) as Car_Make
,count(*) as No_of_Car
FROM
Person
LEFT JOIN
Links as Link
on Person.Unique_ID = Link.LinkEnd1_ID
GROUP BY
Person.Unique_ID
For your option 2, you need to wrap your aggregate functions around case statements
you have to hardcode the 3 different car make, so if you have unknown number of them, it wouldn't work.
SELECT
max(Person.Name) as Person_Name
,sum(case when left(Link.LinkEnd2_ID,3) ='FER' then 1 else 0 end) as FER
,sum(case when left(Link.LinkEnd2_ID,3) ='POR' then 1 else 0 end) as POR
,sum(case when left(Link.LinkEnd2_ID,3) ='MAS' then 1 else 0 end) as MAS
FROM
Person
LEFT JOIN
Links as Link
on Person.Unique_ID = Link.LinkEnd1_ID
GROUP BY
Person.Unique_ID

Related

How to select people that don't know anyone who takes workshops

I have a few tables I want to iterate over. First table is Persons:
id
name
address
1
Laura Jansen
New York
2
Sana Vendi
Miami
3
Adam Smith
Boston
4
Mo Zora
Los Angeles
Second one is TakingWorkshop. This is the workshop the people are taking, so person_id is the id of the one in Persons.
id
person_id
workshop_id
20
4
26
19
2
27
18
3
28
Last table is Knows. The person id's are the same as the id's in Persons. So, PersonX knows PersonY.
PersonX
PersonY
1
2
1
3
2
1
4
1
So 1 is Laura, 2 is Sana and 3 is Adam. We can see that Adam doesn't know anyone. That means that Adam automatically also doesn't know anyone who takes workshops, because he doesn't even know anyone. However, in the table we see that Laura, 1, doesn't take workshops. So 4 and 2, Mo and Sana, know Laura, but she doesn't take any workshops so Mo and Sana don't know anyone who takes workshops.
I wrote some code for the people who don't know anyone taking workshops (in this database, it's Adam)
First I do a left join on the Person table and Knows table, on the id of persons and the id of personA of Knows. PersonA knows person B. This join gives me a table of people who know people, including the people who don't know anyone (those are null).
SELECT distinct P.name, K.personA_id
FROM Persons P LEFT JOIN Knows K
ON P.id = K.personA_id
Now I want to see if personB_id is in the person_id of TakingWorkshop. This way you can see whether the known people are taking workshops or not. PersonB_id should NOT be in TakeingWorkshop, because that's how you filter out Laura. I did this like this:
WHERE K.personB_id NOT IN (SELECT person_id
FROM TakingWorkshop)
So my whole code looks like this
SELECT distinct P.name, K.personA_id
FROM Persons P LEFT JOIN Knows K
ON P.id = K.personA_id
WHERE K.personB_id NOT IN (SELECT person_id
FROM TakingWorkshop)
But I get no results when I do this and want to know what's going wrong
Hmmm . . . Your description of the problem suggests not exists. But not exists what?
This query gets everyone who is known and taking a workshop:
select . . .
from knows k join
TakingWorkshop tw
on k.personY = tw.person_id;
So, we can slip that into the query:
select p.*.
from persons p
where not exists (select 1
from knows k join
TakingWorkshop tw
on k.personY = tw.person_id
where k.personX = p.id
);

How to create a sum of unique number and frequency

Using SQL I want a report that shows how many unique calls and how many total calls was received for a given LinkedUserMailboxName. I'm unable to create a sum for 'Unique Numbers' in the query. Please help.
select count(*), UMADUserAccounts.Country as 'Country',
UMADUserAccounts.RankLevel as 'Level',
LinkedUserMailboxName,
CallingNumber as 'Unique Numbers',
count(callingnumber) as 'How many times'
from UMCallDataRecord
inner join UMADUserAccounts on
UMCallDataRecord.LinkedUserMailboxName = UMADUserAccounts.EmailAddress where
CallType = 'callansweringvoicemessage'
and
[date] between '2019-05-01' and '2019-05-31'
group by UMADUserAccounts.Country,
UMADUserAccounts.abcRankLevel,
LinkedUserMailboxName,
CallingNumber
order by
UMADUserAccounts.Country asc
Expected Output
Country Level LinkedUserMailboxName Unique Numbers How many times
USA Associate Alexia.b#ca.abc.com 2 2
USA Associate Angelina.asas#de.abc.com 2 2
USA Associate Asad.slob#ca.abc.com 2 2
USA Associate Austin.jones#ca.abc.com 3 3
USA Associate Bankole.smith#ca.abc.com 3 13
Output
Country Level LinkedUserMailboxName Unique Numbers How many times
USA Associate Alexia.b#ca.abc.com +11232561600 1
USA Associate Alexia.b#ca.abc.com +11235712476 1
USA Associate Angelina.asas#de.abc.com +49132433113287 1
USA Associate Angelina.asas#de.abc.com daniel.boy#abc.com 1
USA Associate Asad.slob#ca.abc.com +11255722196 1
USA Associate Asad.slob#ca.abc.com terry.q.public#abc.com 1
USA Associate Austin.jones#ca.abc.com +11239840743 1
USA Associate Austin.jones#ca.abc.com +11239543735 1
USA Associate Austin.jones#ca.abc.com +11234325015 1
USA Associate Bankole.smith#ca.abc.com +112355810290 1
USA Associate Bankole.smith#ca.abc.com +11233508936 1
USA Associate Bankole.smith#ca.abc.com +11237477000 11
I think you want count(distinct):
select count(*), ua.Country as Country, ua.RankLevel as Level,
LinkedUserMailboxName,
count(distinct CallingNumber) as Unique_Numbers,
count(*) as how_many_times
from UMCallDataRecord cdr inner join
UMADUserAccounts ua
on cdr.LinkedUserMailboxName = ua.EmailAddress
where cdr.CallType = 'callansweringvoicemessage' and
cdr.[date] >= '2019-05-01' and
cdr.[date] < '2019-06-01'
group by ua.Country, ua.abcRankLevel, LinkedUserMailboxName,
order by ua.Country asc;
Notes:
Table aliases make the query easier to write and to read.
Qualify all column references, particularly when the query refers to more than one table.
I removed the between for the date comparisons. Aaron Bertrand has a good explanation of why you should avoid between in this case.

SQL having trouble getting desired query

Say we have the following relation:
Author(name,title,language)
What I'm trying to accomplish is to make query that lists all the authors and how many books in a particular language they made, and if they made none in that language then display zero.
What I've tried so far is this
select name, count("language") as SomeLanguage
from Author
where "language"='English'
group by name;
Which gives the count of how many books in the specified language they made, but I don't know how to include the other authors that did not write a book in that language.
Example Output:
Name | SomeLanguage
----------------
Bill | 4
Rob | 1
Bob | 0
You want conditional aggregation:
select name,
sum(case when language = 'English' then 1 else 0 end) as SomeLanguage
from Author
group by name;

MS Access: Selecting the first item according to a rank

Imagine I have a query called QueryA that returns stuff like this:
Employee Description Rank
John Happy 1
John Depressed 3
James Happy 1
James Confused 2
Mark Depressed 3
I am trying to make a query that grabs the Employee and the Description, but only one description -- the one with the best "rank." (the lower the rank the better). I sort QueryA by Employee then by Rank (descending).
So I'd want my new query QueryB to show that John as Happy, James as Happy, and Mark as Depressed.
However I try selecting Employee and then First of Description and it doesn't always work.
I'm not able to check this for Access, but it should work fine. Check my SQL Fiddle
select
r.employee, d.description
from
table1 as d
inner join (select min(rank) as rank, employee
from
table1
group by employee) r on d.rank = r.rank
and d.employee = r.employee

Multiple JOIN (SQL)

My problem is Play! Framework / JPA specific. But I think it's applicable to general SQL syntax.
Here is a sample query with a simple JOIN:
return Post.find(
"select distinct p from Post p join p.tags as t where t.name = ?", tag
).fetch();
It's simple and works well.
My question is: What if I want to JOIN on more values in the same table?
Example (Doesn't work. It's a pseudo-syntax I created):
return Post.find(
"select distinct p from Post p join p.tags1 as t, p.tags2 as u, p.tags3 as v where t.name = ?, u.name = ?, v.name = ?", tag1, tag2, tag3,
).fetch();
Your programming logic seems okay, but the SQL statement needs some work. Seems you're new to SQL, and as you pointed out, you don't seem to understand what a JOIN is.
You're trying to select data from 4 tables named POST, TAG1, TAG2, and TAG3.
I don't know what's in these tables, and it's hard to give sample SQL statements without that information. So, I'm going to make something up, just for the purposes of discussion. Let's say that table POST has 6 columns, and there's 8 rows of data in it.
P Fname Lname Country Color Headgear
- ----- ----- ------- ----- --------
1 Alex Andrews 1 1 0
2 Bob Barker 2 3 0
3 Chuck Conners 1 5 0
4 Don Duck 3 6 1
5 Ed Edwards 2 4 2
6 Frank Farkle 4 2 1
7 Geoff Good 1 1 0
8 Hank Howard 1 3 0
We'll say that TAG1, TAG2, and TAG3 are lookup tables, with only 2 columns each. Table TAG1 has 4 country codes:
C Name
- -------
1 USA
2 France
3 Germany
4 Spain
Table TAG2 has 6 Color codes:
C Name
- ------
1 Red
2 Orange
3 Yellow
4 Green
5 Blue
6 Violet
Table TAG3 has 4 Headgear codes:
C Name
- -------
0 None
1 Glasses
2 Hat
3 Monacle
Now, when you select data from these 4 tables, for P=6, you're trying to get something like this:
Fname Lname Country Color Headgear
----- ------ ------- ------ -------
Frank Farkle Spain Orange None
First thing, let's look at your WHERE clause:
where t.name = ?, u.name = ?, v.name = ?
Sorry, but using commas like this is a syntax error. Normally you only want to find data where all 3 conditions are true; you do this by using AND:
where t.name=? AND u.name=? AND v.name=?
Second, why are you joining tables together? Because you need more information. Table POST says that Frank's COUNTRY value is 4; table TAG1 says that 4 means Spain. So we need to "join" these tables together.
The ancient (before 1980, I think) way to join tables is to list more than one table name in the FROM clause, separated by commas. This gives us:
SELECT P.FNAME, P.LNAME, T.NAME As Country, U.NAME As Color, V.NAME As Headgear
FROM POST P, TAG1 T, TAG2 U, TAG3 V
The trouble with this query is that you're not telling it WHICH rows you want, or how they relate to each other. So the database generates something called a "Cartesian Product". It's extremely rare that you want a Cartesian Product - normally this is a HUGE MISTAKE. Even though your database only has 22 rows in it, this SELECT statement is going to return 768 rows of data:
Alex Andrews USA Red None
Alex Andrews USA Red Glasses
Alex Andrews USA Red Hat
Alex Andrews USA Red Monacle
Alex Andrews USA Orange None
Alex Andrews USA Orange Glasses
...
Hank Howard Spain Violet Monacle
That's right, it returns every possible combination of data from the 4 tables. Imagine for a second that the POST table eventually grows to 20000 rows, and the three TAG tables have 100 rows each. The whole database would be less than a megabyte, but the Cartesian Product would have 20,000,000,000 rows of data -- probably about 120 GB of data. Any database engine would choke on that.
So if you want to use the Ancient way of specifying tables, it is VERY IMPORTANT to make sure that your WHERE clause shows the relationship between every table you're querying. This makes a lot more sense:
SELECT P.FNAME, P.LNAME, T.NAME As Country, U.NAME As Color, V.NAME As Headgear
FROM POST P, TAG1 T, TAG2 U, TAG3 V
WHERE P.Country=T.C AND P.Color=U.C AND P.Headgear=V.C
This only returns 8 rows of data.
Using the Ancient way, it's easy to accidentally create Cartesian Products, which are almost always bad. So they revised SQL to make it harder to do. That's the JOIN keyword. Now, when you specify additional tables you can specify how they relate at the same time. The New Way is:
SELECT P.FNAME, P.LNAME, T.NAME As Country, U.NAME As Color, V.NAME As Headgear
FROM POST P
INNER JOIN TAG1 T ON P.Country=T.C
INNER JOIN TAG2 U ON P.Color=U.C
INNER JOIN TAG3 V ON P.Headgear=V.C
You can still use a WHERE clause, too.
SELECT P.FNAME, P.LNAME, T.NAME As Country, U.NAME As Color, V.NAME As Headgear
FROM POST P
INNER JOIN TAG1 T ON P.Country=T.C
INNER JOIN TAG2 U ON P.Color=U.C
INNER JOIN TAG3 V ON P.Headgear=V.C
WHERE P.P=?
If you call this and pass in the value 6, you get only one row back:
Fname Lname Country Color Headgear
----- ------ ------- ------ --------
Frank Farkle Spain Orange None
As was mentioned in the comments, you are looking for an ON clause.
SELECT * FROM TEST1
INNER JOIN TEST2 ON TEST1.A = TEST2.A AND TEST1.B = TEST2.B ...
See example usage of join here:
http://en.wikibooks.org/wiki/Java_Persistence/Relationships#Join_Fetching