How can those two SQL statements be combined into one? - mysql

I wrote and would like to combine these 2 sql, one is based on results of another. I checked this post, but looks like its not results based. How could I achieve it ?
First sql:
SELECT
`potential`.*,
`customer`.`ID` as 'FID_customer'
FROM
`os_potential` as `potential`,
`os_customer` as `customer`
WHERE `potential`.`FID_author` = :randomID
AND `potential`.`converted` = 1
AND `potential`.`street` = `customer`.`street`
AND `potential`.`zip` = `customer`.`zip`
AND `potential`.`city` = `customer`.`city`;
Second sql:
SELECT
sum(`order`.`price_customer`) as 'Summe'
FROM
`os_order` as `order`,
`RESUTS_FROM_PREVIOUS_SQL_STATEMENT` as `results`
WHERE `order`.`FID_status` = 10
AND `results`.`FID_customer` = `order`.`FID_customer`;
I would like to get everything from first sql + the 'Summe' from second sql.
TABLES
1.Potentials:
+----+------------+-----------+--------+-----+------+
| ID | FID_author | converted | street | zip | city |
+----+------------+-----------+--------+-----+------+
2.Customers:
+----+--------+-----+------+
| ID | street | zip | city |
+----+--------+-----+------+
3.Orders:
+----+--------------+----------------+
| ID | FID_customer | price_customer |
+----+--------------+----------------+

SELECT p.*
, c.ID FID_customer
, o.summe
FROM os_potential p
JOIN os_customer c
ON c.street = p.street
AND c.zip = p.zip
AND c.city = p.city
JOIN
( SELECT FID_customer
, SUM(price_customer) Summe
FROM os_order
WHERE FID_status = 10
GROUP
BY FID_customer
) o
ON o.FID_customer = c.ID
WHERE p.FID_author = :randomID
AND p.converted = 1
;

You would just write a single query like this:
SELECT sum(o.price_customer) as Summe
FROM os_order o JOIN
os_potential p JOIN
os_customer c
ON p.street = c.street AND p.zip = c.zip AND p.city = c.city JOIN
os_order o2
ON o2.FID_customer = c.FID_customer
WHERE p.FID_author = :randomID AND p.converted = 1 AND
o2.FID_status = 10 ;
Notes:
Never use commas in the FROM clause. Always use explicit JOIN syntax with conditions in an ON clause.
Table aliases are easier to follow when they are short. Abbreviations for the table names is commonly used.
Backticks are only necessary when the table/column name needs to be escaped. Yours don't need to be escaped.

If the 1st query return 1 record per customer, then just simply join the 3 tables, keep the sum and use the group by clause:
SELECT
`potential`.*,
`customer`.`ID` as 'FID_customer',
sum(`order`.`price_customer`) as Summe
FROM
`os_potential` as `potential`
INNER JOIN
`os_customer` as `customer`
ON `potential`.`street` = `customer`.`street`
AND `potential`.`zip` = `customer`.`zip`
AND `potential`.`city` = `customer`.`city`
LEFT JOIN
`os_order` as `order`
ON `results`.`FID_customer` = `order`.`FID_customer`
AND `order`.`FID_status` = 10
WHERE `potential`.`FID_author` = :randomID
AND `potential`.`converted` = 1
GROUP BY `customer`.`ID`, <list all fields from potential table>
If the 1st query may return multiple records per customer, then you need to do the summing in a subquery:
SELECT
`potential`.*,
`customer`.`ID` as 'FID_customer',
`order`.Summe
FROM
`os_potential` as `potential`
INNER JOIN
`os_customer` as `customer`
ON `potential`.`street` = `customer`.`street`
AND `potential`.`zip` = `customer`.`zip`
AND `potential`.`city` = `customer`.`city`
LEFT JOIN
(SELECT FID_customer, sum(price_customer) as Summe
FROM `os_order`
WHERE FID_status=10
GROUP BY FID_customer
) as `order`
ON `results`.`FID_customer` = `order`.`FID_customer`
WHERE `potential`.`FID_author` = :randomID
AND `potential`.`converted` = 1

I think you should use a subselect, but be careful with the number of results, it's not the best for performance.
You can do something like this:
SELECT n1, n2, (select count(1) from whatever_table) as n3, n4 from whatever_table
note that the subselect must return just 1 result, in other case you'll have an error

Related

DISTINCT on one value from a group selects

I have following sql query
select devices_device.id , devices_device.code, sss.id as "site_id", sss.name as "site_name"
from devices_device
inner join st_site_site sss on devices_device.site_id = sss.id
where devices_device.deleted = false
order by devices_device.id, devices_device.start_date
I now get a list of device id's. Some of them are the same. I want to do a distinct so I only keep the first record for every device (and due to order by on start_date that would be the most recent device record for that device)
How do I do this? If I do
select distinct devices_device.id , devices_device.code, sss.id as "site_id", sss.name as "site_name"
from devices_device
inner join st_site_site sss on devices_device.site_id = sss.id
where devices_device.deleted = false
order by devices_device.id, devices_device.start_date
nothing happens
You can use the ROW_NUMBER() window function to identify the row you want. Then filtering out the other ones is easy.
For example:
select *
from (
select
d.id, d.start_date, d.code,
s.id as "site_id", s.name as "site_name",
row_number() over(partition by d.id order by start_date desc) as rn
from devices_device d
inner join st_site_site s on d.site_id = s.id
where d.deleted = false
) x
where rn = 1
order by id, start_date
In this query the ROW_NUMBER() value will be 1 for the latest row in each device group. That's how the filtering at the end removes all other rows greater than 1.
NOTE: In case there are collisions (two rows with the same recent start_date) this query will always return a single [though random] row between them.
You should probably use a GROUP BY. Something like:
select distinct devices_device.id , devices_device.code, sss.id as "site_id",
sss.name as "site_name"
from devices_device
inner join st_site_site sss on devices_device.site_id = sss.id
where devices_device.deleted = false
group by devices_device.id
order by devices_device.start_date
You could test for the min start date
drop table if exists devices_device,st_site_site;
create table devices_device(id int,code int,site_id int,start_date date,deleted int);
create table st_site_site(id int,name varchar(10));
insert into devices_device values(1,10,1,'2020-10-01',0),(1,20,1,'2020-09-01',0);
insert into st_site_site values(1,'aaa');
select devices_device.id , devices_device.code, sss.id as "site_id", sss.name as "site_name"
from devices_device
inner join st_site_site sss on devices_device.site_id = sss.id
where devices_device.deleted = false and
devices_device.start_date = (select min(d1.start_date) from devices_device d1 where d1.id = devices_device.id)
order by devices_device.id;
+------+------+---------+-----------+
| id | code | site_id | site_name |
+------+------+---------+-----------+
| 1 | 20 | 1 | aaa |
+------+------+---------+-----------+
1 row in set (0.001 sec)

I want to apply an INNER JOIN and use WHERE condition using 3 tables

SELECT
*
FROM
retailer
WHERE
states = 1
AND newsletter.status = 1
AND (
company_id IN (
SELECT
id
FROM
retailer
WHERE
muli_ret_id = 0
)
)
ORDER BY
email
I'm not sure that your exact current query can be expressee using an inner join instead of the where clause. You might try:
SELECT DISTINCT r1.*
FROM retailer r1
INNER JOIN retailer r2
ON r1.company_id = r2.id
WHERE
r1.states = 1 AND
r1.newsletter.status = 1 AND
r2.muli_ret_id = 0
ORDER BY
r1.email;
I use select distinct to remove possible duplicates which could arise from the self join. But honestly, you current approach is fine, though I might use exists here:
SELECT r1.*
FROM retailer r1
WHERE
r1.states = 1 AND
r1.newsletter.status = 1 AND
EXISTS (SELECT 1 FROM retailer r2
WHERE r2.id = r1.company_id AND r2.muli_ret_id = 0)
ORDER BY
r1.email;

How can I optimize my sql code?

I have following tables
contacts
contact_id | contact_slug | contact_first_name | contact_email | contact_date_added | company_id | contact_is_active | contact_subscribed | contact_last_name | contact_company | contact_twitter
contact_campaigns
contact_campaign_id | contact_id | contact_campaign_created | company_id | contact_campaign_sent
bundle_feedback
bundle_feedback_id | bundle_id, contact_id | company_id | bundle_feedback_rating | bundle_feedback_favorite_track_id | bundle_feedback_supporting | campaign_id
bundles
bundle_id | bundle_name | bundle_created | company_id | bundle_is_active
tracks
track_id | company_id | track_title
I wrote this query, but it works slowly, how can I optimize this query to make it faster ?
SELECT SQL_CALC_FOUND_ROWS c.contact_id,
c.contact_first_name,
c.contact_last_name,
c.contact_email,
c.contact_date_added,
c.contact_company,
c.contact_twitter,
concat(c.contact_first_name," ", c.contact_last_name) AS fullname,
c.contact_subscribed,
ifnull(icc.sendCampaignsCount, 0) AS sendCampaignsCount,
ifnull(round((ibf.countfeedbacks/sendCampaignsCount * 100),2), 0) AS percentFeedback,
ifnull(ibf.bundle_feedback_supporting, 0) AS feedbackSupporting
FROM contacts AS c
LEFT JOIN
(SELECT c.contact_id,
count(cc.contact_campaign_id) AS sendCampaignsCount
FROM contacts AS c
LEFT JOIN contact_campaigns AS cc ON cc.contact_id = c.contact_id
WHERE c.company_id = '876'
AND c.contact_is_active = '1'
AND cc.contact_campaign_sent = '1'
GROUP BY c.contact_id) AS icc ON icc.contact_id = c.contact_id
LEFT JOIN
(SELECT bf.contact_id,
count(*) AS countfeedbacks,
bf.bundle_feedback_supporting
FROM bundle_feedback bf
JOIN bundles b
JOIN contacts c
LEFT JOIN tracks t ON bf.bundle_feedback_favorite_track_id = t.track_id
WHERE bf.bundle_id = b.bundle_id
AND bf.contact_id = c.contact_id
AND bf.company_id='876'
GROUP BY bf.contact_id) AS ibf ON ibf.contact_id = c.contact_id
WHERE c.company_id = '876'
AND contact_is_active = '1'
ORDER BY percentFeedback DESC LIMIT 0, 25;
I have done 2 improvements
1) Removed the contacts which is getting joined unnecessarily twice and put the condition at the final where condition.
2) Removed as per SQL_CALC_FOUND_ROWS
Which is fastest? SELECT SQL_CALC_FOUND_ROWS FROM `table`, or SELECT COUNT(*)
SELECT c.contact_id,
c.contact_first_name,
c.contact_last_name,
c.contact_email,
c.contact_date_added,
c.contact_company,
c.contact_twitter,
concat(c.contact_first_name," ", c.contact_last_name) AS fullname,
c.contact_subscribed,
ifnull(icc.sendCampaignsCount, 0) AS sendCampaignsCount,
ifnull(round((ibf.countfeedbacks/sendCampaignsCount * 100),2), 0) AS percentFeedback,
ifnull(ibf.bundle_feedback_supporting, 0) AS feedbackSupporting
FROM contacts AS c
LEFT JOIN
(SELECT cc.contact_id,
count(cc.contact_campaign_id) AS sendCampaignsCount
FROM contact_campaigns
WHERE cc.contact_campaign_sent = '1'
GROUP BY cc.contact_id) AS icc ON icc.contact_id = c.contact_id
LEFT JOIN
(SELECT bf.contact_id,
count(*) AS countfeedbacks,
bf.bundle_feedback_supporting
FROM bundle_feedback bf
JOIN bundles b
LEFT JOIN tracks t ON bf.bundle_feedback_favorite_track_id = t.track_id
WHERE bf.bundle_id = b.bundle_id
GROUP BY bf.contact_id) AS ibf ON ibf.contact_id = c.contact_id
WHERE c.company_id = '876' and c.contact_is_active = '1'
First, you are not identifying any indexes you have to optimize the query. That said, I would ensure you have at least the following composite / covering indexes.
table index
contacts ( company_id, contact_is_active )
contact_campaigns ( contact_id, contact_campaign_sent )
bundle_feedback ( contact_id, bundle_feedback_supporting )
Next, as noted in other answer, unless you really need how many rows qualified, remove the "SQL_CALC_FOUND_ROWS".
In your first left-join (icc), you do a left-join on contact_campaigns (cc), but then throw into your WHERE clause an "AND cc.contact_campaign_sent = '1'" which turns that into an INNER JOIN. At the outer query level, these would result in no matching record and thus NULL for your percentage calculations.
In your second left-join (ibf), you are doing a join to the tracks table, but not utilizing anything from it. Also, you are joining to the bundles table but not using anything from there either -- unless you are getting multiple rows in the bundles and tracks tables which would result in a Cartesian result and possibly overstate your "CountFeedbacks" value. You also do not need the contacts table as you are not doing anything else with it, and the feedback table has the contact ID basis your are querying for. Since that is only grouped by the contact_id, your "bf.bundle_feedback_supporting" is otherwise wasted. If you want counts of feedback, just count from that table per contact ID and remove the rest. (also, the joins should have the "ON" clauses instead of within the WHERE clause for consistency)
Also, for your supporting feedback, the data type and value are unclear, so I implied as a Yes or No and have a SUM() based on how many are supporting. So, a given contact may have 100 records but only 37 are supporting. This gives you 1 record for the contact having BOTH values 100 and 37 respectively and not lost in a group by based on the first entry found for the contact.
I would try to summarize your query to below:
SELECT
c.contact_id,
c.contact_first_name,
c.contact_last_name,
c.contact_email,
c.contact_date_added,
c.contact_company,
c.contact_twitter,
concat(c.contact_first_name," ", c.contact_last_name) AS fullname,
c.contact_subscribed,
ifnull(icc.sendCampaignsCount, 0) AS sendCampaignsCount,
ifnull(round((ibf.countfeedbacks / icc.sendCampaignsCount * 100),2), 0) AS percentFeedback,
ifnull(ibf.SupportCount, 0) AS feedbackSupporting
FROM
contacts AS c
LEFT JOIN
( SELECT
c.contact_id,
count(*) AS sendCampaignsCount
FROM
contacts AS c
JOIN contact_campaigns AS cc
ON c.contact_id = cc.contact_id
AND cc.contact_campaign_sent = '1'
WHERE
c.company_id = '876'
AND c.contact_is_active = '1'
GROUP BY
c.contact_id) AS icc
ON c.contact_id = icc.contact_id
LEFT JOIN
( SELECT
bf.contact_id,
count(*) AS countfeedbacks,
SUM( case when bf.bundle_feedback_supporting = 'Y'
then 1 else 0 end ) as SupportCount
FROM
contacts AS c
JOIN bundle_feedback bf
ON c.contact_id = bf.contact_id
WHERE
c.company_id = '876'
AND c.contact_is_active = '1'
GROUP BY
bf.contact_id) AS ibf
ON c.contact_id = ibf.contact_id
WHERE
c.company_id = '876'
AND c.contact_is_active = '1'
ORDER BY
percentFeedback DESC LIMIT 0, 25;

using joins together with aggregates, and retrieving rows when no aggregate exists

The following query on my MySQL tables returns rows from the purchaseorder table that have corresponding entries in the deliveryorder table. How do I construct this query so that I get rows from the purchaseorder table even if no corresponding rows exist in the deliveryorder table? If the users want to see sql table CREATE statements, I can post those, but I'm not posting now as it really makes the question too big.
SELECT
`purchaseorder`.`id` AS `po_id`,
`purchaseorder`.`order_quantity` AS `po_order_quantity`,
`purchaseorder`.`applicable_approved_unit_rate` AS `po_unit_rate`,
`purchaseorder`.`applicable_sales_tax_rate` AS `po_tax_rate`,
`purchaseorder`.`order_date` AS `po_order_date`,
`purchaseorder`.`remarks` AS `po_remarks`,
`purchaseorder`.`is_open` AS `po_is_open`,
`purchaseorder`.`is_active` AS `po_is_active`,
`purchaseorder`.`approved_rate_id` AS `po_app_rate_id`,
`supplier`.`name` AS `sup_name`,
SUM(`deliveryorder`.`quantity`) AS `total_ordered`
FROM `purchaseorder`
LEFT JOIN `deliveryorder` ON (`deliveryorder`.`purchase_order_id` = `purchaseorder`.`id`)
INNER JOIN `approvedrate` ON (`purchaseorder`.`approved_rate_id` = `approvedrate`.`id`)
INNER JOIN `supplier` ON (`approvedrate`.`supplier_id` = `supplier`.`id`)
WHERE (
`purchaseorder`.`is_active` = 1
AND `purchaseorder`.`is_open` = 1
AND `deliveryorder`.`is_active` = 1
AND `approvedrate`.`material_id` = 2
)
HAVING `purchaseorder`.`order_quantity` >= `total_ordered` + 1
You have an aggregating function but no GROUP BY clause, which is wierd, but anyway - something like this? Oops - edited...
SELECT po.id po_id
, po.order_quantity po_order_quantity
, po.applicable_approved_unit_rate po_unit_rate
, po.applicable_sales_tax_rate po_tax_rate
, po.order_date po_order_date
, po.remarks po_remarks
, po.is_open po_is_open
, po.is_active po_is_active
, po.approved_rate_id po_app_rate_id
, s.name sup_name
, SUM(do.quantity) total_ordered
FROM purchaseorder po
LEFT
JOIN deliveryorder do
ON do.purchase_order_id = po.
AND do.is_active = 1
LEFT
JOIN approvedrate ar
ON ar.id = po.approved_rate_id
AND ar.material_id = 2
LEFT
JOIN supplier s
ON s.id = ar.supplier_id
WHERE po.is_active = 1
AND po.is_open = 1
HAVING po.order_quantity >= total_ordered + 1
I couldn't work out how to get the desired results all in one query, but ended up using the following two queries to fulfill my requirements: -
1st query
SELECT
pot.`id` AS `po_id`,
pot.`order_quantity` AS `po_order_quantity`,
pot.`applicable_approved_unit_rate` AS `po_unit_rate`,
pot.`applicable_sales_tax_rate` AS `po_tax_rate`,
pot.`is_open` AS `po_is_open`,
pot.`is_active` AS `po_is_active`,
st.`id` AS `sup_id`,
st.`name` AS `sup_name`,
SUM(dot.`quantity`) AS `total_ordered`
FROM `purchaseorder` pot
INNER JOIN `deliveryorder` dot ON (dot.`purchase_order_id` = pot.`id`)
INNER JOIN `approvedrate` art ON (pot.`approved_rate_id` = art.`id`)
INNER JOIN `supplier` st ON (art.`supplier_id` = st.`id`)
WHERE (
pot.`is_active` = 1
AND pot.`is_open` = 1
AND art.`material_id` = #materialid
AND art.`in_effect` = 1
AND art.`is_active` = 1
AND dot.`is_active` = 1
AND st.`is_active` = 1
)
HAVING pot.`order_quantity` >= `total_ordered` + #materialquantity
2nd query
SELECT
pot.`id` AS `po_id`,
pot.`order_quantity` AS `po_order_quantity`,
pot.`applicable_approved_unit_rate` AS `po_unit_rate`,
pot.`applicable_sales_tax_rate` AS `po_tax_rate`,
pot.`is_open` AS `po_is_open`,
pot.`is_active` AS `po_is_active`,
st.`id` AS `sup_id`,
st.`name` AS `sup_name`,
0 AS `total_ordered`
FROM `purchaseorder` pot
INNER JOIN `approvedrate` art ON (pot.`approved_rate_id` = art.`id`)
INNER JOIN `supplier` st ON (art.`supplier_id` = st.`id`)
WHERE (
pot.`is_active` = 1
AND pot.`is_open` = 1
AND art.`material_id` = #materialid
AND art.`in_effect` = 1
AND art.`is_active` = 1
AND st.`is_active` = 1
AND pot.`order_quantity` >= #materialquantity
AND pot.`id` NOT IN
(
SELECT dot.`purchase_order_id`
FROM `deliveryorder` dot
WHERE dot.is_active = 1
)
)

Sum of rows with join

This is the current table layout.
There are 3 legs
Each leg has 2 points, where is_start = 1 is the start of the leg, and is_start is the end of the leg.
When the user check in at a point, a entry in points_user are created.
In this application you have multiple legs which has 2 points where one marks the start of the leg, where the other marks the end of the leg. So the sum of User's (with id = 2) Leg (with id= 1) is points_users.created where points_users.leg_id = 1 and points_users.user_id = 2 and points_users.is_start = 0 minus points_users where is_start = 1 (and the other parameters stay the same). And that's for just one leg.
What I would like is to sum all the time differences for each leg, we get the data like this:
| User.id | User.name | total_time |
| 1 | John | 129934 |
Anyone know how I can join these tables and sum it up grouped by user?
(No, this is not homework)
As far as I got:
SELECT
( `end_time` - `start_time` ) AS `diff`
FROM
(
SELECT SUM(UNIX_TIMESTAMP(`p1`.`created`)) AS `start_time`
FROM `points_users` AS `pu1`
LEFT JOIN `points` AS `p1` ON `pu1`.`point_id` = `p1`.`id`
WHERE `p1`.`is_start` = 1
) AS `start_time`,
(
SELECT SUM(UNIX_TIMESTAMP(`pu2`.`created`)) AS `end_time`
FROM `points_users` AS `pu2`
LEFT JOIN `points` AS `p2` ON `pu2`.`point_id` = `p2`.`id`
WHERE `p2`.`is_start` = 0
) AS `end_time`
Try this:
select users.user_id,
users.user_name,
SUM(timeDuration) totalTime
from users
join (
select
pStart.User_id,
pStart.leg_id,
(pEnd.created - pStart.created) timeDuration
from (select pu.user_id, pu.leg_id, pu.created
from points_users pu
join points p on pu.id = p.point_id and pu.leg_id = p.leg_id
where p.is_start = 1 ) pStart
join (select pu.user_id, pu.leg_id, pu.created
from points_users pu
join points p on pu.id = p.point_id and pu.leg_id = p.leg_id
where p.is_start = 0 ) pEnd
on pStart.user_id = pEnd.user_id
and pStart.leg_id = pEnd.leg_id
) tt
on users.user_id = tt.user_id
group by users.user_id, users.user_name
Subquery gets the time duration for each user/leg, and main query then sums them for all the legs of each user.
EDIT: Added the points table now that I can see your attempt at a query.
The simplest way is to join points_users to itself:
select leg_start.user_id, sum(leg_end.created - leg_start.created)
from points_users leg_start
join points_users leg_end on leg_start.user_id = leg_end.user_id
and leg_start.leg_id = leg_end.leg_id
join points point_start on leg_start.point_id = point_start.id
join points point_end on leg_end.point_id = point_end.id
where point_start.is_start = 1 and point_end.is_start = 0
group by leg_start.user_id
Some people prefer to put those is_start filters in the join condition, but since it's an inner join that's mainly just a point of style. If it were an outer join, then moving them from the WHERE to the JOIN could have an effect on the results.