SQL Server : Conditional Sort with Multiple Columns - sql

I am having an issue with a conditional sort in SQL Server. I have a lengthy procedure that in the end produces a list of magazine subscriptions that are up for renewal. This is the basic structure of the end result table:
create table #renewals
(
product_no integer NULL,
quantity integer NULL,
order_date datetime NULL,
order_no integer NULL,
customer_no varchar(10) NULL,
description varchar(50) NULL,
first_name varchar(30) NULL,
middle_name varchar(30) NULL,
last_name varchar(30) NULL,
salute varchar(5) NULL,
acct_no char(8) NULL,
contact_type varchar(8) NULL,
contact_no integer NULL,
line_item int,
edition_code char(3) NULL
)
The issue lies in the multi-level sort that I am trying to accomplish. In the end, I need this data sorted by edition_code ASC which is simple enough.
However, some editions have add-on products which then need to be inserted underneath the correct edition_code. This is where I am running into a problem. An example of a regular edition code is 002 or 014. The add-on edition codes are 300, 302, etc. If I only sort by edition_code then all of the add-on editions get moved to the bottom of the list.
The following is a rundown of the result set:
product_no -- This is a different number than edition_code, but same principal
quantity -- self-explanatory
order_date -- self-explanatory
order_no -- For each unique result set, this number is the same
customer_no -- this is unique for each customer and helps to bundle main editions with their add-ons
description -- title of edition
first (through) last_name -- self-explanatory
salute -- self-explanatory
acct_no -- For each result set, this number is the same because all the customers will belong to the same account (think multiple doctors subscribing to a magazine but all work out of the same hospital)
contact_type -- PK
contact_no -- PK
line_item -- line number when entered on order (this does not help with sorting at all)
edition_code -- Unique code for an edition of our product
Below is the result I am expecting:
Customer_1 | Edition_1
Customer_2 | Edition_1
Customer_2 | Add-On_1
Customer_2 | Add-On_2
Customer_9 | Edition_1
Customer_6 | Edition_2
Customer_5 | Edition_2
Customer_5 | Add-On_2
Customer_3 | Edition_3
The closest I can get is by simply sorting by customer_no which will put the add-ons with their editions (You cannot order an add-on without ordering the main edition), but this does not sort by main edition code, which is what the overall (main) sort should accomplish.
This is the procedure that produces the result, not sure how it'll help:
CREATE TABLE #renewals
(
product_no integer NULL,
quantity integer NULL,
order_date datetime NULL,
order_no integer NULL,
customer_no varchar(10) NULL,
description varchar(50) NULL,
first_name varchar(30) NULL,
middle_name varchar(30) NULL,
last_name varchar(30) NULL,
salute varchar(5) NULL,
acct_no char(8) NULL,
contact_type varchar(8) NULL,
contact_no integer NULL,
line_item int,
edition_code char(3) NULL
)
INSERT INTO #renewals
(
product_no,
quantity,
order_date,
order_no,
customer_no,
description,
first_name,
middle_name,
last_name,
salute,
acct_no,
contact_type,
contact_no ,
line_item,
edition_code
)
SELECT
product.product_no,
order_table_1.quantity,
order_table_1.order_date,
order_table_1.order_no,
order_table_1.customer_no,
product.description,
isnull(customer_table_1.first_name, ''),
isnull(customer_table_1.middle_name, ''),
isnull(customer_table_1.last_name, ''),
isnull(customer_table_1.salute, ''),
order_table_1.acct_no,
'',
-1,
line_item_no,
order_table_1.edition_code
FROM order_table_1
LEFT OUTER JOIN product ON order_table_1.edition_code = product.edition_code
LEFT OUTER JOIN customer_table_1 ON order_table_1.customer_no = customer_table_1.customer_no AND order_table_1.acct_no = customer_table_1.acct
WHERE
(order_table_1.acct_no = #acct) AND
(order_table_1.school_year = #school_year) AND
(order_table_1.edition_code <> '065') AND
(order_table_1.status_code in ('E','A','F')) AND
(isnull(order_table_1.renewed,'N') <> 'Y')
UPDATE o
SET o.first_name = isnull(tc.first_name, ''),
o.middle_name = isnull(tc.middle_name, ''),
o.last_name = isnull(tc.last_name, ''),
o.salute = isnull(tc.salute, ''),
o.contact_type = tc.contact_type,
o.contact_no = tc.contact_no
FROM #renewals o, accounts ta, contacts tc
WHERE
(o.acct_no = ta.acct) AND
(ta.acct_type = tc.acct_type) AND
(ta.acct_no = tc.acct_no) AND
(o.customer_no = tc.customer_no) AND
(o.customer_no is not null) AND
(o.customer_no <> '') AND
(o.customer_no not like '999999999%')
SELECT
cast(o.product_no as int) as 'product_no',
o.quantity,
o.order_date,
o.order_no,
o.customer_no,
o.description,
o.first_name,
o.middle_name,
o.last_name,
o.salute,
o.acct_no,
o.contact_type,
o.contact_no,
o.line_item,
o.edition_code
FROM #renewals o
ORDER BY o.edition_code

You can't sort what you don't have. You need a table of edition_code, addon_code pairs. Then join to that table with
#renewals.edition_code in (X.edition_code, X.addon_code)
...
order by X.edition_code, X.addon_code
I'm making a few assumptions: that the two code sets don't overlap, that edition_code is always less than addon_code, and that you want the addons ordered by their code. Sometimes in cases like this a third column is required to express the sort order.

Related

How can I simplify this GROUPING SETS query into a ROLLUP?

I'm trying to create a SELECT statement to calculate multiple levels of subtotals across two hierarchies. I have a Geo hierarchy which consists in region and department, and a Time hierarchy which consists in month and quarter.
At the moment, I have three tables which have the following structure:
CREATE TABLE Sales(
Time_id Date NOT NULL,
Geo_id CHAR(5) NOT NULL,
Prod_id CHAR(6) NOT NULL,
sales INT NOT NULL,
PRIMARY KEY(Time_id, Geo_id, Prod_id)
);
CREATE TABLE Time(
Time_id Date NOT NULL,
month CHAR(2) NOT NULL,
quarter CHAR(1) NOT NULL,
year CHAR(4) NOT NULL,
PRIMARY KEY(Time_id)
);
CREATE TABLE Geo(
Geo_id CHAR(5) NOT NULL,
city VARCHAR(20) NOT NULL,
dept CHAR(2) NOT NULL,
region CHAR(2) NOT NULL,
PRIMARY KEY(Geo_id)
);
I wrote the following query that returns me the right values (63 rows):
SELECT
Geo.region, Geo.dept, Time.month, Time.quarter, SUM(Sales.sales) AS sales
FROM
Geo, Sales, Time
WHERE
Sales.Time_id = Time.Time_id AND Sales.Geo_id = Geo.Geo_id
GROUP BY
GROUPING SETS(
(Geo.dept, Time.month),
(Geo.dept, Time.quarter),
(Geo.region, Time.month),
(Geo.region, Time.quarter),
Geo.dept,
Geo.region,
Time.month,
Time.quarter,
() -- thanks to #Larnu
)
I find my query to be really repetitive and I think there is a better way of doing it using ROLLUP. Can anyone please help me to simplify it, as I'm relatively new to SQL analytics?

get data from related tables in postgresql

assuming these 3 tables
create table item(
item_id integer NOT NULL primary key,
name varchar(50) NOT NULL,
description varchar(150) NOT NULL,
in_stock integer NOT NULL
)
create table customer(
customer_id VARCHAR(9) NOT NULL primary key,
name VARCHAR(50) NOT NULL,
lastname VARCHAR(50) NOT NULL,
phone VARCHAR(15) NOT NULL,
join_date DATE NOT NULL
)
create table purchase(
purchase_id integer references item,
customer_id varchar(9) references customer,
purchase_date TIMESTAMP NOT NULL,
amount INTEGER NOT NULL,
PRIMARY KEY(purchase_id, customer_id, purchase_date)
)
how could I get each unique name and the total amount of items purchased?
how could I get each purchase name and the buyer's name and lastname?
how could I get each item and how many of it were sold?
The two topics you are looking to learn are how to use GROUP BY and how to JOIN tables. Here's an example (more or less) that answers your first question and uses both tools:
select
C.customer_id as customer_id,
max(C.name) as customer_name,
sum(amount) as total_amount
from customer C
left join purchase P on C.customer_id = P.customer_id
group by C.customer_id

DateDiff asc and desc

SELECT DISTINCT
at.AccountId AS AccountId,
a.FirstName + ' ' + a.LastName AS [FullName],
DATEDIFF(day, T.ArrivalDate, T.ReturnDate) AS LongestTrip,
DATEDIFF(day, T.ArrivalDate, T.ReturnDate) AS ShortestTrip
FROM
Accounts a
JOIN
AccountsTrips at ON a.Id = AT.AccountId
JOIN
Trips t ON T.Id = AT.TripId
WHERE
a.MiddleName IS NULL AND t.CancelDate IS NULL
ORDER BY
DATEDIFF(day, T.ArrivalDate, T.ReturnDate) DESC, ShortestTrip ASC
The code only orders the tables descending in LongestTrip and in ShortestTrip
SAMPLE DATA !
Find the longest and shortest trip for each account, in days. Filter the results to accounts with no middle name and trips, which are not cancelled (CancelDate is null).
Order the results by Longest Trip days (descending), then by Shortest Trip (ascending).
Examples
AccountId FullName LongestTrip ShortestTrip
------------------------------------------------------------
40 Winna Maisey 7 1
56 Tillie Windress 7 1
57 Eadith Gull 7 1
66 Sargent Rockhall 7 1
69 Jerome Flory 7 2
… … … …
The Tables are --
CREATE TABLE Cities
(
Id INT PRIMARY KEY IDENTITY,
Name NVARCHAR(20) NOT NULL,
CountryCode CHAR(2) NOT NULL
)
CREATE TABLE Hotels
(
Id INT PRIMARY KEY IDENTITY,
Name NVARCHAR(30) NOT NULL,
CityId INT FOREIGN KEY REFERENCES Cities(Id) NOT NULL,
EmployeeCount INT NOT NULL,
BaseRate DECIMAL(10,2)
)
CREATE TABLE Rooms
(
Id INT PRIMARY KEY IDENTITY,
Price DECIMAL(10,2) NOT NULL,
Type NVARCHAR(20) NOT NULL,
Beds INT NOT NULL,
HotelId INT FOREIGN KEY REFERENCES Hotels(Id) NOT NULL
)
CREATE TABLE Trips
(
Id INT PRIMARY KEY IDENTITY,
RoomId INT FOREIGN KEY REFERENCES Rooms(Id) NOT NULL,
BookDate DATE NOT NULL, CHECK(BookDate<ArrivalDate),
ArrivalDate DATE NOT NULL, CHECK(ArrivalDate<ReturnDate),
ReturnDate DATE NOT NULL,
CancelDate DATE
)
CREATE TABLE Accounts
(
Id INT PRIMARY KEY IDENTITY,
FirstName NVARCHAR(50) NOT NULL,
MiddleName NVARCHAR(20),
LastName NVARCHAR(50) NOT NULL,
CityId INT NOT NULL,
BirthDate DATE NOT NULL,
Email VARCHAR(100) UNIQUE NOT NULL
CONSTRAINT FK_CityId FOREIGN KEY (CityId)
REFERENCES Cities(Id)
)
CREATE TABLE AccountsTrips
(
AccountId INT FOREIGN KEY REFERENCES Accounts(Id) NOT NULL,
TripId INT FOREIGN KEY REFERENCES Trips(Id) NOT NULL,
Luggage INT NOT NULL, CHECK(Luggage >= 0)
)
You want to pick the longest and shortest trips per account from your data. As all you want to get from the trips is the duration, you can simply aggregate and show MIN and MAX duration:
SELECT
a.Id AS AccountId,
a.FirstName + ' ' + a.LastName AS [FullName],
MIN(DATEDIFF(day, T.ArrivalDate, T.ReturnDate)) AS LongestTrip,
MAXD(ATEDIFF(day, T.ArrivalDate, T.ReturnDate)) AS ShortestTrip
FROM
Accounts a
JOIN
AccountsTrips at ON a.Id = AT.AccountId
JOIN
Trips t ON T.Id = AT.TripId
WHERE
a.MiddleName IS NULL AND t.CancelDate IS NULL
GROUP BY
a.Id, a.FirstName, a.LastName
ORDER BY
LongestTrip DESC, ShortestTrip ASC;
If you wanted to show additional data from the trips, you would use window functions (probably MIN OVER and MAX OVER) and would then either show two rows per account or aggregate these two rows.

Inner join - count and sum of field

I have two tables editors and title_editors that I am trying to run an inner join query to show all editors and how many books they edit. I am not getting the desired result but a total sum of all books edited. How can list the name of all editors and how many books they edit and arrange the report so the editor editing the most books is listed first? SQLFIDDLE
Query
SELECT (e.first_name || ' ' || e.last_name) as Editor_Name, SUM(te.editor_order) as Books_Edited FROM editors e
INNER JOIN title_editors te ON te.editor_id = e.editor_id ORDER BY SUM(te.editor_order);
Table Schema
create table editors (editor_id char(11) not null, editor_lname varchar(40) not null, editor_fname varchar(20) not null, editor_positon varchar(12) null, phone char(12) null, address varchar(40) null, city varchar(20) null, state char(2) null, zip char(5) null, ed_boss char(11) null );
create table title_editors (editor_id char(11) not null, title_id char(6) not null, editor_order tinyint null);
In addition to the comment about CONCAT, you are missing your GROUP BY, and a DESC at the end of your ORDER BY to get them sorted the way you want.
SELECT
CONCAT(e.editor_fname,' ',e.editor_lname),
SUM(te.editor_order)
FROM
editors e
INNER JOIN
title_editors te
ON te.editor_id = e.editor_id
GROUP BY
e.editor_fname,
e.editor_lname
ORDER BY
SUM(te.editor_order) DESC;
http://sqlfiddle.com/#!9/47bd4/28
http://sqlfiddle.com/#!9/47bd4/22
Just GROUP your results.
SELECT (CONCAT(e.editor_fname,' ',e.editor_lname)) as Editor_Name
, SUM(te.editor_order) as Books_Edited
FROM editors e
INNER JOIN title_editors te
ON te.editor_id = e.editor_id
GROUP BY e.editor_fname,e.editor_lname
ORDER BY Books_Edited;

Returning multiple aggregate functions as rows

I need some help formulating a select statement. I need to select the total quantity shipped for each part with a distinct color. So the result should be a row with the color name and the total.
Here's my schema:
create table s
( sno char(5) not null,
sname char(20) not null,
status smallint,
city char(15),
primary key (sno)
);
create table p
( pno char(6) not null,
pname char(20) not null,
color char(6),
weight smallint,
city char(15),
primary key (pno)
);
create table sp
( sno char(5) not null,
pno char(6) not null,
qty integer not null,
primary key (sno, pno)
);
As your schema stands each product PNO has only one colour, so I'm not sure what your question actually requires.
Sales by product:
select p.pno
, sum (sp.qty)
from p join sp on (p.pno = sp.pno)
group by p.pno;
Sales by colour:
select p.color
, sum (sp.qty)
from p join sp on (p.pno = sp.pno)
group by p.color;
edit
To get sales for a specific colour is easy:
select sum (sp.qty)
from p join sp on (p.pno = sp.pno)
where p.color = 'BLUE';