combine select with sum - mysql

I would like to select * from a table and have only certain columns show a sum result at the bottom, see example.
I have tried UNION but there has to be the same number of columns in both queries.

Combine the sum using UNION, and add empty columns to make it the same number of columns.
SELECT rd, dte, vs, venu, h_a, res, g_f, g_a, g_d, pts
FROM yourTable
UNION ALL
SELECT '', '', '', '', '', '', SUM(g_f), SUM(g_a), SUM(g_d), SUM(pts)
FROM yourTable

Related

Emulating a ROLLUP without that keyword

In postgres, I can emulate a two dimensional pivot table by doing a query such as:
SELECT ... FROM ...
GROUP BY
ROLLUP(x,y,z), -- ROWS
ROLLUP(a,b,c) -- COLS
As a concrete example in dbfiddle:
However, if a database did not have access to the ROLLUP keyword, how could this be emulated? I know a one-dimensional ROLLUP could be done with a UNION ALL, such as:
SELECT a, b, SUM(c) FROM Input GROUP BY ROLLUP(a, b);
-->
SELECT NULL, NULL, SUM(c) FROM Input UNION ALL
SELECT a, NULL, SUM(c) FROM Input GROUP BY a UNION ALL
SELECT a, b, SUM(c) FROM Input GROUP BY a, b;
But how could this be done without access to the ROLLUP keyword? We an use postgres (or mysql) as the database here, but in your answer just refrain from using the ROLLUP keyword.
You can emulate this also with UNION ALLs since we have:
GROUP BY ROLLUP(a,b)
--> GROUP BY (), (a), (a,b)
So with ROLLUP(a,b), ROLLUP(x,y) we multiply the products together so we get:
GROUP BY ROLLUP(a,b), (x,y)
--> GROUP BY (), a, (a,b) *cross product* (), x, (x,y)
--> (),() + (),x + (),x,y + a,() + a,x + a,x,y + a,b,(), a,b,x, a,b,x,y
So applying it to the original question we would have:
WITH sales (Year, Half, Category, Product, Revenue) AS (
SELECT 2020, 'H1', 'Electronics', 'Phone', 200 UNION ALL
SELECT 2020, 'H1', 'Electronics', 'Computer', 300 UNION ALL
SELECT 2020, 'H2', 'Electronics', 'Phone', 100 UNION ALL
SELECT 2020, 'H2', 'Electronics', 'Computer', 175 UNION ALL
SELECT 2021, 'H1', 'Electronics', 'Phone', 109 UNION ALL
SELECT 2021, 'H1', 'Electronics', 'Computer', 32 UNION ALL
SELECT 2021, 'H2', 'Electronics', 'Phone', 93 UNION ALL
SELECT 2021, 'H2', 'Electronics', 'Computer', 111
)
SELECT SUM(Revenue) AS "sum", NULL AS category, NULL AS product, NULL AS year, NULL AS half FROM Sales GROUP BY (),() UNION ALL
SELECT SUM(Revenue), NULL, NULL, Year, NULL FROM Sales GROUP BY (),Year UNION ALL
SELECT SUM(Revenue), NULL, NULL, Year, Half FROM Sales GROUP BY (),Year,Half UNION ALL
SELECT SUM(Revenue), Category, NULL, NULL, NULL FROM Sales GROUP BY Category,() UNION ALL
SELECT SUM(Revenue), Category, NULL, Year, NULL FROM Sales GROUP BY Category,Year UNION ALL
SELECT SUM(Revenue), Category, NULL, Year, Half FROM Sales GROUP BY Category,Year,half UNION ALL
SELECT SUM(Revenue), Category, Product, NULL, NULL FROM Sales GROUP BY Category,Product,() UNION ALL
SELECT SUM(Revenue), Category, Product, Year, NULL FROM Sales GROUP BY Category,Product,Year UNION ALL
SELECT SUM(Revenue), Category, Product, Year, half FROM Sales GROUP BY Category,Product,Year,Half
https://dbfiddle.uk/?rdbms=postgres_14&fiddle=be3b0d89ad97eaf44b522caf0df9d7da

SQL finding difference between subqueries

I have got a table of Salaries and i have to find the difference of averages of rows with zeroes and without,so here is the my query
SELECT ((SELECT REPLACE(Salary, '0', '') FROM TEST) - (SELECT ROUND(SUM(Salary)/COUNT(*)) FROM Test)) FROM Test
Unfortunately my query is not working.
Example 5 salaries: 5049,7000,2000,3900
I have to find the average of 5049,7000,2000,3900 and 549,7,2,39
Try this:
SELECT a.without, a.with, a.with - a.without
FROM(
SELECT AVG(CAST(REPLACE(Salary, '0', '') AS INT)) AS 'without', AVG(salary)
as 'with'
FROM test) a

MySQL Query to average 3 columns and exclude 0's?

This is obviously wrong, but what would be the correct way to average the SUM of 3 columns and exclude the 0's?
SELECT (
AVG(NULLIF(`dices`.`Die1`,0)) +
AVG(NULLIF(`dices`.`Die2`,0)) +
AVG(NULLIF(`dices`.`Die3`,0))
) /3 as avgAllDice
FROM (
SELECT `Die1`,`Die2`,`Die3` FROM `GameLog`
WHERE PlayerId = "12345"
) dices
Thanks.
If I was keeping the inline view query (it's not clear why it's needed). I'd probably do something like this:
SELECT AVG( NULLIF( CASE d.i
WHEN 1 THEN dices.`Die1`
WHEN 2 THEN dices.`Die2`
WHEN 3 THEN dices.`Die3`
END
,0)
) AS `avgAllDice`
FROM ( SELECT gl.`Die1`
, gl.`Die2`
, gl.`Die3`
FROM `GameLog` gl
WHERE gl.playerId = '12345'
) dices
CROSS
JOIN ( SELECT 1 AS i UNION ALL SELECT 2 UNION ALL SELECT 3 ) d
The trick is the cross join operation, giving me three rows for each row returned from dices, and an expression that picks out values of Die1, Die2 and Die3 on each of three rows, respectively.
To exclude values of 0, we replace 0 with with NULL (since AVG doesn't include NULL values.)
Now with all of the non-zero DieN values stacked into a single column, we can just use the AVG function.
Another way to do it would be to get the numerator and denominator for each of Die1, Die2, Die3.... and then total up the numerators, total up the denominators, and then divide the total numerator by the total denominator.
This will should give an equivalent result.
SELECT ( IFNULL(t.n_die1,0) + IFNULL(t.n_die2,0) + IFNULL(t.n_die3,0) )
/ ( t.d_die1 + t.d_die2 + t.d_die3 )
AS avgAllDice
FROM ( SELECT SUM( NULLIF(gl.die1,0)) AS n_die1
, COUNT(NULLIF(gl.die1,0)) AS d_die1
, SUM( NULLIF(gl.die2,0)) AS n_die2
, COUNT(NULLIF(gl.die2,0)) AS d_die2
, SUM( NULLIF(gl.die3,0)) AS n_die3
, COUNT(NULLIF(gl.die3,0)) AS d_die3
FROM `GameLog` gl
WHERE gl.playerid = '12345'
) t
(I didn't work out what gets returned in the edge and corner cases... no matching rows in GameLog, all values of Die1, Die2 and Die3 are zero, etc., for either query. The results might be slightly different, returning a zero instead of NULL, divide by zero edge case, etc.)
FOLLOWUP
I ran a quick test of both queries.
CREATE DATABASE d20170228 ;
USE d20170228 ;
CREATE TABLE GameLog
( playerid VARCHAR(5) DEFAULT '12345'
, die1 TINYINT
, die2 TINYINT
, die3 TINYINT
);
INSERT INTO GameLog (die1,die2,die3)
VALUES (3,0,0),(2,1,0),(4,3,3),(3,3,3),(0,0,0),(4,4,4),(5,4,0),(0,0,2)
;
SELECT (3+2+1+4+3+3+3+3+3+4+4+4+5+4+2)/15 AS manual_avg
manual_avg is coming out 3.2.
Both queries are also returning 3.2
If you want to eliminate zeroes and NULLs, you can simply SELECT from the filtered master set multiple times, doing a UNION ALL on the results, then averaging against that.
SELECT AVG(`allDice`.`DieResult`)
FROM (
SELECT `Die1` AS `DieResult` FROM `GameLog` WHERE COALESCE(`Die1`, 0) <> 0 AND PlayerId = '12345'
UNION ALL
SELECT `Die2` FROM `GameLog` WHERE COALESCE(`Die2`, 0) <> 0 AND PlayerId = '12345'
UNION ALL
SELECT `Die3` FROM `GameLog` WHERE COALESCE(`Die3`, 0) <> 0 AND PlayerId = '12345'
) AS `allDice`
There's no need to overthink this one, it's not too difficult a problem

msql query FROM another msl query

I am new to SQL, so I am not too sure how to go about this query that I have to do ...
I have multiple tables which all have 2 columns that i want to take (date_added and path).
So I did an Union select ("date_added" and "path") for each table. So I now have a table with all rows from all the tables I want:
SELECT `date_added`, `path` FROM `art_1` UNION SELECT `date_added`, `path` FROM `art_5484`
This works fine, but now I need to get the row with the lowest date ( I use min function). So I want to do select on the table that I got from my last query so I do:
SELECT `path`
FROM cross_join = (SELECT `date_added`, `path` FROM `art_1` UNION SELECT `date_added`, `path` FROM `art_5484`)
WHERE `date_added` = MIN(`date_added`)
But this doesn't work; I am guessing it's a syntax error, but i can't see where ...
if anybody could help me out, that would be great !
One way to get the minimum date is to use order by and limit:
SELECT `path`
FROM (SELECT `date_added`, `path` FROM `art_1` UNION ALL
SELECT `date_added`, `path` FROM `art_5484`
) a
ORDER BY date_added DESC
LIMIT 1;
Note: this returns only one value, even if when there are duplicates. Also, I changed the UNION to UNION ALL. You should use UNION ALL by default, because UNION incurs the overhead of removing duplicates.

MySQL - match post code based on one or two first characters

I'm trying to create a SQL statement to find the matching record based on the provided post code and stored post codes in the database plus the weight aspect.
The post codes in the database are between 1 or 2 characters i.e. B, BA ...
Now - the value passed to the SQL statement will always have 2 first characters of the client's post code. How can I find the match for it? Say I have a post code B1, which would only match the single B in the database plus the weight aspect, which I'm ok with.
Here's my current SQL statement, which also takes the factor of the free shipping above certain weight:
SELECT `s`.*,
IF (
'{$weight}' > (
SELECT MAX(`weight_from`)
FROM `shipping`
WHERE UPPER(SUBSTRING(`post_code`, 1, 2)) = 'B1'
),
(
SELECT `cost`
FROM `shipping`
WHERE UPPER(SUBSTRING(`post_code`, 1, 2)) = 'B1'
ORDER BY `weight_from` DESC
LIMIT 0, 1
),
`s`.`cost`
) AS `cost`
FROM `shipping` `s`
WHERE UPPER(SUBSTRING(`s`.`post_code`, 1, 2)) = 'B1'
AND
(
(
'{$weight}' > (
SELECT MAX(`weight_from`)
FROM `shipping`
WHERE UPPER(SUBSTRING(`post_code`, 1, 2)) = 'B1'
)
)
OR
('{$weight}' BETWEEN `s`.`weight_from` AND `s`.`weight_to`)
)
LIMIT 0, 1
The above however uses the SUBSTRING() function with hard coded number of characters set to 2 - this is where I need some help really to make it match only number of characters that matches the provided post code - in this case B1.
Marcus - thanks for the help - outstanding example - here's what my code look like for those who also wonder:
First I've run the following statement to get the right post code:
(
SELECT `post_code`
FROM `shipping`
WHERE `post_code` = 'B1'
)
UNION
(
SELECT `post_code`
FROM `shipping`
WHERE `post_code` = SUBSTRING('B1', 1, 1)
)
ORDER BY `post_code` DESC
LIMIT 0, 1
Then, based on the returned value assigned to the 'post_code' index my second statement followed with:
$post_code = $result['post_code'];
SELECT `s`.*,
IF (
'1000' > (
SELECT MAX(`weight_from`)
FROM `shipping`
WHERE `post_code` = '{$post_code}'
),
(
SELECT `cost`
FROM `shipping`
WHERE `post_code` = '{$post_code}'
ORDER BY `weight_from` DESC
LIMIT 0, 1
),
`s`.`cost`
) AS `cost`
FROM `shipping` `s`
WHERE `s`.`post_code` = '{$post_code}'
AND
(
(
'1000' > (
SELECT MAX(`weight_from`)
FROM `shipping`
WHERE `post_code` = '{$post_code}'
ORDER BY LENGTH(`post_code`) DESC
)
)
OR
('1000' BETWEEN `s`.`weight_from` AND `s`.`weight_to`)
)
LIMIT 0, 1
The following query will get all results where the post_code in the shipping table matches the beginning of the passed in post_code, then it orders it most explicit to least explicit, returning the most explicit one:
SELECT *
FROM shipping
WHERE post_code = SUBSTRING('B1', 1, LENGTH(post_code))
ORDER BY LENGTH(post_code) DESC
LIMIT 1
Update
While this query is flexible, it's not very fast, since it can't utilize an index. If the shipping table is large, and you'll only pass in up to two characters, it might be faster to make two separate calls.
First, try the most explicit call.
SELECT *
FROM shipping
WHERE post_code = 'B1'
If it doesn't return a result then search on a single character:
SELECT *
FROM shipping
WHERE post_code = SUBSTRING('B1', 1, 1)
Of course, you can combine these with a UNION if you must do it in a single call:
SELECT * FROM
((SELECT *
FROM shipping
WHERE post_code = 'B1')
UNION
(SELECT *
FROM shipping
WHERE post_code = SUBSTRING('B1', 1, 1))) a
ORDER BY post_code DESC
LIMIT 1