Multiple WHERE clause for same Columns in TSQL - tsql

I am trying to query two tables that are in 1-to-many relationship.
What I've done is create a View knowing that i might end up with multiple records for the first table.
My scenario is as follows: I have a table "Items" and table "Properties".
"Properties" table contains an ItemsId column, PropertyId, PropertyValueId columns.
"Items" table/object contains a list of "Properties".
How would I query that "View" such that, I want to get all "Items" records that have a combination of "PropertyId" & "PropertyValueId" values.
In other words something similar to:
WHERE
(PropertyId = #val1 AND PropertyValueId = #val2) OR
(PropertyId = #val3 AND PropertyValueId = #val4) OR
(PropertyId = #val5 AND PropertyValueId = #val6)
WHERE clause is just a loop over "Items.Properties" collection.
"Items" represents a table of Items being stored in the database. Each & every Item has some dynamic properties, one or more. That's why I have another table called "Properties". Properties table contains columns:
ItemId, PropertyId, PropertyValue
"Item" object has a collection of Properties/Values. Prop1:val1, Prop2:val2, etc ...
Thanks

I may not have understood your requirement (despite the update) - if this or any other answer doesn't solve the problem please add some sample data for Items, Properties and the output and then hopefully it would become clear.
If Items is a specification of the property name-value pairs that you need (and has nothing to do with ItemId on Properties which seems strange...)
select p.itemid
from properties p
where exists (select 1 from items i where i.propertyId = p.propertyId and i.propertyValueId = p.propertyValueId)
group by p.itemid
having count(distinct p.propertyid) = (select count(*) from items)
This returns a set of itemids that have one (and only one) property value for each property defined in items. You can put the items count into a variable if you want.

I would use a query like this:
SELECT ItemId
FROM ItemView
WHERE (PropertyId = #val1 AND PropertyValueId = #val2)
OR (PropertyId = #val3 AND PropertyValueId = #val4)
OR (PropertyId = #val5 AND PropertyValueId = #val6)
GROUP BY ItemId
HAVING COUNT(*) = 3
The WHERE clause is the same as in your question, it only allows a row to be selected if the row has a matching property. You only need to make sure additionally that the items obtained have all the properties in the filter, which is done in the above query with the help of the HAVING clause: you are requesting items with 3 specific properties, therefore the number of properties per item in your result set (COUNT(*)) should be equal to 3.
In a more general case, when the number of properties queried may be arbitrary, you should probably consider passing the arguments in the form of a table and join the view to it:
…
FROM ItemView v
INNER JOIN RequestedProperties r ON v.PropertyId = r.Id
AND v.PropertyValueId = r.ValueId
GROUP BY v.ItemId
HAVING COUNT(*) = (SELECT COUNT(*) FROM RequestedProperties)

Related

Single Value Expression in When Then Aggregate Function TSQL

I am trying to map a certain value of a column based on its count on another table. If the count of [Location] i.e a column of IMPORT.DATA_SCRAP table in each row. For now for location static value i.e Utah and Kathmandu is supplied for test purpose only is equal to 1, then only i need to get the result in the select statement i.e only single value expression must be returned but here n rows of table with value is returned.
For. eg. In the below query,total rows of IMPORT.DATA_SCRAP gets returned, i only need the single first row value in my case.
I came to know whether cursor or CTE will acheive my result but i am unable to figure it out.
Here,
select
case
when
((SELECT COUNT(stateName) FROM Location.tblState where stateName = 'Utah')=1)
then (select stateName, CountryName from Location.tblState where stateName= 'Utah')
end as nameof
from IMPORT.DATA_SCRAP
The relation between country, state, city is as below:
select
case
when
((SELECT COUNT(cityName) FROM Location.tblCity where cityName = 'Kathmandu')=1)
then (select ct.countryName from Location.tblCity c
inner join Location.tblState s
on c.stateID = s.StateID
inner join Location.tblCountry ct
on ct.countryId = s.CountryId
where c.cityName = 'Kathmandu'
)
end as nameof
from IMPORT.DATA_SCRAP
How can i return only a single value expresion despite of multiple nmax rows of IMPORT.DATA_SCRAP row in the result.
If i comment out the -- from IMPORT.DATA_SCRAP in the above query i would get the desired single result expression in my case, but unable how can i acheive it in other ways or suggest me the appropriate way to do these types of situation.

Query row and join many rows as JSON array

I am looking to join three tables via ids, the outcome being three json columns with the content from each.
The issue I am facing is that for each cat_request there are many cat_request_fields, I am currently getting cat_request_fields as one object and not an array of objects.
This query gets me a result set with cat_requests and cat_request_fields.
SELECT
row_to_json("cat_requests") AS cat_request,
array_agg(row_to_json("cat_request_fields")) AS cat_request_fields
FROM
"cat_requests"
LEFT OUTER JOIN "cat_request_fields" ON "cat_requests"."id" = "cat_request_fields"."cat_request_id"
GROUP BY
"cat_requests"."id"
LIMIT 10;
This query gets me a result set with cats and cat_requests.
SELECT
row_to_json("cat_requests") as cat_request,
row_to_json("cats") as cat
FROM
"cat_requests",
"cats"
WHERE
"cat_requests"."cat_id" = "cats"."id"
LIMIT 1;
I'm looking for a query that will give me a combination of the two...
How can I modify this query to map the cat_request_fields to be an array of rows and not just one.
SELECT
row_to_json("cat_requests") AS cat_request,
(select row_to_json("cats".*) as cats from "cats" where "cats"."id" = "cat_requests"."cat_id"),
array_agg(row_to_json("cat_request_fields")) AS cat_request_fields
FROM
"cat_requests"
INNER JOIN "cat_request_fields" ON "cat_requests"."id" = "cat_request_fields"."cat_request_id"
GROUP BY
"cat_requests"."id"
LIMIT 6;

comprare aggregate sum function to number in postgres

I have the next query which does not work:
UPDATE item
SET popularity= (CASE
WHEN (select SUM(io.quantity) from item i NATURAL JOIN itemorder io GROUP BY io.item_id) > 3 THEN TRUE
ELSE FALSE
END);
Here I want to compare each line of inner SELECT SUM value with 3 and update popularity. But SQL gives error:
ERROR: more than one row returned by a subquery used as an expression
I understand that inner SELECT returns many values, but can smb help me in how to compare each line. In other words make loop.
When using a subquery you need to get a single row back, so you're effectively doing a query for each record in the item table.
UPDATE item i
SET popularity = (SELECT SUM(io.quantity) FROM itemorder io
WHERE io.item_id = i.item_id) > 3;
An alternative (which is a postgresql extension) is to use a derived table in a FROM clause.
UPDATE item i2
SET popularity = x.orders > 3
FROM (select i.item_id, SUM(io.quantity) as orders
from item i NATURAL JOIN itemorder io GROUP BY io.item_id)
as x(item_id,orders)
WHERE i2.item_id = x.item_id
Here you're doing a single group clause as you had, and we're joining the table to be updated with the results of the group.

Embedded Select for From value

Having difficulty framing my question for Google.
I am trying to embed a select statement which pulls partition table names from a view. I want to cycle through these tables and do a search within them for a value count.
I have:
SELECT COUNT(objectA)
FROM (SELECT partitiontablename
FROM partitions
WHERE tablename = 'x')
AS tableNameQuery
WHERE objectB = 1
I am getting ERROR: column "objectB" does not exist
The partitions tables do have objectB (they are the same table structure). Can you guide me to what i am doing wrong?
Thank you!
Try this query:
SELECT COUNT(objectA)
FROM (
SELECT partitiontablename, objectB, objectA
FROM partitions
WHERE tablename = 'x'
) AS tableNameQuery
WHERE objectB = 1
The subquery in your query retrieves only partitiontablename column, so the outer query sees only that column, but doesn't see objectB.
The same problem is with objectA used in COUNT() in the outer query.

Select most reviewed courses starting from courses having at least 2 reviews

I'm using Flask-SQLAlchemy with PostgreSQL. I have the following two models:
class Course(db.Model):
id = db.Column(db.Integer, primary_key = True )
course_name =db.Column(db.String(120))
course_description = db.Column(db.Text)
course_reviews = db.relationship('Review', backref ='course', lazy ='dynamic')
class Review(db.Model):
__table_args__ = ( db.UniqueConstraint('course_id', 'user_id'), { } )
id = db.Column(db.Integer, primary_key = True )
review_date = db.Column(db.DateTime)#default=db.func.now()
review_comment = db.Column(db.Text)
rating = db.Column(db.SmallInteger)
course_id = db.Column(db.Integer, db.ForeignKey('course.id') )
user_id = db.Column(db.Integer, db.ForeignKey('user.id') )
I want to select the courses that are most reviewed starting with at least two reviews. The following SQLAlchemy query worked fine with SQlite:
most_rated_courses = db.session.query(models.Review, func.count(models.Review.course_id)).group_by(models.Review.course_id).\
having(func.count(models.Review.course_id) >1) \ .order_by(func.count(models.Review.course_id).desc()).all()
But when I switched to PostgreSQL in production it gives me the following error:
ProgrammingError: (ProgrammingError) column "review.id" must appear in the GROUP BY clause or be used in an aggregate function
LINE 1: SELECT review.id AS review_id, review.review_date AS review_...
^
'SELECT review.id AS review_id, review.review_date AS review_review_date, review.review_comment AS review_review_comment, review.rating AS review_rating, review.course_id AS review_course_id, review.user_id AS review_user_id, count(review.course_id) AS count_1 \nFROM review GROUP BY review.course_id \nHAVING count(review.course_id) > %(count_2)s ORDER BY count(review.course_id) DESC' {'count_2': 1}
I tried to fix the query by adding models.Review in the GROUP BY clause but it did not work:
most_rated_courses = db.session.query(models.Review, func.count(models.Review.course_id)).group_by(models.Review.course_id).\
having(func.count(models.Review.course_id) >1) \.order_by(func.count(models.Review.course_id).desc()).all()
Can anyone please help me with this issue. Thanks a lot
SQLite and MySQL both have the behavior that they allow a query that has aggregates (like count()) without applying GROUP BY to all other columns - which in terms of standard SQL is invalid, because if more than one row is present in that aggregated group, it has to pick the first one it sees for return, which is essentially random.
So your query for Review basically returns to you the first "Review" row for each distinct course id - like for course id 3, if you had seven "Review" rows, it's just choosing an essentially random "Review" row within the group of "course_id=3". I gather the answer you really want, "Course", is available here because you can take that semi-randomly selected Review object and just call ".course" on it, giving you the correct Course, but this is a backwards way to go.
But once you get on a proper database like Postgresql you need to use correct SQL. The data you need from the "review" table is just the course_id and the count, nothing else, so query just for that (first assume we don't actually need to display the counts, that's in a minute):
most_rated_course_ids = session.query(
Review.course_id,
).\
group_by(Review.course_id).\
having(func.count(Review.course_id) > 1).\
order_by(func.count(Review.course_id).desc()).\
all()
but that's not your Course object - you want to take that list of ids and apply it to the course table. We first need to keep our list of course ids as a SQL construct, instead of loading the data - that is, turn it into a derived table by converting the query into a subquery (change the word .all() to .subquery()):
most_rated_course_id_subquery = session.query(
Review.course_id,
).\
group_by(Review.course_id).\
having(func.count(Review.course_id) > 1).\
order_by(func.count(Review.course_id).desc()).\
subquery()
one simple way to link that to Course is to use an IN:
courses = session.query(Course).filter(
Course.id.in_(most_rated_course_id_subquery)).all()
but that's essentially going to throw away the "ORDER BY" you're looking for and also doesn't give us any nice way of actually reporting on those counts along with the course results. We need to have that count along with our Course so that we can report it and also order by it. For this we use a JOIN from the "course" table to our derived table. SQLAlchemy is smart enough to know to join on the "course_id" foreign key if we just call join():
courses = session.query(Course).join(most_rated_course_id_subquery).all()
then to get at the count, we need to add that to the columns returned by our subquery along with a label so we can refer to it:
most_rated_course_id_subquery = session.query(
Review.course_id,
func.count(Review.course_id).label("count")
).\
group_by(Review.course_id).\
having(func.count(Review.course_id) > 1).\
subquery()
courses = session.query(
Course, most_rated_course_id_subquery.c.count
).join(
most_rated_course_id_subquery
).order_by(
most_rated_course_id_subquery.c.count.desc()
).all()
A great article I like to point out to people about GROUP BY and this kind of query is SQL GROUP BY techniques which points out the common need for the "select from A join to (subquery of B with aggregate/GROUP BY)" pattern.