I have a table :
Person Language
6 1
6 2
6 3
7 1
7 2
I would like to select the person who speaks the language 1 AND 2 AND 3 (person 6)
I coded this query, but I don't get the right result :
SELECT guides.id, guides.nom, guides.prenom, langues.langue
FROM guides
JOIN guides_has_langues ON guides_has_langues.guides_id = guides.id
JOIN langues ON guides_has_langues.langues_id = langues.id
WHERE guides_has_langues.langues_id = 1 AND guides_has_langues.langues_id = 2 AND guides_has_langues.langues_id = 3
I think that this query select field that are 1 AND 2 AND 3, is it right ?
It's not my goal.
Select all entries for languages 1, 2 and 3. Then group by guide and stay with those having all three languages (using the HAVING clause). To show the languages use the function your dbms offers to concatenate them (e.g. GROUP_CONCAT for MySQL).
SELECT guides.id, guides.nom, guides.prenom, GROUP_CONCAT(langues.langue)
FROM guides
JOIN guides_has_langues ON guides_has_langues.guides_id = guides.id
JOIN langues ON guides_has_langues.langues_id = langues.id
WHERE guides_has_langues.langues_id IN (1,2,3)
GROUP BY guides.id, guides.nom, guides.prenom
HAVING COUNT(DISTINCT guides_has_langues.langues_id) = 3;
(If you want one line per guide and language this will be a little more complicated, because you need the raw entires plus the aggregation information on how many languages the guide speaks.)
Related
My ch_skills table looks like
uid | skill1 | skill2 | skill3 | skill4 | skill5
1 1 2 2 0 1
2 1 1 2 1 1
3 1 2 3 0 1
My first question: is this correct? I mean would it be better if I made it like this:
uid | skillid | skill_lvl
1 1 1
1 2 2
1 3 2
1 4 0
1 5 1
Everything worked fine until now with the example #1, but now I'm in a trouble with the sql queries. Currently, I'm using 5 different queries to get the level of each skill. I use the following code:
For skill1:
$query = $this->db->prepare("SELECT `skills`.`skill_ID` as `Skill1_id`,
`skill_name`.`skill_name` as `Skill1_name`, `skill_level` as `Skill1_level`,
`skill_price` as `Skill1_price`
FROM `skills`, `skill_name`, `ch_skills`
WHERE `skill_name`.`skill_ID` = `skills`.`skill_ID`
AND `skills`.`skill_ID`= 1
AND `skills`.`skill_level` = `ch_skills`.`skill1`
AND `ch_skills`.`uid` = :uid");
For skill2:
$query = $this->db->prepare("SELECT `skills`.`skill_ID` as `Skill1_id`,
`skill_name`.`skill_name` as `Skill1_name`, `skill_level` as `Skill1_level`,
`skill_price` as `Skill1_price`
FROM `skills`, `skill_name`, `ch_skills`
WHERE `skill_name`.`skill_ID` = `skills`.`skill_ID`
AND `skills`.`skill_ID`= 2
AND `skills`.`skill_level` = `ch_skills`.`skill2`
AND `ch_skills`.`uid` = :uid");
And so on... As you can see, there's only two differences: skill_id = 2, and skill2 as the coulmn's name. Is there any way for querying all the 5 skills in only 1 query? Or would you recommend me anyway to change the table structure?
Note: skills stands for the skill prices, and skill_name for the skill's names.
As the other commenters have suggested, your best choice is to change the table exactly as you proposed.
The biggest reason not to have a wide table like you show in your first example, is that adding a skill means changing the structure of the database, which could break existing queries.
Secondly, as you see when you're trying to query the results, having a single table doesn't even make it easier to work with.
The only possible benefit to a non-normalized table like your example is that it takes up slightly less disk space. But in todays world, disk space should never be your primary concern.
To answer your question about querying the original non-normalized example, however, there are two ways to do it:
Use a union statement which would combine 5 distinct queries together. This is pretty inefficient
Create a table with (in this case) 5 rows (or if you have a Skills table use that). Then join the ch_skills table to that, which should take each row and split it 5 times. See below: (note: I'm assuming for the purposes of this example that skills and skill_name are in a 1:1 relationship and only have 5 records each)
SELECT skills.skill_ID,
skill_name.skill_name,
skill_level as Skill_level,
skill_price as Skill_price
FROM skills
JOIN skill_name on skill_name.skill_ID = skills.skill_ID
JOIN ch_skills
WHERE ch_skills.uid = :uid
AND ((skills.skill_ID = 1 AND skills.skill_level = ch_skills.skill1)
OR (skills.skill_ID = 2 AND skills.skill_level = ch_skills.skill2)
OR (skills.skill_ID = 3 AND skills.skill_level = ch_skills.skill3)
OR (skills.skill_ID = 4 AND skills.skill_level = ch_skills.skill4)
OR (skills.skill_ID = 5 AND skills.skill_level = ch_skills.skill5))
I think this will be an easy one for those using MYSQL a lot, but I just can't quite get it...mainly through not knowing the right terminology to search for.
Basically I have a table mapping tag ids to photo ids called tag_map. When I perform this query:
SELECT * FROM tag_map WHERE tag_id='1' OR tag_id='5';
I get the following results:
ph_id tag_id
1 1
2 1
5 1
7 5
8 1
9 5
10 5
11 1
12 1
13 1
But what I really want is to only select ph_id that have a tag_id of BOTH '1' and '5'.
So, as you can probably see, I am trying to filter down selections based on multiple tags. I want to end up with:
ph_id tag_id
7 1
7 5
8 1
8 5
11 1
11 5
So ph_id 7, 8 and 11 reference tag_id 1 AND 5.
Hope that makes sense.
Thanks.
Solution
Due to the dynamic nature of my query (user selecting any number of available tags to 'narrow down selection) I went with a PHP solution, as suggested by #Y U NO WORK
Basically I get the tag_id of all selected tags from table 'tags'
Then I selected all photo ids (ph_id) that are mapped to the selected tag_ids from my table tag_map.
Then I reduce this down to ph_ids that occur the same number of times as the number of selected tags:
$numTags = count($arTagId); //$arTagId is an array of the selected tags
// get only photo ids that match both tags
$arPhId = array();
// in arPhId, find ph_ids that have occurances equal to $numTags
$arPhIdCnt = array_count_values($arPhIdAll); //$arPhIdAll is array of all ph_id that match all selected tag_ids
foreach($arPhIdCnt as $pid => $pidQty) {
if($pidQty == $numTags) {
$arPhId[] = $pid;
}
}
So I end up with an array of only the ph_ids that match both tag_ids.
Thanks for everyone's help.
You will have to join the table with itself, the code could be kinda complicated. PHP would be an easier, but not such a performant solution.
You have to join the table with itself based on ph_id, then check that the tab_id col of table1 instance equals 1 and that tab_id of table2 instance equals 5.
SELECT t1.* FROM tag_map t1, tag_map t2
WHERE t1.ph_id = t2.ph_id
AND t1.tag_id='1'
AND t2.tag_id='5';
with inner join if you prefer
SELECT t1.* FROM tag_map t1
INNER JOIN tag_map t2 on t2.ph_id=t1.ph_id
WHERE t1.tag_id='1'
AND t2.tag_id='5';
I've got two tables:
content:
id access
1 3
2 5
3 9
viewlevels:
id group
1 [10,12,15]
2 [8,12,11]
3 [9,10,5]
The access field in content is related with the id field in viewlevels.
I select the rows in viewlevels depending on the current user group. So for example, if group is = 10, my query will select rows 1 and 3. If the group is 12, it will select rows 1 and 2, etc. I'm using the following query:
$query="SELECT id FROM #__viewlevels WHERE rules LIKE '%$group%'";
My challenge is to count the number of rows for column id in table content where the access matches with the selected id's from the above query on table viewlevels.
I tried the following code but it is returning the error: undefined variable: nartigos
$query="SELECT count(id) FROM #__content WHERE access IN (SELECT id FROM #__viewlevels WHERE rules LIKE '%$group%')";
if ($stmt = mysqli_prepare($link, $query)) {
/* execute query */
mysqli_stmt_execute($stmt);
/* store result */
mysqli_stmt_store_result($stmt);
$nartigos=mysqli_stmt_num_rows($stmt);
/* close statement */
mysqli_stmt_close($stmt);
};
echo "Nº de artigos " .$nartigos;
First of all, you really should normalize your data. Consider having a many-to-many join table for viewLevels instead of having all groups in one row. That might look like this:
access_id group_id
1 10
1 12
1 15
2 8
2 11
2 12
3 5
3 9
3 10
That would make your query as simple as
SELECT c.id AS `content_id`, COUNT(v.access_id) AS `content_count`
FROM content AS c INNER JOIN viewLevels AS v
ON c.access_id = v.access_id
WHERE v.group_id = ?
GROUP BY c.id
Here ? is the group id you are querying against.
Without normalization (which again I STRONGLY recommend you do), you would still use a join, but it would look like this:
SELECT c.id AS `content_id`, COUNT(v.access_id) AS `content_count`
FROM content AS c INNER JOIN viewLevels AS v
ON c.access_id = v.access_id
WHERE v.group LIKE '%?%'
GROUP BY c.id
you need to "join" your tables.
the sql command cant query two tables seperately.
when you "join" 2 tables in your sql, think of it as making one virtual/temporary table in the air, of the 2 tables which you can then query.
this is quite a good intro http://www.sitepoint.com/understanding-sql-joins-mysql-database/
I'm currently working on a project where I have information stored in several tables that all connect to each other. I believe that the table and column format is logical and the best choice. The problem though, is that I don't have enough knowledge to build queries advanced enough to fetch all the information I need.
The main table is ab_ads, where advertisements are stored. These ads can be assigned several formats (ie. 250x360, 980x120 etc), and you can also select the region where they should be showing (ie. Skåne, Stockholm, Kalmar, Dalarna, Jämtland etc).
This is how I store my data. I'm not showing all tables but I hope this is sufficient.
Advertisements column (ab_ads): (There are more columns but they are not relevant)
ID orgnum company_name title content link
1 556664-7524 Company Inc Lorem ipsum Lorem ipsum URL
Advertisement states (ab_ads_states):
ID adID stateID
1 1 2 // Skåne
2 1 5 // Kalmar
3 1 8 // Stockholm
4 1 10 // Värmland
5 2 2 // Skåne
6 2 5 // Kalmar
7 3 8 // Stockholm
8 4 10 // Värmland
Advertisement formats (ab_ads_formats)
ID adID formatID
1 1 1 // 250x360
2 1 2 // 980x120
3 2 1 // 250x360
4 3 2 // 980x120
Formats table (ab_formats)
ID name width height
1 Format 1 250 360
2 Format 2 980 120
So, I have two flash banners both are supposed to call a PHP-script which in turn is supposed to deliver an XML-file back with all the results.
I know how to select data from different tables, but I've never worked with selecting multiple rows from another table and merging them into one, which I suppose is that I need to do here. I'm very thankful for any help I can get.
The flash banners will send two parameters to the PHP file, stateID and formatID. Which means I have to SELECT ad WHERE state = param AND format = format. But since I store multiple entries for the ad states I don't know how to do it.
EDIT:
I would also like to fetch the format names in the query and get them in the following format: "Format 1,Format 2" in a column named "formats". I guess this would require some kind of join?
Thanks in advance!
I think this will work:
select ab.name as formats, aa.* from ab_ads as aa
inner join ab_ads_states as aas on aa.id = aas.adid and aas.stateId = stateIdParam
inner join ab_ads_formats as aaf on aa.id = aaf.adid and aaf.formatId = formatIdParam
inner join ab_formats as ab on aaf.formatid = ab.id
Edit:
I'm not very good with mySql, and don't have anything to test this on, but I think group_concat may be what you are looking for. If so, it will probably look something like this:
select group_concat(ab.name separator ", ") as formats from ab_ads aa
inner join ab_ads_states as aas on aa.id = aas.adid and aas.stateId = 2
inner join ab_ads_formats as aaf on aa.id = aaf.adid and aaf.formatId in(1,2)
inner join ab_formats as ab on aaf.formatid = ab.id
group by ab.id
Please try below SQL:
SELECT count(aaf.ID) AS TotalFormat,
group_concat(ab.name) AS formats
FROM ab_ads aa
INNER JOIN ab_ads_states AS aas ON aa.ID = aas.adID
AND aas.stateID = 2
INNER JOIN ab_ads_formats AS aaf ON aa.id = aaf.adID
AND aaf.formatID in(1,2)
INNER JOIN ab_formats AS ab ON aaf.formatID = ab.ID
GROUP BY aaf.adID HAVING TotalFormat >=2
SQL Demo: http://sqlfiddle.com/#!2/9f0ab/10
I have this table:
This selection is is duplicated many times for different var_lines (which pretty much work as one row of data, or respondent for a survey) and set_codes (different survey codes).
With this query:
SELECT
*, COUNT(*) AS total
FROM
`data`
WHERE
`var_name` = 'GND.NEWS.INT'
AND(
`set_code` = 'BAN11A-GND'
OR `set_code` = 'BAN09A-GND'
OR `set_code` = 'ALG11A-GND'
)
AND `country_id` = '5'
GROUP BY
`data_content`,
`set_code`
ORDER BY
`set_code`,
`data_content`
The query basically counts the number of answers for a specific question. Then groups them survey (set_code).
What I need is for each of the grouped data_content answers for GND.NEWS.INT to also show the SUM of all the corresponding GND_WT with the same var_line.
For example if I had this:
data_id data_content var_name var_line
1 2 GND.NEW.INT 1
2 1.4 GND_WT 1
3 2 GND.NEW.INT 2
4 1.6 GND_WT 2
5 3 GND.NEW.INT 3
6 0.6 GND_WT 3
I would get something like this:
data_id data_content var_name var_line total weight
1 2 GND.NEW.INT 1 2 3
5 3 GND.NEW.INT 3 1 0.6
Thanks for any help.
Your requirements are not exactly clear, but I think the following gives you what you want:
select d1.data_id,
d1.data_content,
d1.var_name,
d1.var_line,
t.total,
w.weight
from data d1
inner join
(
select data_content,
count(data_content) Total
from data
group by data_content
) t
on d1.data_content = t.data_content
inner join
(
select var_line,
sum(case when var_name = 'GND_WT' then data_content end) weight
from data
group by var_line
) w
on d1.var_line = w.var_line
where d1.var_name = 'GND.NEW.INT'
See SQL Fiddle with Demo
This Query can be suitable for your specific example:
select st.data_id,
st.data_content,
st.var_name,
st.var_line,
count(st.data_id) as total,
sum(st1.data_content) as weight
from data st
left join data st1 on st1.var_name = 'GND_WT' AND st1.var_line=st.var_line
where st.var_name='GND.NEW.INT'
group by st.data_content
Regards,
Luis.