Filling In The Blanks On A SQL Query - sql

Sorry about the vague subject but I couldn't think what to put.
Here's my problem, I'm doing a query on a table that returns me a count of items related to a day. I want to make sure that if I do a query on the DB, I always get a set number of rows. For example, imagine I have the following table that contains a log of when people log into a website:
**WebsiteLogin**
id: Integer
login_date: Datetime
I can then get counts of the logins for each date by doing something like:
SELECT DATE(login_date), COUNT(*) FROM WebsiteLogin GROUP BY DATE(login_date)
Which works great and will return me the data I want. But imagine my website was quite unpopular on the weekends. The data returned would look like:
2008-12-10, 100
2008-12-11, 124
2008-12-12, 151
2008-12-15, 141
2008-12-16, 111
The 13th & 14th are missing because there was no data for those dates. Is there any way I can change my query so that I get data that includes all the dates I query on. E.g.
2008-12-10, 100
2008-12-11, 124
2008-12-12, 151
2008-12-13, 0
2008-12-14, 0
2008-12-15, 141
2008-12-16, 111
I imagine I could do this if I set up a table containing all the dates in a year and then using a left/right join but that's really messy way of doing it.
So any clues on a nice way to do this in SQL? Or is programmatically my only choice? Cheers for any input.

To do this you would need to write a stored procedure that returns a table result.
It would use a loop that would step thru each day and get the count and store it in a row of a temp table, then return that table as the resultset.
Here is a MS SQL server example of a loop:
http://www.databasejournal.com/features/mssql/article.php/3100621/T-SQL-Programming-Part-2---Building-a-T-SQL-Loop.htm

I imagine I could do this if I set up a table containing all the dates in a year and then using a left/right join but that's really messy way of doing it.
Nope. That's pretty much how to do it. On the other hand, you can use a temporary table and populate it with just the date range required.
If only MS SQL had virtual tables, where you provided a generator function...

You shouldn't need to create a temporary table, or similar, you just need a source with enough rows to construct the missing dates:
I don't know mysql, but if it supports "connect by" then you could do the following:
(this is in oracle)
select d login_date, count(login_date) count
from
websitelogin wsl
right outer join (
select start_date+l-1 d from (select start_date, level l
from (select min(login_date) start_date, max(login_date)-min(login_date)+1 num_days
from websitelogin) connect by level <= num_days)) v on d=login_date
group by d
/
if mysql doesn't have connect by you could just join on some arbitrary table with enough rows in it instead and limit the result to the number of required rows:
select d login_date, count(login_date) count
from
websitelogin wsl
right outer join (select start_date+rownum-1 d from
(
select
min(login_date) start_date,
max(login_date)-min(login_date)+1 num_days
from websitelogin)v,all_objects
where rownum<=num_days
) v on d=login_date
group by d
not quite as neat though, and obviously you need to know that the driving table has enough rows in it.

I know it isn't mysql, but I use the following function in MSSQL (see below for MySql version):
CREATE FUNCTION dbo.DatesBetween (#start_date datetime, #end_date datetime)
RETURNS #DateTable TABLE (gen_date datetime)
AS
BEGIN
DECLARE #num_dates int
DECLARE #tmpVal TABLE (a_count int identity(0,1))
SELECT #num_dates = datediff(day, #start_date, #end_date)
WHILE (select isnull(max(a_count), 0) from #tmpVal) < #num_dates
INSERT #tmpVal DEFAULT VALUES
INSERT #DateTable (gen_date)
SELECT dateadd(day, a_count, #start_date) FROM #tmpVal
RETURN
END
So, to use it in your example, I would try something like:
DECLARE #min_date datetime, #max_date datetime
SELECT #min_date = min(login_date), #max_date = max(login_date)
FROM WebsiteLogin
SELECT m.gen_date 'login_date', isnull(l.num_visits, 0) 'num_visits'
FROM dbo.DatesBetween(#min_date, #max_date) as d
LEFT OUTER JOIN (SELECT DATE(login_date) 'login_date', COUNT(*) 'num_visits'
FROM WebsiteLogin
GROUP BY DATE(login_date)) AS l ON d.gen_date = l.login_date
Alternatively, and with a massive speed improvement on my query, you could investigate this blog entry, which does what my code above does, but will work across all versions of SQL.
He explains it more there, but the SQL is:
DECLARE #LowDate DATETIME
SET #LowDate = '01-01-2006'
DECLARE #HighDate DATETIME
SET #HighDate = '12-31-2016'
SELECT DISTINCT DATEADD(dd, Days.Row, DATEADD(mm, Months.Row, DATEADD(yy, Years.Row, #LowDate))) AS Date
FROM
(SELECT 0 AS Row UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4
UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9
UNION ALL SELECT 10 UNION ALL SELECT 11 UNION ALL SELECT 12 UNION ALL SELECT 13 UNION ALL SELECT 14
UNION ALL SELECT 15 UNION ALL SELECT 16 UNION ALL SELECT 17 UNION ALL SELECT 18 UNION ALL SELECT 19
UNION ALL SELECT 20 UNION ALL SELECT 21 UNION ALL SELECT 22 UNION ALL SELECT 23 UNION ALL SELECT 24
UNION ALL SELECT 25 UNION ALL SELECT 26 UNION ALL SELECT 27 UNION ALL SELECT 28 UNION ALL SELECT 29
UNION ALL SELECT 30 -- add more years here...
) AS Years
INNER JOIN
(SELECT 0 AS Row UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4
UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9
UNION ALL SELECT 10 UNION ALL SELECT 11
) AS Months
ON DATEADD(mm, Months.Row, DATEADD(yy, Years.Row, #LowDate)) <= #HighDate
INNER JOIN
(SELECT 0 AS Row UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4
UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9
UNION ALL SELECT 10 UNION ALL SELECT 11 UNION ALL SELECT 12 UNION ALL SELECT 13 UNION ALL SELECT 14
UNION ALL SELECT 15 UNION ALL SELECT 16 UNION ALL SELECT 17 UNION ALL SELECT 18 UNION ALL SELECT 19
UNION ALL SELECT 20 UNION ALL SELECT 21 UNION ALL SELECT 22 UNION ALL SELECT 23 UNION ALL SELECT 24
UNION ALL SELECT 25 UNION ALL SELECT 26 UNION ALL SELECT 27 UNION ALL SELECT 28 UNION ALL SELECT 29
UNION ALL SELECT 30
) AS Days
ON DATEADD(dd, Days.Row, DATEADD(mm, Months.Row, DATEADD(yy, Years.Row, #LowDate))) <= #HighDate
WHERE DATEADD(yy, Years.Row, #LowDate) <= #HighDate
ORDER BY 1

Related

replacement of Offset Limit in SQL Server

We have DataTemp table which has the records in desc order.
select * from (
select 9,'a',3 union
select 8,'a',2 union
select 7,'b',3 union
select 6,'a',1 union
select 5,'b',2 union
select 4,'c',3 union
select 3,'c',2 union
select 2,'b',1 union
select 1,'c',1
) door (sno,id, N_th_Reocord)
sno - Auto Id.
id - code of the Doors*.
N_th_Record - for denoting the n the record.
At a time, only three* records per Door are need to store on this table. For example Door 'a' has new entry(means 4th record) then first of 'a' Door need to delete.
4th record:
select * from (
select 10,'a',4 union --- new entry
select 9,'a',3 union
select 8,'a',2 union
select 7,'b',3 union
select 6,'a',1 union -- need to delete
select 5,'b',2 union
select 4,'c',3 union
select 3,'c',2 union
select 2,'b',1 union
select 1,'c',1
) door (sno,id, N_th_Reocord)
I do following query. But I need easiest way for deleting the row. Because, we are try to reduce the time consumption of over all project.
delete from door where sno = (
select sno from (
select 10,'a',4 union
select 9,'a',3 union
select 8,'a',2 union
select 7,'b',3 union
select 6,'a',1 union
select 5,'b',2 union
select 4,'c',3 union
select 3,'c',2 union
select 2,'b',1 union
select 1,'c',1
) door (sno,id, N_th_Reocord)
where id = 'a'
order by sno desc -- For 'DataTemp' *order by* is no needed.
offset 3 rows fetch next 1 rows only
)
Note:
Three rows and three Door are given for example. Actually we work with 144 rows per 12 Doors.
Before this delete, we check lot of Business rules.
Version: SQL Server 2012
You could use ROW_NUMBER:
WITH cte AS (SELECT *,ROW_NUMBER() OVER(PARTITION BY id ORDER BY sno DESC) rn FROM t)
DELETE FROM cte WHERE rn > 3;
db<>fiddle demo

Looping through specific values on a table and insert new rows

I have the following table:
ID, UserID, CompanyID, AccountID, Year1, Month1
I need to insert 10 rows to each AccountID, is there a way to loop through all AccountIDs and to insert for each one of them the following values?
INSERT INTO Perms (UserID, CompanyID, AccountID, Year1, Month1)
VALUES
(175, 74,x,2017,3),
(175, 74,x,2017,4),
(175, 74,x,2017,5),
(175, 74,x,2017,6),
(175, 74,x,2017,7),
(175, 74,x,2017,8),
(175, 74,x,2017,9),
(175, 74,x,2017,10),
(175, 74,x,2017,11),
(175, 74,x,2017,12)
I have about 100 AccountIDs and I need some sort of a loop.
Is that doable?
Use CTEs to represent the account and date sequences. In the case of the account ID values, we can use a recursive CTE. Below I arbitrarily generate values from 1 to 100, though this approach should work with any continuous range. For the year/month combinations, because there are only 10 we can simply hard code them in a CTE. Then, use INSERT INTO ... SELECT with a cross join of the two CTEs.
WITH accounts AS (
SELECT 1 AS account
UNION ALL
SELECT account + 1
FROM accounts
WHERE account + 1 <= 100
),
cte AS (
SELECT 2017 AS year, 3 AS month UNION ALL
SELECT 2017, 4 UNION ALL
SELECT 2017, 5 UNION ALL
SELECT 2017, 6 UNION ALL
SELECT 2017, 7 UNION ALL
SELECT 2017, 8 UNION ALL
SELECT 2017, 9 UNION ALL
SELECT 2017, 10 UNION ALL
SELECT 2017, 11 UNION ALL
SELECT 2017, 12
)
INSERT INTO Perms (UserID, CompanyID, AccountID, Year1, Month1)
SELECT 175, 74, account, year, month
FROM accounts
CROSS JOIN cte;
OPTION (MAXRECURSION 255);
Edit:
If your account IDs are not continuous, then continuing with this answer you may just manually list them in a CTE, e.g.
WITH accounts AS (
SELECT 71 AS account UNION ALL
SELECT 74 UNION ALL
SELECT 78 UNION ALL
SELECT 112 UNION ALL
SELECT 119
-- and others
)
Try this. This is very similair to already existing answer, but more compact:
;with cte as (
select 175 [UserID], 74 [CompanyID], 2017 [Year1], 3 [Month1]
union all
select 175 [UserID], 74 [CompanyID], 2017 [Year1], [Month1] + 1 from cte
where [Month1] < 12
)
select A.[UserID], A.[CompanyID], B.[AccountID], A.[Year1], A.[Month1] from cte A cross join TABLE_NAME B
If you have the accountId's stored in a table and what you want is to insert 10 rows for each account id with Month1 from 3 to 12, try this
WITH CTE
AS
(
SELECT
Month2 = 1
UNION ALL
SELECT
Month2+1
FROM CTE
WHERE Month2 <12
)
INSERT INTO Perms (UserID, CompanyID, AccountID, Year1, Month1)
SELECT
UserID = 175,
CompanyID ='X',
AccountID = YAT.AccountID,
Year1 = 2017,
Month1 = CTE.Month2
FROM CTE
INNER JOIN YourAccountTable YAT
ON CTE.Month2 BETWEEN 3 AND 12
Change the between clause if you want diffrent values

Using a SQL query, how can I select every date within a range?

I'm querying a support ticket database, and each ticket has a column for "date opened" and "date closed." Tickets frequently remain open for multiple days, so we need to be able to pull the number of tickets that are OPEN on each day.
For example, for 4/8/14, we need to know how many tickets were opened on 4/8, combined with the total number of unclosed tickets that were opened prior to 4/8 but remained still open at 12:00am on 4/8 (may or may not have been closed during or after 4/8).
This seems straightforward enough for a single date, but now I need to write a query that will pull a complete range of dates.
For example, we need to write a query that returns every date between 1/1/14 and 4/10/14 along with the total number of tickets open on each date (including dates on which 0 tickets were open).
Is this possible using only queries or subqueries, without using any stored procedures or temporary datatables?
We're currently pulling the data into Excel and calculating the date stats there, but Excel is not a scalable solution and we'd like to have SQL perform this work so we can migrate this report to SSRS (SQL Server Reporting Services) down the road.
Your question is not very clear to me, but with SQLServer 2005 or better (SQLServer 2008 or better for the type Date) you can create a calendar. This one the way to do it
WITH [counter](N) AS
(SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1)
, days(N) AS (SELECT row_number() over (ORDER BY (SELECT NULL)) FROM [counter])
, months (N) AS (SELECT N - 1 FROM days WHERE N < 13)
SELECT DISTINCT CAST(DATEADD(DAY, days.n,
DATEADD(MONTH, months.n, '20131231')
) AS date)
FROM months
CROSS JOIN days
ORDER BY 1
if you need more year just add a new cte accordingly
SQLFiddle
You can't do it, without having some sort of date table that contains a row for each date possible. Creating a date table is pretty easy, depending on your RDBMS and your requirements.

Simplest way to process multiple hardcoded values in SQL?

How would I do this in SQL Server? (I know it won't run as written but it illustrates the question better than I can explain)
SELECT SQRT(number) WHERE number IN (4,9,16,25)
It would return multiple rows of course
you can use table value constructor
select sqrt(number)
from (
values (4),(9),(16),(25)
) as T(number)
or use union all
select sqrt(number)
from (
select 4 union all
select 9 union all
select 16 union all
select 25
) as T(number)
sql fiddle demo
You could create a derived table:
SELECT SQRT(number)
FROM (
SELECT 4 AS number
UNION ALL SELECT 9
UNION ALL SELECT 16
UNION ALL SELECT 25
) A

how to count each characters in a string and display each instance in a table format

my table master_schedule
cable_no
D110772
D110773
D110774
D110775
D110776
D110777
D110778
D110779
D110880
I would like to create a loop so that each character in the string counted and displayed
D 9
1 18
2 1
3 1
AND SO ON .......
how can i modify in these sql query mentioned below:
select (sum(LEN(cable_no) - LEN(REPLACE(cable_no, 'D', '')))*2) as FERRUL_qtyx2
from MASTER_schedule
Something like this:
select substring(cable_no, n.n, 1) as letter, count(*) as cnt
from FERRUL_qtyx2 t cross join
(select 1 as n union all select 2 union all select 3 union all select 4 union all
select 5 union all select 6 union all select 7
) n
group by substring(cable_no, n.n, 1);
This creates a sequence of numbers n up to the length of the string. It then uses cross join and substring() to extract the nth character of each cable_no.
In general, this will be faster than doing a union all seven times. The union all approach will typically scan the table 7 times. This will scan the table only once.
you can use recursive common table expression:
with cte(symbol, cable_no) as (
select
left(cable_no, 1), right(cable_no, len(cable_no) - 1)
from Table1
union all
select
left(cable_no, 1), right(cable_no, len(cable_no) - 1)
from cte
where cable_no <> ''
)
select symbol, count(*)
from cte
group by symbol
=> sql fiddle demo
Another approach (made after Gordon Linoff solution):
;with cte(n) as (
select 1
union all
select n + 1 from cte where n < 7
)
select substring(t.cable_no, n.n, 1) as letter, count(*) as cnt
from #Table1 as t
cross join cte as n
group by substring(t.cable_no, n.n, 1);
=> sql fiddle demo