Grabbing data from ORACLE DB - sql

I have a database which has Author(id, name), AuthorPublication(aid, pid), Publication(id, title)
Author.id links to AuthorPublication.aid, AuthorPublication.pid links to Publication.od.
I am trying to write a query that returns the name of authors who has co-authored with "amol" or "samuel" BUT NOT BOTH.
So far I have
Select name
From Author, AuthorPublication, Publication
Where Publication.id = PID AND aid = Author.id
In the above code I need to filter the PID to be authors whose pid matches author "samuel" or "amol". But not both
Being new to oracle db, Im not so sure how to implement this, any help?
Thanks in advance!

Logic? Basically, get the id of the two authors, get any pid of their publications, those pids can't be the same....then use the resulting publication ids. There are several ways to do this in 1 query, here's one way using table aliases to use tables more than once in one query:
select auth_sam.name as sams_name
,auth_amol.name as amols_name
,nvl(auth_sam.name, auth_amol.name) as single_author_name
,s_pubinfo.title as sam_publication_titles
,a_pubinfo.title as amol_publication_titles
,nvl(s_pubinfo.title, a_pubinfo.title) as single_pub_name
from author auth_sam
,authorPublication sam_pubs
,publication s_pubinfo -- 3 aliases for samuel data
,author auth_amol
,authorPublication amol_pubs
,publication a_pubinfo -- 3 aliases for amol data
where auth_sam.name = 'samuel'
and auth_sam.id = sam_pubs.aid -- pubs by samuel
and sam_pubs.pid = s_pubinfo.id -- samuel titles
and auth_amol.name = 'amol'
and auth_amol.id = amol_pubs.aid -- pubs by amol
and amol_pubs.pid = a_pubinfo.id -- amol titles
and sam_pubs.pid != amol_pubs.pid -- not the same publication
Because of the !=, the query effectively returns 2 sets of results. Records for 'samuel' will have the 'sams_name' column populated and the 'amols_name' column will be null. Records for 'amol' will have his name column populated and the samuel name column value will be null. Because of these nulls, I included two columns using NVL() to demonstrate a method to choose which author field value and title field value to display. (Not a very "clean" solution, but I think it demonstrates several insights in the power of relational logic and SQL.)
btw - in this example, I really think the SQL is more readable with the Oracle SQL syntax. The ANSI SQL version, with all the JOIN and ON keywords, feels more difficult to read to me.

in your where clause try: and (author.name = 'samuel' and author.name != 'amol') OR (author.name != 'samuel' and author.name = 'amol') (According to https://community.oracle.com/thread/2342467?tstart=0).

Related

Can I delete entries from two tables in one statement?

I have to remove a row from each of two tables, they're linked by an ID but not with a proper PK - FK relationship (this db has NO foreign keys!)
The tables have a supposed 1-1 relationship. I don't know why they weren't just put in the same table but I'm not at liberty to change it.
People
PersonId | Name | OwnsMonkey
----------------------------
1 Jim true
2 Jim false
3 Gaz true
Info
PersonId | FurtherInfo
-----------------------------
1 Hates his monkey
2 Wants a monkey
3 Loves his monkey
To decide what to delete, I have to find a username and whether or not they own a monkey:
Select PersonId from People where Name = 'Jim' and OwnsMonkey = 'false'
SO I'm doing two separate statements using this idea, deleting from Info first and then from People
delete from Info where PersonId = (Select PersonId from People where Name = 'Jim' and OwnsMonkey = 'false');
delete from People where PersonId = (Select PersonId from People where Name = 'Jim' and OwnsMonkey = 'false');
I found a promising answer here on StackOverflow
delete a.*, b.*
from People a
inner join Info b
where a.People = b.Info
and a.PersonId =
(Select PersonId from People where Name = 'Jim' and OwnsMonkey = 'false')
But it gives a syntax error in Sql Server (2012), I tried it without alias' too, but it doesn't seem possible to delete on two tables at once
Can I delete entries from two tables in one statement?
No. One statement can delete rows only from one table in MS SQL Server.
The answer that you refer to talks about MySQL and MySQL indeed allows to delete from several tables with one statement, as can be seen in the MySQL docs. MS SQL Server doesn't support this, as can be seen in the docs. There is no syntax to include more than one table in the DELETE statement in SQL Server. If you try to delete from a view, rather than a table, there is a limitation as well:
The view referenced by table_or_view_name must be updatable and
reference exactly one base table in the FROM clause of the view
definition.
I was hoping to avoid two separate statements on the off-chance the
second doesn't work for whatever reason, interrupted - concurrency
really, I guess the TRY/CATCH will work well for that.
This is what transactions are for.
You can put several statements in a transaction and either all of them would succeed, or all of them would fail. Either all or nothing.
In your case you not just can, but should put both DELETE statements in a transaction.
TRY/CATCH helps to process possible errors in a more controlled way, but the primary concept is "transaction".
BEGIN TRANSACTION
delete from Info where PersonId = (Select PersonId from People where Name = 'Jim' and OwnsMonkey = 'false');
delete from People where PersonId = (Select PersonId from People where Name = 'Jim' and OwnsMonkey = 'false');
COMMIT
I highly recommend to read a great article Error and Transaction Handling in SQL Server by Erland Sommarskog.
If you try to be tricky, like this:
WITH
CTE
AS
(
SELECT
Info.PersonId AS ID1, People.PersonId AS ID2
FROM
Info
INNER JOIN People ON Info.PersonId = People.PersonId
)
DELETE FROM CTE
WHERE ID1 = 1;
You'll get an error:
View or function 'CTE' is not updatable because the modification
affects multiple base tables.
Or like this:
WITH
CTE
AS
(
SELECT
PersonId
FROM Info
UNION ALL
SELECT
PersonId
FROM People
)
DELETE FROM CTE
WHERE PersonId = 1;
You'll get another error:
View 'CTE' is not updatable because the definition contains a UNION
operator.

Get data from other table based on column with concatenated values

I have two tables:
category with columns:
id name
1 business
2 sports
...
article with columns:
id title categories
1 abc 1|2|3
2 xyz 1|2
I know there should be a separate table for article categories but I was given this.
Is it possible to write a query that returns:
id title category_names
1 xyz business,sports
I thought of splitting the string in article -> categories column, then use in query to extract name from category table but couldn't figure it out.
You should fix your data model. But, you can do this in SQL Server:
select a.*, s.names
from article a cross apply
(select string_agg(c.name, ',') as names
from string_split(a.categories, '|') ss join
category c
on try_convert(int, ss.value) = c.id
) s;
Here is a db<>fiddle.
Presumably, you already know the shortcomings of this data model:
SQL Server has poor string handling functionality.
Numbers should be stored as numbers not strings.
Foreign key references should be properly declared.
Such queries cannot make use of indexes and partitions.
If you really want to store multiple values in a field, SQL Server offers both JSON and XML. Strings are not the right approach.

SQL - compare multiple rows and return rows?

I have a large database and i'd like to pull info from a table (Term) where the Names are not linked to a PartyId for a certain SearchId. However:
There are multiple versions of the searches (sometimes 20-40 - otherwise I think SQL - Comparing two rows and two columns would work for me)
The PartyId will almost always be NULL for the first version for the search, and if the same Name for the same SearchId has a PartyId associated in a later version the NULL row should not appear in the results of the query.
I have 8 left joins to display the information requested - 3 of them are joined on the Term table
A very simplified sample of data is below
CASE statement? Join the table with itself for comparison? A temp table or do I just return the fields I'm joining on and/or want to display?
Providing sample data that yields no expected result is not as useful as providing data that gives an expected result..
When asking a question start with defining the problem in plain English. If you can't you don't understand your problem well enough yet. Then define the tables which are involved in the problem (including the columns) and sample data; the SQL you've tried, and what you're expected result is using the data in your sample. Without this minimum information we make many guesses and even with that information we may have to make assumptions; but without a minimum verifiable example showing illustrating your question, helping is problematic.
--End soap box
I'm guessing you're after only the names for a searchID which has a NULL partyID for the highest SearchVerID
So if we eliminated ID 6 from your example data, then 'Bob' would be returned
If we added ID 9 to your sample data for name 'Harry' with a searchID of 2 and a searchVerID of 3 and a null partyID then 'Harry' too would be returned...
If my understanding is correct, then perhaps...
WITH CTE AS (
SELECT Name, Row_Number() over (partition by Name order by SearchVersID Desc)
FROM Term
WHERE SearchID = 2)
SELECT Name
FROM CTE
WHERE RN = 1
and partyID is null;
This assigns a row number (RN) to each name starting at 1 and increasing by one for each entry; for searchID's of 2. The highest searchversion will always have a RN of 1. Then we filter to include only those RN which are 1 and have a null partyID. This would result in only those names having a searchID of 2 the highest search version and a NULL partyID
Ok So I took the question a different way too..
If you simply want all the names not linked to a PartyID for a given search.
SELECT A.*
FROM TERM A
WHERE NOT EXISTS (SELECT 1
FROM TERM B
WHERE A.Name = B.Name
AND SearchID = 2) and partyID is not null)
AND searchID = 2
The above should return all term records associated to searchID 2
that have a partyId. This last method is the exists not exists and set logic I was talking about in comments.

I want to merge the duplicate names in my table or at least see the names that are unique and look alike

I have a employee table with schema as follows:
Id Name Birthday DeathDay Startdate EndDate
The problem is that I have data as follows:
Bergh Celestin 06/09/1791 14/12/1861
Bergh Célestin 06/09/1791 14/12/1861
Bergh Francois 04/04/1958 11/12/2001
Bergh Jozef Francois 04/04/1958 11/12/2001
Now i want to merge these records as 1 as they are the same person how can i do that?
Also, if I just want to display the list of only those person from the table whose names are possibly same, like above, how can I do that?
I used:
select Distinct name,birthday,deathday from table
but that is not good enough.
I would use a function (.NET or SQL) of sorts to remove the accents as per https://stackoverflow.com/a/12715102/1662973 and then group on that together with the dates. You will need to group on something, as essentially "Bergh Célestin" could actually be a different person to "Bergh Celestin".
Sample:
select
RemoveExtraChars(name)
,birthday
,deathday
from
TABLE
group by
RemoveExtraChars(name)
,birthday
,deathday
For your second Question you can use SQL LIKE Operator:
SELECT column_name(s)
FROM table_name
WHERE column_name LIKE pattern;

How to query values by line with a join?

given the two tables and values:
Tables
People- Columns: [ID]PK Name
Field - Columns: [ID FieldName]PK FieldValue
Values
People
ID Name
1 Mary
2 John
3 Tony
Field
ID FieldName FieldValue
1 Age 20
1 Country USA
2 Age 21
2 Country USA
3 Age 20
3 Country USA
I would like a query that gives me the names of the people from USA AND have 20 years old. The result should be Mary and Tony.
SELECT name FROM people p
INNER JOIN field f ON f.ID = f.ID
WHERE
(FieldName = 'Age' AND FieldValue= '20') OR
(FieldName = 'Country' AND FieldValue= 'USA'));
The problem with that query is the "OR" between the where clauses. I will get the 3 people, instead of only Mary and Tony. If I use an AND, none result is return.
Do you have some tips?
Thanks in advance :)
What about this?
SELECT name
FROM people p
JOIN field fAge ON p.ID = fAge.ID
AND fAge.FieldName = 'Age'
JOIN field fCountry ON p.ID = fCountry.ID
AND fCountry.FieldName = 'Country'
WHERE fAge.FieldValue = '20'
AND fCountry.FieldValue = 'USA'
Thereby treating the Country and Age data essentially as separate tables. As it seems that the field table is used to store different types of data.
It might be useful to have different database Views on this table for each type of data. This will further simplify the above query.
What you've done here, unfortunately, is implemented the Entity-Attribute-Value (EAV) antipattern. (Antipattern is a fancy word for "something that's usually a bad idea.") It's generally counter to SQL best practices to have a "Field" table like yours, which is why it has an antipattern named after it. "Entity" refers to your ID column (or, by association, a Person record), "Attribute" corresponds to FieldName, and "Value" to FieldValue.
For more information, take a look at this excellent set of slides (this should link directly to the section on EAV; if not, skip to slide 16).
The solution is to refactor your database so that Age and Country are fields in your People table. Then a far simpler query will be possible: SELECT name FROM people WHERE Age = 20 AND Country = 'USA';
You don't say whether you considered and rejected the "standard" design that would Name, Age, and Country in a single table. That would allow substantially easier querying and also type and range protection for the Age values as well as FK/PK protection for the Country values.
If you do have a positive reason to use the Key/Value approach, then you have three choices for your query:
Join the Key/Value table in the query one time for each term you're searching for (see Philip's answer, above).
Use WHERE EXISTS conditions:
SELECT Name FROM People WHERE
EXISTS (SELECT * FROM Field WHERE ID = People.ID AND FieldName = 'Country' AND Fieldvalue = 'USA')
AND EXISTS (SELECT * FROM Field WHERE ID = People.ID AND FieldName = 'Age' AND Fieldvalue = '20')
Use some kind of INTERSECT or SUBTRACT operator if supported in your dialect of SQL.