Split a date column, and calculate an amount based on the result - sql-server

In the sales table I have a column that contains full dates of each sale, I need to split it and add 2 separate columns of month and year to the sales table, and a column that contains the sum of all sales of each month that was.
This is the table I have-
Sales table
customer_id
date
Quantity
123
01/01/2020
6
124
01/02/2020
7
123
01/03/2021
5
123
15/01/2020
4
Here's what I wrote -
ALTER TABLE SALES ADD SELECT DATEPART (year, date) as year FROM SALES;
ALTER TABLE SALES ADD SELECT DATEPART (month, date) as month FROM SALES;
ALTER TABLE SALES ADD SUM_Quantity AS SUM() (Here I was stuck in a query...)
Is this a valid query, and how to write it correctly? Thanks!

One of the problems you're going to have here is that the outcome of the computed columns you have isn't compatible with the data being stored.
For example you can't build a sum of the quantity for all of the rows in January for each row in January. You need to group by the date and aggregate the quantity.
As such I think this might be an ideal candidate for an indexed view. This will allow you to store the calculated data, whilst preserving the data in the original table.
create table SalesTable (customer_id int, date date, quantity smallint);
insert SalesTable (customer_id, date, quantity)
select 123, '2020-01-01', 6
union
select 124, '2020-02-01', 7
union
select 123, '2021-03-01', 5
union
select 123, '2020-01-15', 4;
select * from SalesTable order by date; /*original data set*/
GO
CREATE VIEW dbo.vwSalesTable /*indexed view*/
WITH SCHEMABINDING
AS
SELECT customer_id, DATEPART(year, date) as year, datepart(MONTH, date) as
month, SUM(quantity) as sum_quantity from dbo.SalesTable group by Customer_Id,
DATEPART(year, date), DATEPART(MONTH, date)
GO
select * from vwSalesTable /*data from your indexed view*/
drop view vwSalesTable;
drop table SalesTable;

Related

Recursive Query in Azure Synapse Analytics for Dates

In the table below, how to insert rows with the first and last date of years between the START_DATE and END_DATE column?
EMPID
EMPNAME
START_DATE
END_DATE
1001
Shivansh
2015-09-01
2018-03-31
1004
Mayank
2019-04-01
2020-06-30
The output should look as follows:
EMPID
EMPNAME
START_DATE
END_DATE
1001
Shivansh
2015-09-01
2015-12-31
1001
Shivansh
2016-01-01
2016-12-31
1001
Shivansh
2017-01-01
2017-12-31
1001
Shivansh
2018-01-01
2018-03-31
1004
Mayank
2019-04-01
2019-12-31
1004
Mayank
2020-01-01
2020-06-30
This has to be implemented using loops as Azure Synapse Analytics doesn't support Recursive common table expressions
This approach uses a numbers table and a number of date functions which are available in Azure Synapse Analytics dedicated SQL pools, including DATEFROMPARTS, DATEDIFF and YEAR.
NB This is not a recursive query. There is a loop used in the creation of the numbers table but this is done only once. Once the numbers table exists it can be used for similar scenarios, eg converting recursive CTEs to set-based approaches compatible with Azure Synapse Analytics.
DATEFROMPARTS is used to construct the first day of the year in the calculated records. I then use DATEADD to add one year, then take away one day, to get the last day of the year. DATEDIFF with year is used to determine the gap in years between the two dates and therefore the number of records that need to be added. I then UNION the original and calculated records for the full result.
IF OBJECT_ID('tempdb..#tmp') IS NOT NULL
DROP TABLE #tmp
GO
CREATE TABLE #tmp (
empId INT NOT NULL,
empName VARCHAR(50) NOT NULL,
start_date DATE NOT NULL,
end_date DATE NULL
)
GO
-- Setup test data
INSERT INTO #tmp ( empId, empName, start_date, end_date )
SELECT 1001, 'Shivansh', '2015-09-01', '2018-03-31'
UNION ALL
SELECT 1004, 'Mayank', '2019-04-01', '2020-06-30'
GO
;WITH cte AS (
SELECT *,
DATEFROMPARTS( YEAR(start_date) + n.number, 1, 1 ) newStart
FROM #tmp t
CROSS JOIN dbo.numbers n
WHERE n.number <= DATEDIFF( year, start_date, end_date )
)
SELECT 'o' s, empId, empName, start_date,
CASE
WHEN YEAR(start_date) = YEAR(end_date) THEN end_date
ELSE DATEFROMPARTS( YEAR(start_date), 12, 13 )
END end_date
FROM #tmp
UNION ALL
SELECT 'c', empId, empName,
newStart AS start_date,
CASE
WHEN YEAR(end_date) = YEAR(newStart) THEN end_date
ELSE DATEADD( day, -1, DATEADD( year, 1, newStart ) )
END newEnd
FROM cte
ORDER BY empId, start_date
My results:
I've added the o and c to indicate original and calculated rows but you can remove that column if you like. If you do not have a numbers table already then the script I used to create this one is here. This code has been tested on an Azure Synapse Analytics dedicated SQL pool, version Microsoft Azure SQL Data Warehouse - 10.0.15554.0 Dec 10 2020 03:11:10.
I found the workaround using a loop. The steps followed are as follows:
Create a temporary table to hold initial values.
CREATE TABLE #EMP
(
EMPID VARCHAR(10),
EMPNAME VARCHAR(10),
START_DATE DATE,
END_DATE DATE
);
Insert initial values.
INSERT INTO #EMP
SELECT '1001', 'Shivansh', '2015-09-01', '2018-03-31';
INSERT INTO #EMP
SELECT '1004', 'Mayank', '2019-04-01', '2020-06-30';
Create the required table.
CREATE TABLE #NEWEMP
(
EMPID VARCHAR(10),
EMPNAME VARCHAR(10),
START_DATE DATE,
END_DATE DATE
);
Insert the first year date (i.e. if the START_DATE for a tuple is 2015-09-01 insert 2015-09-01 for START_DATE and 2015-12-31 for the END_DATE).
INSERT INTO #NEWEMP
SELECT EMPID, EMPNAME, START_DATE, DATEFROMPARTS(YEAR(START_DATE), 12, 31) FROM #EMP;
Similarly, Insert the last year date.
INSERT INTO #NEWEMP
SELECT EMPID, EMPNAME, DATEFROMPARTS(YEAR(END_DATE), 1, 1), END_DATE FROM #EMP;
Run a while loop till the maximum value of the difference between START_DATE and END_DATE column.
DECLARE #counter INT = 1;
DECLARE #len INT = (SELECT MAX(DATEDIFF(YEAR, START_DATE, END_DATE)) FROM #EMP);
WHILE #counter < #len
BEGIN
INSERT INTO #NEWEMP
SELECT EMPID, EMPNAME, DATEFROMPARTS(YEAR(START_DATE) + #counter, 1, 1), DATEFROMPARTS(YEAR(START_DATE) + #counter, 12, 31) FROM #EMP
WHERE #counter < DATEDIFF(YEAR, START_DATE, END_DATE);
SET #counter += 1;
END
Query the output.
SELECT * FROM #NEWEMP ORDER BY START_DATE;
Here is the Query, written in Azure Synapse Analytics.
The required output is.

SQL Server query for calculating daily census

I'm having a problem with a query that populates the daily census(# of current inpatients) for a hospital unit. This previous post is where I found the query.
SELECT
[date], COUNT (DISTINCT
CASE WHEN admit_date <= [date] AND discharge_date >= [date] THEN id END)) AS census
FROM
dbo.patients, dbo.census
GROUP BY
[date]
ORDER BY
[date]
There are 2 tables:
dbo.patients with id, admit_date, and discharge_date columns
dbo.census has a date column with every date since 2017, and a census column, which is blank
The query populates the census column, but the census count diminishes toward the end of the dates to smaller numbers then it should. For example, there are 65 null values for discharge_date, so there should be a census count of 65 for today's date, but the query produces a count of 8.
Probably need to account for NULL discharge date
SELECT [date], COUNT (DISTINCT
CASE WHEN admit_date <= [date] AND COALESCE(discharge_date, GETDATE()) >= [date] THEN id END))
AS census
FROM dbo.patients
CROSS JOIN dbo.census
GROUP BY [date]
ORDER BY [date]
That is, assuming [date] is some sort of current date/time stamp. Also, as per Sean Lange's comment, if you really want a CROSS JOIN then you should specify that in the query.

order by clause in union statement

I have some records coming from 4 tables and date is not the common field in all of them but I use 'as'. Now I need to order by date
select id,convert(varchar(20), SoldDate,3) as Date from sale
union
select id,convert(varchar(20), PaymentDate,3) as Date from purchase
union
select id,convert(varchar(20), PaymentClearedDate,3) as Date from payments
union
select id,convert(varchar(20), PaymentClearedDate,3) as Date from orders
order by Date desc
I need order by Date
You can use CTE or subquery :
SELECT t.*
FROM ( <Query>
) t
ORDER BY r.Date DESC;
However, i would argue on date conversations, if you want just date then use cast(SoldDate as date) & latter convert it to dd\MM\yy.
So, your updated query would be :
SELECT t.id, CONVERT(VARCHAR(10), t.[Date], 3) AS [Date]
FROM (SELECT id, CAST(SoldDate AS DATE) AS [Date]
FROM sale
UNION
SELECT id, CAST(PaymentDate AS DATE)
FROM purchase
UNION
. . .
) t
ORDER BY t.[Date] DESC;
What goes wrong with your code? You are ordering by Date there as far as I can see. If you want to order by the Dates in date order, create another column which is the date, as a date type, e.g.
select id,convert(varchar(20), SoldDate,3) as Date,SoldDate as d2 from sale
union
select id,convert(varchar(20), PaymentDate,3) as Date, PaymentDate as d2 from purchase
union
select id,convert(varchar(20), PaymentClearedDate,3) as Date, PaymentClearedDate as d2 from payments
union
select id,convert(varchar(20), PaymentClearedDate,3) as Date, PaymentClearedDate as d2 from orders
order by d2 desc
you might need to cast your dates to type date if they are stored in some other text format
e.g.
select id,convert(varchar(20), SoldDate,3) as Date,CAST(SoldDate as datetime2) as d2 from sale
union
...etc
order by d2 desc

How to get row with last day of month in Sql Server query

Given a table with a single row for each day of the month, how can I query it to get the row for the last day of each month?
Try adapting the following query. The SELECT statement within the IN clause choses the dates for the outer query to return.
SELECT *
FROM myTable
WHERE DateColumn IN
(
SELECT MAX(DateColumn)
FROM myTable
GROUP BY YEAR(Datecolumn), MONTH(DateColumn)
)
Try to make use of below query:
DECLARE #Dates Table (ID INT, dt DATE)
INSERT INTO #Dates VALUES
(1,'2017-02-01'),
(2,'2017-02-03'),
(3,'2017-02-04'),
(4,'2017-03-03'),
(5,'2017-04-03'),
(6,'2017-04-04')
SELECT MAX(dt) AS LastDay FROM #Dates GROUP BY DATEPART(MONTH,dt)
OUTPUT
LastDay
2017-02-04
2017-03-03
2017-04-04
OR
SELECT DATEPART(MONTH,dt) AS [MONTH],MAX(DATEPART(DAY,dt)) AS LastDay FROM #Dates GROUP BY DATEPART(MONTH,dt)
MONTH LastDay
2 4
3 3
4 4
You need to select from your table where the YourDateColumn field of the record equals the last date of the month YourDateColumn belongs to:
SELECT CAST(DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0, YourDateColumn )+1,0)) AS DATE)

Select all days of the current week

Good Day! I am working on a chart where I need to display all the days of the current week to show the sales per Week. So far, I am able to display all the days of the current week, I'm just having a trouble in displaying the sales for each day of the week.Since there are no records in the database for the days of the week, it the TOTAL_SALES column should all return a Null value. Instead, it returns the total sales recorded in the database. Here is my Stored Procedure query so far.
WITH DAYSOFTHEWEEK AS
(
SELECT 0 DAY
UNION ALL
SELECT DAY + 1 FROM DAYSOFTHEWEEK WHERE DAY < 6
)
SELECT DATEADD(DAY, DAY, DATEADD(DAY, 2-DATEPART(WEEKDAY, CONVERT (date, GETDATE())), CONVERT (date, GETDATE()))) AS DAY_OF_THE_WEEK,
SUM([ORDER].NET_AMOUNT) AS TOTAL_SALES
FROM DAYSOFTHEWEEK, [ORDER]
GROUP BY DAYSOFTHEWEEK.DAY
I tried adding this condition statement,
WHERE DAYSOFTHEWEEK.DAY IN ([ORDER].ORDER_DATE)
But it returns this error
Operand type clash: date is incompatible with int
Can someone help me out on this?Is there a work around with the code that I already have? Thanks in advance!
What I think you're after is a SUM of each day's sales for the current week with NULL if there are no sales. The secret is to left join your date list onto your data:
-- Setup some fake sales data
WITH TestData(N, Order_Date, Net_Amount) AS (
SELECT 1 N, CAST(GETDATE() AS DATE) Order_Date, RAND() * 100 Net_Amount
UNION ALL
SELECT N+1 N, CAST(GETDATE()-N/5 AS DATE) Order_Date, RAND(CHECKSUM(NEWID())) * 100 Net_Amount FROM TestData
WHERE N < 20
)
SELECT TestData.Order_Date, TestData.Net_Amount INTO #Order FROM TestData
--Set the first day of the week (if required)
SET DATEFIRST 7 --Sunday
;WITH Days(N,DayOfTheWeek) AS (
SELECT 1 N, DATEADD(DAY, 1-DATEPART(WEEKDAY, GETDATE()), CONVERT(DATE,GETDATE())) DayOfTheWeek
UNION ALL
SELECT N+1 N,DATEADD(DAY, 1, DayOfTheWeek) DayOfTheWeek FROM Days
WHERE N < 7
)
SELECT d.DayOfTheWeek, SUM(Net_Amount) TotalAmount
FROM Days d
LEFT JOIN #Order ON d.DayOfTheWeek = Order_Date
GROUP BY d.DayOfTheWeek
DayOfTheWeek TotalAmount
------------ ----------------------
2016-08-07 219.036784917497
2016-08-08 273.319570812461
2016-08-09 271.148114731087
2016-08-10 194.780039228967
2016-08-11 NULL
2016-08-12 NULL
2016-08-13 NULL
Here is every day this week, starting at your datefirst date, which can be temporarily varied for the query with SET DATEFIRST if you need to have some other week start date
I think you have some sales table there that you haven't shown us, you need to join to that on date, then group by
WITH DAYSOFTHEWEEK AS
(
SELECT cast(dateadd(
day,
-datepart(weekday,getdate()) + 1 ,
GETDATE()
)
as date) [DAY], 0 as cnt
UNION ALL
SELECT dateadd(day,1,[DAY]), cnt + 1 FROM DAYSOFTHEWEEK WHERE cnt < 6
)
select DAYSOFTHEWEEK.[day], SUM([ORDER].NET_AMOUNT) AS TOTAL_SALES from daysoftheweek
JOIN
SalesTable on
CAST(SalesTable.SalesDate date) = DAYSOFTHEWEEK.[day]
GROUP BY DAYSOFTHEWEEK.[day]
A little over complicated for me:
To get name of the week use, for example
SELECT DATENAME(dw,getdate())
But you really need something like this:
SELECT ProductName,Sum(Sales) From NameOfTable GROUP BY
DATENAME(ww,salesDate)

Resources