I'm facing MySQL query problem with ordering grouped rows.
Tables:
statuses (
id int primary key auto_increment,
name varchar(30)
);
orders (
id int primary key auto_increment,
order_code varchar(10),
order_date timestamp default current_timestamp,
user_id int -- fk to users
);
orders_statuses (
id int primary key auto_increment,
status_changed timestamp default current_timestamp,
order_id int, -- fk to orders(id)
status_id int -- fk to orders(id)
);
Now, I need to join them in ONE query (this is important due to database object limitation with my PHP software - no sub-queries allowed, not even union) and get all possible orders, but only the latest status change as current status.
Statuses from status table may be inserted randomly - there is no way to use max function on status_id field where order_id (currently id 1 is new, 2 is canceled, 4 is paid etc.).
Grouping by order_id (id in orders or order_code) always returns first available status inserted into orders_statuses, even if ordered by date.
I've also tried using having status_changed=max(status_changed), but it won't work.
So my question is:
How should I group them by order_id to get last, not first status name available for each row?
// edit
Here is the code I'm trying to move from my Workbench to PHP DB Object:
select
(select l.name from statuses l where l.id = (select t.status_id from orders_statuses t where t.order_id = oh.id order by t.status_changed desc limit 1)) as status,
oh.order_code,
oh.id,
oh.order_total,
su.name as order_user,
oh.order_date as ordered,
max(os.status_changed) as updated,
oh.id as uid
from orders oh
left join orders_statuses os on (oh.id = os.order_id)
left join users su on (oh.shop_user_id = su.id)
group by
oh.id
order by
oh.order_date desc,
os.created desc
So basically I need simpler query to understand it and put it into $this->db->use('table', on condition)->...->select(array(fields))
// edit
After hours of thinking I can tell I'm lacking know-how. This problem can be solved by using
SELECT
status_id,
created,
order_id
FROM
orders_statuses
GROUP BY
order_id,
status_id
ORDER BY
created DESC;
and transforming it to return just one status_id for each order_id with max created within single order_id group - this is where I need your help.
Related
I need to make a query, where there are columns of client's names and their orders per month.
Some clients don't have orders at some months and there fields must have 0.
The problem is, when i use WHERE and OUTER JOIN (no matter which one) at one query*, nessesary zero`s cutteed by WHERE. So how can i solve that?
Descripton of tables are pinned.
SELECT name
, ordering.id_client
, COUNT(order_date)
FROM ordering
RIGHT
OUTER
JOIN client
ON client.id_client = ordering.id_client
WHERE month(order_date) = 1
GROUP
BY name;
**Descripton**: (https://i.imgur.com/TrUGOLW.png)
**Example of my query** (there are 6 notes about clients at my db, showed only 4 of 6):
(https://i.imgur.com/ABP6pP0.png)
**MRE stuff**
Client: create table client(id_client int primary key auto_increment, name var char(50), passport_code int, addr varchar(70));
insert into client values(null, 'Penny Anderson', 6485, 'New Orlean');
Ordering: create table ordering(id_order int primary key auto_increment, id_client int, order_date date, foreign key(id_client) references client(id_client));
insert into ordering values(null, 1, date('2020-05-01'));
Try a simple left join starting from client's table
SELECT client.name
, client.id_client
, COUNT(order_date)
FROM client
LEFT JOIN ordering ON client.id_client = ordering.id_client
AND month(ordering.order_date) = 1
GROUP BY client.id_client;
If the condition for join is related to the left joined table then add this condition in the related ON clause and not in where otherwise this work as an inner join
I have one table called 'vacancies' which has a 'vacancy_id' PK. It looks like this:
create table vacancies
(
vacancy_id int not null auto_increment,
org_id int not null,
name varchar(255) not null comment 'title',
vacancy_visibility_start_date datetime comment 'vacancy visibility date, when it needs to be active on the website',
vacancy_visibility_end_date datetime,
primary key (vacancy_id)
);
Following this I have a couple of other tables which are linked to this one.
create table vacancy_calendar
(
vacancy_calendar_id int not null auto_increment,
vacancy_id int,
date_from datetime not null,
date_to datetime not null,
primary key (vacancy_calendar_id)
);
create table vacancy_interests
(
vacancy_id int,
interest_id int
);
create table vacancy_skills
(
vacancy_id int,
skill_id int
);
All of these tables can contain multiple rows for the same vacancy_id.
My page has different filters which I want to process via AJAX.
I want to have one line per vacancy containing all data I need + it has to match my filtering criteria. However I am not sure how my query has to look like in order to retrieve the result I am looking for.
It is possible to filter on 'interest_id' , 'skill_id', 'date_from' and 'date_to'.
I started with the following query but I am stuck very fast:
SELECT v.*, vi.interest_id
FROM `vacancies` as v
INNER JOIN `vacancy_interests` as vi on v.vacancy_id = vi.vacancy_id
GROUP BY v.vacancy_id
This query will only return me 1 interest_id for a vacancy, even if the vacancy has 3 interest_id rows in the vacancy_interest table. If I remove the GROUP BY statement I will get 3 rows for the same vacancy which is not what I want either.
Ideally I would want the interest_id's to be each in a separate column or in the same field separated by comma's. Or if there are any other possibilities/suggestions feel free to share!
You can use group_concat for get interest_id separated by comma
SELECT v.*, group_concat(vi.interest_id)
FROM `vacancies` as v
INNER JOIN `vacancy_interests` as vi on v.vacancy_id = vi.vacancy_id
GROUP BY v.vacancy_id
Referring to you comment about add where eg:
You can add where condition
SELECT v.*, group_concat(vi.interest_id)
FROM `vacancies` as v
INNER JOIN `vacancy_interests` as vi on v.vacancy_id = vi.vacancy_id
INNER JOIN `vacancy_skills` as vs ON vs.vacancy_id = v.vacancy_id
WHERE vs.skill_id IN (4) AND vi.interest_id IN (1,3)
GROUP BY v.vacancy_id
In this case the gorup_concat is applied on the resulting rows .. because group by perform the related action on the selected resulting rows .
I need to create a sql that contains a list of users, and for each user the number they have
reviewed.
I tried this, but it didnt give the desired output because i didnt know how to work the SUM into it.
SELECT review.revID, reviewer.name FROM review , reviewer WHERE review.revID = reviewer.revID
Any assistance would be apprectiated
here are my tables
CREATE TABLE reviewer (
revID INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(40),
email VARCHAR(40),
password VARCHAR(125)
);
CREATE TABLE movie (
movID INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
title VARCHAR(30),
release_date date
);
CREATE TABLE review (
revID INT,
movID INT,
rating INT CHECK (rating > 0 AND rating < 10) ,
review_date datetime(6),
comment VARCHAR (300),
helpful INT,
PRIMARY KEY (revID,movID),
FOREIGN KEY (revID)
REFERENCES reviewer(revID),
FOREIGN KEY (movID)
REFERENCES movie(movID)
);
Try this query:
SELECT
count(movId) AS Total_Movie_Count,
r.name AS Reviewer_Name,
r.revId as Reviewer_Id
FROM review
INNER JOIN reviewer r on r.revId = review.revId
GROUP BY review.revId
You have to use GROUP BY for aggregated column movId. And to get the reviewer name, you have to INNER JOIN with revId.
Select count(*) as no, revId from review
Group by revId
You can use the above query
Try to use explicit join syntax (e.g. inner join, left join, cross join). Because the relationships among different tables are better explained using explicit join syntax.
SELECT
review.revID,
reviewer.name,
SUM(rating) As totalReview
FROM review
INNER JOIN reviewer ON review.revID = reviewer.revID
GROUP BY reviewer.revID;
Note: You need to use GROUP BY revID in order to get output for each reviewer.
And using aggregate function SUM along with group by will provide total rating / review for each reviewer.
Btw, MYSQL doesn't support CHECK constraint.
I am currently working with two tables.
status:
id INT(4) AUTO_INCREMENT
username VARCHAR(20)
text LONGTEXT
datetime VARCHAR(25)
attachment VARCHAR(11)
timestamp VARCHAR(50)
friends:
id INT(2) AUTO_INCREMENT
added INT(1)
userto VARCHAR(32)
userfrom VARCHAR(32)
I would like to add the option for a user to filter statuses for only their friend's statuses, and display them with the newest one first. By this I mean most recent status, not newest per friend, which I suspect can be done with:
ORDER BY status.id DESC
How would I order the statuses based on the statuses of the users on the person's friends list?
Well, without any sample data it would be hard to do this for sure, but I would walk through it this way.
The statuses we want to show (regardless of order) are those the user is friends with. So, let's get the friends of Adam for example:
SELECT DISTINCT userto
FROM friends
WHERE userfrom = 'Adam'
UNION
SELECT DISTINCT userfrom
FROM friends
WHERE userto = 'Adam';
At this moment, I should point out that in your friends table the usernames are VARCHAR(32) and in the status table they are VARCHAR(20), I would assume they should be the same.
So, now you can filter the status based on whether or not the username is in the above subquery, and you could order by id descending, assuming you add them in order, but the best way would be to order by the timestamp on the status:
SELECT *
FROM status
WHERE username IN
(SELECT DISTINCT userto
FROM friends
WHERE userfrom = 'Adam'
UNION
SELECT DISTINCT userfrom
FROM friends
WHERE userto = 'Adam')
ORDER BY datetime DESC;
EDIT
I would also rethink your variables for datetime and timestamp, as well as rethink the name. While these are valid, they are also reserved words (DATETIME and TIMESTAMP are data types in MySQL). A possible reconstruction of your status table could be:
id INT(4) AUTO_INCREMENT
username VARCHAR(32)
statusText LONGTEXT
statusPostDate DATETIME
attachment VARCHAR(11)
The DATETIME variable will hold both the date and time portions for you.
Try this
SELECT status.*
FROM status
JOIN friends
ON status.username = friends.userto
WHERE friends.userfrom = '$username'
UNION
SELECT status.*
FROM status
JOIN friends
ON status.username = friends.userfrom
WHERE friends.userto = '$username'
ORDER BY status.id DESC;
this query loads statuses from usernames on either end of the friendship relationship that includes $username. Union is used to glue two lists top to bottom. Join is used to glue two lists side by side and align them on the author's username.
I have a table that shows work tickets, basically like this:
TABLE "ticket"
ticket_id INT UNSIGNED AUTO_INCREMENT
created TIMESTAMP DEFAULT CURRENT_TIMESTAMP
ticket_name VARCHAR(64)
Then I have a table showing the possible list of status codes for a ticket:
TABLE "status_ticket"
status_id INT UNSIGNED AUTO_INCREMENT
created TIMESTAMP DEFAULT CURRENT_TIMESTAMP
status_name VARCHAR(64)
And I have one more table that logs the history of the statuses that a ticket has or has had:
TABLE "xref_ticket_status"
xref_id INT UNSIGNED AUTO_INCREMENT
created TIMESTAMP DEFAULT CURRENT_TIMESTAMP
ticket_id INT UNSIGNED NOT NULL
status_id INT UNSIGNED NOT NULL
When the ticket is created, an entry is made in the ticket table for the ticket, and then an entry is made in the xref_ticket_status table assigning a status to the ticket by linking the ticket table to the status_ticket table. When the status changes, a new entry is made in the xref_ticket_status table to reflect the new status. This way, I have a history of each status that a ticket has had and when it was assigned. The most recent entry for any given ticket_id in the xref_ticket_status table is the ticket's current status.
I'm not sure how I would join these three tables together to get a ticket's current status. Essentially, I want to join the ticket table with the xref_ticket_status table where ticket_id matches but for the newest created column in the xref_ticket_status.
Thanks!
This should do the trick:
select ti.ticket_id,ti.ticket_name,
xr.created, st.status_name
from ticket ti
inner join xref_ticket_status xr
on(ti.ticket_id = xr.ticket_id)
inner join status_ticket st
on(xr.status_id = st.status_id)
group by ti.ticket
order by xr.xref_id desc;
===============Update==================
select * from
(select ti.ticket_id,ti.ticket_name,
xr.created, st.status_name
from ticket ti
inner join xref_ticket_status xr
on(ti.ticket_id = xr.ticket_id)
inner join status_ticket st
on(xr.status_id = st.status_id)
group by ti.ticket
order by xr.xref_id desc)cs
order by cs.ticket_name desc; //or asc
The following gets the most recent status_id and ticket_id from xref_ticket_status:
select xts.*
from xref_ticket_status xts
where not exists (select 1
from xref_ticket_status xts2
where xts2.ticket_id = xts.ticket_id and
xts2.created > xts.created
);
The logic is: "Get me all rows from xref_ticket_status where there is no row with the same ticket id and a more recent created date". The would be the last date for each ticket. And note that you can also use xref_id for the comparison rather than the date.
The rest is just joining the tables together and choosing your desired columns:
select t.*, ts.*, xts.created
from xref_ticket_status xts join
ticket t
on xts.ticket_id = t.ticket_id join
ticket_status ts
on xts.status_id = ts.status_id
where not exists (select 1
from xref_ticket_status xts2
where xts2.ticket_id = xts.ticket_id and
xts2.created > xts.created
);