Dynamic IN+IF clause with GROUP_CONCAT? - mysql

I am filtering buyers (many) for a given property (one) with the main condition of
if the buyer requires specific exact # of bathrooms (buyers.bathroom_exact='1'), match it to the property's bathroom value.
Buyers can select multiple exact bathroom matches. Here's a sample schema:
CREATE TABLE `buyers` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`name` text NOT NULL,
`bathroomreq` tinyint(1) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
CREATE TABLE `buyers_bathrooms` (
`buyer_id` int(10) unsigned NOT NULL DEFAULT '0',
`number` decimal(10,2) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `buyers` ( name, bathroomreq ) VALUES ( 'John Doe', 1);
INSERT INTO `buyers` ( name, bathroomreq ) VALUES ( 'John Smith', 1);
INSERT INTO buyers_bathrooms ( buyer_id, number ) VALUES ( 1, 8 );
INSERT INTO buyers_bathrooms ( buyer_id, number ) VALUES ( 1, 9 );
INSERT INTO buyers_bathrooms ( buyer_id, number ) VALUES ( 1, 10 );
INSERT INTO buyers_bathrooms ( buyer_id, number ) VALUES ( 2, 5 );
The only way I can think of doing this is using a column which has an IF clause to see if the bathroomreq is 1, then match the group_concat of the buyers specified bathrooms:
SELECT IF ( bathroomreq = '1', 8 IN (
SELECT GROUP_CONCAT(number) AS bathrooms
FROM buyers_bathrooms
WHERE buyers.id = buyers_bathrooms.buyer_id
) , 1 ) AS bathroom_match,
bathroomreq,
id, name
FROM buyers
So this property basically has 8 bathrooms and that's why 8 is hardcoded in there.
Here's a mysql fiddle:
http://sqlfiddle.com/#!2/508cc/1
Is this the only way of solving the problem and is it reliable? Can I do it an alternative way with something like a dynamic INNER JOIN if bathroomreq is 1? Basically, can I avoid having to branch based on bathroom_match in my application code and filter this elegantly through SQL alone?

I think you can do what you want with an exists clause:
select b.bathroomreq, b.id, b.name,
(b.bathroomreq <> 1 or
exists (select 1
from buyers_bathrooms bb
where bb.buyer_id = b.id and bb.number = 8
)
) as bathroom_match
from buyers b;

Related

Generate new table with data from old table, multiplied by lookup value

In MySQL, having these two queries:
CREATE TABLE `country` (
`name` varchar(100) NOT NULL,
`value001` decimal(15,2) NOT NULL,
`value002` decimal(15,2) NOT NULL,
`value003` decimal(15,2) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO country (name, value001, value002, value003) VALUES('Bulgaria', 100, 200, 300),('Portugal', 5, 6, 7),('Belarus', 20, 30, 40);
CREATE TABLE `exchange-rate`(
`name` varchar(100) NOT NULL,
`rate` decimal(15,2) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO `exchange-rate` (name, rate) VALUES('Bulgaria', 1.9),('Portugal', 1.1),('Belarus', 10);
I get the following tables:
My idea is to create a third table, where the values from the first table will be multiplied by the values for the second one. E.g., for Belarus and Portugal it will look like this:
name
value1
value2
value3
Belarus
200
300
400
Portugal
5.5
6.6
7.7
What have I done so far?
drop table if exists new_country;
create table if not exists new_country;
select c.*
from country c
left outer join --or something wiser?
(
--probably something is to be written here
)
You can use the create table as select syntax, and join on the country name:
DROP TABLE IF exists new_country;
CREATE TABLE new_country AS
SELECT c.name AS name,
value001 * er.rate AS value1,
value002 * er.rate AS value2,
value003 * er.rate AS value3
FROM test.country c
JOIN `exchange-rate` er ON c.name = er.name;

Average values from different table on join

CREATE TABLE `reviews` (
`id` int(11) NOT NULL,
`average` decimal(11,2) NOT NULL,
`house_id` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `reviews` (`id`, `average`, `house_id`) VALUES
(1, '10.00', 1),
(2, '10.00', 1);
ALTER TABLE `reviews`
ADD PRIMARY KEY (`id`);
ALTER TABLE `reviews`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=3;
CREATE TABLE `dummy_reviews` (
`id` int(11) NOT NULL,
`average` decimal(11,2) NOT NULL,
`house_id` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `dummy_reviews` (`id`, `average`, `house_id`) VALUES
(0, '2.00', 1);
ALTER TABLE `dummy_reviews`
ADD PRIMARY KEY (`id`);
AND the query
SELECT
AVG(r.average) AS avg1,
AVG(dr.average) AS avg2
FROM
reviews r
LEFT JOIN
dummy_reviews dr ON r.house_id = dr.house_id
the result is
avg1 avg2
10.000000 2.000000
All good by now but (10 + 2) / 2 = 6 ... wrong result
I need (10+10+2) / 3 = 7,33 ... How can I get this result?
SQLFiddle
You have values joined and as such you wont have 3 rows, you will have 2. What you need is a union so you can have all rows from your average tables and do the calculation from it. Like this:
select avg(average) from
(select average from reviews
union all
select average from dummy_reviews
) queries
See it here: http://sqlfiddle.com/#!9/e0b75f/3
Jorge's answer is the simplest approach (and I duly upvoted it). In response to your comment, you can do the following:
select ( (coalesce(r.suma, 0) + coalesce(d.suma, 0)) /
(coalesce(r.cnt, 0) + coalesce(d.cnt, 0))
) as overall_average
from (select sum(average) as suma, count(*) as cnt
from reviews
) r cross join
(select sum(average) as suma, count(*) as cnt
from dummy_reviews
) d;
Actually, I suggest this not only because of your comment. Under some circumstances, this could be the better performing code.

Taking the most commonly occuring (modal) value for several columns

I am cleaning records that have poorly recorded and inconsistent socio-demographic information over time, for the same person. I want to take the most commonly occuring value (the mode) for each person.
One way to do that is to partition by id and then count how many times each value occurs, retaining the highest count for each id:
DROP TABLE dbo.table
SELECT DISTINCT [id], [ethnic_group] AS [ethnic_mode], ct INTO dbo.table
FROM (
SELECT row_number() OVER (PARTITION BY [id] ORDER BY count([ethnic_group]) DESC) as rn, count([ethnic_group]) as ct, [ethnic_group], [id]
FROM
dbo.mytable GROUP BY [id], [ethnic_group]) ranked
where rn = 1
ORDER BY ct DESC
But I want to do this for several variables (ethnic group, income group and several more).
How can I select the mode for several variables within one statement and insert into one table (rather than creating separate tables for each variable)?
The table below illustrates an example of what I want to do:
DROP TABLE mytable;
CREATE TABLE mytable(
id VARCHAR(2) NOT NULL PRIMARY KEY
,ethnic_group VARCHAR(12) NOT NULL
,ethnic_mode VARCHAR(11) NOT NULL
,income VARCHAR(6) NOT NULL
,income_mode VARCHAR(11) NOT NULL
);
INSERT INTO mytable(id,ethnic_group,ethnic_mode,income,income_mode) VALUES ('id','ethnic_group','ethnic_mode','income','income_mode');
INSERT INTO mytable(id,ethnic_group,ethnic_mode,income,income_mode) VALUES ('1','white','white','middle','middle');
INSERT INTO mytable(id,ethnic_group,ethnic_mode,income,income_mode) VALUES ('1','white','white','middle','middle');
INSERT INTO mytable(id,ethnic_group,ethnic_mode,income,income_mode) VALUES ('1','mixed','white','high','middle');
INSERT INTO mytable(id,ethnic_group,ethnic_mode,income,income_mode) VALUES ('2','asian','asian','middle','middle');
INSERT INTO mytable(id,ethnic_group,ethnic_mode,income,income_mode) VALUES ('2','mixed','asian','middle','middle');
INSERT INTO mytable(id,ethnic_group,ethnic_mode,income,income_mode) VALUES ('2','asian','asian','middle','middle');
I would use sub-queries to accomplish this in 1 insert statement.
Here is an example based on the table structure from your illustration:
/* This is the original table and contains duplicate ID's */
DECLARE #source_table TABLE(
id VARCHAR(2) NOT NULL
,ethnic_group VARCHAR(12) NULL
,ethnic_mode VARCHAR(11) NULL
,income VARCHAR(6) NULL
,income_mode VARCHAR(11) NULL
);
/* This is the destination table and will not contain duplicate ID's */
DECLARE #destination_table TABLE(
id VARCHAR(2) NOT NULL PRIMARY KEY
,ethnic_group VARCHAR(12) NULL
,income VARCHAR(6) NULL
);
/* Populate the source table with data */
INSERT INTO #source_table(id,ethnic_group,ethnic_mode,income,income_mode) VALUES ('1','white','white','middle','middle');
INSERT INTO #source_table(id,ethnic_group,ethnic_mode,income,income_mode) VALUES ('1','white','white','middle','middle');
INSERT INTO #source_table(id,ethnic_group,ethnic_mode,income,income_mode) VALUES ('1','mixed','white','high','middle');
INSERT INTO #source_table(id,ethnic_group,ethnic_mode,income,income_mode) VALUES ('2','asian','asian','middle','middle');
INSERT INTO #source_table(id,ethnic_group,ethnic_mode,income,income_mode) VALUES ('2','mixed','asian','middle','middle');
INSERT INTO #source_table(id,ethnic_group,ethnic_mode,income,income_mode) VALUES ('2','asian','asian','middle','middle');
INSERT INTO #source_table(id,ethnic_group,ethnic_mode,income,income_mode) VALUES ('3','asian', NULL, NULL, NULL);
INSERT INTO #source_table(id,ethnic_group,ethnic_mode,income,income_mode) VALUES ('3',NULL, NULL,'middle', NULL);
INSERT INTO #source_table(id,ethnic_group,ethnic_mode,income,income_mode) VALUES ('3',NULL, NULL, NULL, NULL);
/* Insert from source into destination (removing duplicates) */
INSERT INTO #destination_table
(
id
, ethnic_group
, income
)
SELECT st.id
, (
SELECT TOP 1 ethnic_group
FROM #source_table sub_st
WHERE sub_st.id = st.id
GROUP BY ethnic_group
ORDER BY COUNT(sub_st.id) DESC
)
, (
SELECT TOP 1 income
FROM #source_table sub_st
WHERE sub_st.id = st.id
GROUP BY income
ORDER BY COUNT(sub_st.id) DESC
)
FROM #source_table st
GROUP BY st.id
/* View the destination to see there are no duplicates */
SELECT id
, ethnic_group
, income
FROM #destination_table

Mysql how to join tables more than two

I have problem with my query,
I have tables below:
CREATE TABLE IF NOT EXISTS `klik_zona` (
`kode_zona` int(10) unsigned NOT NULL,
`klik` int(10) unsigned NOT NULL,
PRIMARY KEY (`kode_zona`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `klik_zona` (`kode_zona`, `klik`) VALUES
(1, 45);
CREATE TABLE IF NOT EXISTS `tampil_zona` (
`kode_zona` int(10) unsigned NOT NULL,
`tanggal` date NOT NULL,
`tampil` int(10) unsigned NOT NULL,
PRIMARY KEY (`kode_zona`,`tanggal`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `tampil_zona` (`kode_zona`, `tanggal`, `tampil`) VALUES
(1, '2014-03-16', 100),
(1, '2014-03-17', 23);
CREATE TABLE IF NOT EXISTS `zona_iklan` (
`kode_zona` int(10) unsigned NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`kode_zona`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=4 ;
INSERT INTO `zona_iklan` (`kode_zona`) VALUES
(1),
(2),
(3);
I have query:
SELECT z.kode_zona, SUM( tz.tampil ) , SUM( kz.klik )
FROM zona_iklan z
LEFT JOIN tampil_zona tz ON tz.kode_zona = z.kode_zona
LEFT JOIN klik_zona kz ON kz.kode_zona = z.kode_zona
GROUP BY z.kode_zona
but it give result:
kode_zona SUM(tz.tampil) SUM(kz.klik)
1 123 90
2 NULL NULL
3 NULL NULL
I want get result:
kode_zona SUM(tz.tampil) SUM(kz.klik)
1 123 45
2 NULL NULL
3 NULL NULL
please help me.. how to make query so that I get result that I hope it..
thanks,
In your example you join two records from tampil_zona on to one record from zona_iklan, which essentially causes that one record to duplicate. Then you are joining one record in klik_zona on to both of those duplicated records, causing the doubling of results that you want to avoid.
Instead, you need to aggregate the records before you join them, to ensure that you are always joining the records 1-to-1.
SELECT
z.kode_zona, tz.tampil, kz.klik
FROM
zona_iklan AS z
LEFT JOIN
(SELECT kode_zona, SUM(tampil) AS tampil FROM tampil_zona GROUP BY kode_zona) AS tz
ON tz.kode_zona = z.kode_zona
LEFT JOIN
(SELECT kode_zona, SUM(klik) AS klik FROM klik_zona GROUP BY kode_zona) AS kz
ON kz.kode_zona = z.kode_zona
Try removing the GROUP BY and look at the result. You will see that there are two records with kode_zona = 1. This because there are two records in tampil_zona matching that id. You could divide by count(*) but that seems futile. You probably want to think about how to modify the join.

How implementation my wants with MySQL JOIN

_
Hello everyone!
I have table
CREATE TABLE `labels` (
`id` INT NULL AUTO_INCREMENT DEFAULT NULL,
`name` VARCHAR(250) NULL DEFAULT NULL,
`score` INT NULL DEFAULT NULL,
`before_score` INT NULL DEFAULT NULL,
PRIMARY KEY (`id`)
);
And I Have This Table
CREATE TABLE `scores` (
`id` INT NULL AUTO_INCREMENT DEFAULT NULL,
`name_id` INT NULL DEFAULT NULL,
`score` INT NULL DEFAULT NULL,
`date` DATETIME DEFAULT NULL,
PRIMARY KEY (`id`)
);
And i want have result where labels.score - have value last scores.score sorted by scores.date and labels.before_score where have value penultimate scores.score sorted by scores.date. Can I do This Only on Mysql slq and how?
Thanks.
ADD
For example i have this data on first table:
INSERT INTO `labels` (id, name, score, before_score) VALUES (1, 'John', 200, 123);
INSERT INTO `labels` (id, name, score, before_score) VALUES (2, 'Eddie', 2000, 2000);
INSERT INTO `labels` (id, name, score, before_score) VALUES (3, 'Bob', 400, 3101);
And second table
INSERT INTO `scores` (`id`,`name_id`,`score`,`date`) VALUES ('1','1','12','2013-07-10');
INSERT INTO `scores` (`id`,`name_id`,`score`,`date`) VALUES ('2','2','2000','2013-05-04');
INSERT INTO `scores` (`id`,`name_id`,`score`,`date`) VALUES ('3','3','654','2012-09-12');
INSERT INTO `scores` (`id`,`name_id`,`score`,`date`) VALUES ('4','1','123','2013-12-17');
INSERT INTO `scores` (`id`,`name_id`,`score`,`date`) VALUES ('5','1','200','2014-04-25');
INSERT INTO `scores` (`id`,`name_id`,`score`,`date`) VALUES ('6','3','3101','2013-12-02');
INSERT INTO `scores` (`id`,`name_id`,`score`,`date`) VALUES ('6','2','2000','2015-12-02');
INSERT INTO `scores` (`id`,`name_id`,`score`,`date`) VALUES ('6','3','400','2013-12-02');
If I understand correctly, you need the last two scores for each name_id.
I would tackle this with temporary tables:
Step 1. Last score:
create temporary table temp_score1
select name_id, max(`date`) as lastDate
from scores
group by name_id;
-- Add the appropriate indexes
alter table temp_score1
add unique index idx_name_id(name_id),
add index idx_lastDate(lastDate);
Step 2. Penultimate score. The idea is exactly the same, but using temp_score1 to filter the data:
create temporary table temp_score2
select s.name_id, max(`date`) as penultimateDate
from scores as s
inner join temp_score1 as t on s.nameId = t.nameId
where s.`date` < t.lastDate
group by name_id;
-- Add the appropriate indexes
alter table temp_score2
add unique index idx_name_id(name_id),
add index idx_penultimateDate(penultimateDate);
Step 3. Put it all together.
select
l.id, l.name,
s1.lastScore, s2.penultimateScore
from
`labels` as l
left join temp_score1 as s1 on l.id = s1.name_id
left join temp_score2 as s2 on l.id = s2.name_id
You can put this three steps inside a stored procedure.
Hope this helps you.