How to show count of sub elements in Group By query - Pivoted - sql-server

We have cars table.
It has marques:
Nissan
Toyota
BMW
We have cities:
New York
Los Angeles
Dallas
Table:
Id
Marque
CityId
It has all 250 000 cars for the 3 cities.
How do we show them grouped by city and count, but the cities are columns.
This is my query:
SELECT Count(veh.id) [Count],pd.District, vet.Name FROM Vehicles veh
INNER JOIN PostalDistricts pd on pd.Id = veh.PostalDistrictId
INNER JOIN VehicleMarqueId vet on vet.id = veh.VehicleMarqueId
GROUP BY pd.District, vet.Name
ORDER BY Count(veh.id) DESC, pd.District asc
But the result is:
+-------+-------------+--------+
| Count | City | Marque |
+-------+-------------+--------+
| 9547 | New York | Toyota |
| 3509 | Dallas | Toyota |
| 2608 | Los Angeles | Toyota |
| 2545 | New York | Nissan |
| 2107 | Dallas | Nissan |
| 1780 | Los Angeles | Nissan |
+-------+-------------+--------+
Expected is:
+-------------+--------+--------+
| City | Toyota | Nissan |
+-------------+--------+--------+
| Dallas | 3509 | 2107 |
| Los Angeles | 2608 | 1780 |
| New York | 9547 | 2545 |
+-------------+--------+--------+

You can use the pivot statement:
declare #tmp table ([count] int , City varchar(50), Marque nvarchar(50))
insert into #tmp values
(9547, 'New York', 'Toyota')
,(3509, 'Dallas', 'Toyota')
,(2608, 'Los Angeles', 'Toyota')
,(2545, 'New York', 'Nissan')
,(2107, 'Dallas', 'Nissan')
,(1780, 'Los Angeles', 'Nissan')
select * from #tmp
pivot
(
max([count])
for Marque in ([Toyota], [Nissan] )
) piv
Results:
But if you have more values in the Marque column you have to use dynamic TSQL to generate all the columns needed

Related

How can I return the last 5 rows from the updated table?

I'm trying to make a query where I want to update the last 5 rows only and get the values(more specifically the column city) updated
+----+-------------+-----------+
| id | city | is_active |
+----+-------------+-----------+
| 1 | Los Angeles | false |
+----+-------------+-----------+
| 2 | London | false |
+----+-------------+-----------+
| 3 | Barcelona | false |
+----+-------------+-----------+
| 4 | Madrid | false |
+----+-------------+-----------+
| 5 | Sydney | false |
+----+-------------+-----------+
| 6 | Melbourne | false |
+----+-------------+-----------+
I tried this:
WITH updated AS (UPDATE table_name SET is_active = true RETURNING city) SELECT * FROM table_name WHERE city IN (SELECT city FROM updated ORDER BY id ASC LIMIT 5);
This returns me the last rows exactly as I want but it updates ALL rows in true, what I'm missing on the UPDATE line? I would like to affect/update just the last 5 rows in true
+----+-------------+-----------+
| id | city | is_active |
+----+-------------+-----------+
| 2 | London | true |
+----+-------------+-----------+
| 3 | Barcelona | true |
+----+-------------+-----------+
| 4 | Madrid | true |
+----+-------------+-----------+
| 5 | Sydney | true |
+----+-------------+-----------+
| 6 | Melbourne | true |
+----+-------------+-----------+
Using psql.
CREATE TABLE city_test(id integer, city varchar, is_active boolean);
INSERT INTO
city_test
VALUES
(1, 'Los Angeles', false),
(2, 'London', false),
(3, 'Barcelona', false),
(4, 'Madrid', false),
(5, 'Sydney', false),
(6, 'Melbourne',false);
UPDATE
city_test
SET
is_active = true
WHERE
id IN
(SELECT id FROM city_test ORDER BY id DESC LIMIT 5)
RETURNING city;
city
-----------
London
Barcelona
Madrid
Sydney
Melbourne
SELECT * FROM city_test ;
id | city | is_active
----+-------------+-----------
1 | Los Angeles | f
2 | London | t
3 | Barcelona | t
4 | Madrid | t
5 | Sydney | t
6 | Melbourne | t

How to combine cell value to column name with CASE logic in SQL

I have two VIEWS where data is represented as below:
VIEW 1
| username | function | level | location |
|:-----------|------------:|:------------:| :-------------:|
| John | Operation | Country | United Kingdom |
| John | Operation | Area | South West |
| John | Operation | Shop | 0001 |
VIEW 2
| shop | region | country | area |
|:-----------|------------:|:---------------:| :-------------:|
| 1200 | u1 | United Kingdom | West |
| 1201 | u2 | United Kingdom | West |
| 1000 | f1 | France | South West |
| 1100 | i1 | Italy | South West |
| 1111 | s1 | Spain | South West |
| 1112 | n2 | Norway | South West |
| 0001 | o1 | Japan | Asia |
The relation between the two VIEWs are that each AREA has more than one Country, each Country has more than one REGION, each REGION has more than one shop.
Trying to: Generate a view where for each user from the top table data all rows will be generated from the bottom table depending on the LEVEL selection on the top table. So in this case the VIEW should show
Expected Result: So in this case the VIEW should show all the SHOPS, REGIONS, COUNTRIES under AREA South West, all the SHOPS and REGIONS under COUNTRY United Kingdom and the SHOP 0001.
| username | function | level | location |
|:-----------|------------:|:---------------:| :-------------:|
| John | Operation | shop | 0001 |
| John | Operation | shop | 1001 |
| John | Operation | shop | ... |
| John | Operation | Country | United Kingdom|
| John | Operation | Country | ... |
| John | Operation | Country | ... |
| John | Operation | Region | ... |
| John | Operation | Region | ... |
| John | Operation | Region | ... |
| John | Operation | Area | South West |
| John | Operation | Area | ... |
| John | Operation | Area | ... |
Not sure how this can be done with CASE statement. Any help would be appreciated.
I think this will require a union of four separate queries:
The first query should select all shops equal to or under the shops/regions/countries/areas specified in the first table that join to the second table.
The second query should select all regions equal to or under the regions/countries/areas specified in the first table that join to the second table.
The third query should select all countries equal to or under the countries/areas specified in the first table that join to the second table.
The fourth query should select all areas specified in the first table that join to the second table.
There could be duplicates within each query, so we should use distinct within each query to eliminate them. There cannot be duplicates between the queries, so we can use union all rather than union.
create table userop (username varchar(32), [function] varchar(32), level varchar(32), location varchar(32) );
create table shopgeo (shop varchar(32), region varchar(32), country varchar(32), area varchar(32) );
insert into userop (username,[function],level,location) values
('John','Operation','Country','United Kingdom'),
('John','Operation','Area','South West'),
('John','Operation','Shop','0001')
;
insert into shopgeo (shop,region,country,area) values
('1200','u1','United Kingdom','West'),
('1201','u2','United Kingdom','West'),
('1000','f1','France','South West'),
('1100','i1','Italy','South West'),
('1111','s1','Spain','South West'),
('1112','n2','Norway','South West'),
('0001','o1','Japan','Asia')
;
-- show base tables
select * from userop;
select * from shopgeo;
-- solution
select * from (
select distinct o.username, o.[function], 'shop' level, g.shop location from userop o inner join shopgeo g on o.location=case when o.level='Area' then g.area when o.level='Country' then g.country when o.level='Region' then g.region when o.level='Shop' then g.shop end
union all select distinct o.username, o.[function], 'region' level, g.region from userop o inner join shopgeo g on o.location=case when o.level='Area' then g.area when o.level='Country' then g.country when o.level='Region' then g.region end
union all select distinct o.username, o.[function], 'country' level, g.country from userop o inner join shopgeo g on o.location=case when o.level='Area' then g.area when o.level='Country' then g.country end
union all select distinct o.username, o.[function], 'area' level, g.area from userop o inner join shopgeo g on o.location=case when o.level='Area' then g.area end
) t1 order by username, case when level='shop' then 1 when level='country' then 2 when level='region' then 3 when level='area' then 4 end, location;
I did it slightly differently
DECLARE #user varchar(50) = 'John'
Declare #view1 table (
username varchar(50),
ufunction varchar(50),
ulevel varchar(50),
location varchar(50))
INSERT INTO #view1 VALUES ('John', 'Operation', 'Country', 'United Kingdom')
INSERT INTO #view1 VALUES ('John', 'Operation', 'Area', 'South West')
INSERT INTO #view1 VALUES ('John', 'Operation', 'Shop', '0001')
Declare #view2 table (
shop varchar(50),
region char(2),
country varchar(50),
area varchar(50))
INSERT INTO #view2 VALUES ('1200', 'u1', 'United Kingdom', 'West')
INSERT INTO #view2 VALUES ('1201', 'u2', 'United Kingdom', 'West')
INSERT INTO #view2 VALUES ('1000', 'f1', 'France', 'South West')
INSERT INTO #view2 VALUES ('1100', 'i1', 'Italy', 'South West')
INSERT INTO #view2 VALUES ('1111', 's1', 'Spain', 'South West')
INSERT INTO #view2 VALUES ('1112', 'n2', 'Norway', 'South West')
INSERT INTO #view2 VALUES ('0001', 'o1', 'Japan', 'Asia')
SELECT username, ufunction, v1.ulevel, location AS Shop, v2.region, v2.country, v2.area from #view1 v1
INNER JOIN #view2 v2 ON v1.location = v2.shop
WHERE v1.ulevel = 'Shop' AND username = #user
UNION
SELECT v1.username, v1.ufunction, v1.ulevel, v2.shop, v2.region, v2.country, v2.area from #view1 v1
INNER JOIN #view2 v2 ON v1.location = v2.area
WHERE v1.ulevel = 'Area' AND username = #user
UNION
SELECT v1.username, v1.ufunction, v1.ulevel, v2.shop, v2.region, v2.country, v2.area from #view1 v1
INNER JOIN #view2 v2 ON v1.location = v2.country
WHERE v1.ulevel = 'Country' AND username = #user
RESULT
username ufunction ulevel Shop region country area
John Operation Area 1000 f1 France South West
John Operation Area 1100 i1 Italy South West
John Operation Area 1111 s1 Spain South West
John Operation Area 1112 n2 Norway South West
John Operation Country 1200 u1 United Kingdom West
John Operation Country 1201 u2 United Kingdom West
John Operation Shop 0001 o1 Japan Asia
The idea being that you list all the shops just once, but that within each record you can read off what right gives the user access to that shop, be it "Shop", "Area" or "Country"

SQL SELECT Query with returning hierarchical data

I have got a table which goes something like this:
State_id | County_id| City_id | Name | Additional_Name
-----------------------------------------------------------------
1 | 0 | 0 | California | State
1 | 1 | 0 | Los Angeles | County
1 | 1 | 1 | Los Angeles | City
1 | 2 | 0 | San Diego | County
1 | 2 | 1 | San Diego | City
2 | 0 | 0 | Texas | State
2 | 1 | 0 | Harris | County
2 | 1 | 1 | Houston | City
It goes on for 10,000 rows. What I'm trying to accomplish is to build a SELECT statement which will result in:
State | County | City
-------------------------------------------
California | Los Angeles | Los Angeles
California | San Diego | San Diego
Texas | Harris | Houston
As you can see i want to select every city and display it's state, and county. The state_id, county_id, city_id and Additonal_Name columns should be essential in solving this problem, but i have no idea how to use them.
This will get your data, using your current table structure:
SELECT t2.[Name] AS [State]
,t3.[Name] AS County
,t1.[Name] AS City
FROM MyTable t1
JOIN MyTable t2 -- get state
ON t2.State_id = t1.State_id
AND t2.County_id = 0
AND t2.City_id = 0
JOIN MyTable t3 -- get county
ON t3.County_id = t1.County_id
AND t3.State_id = t1.State_id
AND t3.City_id = 0
WHERE t1.City_id > 0 --(or use t1.Additional_Name = 'City'
But Really:
You should be normalizing your database into three separate tables:
City
State
County
This will make using your data, and writing queries - muchh simpler and readable.
The tables would look something like this:
State
ID Name
1 California
2 Texas
County
ID Name
1 Los Angeles
2 San Diego
3 Harris
City
ID StateID CountyID Name
1 1 1 Los Angeles
2 1 2 San Diego
3 2 3 Houston
Hopefully you can see how much easier that is to manage things. You can choose to add a StateID to the County table to normalize further. But I'll keep my answer short.
Select the same data from these new tables with a similar query:
SELECT state.[Name] AS [State]
,county.[Name] AS County
,city.[Name] AS City
FROM MyCity city
JOIN MyState state ON state.ID = city.StateID -- get state
JOIN MyCounty county ON county.ID = city.CountyID -- get county

ISNULL/COALESCE for multiple fields

Consider the following data model:
Customers
CustNum | First Name | Last Name
555 John Doe
CustomerAddresses
CustNum | ShippingAddress| Line1 | Line2 | City | State | Zip
555 | ADD1 | 333 A Dr. | Apt. 10 | Dallas | TX | 11345
555 | ADD2 | 111 B St. | NULL | Miami | FL | 22222
555 | WXYZ | 123 Main St. | NULL | Detroit | MI | 99998
OrderHeader
OrdNum | CustNum | OrderTotal | Line1 | Line2 | City | State| Zip
1000 | 555 | 67.00 | 123 Main St. | Ste 1 | Detroit | MI | 99998
OrderLine
OrderNo | Item | Price | ShippingAddress
1000 | X123 | 32.00 | ADD1
1000 | Y234 | 25.00 | ADD2
1000 | ZZZZ | 10.00 | NULL
There is a one-to-many relationship between Customers and CustomerAddresses.
Each OrderHeader, instead of a key relationship to the CustomerAddresses table, stores the address used for shipping in the Line1, Line2, City, State, and Zip fields.
In addition, it's possible to select a shipping address in the OrderLine table that overrides the address stored in the OrderHeader.
I'm trying to come up with a query to return data in the following format, to generate a list of mailing labels:
MailingLabels
OrderNo | Item | Line1 | Line2 | City | State | Zip
1000 | X123 | 333 A Dr. | Apt. 10 | Dallas | TX | 11345
1000 | Y234 | 111 B St. | NULL | Miami | FL | 22222
1000 | ZZZZ | 123 Main St. | NULL | Detroit | MI | 99998
Basically, if the OrderLine record has a ShippingAddress value, I want to return the corresponding address from the CustomerAddresses table.
If it is NULL, then I want to return the Line1, Line2, City, State, and Zip values stored in the OrderHeader table.
The problem is, when I use COALESCE or ISNULL, it's possible to return incorrect results. Here's my query:
SELECT OH.OrderNo, Item, ISNULL(CA.Line1, OH.Line1), ISNULL(CA.Line2, OH.Line2),
ISNULL(CA.City, OH.City), ISNULL(CA.State, OH.State), ISNULL(CA.Zip, OH.Zip)
FROM OrderHeader OH
JOIN OrderLine OL
ON OH.OrderNo = OL.OrderNo
LEFT JOIN CustomerAddress CA
ON OL.CustNum = CA.CustNum
AND OL.ShippingAddress = CA.ShippingAddress
With the above query, if the Line2 field is defined for the OrderHeader, but the ShippingAddress is defined in OrderLine, it's possible to return a mixed address for the Y234 item:
OrderNo | Item | Line1 | Line2 | City | State | Zip
1000 | Y234 | 111 B St. | Ste 1 | Miami | FL | 22222
Note, Ste 1 is not part of the address denoted in the OrderLine, it's actually part of the OrderHeader.
How can I write a query to return the data in the desired fashion? Any and all help is greatly appreciated!
Unfortunately, I can't think of a neat way to do this without being quite repetitive.
Assuming the OL aliases should have been CA:
SELECT OH.OrderNo, Item,
CASE WHEN OL.ShippingAddress IS NULL THEN OH.Line1 ELSE CA.Line1 END,
CASE WHEN OL.ShippingAddress IS NULL THEN OH.Line2 ELSE CA.Line2 END,
CASE WHEN OL.ShippingAddress IS NULL THEN OH.City ELSE CA.City END,
CASE WHEN OL.ShippingAddress IS NULL THEN OH.State ELSE CA.State END,
CASE WHEN OL.ShippingAddress IS NULL THEN OH.Zip ELSE CA.Zip END
FROM OrderHeader OH
JOIN OrderLine OL
ON OH.OrderNo = OL.OrderNo
LEFT JOIN CustomerAddress CA
ON OL.CustNum = CA.CustNum
AND OL.ShippingAddress = CA.ShippingAddress

Moving average in Temporal database in PostgreSQL

How can I apply the moving average in temporal database.
My data includes temperature and I want to apply moving average for every 15 records.
You can fire query as below
marc=# SELECT entity, name, salary, start_date,
avg(salary) OVER (ORDER BY entity, start_date
ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING)
FROM salary;
entity | name | salary | start_date | avg
-----------+-----------+---------+---------------+----------------------
Accounting | millicent | 850.00 | 2006-01-01 | 825.0000000000000000
Accounting | jack | 800.00 | 2010-05-01 | 916.6666666666666667
R&D | tom | 1100.00 | 2005-01-01 | 966.6666666666666667
R&D | john | 1000.00 | 2008-07-01 | 933.3333333333333333
R&D | maria | 700.00 | 2009-01-01 | 733.3333333333333333
R&D | kevin | 500.00 | 2009-05-01 | 633.3333333333333333
R&D | marc | 700.00 | 2010-02-15 | 600.0000000000000000
WITH moving_avrag AS (
SELECT 0 AS [lag] UNION ALL
SELECT 1 AS [lag] UNION ALL
SELECT 2 AS [lag] UNION ALL
SELECT 3 AS [lag] --ETC
)
SELECT
DATEADD(day,[lag],[date]) AS [reference_date],
[otherkey1],[otherkey2],[otherkey3],
AVG([value1]) AS [avg_value1],
AVG([value2]) AS [avg_value2]
FROM [data_table]
CROSS JOIN moving_avg
GROUP BY [otherkey1],[otherkey2],[otherkey3],DATEADD(day,[lag],[date])
ORDER BY [otherkey1],[otherkey2],[otherkey3],[reference_date];

Resources