Every customer, one item, multiple orders - sql-server

My question is actually healthcare related, but I'm going to provide a couple of mock customer,sales driven questions to make this question more practical for most users on this site.
Say I have one table with all the following columns:
CustomerID
CustomerName
OrderID
OrderDate
Region
ItemID
ItemName
SalespersonID
SalespersonName
Since everything is in the same table, I don't believe that any joins are necessary.
Question
Let's say that I run a store that pays its Sales people in the commission that requires customers make appointments. The commission is based on the number of times a customer has returned to that sales person.
How can I find out which customers have only had appointments with the same sales person and have never seen a different sales person?
Alternatively, I can provide another approach to solving this problem.
Is there a way to find out if there are customers that have only bought one item multiple times for separate orders?
For example, if Customer A, Jennifer Smith, really likes one specialty product, Extra Blue Caviar, and Jennifer buys Extra Blue Caviar every three months and has never bought another item in the store. Also, let's say Customer B, John Smith, buys Extra Fragrant Jasmine Rice every week and has never bought another item in the store.
How do I get a list of Jennifer Smith and John Smith along with Extra Blue Caviar and Extra Fragrant Jasmine Rice based on the criteria that they have only ever bought one item from the store multiple times?
Thank you so much!

For option #2
create table #t ( c_ID int, c_Name varchar(20),
o_ID int, o_date datetime, r int,
i_ID int, i_name varchar(20),
s_ID int, s_Name varchar(20))
;with cte as ( select
c_ID,
c_Name,
i_ID,
i_name,
count(distinct i_id) over (partition by c_ID) num_diffent_items,
count(*) over (partition by c_ID, i_ID) number_of_purchases
from #t)
select distinct
c_ID,
c_Name,
i_ID,
i_name,
number_of_purchases
from cte where num_diffent_items = 1

Related

SQL Get row that have maximum values over two columns

I have a table like this in Snowflake. It supports ANSI SQL, so don't worry if this DB isn't familiar to you.
Salesman
Customer
Country
Brown
Super Company
UK
Brown
Another customer
UK
Smith
Contoso
US
Brown
Test company
US
I'd need to find where each salesman have most of customers. So desired response for the query would be like this.
Salesman
Country
cnt(country)
Brown
UK
2
Smith
US
1
I've come up with this
SELECT
salesman,
country,
max(count(country))
FROM
customertable
GROUP BY
salesman, country
But nested aggeregation functions aren't supported. And I've already read quite good reasons for that. But I just cannot find a way to do that in any other way.
QUALIFY could be used to filter the highest value per salesman:
SELECT salesman,
country,
count(country) AS cnt
FROM customertable
GROUP BY salesman, country
QUALIFY RANK() OVER(PARTITION BY salesman ORDER BY cnt DESC) = 1
Regarding your questions i guess you would want to count customer by country instead of country.
This should do the job with the use of WINDOW FUNCTIONS AND QUALIFY
Window Functions documentation
CREATE OR REPLACE TABLE customers (salesman STRING, customer STRING, country STRING);
INSERT INTO customers
VALUES
('Brown', 'Super Company', 'UK'),
('Brown', ' Another customer', 'UK'),
('Smith', 'Contoso', 'US'),
('Brown', 'Test company', 'US')
;
SELECT
salesman,
country,
COUNT(customer) AS nb_customer
FROM customers
GROUP BY
salesman,
country
QUALIFY RANK() OVER (PARTITION BY salesman ORDER BY nb_customer DESC) = 1
;

Sum field(s) from one table in another table, summing from 3 different tables

I have an Access database with customer IDs. Each customer can have multiple orders and each order can be of a different type. I have three separate tables (Online, In-store, Payment Plan) for each order type with various amounts from each order, all are related to a customer ID. In one of the tables, there are two types of order types that amounts must be maintained separately withing the same table. I want to sum each order type in another table called Totals. I can successfully create a query to get the sums for each type based on the customer ID but I am not sure how to pull those values in my Totals table. The scenario below is repeated for multiple customers and each type is its own table---the payment plans are in a table together. I have historical data so I am limited to how I can manipulate as far as merging fields and what not.
Customer ID#: 1
Order Type: Online
Online Amount: $20.00
Order Type: Online
Online Amount: $40.00
Sum of Online Amount: $60.00
Order Type: In-store
Online Amount: $35.00
Order Type: In-store
Online Amount: $60.00
Sum of In-Store Amount: $95.00
Order Type: Payment Plan
Payment Plan 1 Amount: $30.00
Payment Plan 1 Amount: $23.00
Sum of Payment Plan 1 Amount: $53.00
Order Type: Payment Plan 2
Payment Plan 2 Amount: $35.00
Payment Plan 2 Amount: $30.00
Sum of Payment Plan 2 Amount: $65.00
In my Totals table I have a field for each type that sums the amount spent by each customer ID and then a field where all of their order types are summed into one overall total field.
I am learning as I go so any help/example is appreciated. Thank you.
Having separate tables for your different order types doesn't help. For a database it would be better to have a single table for all sales with a sale_type field.
You don't describe exactly what your tables look like, so I've had to make a couple of assumptions. If your tables contain an OrderType field then you can create a Union query to join all your sales together:
SELECT CustomerID
, OrderType
, Amount
FROM Online
UNION ALL SELECT CustomerID
, OrderType
, Amount
FROM [In-Store]
UNION ALL SELECT CustomerID
, OrderType
, Amount
FROM [Payment Plan]
If you don't have an OrderType you can hard-code the values into the query:
SELECT CustomerID
, "Online" AS OrderType
, Amount
FROM Online
UNION ALL SELECT CustomerID
, "In-Store"
, Amount
FROM [In-Store]
UNION ALL SELECT CustomerID
, "Payment Plan"
, Amount
FROM [Payment Plan]
Note - The field name is declared for the OrderType in the first Select block. You could do it in each block, but Access only looks at the first.
Like all queries, the results come in table form and can be treated as such. So now we need to list the CustomerName (I'm assuming you have a Customers table), the OrderType and the sum of the amount for that Customer & OrderType.
SELECT CustomerName
, OrderType
, SUM(Amount)
FROM Customers INNER JOIN
(
SELECT CustomerID
, OrderType
, Amount
FROM Online
UNION ALL SELECT CustomerID
, OrderType
, Amount
FROM [In-Store]
UNION ALL SELECT CustomerID
, OrderType
, Amount
FROM [Payment Plan]
) T1 ON Customers.CustomerID = T1.CustomerID
GROUP BY CustomerName
, OrderType
All sales in your three tables will have a customer within the customers table so we can use an INNER JOIN to return only records where the value appears in both tables (Customers table & result of query table).
The UNION QUERY is wrapped in brackets and given the name T1and joined to the Customers table on the CustomerID field.
We group all fields that aren't part of an aggregate function, so group on CustomerName and OrderType and sum the Amount field.
This is all you really need to do - let the query run each time you want the totals to get the most up to date values. There's shouldn't be a need to push the results to a Totals table as that will be out of date as soon as you make a new sale (or someone returns something).
If you really want to INSERT these figures into a Total table just add a first line to the SQL:
INSERT INTO Total (CustomerName, OrderType, Amount)
Here is a dirty workaround, though I think there might be a more direct solution to it.
You could create an output table (I broke it down to ID, Online, InStore and Total) and use DSum functions within an UPDATE query.
UPDATE tbl_Totals SET
Total_InStore = DSum("Amount", "tbl_InStore", "Customer_ID = " & Customer_ID),
Total_Online = DSum("Amount", "tbl_Online", "Customer_ID = " & Customer_ID),
Total = DSum("Amount", "tbl_InStore", "Customer_ID = " & Customer_ID) + DSum("Amount", "tbl_Online", "Customer_ID = " & Customer_ID)

Problèm with SQL select grouping

I have a small problem with a SQL Server query.
I have an issue with my view of several base tables with duplicate values, so far no problem, these duplicates are logical. By unfortunately I do not get the desired end result, I could do it by programming the front end of my application but I would prefer to do the work on the server.
I will explain the principle:
I have 30 companies which each have an employee table.
My view is a union of the 30 employee tables.
Each employee has a unique serial number, the number is the same across tables, so an employee named "John Doe" with an ID number 'S0000021' can be hired in Company A then transferred to company Q without any problems, it will retain the serial number 'S0000021'.
The difference between the data from the Employee tables A and Q will be in this example the start (hire) and release (transfer) dates entered for Company A and just the start date for company Q so the view will have 2 lines for "John Doe".
12 common fields are the following:
Serial Number (Identical in every employee table)
Social Security Number (Same in every employee table)
Start/Hire Date
Release/Transfer date (empty/null if the employee is current)
Name (Can change across companies if the person divorces)
First name
Maiden name
Last Name
Gender
Final Released
Company Code
The problem seems simple that I would not appear that the latest information of the employee, except with a group by, if it has changed name or release date, it will be displayed twice.
I tried the following different ways but they don't return what I want
I returned results both ways but I always see duplicates because my dates within companies are never identical, and their name may change.
Sorry for this Google translation.
1 --
select
vue.matricule,
vue.numsecu,
vue.name,
vue.lastname,
vue.maidenname,
vue.secondname,
vue.genre,
vue.released,
vue.companycode
from
vue
group by
vue.matricule,
vue.numsecu,
vue.name,
vue.lastname,
vue.maidenname,
vue.secondname,
vue.genre,
vue.released,
vue.companycode
2---
select
distinct(vue.matricule),
vue.numsecu,
vue.name,
vue.lastname,
vue.maidenname,
vue.secondname,
vue.genre,
vue.released,
vue.companycode
from
vue
I assumed the following:
there is a view (vue) that already gathers all data from each of the 30 companies
you are just looking for the latest record for each employee
If you need to also see a record for each name change we can change this.
--set up test data
declare #vue table (
matricule varchar(20),
numsecu varchar(20),
name varchar(20),
lastname varchar(20),
maidenname varchar(20),
secondname varchar(20),
genre varchar(20),
start datetime,
released datetime,
companycode varchar(20));
insert #vue values
('S0000021','123456789','John', 'Doe',null,null,'M','2015-01-01','2015-12-31','A'),
('S0000021','123456789','Johnny', 'Doe',null,null,'M','2016-01-01',null,'Q'), --new company, name change, currently employed
('S0000022','123456780','Jane', 'Doe',null,null,'M','2015-01-01','2015-12-31','A'),
('S0000022','123456780','Jane', 'Doe',null,null,'M','2016-01-01','2016-02-01','Q'); --new company, name change, terminated
select * from #vue order by matricule, start;
--get latest record for each employee
select *
from (--add row numbering
select *, row_number() over (partition by matricule order by start desc) row_num
from #vue
) vue2
where vue2.row_num = 1;

How to automatically set the SNo of the row in MS SQL Server 2005?

I have a couple of rows in a database table (lets call it Customer). Each row is numbered by SNo, which gets automatically incremented by the identity property inherent in MS SQLServer. But when I delete a particular row that particular row number is left blank, but I want the table to auto correct itself.
To give you a example:
I have a sample Customer Table with following rows:
SNo CustomerName Age
1 Dani 28
2 Alex 29
3 Duran 21
4 Mark 24
And suppose I delete 3rd row the table looks like this:
SNo CustomerName Age
1 Dani 28
2 Alex 29
4 Mark 24
But I want the table to look like this:
SNo CustomerName Age
1 Dani 28
2 Alex 29
3 Mark 24
How can I achieve that?
Please help me out
Thanks in anticipation
As has been pointed out doing that would break anything in a relationship with SNo, however if your doing this because you need ordinal numbers in you presentation layer for example, you can pull off a [1..n] row number with;
SELECT ROW_NUMBER() OVER(ORDER BY SNo ASC), SNo, CustomerName, Age FROM Customer
Obviously in this case the row number is just an incrementing number, its meaningless in relation to anything else.
I don't think you want to do that. Imagine the scenario where you have another table CustomerOrder that stores all customer orders. The structure for that table might look something like this:
CustomerOrder
-------------
OrderID INT
SNo INT
OrderDate DATETIME
...
In this case, the SNo field is a foreign key into the CustomerOrder table, and we use it to relate orders to a customer. If you delete a record from your Customer table (say with SNo = 1), are you going to go back and update the SNo values in the entire CustomerOrder table? It's best to just let the ID's autoincrement and not worry about spaces in the IDs due to deletions.
Why not create a view?
CREATE VIEW <ViewName>
AS
SELECT
ROW_NUMBER() OVER(ORDER BY SNo ASC) AS SNo
,CustomerName
,Age
FROM Customers
GO
Then access the data in customers table by selecting from the view.
Of course the SNo shown by the view has no meaning in the context of relationships, but the data returned will look exactly like you want it to look.
Using transactions when inserting records in the Database with C#
You have to use DBCC CHECKIDENT(table_name, RESEED, next_val_less_1);
As have been pointed out in other answers, this is a bad idea, and if the reason is for a presentation there are other solutions.
-- Add data to temp table
select SNo, CustomerName, Age
into #Customer
from Customer
-- Truncate Customer
-- Resets identity to seed value for column
truncate table Customer
-- Add rows back to Customer
insert into Customer(CustomerName, Age)
select CustomerName, Age
from #Customer
order by SNo
drop table #Customer

Best structure for inventory database

I want to create a small database for my inventory but I have some problems on picking a structure. The inventory will be updated daily at the end of the day.
The problem I am facing is the following.
I have a table for my products, having an
id, name, price, quantity.
Now I have another table for my sales, but there is my problem. What kind of fields do I need to have. At the end of the day I want to store a record like this:
20 product_x $ 5,00 $ 100,-
20 product_y $ 5,00 $ 100,-
20 product_z $ 5,00 $ 100,-
20 product_a $ 5,00 $ 100,-
-------------------------------------------------
$ 400,-
So how do I model this in a sales record. Do I just create a concatenated record with the product id's comma separated.
Or is there another way do model this the right way.
This is a model which supports many aspects,
Supports Sites, Locations and Warehouses etc.
Supports Categorization and Grouping
Support Generic Product (Ex. "Table Clock" and specific product "Citizen C123 Multi Alarm Clock" )
Also support Brand Variants (by various manufacturers)
Has CSM (color / size / model support) Ex. Bata Sandles (Color 45 Inch Blue color)
Product Instances with serials (such as TVs , Refrigerators etc.)
Lot control / Batch control with serial numbers.
Pack Size / UOM and UOM Conversion
Manufacturer and Brands as well as Suppliers
Also included example transaction table (Purchase order)
There are many other transaction types such as Issues, Transfers, Adjustments etc.
Hope this would help. Please let me know if you need further information on each table.
Cheers...!!!
Wajira Weerasinghe.
Sites
id
site_code
Site_name
Warehouse
id
site_id
warehouse_code
warehouse_name
Item Category
id
category_code
category_name
Item Group
id
group_code
group_name
Generic Product
id
generic_name
Product
id
product_code
category_id
group_id
brand_id
generic_id
model_id/part_id
product_name
product_description
product_price (current rate)
has_instances(y/n)
has_lots (y/n)
has_attributes
default_uom
pack_size
average_cost
single_unit_product_code (for packs)
dimension_group (pointing to dimensions)
lot_information
warranty_terms (general not specific)
is_active
deleted
product attribute type (color/size etc.)
id
attribute_name
product_attribute
id
product_id
attribute_id
product attribute value (this product -> red)
id
product_attribute_id
value
product_instance
id
product_id
instance_name (as given by manufacturer)
serial_number
brand_id (is this brand)
stock_id (stock record pointing qih, location etc.)
lot_information (lot_id)
warranty_terms
product attribute value id (if applicable)
product lot
id
lot_code/batch_code
date_manufactured
date_expiry
product attribute value id (if applicable)
Brand
id
manufacturer_id
brand_code
brand_name
Brand Manufacturer
id
manufacturer_name
Stock
id
product_id
warehouse_id, zone_id, level_id, rack_id etc.
quantity in hand
product attribute value id (if applicable) [we have 4 red color items etc.]
Product Price Records
product_id
from_date
product_price
Purchase Order Header
id
supplier_id
purchase_date
total_amount
Purchase Order Line
id
po_id
product_id
unit_price
quantity
Supplier
id
supplier_code
supplier_name
supplier_type
product_uom
id
uom_name
product_uom_conversion
id
from_uom_id
to_uom_id
conversion_rule
I'd have a table with a row per item per day - store the date, the item ID, the quantity sold, and the price sold at (store this even though it's also in the product table - if that changes, you want the value you actually sold at preserved). You can compute totals per item-day and totals per day in queries.
Tables:
create table product (
id integer primary key,
name varchar(100) not null,
price decimal(6,2) not null,
inventory integer not null
);
create table sale (
saledate date not null,
product_id integer not null references product,
quantity integer not null,
price decimal(6,2) not null,
primary key (saledate, product_id)
);
Reporting on a day:
select s.product_id, p.name, s.quantity, s.price, (s.quantity * s.price) as total
from product p, sale s
where p.id = s.product_id
and s.saledate = date '2010-12-5';
Reporting on all days:
select saledate, sum(quantity * price) as total
from sale
group by saledate
order by saledate;
A nice master report over all days, with a summary line:
select *
from (
(select s.saledate, s.product_id, p.name, s.quantity, s.price, (s.quantity * s.price) as total
from product p, sale s
where p.id = s.product_id)
union
(select saledate, NULL, 'TOTAL', sum(quantity), NULL, sum(quantity * price) as total
from sale group by saledate)
) as summedsales
order by saledate, product_id;
Try modelling your sales as a transaction - with a "header", i.e. who sold to, when sold, invoice # (if applicable), etc. and "line items", i.e. 20 * product_x # $5 = $100. The safest approach is to avoid relying upon prices etc. from the products table - as these will presumably change over time, and instead copy much of the product information (if not all) into your line item - so even when prices, item descriptions etc. change, the transaction information remains as was at the time the transaction was made.
Inventory can get quite complex to model. First you need to understand that you need to be able to tell the value of the inventory onhand based on what you paid for it. This means you cannot rely on a product table that is updated to the current price. While you might want such a table to help you figure out what to sell it for, there are tax reasons why you need to know the actual vlaue you paid for each item in the warehouse.
So first you need the product table (you might want to make sure you have an updated date column in this, it can be handy to know if your prices seem out of date).
Then you need a table that stores the actual warehouse location of each part and the price at purchase. If the items are large enough, you need a way to individually mark each item, so that you know what was taken out. Usually people use barcodes for that. This table needs to be updated to record that the part is no longer there when you sell it. I prefer to make the record inactive and have a link to my sales data to that record, so I know exactly what I paid for and what I sold each part for.
Sales should have at least two tables. One for the general information about the sale, the customername (there should also be a customer table most of the time to get this data from), the date, where it was shipped to etc.
Then a sales detail table that includes a record for each line item in the order. Include all the data you need about the part, color, size, quantity, price. This is not denormalizing, this is storing historical data. The one thing you do not want to do is rely on the prices in the product table for anything except the inital entry to this table. You do not want to do a sales report and have the numbers come out wrong becasue the product prices changed the day before.
Do not design an inventory database without consulting with an accountant or specialist in taxes. You also should do some reading on internal controls. It is easy to steal from a company undetected that has not done their work on internal controls in the database.
I think you need a table with fields showing the transaction properties per customer
OR
a table with fields - date, product(foreign), quantity - this way you'll have no problem with new products
Try multiple tables with links
table_products
id
name
table_product_sales
id
product_id
quantity
price_per
transaction_time AS DATETIME
SELECT table_product_sales.*, table_product.name
FROM table_product
JOIN table_product_sales
ON table_product_sales.product_id = table_product.id
GROUP BY DATE(transaction_time)
Haven't tried it but will something like that work? That allows you to keep each transactions separate so you can query things like average number sold per sale, total sold per date, total sales each day, etc.

Resources