Write SQL query with division, total and % - sql

I'm writing test queries in MS SQL Server to test reports.
Can't figure out how to calculate following:
Ingredient_Cost_Paid / Total Ingredient_Cost_Paid * 100 as 'Ingredient Cost Allow as % of Total'
This is Ingredient cost allowable as a percentage of the total ingredient cost allowable.
P.S. I'm new to SQL, so would appreciate explanations as well, so I learn for the future. Thanks
Also I'm not sure I correctly understand difference between Total and SUM.
Thanks everyone

The single quote (') is used as a delimiter for textual values. If you use the AS keyword to specify a (column) alias, you need to use square brackets ([]) if it includes spaces and/or special characters:
Ingredient_Cost_Paid / Total_Ingredient_Cost_Paid * 100 as [Ingredient Cost Allow as % of Total]
Is that what you are looking for?
Edit: I noticed that it also works with single quotes! I didn't know that! But honestly, I would not use it. I'm not sure if it's officially considered to be valid.
Regarding the difference between "Total" and SUM, I would need to understand what you mean with "Total", since that is not something that SQL understands. You could probably use the SUM aggregate function to calculate a total. An aggregate function calculates a value based on a certain column/expression in groups of rows (or in the entire table as a whole single group). So you probably need to provide (much) more information in your question to get effective help with that.
Edit:
I would like to elaborate a little on this SQL issue for you. My apologies in advance for this rather lengthy post. ;)
For example, assume that all query logic described here applies to a table called Recipe_Ingredients, which contains rows with information about ingredients for various recipes (identified by the column Recipe_ID) and the price of the recipe ingredient (in a column called Ingredient_Cost_Paid).
The (simplified) table definition would look something like this:
CREATE TABLE Recipe_Ingredients (
Recipe_ID INT NOT NULL,
Ingredient_Cost_Paid NUMERIC NOT NULL
);
For testing purposes, I created this table in a test database and populated it with the following query:
INSERT INTO Recipe_Ingredients
VALUES
(12, 4.65),
(12, 0.40),
(12, 9.98),
(27, 5.35),
(27, 12.50),
(27, 1.09),
(27, 3.00),
(65, 2.35),
(65, 0.99);
You could select all rows from the table to view all data in the table:
SELECT
Recipe_ID,
Ingredient_Cost_Paid
FROM
Recipe_Ingredients;
This would yield the following results:
Recipe_ID Ingredient_Cost_Paid
--------- --------------------
12 4.65
12 0.40
12 9.98
27 5.35
27 12.50
27 1.09
27 3.00
65 2.35
65 0.99
You could group the rows based on corresponding Recipe_ID values. Like this:
SELECT
Recipe_ID
FROM
Recipe_Ingredients
GROUP BY
Recipe_ID;
This will yield the following result:
Recipe_ID
---------
12
27
65
Not very spectacular, I agree. But you could ask the query to calculate values based on those groups as well. That's where aggregate functions like COUNT and SUM come into play:
SELECT
Recipe_ID,
COUNT(Recipe_ID) AS Number_Of_Ingredients,
SUM(Ingredient_Cost_Paid) AS Total_Ingredient_Cost_Paid
FROM
Recipe_Ingredients
GROUP BY
Recipe_ID;
This will yield the following result:
Recipe_ID Number_Of_Ingredients Total_Ingredient_Cost_Paid
--------- --------------------- --------------------------
12 3 15.03
27 4 21.94
65 2 3.34
Introducing your percentage column is somewhat tricky. The calculation has to be performed on a rowset (a table or a query result) and cannot be expressed directly in a SUM.
You could specify the previous query as a subquery in the FROM-clause of another query (this is called a table expression) and join it with table Recipe_Ingredients. That way you combine the group data back with the detail data.
I will drop the Number_Of_Ingredients column from now on. It was just an example for the COUNT function, but you do not need it for your issue at hand.
SELECT
Recipe_Ingredients.Recipe_ID,
Recipe_Ingredients.Ingredient_Cost_Paid,
Subquery.Total_Ingredient_Cost_Paid
FROM
Recipe_Ingredients
INNER JOIN (
SELECT
Recipe_ID,
SUM(Ingredient_Cost_Paid) AS Total_Ingredient_Cost_Paid
FROM
Recipe_Ingredients
GROUP BY
Recipe_ID
) AS Subquery ON Subquery.Recipe_ID = Recipe_Ingredients.Recipe_ID;
This will yield the following results:
Recipe_ID Ingredient_Cost_Paid Total_Ingredient_Cost_Paid
--------- -------------------- --------------------------
12 4.65 15.03
12 0.40 15.03
12 9.98 15.03
27 5.35 21.94
27 12.50 21.94
27 1.09 21.94
27 3.00 21.94
65 2.35 3.34
65 0.99 3.34
With this, it is pretty easy to add your calculation for the percentage:
SELECT
Recipe_Ingredients.Recipe_ID,
Recipe_Ingredients.Ingredient_Cost_Paid,
Subquery.Total_Ingredient_Cost_Paid,
CAST(Recipe_Ingredients.Ingredient_Cost_Paid / Subquery.Total_Ingredient_Cost_Paid * 100 AS DECIMAL(8,1)) AS [Ingredient Cost Allow as % of Total]
FROM
Recipe_Ingredients
INNER JOIN (
SELECT
Recipe_ID,
SUM(Ingredient_Cost_Paid) AS Total_Ingredient_Cost_Paid
FROM
Recipe_Ingredients
GROUP BY
Recipe_ID
) AS Subquery ON Subquery.Recipe_ID = Recipe_Ingredients.Recipe_ID;
Note that I also cast the percentage column values to type DECIMAL(8,1) so that you do not get values with large fractions. The above query yields the following results:
Recipe_ID Ingredient_Cost_Paid Total_Ingredient_Cost_Paid Ingredient Cost Allow as % of Total
--------- -------------------- -------------------------- -----------------------------------
12 4.65 15.03 30.9
12 0.40 15.03 2.7
12 9.98 15.03 66.4
27 5.35 21.94 24.4
27 12.50 21.94 57.0
27 1.09 21.94 5.0
27 3.00 21.94 13.7
65 2.35 3.34 70.4
65 0.99 3.34 29.6
As I said earlier, you will need to supply more information in your question if you need more specific help with your own situation. These queries and their results are just examples to show you what can be possible. Perhaps (and hopefully) this contains enough information to help you find a solution yourself. But you may always ask more specific questions, of course.

Related

SQL query to order sum of a column

I have the following tables:
Drivers table, with a Driver_Code column
Route_files table, with a Driver_Code column and a Route_Code column
Routes table, with a Route_Code column and a Kilometers column
For every entry in the Drivers table there may be more than 1 entry in the Route_files table with the same Driver_Code. For every entry in the Route_files table, there is only one entry in the Routes table with the same Route_Code.
What I am trying to do is order the Drivers based on the total number of kilometers that they drove. So if I have the following data:
Drivers:
Driver_Code
2
3
4
Route_files:
Driver_Code Route_Code
2 20
2 50
2 30
3 30
4 40
Routes:
Route_Code Kilometers
20 1231
30 9
40 400000
50 24234
Then Driver 2 drove routes 20 30 and 50 so the total kilometers is 25474. Similarly driver 3 drove 9km and driver 4 drove 400000. The SQL query that I need should output:
Driver_Code Total_km
4 400000
2 25474
3 9
I tried to use an inner join on the Route_files and Routes tables to obtain a single "table" with all the necessary information, hoping that I could further use this obtained table, but couldn't figure out how to do that. I am working in dBase 2019(and can't change to something better, unfortunately). Any hints and ideas are appreciated!
I finally managed to do it. This is the working query:
select
Driver_Code,
SUM(km) as Total_km
from
Route_files
inner join Routes on Route_files.Route_Code = Routes.Route_Codes
GROUP BY
Route_files.Driver_Code
ORDER BY
Total_km Descending
Initially I was doing select Driver_Code, km, SUM(km) and when trying to do GROUP BY, dBase was forcing me to group by Driver_Code as well as km, which meant that the SUM function was being applied to every single entry instead of on all the entries of a single Driver_Code, which is what I needed. Now I finally understand what GROUP BY does!
Thanks everyone for your comments!

SQL query to get lowest 3 values per column grouping by another column

So i have a table that has a set of information like this
name Type PRICE
11111 XX 0.001
22222 YY 0.002
33333 ZZ 0.0001
11111 YY 0.021
11111 ZZ 0.0111
77777 YY 0.1
77777 ZZ 1.2
Now these numbers go on for about a million rows and there could be upwards of 20 of the same 'name' mapping to 20 different TYPE. But there will only be 1 unique type per name. What I mean by this is that 11111 could have XX,YY,ZZ on it but it cannot have YY,ZZ,YY on it.
What I need is to get the lowest 3 prices and what TYPE they are per name.
Right now I can get the lowest price per name by doing:
select name, type, min(price) from table group by name;
However that is just for the lowest price but I need the lowest 3 prices. I've been trying for a couple days and I cant seem to get it. All help is appreciated.
Also, please let me know if I forgot any information, i'm still trying to figure out stack overflow :P
Oh and the database is a noSQL that uses SQL syntax.
edit: I can't seem to get the format down for my example data from my table to show correctly
If your database supports window functions, and allowing for the possibility that there may be more than three rows in your data with any of the three lowest prices, this should do it:
select the_table.*
from
the_table
inner join (
select name, price
from (
select name, price, row_number() over(partition by name order by price) as rn
from the_table) as x
where rn < 4
) as y on y.name=the_table.name and y.price=the_table.price;

SQL/sqlite query to union tables with matching columns and merged columns

I'm working with a database that has 2 tables for each company with 4 companies (8 total DBs for thisquery) and for reasons outside of my control that can't be changed. This is also an sqlite DB.
My app currently has to do 8 round trips to get all the data. I want to consolidate that down to one table view query but I can't figure out how to combined the data in a way that would make it work. Here is an example of the tables.
Table 1 (Type A)
name zone
ABCD ABC1
DBAA CBA1
Table 2 (Type A)
name zone
ABCD 1234
DBAA 4321
Table 1 (Type B)
zone weight rate
ABC1 1 0.50
CBA1 2 0.88
Table 2 (Type B)
zone weight rate
1234 1 0.52
4321 2 0.80
Finally I want the view to look like this:
name weight Table 1 rate Table 2 rate
CABA 1 0.52 0.50
AEAS 2 0.80 0.88
I tried this for my SQL statement:
SELECT 1A.name, 1B.weight, 1B.rate as A from 1A, 1B WHERE 1A.zone = 1B.zone
UNION ALL
SELECT 2A.name, 2B.weight, 2B.rate as B from 2A, 2B WHERE 2A.zone = 2B.zone
I have also tried a couple joins statements after reading unions must have matching column counts but I can't seem to hit the right query. Any ideas what I'm doing wrong or how I can achieve this with a query?
Any help is greatly appreciated!
Updated with Fiddle example here: http://sqlfiddle.com/#!5/37c19/3/0
Here is a query that will produce something similar to your example:
SELECT
ZonesOne.name
, RatesOne.weight
, RatesOne.rate as Table1Rate
, RatesTwo.Rate AS Table2Rate
FROM ZonesOne, RatesOne, RatesTwo
WHERE
RatesOne.zone = ZonesOne.zone
AND RatesOne.Weight = RatesTwo.weight
UNION ALL
SELECT
ZonesTwo.name
, RatesOne.weight
, RatesOne.rate as Table1Rate
, RatesTwo.Rate AS Table2Rate
FROM ZonesTwo, RatesOne, RatesTwo
WHERE
RatesOne.zone = ZonesTwo.zone
AND RatesOne.Weight = RatesTwo.weight
However, your Table 1 Rate and Table 2 Rate seem to be switched around. Also, your data from ZonesTwo has two entries for "DBAA".

Cartesian product and WHERE clause issue

Let us have simple table:
CREATE TABLE dbo.test
(
c1 INT
)
INSERT INTO test (c1) VALUES (1)
INSERT INTO test (c1) VALUES (2)
INSERT INTO test (c1) VALUES (3)
Next calculate some SUM:
SELECT SUM(t1.c1) FROM test AS t1 , test AS t2
WHERE t2.c1 = 1
Output is: 6 . Simple and easy.
But if I run:
SELECT SUM(t1.c1), * FROM test AS t1 , test AS t2
WHERE t2.c1 = 1
The output is:
6 2 2
6 2 3
6 2 1
6 3 2
6 3 3
6 3 1
6 1 2
6 1 3
6 1 1
My question is: Why the second output is not matching the condition in WHERE clause?
Looks like Sybase implements it's own extensions to GROUP BY:
Through the following extensions, Sybase lifts restrictions on what
you can include or omit in the select list of a query that includes
group by.
The columns in the select list are not limited to the grouping columns
and columns used with the vector aggregates.
The columns specified by group by are not limited to those
non-aggregate columns in the select list.
However, the results of the extension are not always intuitive:
When you use the Transact-SQL extensions in complex queries that
include the where clause or joins, the results may become even more
difficult to understand.
How does this relate to your problem?
However, the way that Adaptive Server handles extra columns in the
select list and the where clause may seem contradictory. For example:
select type, advance, avg(price)
from titles
where advance > 5000
group by type
type advance
------------- --------- --------
business 5,000.00 2.99
business 5,000.00 2.99
business 10,125.00 2.99
business 5,000.00 2.99
mod_cook 0.00 2.99
mod_cook 15,000.00 2.99
popular_comp 7,000.00 21.48
popular_comp 8,000.00 21.48
popular_comp NULL 21.48
psychology 7,000.00 14.30
psychology 2,275.00 14.30
psychology 6,000.00 14.30
psychology 2,000.00 14.30
psychology 4,000.00 14.30
trad_cook 7,000.00 17.97
trad_cook 4,000.00 17.97
trad_cook 8,000.00 17.97
(17 rows affected)
It only seems as if the query is ignoring the where clause when you
look at the results for the advance (extended) column. Adaptive Server
still computes the vector aggregate using only those rows that satisfy
the where clause, but it also displays all rows for any extended
columns that you include in the select list. To further restrict these
rows from the results, you must use a having clause.
So, to give you the results you would expect, Sybase should allow you to do:
SELECT SUM(t1.c1), * FROM test AS t1 , test AS t2
WHERE t2.c1 = 1
HAVING t2.c1 = 1
The WHERE will exclude the results from the total SUM; the HAVING will hide records that don't match the condition.
Confusing, isn't it?
Instead, you'd probably be better off writing the query so that it doesn't require Sybase's GROUP BY extensions.

Need to repeat and calculate values in a single Select statement

I hope that someone can help me with my issue. I need to create in a single SELECT statement (the system that we use has some pivot tables in Excel that handle one single SELECT) the following:
I have a INL (Invoice Lines) table, that has a lot of fields, but the important one is the date.
INL_ID DATE
19 2004-03-15 00:00:00.000
20 2004-03-15 00:00:00.000
21 2004-03-15 00:00:00.000
22 2004-03-16 00:00:00.000
23 2004-03-16 00:00:00.000
24 2004-03-16 00:00:00.000
Now, I also have a ILD (Invoice Line Details) that are related by an ID field to the INL table. From the second table I will need to use the scu_qty field to "repeat" values from the first one in my results sheet.
The ILD table values that we need are:
INL_ID scu_qty
19 1
20 1
21 1
22 4
23 4
Now, with the scu_qty I need to repeat the value of the first table and also add one day each record, the scu_qty is the quantity of days of the services that we sell in the ILD table.
So I need to get something like (i'm going to show the INL_ID 22 that you can see has a value different of 1 in the SCU_QTY). The results of the select has to give me something like:
INL_ID DATE
22 2004-03-15 0:00:00
22 2004-03-16 0:00:00
22 2004-03-17 0:00:00
22 2004-03-18 0:00:00
In this information I only wrote the fields that need to be repeated and calculated, of course I will need more fields, but will be repeated from the INL table, so I don't put them so you don't get confused.
I hope that someone can help me with this, it's very important for us this report. Thanks a lot in advance
(Sorry for my English, that isn't my first language)
SELECT INL_ID, scu_qty, CalculatedDATE ...
FROM INL
INNER JOIN ILD ON ...
INNER JOIN SequenceTable ON SequenceTable.seqNo <= ILD.scu_qty
ORDER BY INL_ID, SequenceTable.seqNo
Depending on your SQL flavour you will need to lookup date manipulation functions to do
CalculatedDATE = {INL.DATE + SequenceTable.seqNo (days)}
select INL.INL_ID, `DATE`
from
INL
inner join
ILD on INL.INL_ID = ILD.INL_ID
inner join (
select 1 as qty union select 2 union select 3 union select 4
) s on s.qty <= ILD.scu_qty
order by INL.INL_ID
In instead of that subselect you will need a table if quantity is a bit bigger. Or tell what is your RDBMS and there can be an easier way.