Update an column in SQL Server with values from a different lookup table? - sql

I'm working on a project where I need to update a Mailing Rate column that is currently blank. I have a few different tables of rates/costs based on type of delivery service. Within each table is essentially a table of weights and zones where the cost depends on weight (y column) and distance travelled (x column).
The table I need to update lists the various types of delivery service, associated weight and associated distance travelled for each piece of mail. I'm having trouble figuring out the logic on how to write a query to update the rates column in this table that match the corresponding tables' lookup of weight/travel distance for each service.
Below is an example of the tables:
| Type of Service | Weight | Distance | Cost |
+-----------------+----------+----------+----------+
| A | 1 | 15 | ? |
| B | 2 | 20 | ? |
| C | 3 | 10 | ? |
| | | | |
| Service A Table | | | |
| Weight | 10 km | 15 km | 20 km |
| 1 | $25.00 | $30.00 | $40.00 |
| 2 | $27.00 | $32.00 | $41.00 |
| 3 | $28.00 | $34.00 | $43.00 |
| | | | |
| Service B Table | | | |
| Weight | 10 km | 15 km | 20 km |
| 1 | $28.00 | $32.00 | $41.00 |
| 2 | $29.00 | $35.00 | $44.00 |
| 3 | $30.00 | $37.00 | $47.00 |
+-----------------+----------+----------+----------+

You can use left join to bring in all the tables . . . and a lot of case logic to choose the columns:
update t
set cost = (case when distance = '10 km'
then coalesce(sa.10km, sb.10km, sc.10km)
when distance = '15 km'
then coalesce(sa.15km, sb.15km, sc.15km)
when distance = '20 km'
then coalesce(sa.20km, sb.20km, sc.20km)
end)
from t left join
servicea sa
on sa.weight = t.weight and t.service = 'A' left join
servicea sb
on sb.weight = t.weight and t.service = 'B' left join
servicea sc
on sc.weight = t.weight and t.service = 'C' ;

Since you have different tables for each type of service it is going to be hard for you if you add new types of service. Optimally the type of service would be a column in one table for the costs. You could create views for each type of service if for some reason you want to view your data that way. I would organize the data like this for easy lookup:
create table ServiceCosts(
ServiceType char(1),
MaxDistance decimal(9, 2),
Cost decimal(9, 2),
constraint pk_ServiceCosts primary key clustered (ServiceType, MaxDistance)
)
Then you could update the table using an outer apply:
update s
set s.Cost = oa.Cost
from SourceTable s
outer apply (
select top(1) Cost
from ServiceCosts sc
where sc.Weight >= s.Weight
and sc.Distance >= s.Distance
order by Cost -- cheapest that fits under weight and distance
) oa
There are a few options you can use, which option is best would depend on the exact criteria of your data. Does every table have every weight? Every distance? What do you do if there is no record meeting your criteria (Cost would be null here)?
Another option would be to create a function...
create function GetCost(
#ServiceType char(1),
#Weight decimal(9, 2),
#Distance decimal(9, 2)
) returns decimal(9, 2)
as
begin
declare #Cost decimal(9, 2) = null;
if #ServiceType = 'A'
select top(1) #Cost = case
when #Distance < 10 then [10 km]
when #Distance < 20 then [20 km]
when #Distance < 30 then [30 km]
else null
end
from ServiceATable
where Weight >= #Weight order by Weight -- least weight at or above limit
else if -- repeat for each service table
return #Cost;
end
Then update:
update ItemTable
set Cost = GetCost(ServiceType, Weigth, Distance)
where Cost is null; -- only update rows without Cost already

You can use a MERGE statement between your main table you want to calculate the cost for and the services table. Do the merge on weight and distance and calculate the costs based on service type.
Consult the merge documentation it should be very easy to do it. I could do it on here but better learning for you.
Here there is an example:
https://www.sqlservertutorial.net/sql-server-basics/sql-server-merge/

Related

SQLite Create Table and Insert & Update Value

When I am studying at Udemy SQL course, I am stuck on the below problem. Could you help me how to solve? It is very difficult for me.
Here's the question:
The goal of this exercise is to find out whether each one of the 4 numeric attributes in TemporalData is
positively or negatively correlated with sales.
Requirements:
Each example in the sample consists of a pair (Store, WeekDate). This means that Sales data must be summed across all departments before the correlation is computed.
Return a relation with schema (AttributeName VARCHAR(20), CorrelationSign Integer).
The values of AttributeName can be hardcoded string literals, but the values
of CorrelationSign must be computed automatically using SQL queries over
the given database instance.
You can use multiple SQL statements to compute the result. It might be of help to create and update your own tables.
Here's the sales table:
| store | dept | weekdate | weeklysales |
|1 | 1 | 2010-07-01| 2400.0 |
|2 | 1 | 2010-07-02| 40.0 |
Here's the temporaldata table:
| store | weekdate | temperature | fuelprice | cpi | unemploymentrate|
| 1 |2010-07-01| 100.14. | 2.981 | 125.1222 | 9.402
| 2 |2010-07-02| 99.13 | 3.823 | 129.2912 | 14.81
So, I wrote code like below. Could you give how to solve this question.
DROP TABLE IF EXISTS CorrelationGroup
CREATE TABLE CorrelationGroup(
attributName VARCHAR(30),
CorrelationSign INTEGER
);
SELECT SUM(weeklysales)
FROM temporaldate t, sales s
WHERE s.weekdate = t.weekdate AND s.store = t.store ;
SELECT AVG(t.temperature) as 'temp', AVG(t.fuelprice) as 'fuel', AVG(t.cpi) as 'fuel', AVG(t.unemploymentrate) as'unemploymentrate
FROM sales s, temporaldate t
WHERE s.store = t.store AND s.weekdate = t.weekdate;
INSERT INTO CorrelationGroup VALUES ('Temp', 'The Value Of CorrelationSign');
-- 3 more

How to update a table cells from another table's columns with different conditions?

Let assume that I have two tables in my database as below:
Table 1 (current_prices): Which contains some stuffs and their prices and it updates one time per day:
# current_prices
| commodity | price |
____________________
| stuff1 | price1
| stuff2 | price2
| stuff3 | price3
|. |
|. |
|. |
| stuffN | priceN
Table 2 (stat_history): Which divide stuffs in price ranges and keep the number of elements of each range for all days as below:
# stat_history
| date | range1_count | range2_count | range3_count
________________________________________________________
| 20200411 | 12 | 5 | 9
| 20200412 | 10 | 5 | 11
| 20200413 | 13 | 4 | 9
| 20200414 | 15 | 3 | 8
The content of stat_history table are generated from current_price contents at the end of the day.
Currently I use multiple Update-Insert (Upsert) queries to update my stat_history table as below:
insert into stat_history (date, range1_count)
select now()::date , count(stuff) as range1_count from current_prices
where 0 < price and price < VAL1
on conflict(day)
update set
range1_count = excluded.range1_count
insert into stat_history (date, range2_count)
select now()::date , count(stuff) as range2_count from current_prices
where VAL1 < price and price < VAL2
on conflict(day)
update set
range2_count = excluded.range2_count
..... (blah blah)
The question is:
Is there any shorter, simpler or more efficient way to do this (In a single SQL query for example)?
You could do conditional counts, using Postgres standard filter clause:
insert into stat_history (date, range1_count)
select
now()::date,
count(stuff) filter(where price >= 0 and price < VAL1) as range1_count,
count(stuff) filter(where price >= VAL1 and price < VAL2) as range2_count
from current_prices
where price >= 0 and price < VAL2
on conflict(day)
update set
range1_count = excluded.range1_count
range2_count = excluded.range2_count
Notes:
I adapted the logic that puts rows in the intervals to make them contiguous (in your original query for example, a price that is equal to VA1 would never be counted in)
with this logic at hand, you might not even need the on conflict clause

SQL union / join / intersect multiple select statements

I have two select statements. One gets a list (if any) of logged voltage data in the past 60 seconds and related chamber names, and one gets a list (if any) of logged arc event data in the past 5 minutes. I am trying to append the arc count data as new columns to the voltage data table. I cannot figure out how to do this.
Note that, there may or may not be arc count rows, for a given chamber name that is in the voltage data table. If there are no rows, I want to set the arc count column value to zero.
Any ideas on how to accomplish this?
Voltage Data:
SELECT DISTINCT dbo.CoatingChambers.Name,
AVG(dbo.CoatingGridVoltage_Data.ChanA_DCVolts) AS ChanADC,
AVG(dbo.CoatingGridVoltage_Data.ChanB_DCVolts) AS ChanBDC,
AVG(dbo.CoatingGridVoltage_Data.ChanA_RFVolts) AS ChanARF,
AVG(dbo.CoatingGridVoltage_Data.ChanB_RFVolts) AS ChanBRF FROM
dbo.CoatingGridVoltage_Data LEFT OUTER JOIN dbo.CoatingChambers ON
dbo.CoatingGridVoltage_Data.CoatingChambersID =
dbo.CoatingChambers.CoatingChambersID WHERE
(dbo.CoatingGridVoltage_Data.DT > DATEADD(second, - 60,
SYSUTCDATETIME())) GROUP BY dbo.CoatingChambers.Name
Returns
Name | ChanADC | ChanBDC | ChanARF | ChanBRF
-----+-------------------+--------------------+---------------------+------------------
OX2 | 2.9099999666214 | -0.485000004371007 | 0.344801843166351 | 0.49748428662618
S2 | 0.100000001490116 | -0.800000016887983 | 0.00690172302226226 | 0.700591623783112
S3 | 4.25666658083598 | 0.5 | 0.96554297208786 | 0.134956782062848
Arc count table:
SELECT CoatingChambers.Name,
SUM(ArcCount) as ArcCount
FROM CoatingChambers
LEFT JOIN CoatingArc_Data
ON dbo.[CoatingArc_Data].CoatingChambersID = dbo.CoatingChambers.CoatingChambersID
where EventDT > DATEADD(mi,-5, GETDATE())
Group by Name
Returns
Name | ArcCount
-----+---------
L1 | 283
L4 | 0
L6 | 1
S2 | 55
To be clear, I want this table (with added arc count column), given the two tables above:
Name | ChanADC | ChanBDC | ChanARF | ChanBRF | ArcCount
-----+-------------------+--------------------+---------------------+-------------------+---------
OX2 | 2.9099999666214 | -0.485000004371007 | 0.344801843166351 | 0.49748428662618 | 0
S2 | 0.100000001490116 | -0.800000016887983 | 0.00690172302226226 | 0.700591623783112 | 55
S3 | 4.25666658083598 | 0.5 | 0.96554297208786 | 0.134956782062848 | 0
You can treat the select statements as virtual tables and just join them together:
select
x.Name,
x.ChanADC,
x.ChanBDC,
x.ChanARF,
x.ChanBRF,
isnull( y.ArcCount, 0 ) ArcCount
from
(
select distinct
cc.Name,
AVG(cgv.ChanA_DCVolts) AS ChanADC,
AVG(cgv.ChanB_DCVolts) AS ChanBDC,
AVG(cgv.ChanA_RFVolts) AS ChanARF,
AVG(cgv.ChanB_RFVolts) AS ChanBRF
from
dbo.CoatingGridVoltage_Data cgv
left outer join
dbo.CoatingChambers cc
on
cgv.CoatingChambersID = cc.CoatingChambersID
where
cgv.DT > dateadd(second, - 60, sysutcdatetime())
group by
cc.Name
) as x
left outer join
(
select
cc.Name,
sum(ac.ArcCount) as ArcCount
from
dbo.CoatingChambers cc
left outer join
dbo.CoatingArc_Data ac
on
ac.CoatingChambersID = cc.CoatingChambersID
where
EventDT > dateadd(mi,-5, getdate())
group by
Name
) as y
on
x.Name = y.Name
Also, it's worthwhile to simplify your names with aliases and format the queries for readability...which I shamelessly took a stab at.

how to get daily profit from sql table

I'm stucking for a solution at the problem of finding daily profits from db (ms access) table. The difference wrt other tips I found online is that I don't have in the table a field "Price" and one "Cost", but a field "Type" which distinguish if it is a revenue "S" or a cost "C"
this is the table "Record"
| Date | Price | Quantity | Type |
-----------------------------------
|01/02 | 20 | 2 | C |
|01/02 | 10 | 1 | S |
|01/02 | 3 | 10 | S |
|01/02 | 5 | 2 | C |
|03/04 | 12 | 3 | C |
|03/03 | 200 | 1 | S |
|03/03 | 120 | 2 | C |
So far I tried different solutions like:
SELECT
(SELECT SUM (RS.Price* RS.Quantity)
FROM Record RS WHERE RS.Type='S' GROUP BY RS.Data
) as totalSales,
(SELECT SUM (RC.Price*RC.Quantity)
FROM Record RC WHERE RC.Type='C' GROUP BY RC.Date
) as totalLosses,
ROUND(totalSales-totaleLosses,2) as NetTotal,
R.Date
FROM RECORD R";
in my mind it could work but obviously it doesn't
and
SELECT RC.Data, ROUND(SUM (RC.Price*RC.QuantitY),2) as DailyLoss
INTO #DailyLosses
FROM Record RC
WHERE RC.Type='C' GROUP BY RC.Date
SELECT RS.Date, ROUND(SUM (RS.Price*RS.Quantity),2) as DailyRevenue
INTO #DailyRevenues
FROM Record RS
WHERE RS.Type='S'GROUP BY RS.Date
SELECT Date, DailyRevenue - DailyLoss as DailyProfit
FROM #DailyLosses dlos, #DailyRevenues drev
WHERE dlos.Date = drev.Date";
My problem beyond the correct syntax is the approach to this kind of problem
You can use grouping and conditional summing. Try this:
SELECT data.Date, data.Income - data.Cost as Profit
FROM (
SELECT Record.Date as Date,
SUM(IIF(Record.Type = 'S', Record.Price * Record.Quantity, 0)) as Income,
SUM(IIF(Record.Type = 'C', Record.Price * Record.Quantity, 0)) as Cost,
FROM Record
GROUP BY Record.Date
) data
In this case you first create a sub-query to get separate fields for Income and Cost, and then your outer query uses subtraction to get actual profit.

Multiplying values from two Access tables

I have two tables as below in Accecss 2007.
Town Table
----------
TownName | FlatCount | DetachedCount | SemiCount
A | 5 | 3 | 4
B | 2 | 6 | 3
Cost Table
----------
Prop | PCost
Flat | 10
Detached | 20
Semi | 30
I would like to get an output like below by multiplying the Count from the Town table with the corresponding PCost in the Cost table. FlatCost = Town.FlatCount * Cost.PCost for a flat.
Results
-------
Town | FlatCount | FlatCost | DetachedCount | DetachedCost | .....
A | 5 | 50 | 3 | 60 |
B | 2 | 20 | 6 | 120 |
I have tried to do this by using IIF, but not sure how to get PCost for each property type within the IIF clause.
Thanks
Looks like you are mixing data and meta data e.g. the data value Flat in table Cost becomes metadata value (column name) FlatCount in table Town. This is not a good idea and is probably why you are having difficulties writing what should be a simply query.
Restructure your Town table so that it has columns TownName, Prop and PCount. And remember that most bad SQL DML is caused by bad SQL DDL ;)
You could use a subquery to retrieve the cost of an item:
select TownName
, FlatCount
, FlatCount * (select PCost from Cost where Prop = 'Flat') as FlatCost
, DetachedCount
, DetachedCount * (select PCost from Cost where Prop = 'Detached')
as DetachedCost
, ...
from Town
You have to cross join the tables. Then, for good values, put PCost in the multiplication, else, put 0.
You can then do a SUM using a Group by :
SELECT t.Town,
t.FlatCount,
SUM(t.FlatCount * IIF(c.Prop = 'Flat', c.PCost, 0)) AS FlatCost,
t.DetachedCount,
SUM(t.DetachedCount * IIF(c.Prop = 'Detached', c.PCost, 0)) AS DetachedCost,
FROM Town t, Cost c
GROUP BY t.Town, t.FlatCount, t.DetachedCount