SQL SELECT WHERE NOT IN from same table query - mysql

I'm having problem with the following SQL query and MySQL
SELECT
id, cpid, label, cpdatetime
FROM
mytable AS a
WHERE
id NOT IN
(
SELECT
id
FROM
mytable AS b
WHERE
a.label = b.label
AND
a.cpdatetime > b.cpdatetime
)
AND
label LIKE 'CB%'
AND
cpid LIKE :cpid
GROUP BY label
ORDER BY cpdatetime ASC
the table looks like this
1 | 170.1 | CB55 | 2013-01-01 00:00:01
2 | 135.5 | CB55 | 2013-01-01 00:00:02
3 | 135.6 | CB59 | 2013-01-01 00:00:03
4 | 135.5 | CM43 | 2013-01-01 00:00:04
5 | 135.5 | CB46 | 2013-01-01 00:00:05
6 | 135.7 | CB46 | 2013-01-01 00:00:06
7 | 170.2 | CB46 | 2013-01-01 00:00:07
I would like my query to return
3 | 135.6 | CB59
5 | 135.5 | CB46
Edit
labels are dogs/cats and cpids are temporary family keeping the dogs/cats.
Dogs/cats move from family to family.
I need to find dogs/cats who were in :userinput family but only if they were not in another family previously
I can't alter the database and just have to work with the data as they are and I'm not the one who wrote the application/database schema.

Try to avoid correlated sub queries by using LEFT JOIN:
SELECT a.id, a.cpid, a.label, a.cpdatetime
FROM mytable AS a
LEFT JOIN mytable AS b ON a.label = b.label AND a.cpdatetime > b.cpdatetime
WHERE a.label LIKE 'CB%' AND a.cpid LIKE :cpid
AND b.label IS NULL
GROUP BY a.label
ORDER BY a.cpdatetime ASC
Fiddle
If the join condition fails, the fields of the second table alias b will be set to NULL.
Alternatively, use a non-correlated sub query:
SELECT a.id, a.cpid, a.label, a.cpdatetime
FROM mytable AS a
INNER JOIN (
SELECT label, MIN(cpdatetime) AS cpdatetime
FROM mytable
WHERE label LIKE 'CB%'
GROUP BY label
) AS b ON a.label = b.label AND a.cpdatetime = b.cpdatetime
WHERE a.cpid LIKE '135%'
ORDER BY a.cpdatetime
First, you find the minimum cpdatetime for each label and then join that with the first table where you add the additional cpid condition.

I think this is really what you want to do - select the IDs that are the earliest IDs for each label, then select out of those the records with a 135 cpid and a CB label.
SELECT
A.id, cpid, A.label, cpdatetime
FROM
mytable AS a inner join
(select id, label from mytable
group by label
having min(cpdatetime)) as b
on A.label=B.label and A.id=B.id
WHERE
A.label LIKE 'CB%'
AND
cpid LIKE '135%'
GROUP BY A.label
ORDER BY cpdatetime ASC;
http://sqlfiddle.com/#!2/ccccf/16

Related

MYSQL Inner join the table itself based on certain conditions to show either all, or none

Consider the tables:
table
no | date
--------------------------------
1 | 2015-03-17 00:00:00.000
1 | 2015-03-17 00:00:00.000
1 | 2015-03-17 00:00:00.000
2 | 2015-03-01 00:00:00.000
2 | 2016-03-01 00:00:00.000
2 | 2016-03-01 00:00:00.000
What is the most efficient self-join query I can make, in order to produce the records that returns only the first 3 records (no. = 1) considering the condition is that the date must fall before 2016.
For instance, document no.2 will not show at all, because one of its date is > 2016, however document no.1 will show for all 3 records, because all 3 dates are < 2016
I tried the following:
SELECT a.no, a.date
FROM table a
INNER JOIN table b ON b.no = a.no AND b.date < '2016' --pseudocode for date comparison
However, the returned results are
no | date
--------------------------------
1 | 2015-03-17 00:00:00.000
1 | 2015-03-17 00:00:00.000
1 | 2015-03-17 00:00:00.000
2 | 2015-03-01 00:00:00.000
There's are couple of ways of doing it without even using JOIN! Here's one:
select * from tbl
where `no` not in
(
select `no` from tbl
where `date` >= '2016-01-01 00:00:00.000'
)
First you can get the list of id's that should return and then use the self join as below:
select b.*
from table b
join (select id from table group by id having year(max(date)) <2016) x
on (a.no = x.no);
SELECT t1.* FROM table t1
JOIN
(
SELECT no, max(date) as max_date FROM table
GROUP BY no
HAVING max_date < '2016-01-01'
) t2
ON t1.no = t2.no
Maybe I don't understand something but from your requirements - you don't need self-join.
For instance, document no.2 will not show at all, because one of its date is > 2016, however document no.1 will show for all 3 records, because all 3 dates are < 2016
You need anti-join.
SELECT a.no, a.date
FROM table A
WHERE
NOT EXISTS(
SELECT * FROM table B
WHERE
B.no = A.no
AND B.date < DATE('2016-01-01')
)

MySQL left outer join the same table multiple times?

So I have a MySQL dilemma which seemed to be relatively simple, however not the case.
I have two tables: one which holds a list of unique ids to display and another table which lists the ids next to a timestamp.
====== ============================
| ID | | ID | Timestamp |
====== ============================
| 1 | | 1 | 2015-10-10 00:00:00 |
| 2 | | 1 | 2015-10-10 00:10:00 |
| .. | | 2 | 2015-10-10 00:00:00 |
====== ============================
I need to display a boolean if the relevant id has records in Table B between two Date-Times and the last date it was active of all time.
I have tried something similar to this:
SELECT
a.`ID`,
MAX(b1.`Timestamp`) IS NOT NULL as 'Active',
MAX(b2.`Timestamp`) AS 'LastActive'
FROM `Table-A` a
LEFT OUTER JOIN `Table-B` b1
ON a.ID = b1.ID
AND b1.`Timestamp` BETWEEN #startTime AND #endTime
LEFT OUTER JOIN `Table-B` b2
ON a.ID = b2.ID
GROUP BY a.ID
;
Currently not sure why: but the query seems to run infinitely and not get any results. Can anyone suggest the correct way to get the results needed in my query?
EDIT:
Here is an EXPLAIN SELECT for the above query.
Use this
SELECT
a.*,
IF(b1.cnt IS NULL, FALSE, TRUE) AS is_found,
IFNULL(b2.dt, '-') AS max_dt
FROM table1 a
LEFT OUTER JOIN (
SELECT
id,
COUNT(*) AS cnt
FROM table2
WHERE
`timestamp` BETWEEN '2015-01-01' AND '2015-12-31'
GROUP BY 1) b1
ON a.id=b1.id
LEFT OUTER JOIN (
SELECT id,
MAX(TIMESTAMP) AS dt
FROM table2
GROUP BY 1) b2
ON a.id=b2.id

Get min price id without inner select

I have a table called a with this data:
+-----+-----------+-------+
| id | parent_id | price |
+-----+-----------+-------+
| 1 | 1 | 100 |
| 2 | 1 | 200 |
| 3 | 1 | 99 |
| 4 | 2 | 1000 |
| 5 | 2 | 999 |
+-----+-----------+-------+
I want to get the id of min pirce for each parent_id.
There is any way to get this result without subquery?
+-----+-----------+-------+
| id | parent_id | price |
+-----+-----------+-------+
| 3 | 1 | 99 |
| 5 | 2 | 999 |
+-----+-----------+-------+
SELECT D1.id, D1.parent_id, D1.price
FROM Data D1
LEFT JOIN Data D2 on D2.price < D1.price AND D1.parent_id = D2.parent_id
WHERE D2.id IS NULL
Here is a shot at how to do it without subqueries. I haven't tested, let me know if it works!
SELECT t.id, t.parent_id, t.price
FROM table t
LEFT JOIN table t2
ON (t.parent_id = t2.parent_id AND t.price > t2.price)
GROUP BY t.id, t.parent_id, t.price
HAVING COUNT(*) = 1 AND max(t2.price) is null
ORDER BY t.parent_id, t.price desc;
Try this:
SELECT T1.id,T2.parent_id,T2.price FROM
(SELECT id,price
FROM TableName) T1
INNER JOIN
(
SELECT parent_id,MIN(price) as price
FROM TableName
GROUP BY parent_id) T2 ON T1.price=T2.price
See result in SQL Fiddle.
Try group by,
SELECT parent_id,min(price)
FROM TableName
GROUP BY parent_id
You can do this with a LEFT JOIN
SELECT a.id, a.parent_id, a.price
FROM a
LEFT JOIN a AS b ON b.price < a.price AND b.parent_id = a.parent_id
WHERE b.id IS NULL
Find the results at this fiddle:
http://sqlfiddle.com/#!2/09c888/10
You can try this without using any join or subquery you will surely get the desired result.
SELECT TOP 2 FROM a ORDER BY price

MySQL same Query multiple tables

I need to query different tables that have the same columns but different content.
Table A:
ID DocDate Type
1 2013-05-01 A
2 2013-05-01 B
3 2013-05-02 D
4 2013-05-04 D
Table B:
ID DocDate Type
1 2013-05-01 F
2 2013-05-03 G
3 2013-05-03 G
4 2013-05-05 H
What I need:
COUNT(Tablea.ID) COUNT(Tableb.ID) DocDate
2 1 2013-05-01
1 NULL 2013-05-02
NULL 2 2013-05-03
1 NULL 2013-05-04
NULL 1 2013-05-05
Any help would be really appreciated.
Try
SELECT d.docdate, a.total totala, b.total totalb
FROM
(
SELECT docdate
FROM tablea
UNION
SELECT docdate
FROM tableb
) d LEFT JOIN
(
SELECT docdate, COUNT(*) total
FROM tablea
GROUP BY docdate
) a ON d.docdate = a.docdate LEFT JOIN
(
SELECT docdate, COUNT(*) total
FROM tableb
GROUP BY docdate
) b ON d.docdate = b.docdate
 ORDER BY d.docdate
Output:
| DOCDATE | TOTALA | TOTALB |
--------------------------------
| 2013-05-01 | 2 | 1 |
| 2013-05-02 | 1 | (null) |
| 2013-05-03 | (null) | 2 |
| 2013-05-04 | 1 | (null) |
| 2013-05-05 | (null) | 1 |
Here is SQLFiddle demo
There are a couple of ways to get this result.
The most efficient query to return the specified rows is likely going to be:
SELECT NULLIF(SUM(c.cnt_a_id),0) AS cnt_a_id
, NULLIF(SUM(c.cnt_b_id),0) AS cnt_b_id
, c.DocDate
FROM (
SELECT COUNT(a.ID) AS cnt_a_id
, 0 AS cnt_b_id
, a.DocDate AS DocDate
FROM Table_A a
GROUP BY a.DocDate
UNION ALL
SELECT 0
, COUNT(b.ID)
, b.DocDate
FROM Table_B b
GROUP BY b.DocDate
) c
GROUP BY c.DocDate
Suitable covering indexes on (DocDate, ID) of each table will benefit performance on large sets.
Another simpler to understand, but more expensive, would be create the UNION of the tables, and then perform the GROUP BY.
SELECT NULLIF(COUNT(c.a_id)) AS cnt_a_id
, NULLIF(COUNT(c.b_id)) AS cnt_b_id
, c.DocDate
FROM (
SELECT a.ID AS a_id
, NULL + 0 AS b_id
, a.DocDate AS DocDate
FROM Table_A a
UNION ALL
SELECT NULL + 0 AS a_id
, b.ID AS b_id
, b.DocDate AS DocDate
FROM Table_B b
) c
GROUP BY c.DocDate
(This second query is less efficient, because of the way MySQL materializes the query in the inline view as a temporary MyISAM table; this second query basically creates a copy of Table_A and Table_B concatenated together, and runs a query against that.
The first query is little different, in that it produces smaller sets to be concatenated together.

MySQL count rows within the same intervals to eachother

I have a table where one column is the date:
+----------+---------------------+
| id | date |
+----------+---------------------+
| 5 | 2012-12-10 10:12:37 |
+----------+---------------------+
| 4 | 2012-12-10 09:09:55 |
+----------+---------------------+
| 3 | 2012-12-09 21:12:35 |
+----------+---------------------+
| 2 | 2012-12-09 20:15:07 |
+----------+---------------------+
| 1 | 2012-12-09 20:01:42 |
+----------+---------------------+
What I need, is to count the rows which are for example whitin 3 hours to each other. In this example I want to join the upper row with the 2nd row, and the 3rd row with the 4th and 5th rows. So my output should be like this:
+----------+---------------------+---------+
| id | date | count |
+----------+---------------------+---------+
| 5 | 2012-12-10 10:12:37 | 2 |
+----------+---------------------+---------+
| 3 | 2012-12-09 21:12:35 | 3 |
+----------+---------------------+---------+
How could I do this?
I think you need a self-join for this:
select t.id, t.date, COUNT(t2.id)
from t left outer join
t t2
on t.date between t2.date - interval 3 hour and t2.date + interval 3 hour
group by t.id, t.date
(This is untested code so it might have a syntax error.)
If you are trying to divide everything into 3-hour intervals, you can do something like:
select max(t.date), t.id, count(*)
from (select t.*,
(date(date)*100 + floor(hour(date)/3)*3) as interval
from t
) t
group by interval
I am not sure how to do this with My SQL but i am able to build a set of queries in SQL Server 2005 which will provide the intended results. Here is the working sample, its very complex and may be overly complex but that's how i was able to get the desired result:
WITH BaseData AS
(
SELECT 5 AS ID, '2012-12-10 10:12:37' AS Date
UNION ALL
SELECT 4 AS ID, '2012-12-10 09:09:55' AS Date
UNION ALL
SELECT 3 AS ID, '2012-12-09 21:12:35' AS Date
UNION ALL
SELECT 2 AS ID, '2012-12-09 20:15:07' AS Date
UNION ALL
SELECT 1 AS ID, '2012-12-09 20:01:42' AS Date
),
BaseDataWithRowNum AS
(
SELECT ID,DATE, ROW_NUMBER() OVER (ORDER BY Date DESC) AS RowNum
FROM BaseData
),
InterRelatedDates AS
(
SELECT B1.RowNum AS RowNum1,B2.RowNum AS RowNum2
FROM BaseDataWithRowNum B1
INNER JOIN BaseDataWithRowNum B2
ON B1.Date BETWEEN B2.Date AND DATEADD(hh,3,B2.Date)
AND B1.RowNum < B2.RowNum
AND B1.ID != B2.ID
),
InterRelatedDatesWithinMultipleGroups AS
(
SELECT G1.RowNum1,G2.RowNum2
FROM InterRelatedDates G1
LEFT JOIN InterRelatedDates G2
ON G1.RowNum2 = G2.RowNum2
AND G1.RowNum1 != G2.RowNum1
)
SELECT BN.ID,
BN.Date,
CountExcludingOriginalGrouppingRecord +1 AS C
FROM
(
SELECT RowNum1 AS RowNum,COUNT(1) AS CountExcludingOriginalGrouppingRecord
FROM
(
-- If a row was used in only one group then it is ok. use as it is
SELECT D1.RowNum1
FROM InterRelatedDatesWithinMultipleGroups AS D1
WHERE D1.RowNum2 IS NULL
UNION ALL
-- In case a row was selected in two groups, choose the one with higher date
SELECT Min(D1.RowNum1)
FROM InterRelatedDatesWithinMultipleGroups AS D1
WHERE D1.RowNum2 IS NOT NULL
GROUP BY D1.RowNum2
) T
GROUP BY RowNum1
) T2
INNER JOIN BaseDataWithRowNum BN
ON BN.RowNum = T2.RowNum