Select distinct on multiple columns with where statement - php

I want to select each available entry for each column once. This problem was solved with
SELECT DISTINCT a from my_table
UNION
SELECT DISTINCT b from my_table
UNION
SELECT DISTINCT c from my_table
UNION
SELECT DISTINCT d from my_table
in this question: MySQL SELECT DISTINCT multiple columns
I want to go further and use the same WHERE statements on each subquery. Is there any way without defining the WHERE each time? My current query would look like this:
SELECT DISTINCT a from my_table WHERE a='a' AND b=1 AND c='.' AND d='ab'
UNION
SELECT DISTINCT b from my_table WHERE a='a' AND b=1 AND c='.' AND d='ab'
UNION
SELECT DISTINCT c from my_table WHERE a='a' AND b=1 AND c='.' AND d='ab'
UNION
SELECT DISTINCT d from my_table WHERE a='a' AND b=1 AND c='.' AND d='ab'
All parameters don't have to be given, I just want to show the maximum that has to be possible. Is there any way to write this shorter?
I use PHP with doctrine, if that is any help.
Thanks in advance!
Example:
my_table:
a | b | c | d
-----+-----+-----+-----
a | 0 | . | ab
b | 0 | - | ag
a | 1 | . | cfd
c | 1 | . | b
a | 1 | - | ab
c | 1 | - | cfd
should give this result (without where statement):
a | b | c | d
-----+-----+-----+-----
a | 0 | . | ab
b | 1 | - | ag
c | | | cfd
| | | b
And with WHERE b=0 statement:
a | b | c | d
-----+-----+-----+-----
a | 0 | . | ab
b | | - | ag
EDIT: changed subqueries to UNION and made the data types fit to the example

UPDATE: Well, I originally wrote up a generic SQL solution for this problem, not realizing that MySQL apparently doesn't allow for it.
So if you can create a view, that may be the lightest-weight solution. (The view's defining query would be the same as the select in my original solution's with clause.)
Alternately you could create a temporary table. Maybe a little more resource-intensive if there's much data, but less likely that anyone would restrict the required permissions.
For the record, original solution was as follows:
with my_filtered as (
select *
from my_table
where a = 1 and b = 2 -- and ...
)
-- carry on with your query, using my_filtered instead of my_table

Related

PDO Query : Count related and repeated values then inner join

I work with PHP and PDO.
So I have 2 tables like,
Table 1
| id | name | age |
| 1 | John | 25 |
| 2 | Tom | 32 |
| 3 | James| 45 |
Table 2
| id | Comment | Link |
| 1 | some text | 3 |
| 2 | some text | 3 |
| 3 | some text | 1 |
So, Link column numbers represent id's in table1. For example Link = 3s in table 2 represent James in table 1. I need a query which brings all table1's data and also a number of repeated value for related Link column which comes from table2.
For example, the query should give me (let's choose James),
| id | name | age | Value |
| 3 | James | 45 | 2 |
value=2, because there are two 3s in link column which related to James
I tried somethings but got lots of errors.
I think you just need the GROUP BY
SELECT a.id,
a.name,
a.age,
count(*) as value
FROM table1 a
JOIN table2 b ON a.id = b.link
GROUP BY a.id, a.name, a.age
If you really want just one row then add WHERE
SELECT a.id,
a.name,
a.age,
count(*) as value
FROM table1 a
JOIN table2 b ON a.id = b.link
WHERE a.name = 'James'
GROUP BY a.id, a.name, a.age
or use subquery
SELECT a.id,
a.name,
a.age,
(SELECT count(*) FROM table2 b WHERE a.id = b.link) as value
FROM table1 a
WHERE a.name = 'James'

MySQL Multiple Conditions on Group By / Having Clause

I have three tables that are all inter-related with the following structure.
ModuleCategory Table:
+------------------+----------------+------------+
| ModuleCategoryID | ModuleCategory | RequireAll |
+------------------+----------------+------------+
| 90 | Cat A | YES |
| 91 | Cat B | NO |
+------------------+----------------+------------+
ModuleCategorySkill Table:
+------------------+---------+
| ModuleCategoryID | SkillID |
+------------------+---------+
| 90 | 1439 |
| 90 | 3016 |
| 91 | 1440 |
| 91 | 3016 |
+------------------+---------+
EmployeeSkill Table:
+---------+---------+
| EmpName | SkillID |
+---------+---------+
| Emp1 | 1439 |
| Emp1 | 3016 |
| Emp2 | 1440 |
| Emp2 | 3016 |
| Emp3 | 1439 |
| Emp4 | 3016 |
+---------+---------+
Desired Output:
+------------------+-------+
| ModuleCategory | Count |
+------------------+-------+
| Cat A | 1 |
| Cat B | 3 |
+------------------+-------+
I am trying to group by ModuleCategoryID's and get the count of employees which have the skills being tracked.
Normally, I can do the following query to obtain the numbers:
select mc.ModuleCategory, Count(*) as Count from ModuleCategory as mc
join ModuleCategorySkill as mcs on mc.ModuleCategoryID = mcs.ModuleCategoryID join EmployeeSkill as es on es.SkillID= mcs.SkillID
group by mc.ModuleCategoryID
However, I have a column RequireAll in the ModuleCategory table which if it is set to 'YES' should only count employees as 1 only if they have all the skills in the category. If it is set to NO then it can count each row normally and increase the count by the number of rows it groups by.
I can achieve this by writing separate queries for each modulecategoryID and using a having Count() > 1 (which will find me anyone that has all the skills for ModuleCategoryID 90). If there were 3 skills than I would have to change it to Having Count() > 2. If there isn't anyone that has all the skills specified, the count should be 0.
I need a dynamic way of being able to do this since there is a lot of data and writing one query for each ModuleCategoryID isn't the proper approach.
Also, I am using PHP so I can loop through and create a sql string that can help me achieve this. But I know I will run into performance issues on big tables with a lot of skills and modulecategoryID's.
Any guidance on how to achieve this is much appreciated.
You can do it by joining on the total category counts, and then using conditional aggregation:
select modulecategory,
count(case when requireall = 'yes'
then if(s = t, 1, null)
else s
end)
from (
select modulecategory,empname, requireall, count(*) s, min(q.total) t
from employeeskill e
inner join modulecategoryskill mcs
on e.skillid = mcs.skillid
inner join modulecategory mc
on mcs.modulecategoryid = mc.modulecategoryid
inner join (
select modulecategoryid, count(*) total
from modulecategoryskill
group by modulecategoryid
) q
on mc.modulecategoryid = q.modulecategoryid
group by modulecategory, empname
) qq
group by modulecategory;
demo here
This operates under the assumption an employee isn't going to be allocated the same skill twice, if that is something that may happen, this query is alterable to support it, but it seems like a broken scenario to me.
What we have here is an inner query that collates all the information we need (category name, employee name, whether or not all skills are required, how many skills are in the group per employee, and how many there in the group total), with an outer query that uses a conditional count to change how the rows are tallied, based on the value of requireall.

How to check if a row does not exist in a database

I have the following setup:
A table with columns a, b, c.
An array with a random number of items (i1, i2, i3,... in).
Table contains rows like
1 1 i1
1 1 i2
. . .
. . .
. . .
1 1 in
1 2 i1
. . .
. . .
. . .
2 1 i1
2 2 i2
The thing is that not all records are there. For example row 1 2 i1 might be missing.
What I would like to do, from query (without getting all rows and iterate through them) is to see IF any row is missing (I don't care witch one, only IF one is missing).
This is a very simplified example for a much more complex problem so if I didn't expose it clear, or I forgot to mention anything feel free to ask for details.
A select and process in PHP is acceptable, as long as I don't select everything in table (although I don't see how to do this by processing data without selecting all but felt like it worth mentioning).
Some of you asked for a pattern so...:
Let's simplify some more... let's say column one has an array of possible data that can be found there, same for column 2, and already said it for column 3. All possible combinations between the 3 of then should be found on the table. I need to know if any are missing...
assuming you know he values for column a and b you could try the following:
select c, count (*) group by c;
this would tell you how many entries for each value are there.
i1 3
i2 0
in 3
then you could iterate over that result to see whats missing
Assume we have a table with this data.
mysql> SELECT * FROM stuff;
+------+------+------+
| a | b | c |
+------+------+------+
| 1 | 1 | i1 |
| 1 | 1 | i2 |
| 1 | 2 | i2 |
| 1 | 2 | i3 |
| 2 | 1 | i1 |
+------+------+------+
5 rows in set (0.00 sec)
Lets also assume that all possible values for C is in the table. Then we can construct a reference table like this.
mysql> SELECT a,b,c FROM (SELECT DISTINCT a,b FROM stuff) t1 CROSS JOIN (SELECT DISTINCT c FROM stuff) t2;
+------+------+------+
| a | b | c |
+------+------+------+
| 1 | 1 | i1 |
| 1 | 2 | i1 |
| 2 | 1 | i1 |
| 1 | 1 | i2 |
| 1 | 2 | i2 |
| 2 | 1 | i2 |
| 1 | 1 | i3 |
| 1 | 2 | i3 |
| 2 | 1 | i3 |
+------+------+------+
9 rows in set (0.00 sec)
We can then compare the table with actual data and the reference table by joining them together like this and get all missing rows like this:
mysql> SELECT * FROM stuff RIGHT JOIN (SELECT a,b,c FROM (SELECT DISTINCT a,b FROM stuff) t1 CROSS JOIN (SELECT DISTINCT c FROM stuff) t2) r ON stuff.a = r.a AND stuff.b = r.b AND stuff.c = r.c WHERE stuff.a IS NULL;
+------+------+------+------+------+------+
| a | b | c | a | b | c |
+------+------+------+------+------+------+
| NULL | NULL | NULL | 1 | 2 | i1 |
| NULL | NULL | NULL | 2 | 1 | i2 |
| NULL | NULL | NULL | 1 | 1 | i3 |
| NULL | NULL | NULL | 2 | 1 | i3 |
+------+------+------+------+------+------+
4 rows in set (0.00 sec)
The RIGHT JOIN ON a,b,c will match the rows in the reference table r against the actual rows. The missing rows will manifest as NULL on stuff side. Therefore we can get all missing rows by selecting any row with a NULL field in the stuff table.
Edit: You can change the SELECT * ... in the last query to SELECT count(*) ... and you get the number of missing rows in this case 4.
You can do this with a simple count. The number of expected rows is the number of distinct elements in A times the number of distinct elements in B times the number of distinct elements in C.
To count the number that are missing, just do arithmetic on the appropriate values:
select (cnt - cntA*cntB*cntC) as NumMissingRows
from (select count(distinct a) as cntA,
count(distinct b) as cntB,
count(distinct c) as cntC,
count(*) as cnt
from t
) t
What about such a query, this might not be the best performance but for a one time task this should work.
SELECT t1.id,
(
SELECT t2.id FROM table t2 WHERE t2.id < t1.id ORDER BY t2.id DESC LIMIT 1
) as prv
FROM table t1
HAVING id <> prv + 1
I would think about doing it this way, which will still work even if there are duplicates in your list of values. This sames doing any looping over the resulting fields (just a single row comes back which will tell you how many unique in your array are not found on the table.
SELECT COUNT(*)
FROM (SELECT 'i1' AS aCol
UNION
SELECT 'i2' AS aCol
UNION
SELECT 'i3' AS aCol
UNION
.......
UNION
SELECT 'in' AS aCol) Sub1
LEFT OUTER JOIN aTable
ON Sub1.aCol = aTable.c
WHERE aTable.c IS NULL
Could also be modified very easily to bring back a list of the items that are not found should that be required in the future.

Obtaining information from 4 tables and manipulate it?

I'm having a lot of difficulties in manipulating information in order to achieve the desired result. Through a query I join with the following result:
Area | ID_AT | AT | TYPE
-----------------------------------------
Informatica | 1 | Sistemaa | E
Informatica | 3 | engraçado | E
Informatica | 3 | engraçado | I
Gestão | 2 | aaaaa | I
query:
select a.Area, c.ID_AT, c.AT, dest.type
from AREA_AT a
left join AT_C c on a.id_AREA_AT = c.id_AREA_AT
left join dest_atv d on d.id_AT = c.id_at and d.id_uo = c.id_uo
left join CLIE dest on d.id_CLIE = dest.id_CLIE
where id.uo = 1222
order by a.id_AREA_AT, c.id_at, dest.type
But what I want is to create a table in php as follows:
Area | ID_AT | AT | E | I
-------------------------------------------
Informatica | 1 | Sistemaa | X |
Informatica | 3 | engraçado | X | X
Gestão | 2 | aaaaa | | X
In short, what I intend to do here is to show only one table ID ativ, showing that may exist or not type I or E depending on what comes from the query.
Do I have to modify the query to facilitate the work myself? Or will I have to create a fairly complicated algorithm in php to perform this function? Ando here turns hours and still could not find a solution that will can help me?
It sounds like you need to do a pivot query. Something like
select a.Area,
c.ID_AT,
c.AT,
max( case when dest.type = 'E' then 'X' else null end) E,
max( case when dest.type = 'I' then 'X' else null end) I
from AREA_AT a
left join AT_C c on a.id_AREA_AT = c.id_AREA_AT
left join dest_atv d on d.id_AT = c.id_at and d.id_uo = c.id_uo
left join CLIE dest on d.id_CLIE = dest.id_CLIE
where id.uo = 1222
group by a.area, c.id_at, c.at
order by a.area, c.id_at
Note that you won't be able to ORDER BY the a.id_area_at column if you are not selecting (and grouping by) that column in this formulation. I changed that to sort by a.area instead.

GROUP_CONCAT With Nested Set Model

I have an application that uses a nested set model class to organise my data, however I'm trying to write a query that will group_concat my results. I know I need to put some sub select statements somewhere but I can't figure it out!
Here's my structure at the moment:
table: person
-----------+------------+-----------
|Person_ID | Name | Age |
-----------+------------+-----------
| 1 | Mark Vance | 19 |
| 2 | Michael Tsu| 22 |
| 3 | Mark Jones | 29 |
| 4 | Sara Young | 25 |
-----------+------------+-----------
table: person_to_group
----+------------+-----------
|ID | Person_ID | Group_ID |
----+------------+-----------
| 1 | 3 | 1 |
| 2 | 3 | 2 |
| 3 | 1 | 2 |
| 4 | 4 | 3 |
----+------------+-----------
table: groups
----------+--------------+--------------+-------------
|Group_ID | Group_Name | Group_Left | Group_Right |
----------+--------------+--------------+-------------
| 1 | Root | 1 | 6 |
| 2 | Node | 2 | 5 |
| 3 | Sub Node | 3 | 4 |
----------+--------------+--------------+-------------
I need to render something like this with my results:
//Grab the group_IDs for this person and put them in the class tag...
<li class="2 3">Sara Young is in the Sub Node Group</li>
Notice that although Sara is in the Sub Node group, she is still being given the id for Node aswell because she is a child of Node.
The following is the query that I am working with as a starting point.
SELECT *, GROUP_CONCAT( CAST( gg.Group_ID AS CHAR ) SEPARATOR ' ' ) Group_IDs
FROM groups gg
LEFT JOIN person_to_group AS t1 ON gg.Group_ID = t1.Group_ID
LEFT JOIN person AS t2 ON t2.Person_ID = t1.Person_ID
GROUP BY t2.per_ID
ORDER BY t2.Name ASC
Any help would be much appreciated!
Here's how I'd write the query:
SELECT p.Name,
GROUP_CONCAT( g.Group_Name ) AS Group_List,
GROUP_CONCAT( CAST( gg.Group_ID AS CHAR ) SEPARATOR ' ' ) AS Group_ID_List
FROM person AS p
INNER JOIN person_to_group AS pg ON p.Person_ID = pg.Person_ID
INNER JOIN groups AS g ON pg.Group_ID = g.Group_ID
INNER JOIN groups AS gg ON g.Group_Left BETWEEN gg.Group_Left AND gg.Group_Right
GROUP BY p.Name
ORDER BY p.Name ASC
Note that if you group by person name, you also need to GROUP_CONCAT the list of group names. According to your schema, a person could belong to multiple groups, because of the many-to-many relationship.
I also recommend against using SELECT * in general. Just specify the columns you need.
This was little bit interesting as I do programming in both MsSQL and MySql. In SQL I have used function called STUFF. In MySQL you can use a function called INSERT. I tried out the below query in MsSQL. Don't have a MySQL handy to try out my query. If I have time I will post the MySQL version of the query.
DECLARE #person TABLE (Person_ID INT, Name VARCHAR(50), Age INT)
INSERT INTO #person VALUES
(1,'Mark Vance',19),
(2,'Michael Tsu',22),
(3,'Mark Jones',29),
(4,'Sara Young',25)
DECLARE #groups TABLE (Group_ID INT, Group_Name VARCHAR(50), Group_Left INT, Group_Right INT)
INSERT INTO #groups VALUES
(1,'Root',1,6),
(2,'Node',2,5),
(3,'Sub Node',3,4)
DECLARE #person_to_group TABLE (ID INT, Person_ID INT, Group_ID INT)
INSERT INTO #person_to_group VALUES
(1,3,1),
(2,3,2),
(3,1,1),
(4,4,1),
(4,1,1)
SELECT *,STUFF((SELECT ',' + CAST(g.Group_ID AS VARCHAR) FROM #groups g
JOIN #person_to_group pg ON g.Group_ID = pg.Group_ID AND pg.Person_ID = a.Person_ID FOR XML PATH('')) , 1, 1, '' ) FROM #person a
Function: INSERT(str,pos,len,newstr)
Documentation: http://dev.mysql.com/doc/refman/5.0/en/string-functions.html#function_insert

Categories