SELECT CASE with Alias in SQL Server 2008 - sql-server-2008

I have a stored Procedure that I cant get to work:
ALTER PROCEDURE GetBrands
#sortColumn INT
AS
SELECT DISTINCT(tblBrand.BrandID),
tblBrandinCategory.CategoryID,
tblBrand.BrandName AS Brand,
AVG(tblReview.Grade) AS AverageGrade,
COUNT(tblReview.ReviewID) AS Review
FROM tblBrand LEFT JOIN
tblBrandinCategory ON tblBrand.BrandID = tblBrandinCategory.BrandID LEFT JOIN
tblReview ON tblBrand.BrandID = tblReview.BrandID
GROUP BY tblBrand.BrandID, tblBrandinCategory.CategoryID, tblBrand.BrandName
ORDER BY
CASE
WHEN #sortColumn = 1 THEN Brand
WHEN #sortColumn = 2 THEN Review
WHEN #sortColumn = 4 THEN AverageGrade
ELSE Brand
END
The result that I want to have is a list with brands, that only will be displayed once. The tblBrandInCategory messes that up for me.
tblBrand
BrandID BrandName
1 Nike
2 Adidas
3 Puma
tblCategory
CategoryID CategoryName
1 Shoes
2 Shorts
3 Pants
tblBrandInCategory
CategoryID BrandID
1 1
2 1
3 1
tblReview
ReviewID Grade BrandID
1 5 1
2 9 1
3 2 1
I get the result multiplyed with three becouse BrandID 1 exist 3 times in tblBrandInCategoiry.
Another problem is that in the ORDER BY CASE I get errors that AverageGrade is not recognizable, but tblReview.Grade is fine, but I want to order it on the Average Grade.

Another problem is that in the ORDER BY CASE I get errors that
AverageGrade is not recognizable, but tblReview.Grade is fine, but I
want to order it on the Average Grade.
AverageGrade can not be used as a column to sort until your main query is the Sub Query....
I get the result multiplyed with three becouse BrandID 1 exist 3 times
in tblBrandInCategoiry.
create table #tblBrand
(
BrandID int,
BrandName varchar(10)
)
create table #tblCategory
(
CategoryID int,
CategoryName varchar(10)
)
create table #tblBrandInCategory
(
CategoryID int,
BrandID int
)
create table #tblReview
(
ReviewID int,
Grade int,
BrandID int
)
insert into #tblBrand(BrandID, BrandName)values(1, 'Nike')
insert into #tblBrand(BrandID, BrandName)values(2, 'Adidas')
insert into #tblBrand(BrandID, BrandName)values(3, 'Puma')
insert into #tblCategory(CategoryID, CategoryName)values(1, 'Shoes')
insert into #tblCategory(CategoryID, CategoryName)values(2, 'Shorts')
insert into #tblCategory(CategoryID, CategoryName)values(3, 'Pants')
insert into #tblBrandInCategory(CategoryID, BrandID)values(1, 1)
insert into #tblBrandInCategory(CategoryID, BrandID)values(2, 1)
insert into #tblBrandInCategory(CategoryID, BrandID)values(3, 1)
insert into #tblReview(ReviewID, Grade, BrandID)values(1, 5, 1)
insert into #tblReview(ReviewID, Grade, BrandID)values(2, 5, 9)
insert into #tblReview(ReviewID, Grade, BrandID)values(3, 2, 1)
Select BrandID, Brand, AverageGrade, Review
From
(
SELECT DISTINCT(#tblBrand.BrandID),
--#tblBrandinCategory.CategoryID,
#tblBrand.BrandName AS Brand,
AVG(#tblReview.Grade) AS AverageGrade,
COUNT(#tblReview.ReviewID) AS Review
FROM #tblBrand
LEFT JOIN #tblBrandinCategory ON #tblBrand.BrandID = #tblBrandinCategory.BrandID
LEFT JOIN #tblReview ON #tblBrand.BrandID = #tblReview.BrandID
GROUP BY #tblBrand.BrandID, #tblBrandinCategory.CategoryID, #tblBrand.BrandName
)K
ORDER BY
CASE
WHEN #sortColumn = 1 THEN Brand
WHEN #sortColumn = 2 THEN Review
WHEN #sortColumn = 4 THEN AverageGrade
ELSE Brand
drop table #tblBrand
drop table #tblCategory
drop table #tblBrandInCategory
drop table #tblReview
Final Resultset

Just edit the SELECT to leave off tblBrandInCategory results:
SELECT tblBrand.BrandID,
tblBrand.BrandName AS Brand,
AVG(tblReview.Grade) AS AverageGrade,
COUNT(tblReview.ReviewID) AS Review
FROM tblBrand
--tblBrandinCategory isn't even needed for what you're doing
--LEFT JOIN tblBrandinCategory ON tblBrand.BrandID = tblBrandinCategory.BrandID
LEFT JOIN tblReview ON tblBrand.BrandID = tblReview.BrandID
GROUP BY tblBrand.BrandID, tblBrand.BrandName
This would need to be edited further to account for null joins, where a grade does not exist (maybe with AVG(ISNULL(tblReview.Grade, 0))). Depends on your requirements.

Related

How to transform this SQL query to query with GROUP BY and HAVING?

There're products, products_filters, filters_values tables in DB.
products_filters references to products table via product_id column which is a foreign key.
Also products_filters references to filters_values table via filter_value_id column which is also a foreign key
When user selects filters, SQL query which extracts all ids of suitable products is formed.
For example, chosen filters are:
Sex: Male, Female
Brand: Brand1, Brand2, Brand3
How it should work:
It needs to select all products which have filter Sex set to Male OR Female AND filter Brand set to Brand1 OR Brand2 OR Brand3. But products having matching only in one of the chosen filter category either Sex or Brand, must not be selected. It necessiraly to have matching in all selected categories.
I think SQL should look like this:
SELECT product_id FROM products_filters WHERE
(filter_value_id = 1 OR filter_value_id = 2)
AND
(filter_value_id = 3 OR filter_value_id = 4 OR filter_value_id = 5)
Where 1 is Male, 2 is Female, 3 is Brand1, 4 is Brand2, 5 is Brand3.
But this query doesn't work.
In my previous question I was answered that GROUP BY and HAVING may help.
Q: How can I transform SQL above with GROUP BY and HAVING?
Given
drop table if exists t;
create table t(id int ,gender varchar(1), brand varchar(1));
insert into t values
(1,'m',1),(1,'f',2),(1,'m',3),(2,'f',1),(2,'f',2),(2,'f',3),
(3,'m',1),(4,'f',2);
Correlated sub queries with distinct
select distinct id
from t
where
(select count(distinct gender) from t t1 where gender in('m','f') and t1.id = t.id) = 2 and
(select count(distinct brand) from t t1 where brand in(1,2,3) and t1.id = t.id) = 3
;
+------+
| id |
+------+
| 1 |
+------+
1 row in set (0.002 sec)
SELECT product_id
FROM products_filters AS f1
JOIN products_filters AS f2 USING(product_id)
WHERE f1.filter_value_id IN (1,2)
AND f2.filter_value_id IN (3,4,5)
(I don't think GROUP BY...HAVING COUNT(*) = 2 is reasonable for this case.)
(The EAV schema design is a pain to deal with.)

Recursively select all subcategories of a category

I have a table with the following fields:
node_id (int, AI)
category_id, (int)
parent_node_id (int)
How can I select all the nodes (or categories, if you wish) that hang down from a given category id. And by "hang down" I mean all the recursively stored nodes.
Example:
Category node parent
1 1 none
2 2 none
3 3 none
4 4 1
5 5 4
6 6 5
Expected output of the select:
Category node parent
1 1 none
4 4 1
5 5 4
6 6 5
exactly I don't know what you are looking for but as per my assumption.
DECLARE #Table1 TABLE
(node_id varchar(9), category_id varchar(5), parent_node_id varchar(11))
;
INSERT INTO #Table1
(node_id, category_id, parent_node_id)
VALUES
('Category1', 'node1', 'parentnone.'),
('Category2', 'node2', 'parentnone.'),
('Category3', 'node3', 'parentnone.'),
('Category4', 'node4', 'parent1.'),
('Category5', 'node5', 'parent4.'),
('Category6', 'node6', 'parent5.')
;
select node_id, category_id, parent_node_id from (
select node_id, category_id, parent_node_id,Row_number()OVER(PARTITION BY parent_node_id ORDER BY node_id desc)RN from #Table1
GROUP BY node_id, category_id, parent_node_id
)T
WHERE T.RN = 1
--ORDER BY cat desc
ORDER BY RIGHT(category_id,1)
You can create a function that will return whether the category is a child at any level to category you are interested.
CREATE FUNCTION `is_child_of`(id INT, related_to_id INT) RETURNS int(11)
BEGIN
DECLARE `exists` BOOL;
/* to avoid infinite loop */
SELECT EXISTS(SELECT `parent_id` FROM `category` WHERE `category_id` = id) INTO `exists`;
IF `exists` IS FALSE THEN
RETURN 0;
END IF;
WHILE id IS NOT NULL DO
IF id = related_to_id THEN
RETURN 1;
END IF;
SELECT `parent_id` INTO id FROM `category` WHERE `category_id` = id;
END WHILE;
RETURN 0;
END
Then just select by it's result regarding category you want to drill down.
For example for a category with id - 1
SELECT * FROM `category` WHERE `is_child_of`(category_id, 1);
I admit it is far from being efficient. It is difficult to be efficient when dealing with hierarchy in a relational database.
Assuming the table is called categories:
select
children.category_id as category,
children.node_id as node,
parent.node_id as parent_node_id
from categories parent
join categories children
on parent.node_id = children.parent_id;
That should get you somewhere.

Check whether particular name order is available in my table

I have the following table stops how can I check whether the following stops name order GHI, JKL, MNO is available in my stops table?
stops table:
CREATE TABLE IF NOT EXISTS stops
(
stop_id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
name varchar(30) NOT NULL,
lat double(10,6),
longi double(10,6)
);
Simple:
1 ABC
2 DEF
3 GHI
4 JKL
5 MNO
6 PQR
7 SDU
8 VWX
This query will return 1 when there is an ordered of 'GHI','JKL','MNO':
SELECT 1
FROM stops s1
JOIN stops s2 ON s1.stop_id = s2.stop_id - 1
JOIN stops s3 ON s2.stop_id = s3.stop_id - 1
WHERE CONCAT(s1.name, s2.name, s3.name) = CONCAT('GHI','JKL','MNO')
SQL Fiddle Demo
This is a variation of the well known "find equal sets" task.
You need to insert the searched route into a table with a sequenced stop_id:
create table my_stops(stop_id INT NOT NULL,
name varchar(30) NOT NULL);
insert into my_stops (stop_id, name)
values (1, 'GHI'),(2, 'JKL'),(3, 'MNO');
Then you join and calculate the difference between both sequences. This returns a totally meaningless number, but always the same for consecutive values:
select s.*, s.stop_id - ms.stop_id
from stops as s join my_stops as ms
on s.name = ms.name
order by s.stop_id;
Now group by that meaningless number and search for a count equal to the number of searched steps:
select min(s.stop_id), max(s.stop_id)
from stops as s join my_stops as ms
on s.name = ms.name
group by s.stop_id - ms.stop_id
having count(*) = (select count(*) from my_stops)
See Fiddle
Another alternative:
select 1
from stops x
where x.name = 'GHI'
and (select GROUP_CONCAT(name order by y.stop_id)
from stops y where y.stop_id between x.stop_id + 1
and x.stop_id + 2
) = 'JKL,MNO';

How to find if a list/set is contained within another list

I have a list of product IDs and I want to find out which orders contain all those products. Orders table is structured like this:
order_id | product_id
----------------------
1 | 222
1 | 555
2 | 333
Obviously I can do it with some looping in PHP but I was wondering if there is an elegant way to do it purely in mysql.
My ideal fantasy query would be something like:
SELECT order_id
FROM orders
WHERE (222,555) IN GROUP_CONCAT(product_id)
GROUP BY order_id
Is there any hope or should I go read Tolkien? :) Also, out of curiosity, if not possible in mysql, is there any other database that has this functionality?
You were close
SELECT order_id
FROM orders
WHERE product_id in (222,555)
GROUP BY order_id
HAVING COUNT(DISTINCT product_id) = 2
Regarding your "out of curiosity" question in relational algebra this is achieved simply with division. AFAIK no RDBMS has implemented any extension that makes this as simple in SQL.
I have a preference for doing set comparisons only in the having clause:
select order_id
from orders
group by order_id
having sum(case when product_id = 222 then 1 else 0 end) > 0 and
sum(case when product_id = 555 then 1 else 0 end) > 0
What this is saying is: get me all orders where the order has at least one product 222 and at least one product 555.
I prefer this for two reasons. The first is generalizability. You can arrange more complicated conditions, such as 222 or 555 (just by changing the "and" to and "or"). Or, 333 and 555 or 222 without 555.
Second, when you create the query, you only have to put the condition in one place, in the having clause.
Assuming your database is properly normalized, i.e. there's no duplicate Product on a given Order
Mysqlism:
select order_id
from orders
group by order_id
having sum(product_id in (222,555)) = 2
Standard SQL:
select order_id
from orders
group by order_id
having sum(case when product_id in (222,555) then 1 end) = 2
If it has duplicates:
CREATE TABLE tbl
(`order_id` int, `product_id` int)
;
INSERT INTO tbl
(`order_id`, `product_id`)
VALUES
(1, 222),
(1, 555),
(2, 333),
(1, 555)
;
Do this then:
select order_id
from tbl
group by order_id
having count(distinct case when product_id in (222,555) then product_id end) = 2
Live test: http://www.sqlfiddle.com/#!2/fa1ad/5
CREATE TABLE orders
( order_id INTEGER NOT NULL
, product_id INTEGER NOT NULL
);
INSERT INTO orders(order_id,product_id) VALUES
(1, 222 ) , (1, 555 ) , (2, 333 )
, (3, 222 ) , (3, 555 ) , (3, 333 ); -- order#3 has all the products
CREATE TABLE products AS (SELECT DISTINCT product_id FROM orders);
SELECT *
FROM orders o1
--
-- There should not exist a product
-- that is not part of our order.
--
WHERE NOT EXISTS (
SELECT *
FROM products pr
WHERE 1=1
-- extra clause: only want producs from a literal list
AND pr.product_id IN (222,555,333)
-- ... that is not part of our order...
AND NOT EXISTS ( SELECT *
FROM orders o2
WHERE o2.product_id = pr.product_id
AND o2.order_id = o1.order_id
)
);
Result:
order_id | product_id
----------+------------
3 | 222
3 | 555
3 | 333
(3 rows)

How to get ID for INSERT if name already used, else generate new one

I'm having a bit of trouble with an INSERT query.
I have a table I'm inserting a value into that's like this:
TABLE cars
ID Brand Model B_ID
---------------------------
1 Ford Escort 1
2 Ford Focus 1
3 Nissan Micra 2
4 Renault Megane 3
5 Ford Mustang 1
ID is unique and B_ID is the same ID for every same brand.
When inserting a new entry I want to be able to check if a brand is already in there and use that same B_ID otherwise I want to increment the highest B_ID and insert that.
I've got this far:
INSERT INTO 'cars' ('brand', 'model', 'B_ID')
VALUES (
'Nissan'
'Note'
'SELECT B_ID FROM cars WHERE brand = 'Nissan'
)
How can I get the highest B_ID and increment it by one if there is no match with my subquery because it's a new brand?
I'm using MySQL.
INSERT INTO `cars` (`brand`, `model`, `B_ID`)
select 'Nissan', 'Note', coalesce(Max(B_ID),0)+1 FROM cars WHERE brand = 'Nissan'
Until you normalize your tables:
INSERT INTO cars
(brand, model, B_ID)
SELECT 'Nissan'
, 'Note'
, COALESCE( ( SELECT B_ID
FROM cars
WHERE brand = 'Nissan'
LIMIT 1
)
, ( SELECT MAX(B_ID)
FROM cars
) + 1
, 1 --- this is for the case when the table is empty
)
Also notice that if you have multiple concurrent INSERT, you may end with rows that have different brand but same B_ID.