Replace < 1 values in an aggregate SUM T-SQL query - sql-server-2008

I am try to avoid using #temp to get an average where a field from table contains just a few less than 1 values. Convert to varchar, replace then convert back seems ugly. Suggestions very welcomed!!
Select
(select cmp_zip from company where cmp_id = ord_originpoint) as OriginZip,
ord_originpoint as OriginPoint,
replace((select cty_nmstct from company where cmp_id = ord_originpoint),'/','') as Origin_City_State,
(select cmp_zip from company where cmp_id = ord_destpoint) as DestZip,
ord_destpoint as DestPoint,
replace((select cty_nmstct from company where cmp_id = ord_destpoint),'/','') as Dest_City_State,
COUNT(ord_hdrnumber) as OrdCount,
SUM(ord_rate)/COUNT(ord_hdrnumber) as AvgRate,
SUM(ord_totalmiles)/COUNT(ord_hdrnumber) as AvgMiles,
(SUM(ord_rate) / COUNT(ord_hdrnumber)) / (SUM(ord_totalmiles) / COUNT(ord_hdrnumber)) as AvgRevperMiles
from
orderheader (NOLOCK)
where ord_billto <> 'CSXJAC01'
and ord_revtype1 = 'NE'
and ord_status = 'CMP'
and ord_bookdate > GETDATE() - 730
and ord_completiondate < GETDATE()
and ord_totalmiles > 0
group by
ord_originpoint,ord_destpoint
order by
OrdCount desc
If I exclude columns less than zero in the totalmiles column I get :
+-------+----------+---------------+-------+-------+----------+-----------------+-------+---+------+-----+
| 8850 | DSDMIL | MILLTOWN | NJMID | 7206 | NORELI | ELIZABETH | NJ | 1 | 243 | 25 |
| 7047 | CSXNOR | NORTH BERGEN | NJ | 11550 | NASHEM | HEMPSTEAD | NYNAS | 1 | 492 | 34 |
| 7047 | CSXNOR | NORTH BERGEN | NJ | 7022 | PERFAI | FAIRVIEW | NJBER | 1 | 190 | 1 |
| 17013 | PEPCAR01 | CARLISLE | PA | 21224 | CSXBAL | BALTIMORE | MD | 1 | 350 | 97 |
| 23944 | GARKEN | KENBRIDGE | VA | 21224 | CSXBAL | BALTIMORE | MD | 1 | 814 | 230 |
| 21224 | CSXBAL | BALTIMORE | MD | 18202 | HAZHAZ04 | HAZLETON | PA | 1 | 621 | 161 |
| 17055 | WOOMEC | MECHANICSBURG | PACUM | 21224 | CSXBAL | BALTIMORE | MD | 1 | 355 | 85 |
| 23139 | MORPOW01 | POWHATAN | VA | 21224 | CSXBAL | BALTIMORE | MD | 1 | 1376 | 186 |
| 17109 | PEPHAR | HARRISBURG | PA | 21224 | CSXBAL | BALTIMORE | MD | 1 | 350 | 78 |
| 21224 | CSXBAL | BALTIMORE | MD | 20066 | WASWAS05 | WASHINGTON | DC | 1 | 675 | 54 |
| 21224 | CSXBAL | BALTIMORE | MD | 20743 | GRACAP | CAPITOL HEIGHTS | MD | 1 | 300 | 45 |
| 7047 | CSXNOR | NORTH BERGEN | NJ | 7866 | ROCROC05 | ROCKAWAY | NJ | 1 | 243 | 34 |
| 21224 | CSXBAL | BALTIMORE | MD | 20772 | BOBUPP | UPPER MARLBORO | MD | 1 | 283 | 37 |
+-------+----------+---------------+-------+-------+----------+-----------------+-------+---+------+-----+

I'm still not entirely sure of the exact question that you're asking, but the way that you're doing subqueries within your SELECT statement is always going to be poor for performance, try using correct joins, something like this;
SELECT
c1.cmp_zip AS OriginZip
,oh.ord_originpoint AS OriginPoint
,replace(c1.cty_nmstct),'/','') AS Origin_City_State
,c2.cmp_zip AS DestZip
,oh.ord_destpoint AS DestPoint
,replace(c2.cty_nmstct,'/','') AS Dest_City_State
,COUNT(oh.ord_hdrnumber) AS OrdCount
,SUM(oh.ord_rate)/COUNT(oh.ord_hdrnumber) AS AvgRate
,SUM(oh.ord_totalmiles)/COUNT(oh.ord_hdrnumber) AS AvgMiles
,(SUM(oh.rd_rate)/COUNT(oh.ord_hdrnumber))/( SUM(oh.ord_totalmiles)/COUNT(oh.ord_hdrnumber)) AS AvgRevperMiles
FROM orderheader oh
LEFT JOIN company c1
ON oh.ord_originpoint = c1.cmp_id
LEFT JOIN company c2
ON oh.ord_destpoint = c2.cmp_id
WHERE oh.ord_billto <> 'CSXJAC01'
AND oh.ord_revtype1 = 'NE'
AND oh.ord_status = 'CMP'
AND oh.ord_bookdate > GETDATE() - 730
AND oh.ord_completiondate < GETDATE()
AND oh.ord_totalmiles > 0
GROUP BY
c1.cmp_zip
,oh.ord_originpoint
,replace(c1.cty_nmstct),'/','')
,c2.cmp_zip
,oh.ord_destpoint
,replace(c2.cty_nmstct,'/','')
ORDER BY OrdCount DESC
You may need to tweak the odd field (i'm not sure the table that contains the field rd_rate for example, althouth this may be a typo for ord_rate at a guess)

Related

MYSQL Select using Left Join, Count, Group by

I have this query:
SELECT c.`id`, w.`qty`, COUNT(c.`id`) AS pieces, c.`location`
FROM `control` AS c
LEFT JOIN `warehouse` AS w ON w.`id` = c.`id`
WHERE c.`code` = '40'
GROUP BY c.`id`
I have these tables:
`control` c `warehouse` w
+----+--------+------+----------+ +------+-------+
| id | pieces | code | location | | id | qty |
+----+--------+------+----------+ +------+-------+
| 112| 112-1 | 40 | london | | 112 | 3 |
| 112| 112-2 | 40 | london | | 113 | 3 |
| 112| 112-3 | 40 | london | | 114 | 1 |
| 113| 113-1 | 40 | italy | | 115 | 1 |
| 113| 113-2 | 40 | italy | +--------------+
| 113| 113-3 | 40 | italy |
| 114| 114-1 | 41 | france |
| 115| 115-1 | 41 | france |
| 112| 112-1 | 40 | germany |
| 112| 112-2 | 40 | germany |
| 112| 112-3 | 40 | germany |
| 113| 112-1 | 40 | russia |
| 113| 112-2 | 40 | russia |
| 113| 112-3 | 40 | russia |
| 112| 112-1 | 40 | poland |
| 112| 112-2 | 40 | poland |
| 112| 112-3 | 40 | poland |
+-------------------------------+
Im getting this:
actual output
+-----+-----+--------+----------+
| id | qty | pieces | location |
+-----+-----+--------+----------+
| 112 | 3 | 9 | poland |
| 113 | 3 | 6 | russia |
+-------------------------------+
I'm trying to get this result:
desired output
+-----+-----+--------+----------+
| id | qty | pieces | location |
+-----+-----+--------+----------+
| 112 | 3 | 3 | london |
| 113 | 3 | 3 | italy |
| 112 | 3 | 3 | germany |
| 113 | 3 | 3 | russia |
| 112 | 3 | 3 | poland |
+-------------------------------+
Is possible this result? maybe tweaking my query?
I tried without GROUP BY but in that case i just get 1 row totalizing pieces.
If you want to separate the different locations to different rows, you need to add that column to the group by clause:
SELECT c.`id`, w.`qty`, COUNT(c.`id`) AS pieces, c.`location`
FROM `control` AS c
LEFT JOIN `warehouse` AS w ON w.`id` = c.`id`
WHERE c.`code` = '40'
GROUP BY c.`id`, c.`location`
-- Here ---------^
I suspect that you just need to add qty and location to the group by clause:
SELECT c.`id`, w.`qty`, COUNT(*) AS pieces, c.`location`
FROM `control` AS c
LEFT JOIN `warehouse` AS w ON w.`id` = c.`id`
WHERE c.`code` = '40'
GROUP BY c.`id`, w.`qty`, c.`location`
Starting MySQL 5.7, it is mandatory to list all-non aggregated columns in the group by clause (unless you change default sql option ONLY_FULL_GROUP_BY); most other databases also implement this constraint. I would recommend getting used to it...
Side notes:
COUNT(c.id) is better written COUNT(*), since id seems like a not nullable column
generally spearking, you shoud avoid using backticks around table and column names unless when absolutly necessary.

MySQL returns bad result

I have question about SELECT FROM WHERE statement, which returns me bad result.
Here is my table called friends:
+----------+-----------+------------+--------+--------+-------+
| lastname | firstname | callprefix | phone | region | zip |
+----------+-----------+------------+--------+--------+-------+
| Lužný | Bob | 602 | 111222 | OL | 79821 |
| Matyáš | Bob | 773 | 123456 | BR | NULL |
| Strouhal | Fido | 300 | 343434 | ZL | 76701 |
| Přikryl | Tom | 581 | 010101 | PL | 72000 |
| Černý | Franta | 777 | 000999 | OL | 79801 |
| Zavadil | Olda | 911 | 111311 | OL | 79604 |
| Berka | Standa | 604 | 111234 | ZL | 72801 |
| Vlcik | BbB | 736 | 555444 | KV | 35210 |
+----------+-----------+------------+--------+--------+-------+
And here is my query.
SELECT * FROM friends WHERE region <= 'z';
I would expect that the rows with region ZL should be present, but they are not. Can you please tell me why?
Result is:
+----------+-----------+------------+--------+--------+-------+
| lastname | firstname | callprefix | phone | region | zip |
+----------+-----------+------------+--------+--------+-------+
| Lužný | Bob | 602 | 111222 | OL | 79821 |
| Matyáš | Bob | 773 | 123456 | BR | NULL |
| Přikryl | Tom | 581 | 010101 | PL | 72000 |
| Černý | Franta | 777 | 000999 | OL | 79801 |
| Zavadil | Olda | 911 | 111311 | OL | 79604 |
| Vlcik | BbB | 736 | 555444 | KV | 35210 |
+----------+-----------+------------+--------+--------+-------+
When I try this query:
SELECT * FROM friends WHERE region >= 'z';
the result contains both rows with region = 'ZL'
????
Thank you!
Because "ZL" is greater than "Z." Z is just one character so will only return values less that Z or with the value of Z. What are you trying to achieve with this query?
Can you please tell me why?
If you add a record where region is Z, and sorted those rows alphabetically by region, would you expect ZL to come before or after Z? Obviously it would come after, so it does not meet your criteria.
If you want to only consider the first character, then add that to your criteria:
SELECT * FROM friends WHERE LEFT(region,1) <= 'Z';
I would also make Z explicitly a capital letter in case your database settings make it a case-sensitive search.
Have you tried
SELECT * FROM friends WHERE region <= 'zl';?
From the computer's perspective, 'z' < 'zl'

mysql: comparing two columns

my tables and their layout:
mysql> select * FROM xt_shipping_zones;
+---------+-------------+---------------------------------------------------------------------------+
| zone_id | zone_name | zone_countries |
+---------+-------------+---------------------------------------------------------------------------+
| 5 | ZONE1 | AT,BE,BG,DK,FI,FR,GR,IE,IT,LV,LT,LU,MC,NL,PL,PT,RO,SM,SE,SK,SI,ES,HU,GB |
| 6 | Deutschland | DE |
| 8 | ZONE2Brutto | AD,NO,VA |
| 9 | ZONE2NETTO | CH,LI |
+---------+-------------+---------------------------------------------------------------------------+
mysql> select * FROM xt_shipping_cost WHERE shipping_geo_zone = 99995 LIMIT 5;
+------------------+-------------+-------------------+-----------------------+--------------------------+------------------------+----------------+------------------+
| shipping_cost_id | shipping_id | shipping_geo_zone | shipping_country_code | shipping_type_value_from | shipping_type_value_to | shipping_price | shipping_allowed |
+------------------+-------------+-------------------+-----------------------+--------------------------+------------------------+----------------+------------------+
| 269 | 34 | 99995 | | 0.31 | 17.99 | 17.0000 | 1 |
| 270 | 34 | 99995 | | 17.99 | 35.99 | 34.0000 | 1 |
| 271 | 34 | 99995 | | 35.99 | 53.99 | 51.0000 | 1 |
| 272 | 34 | 99995 | | 53.99 | 71.99 | 68.0000 | 1 |
| 273 | 34 | 99995 | | 71.99 | 89.99 | 85.0000 | 1 |
+------------------+-------------+-------------------+-----------------------+--------------------------+------------------------+----------------+------------------+
mysql> SELECT * FROM geoip WHERE 92569600 BETWEEN start AND end;
+----------+----------+---------+-----+
| start | end | country | id |
+----------+----------+---------+-----+
| 92569600 | 92585983 | AT | 895 |
+----------+----------+---------+-----+
My Query:
SELECT
xt_shipping_cost.shipping_type_value_from,
xt_shipping_cost.shipping_type_value_to,
xt_shipping_cost.shipping_price,
geoip.country
FROM xt_shipping_cost
INNER JOIN xt_shipping_zones
ON xt_shipping_cost.shipping_geo_zone = xt_shipping_zones.zone_id + 99990
INNER JOIN geoip
ON geoip.country REGEXP xt_shipping_zones.zone_countries
WHERE 34664448 BETWEEN geoip.start AND geoip.end
My Problem:
Query is working if there is only ONE entry in xt_shipping_zones.zone_countries like DE. If there are multiple (with comma seperated entries) i cant get a match on that row.
Doing it manually:
mysql> SELECT * FROM `xt_shipping_zones` WHERE `zone_countries` REGEXP 'AT';
+---------+-----------+---------------------------------------------------------------------------+
| zone_id | zone_name | zone_countries |
+---------+-----------+---------------------------------------------------------------------------+
| 5 | ZONE1 | AT,BE,BG,DK,FI,FR,GR,IE,IT,LV,LT,LU,MC,NL,PL,PT,RO,SM,SE,SK,SI,ES,HU,GB |
+---------+-----------+---------------------------------------------------------------------------+
SQLFiddle: http://sqlfiddle.com/#!9/68f8d0/1
I hope i didn't failed to much to make my problem clear.
Thank you
I think you can use find_in_set()
SELECT
xt_shipping_cost.shipping_type_value_from,
xt_shipping_cost.shipping_type_value_to,
xt_shipping_cost.shipping_price,
geoip.country
FROM xt_shipping_cost
INNER JOIN xt_shipping_zones
ON xt_shipping_cost.shipping_geo_zone = xt_shipping_zones.zone_id + 99990
INNER JOIN geoip
ON find_in_set(geoip.country, xt_shipping_zones.zone_countries)
WHERE 34664448 BETWEEN geoip.start AND geoip.end
It is no good idea to store the values as csv. That is very bad database design.

how to subsort of LEFT JOIN in MySQL query?

How can I build a query to sort the results by vl_name (vldescription) alphabetically, and subsort table vllinks by vlk_addeddate from the latest with internal limit equal to 1?
SELECT
aa.vl_id, aa.vl_name, aa.vl_code, aa.vl_vcc, aa.vl_description,
bb.vlk_id, bb.vlk_vlid, bb.vlk_link, bb.vlk_platform, bb.vlk_location, bb.vlk_addeddate,
le.vop_vlid, le.vop_thumbnail,
cx.vcc_id, cx.vcc_type, cx.vcc_brand, cx.vcc_variant
FROM vldescription AS aa
LEFT JOIN vllinks AS bb ON aa.vl_id = bb.vlk_vlid
LEFT JOIN vlofferphotos AS le ON le.vop_vlid = aa.vl_id
LEFT JOIN vlcarcats AS cx ON cx.vcc_id = aa.vl_vcc
WHERE aa.vl_vcc = '$change_me_if_you_need'
GROUP BY vl_id
ORDER BY vl_name
Table vlcarcats (vcc_
vcc_id | vcc_type | vcc_brand | vcc_variant
1 | OpenPlace | SomeCorp1 | website
2 | ForPrive | SomeCorp2 | other way
Table vldescription
vl_id | vl_name | vl_code | vl_vcc | vl_description
1 | OpTECC | xDAOcm | 1023 | text, text,...
2 | NewCop | d9MMo2 | 42 | more text,...
Table vllinks (vlk_vlid == vl_id)
vlk_id | vlk_vlid | vlk_link | vlk_platform | vlk_location | vlk_addeddate
1 | 1 | http://... | 1 | USA | 2014-01-10
2 | 2 | http://... | 1 | UK | 2014-01-12
3 | 2 | ftp://... | 2 | UK | 2014-01-15
4 | 2 | ftp://... | 2 | India | 2014-01-19
5 | 1 | ftp://... | 2 | Austria | 2014-01-22
Table vlofferphotos (vop_vlid == vl_id)
vop_vlid | vop_thumbnail
1 | abcdefg.jpg
2 | hijklmn.jpg

Minimum price selection for product when database stores the prices in differrent currencies

I geather prices for products in different online store which are in United States (us), United Kingdom (uk) and etc. and hold it in one mysql TABLE (com) in local currency.
For example, for United States price in US Dollar, for United Kingdom - in GB pound.
TABLE com
---------------------------------------------------------------------------------------
| AUTO_INC | COUNTER | ID | CONC | VOLUME | PRICE | SHOP | DATE_G | COUNTRY |
|----------|---------|------|-----------|--------|-------|-----------|--------|---------|
| 115124 | 76720 | 2399 | prod_name | 13 | 34.23 | store1.us | 3 | us |
| 115186 | 50952 | 2399 | prod_name | 13 | 36 | store2.us | 3 | us |
| 115187 | 45828 | 2399 | prod_name | 13 | 37.44 | store3.us | 3 | us |
| 116448 | 73419 | 2399 | prod_name | 11.6 | 48 | store4.us | 3 | us |
| 116449 | 73421 | 2399 | prod_name | 13 | 65.5 | store4.us | 3 | us |
| 133334 | 22154 | 2399 | prod_name | 13 | 36.95 | store5.us | 4 | us |
| 133386 | 31646 | 2399 | prod_name | 13 | 37.44 | store3.us | 4 | us |
| 134828 | 54667 | 2399 | prod_name | 11.6 | 48 | store4.us | 4 | us |
| 134929 | 54670 | 2399 | prod_name | 13 | 65.5 | store4.us | 4 | us |
| 133337 | 22155 | 2399 | prod_name | 13 | 26.95 | store1.uk | 4 | uk |
| 133387 | 31647 | 2399 | prod_name | 13 | 17.44 | store2.uk | 4 | uk |
| 134829 | 54668 | 2399 | prod_name | 11.6 | 30 | store3.uk | 4 | uk |
| 134830 | 54671 | 2399 | prod_name | 13 | 45.5 | store4.uk | 4 | uk |
---------------------------------------------------------------------------------------
To convert prices from local currencies to US dollar I have created TABLE my_currency
TABLE my_currency
--------------------------------------
| AUTO_INC | DOMAIN | EX_RATE | DATE_G |
|----------|--------|---------|--------|
| 235 | uk | 0.6066 | 4 |
| 236 | us | 1 | 4 |
| 237 | uk | 0.6066 | 3 |
| 238 | us | 1 | 3 |
--------------------------------------
If I want to select minimum prices for us online stores (where COUNTRY = 'us'), I use the following query:
SELECT t1.* FROM com as t1
INNER JOIN (
SELECT id, conc, volume, min(price) as usd_price, date_g
FROM com
WHERE id=2399 AND date_g=4 AND country='us'
GROUP BY conc, volume) as t2
ON t1.conc=t2.conc and t1.volume=t2.volume and t1.id=t2.id and t1.price=t2.usd_price and t1.date_g=t2.date_g
ORDER BY conc DESC, volume DESC
And I get CORRECT result:
| AUTO_INC | COUNTER | ID | CONC | VOLUME | PRICE | SHOP | DATE_G | COUNTRY |
|----------|---------|------|-----------|--------|-------|-----------|--------|---------|
| 133334 | 22154 | 2399 | prod_name | 13 | 36.95 | store5.us | 4 | us |
| 134828 | 54667 | 2399 | prod_name | 11.6 | 48 | store4.us | 4 | us |
But now my goal is to select minimum price from all stores (country in ('us', 'uk')) and take into account that database holds prices in local currencies:
1. convert prices from local currencies to us dollar
2. select minimum prices in us dollar
So I tried to use the following query:
SELECT t1.auto_inc, t1.id, t1.conc, t1.volume, (t1.price / my_currency.ex_rate) as sub_price, t1.date_g
FROM com as t1
inner join my_currency
ON t1.country=my_currency.domain AND t1.date_g=my_currency.date_g
inner join (
select com.id, com.conc, com.volume, min(com.price / my_currency.ex_rate) as usd_price, com.date_g
from com
inner join my_currency
ON com.country=my_currency.domain AND com.date_g=my_currency.date_g
WHERE com.id=2399 AND com.date_g=4 AND com.country in ('us', 'uk')
GROUP BY conc, volume) as t2
on
t1.id=t2.id and
t1.conc=t2.conc and
t1.volume=t2.volume and
(t1.price / my_currency.ex_rate)=t2.usd_price and
t1.date_g=t2.date_g
ORDER BY conc DESC, volume DESC
But I get INCORRECT result:
| AUTO_INC | ID | CONC | VOLUME | SUB_PRICE | DATE_G |
|----------|------|-----------|--------|-----------|--------|
| 134828 | 2399 | prod_name | 11.6 | 48 | 4 |
CORRECT result is:
| AUTO_INC | ID | CONC | VOLUME | SUB_PRICE | DATE_G |
|----------|------|-----------|--------|-----------|--------|
| 134828 | 2399 | prod_name | 11.6 | 48 | 4 |
|----------|------|-----------|--------|-----------|--------|
| 133387 | 2399 | prod_name | 13 | 28.750412 | 4 |
Does anybody have any ideas?
sqlfiddle.com
The problem is the join on the calculated decimal value. The following works:
SELECT t1.auto_inc, t1.id, t1.conc, t1.volume,
(t1.price / my_currency.ex_rate) as sub_price, t1.date_g
FROM com as t1 inner join
my_currency
ON t1.country = my_currency.domain AND t1.date_g = my_currency.date_g
inner join
(select com.id, com.conc, com.volume, min(com.price / my_currency.ex_rate) as usd_price, com.date_g
from com inner join
my_currency
ON com.country = my_currency.domain AND
com.date_g = my_currency.date_g
WHERE com.id=2399 AND com.date_g = 4 AND
com.country in ('us', 'uk')
GROUP BY com.id, com.conc, com.volume
) as t2
on t1.id = t2.id and
t1.conc = t2.conc and
t1.volume = t2.volume and
abs((t1.price / my_currency.ex_rate) - t2.usd_price) < 0.01 and
t1.date_g = t2.date_g
ORDER BY conc DESC, volume DESC;
However, if you change the join condition on price to:
t1.price / my_currency.ex_rate = t2.usd_price and
Then it doesn't work.
It does work if you cast both results back to decimal(10, 2):
cast(t1.price / my_currency.ex_rate as decimal(10, 2)) = cast(t2.usd_price as decimal(10, 2))
It might have something to do with this note in the documentation:
In division performed with /, the scale of the result when using two
exact-value operands is the scale of the first operand plus the value
of the div_precision_increment system variable (which is 4 by
default). For example, the result of the expression 5.05 / 0.014 has a
scale of six decimal places (360.714286)