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
Related
My aim is to return the data in a way where the 'name' column has at max 3 distinct values.
My table is like this
id, name, year
1, John, 2012
2, Jake, 2012
3, Jenna, 2013
1, John, 2013
4, Tyler, 2012
5, Jenna, 2013
I need to do distinct on name field in such a way that the output contains atmax 3 unique values in name field. There can be repetitions due to different values in other fields and those can come in the output as well. Example if the set the threshold as 3, then we the output should contain only 3 distinct names with repetition allowed.
Output I need is
id, name, year
1, John, 2012
2, Jake, 2012
3, Jenna, 2013
1, John, 2013
5, Jenna, 2013
How to achieve this kind of result with distinct? Because distinct would identify 3 distinct records if I want all the columns.
Consider below approach
WITH sample_table AS (
SELECT 1 id, 'John' name, 2012 `year` UNION ALL
SELECT 2, 'Jake', 2012 UNION ALL
SELECT 3, 'Jenna', 2013 UNION ALL
SELECT 1, 'John', 2013 UNION ALL
SELECT 4, 'Tyler', 2012 UNION ALL
SELECT 5, 'Jenna', 2013
)
SELECT id, name, `year` FROM (
SELECT *, DENSE_RANK() OVER (ORDER BY name) rank
FROM sample_table
) t WHERE rank <= 3;
Query results
For random sampling, you might consider below.
SELECT id, name, `year` FROM (
SELECT *, DENSE_RANK() OVER (ORDER BY hash) rnk FROM (
SELECT *, SUM(rand()) OVER (PARTITION BY name) hash
FROM sample_table
) t
) t WHERE rnk <= 3;
This will show different result from above.
I have a unique requirement to return number of result rows in multiples of 10. Example, if actual data rows are 3, I must add another 7 blank rows to make it 10. If actual data rows are 16, I must add another 4 blank rows to make it 20, and so on.
Without using a procedure, is it possible to achieve this using SELECT statement?
The blank rows can simply contain NULL values or spaces or zeroes.
You can assume any simple query for data rows; the objective is to understand how to return rows dynamically in multiples of 10.
Example:
Select EmpName FROM Employees
If there are 3 employees, I should still return 10 rows, with the balance 7 rows containing either NULL value or blanks.
I am using SQL Server 2012.
This is very raw idea how it can be achieved:
WITH data(r) AS (
SELECT 1 r FROM dual
UNION ALL
SELECT r+1 r FROM data WHERE r < 10
)
SELECT sd.*
FROM data d
left join some_data sd on d.r = sd.id
This is dual table structure:
create table dual (dummy varchar(1));
insert into dual values ('x');
Fiddle: http://sqlfiddle.com/#!6/5ffcc/4
One of the possible options is this:
WITH data(r) AS (
SELECT 1 r FROM dual
UNION ALL
SELECT r+1 r FROM data WHERE r < 10
)
SELECT sd.*
FROM
(select r, row_number() over (order by r) rn from data) d
left join (
select id, name, row_number() over (order by id) rn from some_data sd
) sd
on d.rn = sd.rn
The obvious disadvantages of this colutions:
'r' value generation rule most probably is not as simple in your
case.
Number of rows must be known before query execution.
But maybe it will help you to find better solution.
Here's another, fairly easy, way to handle it...
IF OBJECT_ID('tempdb..#TestData', 'U') IS NOT NULL
DROP TABLE #TestData;
CREATE TABLE #TestData (
EmpID INT NOT NULL,
EmpName VARCHAR(20) NOT NULL
);
INSERT #TestData(EmpID, EmpName) VALUES
(47, 'Bob'),(33, 'Mary'), (88, 'Sue');
-- data as it exists...
SELECT
td.EmpID,
td.EmpName
FROM
#TestData td;
-- the desired output...
WITH
cte_AddRN AS (
SELECT
td.EmpID,
td.EmpName,
RN = ROW_NUMBER() OVER (ORDER BY td.EmpName)
FROM
#TestData td
),
cte_TenRows AS (
SELECT n.RN FROM ( VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(10) ) n (RN)
)
SELECT
ar.EmpID,
ar.EmpName
FROM
cte_TenRows tr
LEFT JOIN cte_AddRN ar
ON tr.RN = ar.RN
ORDER BY
tr.RN;
Results...
-- data as it exists...
EmpID EmpName
----------- --------------------
47 Bob
33 Mary
88 Sue
-- the desired output...
EmpID EmpName
----------- --------------------
47 Bob
33 Mary
88 Sue
NULL NULL
NULL NULL
NULL NULL
NULL NULL
NULL NULL
NULL NULL
NULL NULL
Based on the above 2 answers, here is what I did:
WITH DATA AS
(SELECT EmpName FROM Employees),
DataSummary AS
(SELECT COUNT(*) AS NumDataRows FROM DATA),
ReqdDataRows AS
(SELECT CEILING(NumDataRows/10.0)*10 AS NumRowsReqd FROM DataSummary),
FillerRows AS
(
SELECT 1 AS SLNO, '00000' AS FillerCol
UNION ALL
SELECT 2 AS SLNO, '00000' AS FillerCol
UNION ALL
SELECT 3 AS SLNO, '00000' AS FillerCol
UNION ALL
SELECT 4 AS SLNO, '00000' AS FillerCol
UNION ALL
SELECT 5 AS SLNO, '00000' AS FillerCol
UNION ALL
SELECT 6 AS SLNO, '00000' AS FillerCol
UNION ALL
SELECT 7 AS SLNO, '00000' AS FillerCol
UNION ALL
SELECT 8 AS SLNO, '00000' AS FillerCol
UNION ALL
SELECT 9 AS SLNO, '00000' AS FillerCol
UNION ALL
SELECT 10 AS SLNO, '00000' AS FillerCol
)
SELECT * FROM DATA
--UNION ALL
--SELECT CONVERT(VARCHAR(10), NumDataRows) FROM DataSummary
--UNION ALL
--SELECT CONVERT(VARCHAR(10), NumRowsReqd) FROM ReqdDataRows
UNION ALL
SELECT FillerCol FROM FillerRows
WHERE (SELECT NumDataRows FROM DataSummary) + SLNO <= (SELECT NumRowsReqd FROM ReqdDataRows)
This gives me the output what I want. This avoids use of ROW_NUMBER and ORDERing. The table FillerRows can be further simplified using SELECT * FROM (VALUES...), and the 2nd and 3rd table DataSummary and ReqdDataRows can be merged into a single SELECT statement.
This approach is a step by step approach and easy to understand and debug, like:
Get the actual data rows
Get count of the data rows
Calculate required no. data rows
UNION the actual data rows with filler rows
Any suggestions on further simplifying this are welcome.
On SQL server 2008 R2, I would like to select one value of a column for each distinct value of another column.
e.g.
name id_num
Tom 53
Tom 60
Tom 27
Jane 16
Jane 16
Bill 97
Bill 83
I need to get one id_num for each distinct name, such as
name id_num
Tom 27
Jane 16
Bill 97
For each name, the id_num can be randomly picked up (not required to be max or min) as long as it is associated with the name.
For example, for Bill, I can pick up 97 or 83. Either one is ok.
I do know how to write the SQL query.
Thanks
SELECT
name,MIN(id_num)
FROM YourTable
GROUP BY name
UPDATE:
If you want pick id_num randomly, you may try this
WITH cte AS (
SELECT
name, id_num,rn = ROW_NUMBER() OVER (PARTITION BY name ORDER BY newid())
FROM YourTable
)
SELECT *
FROM cte
WHERE rn = 1
SQL Fiddle Demo
You could grab the max id like this:
SELECT name, MAX(id_num)
FROM tablename
GROUP BY name
That would get you one id for each distinct name.
select name, max(id_num)
from [mytable]
group by name
The (SELECT 1) in the cte does not really order the data in each of the partitions. which should give you the random selection.
CREATE TABLE #tmp
(
name VARCHAR(10)
, id_num INT
)
INSERT INTO #tmp
SELECT 'Tom', 53 UNION ALL
SELECT 'Tom', 60 UNION ALL
SELECT 'Tom', 27 UNION ALL
SELECT 'Jane', 16 UNION ALL
SELECT 'Jane', 16 UNION ALL
SELECT 'Bill', 97 UNION ALL
SELECT 'Bill', 83
;WITH CTE AS (
SELECT
ROW_NUMBER() OVER (PARTITION BY name ORDER BY (SELECT 1)) AS ID
, name
, id_num
FROM #tmp
)
SELECT *
FROM CTE
WHERE ID = 1
I have some data and want to be able to number each row sequentially, but rows with the same type consecutively, number the same number, and when it's a different type continue numbering.
There will only be types 5 and 6, ID is actually more complex than abc123. I've tried rank but I seem to get two different row counts - in the example instead of 1 2 2 3 4 it would be 1 1 2 2
original image
dense rank result
MS SQL 2008 R2
As far as I understand, you want to number your continous groups
declare #Temp table (id1 bigint identity(1, 1), ID nvarchar(128), Date date, Type int)
insert into #Temp
select 'abc123', '20130101', 5 union all
select 'abc124', '20130102', 6 union all
select 'abc125', '20130103', 6 union all
select 'abc126', '20130104', 5 union all
select 'abc127', '20130105', 6 union all
select 'abc128', '20130106', 6 union all
select 'abc129', '20130107', 6 union all
select 'abc130', '20130108', 6 union all
select 'abc131', '20130109', 5
;with cte1 as (
select
*,
row_number() over (order by T.Date) - row_number() over (order by T.Type, T.Date) as grp
from #Temp as T
), cte2 as (
select *, min(Date) over (partition by grp) as grp2
from cte1
)
select
T.ID, T.Date, T.Type,
dense_rank() over (order by grp2)
from cte2 as T
order by id1
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