I have 2 databases, 1 with order number (orders)
| id | reference | ....
1 12345678 ....
2 12345679 ....
and another with order_state_history (order_history)
| id | id_order | order_state | ....
1 1 3
1 1 7
1 1 5
2 1 3
2 1 7
2 1 1
And I need to get all order references that have had 1 specific order_state in the order_history, but NEVER had some other states
For example, all order that have had state 3 and 7, but never had state 5. (only should return the reference from order id 2)
I tried some random simple queries like, but without any luck, if someone can help me with this it would be really much appreciated.
[Here where my attempts, I don't have much experience with SQL]
SELECT
p.id_order,
p.reference,
o.id_order_state AS "state_a",
b.id_order_state AS "state_b"
FROM
order_history o
JOIN orders p
ON o.id_order = p.id_order
JOIN order_history AS b
ON p.id_order = b.id_order
WHERE o.id_order_state = 76 AND b.id_order_state = 4 AND b.id_order_state NOT IN (26,20,22,23)
Thank for your time!
Your question is rather poorly phrased but you can use aggregation and a having clause:
select order_id
from order_state
group by order_id
having sum(state = 3) > 0 and
sum(state = 7) > 0 and
sum(state = 5) = 0;
Related
Here is the table ihave, i was trying days between by joining the same table with left join and group by with min difference. I was not so successful.
Customer|Order|Date
1 | 1 |Date1
1 | 2 |Date2
1 | 3 |Date3
1 | 4 |Date4
2 | 1 |Date1
2 | 2 |Date3
2 | 3 |Date6
3 | 1 |Date3
3 | 2 |Date5
Required is:
Customer|Order|Date |diff
1 | 1 |Date1| 0
1 | 2 |Date2| days_betwen(Date2, Date1)
1 | 3 |Date3| days_betwen(Date3, Date2)
1 | 4 |Date4| days_betwen(Date4, Date3)
2 | 1 |Date1| 0
2 | 2 |Date3| days_betwen(Date3, Date1)
2 | 3 |Date6| days_betwen(Date6, Date3)
3 | 1 |Date3| 0
3 | 2 |Date5| days_betwen(Date5, Date3)
I need suggestion with the logic part!
EDIT: What if the order numbers are not sequential?
In first, you need to join the table to itself by Customer and Order fields. Then use DATEDIFF() function to get days number between two dates.
If the Order column is numbered sequentially then solution is simplest:
SELECT
cur.`Customer` AS `Customer`,
cur.`Order` AS `Order`,
cur.`Date` AS `Date`,
DATEDIFF(cur.`Date`, IFNULL(prv.`Date`, cur.`Date`)) AS `DaysPassed`
FROM
MyTable cur
LEFT JOIN
MyTable prv
ON cur.`Customer` = prv.`Customer` AND cur.`Order` = prv.`Order`+ 1;
If the Order column is not numbered sequentially, but next Order value is greater than previous, then you could use greater than or less than operators. Use GROUP BY clause and an aggregate function to return single row for each order. Note, maybe it will be long!
SELECT
comb.`Customer` AS `Customer`,
comb.`curOrder` AS `Order`,
comb.`curDate` AS `Date`,
DATEDIFF(comb.`curDate`, IFNULL(pr.`Date`, comb.`curDate`)) AS `DaysPassed`
FROM
(SELECT
cur.`Customer` AS `Customer`, cur.`Order` AS curOrder, cur.`curDate` AS `Date`, max(prv.`Order`) AS `prvOrder`
FROM
MyTable cur
LEFT JOIN
MyTable prv
ON cur.`Customer` = prv.`Customer` AND cur.`Order` > prv.`Order`
GROUP BY cur.`Order`, cur.`Customer`) comb
LEFT JOIN
MyTable pr
ON pr.`Customer` = comb.`Customer` AND pr.`Order` = comb.prvOrder;
If you use random order number, then it is possible to use Date column instead of Order in the comb subquery to join records by nearest order dates of same customer.
Good luck!
I need to implement a function which returns all the networks the installation is not part of.
Following is my table and for example if my installation id is 1 and I need all the network ids where the installation is not part of then the result will be only [9].
network_id | installation_id
-------------------------------
1 | 1
3 | 1
2 | 1
2 | 2
9 | 2
2 | 3
I know this could be solved with a join query but I'm not sure how to implement it for the same table. This is what I've tried so far.
select * from network_installations where installation_id = 1;
network_id | installation_id
-------------------------------
1 | 1
2 | 1
3 | 1
select * from network_installations where installation_id != 1;
network_id | installation_id
-------------------------------
9 | 2
2 | 2
2 | 3
The intersection of the two tables will result the expected answer, i.e. [9]. But though we have union, intersect is not present in mysql. A solution to find the intersection of the above two queries or a tip to implement it with a single query using join will be much appreciated.
The best way to do this is to use a network table (which I presume exists):
select n.*
from network n
where not exists (select 1
from network_installation ni
where ni.network_id = n.network_id and
ni.installation_id = 1
);
If, somehow, you don't have a network table, you can replace the from clause with:
from (select distinct network_id from network_installation) n
EDIT:
You can do this in a single query with no subqueries, but a join is superfluous. Just use group by:
select ni.network_id
from network_installation ni
group by ni.network_id
having sum(ni.installation_id = 1) = 0;
The having clause counts the number of matches for the given installation for each network id. The = 0 is saying that there are none.
Another solution using OUTER JOIN:
SELECT t1.network_id, t1.installation_id, t2.network_id, t2.installation_id
FROM tab t1 LEFT JOIN tab t2
ON t1.network_id = t2.network_id AND t2.installation_id = 1
WHERE t2.network_id IS NULL
You can check at http://www.sqlfiddle.com/#!9/4798d/2
select *
from network_installations
where network_id in
(select network_id
from network_installations
where installation_id = 1
group by network_id )
I have the below code which works:
SELECT admin_teams.name,
SUM(temp_orders.amount_paid) as amount,
SUM(instalments.amount) as amount2
FROM temp_orders
LEFT JOIN admin_teams
ON admin_teams.id = temp_orders.team
LEFT JOIN instalments
ON instalments.order_id = temp_orders.order_id
WHERE
(DATE(temp_orders.date_paid) = CURDATE()
OR DATE(instalments.date_paid) = CURDATE())
AND (temp_orders.pay_status = 4
OR instalments.pay_status = 4)
GROUP BY temp_orders.team
ORDER BY temp_orders.team ASC
LIMIT 5
It produces a table that looks like:
+-------------+--------+---------+
| name | amount | amount2 |
+-------------+--------+---------+
| team name 1 | 100 | 150 |
| team name 2 | 200 | 250 |
| team name 3 | 300 | 175 |
+-------------+--------+---------+
I have two issues;
I actually only want one column which is the sum of amount and amount2.
The query is VERY slow - this took 190 sec to run.
I did have it almost working with a Union which was almost instant - I couldn't however get it fully working because the number of columns in my first select statement will not match those in the second - the table 'instalments' does not have a team column but the table temp_orders does.
Can anyone help with either problem?
Thanks.
SELECT admin_teams.name,
(SUM(temp_orders.amount_paid) + SUM(instalments.amount)) as amount,
FROM temp_orders
LEFT JOIN admin_teams
ON admin_teams.id = temp_orders.team
LEFT JOIN instalments
ON instalments.order_id = temp_orders.order_id
WHERE
temp_orders.date_paid >= CURDATE()
OR instalments.date_paid >= CURDATE())
AND (temp_orders.pay_status = 4
OR instalments.pay_status = 4)
GROUP BY temp_orders.team
ORDER BY temp_orders.team ASC
LIMIT 5
And add these indexes
ALTER TABLE temp_orders ADD KEY (date_paid ,pay_status,team);
ALTER TABLE instalments ADD KEY (date_paid ,pay_status);
I have a table of lists and a table of list items. I want to formulate a query to select just one list item for each item in the lists table. Here's some simple data to illustrate my question:
'lists' table
id updated share
--- ---------- -----
1 2013-07-11 1
2 2013-07-13 0
3 2013-07-15 1
4 2013-07-14 0
5 2013-07-14 1
'list_items' table
id l_id description sort likes
-- ---- ----------- ---- -----
1 1 hello 0 3
2 1 goodbye 0 0
3 1 thanks 0 4
4 2 ok 0 0
5 3 love 0 2
6 3 hate 1 1
7 4 celebrate 0 0
8 5 party 0 1
9 5 summer 1 5
10 5 winter 2 2
Now say I want to get the first item from each shared list (share = 1). By first I mean if the list items were sorted by 'sort'.
The expected result based on the above data would be:
lists.id id l_id description sort likes
-------- -- ---- ----------- ---- -----
1 1 1 hello 0 3
3 5 3 love 0 2
5 8 5 party 0 1
Update:
I struggled to get my head around the solutions provided by peterm and hims056 and while kayla's solution looked more like something I could follow it didn't return the correct results. Taking ideas from these solutions I had a crack at it myself and came up with
SELECT * FROM (
SELECT lists.id AS listid, lists.share, list_items.*
FROM list_items, lists
WHERE lists.id = l_id
AND lists.share = 1
ORDER BY sort) q
GROUP BY q.listid
This seems to work but as peterm points out, the values for the columns in select clause that are not part of group by clause may be ambiguous.
I though someone would come up with a solution using LIMIT as that was the way I was thinking about doing it first. You can return the list ids which allow sharing simply by:
SELECT lists.id FROM lists WHERE share = 1
and for a given list id you can return the top list item by:
SELECT lists.id AS listid, lists.share, list_items.*
FROM list_items, lists
WHERE lists.id = l_id
AND lists.id = 1
ORDER BY sort
LIMIT 1
But is there a way to put these 2 statements together to return top list item for each list that allows sharing?
SELECT lists.id, list_items.id, l_id, description, sort, likes
FROM (SELECT * FROM lists WHERE share = 1) lists
LEFT JOIN (SELECT * FROM list_items GROUP BY l_id) list_items
ON lists.id = l_id
UPDATED To ensure getting first per group with order by sort try
SELECT q.l_id list_id, q.id, i.description, i.sort, i.likes
FROM
(
SELECT l_id, id, #n := IF(#g = l_id, #n + 1, 1) n, #g := l_id g
FROM
(
SELECT i.l_id, i.id
FROM list_items i JOIN lists l
ON i.l_id = l.id
WHERE l.share = 1
ORDER BY l_id, sort, id
) b CROSS JOIN (SELECT #n := 0, #g := 0) a
HAVING n = 1
) q JOIN list_items i
ON q.id = i.id
Sample output:
| LIST_ID | ID | DESCRIPTION | SORT | LIKES |
---------------------------------------------
| 1 | 1 | hello | 0 | 3 |
| 3 | 5 | love | 0 | 2 |
| 5 | 8 | party | 0 | 1 |
Here is SQLFiddle demo
Since you want to get minimum list_items.id sorted by list_items.sort you need to perform double nested query like this:
SELECT tbl.l_id list_id, tbl.minID, li.description, li.sort, li.likes
FROM list_items li
JOIN
(
SELECT l.l_id,MIN(l.id) minID FROM list_items l
JOIN
(
SELECT li.l_id,MIN(li.sort) sort FROM list_items li
JOIN lists l ON li.l_id = l.id WHERE l.share = 1
GROUP BY li.l_id
) l2
ON l.l_id = l2.l_id
AND l.sort = l2.sort
GROUP BY l.l_id
) tbl
ON li.id = tbl.minID;
See this SQLFiddle
See this SQLFiddle with different values.
I have a table called user_scores as below:
id | af_id | uid | level | record_date
----------------------------------------
1 | 1.1 | 1 | 3 | 2012-01-01
2 | 1.1 | 1 | 4 | 2012-02-01
3 | 1.2 | 1 | 3 | 2012-01-01
4 | 1.2 | 1 | 5 | 2012-03-01
...
I have another table call user_info as below:
uid | forename | surname | gender
-----------------------------------
1 | Homer | Simpson | M
2 | Marge | Simpson | F
3 | Bart | Simpson | M
4 | Lisa | Simpson | F
...
In user scores uid is the user id of a registered user on the system, af_id identifies a particular test a user submits. A user scores a level between 1 - 5 for each test, which can be submitted every month.
My problem is I need to produce an analysis at the end of the year to COUNT the number of users that have achieved each level for a particular test. The analysis is to show a gender split for male and female.
So for example an administrator would select test 1.1 and the system would generate stats based that would COUNT of the total MAX level achieved by each user in the year, with a gender split.
Any help is much appreciated. Thank you in advance.
-
I think I need to clarify myself a bit. Because a user can complete the test multiple times throughout the year, there will be multiple scores for the same test. The query should take the highest level achieved and include this in the count. An example result would be:
Male Results:
level1 | level2 | level3 | level4 | level5
------------------------------------------
2 | 5 | 10 | 8 | 1
I am not certain I get exactly what you mean, but as always I'll have a go. As I understand it you want to know how many people from each gender reached each level in a certain year.
SELECT MaxLevel,
COUNT(CASE WHEN ui.Gender = 'M' THEN 1 END) AS Males,
COUNT(CASE WHEN ui.Gender = 'F' THEN 1 END) AS Females
FROM User_Info ui
INNER JOIN
( SELECT MAX(Level) AS MaxLevel,
UID
FROM User_Scores us
WHERE af_ID = '1.1'
AND YEAR(Record_Date) = 2012
GROUP BY UID
) AS MaxUs
ON MaxUs.uid = ui.UID
GROUP BY MaxLevel
I've put some sample data on SQL Fiddle so you see if it is what you were after.
EDIT
To transpose the data so levels are along the top and Gender in the rows the following will work:
SELECT Gender,
COUNT(CASE WHEN MaxLevel = 1 THEN 1 END) AS Level1,
COUNT(CASE WHEN MaxLevel = 2 THEN 1 END) AS Level2,
COUNT(CASE WHEN MaxLevel = 3 THEN 1 END) AS Level3,
COUNT(CASE WHEN MaxLevel = 4 THEN 1 END) AS Level4,
COUNT(CASE WHEN MaxLevel = 5 THEN 1 END) AS Level5
FROM User_Info ui
INNER JOIN
( SELECT MAX(Level) AS MaxLevel,
UID
FROM User_Scores us
WHERE af_ID = '1.1'
AND YEAR(Record_Date) = 2012
GROUP BY UID
) AS MaxUs
ON MaxUs.uid = ui.UID
GROUP BY Gender
Note, that if there are ever more than 5 levels you will need to add more to the select statement, or start building dynamic SQL.
Assuming record_date holds only dates (without time parts):
SELECT
s.maxlevel,
COUNT(NULLIF(gender, 'F')) AS M,
COUNT(NULLIF(gender, 'M')) AS F
FROM user_info u
INNER JOIN (
SELECT
uid,
MAX(level) AS maxlevel
FROM user_scores
WHERE record_date > DATE_SUB(CURDATE(), INTERVAL DAYOFYEAR(CURDATE()) DAY)
AND af_id = '1.1'
GROUP BY
uid
) s ON s.uid = u.uid
GROUP BY
s.maxlevel
That will show you only the maximum levels found in the user_scores table. If you have a Levels table where all possible levels (1 to 5) are listed, you could use that table to get a complete list of levels. If some levels are not present in the requested subset of data, the corresponding rows will show 0s in both columns.
Here's the above script with minor changes to show the complete chart of levels:
SELECT
l.level AS maxlevel,
COUNT(NULLIF(gender, 'F')) AS M,
COUNT(NULLIF(gender, 'M')) AS F
FROM user_info u
INNER JOIN (
SELECT
uid, MAX(level) AS maxlevel
FROM user_scores
WHERE record_date > DATE_SUB(CURDATE(), INTERVAL DAYOFYEAR(CURDATE()) DAY)
AND af_id = '1.1'
GROUP BY
uid
) s ON s.uid = u.uid
RIGHT JOIN Levels l ON s.maxlevel = l.level
GROUP BY
l.level
Hope this is what your looking for!
Show number of records group by userid and gender of the max score for af_id '1.1'.
select count(*), info.uid, info.gender, max(score.level)
from user_info as info
join user_scores as score
on info.uid = score.uid
where score.af_id = '1.1'
group by info.uid, info.gender;
EDITED based on your edit.
select sum(if(a.gender="M",1,0)) Male_users, sum(if(a.gender="F",1,0)) Female_users
from myTable a where
a.level = (select max(b.level) from myTable b where a.uid=b.uid)
group by af_id.
I typed this in a rush. But it should work or at least get you where you need to go. E.G. if you need to specify time frame, add that.
You need something like
SELECT
uid,
MAX(level)
WHERE
record_date BETWEEN '2012-01-01' AND '2012-12-31'
AND af_id='1.1'
GROUP BY uid
If you need the gender splits then depending on what stat you need per gender you can either add a JOIN on the user_info table into this query (to get the MAX per gender) to wrap this as a sub-query and JOIN on the whole thing.