sql select all if column has only two particular values - mysql

orders (
o_id INT AUTO_INCREMENT,
o_status TINYINT,
o_description VARCHAR(50),
)
orders_products (
op_id INT AUTO_INCREMENT,
op_order_id INT(11),
op_product_id INT(11),
op_price DECIMAL(19, 2),
)
How to select all orders that have ONLY products with id = 1 and id = 2.
Thank you and sorry from my English...

There are different ways to get the desired result, this utilizes conditional aggregation:
select *
from orders
where o_id in
(
select op_order_id
from orders_products
having count(case when op_product_id = 1 then 1 end) > 0 -- at least one row with 1
and count(case when op_product_id = 2 then 1 end) > 0 -- at least one row with 2
and count(case when op_product_id not in (1,2) then 1 end) = 0 -- no other value
)
Depending on indexes/selectivity EXISTS/NOT EXISTS might be faster:
select o_id
from orders as o
where exists (select *
from orders_products as op
where op.op_order_id = o.o_id
and op.op_product_id = 1) -- at least one row with 1
and exists (select *
from orders_products as op
where op.op_order_id = o.o_id
and op.op_product_id = 2) -- at least one row with 2
and not exists (select *
from orders_products as op
where op.op_order_id = o.o_id
and op.op_product_id not in (1,2)) -- no other value

You could first find all the distinct order and product combination for product 1 or 2 or both, and then look for orders that have both.
create table orders (o_id INT);
create table orders_products (op_order_id INT(11), op_product_id INT(11));
insert into orders values (1), (2);
insert into orders_products values (1, 1), (1, 2), (2, 2);
select o_id from (
select distinct o_id, op_product_id
from orders o
inner join orders_products op on op.op_order_id = o.o_id
where op.op_product_id in (1,2)
) main
group by o_id
having count(*) = 2
Result:
1
Another way to write the query could be like this:
select o_id
from orders o
where exists (select 1 from orders_products where op_order_id = o.o_id and op_product_id = 1)
and exists (select 1 from orders_products where op_order_id = o.o_id and op_product_id = 2)

I would do this using aggregation and having:
select order_id
from order_products op
group by order_id
having sum(product_id = 1) > 0 and
sum(product_id = 2) > 0 and
sum(product_id not in (1, 2)) = 0;
If you want additional information about the order, then just join in the orders table.
Your question is what I call a "set-within-set" query . . . looking for patterns in a hierarchy (that is products within an order). There are several ways to solve this, but the having clause turns out to be quite general.

Related

Need json value without re-joining the table in SQL Server

I have written the code shown below and I got the expected output. but,need the same output without use the same tables in inline view (such as tables join for UserPhoneDetail_JSON). thanks in advance
Code:
BEGIN
DROP TABLE #USERMASTER;
DROP TABLE #USERPHONE;
CREATE TABLE #USERMASTER (ID INT, NAME VARCHAR(100));
CREATE TABLE #USERPHONE (ID INT, PHONENUMBER NUMERIC,PHONETYPE CHAR(1));
INSERT INTO #USERMASTER VALUES(1,'JOHN');
INSERT INTO #USERMASTER VALUES(2,'VICTOR');
INSERT INTO #USERPHONE VALUES(1,1356487965,'W');
INSERT INTO #USERPHONE VALUES(1,9841007493,'M');
INSERT INTO #USERPHONE VALUES(1,7255952105,'O');
INSERT INTO #USERPHONE VALUES(2,9874563212,'M');
WITH E AS (SELECT A.ID,A.NAME,B.PHONENUMBER,B.PHONETYPE,ROW_NUMBER() OVER(PARTITION BY A.ID ORDER BY A.ID) RN
FROM #USERMASTER A JOIN #USERPHONE B ON A.ID=B.ID)
SELECT E.ID,
E.NAME,
E.PHONENUMBER,
E.PHONETYPE,
UserPhoneDetail_JSON = (
SELECT A.ID,B.PHONETYPE,PHONENUMBER,A.NAME FROM #USERMASTER A JOIN #USERPHONE B ON A.ID=B.ID
FOR JSON PATH )
FROM E WHERE RN=1;
END
output:
1 JOHN 1356487965 W [{"ID":1,"PHONETYPE":"W","PHONENUMBER":1356487965,"NAME":"JOHN"},{"ID":1,"PHONETYPE":"M","PHONENUMBER":9841007493,"NAME":"JOHN"},{"ID":1,"PHONETYPE":"O","PHONENUMBER":7255952105,"NAME":"JOHN"},{"ID":2,"PHONETYPE":"M","PHONENUMBER":9874563212,"NAME":"VICTOR"}]
2 VICTOR 9874563212 M [{"ID":1,"PHONETYPE":"W","PHONENUMBER":1356487965,"NAME":"JOHN"},{"ID":1,"PHONETYPE":"M","PHONENUMBER":9841007493,"NAME":"JOHN"},{"ID":1,"PHONETYPE":"O","PHONENUMBER":7255952105,"NAME":"JOHN"},{"ID":2,"PHONETYPE":"M","PHONENUMBER":9874563212,"NAME":"VICTOR"}]
EXPECTED OUTPUT:
1 JOHN 1356487965 W [{"ID":1,"PHONETYPE":"W","PHONENUMBER":1356487965,"NAME":"JOHN"},{"ID":1,"PHONETYPE":"M","PHONENUMBER":9841007493,"NAME":"JOHN"},{"ID":1,"PHONETYPE":"O","PHONENUMBER":7255952105,"NAME":"JOHN"}]
2 VICTOR 9874563212 M [{"ID":2,"PHONETYPE":"M","PHONENUMBER":9874563212,"NAME":"VICTOR"}]
If I understand you correctly and you need to generate JSON content for each first row per ID, this statement is an option:
SELECT
t.ID, t.NAME, t.PHONENUMBER, t.PHONETYPE,
UserPhoneDetail_JSON = (
SELECT t.ID, PHONETYPE, PHONENUMBER, t.NAME
FROM #USERPHONE
WHERE ID = t.ID
FOR JSON PATH
)
FROM (
SELECT
m.ID, m.NAME, p.PHONENUMBER, p.PHONETYPE,
ROW_NUMBER() OVER (PARTITION BY m.ID ORDER BY p.ID) RN
FROM #USERMASTER m
JOIN #USERPHONE p ON m.ID = p.ID
) t
WHERE t.RN = 1

MySQL select with group and one to many relations condition

For example have such structure:
CREATE TABLE clicks
(`date` varchar(50), `sum` int, `id` int)
;
CREATE TABLE marks
(`click_id` int, `name` varchar(50), `value` varchar(50))
;
where click can have many marks
So example data:
INSERT INTO clicks
(`sum`, `id`, `date`)
VALUES
(100, 1, '2017-01-01'),
(200, 2, '2017-01-01')
;
INSERT INTO marks
(`click_id`, `name`, `value`)
VALUES
(1, 'utm_source', 'test_source1'),
(1, 'utm_medium', 'test_medium1'),
(1, 'utm_term', 'test_term1'),
(2, 'utm_source', 'test_source1'),
(2, 'utm_medium', 'test_medium1')
;
I need to get agregated values of click grouped by date which contains all of selected values.
I make request:
select
c.date,
sum(c.sum)
from clicks as c
left join marks as m ON m.click_id = c.id
where
(m.name = 'utm_source' AND m.value='test_source1') OR
(m.name = 'utm_medium' AND m.value='test_medium1') OR
(m.name = 'utm_term' AND m.value='test_term1')
group by date
and get 2017-01-01 = 700, but I want to get 100 which means that only click 1 has all of marks.
Or if condition will be
(m.name = 'utm_source' AND m.value='test_source1') OR
(m.name = 'utm_medium' AND m.value='test_medium1')
I need to get 300 instead of 600
I found answer in getting distinct click_id by first query and then sum and group by date with condition whereIn, but on real database which is very large and has id as uuid this request executes extrimely slow. Any advices how to get it work propely?
You can achieve it using below queries:
When there are the three conditions then you have to pass the HAVING count(*) >= 3
SELECT cc.DATE
,sum(cc.sum)
FROM clicks AS cc
INNER JOIN (
SELECT id
FROM clicks AS c
LEFT JOIN marks AS m ON m.click_id = c.id
WHERE (
m.NAME = 'utm_source'
AND m.value = 'test_source1'
)
OR (
m.NAME = 'utm_medium'
AND m.value = 'test_medium1'
)
OR (
m.NAME = 'utm_term'
AND m.value = 'test_term1'
)
GROUP BY id
HAVING count(*) >= 3
) AS t ON cc.id = t.id
GROUP BY cc.DATE
When there are the three conditions then you have to pass the HAVING count(*) >= 2
SELECT cc.DATE
,sum(cc.sum)
FROM clicks AS cc
INNER JOIN (
SELECT id
FROM clicks AS c
LEFT JOIN marks AS m ON m.click_id = c.id
WHERE (
m.NAME = 'utm_source'
AND m.value = 'test_source1'
)
OR (
m.NAME = 'utm_medium'
AND m.value = 'test_medium1'
)
GROUP BY id
HAVING count(*) >= 2
) AS t ON cc.id = t.id
GROUP BY cc.DATE
Demo: http://sqlfiddle.com/#!9/fe571a/35
Hope this works for you...
You're getting 700 because the join generates multiple rows for the different IDs. There are 3 rows in the mark table with ID=1 and sum=100 and there are two rows with ID=2 and sum=200. On doing the join where shall have 3 rows with sum=100 and 2 rows with sum=200, so adding these sum gives 700. To fix this you have to aggregate on the click_id too as illustrated below:
select
c.date,
sum(c.sum)
from clicks as c
inner join (select * from marks where (name = 'utm_source' AND
value='test_source1') OR (name = 'utm_medium' AND value='test_medium1')
OR (name = 'utm_term' AND value='test_term1')
group by click_id) as m
ON m.click_id = c.id
group by c.date;
DEMO SQL FIDDLE
I found the right way myself, which works on large amounts of data
The main goal is to make request generate one table with subqueries(conditions) which do not depend on amount of data in results, so the best way is:
select
c.date,
sum(c.sum)
from clicks as c
join marks as m1 ON m1.click_id = c.id
join marks as m2 ON m2.click_id = c.id
join marks as m3 ON m3.click_id = c.id
where
(m1.name = 'utm_source' AND m1.value='test_source1') AND
(m2.name = 'utm_medium' AND m2.value='test_medium1') AND
(m3.name = 'utm_term' AND m3.value='test_term1')
group by date
So we need to make as many joins as many conditions we have

same colum name with different table id in mysql

tables
person_id (primary key)
phs_people (person_id,first_name,last_name)
phs_cutomers (person_id,company_name)
phs_waiters (person_id,commission)
person_id is key between them.
So my question how can retrive customers firstname and last name, waiter firstname and lastname via person_id?
SELECT
c.first_name AS customer_Fist_name,
c.last_name AS Customer_LastName,
c.first_name AS WaiterFirstName,
c.last_name AS Waiter_LastName,
invoice_number, amount_tendered, sale_time, DATE_FORMAT( sale_time, '%d-%m-%Y' ) AS sale_date, phs_sales.sale_id AS sale_id, SUM( item_unit_price * quantity_purchased * ( 1 - discount_percent /100 ) ) AS amount_due
FROM (
phs_sales
)
LEFT JOIN phs_people c ON c.person_id = phs_sales.customer_id
AND person_id = phs_sales.waiter_id
JOIN phs_sales_items ON phs_sales_items.sale_id = phs_sales.sale_id
LEFT JOIN (
SELECT sale_id, SUM( payment_amount ) AS amount_tendered
FROM phs_sales_payments
WHERE payment_type <> 'Check'
GROUP BY sale_id
) AS payments ON payments.sale_id = phs_sales.sale_id
GROUP BY sale_id
ORDER BY sale_time DESC
LIMIT 25
if I execute this query, I get the following error:
customer_Fist_name NULL,Customer_LastName NULL, WaiterFirstName NULL, Waiter_LastName NULL,
You want to do JOIN's two times on the same table but with different values (customer's data and waiter's data), but you just use a JOIN once and give both conditions there.
To fix this, your have to JOIN the phs_people-Table twice like this:
...
LEFT JOIN phs_people AS c1 ON c1.person_id = phs_sales.customer_id
LEFT JOIN phs_people AS c2 ON c2.person_id = phs_sales.waiter_id
...
and then select the correct data like this:
SELECT
c1.first_name AS customer_Fist_name,
c1.last_name AS Customer_LastName,
c2.first_name AS WaiterFirstName,
c2.last_name AS Waiter_LastName,
...
PS: With this query, you should still get multiple NULL-Values, that's because half of your phs_sales-Table is filled with empty fields...

Insert in to a new table results from multiple queries

I have these queries and I want to insert these results in to a single temporary table. How Can I do that?
select date(max(created_date)) AS 'Last Shipped Date',
sum(item_count) AS 'Last Shipped Units'
from order
where is_shipped = 1
AND date(shipped_date) = (select date(max(shipped_date)) from order);
select
count(distinct o.key) AS 'ACTIVE ',
count(od.ean) AS 'Active_ Units'
from order o
left join order_details od on o.id = od. order_details
Where o.is_active = 1;
select count(distinct order_key) AS 'Total_Orders_Shipped_Yesterday',
sum(item_count) AS 'Total_units_Shipped_yesterday'
from order
where datediff(curdate(), modified_date)=1
AND is_shipped =1;
select count(distinct liquidation_order_id) AS 'orders cancelled',
count(ean) AS 'Units cancelled'
from order_details
where datediff(curdate(), modified_date)=1
AND order_details_status_ =4;
There may be a way to do it in one query, but it will be complicated. It's easier to just do a series of UPDATE queries that fill in the appropriate columns in the table by joining with the other queries that calculate the values.
CREATE TEMPORARY TABLE tempTable (
`Last Shipped Date` DATE,
`Last Shipped Units` INT,
Active INT,
Active_Units INT,
Total_Orders_Shipped_Yesterday INT,
Total_units_Shipped_yesterday INT,
`orders cancelled` INT,
`Units cancelled` INT);
INSERT INTO tempTable (`Last Shipped Date`, `Last Shipped Units`)
select date(max(created_date)) AS 'Last Shipped Date',
sum(item_count) AS 'Last Shipped Units'
from order
where is_shipped = 1
AND date(shipped_date) = (select date(max(shipped_date)) from order);
UPDATE tempTable AS t
JOIN order o
left join order_details od on o.id = od. order_details
SET t.Active = count(distinct o.key), t.Active_Units = count(od.ean)
Where o.is_active = 1;
UPDATE tempTable AS t
JOIN order
SET t.Total_Orders_Shipped_Yesterday = count(distinct order_key),
t.Total_units_Shipped_yesterday = SUM(item_count)
where datediff(curdate(), modified_date)=1
AND is_shipped =1;
UPDATE tempTable AS t
JOIN order_details
SET t.`orders cancelled` = count(distinct liquidation_order_id),
t.`Units cancelled` = COUNT(ean)
where datediff(curdate(), modified_date)=1
AND order_details_status_ =4;

MySql return multiple rows from different columns of the same row

Given two tables:
The 'people' table contains the following columns:
name
favorite_walking_shoe
favorite_running_shoe
favorite_dress_shoe
favorite_house_shoe
favorite_other_shoe
The 'shoes' table contains the following columns:
shoe
name
description
I want to create a result set that contains:
people.name, people.favorite_shoe_type, shoes.name, shoes.description
I know I can get the desired results using something like:
select p.name, p.favorite_shoe_type, s.name, s.description
from (select name, favorite_walking_shoe as shoe, 'walking' as favorite_shoe_type
from people where favorite_walking_shoe is not null
union all
select name, favorite_running_shoe, 'running'
from people where favorite_running_shoe is not null
union all
select name, favorite_dress_shoe, 'dress'
from people where favorite_dress_shoe not is null
union all
select name, favorite_house_shoe, 'house'
from people where favorite_house_shoe not is null
union all
select name, favorite_other_shoe, 'other'
from people where favorite_other_shoe not is null
) p
join shoes s on s.shoe = p.shoe
order by 1,2
but this would require 5 passes of the 'people' table. Is there a way to accomplish the UNION ALLs without requiring multiple passes?
I should point out that the structures are part of a vendor product which I cannot modify. :(
You can get around the five scans by doing a cross join:
select p.name, p.favorite_shoe_type, s.name, s.description
from (select p.*,
(case when favorite_shoetype = 'walking' then p.favore_walking_shoe
when favorite_shoetype = 'running' then p.favorite_running_shoe
when favorite_shoetype = 'dress' then p.favorite_dress_shoe
when favorite_shoetype = 'house' then p.favorite_house_shoe
when favorite_shoetype = 'other' then p.favorite_other_shoe
end) as shoe
from people p cross join
(select 'walking' as favorite_shoe_type union all
select 'running' union all
select 'dress' union all
select 'house' union all
select 'other'
) shoetypes join
shoes s
) p
on s.shoe = p.shoe
I'm not sure this will be more efficient. If you have indexes on shoe, this even more complicated version might be more efficient:
select p.name, p.favorite_shoe_type, s.name, s.description
from (select p.name, favorite_shoe_types,
(case when favorite_shoetype = 'walking' then ws.name
when favorite_shoetype = 'running' then rs.name
when favorite_shoetype = 'dress' then ds.name
when favorite_shoetype = 'house' then hs.name
when favorite_shoetype = 'other' then os.name
end) as name,
(case when favorite_shoetype = 'walking' then ws.description
when favorite_shoetype = 'running' then rs.description
when favorite_shoetype = 'dress' then ds.description
when favorite_shoetype = 'house' then hs.description
when favorite_shoetype = 'other' then os.name
end) as description
from people p left outer join
shoes ws
on ws.shoe = favorite_walking_shoe left outer join
shoes rs
on rs.shoe = favorite_running_shoe left outer join
shoes ds
on ds.shoe = favorite_dress_shoe left outer join
shoes hs
on hs.shoe = favorite_house_shoe left outer join
shoes os
on os.shoe = favorite_other_shoe cross join
(select 'walking' as favorite_shoe_type union all
select 'running' union all
select 'dress' union all
select 'house' union all
select 'other'
) shoetypes
) p
on s.shoe = p.shoe
where s.name is not null
This should do the five joins using indexes -- quite fast, one scan of the people table, and feed this to the cross join. The logic then returns the values that you want.
Note: both of these are untested so they might have syntax errors.
Unfortunately, the way your current table is structured you will have multiple passes to get each value. If it is possible, I would suggest changing your table structure to the include a shoe_type table and then a join table between the people and shoes and in this table, you can include a flag that will show if the shoe is the favorite.
So it will be similar to this:
create table people_shoe
(
people_id int,
shoe_id int,
IsFavorite int
);
You could also have a shoe_type table to store each of the different show types:
create table shoe_type
(
id int,
name varchar(10)
);
insert into shoe_type
values('Walking'), ('Running'), ('Dress'), ('House'), ('Other');
The shoe_type.id would be added to your shoe table and you would join the tables.
Edit #1, if you can remodel the database, you could use the following (mock-up model):
create table people
(
id int, 
name varchar(10)
);
insert into people values (1, 'Jim'), (2, 'Jane');
create table shoe_type
(
id int,
name varchar(10)
);
insert into shoe_type
values(1, 'Walking'), (2, 'Running'), (3, 'Dress'), (4, 'House'), (5, 'Other');
create table shoes
(
id int,
name varchar(10),
description varchar(50),
shoe_type_id int
);
insert into shoes 
values(1, 'Nike', 'test', 2), (2, 'Cole Haan', 'blah', 3);
create table people_shoe
(
    people_id int,
    shoe_id int,
    IsFavorite int
);
insert into people_shoe
values (1, 1, 1),
(1, 2, 0),
(2, 1, 1);
Then when you query, your code will be similar to this:
select p.name PersonName,
s.name ShoeName,
st.name ShoeType,
ps.isFavorite
from people p
inner join people_shoe ps
on p.id = ps.people_id
inner join shoes s
on ps.shoe_id = s.id
inner join shoe_type st
on s.shoe_type_id = st.id
See SQL Fiddle with Demo