I have probably missed something simple with my problem. However its like me to overlook any small details. But I have been searching for a while now and havent come across anything similar to my issue.
Setup:
SQL 2005, Stored Procedure.
I have a table that is updated frequently with call attempts. Using a UNIQUEIDENTIFIER to tie all the records together i.e.
xxx-xxx-xxx-xxx-xxxx | 20/06/2011 12:00 | 10
I want to have a stored procedure that Grabs all the records, and sorts them by the UNIQUEIDENTIFIER and at the same time, producing a counter for the attempts. i.e.
1111-1111-1111-1111 | 20/06/2011 12:05 | 10 | 0
1111-1111-1111-1111 | 20/06/2011 12:06 | 30 | 1
2222-2222-2222-2222 | 20/06/2011 12:10 | 120 | 0
3333-3333-3333-3333 | 20/06/2011 12:20 | 50 | 0
From the above it should be simple to be able to to indentify the call attempts and add on the number. However im probably being very silly.
Any help is appreciated.
Regards
Chris
You can use ROW_NUMBER.
e.g.
SELECT ID, DateField, FieldA,
ROW_NUMBER() OVER(PARTITION BY ID ORDER BY DateField ASC) AS Counter
FROM YourTable
ORDER BY ID, DateField
The PARTITION BY basically resets the counter for each distinct ID and the following ORDER BY ensures the counter is assigned incrementally ordered by the Date field. Note this will be a counter starting from 1 each time. If you want it to start from 0, you can just subtract 1 in the SELECT
Could you not use the "group by" clause (with a count) in SQL ?
You could use "order by" to perform the sort (although its not immediately apparent how SQL implements that or what use it would be).
Also, the question title doesn't seem to match what you're asking.
Related
Supposedly I have 2 tables in the formats below:
Table 1: Visits
Customer | Website_visit_id | Time_of_visit
Table 2: Booking
Customer | Hotel_booking_id | Time_of_booking
I created a table that has customer's id, their booking, and all the visits they made to the website within 30 days prior to making the booking using this:
select customer, booking_id, website_visit_id
from visits a
join booking b
on a.customer = b.customer
and Time_of_visit between dateadd(days, -30, time_of_booking) and time_of_booking
My question is- if I want to expand this time frame to look at how many visits within 60,90,120...365 days prior to making the booking, how do I do that most efficiently (instead of having to run the join 12 times with the dateadd number changed? Is there a way to add a parameter in place of the '30' days in the join statement?
The output can be in the form of a list of booking and their corresponding visits/ timeframe, or a table of bookings and total of visits for each time frame (something that looks like this:
Time_frame| Customer | Hotel_booking_id | Number of visits
T30D | Mike | 1A | 5
T60D | Mike | 1A | 15
T90D | Mike | 1A | 22
T120D | Mike | 1A | 27
Thank you in advance
If you are looking to dynamically build a query, you should look to see if your DB supports dynamic queries/sql. Essentially, instead of running a query, you would run a stored procedure with logic to identify what your query parameters should be. From there, you would be able to dynamically create your query based on your preferred logic and execute it.
I'm not sure which DB you are using however I know that MySQL and Oracle DBs support this.
Please see links below for further information:
Oracle Documentation
MySQL Documentation
I'm trying to create a calendar table for 20 years ranging from 2000 - 2020. The aim is to have one row per day along with some other columns that will use logic based on the calendar date generated. An example of this would be having One column as the calendar date (2000-01-01) and the year column reading the year from the values within the calendar date column (2000).
The code for the table is below:
CREATE TABLE TEST.CALENDAR(
CALENDAR_DATE DATE NOT NULL,
CALENDAR_YEAR INTEGER NOT NULL,
CALENDAR_MONTH_NUMBER INTEGER NOT NULL,
CALENDAR_MONTH_NAME VARCHAR(100),
CALENDAR_DAY_OF_MONTH INTEGER NOT NULL,
CALENDAR_DAY_OF_WEEK INTEGER NOT NULL,
CALENDAR_DAY_NAME VARCHAR(100),
CALENDAR_YEAR_MONTH INTEGER NOT NULL);
At the moment, I have a bunch of insert statements that manually insert rows for this table over 20 years. I'm looking to make an insert statement with variables instead and this insert statement would insert data in daily increments until the start date variable is not less than the end date variable.
Currently, I cannot get this to work at all let alone include any logic for any other columns.
Code for the variable insert statement:
declare startdate DATE, enddate DATEset startdate = '2000-01-01'
set enddate = DATEADD(yy,20,startdate)
while startdate < enddate
begin insert into TEST.CALENDAR (CALENDAR_DATE) select startdate
set startdate = DATEADD(dd,1,startdate) end
Would anyone have any ideas of how I can get this to work?
You can do this with a DB2 recursive query and date functions:
Consider:
with cte (
calendar_date,
calendar_year,
calendar_month_number,
calendar_month_name,
calendar_day_of_month,
calendar_day_of_week,
calendar_day_name
) as (
select
calendar_date,
year(calendar_date),
month(calendar_date),
monthname(calendar_date),
dayofmonth(calendar_date),
dayofweek(calendar_date),
dayname(calendar_date)
from (values(date('2000-01-01'))) as t(calendar_date)
union all
select
calendar_date + 1,
year(calendar_date + 1),
month(calendar_date + 1),
monthname(calendar_date + 1),
dayofmonth(calendar_date + 1),
dayofweek(calendar_date + 1),
dayname(calendar_date + 1)
from cte where calendar_date < date('2021-01-01')
)
select * from cte
Note: it is unclear to me what column CALENDAR_YEAR_MONTH means, so I left it apart.
Demo on DB Fiddle for the first 10 days:
CALENDAR_DATE | CALENDAR_YEAR | CALENDAR_MONTH_NUMBER | CALENDAR_MONTH_NAME | CALENDAR_DAY_OF_MONTH | CALENDAR_DAY_OF_WEEK | CALENDAR_DAY_NAME
------------: | ------------: | --------------------: | ------------------: | --------------------: | -------------------: | ----------------:
2000-01-01 | 2000 | 1 | January | 1 | 7 | Saturday
2000-01-02 | 2000 | 1 | January | 2 | 1 | Sunday
2000-01-03 | 2000 | 1 | January | 3 | 2 | Monday
2000-01-04 | 2000 | 1 | January | 4 | 3 | Tuesday
2000-01-05 | 2000 | 1 | January | 5 | 4 | Wednesday
2000-01-06 | 2000 | 1 | January | 6 | 5 | Thursday
2000-01-07 | 2000 | 1 | January | 7 | 6 | Friday
2000-01-08 | 2000 | 1 | January | 8 | 7 | Saturday
2000-01-09 | 2000 | 1 | January | 9 | 1 | Sunday
2000-01-10 | 2000 | 1 | January | 10 | 2 | Monday
Problem • Relational Knowledge
Currently, I cannot get this to work at all let alone include any logic for any other columns.
Well, there is a reason for that:
Since the Relational Model is founded on First Order Predicate Calculus (aka First Order Logic)
there is nothing in the universe that cannot be defined in terms of the RM, and stored in a Relational database (ie. one that complies with the RM), such that it can be retrieved easily and without limitation (including complex queries and DW).
Since SQL is the data sub-language for the RM, and it is Set-oriented
there is nothing, no code requirement, that cannot be implemented in Set-oriented SQL.
DB2 is a Relational database engine, with genuine Set-oriented processing, using SQL.
It appears that you are not aware of that. Instead, you are attempting to:
define low-level data structures
that you think you need for your programming,
rather than ones that are required within the RM and SQL,
that define the data, as data, and nothing but data.
write code that you need to manipulate those data structures, which is:
(a) procedural (one row at a time; WHILE loops; CURSORS; similar abominations), instead of Set-oriented, and
(b) thus the code is consequently complex, if not impossible.
Not to mention, slow as maple syrup in winter
Rather than using the available blindingly fast, Set-oriented code, which will be simple and straight-forward.
The problem may be a bit tricky, but the tables and the code required are not.
Problem • Platform Knowledge
An example of this would be having One column as the calendar date (2000-01-01) and the year column reading the year from the values within the calendar date column (2000)
That breaks two principles, and results in massive duplication within each row:
Use the correct datatype for the datum, as you have with CALENDAR_DATE. Only.
It is a DATE datatype
Using the built-in DATE datatype and DATE_PART(), DATEADD() functions means that DB2 controls the year; month; day; etc values
and all DATE errors such as 29-Feb-2019 and 31-Sep-2019 are prevented
as opposed to your code, which may have one or more errors.
a column that contains any part of a date must be stored as a DATE datatype (any part of a time as TIME; date and time as DATETIME; etc).
It breaks Codd's First Normal Form (as distinct from the ever-changing insanity purveyed as "1NF" by the pretenders)
.
Each domain [column, datum] must be Atomic wrt the platform
.
DB2 handles DATE and DATE_PART() perfectly, so there is no excuse.
All the following columns are redundant, duplicates of CALENDAR_DATE *in the same row`:
CALENDAR_YEAR
CALENDAR_MONTH_NUMBER
CALENDAR_MONTH_NAME
CALENDAR_DAY_OF_MONTH
CALENDAR_DAY_OF_WEEK
CALENDAR_DAY_NAME
CALENDAR_YEAR_MONTH
Kind of like buying a car (CALENDAR_DATE), putting it drive, and then walking beside it (7 duplicated DATE parts). You need to understand the platform; SQL, and trust it a little.
Not only that, but you will have a lot of fun and no hair left, trying to populate those duplicate columns out without making mistakes.
It needs to be said, you need to know all the date Functions in DB2 reasonably well, in order to use it proficiently.
They are duplicate columns because the values can be derived easily via DATE_PART(), something like:
SELECT DATE,
Year = DATE_PART( 'YEAR', DATE ),
MonthNo = DATE_PART( 'MONTH', DATE ),
MonthName = SUBSTR( ‘January February March April May June July August SeptemberOctober November December ‘,
( DATE_PART( 'MONTH', DATE ) - 1 ) * 9 + 1, 9 ),
DayOfMonth = DATE_PART( 'DAY', DATE ),
DayOfWeek = DATE_PART( 'DOW', DATE ),
DayName = SUBSTR( ‘Sunday Monday Tuesday WednesdayThursday Friday Saturday',
( DATE_PART( 'DOW', DATE ) - 1 ) * 9 + 1, 9 ),
YearMonth = DATE_PART( 'YEAR', DATE ) * 100 + DATE_PART( 'MONTH', DATE )
FROM TEST.CALENDAR
In Sybase, I do not have to use SUBSTR() because I have the MonthName and DayName values in tables, the query is simpler still. Or else use CASE.
Do not prefix the columns in each table with the table name. In SQL, to reference a column in a particular table, in order to resolve ambiguity, use:
.
TableName.ColumnName
.
Same as prefixing the table name with an user name TEST.CALENDAR.
The full specification is as follows, with DB2 supplying the relevant defaults based on the context of the command:
.
[SERVER.][Database.][Owner.][Table.]Column
The reason for this rule is this. Columns in one table may well be related to the same column in another table, and should be named the same. That is the nature of Relational. If you break this rule, it will retard your progressive understanding of the Relational Model, and of SQL, its data sub-language.
Problem • Data Knowledge
The aim is to have one row per day along with some other columns that will use logic based on the calendar date generated.
Why on earth would you do that ?
We store Facts about the universe in a Relational database. Only.
We do not need to store non-facts, such as:
Kyle's name isNOT"Fred"
CustomerCode "IBX" doesNOTexist.
A non-fact is simply the absence of a stored Fact.
If Fred does not exist in the Person table, and you
SELECT ... FROM Person WHERE Name = "Fred"
you will obtain an empty result set.
As it should be.
You are storing the grid that you imagine, consisting of
20 years
* 365 days
* whatever Key is relevant [eg. CustomerCode, etc),
in the form of rows.
That will only keep the database chock-full of empty rows, storing non-facts such as [eg.] CustomerCode XYZ has no event on each date for the next 20 years.
What you imagine, is the result set, or the view, which you may construct in the GUI app. It is not the storage.
Store only Facts, [eg.] actual Events per Customer.
Solution
Now for the solution.
Let me assure you that I have implemented this structure, upon which fairly complex logic has been built, in quite a few databases.
- The problem is, educating the developers in order to get them to write correct SQL code.
- Your situation is that of a developer, trying to not only write non-simple code, but to define the structures upon which it depends.
- Two distinct and different sciences.
Data Model
First, a visual data model, in order to understand the data properly.
All my data models are rendered in IDEF1X, the Standard for modelling Relational databases since 1993
My IDEF1X Introduction is essential reading for beginners.
SQL DDL
Only because you appear to work at that level:
CREATE TABLE TEST.Customer (
CustomerCode CHAR(6) NOT NULL,
Name CHAR(30) NOT NULL,
CONSTRAINT PK
PRIMARY KEY ( CustomerCode )
CONSTRAINT AK1
UNIQUE ( Name )
...
);
CREATE TABLE TEST.Event (
CustomerCode CHAR(6) NOT NULL,
Date DATE NOT NULL,
Event CHAR(30) NOT NULL,
CONSTRAINT pk
PRIMARY KEY ( CustomerCode, Date )
CONSTRAINT Customer_Schedules_Event
FOREIGN KEY ( CustomerCode )
REFERENCES Customer ( CustomerCode )
...
);
INSERT a row only when a Customer reserves a Date
Non-facts are not stored.
SELECT ... WHERE CustomerCode = ... AND Date = ...
will obtain a row if the Customer has booked an Event on that Date
will obtain nothing (empty result set) if the Customer has NOT booked an Event on that Date
If you need to store recurring Events, such as a birthday for the next 20 years, use a Projection in SQL to generate the 20 INSERT commands, which is the Set-oriented method.
If you cannot code a Projection, and only then, write a WHILE loop, which is procedural, one row per execution.
Please feel free to ask questions, the more specific the better.
As you can see, this Question is really about How to set up a Calendar for Events, but I won't change the title until I am sure this answer is what you are seeking. And about modelling data for a Relational database. I will add the tag.
I need to query from my database (postgreSQL) the value average of 1 minute.
The database is recorded in milliseconds and looks like this:
timestamp | value
------------------
1528029265001 | 123
1528029265020 | 232
1528029265025 | 332
1528029265029 | 511
... | ...
1528029265176 | 231
I tried:
SELECT
avg(value),
extract(minutes FROM to_timestamp(timestamp/1000)) as one_min
FROM table GROUP BY one_min
But it seems to be stuck in querying.
I'm sure there is a super easy way to do it.
Any suggestions?
I am guessing that you want:
SELECT floor(timestamp/(60 * 1000)) as timestamp_minute
avg(value)
FROM table
GROUP BY timestamp_minute;
However, if your problem is performance, this will have the same performance issues. For that, you would want a where clause that limits the amount of data being processed.
Because the data is not being collected at even intervals, you might want the simple average of the first and last values, or something like that.
I've seen a lot of questions on SO concerning how to group data by a range in a SQL query.
The exact scenarios vary, but the general underlying problem in each is to group by a range of values rather than each discrete value in the GROUP BY column. In other words, to group by a less precise granularity than you're storing in the database table.
This crops up often in the real world when producing things like histograms, calendar representations, pivot tables and other bespoke reporting outputs.
Some example data (tables unrelated):
| OrderHistory | | Staff |
--------------------------- ------------------------
| Date | Quantity | | Age | Name |
--------------------------- ------------------------
|01-Jul-2012 | 2 | | 19 | Barry |
|02-Jul-2012 | 5 | | 53 | Nigel |
|08-Jul-2012 | 1 | | 29 | Donna |
|10-Jul-2012 | 3 | | 26 | James |
|14-Jul-2012 | 4 | | 44 | Helen |
|17-Jul-2012 | 2 | | 49 | Wendy |
|28-Jul-2012 | 6 | | 62 | Terry |
--------------------------- ------------------------
Now let's say we want to use the Date column of the OrderHistory table to group by weeks, i.e. 7-day ranges. Or perhaps group the Staff into 10-year age ranges:
| Week | QtyCount | | AgeGroup | NameCount |
-------------------------------- -------------------------
|01-Jul to 07-Jul | 7 | | 10-19 | 1 |
|08-Jul to 14-Jul | 8 | | 20-29 | 2 |
|15-Jul to 21-Jul | 2 | | 30-39 | 0 |
|22-Jul to 28-Jul | 6 | | 40-49 | 2 |
-------------------------------- | 50-59 | 1 |
| 60-69 | 1 |
-------------------------
GROUP BY Date and GROUP BY Age on their own won't do it.
The most common answers I see (none of which are consistently voted "correct") are to use one or more of:
a bunch of CASE statements, one per grouping
a bunch of UNION queries, with a different WHERE clause per grouping
as I'm working with SQL Server, PIVOT() and UNPIVOT()
a two-stage query using a sub-select, temp table or View construct
Is there an established generic pattern for dealing with such queries?
You can use some of the dimensional modeling techniques, such as fact tables and dimension tables. Order History can act as a fact table with DateKey foreign key relation to a Date dimension.
Date dimension can have a schema such as below:
Note that Date table is pre-filled with data up-to N number of years.
Using an example above, here is a sample query to get the result:
select CalendarWeek, sum(Quantity)
from OrderHistory a
join DimDate b
on a.DateKey = b.DateKey
group by CalendarWeek
For Staff table, you can store Birthday Key instead of age and let the query calculate the age and ranges.
Here is SQL Fiddle
Date dimension population script was taken from here.
As is often the case this SQL problem requires using more than one pattern in composition.
In this case the two you can use are
NTILE
Numbers Table
You can use NTITLE to create a set number of groups. However since you don't have each member of the groups represented you also need to use a numbers table Since you're using SQL Server you have it easy as you don't have to simulate either.
Here's an example for the Staff problem
WITH g as (
SELECT
NTILE(6) OVER (ORDER BY number) grp,
NUMBER
FROM
master..spt_values
WHERE
TYPE = 'P'
and number >=10 and number <=69
)
SELECT
CAST(min(g.number) as varchar) + ' - ' +
CAST(max(g.number) as varchar) AgeGroup ,
COUNT(s.age) NameCount
FROM
g
LEFT JOIN Staff s
ON g.NUMBER = s.Age
GROUP BY
grp
DEMO
You can apply this to dates as well it just requires some date to day maniplulation
Take a look at the OVER clause and its associated clauses: PARTITION BY, ROW, RANGE...
Determines the partitioning and ordering of a rowset before the
associated window function is applied. That is, the OVER clause
defines a window or user-specified set of rows within a query result
set. A window function then computes a value for each row in the
window. You can use the OVER clause with functions to compute
aggregated values such as moving averages, cumulative aggregates,
running totals, or a top N per group results.
My favorite case in this genre is where transactions must be grouped by fiscal quarter or fiscal year. The fiscal quarter or fiscal year boundaries of various enterprises can border on the bizarre.
My favorite way to implement this is to create a separate table for the attributes of a date. Let's call the table "Almanac". One of the columns in this table is the fiscal quarter, and another one is the fiscal year. The key to this table is of course the date. Ten years worth of data fill up 3,650 rows, plus a few for leap years. You then need a program that can populate this table from scratch. All the enterprise calendar rules are built into this one program.
When you need to group transaction data by fiscal quarter, you just join with this table over date, and then group by fiscal quarter.
I figure this pattern could be extended to groupings by other kinds of ranges, but I've never done it myself.
In your first example your intervals are regular so you can achieve the desired result simply by using functions. Below is an example that gets the data as you require it. The first query keeps the first column in date format (how I would preferably deal with it doing any formatting outside of SQL), the second does the string conversion for you.
DECLARE #OrderHistory TABLE (Date DATE, Quantity INT)
INSERT #OrderHistory VALUES
('20120701', 2), ('20120702', 5), ('20120708', 1), ('20120710', 3),
('20120714', 4), ('20120717', 2), ('20120728', 6)
SET DATEFIRST 7
SELECT DATEADD(DAY, 1 - DATEPART(WEEKDAY, Date), Date) AS WeekStart,
SUM(Quantity) AS Quantity
FROM #OrderHistory
GROUP BY DATEADD(DAY, 1 - DATEPART(WEEKDAY, Date), Date)
SELECT WeekStart,
SUM(Quantity) AS Quantity
FROM #OrderHistory
CROSS APPLY
( SELECT CONVERT(VARCHAR(6), DATEADD(DAY, 1 - DATEPART(WEEKDAY, Date), Date), 6) + ' to ' +
CONVERT(VARCHAR(6), DATEADD(DAY, 7 - DATEPART(WEEKDAY, Date), Date), 6) AS WeekStart
) ws
GROUP BY WeekStart
Something similar can be done for your age grouping using:
SELECT CAST(FLOOR(Age / 10.0) * 10 AS INT)
However this fails for 30-39 because there is no data for this group.
My stance on the matter would be, if you are doing the query as a one off, using a temp table, cte or case statement should work just fine, this should also extend to reusing the same query on small sets of data.
If you are likely to reuse the group however, or you are referring to significant amounts of data then create a permanent table with the ranges defined and indices applied to any columns required. This is the basis of creating dimensions in OLAP.
Couldn't you treat the age (or date) as a foreign key in a new, tiny table that is just ages (or dates) and their corresponding ranges? A join statement could provide a new table with a column that contains AgeGroups. With the new table you could use the standard group-by method.
It does seem reckless to make a new table for grouping, but it would be easy to make programatically and I think it would be easier to maintain (or drop and recreate) than a case statement or a where clause. If the result of this query is a one-off, a throwaway sql statement would probably work best, but I think my method makes the most sense for long-term use.
Well, some years ago with Oracle DB we did it the following way:
We had two tables: Sessions and Ranges. Ranges had foreign key that referenced Session.
When we needed to perform SQL, we created a new record in Sessions and several new records in Ranges that referred to that session.
Our SQL joined Ranges with filter by Session:
select sum(t.Value), r.Name
from DataTable t
join Ranges r on (r.Session = ? and r.Start t.MyDate)
group by r.Name
After we got results we deleted that record from Sessions and records from Ranges where deleted by cascade.
We had daemon job that purged Sessions from junk records that were leaked in case of extraordinary situation (killed processes, etc).
This worked perfectly. Since that time Oracle added new SQL clauses, and maybe they could be used instead. But on other RDBMSes this is still a valid way.
Another approach is to create a number of functions such as GET_YEAR_BY_DATE or GET_QUARTER_BY_DATE or GET_WEEK_BY_DATE (they would return start date of corresponding
period, for example, for any date return start date of year). And then group by them:
select sum(Value), GET_YEAR_BY_DATE(MyDate) from DataTable
group by GET_YEAR_BY_DATE(MyDate)
What is the most efficient way to get a list of all of the unique Mondays from a date field?
When I'm not all that concerned about efficiency, I have done something like:
DATE-weekday(DATE) + 1. But now I need to compute this on a large dataset and I don't want my user wishing for a Rubik's cube because it is taking so long. :)
Yes, the field is indexed.
EDIT:
What I need is a list of all of the weeks that contain records. I am creating a payroll report where the user will select the week to filter the report.
Here is what I came up with:
SELECT DISTINCT ((DATE(`timStart`)-DAYOFWEEK(`timStart`))+2)
FROM `time`
ORDER BY 1 DESC
Anyone have any improvement to suggest?
"unique mondays from a date field" should be as simple as:
SELECT DISTINCT(`date`) FROM `table` WHERE WEEKDAY(`date`)=0
"weeks in which we have date values" should be as simple as:
SELECT DISTINCT(WEEK(`date`)) FROM `table` WHERE YEAR(`date`)=2010;
SELECT WEEK(now()),YEAR(now());
+-------------+-------------+
| WEEK(now()) | YEAR(now()) |
+-------------+-------------+
| 31 | 2010 |
+-------------+-------------+
which will benefit you as well in your other payroll queries, using
WHERE WEEK(`date`)=31
Put your trust in mysql to handle things from there.