MySQL: Using ORDER BY and UNION - mysql

Please take a look at the following query:
INSERT INTO product_filter (product_id,filter_id)
SELECT product_id,
(CASE WHEN price < 100 then 1
WHEN price >= 100 AND price < 500 then 2
WHEN price >= 500 AND price < 1000 then 3
WHEN price >= 1000 AND price < 1500 then 4
WHEN price >= 1500 AND price < 2000 then 5
WHEN price >= 2000 AND price < 2500 then 50
WHEN price >= 2500 AND price < 3000 then 6
ELSE 51 END) AS filter_id
FROM product_special
ORDER BY priority DESC, date_end DESC
LIMIT 1
UNION
SELECT product_id,
(CASE WHEN price < 100 then 1
WHEN price >= 100 AND price < 500 then 2
WHEN price >= 500 AND price < 1000 then 3
WHEN price >= 1000 AND price < 1500 then 4
WHEN price >= 1500 AND price < 2000 then 5
WHEN price >= 2000 AND price < 2500 then 50
WHEN price >= 2500 AND price < 3000 then 6
ELSE 51 END) AS filter_id
FROM product WHERE product_id not IN
(SELECT product_id FROM product_special)
This is what the query is supposed to do:
select all special prices from the product_special table and depending on the price, associate them to different price filters
every product can have multiple special prices, so just pick the one that has higher priority and will last longer
select all regular prices from the product table (only those that don't have special price in the product_special table) and depending on the price, associate them to different price filters
Error I'm receiving:
Error Code: 1221. Incorrect usage of UNION and ORDER BY
Sample Data sets:
Products
Specials
Any help is appreciated.

When you use UNION queries with LIMIT or ORDER BY for each separate query then you need to organize your queries using brackets like
(query 1 with limit order by )
UNION
(query 1 with limit order by )
Your above query can be written as to avoid this error
INSERT INTO product_filter (product_id,filter_id)
(
SELECT product_id,
(CASE WHEN price < 100 then 1
WHEN price >= 100 AND price < 500 then 2
WHEN price >= 500 AND price < 1000 then 3
WHEN price >= 1000 AND price < 1500 then 4
WHEN price >= 1500 AND price < 2000 then 5
WHEN price >= 2000 AND price < 2500 then 50
WHEN price >= 2500 AND price < 3000 then 6
ELSE 51 END) AS filter_id
FROM product_special
ORDER BY priority DESC, date_end DESC
LIMIT 1 )
UNION
(SELECT product_id,
(CASE WHEN price < 100 then 1
WHEN price >= 100 AND price < 500 then 2
WHEN price >= 500 AND price < 1000 then 3
WHEN price >= 1000 AND price < 1500 then 4
WHEN price >= 1500 AND price < 2000 then 5
WHEN price >= 2000 AND price < 2500 then 50
WHEN price >= 2500 AND price < 3000 then 6
ELSE 51 END) AS filter_id
FROM product WHERE product_id not IN
(SELECT product_id FROM product_special)
)

You need to use a sub-query for the ORDER BY and LIMIT in the first query.
Something like this:
INSERT INTO product_filter (product_id,filter_id)
SELECT * FROM
(SELECT product_id,
(CASE WHEN price < 100 then 1
WHEN price >= 100 AND price < 500 then 2
WHEN price >= 500 AND price < 1000 then 3
WHEN price >= 1000 AND price < 1500 then 4
WHEN price >= 1500 AND price < 2000 then 5
WHEN price >= 2000 AND price < 2500 then 50
WHEN price >= 2500 AND price < 3000 then 6
ELSE 51 END) AS filter_id
FROM product_special
ORDER BY priority DESC, date_end DESC
LIMIT 1) a
UNION
SELECT product_id,
(CASE WHEN price < 100 then 1
WHEN price >= 100 AND price < 500 then 2
WHEN price >= 500 AND price < 1000 then 3
WHEN price >= 1000 AND price < 1500 then 4
WHEN price >= 1500 AND price < 2000 then 5
WHEN price >= 2000 AND price < 2500 then 50
WHEN price >= 2500 AND price < 3000 then 6
ELSE 51 END) AS filter_id
FROM product WHERE product_id not IN
(SELECT product_id FROM product_special)

You should round brackets in order to use ORDER/LIMIT on individual queries
For example
(SELECT * FROM table1 WHERE ... ORDER BY field1 LIMIT 0, 1)
UNION
(SELECT * FROM table1 WHERE ...)
If you use Order BY outside brackets , it applies to UNIONED result

Related

Show number of lower and higher values in SQL

I have a certain problem while trying to make an SQL. I have a table with the following format and data.
id
value
date
12
3
2020-06-01
12
4
2020-06-09
12
1
2020-06-20
5
4
2020-06-11
5
5
2020-06-17
My goal is to make something like that:
id
lower
higher
12
1
1
5
0
1
This looks for the value of the oldest row IN specific interval (ex. 100 days)and it compares it with all dates after that if their values are higher and lower and return the count.
I do have something that works but it requires more queries:
One to group take all ids with dates in the interval of xx days
SELECT id FROM table
WHERE date >= CURDATE() - INTERVAL 30 DAY GROUP BY id
ORDER BY id ASC;
And then I loop through each row and get its lower and higher values.
SELECT
*
FROM
(
SELECT
COUNT(*) AS higher, id
FROM
`table`
WHERE
id = 12 AND date > CURDATE() - INTERVAL 30 DAY AND value > (
SELECT value FROM table
WHERE table.date >= CURDATE() - INTERVAL 30 DAY AND id = 12
ORDER BY `table`.`date` ASC LIMIT 1
)
) AS t1,(
SELECT
COUNT(*) AS deteriorated_placements
FROM
`table`
WHERE
id = 12 AND date > CURDATE() - INTERVAL 30 DAY AND value < (
SELECT value FROM table
WHERE table.date >= CURDATE() - INTERVAL 30 DAY AND id = 12
ORDER BY `table`.`date` ASC LIMIT 1
)
) AS t2;
The problem with that is that I do around 40 more queries. I know it maybe is not a big issue but
Is there a way to somehow combine those 2 queries?
Use first_value():
select id,
sum(value < value_1) as lower,
sum(value > value_1) as higher
from (select t.*,
first_value(value) over (partition by id order by date) as value_1
from t
) t
group by id;

How to group by two fields?

I have the following table:
listId | accountId | amount
1 1 20
1 1 20
2 2 30
2 2 30
I need to SUM(amount) and group by listId, accountId to get result:
listId | accountId | amount |
1 1 40
2 2 60
But it does not work for me: SUM(amount) ... GROUP BY listId, accountId
My full query is:
select `account_transactions`.*,
`enterprise_invoces`.*,
ABS(SUM(IF(AT_amount>0, AT_amount, 0))) AS debit,
ABS(SUM(IF(AT_amount<0, AT_amount, 0))) AS credit
from `account_transactions`
inner join `enterprise_invoces`
on `enterprise_invoces`.`AC_id` = `account_transactions`.`AT_code`
where `AT_createuser` = 15 and
date(`AT_transactiondatetime`) >= 2019-04-11 and
date(`AT_transactiondatetime`) <= 2019-07-29 and
`AC_code` >= 601 and
`AC_code` <= 761
group by `enterprise_invoces`.`AC_id`, `account_transactions.AT_transactionficheno`
order by `AT_transactiondatetime` desc
Your select query should not have other columns and should have only the columns mentioned in group by and also the column which needs to be aggregated. So the query should be like this below.
select enterprise_invoces.AC_id, account_transactions.AT_transactionficheno ,
ABS(SUM(IF(AT_amount>0, AT_amount, 0))) AS debit,
ABS(SUM(IF(AT_amount<0, AT_amount, 0))) AS credit
from account_transactions
inner join enterprise_invoces
on enterprise_invoces.AC_id = account_transactions.AT_code
where AT_createuser = 15 and
date(AT_transactiondatetime) >= 2019-04-11 and
date(AT_transactiondatetime) <= 2019-07-29 and
AC_code >= 601 and
AC_code <= 761
group by enterprise_invoces.AC_id, account_transactions.AT_transactionficheno
order by AT_transactiondatetime desc

SQL printing all in one row

I want to print data in two columns, like:
Count Category
But my code prints all in one row, like:
Count Category Count Category
Any tips?
Select sum(Case when population >= 1000000 and population < 5000000 then 1 else 0 end) as Count, '1 000 000 - 4 999 999' as Category,
sum(Case when population >= 500000 and population < 100000 then 1 else 1 end) as Count, '100 000 - 499 999' as Category,
sum(Case when population >= 500000 and population < 100000 then 1 else 1 end) as Count, '500 000 - 999 999' as Category,
sum(Case when population >= 500000 and population < 100000 then 1 else 1 end) as Count, 'Under 100 000' as Category,
sum(Case when population >= 500000 and population < 100000 then 1 else 1 end) as Count, 'Over 5 million' as Category
from cities
Select sum(Case when population >= 1000000 and population < 5000000 then 1 else 0 end) as Count, '1 000 000 - 4 999 999' as Category FROM cities
UNION
SELECT sum(Case when population >= 500000 and population < 100000 then 1 else 1 end) as Count, '100 000 - 499 999' as Category FROM cities
and so on...
Concat all single resuts in one result table with UNION keyword
your else in case part should be everywhere 0 not 1 if you just want to count the cities...
A Better solution anyway would be
SELECT COUNT(cities.primKey) as Count, '1 000 000 - 4 999 999' as Category FROM cities WHERE population >= 1000000 and population < 5000000
UNION
SELECT COUNT(cities.primKey) as Count, '1 000 000 - 4 999 999' as Category FROM cities WHERE population >= 500000 and population < 100000
UNION ...
SELECT CASE WHEN Population >= 5000000
THEN 'Over 5 million'
WHEN Population >= 1000000 AND Population < 5000000
THEN '1 000 000 - 4 999 999'
WHEN Population >= 500000 AND Population < 1000000
THEN '500 000 - 999 999'
WHEN Population >= 100000 AND Population < 500000
THEN '100 000 - 499 999'
WHEN Population < 100000
THEN 'Under 100 000'
END [Category]
,COUNT(*) [Count]
FROM Cities
If you want to get all categories even if there are no cities that belong to it, you can do:
select sum(if(ci.population is null, 0, 1)) as 'Count', cat.Category
from (
select 0 as 'low', 100000 as 'high', 'Under 100 000' as Category, 1 as 'ord'
union
select 100000, 499999, '100 000 - 499 999', 2
union
select 500000, 999999, '500 000 - 999 999', 3
union
select 1000000, 4999999, '1 000 000 - 4 999 999', 4
union
select 5000000, null, 'Over 5 million' , 5
) as cat
left join cities ci on ci.population between cat.low and ifnull(cat.high, ci.population)
group by cat.Category, cat.ord
order by cat.ord

Grouping items between 2 numbers

I have a query that looks like this:
select
price,
item_id,
sum(price),
count(item_id)
from transactions
group by
(price <= 20),
(price between 21 and 30),
(price between 31 and 40),
(price between 41 and 50),
(price > 50)
I have never done a group like this before when I wrote it I was just guessing to see if the query was even valid, and it was. But my question is, is it really getting me what I want?
I want all transactions grouped by:
Items that cost less than or equal to $20
Items that cost between $21 and $30
Items that cost between $31 and $40
Items that cost between $41 and $50
Items that cost more than $50
So, is that query doing what I am asking?
The way to do this in standard SQL (and MySQL) is to use the case statement. Also, I put the definition in a subquery like this:
select pricegrp, sum(price), count(item_id)
from (select t.*,
(case when price <= 20 then '00-20'
when price between 21 and 30 then '21-30'
when price between 31 and 40 then '31-40'
when price between 41 and 50 then '41-50'
when price > 50 then '50+'
end) as pricegrp
from transactions t
) t
group by pricegrp
Also, do you want to group by item_id as well? Or are you just trying to return one arbitrary item? Based on what you want, I'm removing the item_id from the select clause. It doesn't seem necessary.
Your query actually does work in MySQL, in the sense that it runs. It is going to produce one row for each group that you want, so in that sense it "works". However, within each group, it is going to choose an arbitrary price and item_id. These are not explicitly mentioned in the group by clause, so you are using a MySQL (mis)feature called Hidden Columns. Different runs of the query or slight changes to the data or slight changes to the query can change the values of price and item_id returned for each group.
I strongly suggest that you actually name the group. This makes the query and the output much clearer.
Also, I recommend that you get in the habit of putting all columns in the select in the group by clause. There are a few cases where hidden columns are actually useful, but I think, in general, you should depend on them sparingly.
If the price is not stored as an integer, then correct logic is:
select pricegrp, sum(price), count(item_id)
from (select t.*,
(case when price <= 20 then '00-20'
when price <= 30 then '21-30'
when price <= 40 then '31-40'
when price <= 50 then '41-50'
when price > 50 then '50+'
end) as pricegrp
from transactions t
) t
group by pricegrp
SELECT
price,
item_id,
sum(price),
count(item_id),
IF(price<=20,0,IF(price<=30,1,IF(price<=40,2,IF(price<=50,3,4)))) AS pricegroup
FROM transactions
GROUP BY pricegroup
or even
SELECT
price,
item_id,
sum(price),
count(item_id)
FROM transactions
GROUP BY
IF(price<=20,0,IF(price<=30,1,IF(price<=40,2,IF(price<=50,3,4))))
SELECT price,
item_id,
SUM(CASE WHEN price <= 20 THEN price ELSE 0 END) `(price <= 20) SUM`,
SUM(CASE WHEN price <= 20 THEN 1 ELSE 0 END) `(price <= 20) COUNT`,
SUM(CASE WHEN price between 21 and 30 THEN price ELSE 0 END) `(price <= 20) SUM`,
SUM(CASE WHEN price between 21 and 30 THEN 1 ELSE 0 END) `(price <= 20) COUNT`,
SUM(CASE WHEN price between 31 and 40 THEN price ELSE 0 END) `price between 31 and 40 SUM`,
SUM(CASE WHEN price between 31 and 40 THEN 1 ELSE 0 END) `price between 31 and 40 COUNT`,
SUM(CASE WHEN price between 41 and 50 THEN price ELSE 0 END) `price between 41 and 50 SUM`,
SUM(CASE WHEN price between 41 and 50 THEN 1 ELSE 0 END) `price between 41 and 50 COUNT`,
SUM(CASE WHEN price > 50 THEN price ELSE 0 END) `price > 50 SUM`,
SUM(CASE WHEN price > 50 THEN 1 ELSE 0 END) `price > 50 COUNT`
FROM transactions
GROUP BY price, item_id

SQL: Nested queries based on values of a column

I have queried a table with the following query
select content_type_code_id
, price
, count(price) AS PRICECOUNT
from dbo.transaction_unrated
where transaction_date >= '2012/05/01'
and transaction_date < '2012/06/01'
and content_provider_code_id in (1)
group by content_type_code_id, price
ORDER BY price ASC
which produces the following result set
content_type_code_id price PRICECOUNT
1 -1.99 1
1 -0.99 1
1 0.99 178
1 1.99 786
But I want a result set like this:
content_type_code_id price Debits Credits
1 0.99 178 1
1 1.99 786 1
(Negative price as credit and positive price as debit)
try this one
select content_type_code_id
, ABS(price)
, count(IF(price >= 0,1,null)) AS debits,
, count(IF(price < 0,1,null)) AS credits,
from dbo.transaction_unrated
where transaction_date >= '2012/05/01'
and transaction_date < '2012/06/01'
and content_provider_code_id in (1)
group by content_type_code_id, ABS(price)
ORDER BY price ASC
Try this:
SELECT content_type_code_id
, price * -1
, COUNT(price) AS PRICECOUNT
, (
SELECT COUNT (deb.price)
FROM dbo.transaction_unrated deb
WHERE deb.transaction_date >= '2012/05/01'
AND deb.transaction_date < '2012/06/01'
AND deb.content_provider_code_id IN (1)
AND deb.price = ( dbo.transaction_unrated.price * -1 )
)
FROM dbo.transaction_unrated
WHERE transaction_date >= '2012/05/01'
AND transaction_date < '2012/06/01'
AND content_provider_code_id IN (1)
AND price < 0
GROUP BY content_type_code_id
, price
, 4
ORDER BY price ASC