Self-referencing MySQL table - mysql

I have a table:
CREATE TABLE IF NOT EXISTS `columns` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`section_id` int(11) NOT NULL,
`columntype` int(11) NOT NULL,
`name` varchar(128) COLLATE utf8_bin NOT NULL,
`refer_type` int(11) NOT NULL,
PRIMARY KEY (`id`));
with the following content:
INSERT INTO `columns` (`id`, `section_id`, `columntype`, `name`, `refer_type`) VALUES
(9, 35, 1, 'Headline1', 0),
(10, 36, 1, 'Headline2', 0),
(11, 36, 5, 'Headline3', 10),
(12, 36, 3, 'Headline4', 0),
(13, 36, 4, 'Headline5', 10);
The column refer_from contains info on which other column the containing data must be pulled from and calculated with. When Headline2 is changed in my webapp, the content of Headline3 and Headline5 should be changed. I'm performing the final calculations in the webapp, but I need a SQL that will give me a result like
id name columntype refer_type
10 Headline2 1 4, 5
11 Headline3 5 null
12 Headline4 3 null
13 Headline5 4 null
I've tried with something like SELECT id, name, columntype, (SELECT GROUP_CONCAT(refer_type SEPARATOR "," )... but that gave me 4,5 on all four result rows.
Thanks in advance!

If I get it right, you might need something like this select:
SELECT c.id, c.name, c.columntype, ref.reftypes
FROM columns c LEFT JOIN
(SELECT GROUP_CONCAT(columntype SEPARATOR "," ) reftypes, refer_type
FROM columns
GROUP BY refer_type) ref
ON c.id = ref.refer_type;

This does it:
SELECT c1.id, c1.name, c1.columntype, GROUP_CONCAT(c2.columntype ORDER BY c2.columntype) AS refer_type
FROM columns AS c1
LEFT JOIN columns AS c2 ON c1.id = c2.refer_type
GROUP BY c1.id
ORDER BY c1.id
DEMO

Thanks! Both SQLs works great! I've selected Bamar's answer because of the simplicity.

Related

Unexpected LEFT JOIN behavior with condition in ON clause

I have the following three tables:
fees - has information about a fee on a bill
payments - has information about payments on a bill (including payments to fees)
details - has detailed information about the payment
I'm struggling to understand why my query isn't returning the result I expect.
SQL Fiddle: sqlfiddle.com/#!9/942636/3
CREATE TABLE fees (
receipt_number int(11) NOT NULL,
bill_number int(11) NOT NULL,
version int(11) NOT NULL,
fee_id int(11) NOT NULL,
fee_type varchar(30) NOT NULL
);
CREATE TABLE payments (
receipt_number int(11) NOT NULL,
bill_number int(11) NOT NULL,
version int(11) NOT NULL,
payment_id int(11) NOT NULL,
amount decimal(13,2) NOT NULL DEFAULT '0.00'
);
CREATE TABLE details (
receipt_number int(11) NOT NULL,
payment_id int(11) NOT NULL,
fee_type varchar(30) DEFAULT NULL
amount decimal(13,2) NOT NULL DEFAULT '0.00'
);
INSERT INTO fees (receipt_number, bill_number, version, fee_id, fee_type)
VALUES (111, 100, 1, 1, 'a'),
(111, 100, 1, 1, 'b'),
(111, 100, 1, 2, 'c'),
(111, 100, 1, 2, 'd');
INSERT INTO payments (receipt_number, bill_number, version, payment_id, amount)
VALUES (111, 100, 1, 98, 30.00),
(111, 100, 1, 99, 60.00);
INSERT INTO details (receipt_number, payment_id, fee_type, amount)
VALUES (111, 98, 'a', 10.00),
(111, 98, 'b', 10.00),
(111, 98, 'd', 10.00),
(111, 99, 'a', 20.00),
(111, 99, 'b', 20.00),
(111, 99, 'c', 20.00);
I'm attempting to find out if:
A bill has a fee with type 'c'
There is a payment made towards that fee (determined by the fee_type)
My query:
SELECT fees.bill_number, details.receipt_number AS has_payment_type_c
FROM fees
LEFT JOIN payments
USING (bill_number, version)
LEFT JOIN details
ON details.receipt_number = payments.receipt_number
AND details.payment_id = payments.payment_id
AND details.fee_type = 'c'
WHERE fees.fee_type = 'c'
AND details.receipt_number IS NULL;
I get the result:
bill_number has_payment_type_c
100 (null)
I should not see the bill_number in this list, as the bill has a fee and a payment towards that fee.
I am filtering the details.fee_type in the ON clause to both reduce the number of records looked up in that table and to only join to that table for that specific fee_type. The query seems to "work" (0 results) by moving the fee_type join condition to the WHERE clause, but I don't think that is correct.
My questions:
What am I doing wrong in my query? How can I fix it to produce the result I'm looking for, and how does that fix work?
Assuming I get a working query, can I just simply reverse the table order and the SELECT to find the opposite information - payments of a certain fee_type without an associated fee record of that same type?
The answer is to use a derived table that inner joins payments and details, and then left join the derived table to fees:
SELECT DISTINCT fees.bill_number, fees.version, x.bill_number AS has_payment_type_c
FROM fees
LEFT JOIN (
SELECT payments.bill_number, payments.version, details.fee_type
FROM payments
JOIN details
USING(receipt_number, payment_id)
WHERE details.fee_type = 'c'
) x
ON fees.bill_number = x.bill_number
AND fees.version = x.version
WHERE fees.fee_type = 'c'
AND x.bill_number IS NULL;
This will return the expected 0 results, and can be validated by removing the where condition that checks for a null x.bill_number:
bill_number version has_payment_type_c
100 1 100
The same approach works for finding payments without a fee. Use a derived table that inner joins payments and details, then left join to fees. SELECT the fee.bill_number AS has_payment_type_c and add WHERE fee.bill_number IS NULL to the WHERE clause.

Get rows with matching values in column in same table

I have the following DDLs...
CREATE TABLE IF NOT EXISTS `product` (
`id_product` int(10),
`id_manufacturer` int(10)
);
INSERT INTO `product` (`id_product`, `id_manufacturer`) VALUES
(1,1),
(2,1),
(3,2),
(4,1),
(5,2);
CREATE TABLE IF NOT EXISTS `feature_product` (
`id_feature` int(10),
`id_product` int(10),
`id_feature_value` int(10)
);
INSERT INTO `feature_product` (`id_feature`, `id_product`, `id_feature_value`) VALUES
(5, 1, 9),
(5, 2, 9),
(5, 3, 10),
(5, 4, 10),
(7, 5, 10);
http://sqlfiddle.com/#!2/cbe05/1/0
Can you explain me please, how I can get - all Products with the same Manufacturer and the same Feature_value?
Now (in project) I do it with 2 additional SELECT's (for getting id_manufacturer and id_feature_value), but maybe there are more correct (and fast) way?
Thanks for your time and sorry for my English)
I need too see result like this:
id_product |
-----------|
1 |
2 |
only this 2 products have same manufacturer and (at the same time) same feature value
Just use GROUP_CONCAT:
SELECT GROUP_CONCAT(p.id_product SEPARATOR '\n') AS Products
FROM product p
INNER JOIN feature_product fp ON (p.id_product = fp.id_product AND fp.id_feature = 5)
GROUP BY p.id_manufacturer, fp.id_feature_value
HAVING COUNT(p.id_manufacturer) > 1
AND COUNT(fp.id_feature_value)>1;
This will give you the list of Products having multiple Manufacturer Id and Feature Value in a single line, separated by a newline character. You can change the separator as your requirement.
Here is the SQL Fiddle link:
http://sqlfiddle.com/#!2/cbe05/70

MySQL GROUP BY "group" and order by largest ID

I have the following table: Tree. I am trying to select the highest Primary Key ID per scenario_id
id user_id scenario_id
----------------------------------
100 1 10
200 1 10
300 1 5
400 1 5
500 1 5
SELECT * FROM tree
WHERE user_id = 1
GROUP BY scenario_id
ORDER BY id DESC
With my above query I don't get the largest ID. I get 300 and 100 -- But I want to get 200 and 500.
Here is the table dump to test:
CREATE TABLE IF NOT EXISTS `tree` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) DEFAULT NULL,
`scenario_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=latin1;
INSERT INTO `tree` (`id`, `user_id`, `scenario_id`) VALUES
(5, 1, 5),
(100, 1, 10),
(200, 1, 10),
(300, 1, 5),
(400, 1, 5),
(500, 1, 5);
Use an aggregate function to get a specific value for a group
SELECT scenario_id, max(id) as max_id
FROM tree
WHERE user_id = 1
GROUP BY scenario_id
If you would like to keep your select * and avoid grouping to get these results from the same record you could also use a self join:
SELECT t1.*
FROM
tree t1 LEFT JOIN tree t2 ON t1.scenario_id = t2.scenario_id AND t2.id > t1.id
WHERE
t2.id IS NULL;
Sometimes this can be useful to pull additional fields that you can't get as efficiently using a group by/aggregate solution.

Short-circuit logic evaluation operators

Are there any short-circuit logic operators (specifically short-circuit AND and short-circuit OR) that I can use in a WHERE clause in MySQL 5.5? If there isn't, what are the alternatives?
An abstract view at my problem along with an explanation as to why I need this can be found at this fiddle:
http://sqlfiddle.com/#!2/97fd1/3
In reality we are looking at millions of books in millions of bookstores in thousands of cities in hundreds of countries, which is why we cannot accept the overhead of receiving the unneeded information with every query we dispatch and seriously need to find a way to make the evaluation stop as soon as we have all rows that satisfy the current condition, before moving on to the next OR.
Let me know if you need more information. Thanks in advance.
As requested, here is the schema used in the fiddle:
CREATE TABLE quantitycache (
id INT AUTO_INCREMENT,
quantity INT,
book_id INT NOT NULL,
bookstore_id INT NULL,
city_id INT NULL,
country_id INT NULL,
PRIMARY KEY (id)
);
As well as some example data:
INSERT INTO quantitycache
(quantity, book_id, bookstore_id, city_id, country_id)
VALUES
(5, 1, 1, NULL, NULL),
(100, 2, 1, NULL, NULL),
(7, 1, 2, NULL, NULL),
(12, 1, NULL, 1, NULL),
(12, 1, NULL, NULL, 1),
(100, 2, NULL, 1, NULL),
(100, 2, NULL, NULL, 1),
(200, 3, NULL, 1, NULL),
(250, 3, NULL, NULL, 1);
Keep in mind that a query does not execute imperatively. The query you wrote may run on multiple threads, and therefore a short-circuit operator in the where clause would not result in only one result.
Instead, use the LIMIT clause to only return the first row.
SELECT * FROM quantitycache
WHERE bookstore_id = 1 OR city_id = 1 OR country_id = 1
ORDER BY bookstore_id IS NULL ASC,
city_id IS NULL ASC,
country_id IS NULL ASC
LIMIT 1;
To get the best match for all books in a result set, save the results to a temp table, find the best result, then return interesting fields.
CREATE TEMPORARY TABLE results (id int, book_id int, match_rank int);
INSERT INTO results (id, book_id, match_rank)
SELECT id, book_id,
-- this assumes that lower numbers are better
CASE WHEN Bookstore_ID is not null then 1
WHEN City_ID is not null then 2
ELSE 3 END as match_rank
FROM quantitycache
WHERE bookstore_id = 1 OR city_id = 1 OR country_id = 1;
Select *
from (
select book_id, MIN(match_rank) as best_rank
from results
group by book_id
) as r
inner join results as rid
on r.book_id = rid.book_id
and rid.match_rank = r.best_rank
inner join quantitycache as q on q.id = rid.id;
DROP TABLE results;

How to select records only when the SUM(field) from a separate table > 0 using MAX to identify the most recent 'field'

I have been battling with this query for over a day now. My SQL is not amazing, so that might explain why! Anyway, I'd really appreciate if anyone could clarify what I'm doing wrong here.
CREATE TABLE `business` (
`business_id` int(11) NOT NULL AUTO_INCREMENT,
)
CREATE TABLE `business_unit` (
`business_unit_id` int(11) NOT NULL AUTO_INCREMENT,
`business_id` int(11) NOT NULL,
)
CREATE TABLE `offer` (
`offer_id` int(11) NOT NULL AUTO_INCREMENT,
`business_unit_id` int(11) NOT NULL,
`points_required` int(11) NOT NULL,
)
CREATE TABLE `points_balance` (
`points_balance_id` int(11) NOT NULL AUTO_INCREMENT,
`mobile_user_id` int(11) NOT NULL,
`business_unit_id` int(11) NOT NULL,
`points` int(11) NOT NULL DEFAULT '0',
`record_created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
)
The data would be:
INSERT INTO `business` (`business_id`) VALUES (1);
INSERT INTO `business` (`business_id`) VALUES (2);
INSERT INTO `business_unit` (`business_unit_id`,`business_id`) VALUES (11, 1);
INSERT INTO `business_unit` (`business_unit_id`,`business_id`) VALUES (12, 1);
INSERT INTO `business_unit` (`business_unit_id`,`business_id`) VALUES (13, 2);
INSERT INTO `business_unit` (`business_unit_id`,`business_id`) VALUES (14, 2);
INSERT INTO `offer` (`offer_id`,`business_unit_id`,`points_required`) VALUES (21, 11, 50);
INSERT INTO `offer` (`offer_id`,`business_unit_id`,`points_required`) VALUES (22, 12, 50);
INSERT INTO `offer` (`offer_id`,`business_unit_id`,`points_required`) VALUES (23, 12, 60);
INSERT INTO `offer` (`offer_id`,`business_unit_id`,`points_required`) VALUES (24, 13, 100);
INSERT INTO `offer` (`offer_id`,`business_unit_id`,`points_required`) VALUES (25, 14, 30);
INSERT INTO `offer` (`offer_id`,`business_unit_id`,`points_required`) VALUES (26, 14, 150);
INSERT INTO `points_balance` (`points_balance_id`,`user_id`,`business_unit_id`,`points`,`record_created`) VALUES (31, 27, 11, 10, '2013-04-01');
INSERT INTO `points_balance` (`points_balance_id`,`user_id`,`business_unit_id`,`points`,`record_created`) VALUES (32, 27, 11, 30, '2013-04-02');
INSERT INTO `points_balance` (`points_balance_id`,`user_id`,`business_unit_id`,`points`,`record_created`) VALUES (33, 27, 12, 10, '2013-03-02');
INSERT INTO `points_balance` (`points_balance_id`,`user_id`,`business_unit_id`,`points`,`record_created`) VALUES (34, 27, 12, 20, '2013-03-04');
INSERT INTO `points_balance` (`points_balance_id`,`user_id`,`business_unit_id`,`points`,`record_created`) VALUES (34, 27, 14, 20, '2013-04-12');
INSERT INTO `points_balance` (`points_balance_id`,`user_id`,`business_unit_id`,`points`,`record_created`) VALUES (34, 27, 14, 100, '2013-04-14');
Records in the points balance table are insert only, so the user's balance for a unit is on the most recent record for that user/unit
Business units within the same business share the points balance (i.e. a user's total points is the sum of their points on the various units within that business).
I want to select offers where the user has enough points to redeem them.
SELECT up.user_id, up.points, o.*
FROM offer o
JOIN (
SELECT user_id, business_id, sum(points) AS points
FROM points_balance pb
JOIN business_unit bu on pb.business_unit_id = bu.business_unit_id
WHERE pb.points_balance_id IN (
SELECT MAX(pb2.points_balance_id)
FROM points_balance pb2
WHERE pb2.user_id = 27
GROUP BY pb2.business_unit_id)
GROUP BY user_id, business_id
) up ON up.points >= o.points_required
The query above is returning one instance of the same offer multiple times (times the number of business the user has points at) :(
The result set I'm looking for is:
user_id | points | offer_id
27 | 50 | 21
27 | 50 | 22
27 | 100 | 24
27 | 100 | 25
Thanks all.
G
Here is a query that does what I think you want:
select up.user_id, up.business_id, up.allpoints, o.*
from (select user_id, business_id, sum(points_balance) as allpoints
from points_balance pb join
business_Unit bu
on pb.business_unit_id = bu.business_unit_id
group by user_id, business_id
) up join
offers o
on o.point_required <= up.allpoints;
The logic behind this query is much simpler than the approach you were taking. The first subquery calculates the total number of points available for a user across all business units within a business. It then finds the matching points.
You can add where clauses either in the subquery or in the overall query to limit to particular businesses or users.
Also, this is based on the field names in the text of the question, not the ones in the query. They are different.
I found out it was returning multiple instances of the same offer when I had points to redeem the offer in multiple business. So the problem is that I was not linking the offer to the specific business! so I added to the bottom of the query
JOIN business_unit bu ON bu.business_unit_id = o.business_unit_id
AND up.business_id = bu.business_id
Sorted :)