How can I make a WHERE clause restriction dynamic on ID level - mysql

I want to create a WHERE clause where the restriction is dynamic depending on the ID and date I am looking for to do a count on the number of rows on an ID level in one table and bring it into another table:
Table A looks like this with several IDs:
ID - Create date
12221 - 12/12/2018
13331 - 12/10/2018
Table B has the following information with several IDs
ID - Date
12221 - 10/10/2018
12221 - 07/06/2017
13331 - 01/20/2019
I now want to do a count of all the rows in Table 2 that have a date in there before the actual signup date per ID. so in the example above it should say:
ID - Count
12221 - 2
13331 - 0
Not sure how to do that in SQL with a WHERE clause. I could potentially bring in the "create date" as a new column in Table B and do a workaround on that (e.g. 1 if date smaller than create date, and 0 if date bigger than create date) and then put that into the WHERE clause, but since I have a ton of data, that is not the ideal solution.
Thank you!

Join the tables and group with the condition on dates:
select b.id, sum(a.createdate is not null) count
from tableb b left join tablea a
on a.id = b.id and b.date < a.createdate
group by b.id
See the demo.
Results:
| id | count |
| ----- | ----- |
| 12221 | 2 |
| 13331 | 0 |

You need to select all records from TableA and a count of records in TableB that have a date that is before the date in TableA:
CREATE TEMPORARY TABLE TableA (ID INT, CreateDate DATETIME);
INSERT INTO TableA VALUES (12221, '2018-12-12'), (13331, '2018-10-12');
CREATE TEMPORARY TABLE TableB (ID INT, DDate DATETIME);
INSERT INTO TableB VALUES (12221, '2018-10-10'), (12221, '2017-06-07'), (13331, '2019-01-20');
SELECT a.ID,
COUNT(b.ID) AS MatchCount
FROM TableA a
LEFT JOIN TableB b ON b.ID = a.ID AND b.DDate < CreateDate
GROUP BY ID;
DROP TABLE TableA;
DROP TABLE TableB;
Note that in my example I have renamed the date column from TableB to DDate.
Output:
ID MatchCount
12221 2
13331 0
In the above SQL, TableB is joined to TableA using the criteria that the ID must be equal and the date must be before the created date. The result set is built by counting the number of rows returned from TableB that match this criteria.

This should give you every id from TableA and the desired count:
SELECT a.id, COUNT(b.id) AS preSignupEntries
FROM TableA AS a
LEFT JOIN TableB AS b ON a.id = b.id AND a.`Create date` > b.`Date`
GROUP BY a.id;
COUNT, like most aggregate functions, ignores NULL values.
Edit: this assumes your "date" fields are actually a date data type; if you are storing them as strings, you'll need to do some parsing.
Edit2: Using an INNER JOIN could be slightly faster if you do not actually care about TableA entries without predated TableB entries.

select a.id, IFNULL((select count(b.id)
from tableb b
where a.id = b.id and b.date < a.createdate
group by b.id ),0) as total
from tablea a
For the scenario the you have a row in table a, but not in table b:
IFNULL function only applies for mysql.
table a
ID - Create date
12221 - 12/12/2018
13331 - 12/10/2018
13336 - 12/10/2018
table b
ID - Date
12221 - 10/10/2018
12221 - 07/06/2017
13331 - 01/20/2019
result
ID MatchCount
12221 2
13331 0
13336 0

try
Select id,Count(id) From TableB Group By ID

Related

MySQL join two tables with group by in each

I have two mysql tables with part numbers and qty's. I want to sum each tables qty sum(qty) ... group by partNumber Then join the two tables on the part number.
Sometimes table A will have part numbers that table b does not and vice versa. Below is an image of what I am expecting.
I've tried something like this, but this returns a row for each table and I want it to return 1 combined row
SELECT *, null as macroQty, sum(qty) as cardinalQty
FROM parts.cardinal where fileinfoid IN
(select cardinalFiles from parts.reports where fileinfoid = 418)
GROUP BY partNumber UNION ALL
SELECT *, sum(qty) as macroQty, null as cardinalQty
FROM parts.macro where fileinfoid IN
(select macroFiles from parts.reports where fileinfoid = 418 )
GROUP BY partNumber
I also tried wrapping it in an outer select and grouping by the part number from the outer select like this, but this results in the second inner select being null always
SELECT * FROM (
SELECT *, null as macroQty, sum(qty) as cardinalQty
FROM parts.cardinal where fileinfoid IN
(select cardinalFiles from parts.reports where fileinfoid = 418)
GROUP BY partNumber UNION ALL
SELECT *, sum(qty) as macroQty, null as cardinalQty
FROM parts.macro where fileinfoid IN
(select macroFiles from parts.reports where fileinfoid = 418 )
GROUP BY partNumber
) combined GROUP BY combined.partNumber
One approach would be to identify unique part numbers across the 2 tables (using a UNION with it's applied distinct) and then use correlated sub queries to get the sums. For example
drop table if exists a,b;
create table a(id int,val int);
create table b(id int,val int);
insert into a values(1,10),(1,10),(3,10),(4,10);
insert into b values (2,10),(4,10),(4,10);
select (select sum(a.val) from a where a.id = s.id) aval,
(select sum(b.val) from b where b.id = s.id) bval,
s.id partno
from
(
select id from a
union select id from b
) s
order by s.id;
+------+------+--------+
| aval | bval | partno |
+------+------+--------+
| 20 | NULL | 1 |
| NULL | 10 | 2 |
| 10 | NULL | 3 |
| 10 | 20 | 4 |
+------+------+--------+
4 rows in set (0.00 sec)
I would phrase this as a join between two subqueries which each find the sum in their respective tables. However, since each table does not necessarily contain all part numbers, and in fact there may be part numbers unique to each table, we will have to use a full outer join approach.
SELECT
t1.partNumber,
t1.cardinalQty,
COALECSE(t2.macroQty, 0) AS macroQty
FROM
(
SELECT partNumber, SUM(qty) AS cardinalQty
FROM cardinal
GROUP BY partNumber
) t1
LEFT JOIN
(
SELECT partNumber, SUM(qty) AS macroQty
FROM macro
GROUP BY partNumber
) t2
ON t1.partNumber = t2.partNumber
UNION ALL
SELECT
t2.partNumber,
0 AS cardinalQty,
t2.macroQty
FROM
(
SELECT partNumber, SUM(qty) AS cardinalQty
FROM cardinal
GROUP BY partNumber
) t1
RIGHT JOIN
(
SELECT partNumber, SUM(qty) AS macroQty
FROM macro
GROUP BY partNumber
) t2
ON t1.partNumber = t2.partNumber
WHERE t1.partNumber IS NULL;
Keep in mind that under normal conditions, in a well designed database, you should rarely encounter a situation which requires using a full outer join. Actually, a full outer join screams out that there is a design problem. In this case, you don't have a single parts table containing all part numbers. That table should exist, so unless you enjoy big ugly queries, you should create a parts table where the partNumber is a primary key.

MySQL Insert and merge into new Table with Rows from one Table

I have a table as follows:
id - rowid - value - date
and in each row I have:
1 - 9 - 123 - 03/2013
2 - 10 - 456 - 03/2013
I want to join both rows into one table like this:
id - rowid - value1 value2 - date
1 - 9 - 123 - 456 - 03/2013
I only need from the first table, the rowid 9 as in the example and the value and date.
From the second row I only need the value. I tried union all and multiple selection but with no success effort.
Help would be highly appreciated. Thanks in advance!
--Assumes there are ONLY 2 records in table with same date.
--If not accurate you will end up with only 1 less record per date than you currently have.
--Assumes you only what the lowest RowId recorded.
create table myNewtable as
(SELECT A.RowID, A.Value, B.Value, A.Date
FROM Oldtable A INNER JOIN Oldtable B
on A.Date=B.Date
and A.RowID < B.RowId)
--Now that we know date is date time and is likely different...
but that RowID is always +1 for the ones you want to join
create table myNewtable as
(SELECT A.RowID, A.Value, B.Value, A.Date
FROM Oldtable A
INNER JOIN Oldtable B
on A.RowID = B.RowId-1)

Need Help on .... Select Where Date NOT BETWEEN

Hope someone can tell ..
Table A Table E
Id | Date Id | Start_date | End_date
1 2012-12-10 1 2012-12-09 2012-12-10
2 2012-12-11 2 2012-12-12 2012-12-14
The Result that I'm hoping ..
2012-12-11
This is the code that I think might work to select date from Table A that not in Table E ranga date...
SELECT * FROM `A`
WHERE `A`.`DATE` NOT BETWEEN (SELECT `E`.`DATE_START` FROM `E`) AND (SELECT `E`.`DATE_END`
FROM `E`);
but unfortunately not, the subquery return more than 1 row.
I wonder how??
thanks
You wonder how the subquery returned more than one row? That's because there's more than one row in the table matching your query.
If you want one row, you'll need to limit the query a little more, such as with:
select `e`.`date_start` from `e` where `e`.`id` = 1
If you want all dates in A that are not contained in any date range in E, one way to do it is to get a list of the A dates that are contained within a range, and then get a list of dates from A that aren't in that list.
Something like:
select date
from a
where date not in (
select a.date
from a, e
where a.date between e.start_date and e.end_date
)
Putting this through the excellent phpMyAdmin demo site as:
create table a (id int, d date);
create table e (id int, sd date, ed date);
insert into a (id, d) values (1, '2012-12-10');
insert into a (id, d) values (2, '2012-12-11');
insert into e (id, sd, ed) values (3, '2012-12-09', '2012-12-10');
insert into e (id, sd, ed) values (4, '2012-12-12', '2012-12-14');
select d from a where d not in (
select a.d from a, e where a.d between e.sd and e.ed
);
results in the output:
2012-12-11
as desired.
To get all records from A that are not inside any of the date ranges in E, get the records that are within the date ranges, and select the ones not in that result:
select *
from A
where Id not in (
select A.Id
from A
inner join E on A.Date between E.Start_date and E.End_date
)
If the Id in table A is the same as the Id in table E :
SELECT *
FROM A, E
WHERE A.Id = E.Id
AND A.Date NOT BETWEEN E.Start_Date AND E.End_Date
What you're looking for here is the set of records in A where there does not exist a record in B for which the date in A is between the begin and end dates in B.
Therefore I'd suggest that you structure the query in that way.
Something like ...
Select ...
From table_A
Where not exists (
Select null
From table_b
Where ...)
Depending on the join cardinality of the tables and their sizes you may find that this performs better than the "find the rows that are not in the set for which a John exists" method, aside from it being a more intuitive match to your logic.

Adding one extra row to the result of MySQL select query

I have a MySQL table like this
id Name count
1 ABC 1
2 CDF 3
3 FGH 4
using simply select query I get the values as
1 ABC 1
2 CDF 3
3 FGH 4
How I can get the result like this
1 ABC 1
2 CDF 3
3 FGH 4
4 NULL 0
You can see Last row. When Records are finished an extra row in this format
last_id+1, Null ,0 should be added. You can see above. Even I have no such row in my original table. There may be N rows not fixed 3,4
The answer is very simple
select (select max(id) from mytable)+1 as id, NULL as Name, 0 as count union all select id,Name,count from mytable;
This looks a little messy but it should work.
SELECT a.id, b.name, coalesce(b.`count`) as `count`
FROM
(
SELECT 1 as ID
UNION
SELECT 2 as ID
UNION
SELECT 3 as ID
UNION
SELECT 4 as ID
) a LEFT JOIN table1 b
ON a.id = b.id
WHERE a.ID IN (1,2,3,4)
UPDATE 1
You could simply generate a table that have 1 column preferably with name (ID) that has records maybe up 10,000 or more. Then you could simply join it with your table that has the original record. For Example, assuming that you have a table named DummyRecord with 1 column and has 10,000 rows on it
SELECT a.id, b.name, coalesce(b.`count`) as `count`
FROM DummyRecord a LEFT JOIN table1 b
ON a.id = b.id
WHERE a.ID >= 1 AND
a.ID <= 4
that's it. Or if you want to have from 10 to 100, then you could use this condition
...
WHERE a.ID >= 10 AND
a.ID <= 100
To clarify this is how one can append an extra row to the result set
select * from table union select 123 as id,'abc' as name
results
id | name
------------
*** | ***
*** | ***
123 | abc
Simply use mysql ROLLUP.
SELECT * FROM your_table
GROUP BY Name WITH ROLLUP;
select
x.id,
t.name,
ifnull(t.count, 0) as count
from
(SELECT 1 AS id
-- Part of the query below, you will need to generate dynamically,
-- just as you would otherwise need to generate 'in (1,2,3,4)'
UNION ALL SELECT 2
UNION ALL SELECT 3
UNION ALL SELECT 4
UNION ALL SELECT 5
) x
LEFT JOIN YourTable t
ON t.id = x.id
If the id does not exist in the table you're selecting from, you'll need to LEFT JOIN against a list of every id you want returned - this way, it will return the null values for ones that don't exist and the true values for those that do.
I would suggest creating a numbers table that is a single-columned table filled with numbers:
CREATE TABLE `numbers` (
id int(11) unsigned NOT NULL
);
And then inserting a large amount of numbers, starting at 1 and going up to what you think the highest id you'll ever see plus a thousand or so. Maybe go from 1 to 1000000 to be on the safe side. Regardless, you just need to make sure it's more-than-high enough to cover any possible id you'll run into.
After that, your query can look like:
SELECT n.id, a.*
FROM
`numbers` n
LEFT JOIN table t
ON t.id = n.id
WHERE n.id IN (1,2,3,4);
This solution will allow for a dynamically growing list of ids without the need for a sub-query with a list of unions; though, the other solutions provided will equally work for a small known list too (and could also be dynamically generated).

Getting the good row in a MySQL GROUP BY query

I have a MySql table named 'comments' :
id | date | movie_id | comment_value
1 2011/11/05 10 comment_value_1
2 2012/01/10 10 comment_value_2
3 2011/10/10 15 comment_value_3
4 2011/11/20 15 comment_value_4
5 2011/12/10 30 comment_value_5
And i try to have the most recent comment for each movie with the query :
SELECT MAX(date),id,date,movie_id,comment_value FROM comments GROUP BY movie_id
The MAX(date) return the most recent date, but the row associated (movie_id,id,comment_value,date) did not match. It returns the value of the first comment of the movie, like this :
MAX(date) | id | date | movie_id | comment_value
2012/01/10 1 2011/11/05 10 comment_value_1
2011/11/20 3 2011/10/10 15 comment_value_3
2011/12/10 5 2011/12/10 30 comment_value_5
So, my question is : how can i have the most recent comment for each movie, in only one query ( i'm actually using a second query to get the good comment)
Using two queries isn't so bad. Otherwise you can do something like
SELECT id, date, movie_id, comment_value FROM comments c JOIN
(SELECT movie_id, MAX(date) date FROM comments GROUP BY movie_id) x
ON x.movie_id=c.movie_id AND x.date=c.date GROUP BY movie_id;
Try this:
SELECT c1.*
FROM comments c1
LEFT JOIN comments c2 ON (c1.movie_id = c2.movie_id AND c1.date < c2.date)
WHERE c2.id IS NULL
Because of the join condition it will be able to join only the rows which don't contain the maximum date value, so filtering the rows with c2.id IS NULL gives you rows with maximum values.
create table comments (id int,movie_dt datetime,movie_id int,comment_value nvarchar(100))
insert into comments values (1,'2011/11/05',10,'comment_value_1')
insert into comments values (2,'2012/01/10',10,'comment_value_2')
insert into comments values (3,'2011/10/10',15,'comment_value_3')
insert into comments values (4,'2011/11/20',15,'comment_value_4')
insert into comments values (5,'2011/12/10',30,'comment_value_5')
select a.id, m.movie_dt, m.movie_id,a.comment_value
from comments a
inner join
(
SELECT MAX(movie_dt) movie_dt,movie_id
FROM comments
GROUP BY movie_id
) m on (a.movie_dt = m.movie_dt and a.movie_id = m.movie_id)
Is it possible to use a DATETIME field instead of just DATE? That would make the query a lot easier plus give better reporting capabilities. You can always aggregate the DATETIME field down to something more specific if needed.