Combine two separate SQL queries in a single query - mysql

I have these two tables:
Name | Income
----------|----------
Alice | 200
Bob | 100
Charlie | 50
Dave | 500
Name | Outcome
----------|----------
Alice | 300
Bob | 40
Charlie | 100
Dave | 250
I can make this query to get all the people who have an income which is greater than 150 and order them
SELECT Name, Income
FROM table1
WHERE Income > 150
ORDER BY Income DESC
Similarly I can get all the people who have an outcome which is less than 200:
SELECT Name, Outcome
FROM table2
WHERE Outcome < 200
ORDER BY Outcome DESC
Is there a way to get the two views by writing a single query i.e. using only one ;?
EDIT: I'm sorry, I just realised I wasn't clear about what I want to get. This is more or less what I am trying to achieve:
Name | Income
----------|----------
Dave | 500
Alice | 200
Name | Outcome
----------|----------
Charlie | 100
Bob | 40
I know about JOIN but that would make only one table in a result. I can't use UNION either because Outcome and Income do have same datatype but they mean different things.

What you are showing is still two separate results. One query gives you one result. If you want to combine the two queries that gives one query and one result. One method:
SELECT what, name, value
FROM
(
SELECT 'INCOME' as what, name, income as value, 1 as sortkey1, -income as sortkey2
FROM table1
WHERE income > 150
UNION ALL
SELECT 'OUTCOME' as what, name, outcome as value, 2 as sortkey1, outcome as sortkey2
FROM table2
WHERE outcome < 200
)
ORDER BY sortkey1, sortkey2;

You can JOIN these two tables by Name and SELECT both Income and Outcome, e.g.:
SELECT t1.name, t1.Income, t2.Outcome
FROM table1 t1 JOIN table2 t2 ON t1.Name = t2.Name
WHERE t1.Income > 150 AND t2.Outcome < 200
ORDER BY t1.Income DESC t2.Outcome DESC;
update (as per edits in question)
You can't have one query resulting on two separate outputs. Closest you can get to it is by using UNION with another column to distinguish between the outputs, e.g.:
SELECT Name, Income, 'Income'
FROM table1
WHERE Income > 150
ORDER BY Income DESC
UNION
SELECT Name, Outcome, 'Outcome'
FROM table2
WHERE Outcome < 200
ORDER BY Outcome DESC

Related

How to assign to each row a number of times a value appears in the whole table?

I'm trying to run an SQL query on Vertica but I can't find a way to get the results I need.
Let's say I have a table showing:
productID
campaignID (ID of the sales campaign)
calendarYearWeek (calendar week when the campaign was active [usually they're active for 5 days)
countryOrigin (in which country was the product sold, as it's international sales)
valueLocal (price in local currency)
What I need to do is to find products sold in different countries and compare their prices between markets.
Sometimes the campaigns are available only in one country, sometimes in more, so to avoid having hundreds of thousands of unnecessary rows that I can't compare to others, I want to distill only those products that were available in more than 1 countryOrigin.
What's important - a product can be available in different campaigns with a different price.
That's why in my SELECT statement I added a new column:
calendarYearWeek||productID||campaignID AS uniqueItem - that way I know that I'm checking the price only for a specific product in a specific campaign during a specific week of year.
The table is also joined with another table to get exchange rates etc., so it's also GROUPed BY, so in each row I have a price and average exchange rate for a given uniqueItem in a specific country.
If I run this query, it works but even just for this year it gives me several million results, most of which I don't need because these are products sold only in one country and I need to compare prices across different markets.
So what I thought I need is to assign to each row a number of times a uniqueItem value appears in the whole table. If it's 1 - then the product is sold only in one country and I don't have to care about it. If it's 2 or 3 - this is what I need. Then I can filter out the unnecessary results in the WHERE clause ( > 1) and I can work on a smaller, better data set.
I tried different combinations of COUNT, I tried row_number + OVER(PARTITION BY) (works only partially, as when a product is available in 2 or more countries it counts the rows, but still I cannot filter out "1" because then I'll lose the "first" country on the list). I thought about MATCH_RECOGNIZED, but I've never used it before and I think it's not available in Vertica.
Sorry if it's messy, but I'm not really advanced in SQL and English is not my native language.
Do you have any ideas how to get only the data I need?
What I have now is:
SELECT
a.originCountry,
a.calendarYearWeek,
a.productID,
a.campaignId,
a.valueLocal,
ROUND(AVG(b.exchange_rate),4),
a.calendarYearWeek||a.productID||a.campaignID AS uniqueItem
FROM table1 a
LEFT JOIN table2 b
ON a.reportDate = b.reportDate
AND a.originCountry = b.originCountry
WHERE a.originCountry IN ('ES', 'DE', 'FR')
GROUP BY 3, 4, 7, 1, 5, 2
ORDER BY 3, 4, 1
----------
I need some sample data - so I make up a few rows.
You need to find the identifying grouping columns of those combinations that occur more than once in a sub select or a common table expression, to join with table1.
You need to formulate the average as an OLAP function if you want the country back in the report.
WITH
-- input, don't use in final query ..
table1(originCountry,calendarYearWeek,productID,campaignId,valuelocal,reportDate) AS (
SELECT 'ES',202203,43,142,100.50, DATE '2022-01-19'
UNION ALL SELECT 'DE',202203,43,142,135.00, DATE '2022-01-19'
UNION ALL SELECT 'FR',202203,43,142, 98.75, DATE '2022-01-19'
UNION ALL SELECT 'ES',202203,44,147,198.75, DATE '2022-01-19'
UNION ALL SELECT 'DE',202203,44,147,205.00, DATE '2022-01-19'
UNION ALL SELECT 'FR',202203,44,147,198.75, DATE '2022-01-19'
UNION ALL SELECT 'es',202203,49,150, 1.25, DATE '2022-01-19'
)
,
table2(originCountry,reportDate,exchange_rate) AS (
SELECT 'ES',DATE '2022-01-19', 1
UNION ALL SELECT 'DE',DATE '2022-01-19', 1
UNION ALL SELECT 'FR',DATE '2022-01-19', 1
)
-- end of input; real query starts here, replace following comma with "WITH" ..
,
-- you need the unique ident grouping values to join with ..
selgrp AS (
SELECT
a.calendarYearWeek
, a.productID
, a.campaignId
FROM table1 a
GROUP BY
a.calendarYearWeek
, a.productID
, a.campaignId
HAVING COUNT(*) > 1
-- chk calendarYearWeek | productID | campaignId
-- chk ------------------+--------+--------
-- chk 202203 | 43 | 142
-- chk 202203 | 44 | 147
)
SELECT
a.originCountry
, a.calendarYearWeek
, a.productID
, a.campaignId
, a.valueLocal
, AVG(b.exchange_rate) OVER w::NUMERIC(9,4) AS avg_exch_rate
-- a.calendarYearWeek||a.productID||a.campaignID AS uniqueItem
FROM table1 a
JOIN selgrp USING(calendarYearWeek,productID,campaignId)
LEFT JOIN table2 b
ON a.reportDate = b.reportDate
AND a.originCountry = b.originCountry
WHERE UPPER(a.originCountry) IN ('ES', 'DE', 'FR')
WINDOW w AS (PARTITION BY a.calendarYearWeek,a.productID,a.campaignID)
ORDER BY 3, 4, 1
-- out originCountry | calendarYearWeek | productID | campaignId | valueLocal | avg_exch_rate
-- out ---------------+------------------+-----------+------------+------------+---------------
-- out DE | 202203 | 43 | 142 | 135.00 | 1.0000
-- out ES | 202203 | 43 | 142 | 100.50 | 1.0000
-- out FR | 202203 | 43 | 142 | 98.75 | 1.0000
-- out DE | 202203 | 44 | 147 | 205.00 | 1.0000
-- out ES | 202203 | 44 | 147 | 198.75 | 1.0000
-- out FR | 202203 | 44 | 147 | 198.75 | 1.0000

configure query to bring rows which have more than 1 entries

How to get those entries which have more than 1 records?
If it doesn't make sense... let me explain:
From the below table I want to access the sum of the commission of all rows where type is joining and "they have more than 1 entry with same downmem_id".
I have this query but it doesn't consider more entries scenario...
$search = "SELECT sum(commission) as income FROM `$database`.`$memcom` where type='joining'";
Here's the table:
id mem_id commission downmem_id type time
2 1 3250 2 joining 2019-09-22 13:24:40
3 45 500 2 egbvegr new time
4 32 20 2 vnsjkdv other time
5 23 2222 2 vfdvfvf some other time
6 43 42 3 joining time
7 32 353 5 joining time
8 54 35 5 vsdvsdd time
Here's the expected result: it should be the sum of the id no 2, 7 only
ie. 3250+353=whatever.
It shouldn't include id no 6 because it has only 1 row with the same downmem_id.
Please help me to make this query.
Another approach is two levels of aggregation:
select sum(t.commission) income
from (select sum(case when type = 'joining' then commission end) as commission
from t
group by downmem_id
having count(*) > 1
) t;
The main advantage to this approach is that this more readily supports more complex conditions on the other members of each group -- such as at most one "joining" record or both "joining" records and no more than two "vnsjkdv" records.
Use EXISTS:
select sum(t.commission) income
from tablename t
where t.type = 'joining'
and exists (
select 1 from tablename
where id <> t.id and downmem_id = t.downmem_id
)
See the demo.
Results:
| income |
| ----- |
| 3603 |
You can use subquery that will find all downmem_id having more than one occurrence in the table.
SELECT Sum(commission) AS income
FROM tablename
WHERE type = 'joining'
AND downmem_id IN (SELECT downmem_id
FROM tablename t
GROUP BY downmem_id
HAVING Count(id) > 1);
DEMO

Group by with sum doesn't return correct result

Say a table has this schema :
grp | number
1 | 10
1 | 10
1 | 10
2 | 30
2 | 30
3 | 20
Note that each unique grp has a unique number even if there are more than 1 grp. I'm looking to sum all numbers for each unique grp.
So I want to group my table by grp to have this :
grp | number
1 | 10
2 | 30
3 | 20
And then get the sum which is now 60, but without grouping it gets me 110 as it calculates the sum of everything without grouping. All in one query, with no sub-queries if possible.
I've tried doing the following :
SELECT sum(number) as f
FROM ...
WHERE ...
GROUP BY grp
But this doesn't work, it returns multiple results and not the single result of the sum. What am I doing wrong?
You can use subquery to select unique records & do the sum:
select sum(number)
from (select distinct grp, number
from table t
) t;
If you group by the group, then you'll get one result for each group. And it won't take into account the fact that you only want to use the value from each group once.
To get your desired result, taking one row from each group, you first need to make a subquery selecting DISTINCT group/number combinations from the table, and then SUM that.
SELECT
sum(`number`) as f
FROM
(SELECT DISTINCT `grp`, `number` FROM table1) g
This will output 60.
Demo: https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=8a3b346041731a4b4c85f4e151c10f70

Mysql Agregate function to select maximum and then select minimum price within that group

I am trying to get the maximum value out of a aggregate function, and then also get the min value out of a Price column which comes back in results.
id | discount | price
1 | 60 | 656
2 | 60 | 454
3 | 60 | 222
4 | 30 | 335
5 | 30 | 333
6 | 10 | 232
So in above table, I would like to separate Minimum Price vs Highest Discount.
This is the result I should be seeing:
id | discount | price
3 | 60 | 222
5 | 30 | 333
6 | 10 | 232
As you can see, its taken discount=60 group and separated the lowest price - 222, and the same for all other discount groups.
Could someone give me the SQL for this please, something like this -
SELECT MAX(discount) AS Maxdisc
, MIN(price) as MinPrice
,
FROM mytable
GROUP
BY discount
However, this doesnt separate the minimum price for each group. I think i need to join this table to itself to achieve that. Also, the table contains milions of rows, so the sql needs to be fast. One flat table.
This question is asked and answered with tedious regularity in SO. If only the algorithm was better at spotting duplicates. Anyway...
SELECT x.*
FROM my_table x
JOIN
( SELECT discount,MIN(price) min_price FROM my_table GROUP BY discount) y
ON y.discount = x.discount
AND y.min_price = x.price;
In your query, you cannot group by discount and then maximize the discount value.
This should get you the result you are looking for..
SELECT Max(ID) AS ID, discount, MIN(price) as MinPrice, FROM mytable GROUP BY discount
If you do not need the id, yo would do:
select discount, min(price) as minprice
from table t
group by discount;
If you want other columns in the row, you can either join back to the original table or use the substring_index()/group_concat() trick:
select substring_index(group_concat(id order by price), ',', 1) as id,
discount, min(price)
from table t
group by discount;
This will not always work because the intermediate result for group_concat() can overflow if there are too many matches within a column. This is controlled by a system parameter, which could be made bigger if necessary.

Counting distinct values for multiple months

Got a little problem here. I can't for the life of me, figure out how to do this.
pid | firstlast | lastvisit | zip
---------------------------------------
435 | 2001-01-17 | 2012-01-21 | 46530
567 | 2001-01-18 | 2012-01-21 | 46530
532 | 2001-01-19 | 2012-01-22 | 46535
536 | 2001-01-19 | 2012-01-23 | 46535
539 | 2001-01-20 | 2012-01-27 | 46521
Here is my SQL query:
SELECT DISTINCT zip, COUNT(zip) AS totalzip FROM production WHERE MONTH(lastvisit) = "1" GROUP BY zip ORDER BY totalzip DESC;
Output:
Jan:
zip | totalzip
---------------------
46530 | 2
46535 | 2
46521 | 1
Feb:
zip | totalzip
---------------------
46530 | 1
46521 | 4
49112 | 3
This is great for the 1st month, but I need this for the entire year. I could run this query 12 times, however 2 problems occur. I have over 300 zip codes for the entire year. On some months the zip code is not present, so the count is 0 (but the MySQL output doesn't output the "zero data". Also, when I order by totalzip, the order changes from month to month, and this does not allow me to paste them into a spread sheet. I can order by zip code, but again the "zero" data zipcodes are not present and so the list changes from month to month.
Any thoughts or suggestions would be much appreciated!
You can make this work with subqueries:
select
a.*, count(c.zip) as totalZip
from
(select
monthVisit, zip
from
(select distinct last_day(lastVisit) as monthVisit from production) as m,
(select distinct zip from production) as z
) as a
left join (select
last_day(lastVisit) as monthVisit, zip
from production) as c
on a.monthVisit=c.monthVisit and a.zip=c.zip
group by
a.monthVisit, a.zip
This should give you the count of zips for each month you have, including zeros.
Let me explain how this works:
First, I defined a subquery that makes all the possible combinations of zips and months (the a subquery), and then I left joined this with a second subquery that returns the values of ZIPs and months (the c subquery). Using left join allows to count the possible empty combinations in the a subquery.
Hope this help you.
Note: The last_day() function returns the last day of the month of a given date; e.g.: last_day('2012-07-17')='2012-07-31'
If you have a zipcode table (you should), you could join it with your data table (a left join), which would bring even the zero-count zipcodes.
The first part of your question is solved with additional grouping. Try something like this:
SELECT DISTINCT zip, YEAR(lastvisit), MONTH(lastvisit), COUNT(zip) AS totalzip
FROM production
GROUP BY zip, YEAR(lastvisit), MONTH(lastvisit)
ORDER BY totalzip DESC;
To add in the "zero" summaries when no data is present I typically do a left-join with a complete list. (This is also stated by #Alfabravo above). So the final query looks a bit like:
SELECT DISTINCT zip, YEAR(lastvisit), MONTH(lastvisit), COUNT(zip) AS totalzip
FROM production left join
(SELECT DISTINCT zip from production) as zipMap on zipmap.zip = production.zip
GROUP BY zip, YEAR(lastvisit), MONTH(lastvisit)
ORDER BY totalzip DESC;