SQL sub-query check if data exists in another table - sql

I am new to SQL and the question may already exist in some form. I am developing a web app that displays cards that contain title,description and more information regarding a place, and with a sub-query fetches the image data regarding each place. PostgreSQL is used. I want to add a sub-query or join that gets which items are liked by the user from the ones selected(present in the favoritePlaces with the corresponding user_id and place_id received from the query). Sample dynamically generated query:
SELECT places.place_id,
username,
title,
description,
visible,
score,
placelocation,
category,
price,
accessibility,
places.date,
dangerous,
url,
image_id
FROM
(SELECT *
FROM places
GROUP BY place_id
HAVING count(*) < 10) places
LEFT JOIN images ON images.place_id = places.place_id
WHERE description SIMILAR TO Concat('%', '', '%')
AND placelocation =placelocation
AND category =category
AND price =price
AND dangerous =dangerous
AND accessibility =accessibility
OR title SIMILAR TO Concat('%', '', '%')
AND visible=TRUE
AND placelocation =placelocation
AND category =category
AND price =price
AND dangerous =dangerous
AND accessibility =accessibility
The end goal is to have for each item whether it is liked or not(present in the the favoritePlaces table with regards to the current user). favoritePlaces Tables structure:
I looked at this answer: https://stackoverflow.com/a/44773730/14131447 but I am not sure how to implement it.
Sample data within the favoritePlaces table:

I solved the issue by simply adding the case after the SELECT columns
SELECT places.place_id,
username,
title,
description,
visible,
score,
placelocation,
category,
price,
accessibility,
places.date,
dangerous,
url,
image_id, CASE
WHEN EXISTS (select *
from "favoritePlaces"
where "favoritePlaces".place_id = places.place_id AND user_id=128
)
THEN 'true'
ELSE 'false'
END
FROM
(SELECT *
FROM places
GROUP BY place_id
HAVING count(*) < 10) places
LEFT JOIN images ON images.place_id = places.place_id
WHERE description SIMILAR TO Concat('%', '', '%')
AND placelocation =placelocation
AND category =category
AND price =price
AND dangerous =dangerous
AND accessibility =accessibility
OR title SIMILAR TO Concat('%', '', '%')
AND visible=TRUE
AND placelocation =placelocation
AND category =category
AND price =price
AND dangerous =dangerous
AND accessibility =accessibility;

Related

SSRS is removing multiple lines in grouping

I have an SSRS report with the following query:
SELECT DISTINCT
Rtrim(ltrim(CUSTNAME)) as 'CUSTNAME',
ItemName,
ISNULL(NAME, LOGCREATEDBY) AS 'Modified By'
,b.ITEMID as 'Item Id'
,[PRICE_NEW] as 'New Price'
,[PRICE_OLD] as 'Old Price'
,[PRICEUNIT_NEW] as 'New Unit Price'
,[PRICEUNIT_OLD] as 'Old Unit Price'
,LOGCREATEDDATE as 'Created Date'
,LOGCREATEDTIME
,(select Description from Dimensions where a.Dimension2_ = Dimensions.Num) as 'Division'
,(Select TOP 1 INVENTTRANS.DATEFINANCIAL From INVENTTRANS Where
INVENTTRANS.ITEMID = B.ITEMID and InvoiceID like 'Inv%' order by INVENTTRANS.DATEFINANCIAL desc) As 'LastInvoice'
FROM PMF_INVENTTABLEMODULELOG AS b
LEFT JOIN USERINFO ON ID = LOGCREATEDBY
LEFT JOIN INVENTTABLE AS a on a.ITEMID in (b.itemId)
WHERE LOGCREATEDDATE between #beginCreatedDate and #endCreatedDate
and a.dimension2_ in (#dimension)
order by LOGCREATEDDATE,LOGCREATEDTIME desc
What happens, in short, is it goes through a table and picks out an item number and lists each price change for that item.
the query, wen run, will return something like:
CUSTNAME | Modified By | Item ID | New Price | Old Price
------------------------------------------------------------------
Performance Joe 12345 21.50 21.49
Performance Mary 12345 21.49 19.10
(This happens to be the return that is causing problem)
My report lists each line by division, Customer name and item Number. The problem is, when I have an Item ID group, it adds up the total (makes sense) So i get rid of the item number group, but now it will list only one item per customer!
it should show the two lines for Performance in the example, but instead, it lists neither. I would like it to show every single line for each customer. It must be the ITEM ID group, but I can't seem to get it right.
Rather than getting rid of the group, change it to show detail data.
Right click on the group select 'Group Properties' and select the Group On expression. Then click the delete button. It will then no longer sum as it is a detail group.
I would recommend that you then remove sum from the relevant expressions, to avoid confusion, as they will only be summing single values but will make it look otherwise.

Grouping by titles that are the same with the same (and then different) composers

I have two database tables,
PIECE (PNo, CNo, Title, Tune, Opus)
and
COMPOSER (CNO, LAST, FIRST, BORN, DIED)
are the ones I'm using for this query.
I need to Select Titles that are the same and with the same Composer. I need to list the titles and the number of versions of each. The next question requires the same listing if the Composers are different.
I tried:
SELECT TITLE, COUNT(*)
FROM PIECE, COMPOSER
WHERE PIECE.CNo = COMPOSER.CNo
GROUP BY TITLE
HAVING COUNT(*) > 1
ORDER BY COUNT(*);
Something is wrong with that query though. I am using SQLPLUS. Any help is appreciated.
The query for the question before it was to answer this:
"Different music pieces (with different PNo) may have the exact same title, list the titles of these music pieces. List these music titles, along with the number of versions (of music pieces) there are sharing the same title."
I used:
SELECT TITLE, COUNT(*)
FROM PIECE
GROUP BY TITLE
HAVING COUNT(*) > 1
ORDER BY COUNT(*);
Try this:
SELECT TITLE, COUNT(*)
FROM PIECE
GROUP BY TITLE
HAVING COUNT(*) > 1
MINUS
SELECT TITLE, count(*)
FROM PIECE P2
JOIN COMPOSER C2 ON P2.CNO = C2.CNO
GROUP BY TITLE, C2.CNO
HAVING COUNT(*) > 1
We find all titles that occur more then once then subtract the titles that have the same composer for all title versions. If you want more data then just the title, join back to the composers table to get more details for that title. We could also consider windowing functions in Oracle.
try replacing your query like this:
SELECT TITLE, COUNT(*) as Count
FROM PIECE, COMPOSER
WHERE PIECE.CNo = COMPOSER.CNo
GROUP BY TITLE
HAVING COUNT(*) > 1
ORDER BY count;

Sql for distinct record comparison

I am comparing a table to itself trying to determine whether an email in one record is being used in any one of four other columns in another record.
To make this easier, lets look at an example (simplified):
Name: Bob
Office Email: bob#aaa.com
Home Email: bob#home.com
Mobile Email: bobster#gmail.com
.
Name: Rob
Office Email: rob#bbb.com
Home Email: bob#home.com
Mobile Email: robert#gmail.com
Now I have a sql statement like this:
select c1.ContactId id1, c1.FullName Name1, 'Office Email 1' EmailType1, c1.EMailAddress1 Email,
c2.ContactId id2, c2.FullName Name2,
CASE c1.EmailAddress1
WHEN c2.EMailAddress1 THEN 'Office Email 1'
WHEN c2.Si_OfficeEmail2nd THEN 'Office Email 2'
WHEN c2.EMailAddress2 THEN 'Mobile Email'
WHEN c2.pc_hmemail THEN 'Home Email'
ELSE '?'
END EmailType2,
CASE c1.EmailAddress1
WHEN c2.EMailAddress1 THEN c2.EMailAddress1
WHEN c2.Si_OfficeEmail2nd THEN c2.Si_OfficeEmail2nd
WHEN c2.EMailAddress2 THEN c2.EMailAddress2
WHEN c2.pc_hmemail THEN c2.pc_hmemail
ELSE '?'
END DuplicateEmail
from Contact c1, Contact c2
where (
LTRIM(RTRIM(c1.EMailAddress1 )) = LTRIM(RTRIM(c2.EMailAddress1))
Or LTRIM(RTRIM(c1.EMailAddress1 )) = LTRIM(RTRIM(c2.EMailAddress2))
Or LTRIM(RTRIM(c1.EMailAddress1 )) = LTRIM(RTRIM(c2.pc_hmemail))
Or LTRIM(RTRIM(c1.EMailAddress1 )) = LTRIM(RTRIM(c2.Si_OfficeEmail2nd))
)
And c1.ContactId <> c2.ContactId
And c1.StateCode = 0
and c2.StateCode = 0
order by c1.FullName, c2.FullName
Unfortunately, because Bob and Rob have the same email 'type' (Home Email) that is duplicated due to a typo, my query returns two records, one which shows that Bobs email is duplicated in Robs email, and a second that Robs email is duplicated in Bobs email.
I only need one record. I'm sure this is a common problem but I don't quite know how to describe this problem well enough to have a search engine return something useful.
Perhaps there is a better way of going about this? If not, other than jumping through a bunch of intermediate temporary tables to eliminate these equivalent records, is there a way to write a single query for this?
The solution to your problem is to add the condition: c1.contactId < c2.ContactId. This limits the pairs you are looking at.
If you are looking at emails, you might find a faster approach to look directly at emails. Something like the following will return all emails (on separate rows) that are duplicated:
select e.*
from (select e.*, COUNT(*) over (partition by email) as NumTimes
from ((select contactId, 'Office' as which, EmailAddress1 as email
from Contact
) union all
(select contactId, 'Office2', Si_OfficeEmail2nd
from Contact
) union all
(select contact_id, 'Home', pc_hmemail
from Contact
) union all
(select contact_id, 'Mobile', EmailAddress2
from Contact
)
) e
where email is not null and email <> ''
) e
where NumTimes > 1
order by email
I'd first suggest to continue to normalise your datastructure. A person may have several types of contact information. Therefore the personID, typeID and value can be placed into another table. From this table you can create another relation with a type table, where you keep track of the different contact types (e.g. Home E-mail, Work e-mail, Twitter, linkedIn, Facebook etc). It does not only improve the extendibility of your system but also enables to run these types of queries much more efficiently.
SELECT user.username FROM user u LEFT JOIN contactinfo ci ON u.user_id=ci.user_id LEFT JOIN contacttype ct ON ci.type_id=ct.type_id GROUP BY ci.value HAVING count(value)>1 would be the query to find any duplicate source

Parent-child sql query with order by and limit

I have two tables DOCUMENT and ATTRIBUTES like these
DOCUMENT(id),
ATTRIBUTE(name, value, doc_fk).
I need to run a query that works like this "abstract query"
select top 100 documents
where $state='COMPLETED'
order by $creationDate
Where $state and $creationDate are two attributes.
Note that the limit is on documents, not attributes, and sort and filter are on two different attributes. The final query should return all document attributes, not only the filtered/sorted ones.
I was able to write this with a very complex query and I'm looking for better alternatives. I could post my solution if useful, but I do not want to point you in the, possibly, wrong direction.
It's ok to get a FEW extra documents, like 1000 instead of 100, and filter/sort in memory.
Could be ok for the limit not to be exact, like 74 instead of the required limit 100, but not too far from it.
Extra "soft" requirements:
the query should work with several databases (oracle, mysql and sqlserver), so weird analytic functions should be avoided unless available on all platforms
should work with JPA (eclipselink 2.4.0 implementation)
The expected output is something like this
DOC_ID ATTRIBUTE_NAME VALUE
123 state COMPLETED
123 creationDate 21/11/2012
123 userid someone
456 state COMPLETED
...
Ah, the flaws of an EAV design.
Try this.
select
top 100
document.*
from document
inner join attribute astate on document.id = astate.doc_fk
and astate.name='state'
and astate.value = 'completed'
inner join attribute acreation on document.id = acreation.doc_fk
and acreation.name='creationdate'
order by cast(acreation.value as date)
But it's only going to get more complicated if you persist with this EAV structure.
(PS. MySQL doesn't use TOP, but LIMIT instead)
SELECT doc_id, attr_name, attr_val, creationDate FROM
(
SELECT * FROM (
SELECT
doc.id as 'doc_id', attr.name as 'attr_name', null as 'attr_val', attr.value as 'creationDate'
FROM
ATTRIBUTE attr
LEFT JOIN
DOCUMENT doc ON attr.doc_fk = doc.id
WHERE
attr.name='creationDate'
ORDER BY creationDate desc;
) AS dt1
UNION ALL
SELECT * FROM(
SELECT
doc.id as 'doc_id', attr.name as 'attr_name', attr.value as 'attr_val', null as 'creationDate'
FROM
ATTRIBUTE attr
LEFT JOIN
DOCUMENT doc ON attr.doc_fk = doc.id;
) as dt2
) as dt0 GROUP BY doc_id ORDER by creationDate desc LIMIT 100;
Derived table 1 (dt1) gives you all the date attributes - to enable order your results by document's creation date.
Derived table 2 gives you all the attribute.. all put together by "union all", enables you to group by document, then order by the date of creation.
Hope this is in the right direction.

SQL many-to-many matching

I'm implementing a tagging system for a website. There are multiple tags per object and multiple objects per tag. This is accomplished by maintaining a table with two values per record, one for the ids of the object and the tag.
I'm looking to write a query to find the objects that match a given set of tags. Suppose I had the following data (in [object] -> [tags]* format)
apple -> fruit red food
banana -> fruit yellow food
cheese -> yellow food
firetruck -> vehicle red
If I want to match (red), I should get apple and firetruck. If I want to match (fruit, food) I should get (apple, banana).
How do I write a SQL query do do what I want?
#Jeremy Ruten,
Thanks for your answer. The notation used was used to give some sample data - my database does have a table with 1 object id and 1 tag per record.
Second, my problem is that I need to get all objects that match all tags. Substituting your OR for an AND like so:
SELECT object WHERE tag = 'fruit' AND tag = 'food';
Yields no results when run.
Given:
object table (primary key id)
objecttags table (foreign keys objectId, tagid)
tags table (primary key id)
SELECT distinct o.*
from object o join objecttags ot on o.Id = ot.objectid
join tags t on ot.tagid = t.id
where t.Name = 'fruit' or t.name = 'food';
This seems backwards, since you want and, but the issue is, 2 tags aren't on the same row, and therefore, an and yields nothing, since 1 single row cannot be both a fruit and a food.
This query will yield duplicates usually, because you will get 1 row of each object, per tag.
If you wish to really do an and in this case, you will need a group by, and a having count = <number of ors> in your query for example.
SELECT distinct o.name, count(*) as count
from object o join objecttags ot on o.Id = ot.objectid
join tags t on ot.tagid = t.id
where t.Name = 'fruit' or t.name = 'food'
group by o.name
having count = 2;
Oh gosh I may have mis-interpreted your original comment.
The easiest way to do this in SQL would be to have three tables:
1) Tags ( tag_id, name )
2) Objects (whatever that is)
3) Object_Tag( tag_id, object_id )
Then you can ask virtually any question you want of the data quickly, easily, and efficiently (provided you index appropriately). If you want to get fancy, you can allow multi-word tags, too (there's an elegant way, and a less elegant way, I can think of).
I assume that's what you've got, so this SQL below will work:
The literal way:
SELECT obj
FROM object
WHERE EXISTS( SELECT *
FROM tags
WHERE tag = 'fruit'
AND oid = object_id )
AND EXISTS( SELECT *
FROM tags
WHERE tag = 'Apple'
AND oid = object_id )
There are also other ways you can do it, such as:
SELECT oid
FROM tags
WHERE tag = 'Apple'
INTERSECT
SELECT oid
FROM tags
WHERE tag = 'Fruit'
#Kyle: Your query should be more like:
SELECT object WHERE tag IN ('fruit', 'food');
Your query was looking for rows where the tag was both fruit AND food, which is impossible seeing as the field can only have one value, not both at the same time.
Combine Steve M.'s suggestion with Jeremy's you'll get a single record with what you are looking for:
select object
from tblTags
where tag = #firstMatch
and (
#secondMatch is null
or
(object in (select object from tblTags where tag = #secondMatch)
)
Now, that doesn't scale very well but it will get what you are looking for. I think there is a better way to go about doing this so you can easily have N number of matching items without a great deal of impact to the code but it currently escapes me.
I recommend the following schema.
Objects: objectID, objectName
Tags: tagID, tagName
ObjectTag: objectID,tagID
With the following query.
select distinct
objectName
from
ObjectTab ot
join object o
on o.objectID = ot.objectID
join tabs t
on t.tagID = ot.tagID
where
tagName in ('red','fruit')
I'd suggest making your table have 1 tag per record, like this:
apple -> fruit
apple -> red
apple -> food
banana -> fruit
banana -> yellow
banana -> food
Then you could just
SELECT object WHERE tag = 'fruit' OR tag = 'food';
If you really want to do it your way though, you could do it like this:
SELECT object WHERE tag LIKE 'red' OR tag LIKE '% red' OR tag LIKE 'red %' OR tag LIKE '% red %';