setSelection on Spinner based on rowId - database

I have a Spinner View that's populated through a SimpleCursorAdapter.
Based on the selection I need to save the rowid in the entry database (position won't work because things can be added and deleted from the Spinner Database).
This I can do by using spinner.getAdapter().getItemId(pos);. But When I edit an entry I need to make the Spinner position selected that is associated with this rowid (currently).
spinner.setSelection(position); won't work because I have the rowid, I need a way to find the current position of the item in the current spinner based on the rowid in the database.

If you want to set the selection of a Spinner thats backed by a CursorAdapter, you can loop through all the items in the Cursor and look for the one you want (assuming that the primary key in your table is named "_id"):
Spinner spinner = (Spinner) findViewById(R.id.spinner);
spinner.setAdapter(new SimpleCursorAdapter(...));
for (int i = 0; i < spinner.getCount(); i++) {
Cursor value = (Cursor) spinner.getItemAtPosition(i);
long id = value.getLong(value.getColumnIndex("_id"));
if (id == rowid) {
spinner.setSelection(i);
}
}
If you want to get the rowid of a selected item, you can do something similar:
Cursor cursor = (Cursor) spinner.getSelectedItem();
long rowid = cursor.getLong(cursor.getColumnIndex("_id"));
There might be a quicker way to do it, but that's always worked for me.

Had an idea when writing this, made a hashtable with rowid->pos when populating the spinner and then used that. Might help someone if they're searching.

I agree with Erich Douglass's above answer but i found fastest loop syntax which will be useful while spinner.getCount() is greater than 50k to 100k.
/* 1 (fastest) */
for (int i = initializer; i >= 0; i--) { ... }
/* 2 */
int limit = calculateLoopLimit();
for (int i = 0; i < limit; i++) { ... }
/* 3 */
Type[] array = getMyArray();
for (Type obj : array) { ... }
/* 4 */
for (int i = 0; i < array.length; i++) { ... }
/* 5 */
for (int i = 0; i < this.var; i++) { ... }
/* 6 */
for (int i = 0; i < obj.size(); i++) { ... }
/* 7 (slowest) */
Iterable<Type> list = getMyList();
for (Type obj : list) { ... }
So i think we can use here second for better performance:
int spinnerCount = spinner.getCount();
for (int i = 0; i < spinnerCount; i++) {
Cursor value = (Cursor) spinner.getItemAtPosition(i);
long id = value.getLong(value.getColumnIndex("_id"));
if (id == rowid) {
spinner.setSelection(i);
}
}

I think that instead of a for loop is better a while, because when you find your item, can break the loop.
int spinnerCount = spinner.getCount();
int i = 0;
while(i++ < spinnerCount) {
Cursor value = (Cursor) spinner.getItemAtPosition(i);
long id = value.getLong(value.getColumnIndex("_id"));
if (id == rowid) {
spinner.setSelection(i);
break;
}
}

First step, create view for your data set, with joins etc.:
CREATE VIEW my_view AS
SELECT _id, field FROM my_table
Second step:
CREATE VIEW my_view2 AS
SELECT count(*) AS row_id, q1.*
FROM my_view AS q1
LEFT JOIN my_view AS q2
WHERE q1._id >= q2._id
GROUP BY q1._id
Then simply:
SELECT * FROM my_view2
Results:
row_id | _id | field
1 4 XbMCmUBFwb
2 6 Te JMejSaK
3 8 dDGMMiuRuh
4 10 phALAbnq c
5 11 EQQwPKksIj
6 12 PAt tbDnf
7 13 f zUSuhvM
8 14 TIMBgAGhkT
9 15 OOcnjKLLER
To get position by id:
SELECT * FROM my_view2 WHERE _id=11
Results:
row_id | _id | field
5 11 EQQwPKksIj
Hope that help

https://stackoverflow.com/a/5040748/1206052
dziobas
has provided an awesome answer.. But I am not surprised why nobody has recommended it.. just want to make some correction in his answer..
CREATE VIEW my_view2 AS
SELECT count(*) AS row_id, q1.*
FROM my_view AS q1
LEFT JOIN my_view AS q2
ON (q1._id >= q2._id)
GROUP BY q1._id
I just replaced "where" with "on" that is required by join..
now u have all the items with their Positions associated with there ID's..
Just assign the ROW_id to the setselection() of spinner

Why do it the hard way when you can do it the right way?
I refer to the manual:
http://d.android.com/reference/android/widget/AdapterView.OnItemSelectedListener.html#onItemSelected%28android.widget.AdapterView%3C?%3E,%20android.view.View,%20int,%20long%29
example code:
spinner.setOnItemSelectedListener(new OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> parent, View view, int position, long id)
{
int index = spinner.getSelectedItemPosition();
Toast.makeText(getBaseContext(),
"You have selected item : " + index + " which is row " + id,
Toast.LENGTH_SHORT).show();
}
public void onNothingSelected(AdapterView<?> arg0) {}
});
Thanks to evancharlton on #android-dev for this enlightment. :)

Related

What are the options available to get Primary Key Column Names in Snowflake?

I need to fetch all Primary Keys, their parent Table Name , Column Name, and Schema Name together.
I am using INFORMATION_SCHEMA for all metadata fetching, SHOW PRIMARY KEYS/DESCRIBE TABLE does the job but it's not an option here.
Need something similar to SELECT *FROM DB.INFORMATION_SCHEMA.XXX.
What are the options we have here?
*I am Using JDBC
You may consider using: getPrimaryKeys(String, String, String)
Details: https://docs.snowflake.com/en/user-guide/jdbc-api.html#object-databasemetadata
A while back, I wrote a user defined table function (UDTF) to get the PK column(s) for a single table, each column in the PK as a single row in the return. I extended it to return a table with all the columns in PKs in an entire database.
Once you create the UDTF, you can get all the PKs for a database like this:
select * from table(get_pk_columns(get_ddl('database', 'MY_DB_NAME')));
It will return a table with columns for the schema name, table name, and column name(s). Note that if there's a composite PK, it shows in the table as one row per column. You can of course use an aggregate function such as listagg() to change that into a single row with the columns names of the composite PK separated by commas.
It's possible that if you have a very large number of tables/columns in your database, the return of the GET_DDL() function will be too large to fit into the 16mb limit. If it does fit, this should return the results quickly.
/********************************************************************************************************
* *
* User defined table function (UDTF) to get all primary keys for a database. *
* *
* #param {string}: DATABASE_DDL The DDL for the database to get the PKs. Usually use GET_DDL() *
* #return {table}: A table with the columns comprising the table's primary key *
* *
********************************************************************************************************/
create or replace function GET_PK_COLUMNS(DATABASE_DDL string)
returns table ("SCHEMA_NAME" string, "TABLE_NAME" string, PK_COLUMN string)
language javascript
as
$$
{
processRow: function get_params(row, rowWriter, context){
var startTableLine = -1;
var endTableLine = -1;
var dbDDL = row.DATABASE_DDL.replace(/'[\s\S]*'/gm, '')
var lines = dbDDL.split("\n");
var currentSchema = "";
var currentTable = "";
var ln = 0;
var tableDDL = "";
var pkCols = null;
var c = 0;
for (var i=0; i < lines.length; i++) {
if (lines[i].match(/^create .* schema /)) {
currentSchema = lines[i].split("schema")[1].replace(/;/, '');
//rowWriter.writeRow({PK_COLUMN: "currentSchema = " + currentSchema});
}
if (lines[i].match(/^create or replace TABLE /)) {
startTableLine = i;
}
if (startTableLine != -1 && lines[i] == ");") {
endTableLine = i;
}
if (startTableLine != -1 && endTableLine != -1) {
// We found a table. Now, join it and send it for parsing
tableDDL = "";
for (ln = startTableLine; ln <= endTableLine; ln++) {
if (ln > 0) tableDDL += "\n";
tableDDL += lines[ln];
}
startTableLine = -1;
endTableLine = -1;
currentTable = getTableName(tableDDL);
pkCols = getPKs(tableDDL);
for (c = 0; c < pkCols.length; c++) {
rowWriter.writeRow({PK_COLUMN: pkCols[c], SCHEMA_NAME: currentSchema, TABLE_NAME: currentTable});
}
}
}
function getTableName(tableDDL) {
var lines = tableDDL.split("\n");
var s = lines[1];
s = s.substring(s.indexOf(" TABLE ") + " TABLE ".length);
s = s.split(" (")[0];
return s;
}
function getPKs(tableDDL) {
var c;
var keyword = "primary key";
var ins = -1;
var s = tableDDL.split("\n");
for (var i = 0; i < s.length; i++) {
ins = s[i].indexOf(keyword);
if (ins != -1) {
var colList = s[i].substring(ins + keyword.length);
colList = colList.replace("(", "");
colList = colList.replace(")", "");
var colArray = colList.split(",");
for (pkc = 0; c < colArray.length; pkc++) {
colArray[pkc] = colArray[pkc].trim();
}
return colArray;
}
}
return []; // No PK
}
}
}
$$;
I did this using a very simple SQL based UDTF:
CREATE OR REPLACE FUNCTION admin.get_primary_key(p_table_nm VARCHAR)
RETURNS TABLE(column_name VARCHAR, ordinal_position int)
AS
WITH t AS (select get_ddl('TABLE', p_table_nm) tbl_ddl)
, t1 AS (
SELECT POSITION('primary key (', tbl_ddl) + 13 pos
, SUBSTR(tbl_ddl, pos, POSITION(')', tbl_ddl, pos) - pos ) str
FROM t
)
SELECT x.value column_name
, x.index ordinal_position
FROM t1
, LATERAL SPLIT_TO_TABLE(t1.str, ',') x
;
You can then query this in a SQL statement:
select *
FROM TABLE(admin.get_primary_key('<your table name>'));
Unfortunately, due to the odd implementation of GET_DDL(), it will only accept a string literal and you can't use this function with a lateral join to information_schema.tables. Get the following error:
SQL compilation error: Invalid value [CORRELATION(T.TABLE_SCHEMA) ||
'.' || CORRELATION(T.TABLE_NAME)] for function '2', parameter
EXPORT_DDL: constant arguments expected

Kotlin-Exposed : How to select a max timestamp

New to the Jetbrains Exposed Framework here. I have an oracle table with 3 fields in it.
ID
TIMESTAMP
FLAG
1234
May1 12AM
Y
1234
May1 3PM
N
I want to query the max timestamp row where FLAG = Y, but I'm not sure how to do this.
Employee.run {
val results = select {(id eq employeeId) and (flag eq "Y")}
// If no row with Flag = Y then I guess I can just return if results are empty
!results.empty()
}
I'm not sure how to check for a max timestamp in this.
Ended up figuring out a way to do it. I'm sure there's a better way with both Kotlin and Expose.
fun Person.hasFlag(employeeId: String): Boolean {
return transaction(db) {
Employee.run {
val results = slice(id, timeStamp, flag).select {(id eq employeeId)}.groupBy(id, timeStamp, flag)
if (results.empty()) false else results.map {
Pair(it[timeStamp], it[flag])
}.maxByOrNull { m -> m.first }?.second == "Y"
}
}
}

Outer Join in Google Sheets

I have 2 sets of data that I want to do an outer join on (basically include all data from both sets, with empty cells where data is in one set but not the other)
I've looked at
Join tables in google sheet - full join
and
Google Sheets outer join on 2 tables that get summarised
but I just can't get mine to work.
I've simplified my data till I can get this working, but basically both datasets have year and month (which I combine to make an ID, year and month only appears once in each dataset.
My output should look like the Dataset highlighted in green (I typed out manually)
But all I get from my formula is either an Error (Like in the 1st screenshot)
Or a load of #VALUE!
This is a link to my sample sheet.
https://docs.google.com/spreadsheets/d/1Iyhi7WKAA6g0hWpgl33fOe8q78MtzsATq4khnqcAT-w/edit?usp=sharing
It's driving me insane, as I don't fully understand how the array formula is working
Try this:
=arrayformula(
unique(
{
B2:E5,
iferror(
vlookup(
A2:A5,
G2:K5,
column(J2:K5) - column(G2) + 1,
false
)
);
H3:I5,
iferror(
vlookup(
G3:G5,
A3:E5,
column(D3:E5) - column(A3) + 1,
false
)
),
J3:K5
}
)
)
If you need more functionality, the Formulas by Top Contributors add-on includes SQL join functions.
The Google QUERY() function is very powerful and modeled after SQL but it is not a full implementation. So, I decided to write a custom function to simulate SQL JOINs, Inner, Left, Right and Full.
const ss = SpreadsheetApp.getActiveSpreadsheet();
/**
* Combines two ranges with a common key and can be used standalone or with the QUERY() function to simulate joins.
*
*
* #constructor
* #param {(string|array)} range1 - the main table as a named range, a1Notation or an array
* #param {(string|array)} range2 - the related table as a named range, a1Notation or an array
* #param {number} primaryKey - the unique identifier for the main table, columns start with "1"
* #param {number} foreignKey - the key in the related table to join to the main table, columns start with "1"
* #param {string} joinType, type of join - "Inner", "Left", "Right", "Full", optional and defaults to "Inner", case insensitive
* #returns {array} array results as a two dimensional array
* #customfunction
*
* Result Set Example:
*
* =QUERY(denormalize("Employees","Orders",1,3), "SELECT * WHERE Col2 = 'Davolio' AND Col8=2", FALSE)
*
* |EmpID|LastName|FirstName|OrderID|CustomerID|EmpID|OrderDate|ShipperID|
* |:----|:-------|:--------|:------|:---------|:----|:--------|:--------|
* |1 |Davolio |Nancy |10285 |63 |1 |8/20/1996|2 |
* |1 |Davolio |Nancy |10292 |81 |1 |8/28/1996|2 |
* |1 |Davolio |Nancy |10304 |80 |1 |9/12/1996|2 |
* etc.
*
* Other Examples:
* =denormalize("Employees","Orders",1,3)
* =denormalize("Employees","Orders",1,3,"full")
* =QUERY(denormalize("Employees","Orders",1,3,"left"), "SELECT * ", FALSE)
* =QUERY(denormalize("Employees","Orders",1,3), "SELECT * WHERE Col2 = 'Davolio'", FALSE)
* =QUERY(denormalize("Employees","Orders",1,3), "SELECT * WHERE Col2 = 'Davolio' AND Col8=2", FALSE)
* =denormalize("Orders","OrderDetails",1,2)
* // multiple joins
* =denormalize("Employees",denormalize("Orders","OrderDetails",1,2),1,3)
* =QUERY(denormalize("Employees",denormalize("Orders","OrderDetails",1,2),1,3), "SELECT *", FALSE)
* =denormalize(denormalize("Employees","Orders",1,3),"OrderDetails",1,2)
* =QUERY(denormalize("Employees",denormalize("Orders","OrderDetails",1,2),1,3), "SELECT *", FALSE)
* =QUERY(denormalize(denormalize("Employees","Orders",1,3),"OrderDetails",4,2), "SELECT *", FALSE)
*
* Joins Types:
* (INNER) JOIN: Returns records that have matching values in both tables
* LEFT (OUTER) JOIN: Returns all records from the left table, and the matched records from the right table
* RIGHT (OUTER) JOIN: Returns all records from the right table, and the matched records from the left table
* FULL (OUTER) JOIN: Returns all records when there is a match in either left or right table
*
* Note: the most common join is INNER which is why that is the default join type
*
* General:
* This alogithm is more efficient than using nested loops and uses a form of a hash table instead.
* A hash table is a structure that can map index keys to values and typically resembles something like this:
* [index][values]
*
* Since javascript provides a native function to return the index, there is no need to store it so,
* this hash table only stores the values.
*
* There is minimal testing in DENORMALIZE() to validate parameters.
*
* Author/Coder/Tester: John Agusta, 03/28/2021, Raleigh, NC USA
*
* License: Follows the GNU General Public License (GNU GPL or simply GPL), a series of widely-used free
* software licenses that guarantee end users the freedom to run, study, share, and modify the software.
*
* http://www.gnu.org/licenses/gpl.html
*
*
* Note: DENORMALIZE() can simulate multiple joins by nesting DENORMALIZE() functions as needed.
*
* Recursion is theoretically possible to unlimited depth, although only a few levels are normally used in practical programs
* as performance will degrade accordingly.
*
* DENORMALIZE(range1, range2, primaryKey, foreignKey, joinType)
*
*/
function DENORMALIZE(range1, range2, primaryKey, foreignKey, joinType) {
var i = 0;
var j = 0;
var index = -1;
var lFound = false;
var aDenorm = [];
var hashtable = [];
var aRange1 = "";
var aRange2 = "";
joinType = DefaultTo(joinType, "INNER").toUpperCase();
// the 6 lines below are used for debugging
//range1 = "Employees";
//range1 = "Employees!A2:C12";
//range2 = "Orders";
//primaryKey = 1;
//foreignKey = 3;
//joinType = "LEFT";
// Sheets starts numbering columns starting with "1", arrays are zero-based
primaryKey -= 1;
foreignKey -= 1;
// check if range is not an array
if (typeof range1 !== 'object') {
// Determine if range is a1Notation and load data into an array
if (range1.indexOf(":") !== -1) {
aRange1 = ss.getRange(range1).getValues();
} else {
aRange1 = ss.getRangeByName(range1).getValues();
}
} else {
aRange1 = range1;
}
if (typeof range2 !== 'object') {
if (range2.indexOf(":") !== -1) {
aRange2 = ss.getRange(range2).getValues();
} else {
aRange2 = ss.getRangeByName(range2).getValues();
}
} else {
aRange2 = range2;
}
// make similar structured temp arrays with NULL elements
var tArray1 = MakeArray(aRange1[0].length);
var tArray2 = MakeArray(aRange2[0].length);
var lenRange1 = aRange1.length;
var lenRange2 = aRange2.length;
hashtable = getHT(aRange1, lenRange1, primaryKey);
for(i = 0; i < lenRange2; i++) {
index = hashtable.indexOf(aRange2[i][foreignKey]);
if (index !== -1) {
aDenorm.push(aRange1[index].concat(aRange2[i]));
}
}
// add left and full no matches
if (joinType == "LEFT" || joinType == "FULL") {
for(i = 0; i < lenRange1; i++) {
index = aDenorm.indexOf(aRange1[i][primaryKey]);
//index = aScan(aDenorm, aRange1[i][primaryKey], primaryKey)
if (index == -1) {
aDenorm.push(aRange1[i].concat(tArray2));
}
}
}
// add right and full no matches
if (joinType == "RIGHT" || joinType == "FULL") {
for(i = 0; i < lenRange2; i++) {
index = ASCAN(aDenorm, aRange2[i][foreignKey], primaryKey)
if (index == -1) {
aDenorm.push(tArray1.concat(aRange2[i]));
}
}
}
return aDenorm;
}
function getHT(aRange, lenRange, key){
var aHashtable = [];
var i = 0;
for (i=0; i < lenRange; i++ ) {
//aHashtable.push([aRange[i][key], i]);
aHashtable.push(aRange[i][key]);
}
return aHashtable;
}
function MakeArray(length) {
var i = 0;
var retArray = [];
for (i=0; i < length; i++) {
retArray.push("");
}
return retArray;
}
function DefaultTo(valueToCheck, valueToDefault) {
return typeof valueToCheck === "undefined" ? valueToDefault : valueToCheck;
}
/**
*
* Search a multi-dimensional array for a value and return either the index or value if found, -1 or an empty sting otherwise
* #constructor
* #param {array} aValues - the array to scan
* #param {string} searchVal - the value to look for
* #param {number} searchCol - the array column to search
* #param {number} returnCol - optional, the array column to return if specified, otherwise array index is returned
* #returns {(number|value)} array index of value found or array value specified by returnCol
* #customfunction
*/
function ASCAN(aValues, searchVal, searchCol, returnCol) {
var retval = typeof returnCol === "undefined" ? -1 : "";
var i = 0;
var aLen = aValues.length;
for (i = 0; i < aLen; i++) {
if (aValues[i][searchCol] == searchVal) {
retval = typeof returnCol === "undefined" ? i : aValues[i][returnCol];
break;
}
}
return retval;
}
I have a sheet with examples here:
https://script.google.com/home/projects/1aQDY3Y0rOj0VrViLffYfARP9rp2j9jQ0XpUcFvye8XnxvkHy3Qr6_d0_/edit

How to replace nested for loop to Map and any way to reduce the time calculation part?

How to replace the nested for loop with map?
HOW TO REPLACE THE NESTED FOR LOOP BY MAP
public class Casecal {
public void calculatetime(List caseids,Map casemap){
map<string,List<CaseMilestone>> milestonemap = new map<string,List<CaseMilestone>>();
if(casemap!=null && caseids.size()>0){
BusinessHours bh = [SELECT Id FROM BusinessHours WHERE IsDefault=true];
List<Case> caselist = [Select id, (select caseId,id,isCompleted,MilestoneTypeId,BusinessHoursId,MilestoneType.name,CompletionDate from
CaseMilestones where(MilestoneType.name='First Response' or MilestoneType.name='Technical Resolution')
AND caseid IN:casemap.keySet()) from case];
for(Case cs: caselist ){
milestonemap.put(cs.id,cs.Casemilestones);
}
for(Case c: caseids){
if(c.Request_for_Closure_Date__c!=null && milestonemap.containskey(c.id) ){
for(CaseMilestone ml:milestonemap.get(c.id)){
if(ml.MilestoneType.name=='First Response'){
Integer ms = Integer.valueOf((BusinessHours.diff(bh.id, ml.CompletionDate, c.Request_for_Closure_Date__c))/1000);
system.debug('Time#calculation'+ms);
Integer sec = ms;
Integer mns = sec/60;
integer days = mns / 60 / 24 ;
integer hours = (mns - days * 60 * 24) / 60 ;
integer mins = mns - days * 60 * 24 - hours * 60 ;
String timeSpentOnCase = days+'Days '+hours+'Hours '+mins+'Minutes'+sec+'Secs';
System.debug('Time'+timeSpentOnCase);
c.Test_Ignore__c = ml.CompletionDate;
}
else if(ml.MilestoneType.name=='Technical Resolution'){
c.Test_Ignore_2__c = ml.CompletionDate;
}
}
}
}
}
}
}
These loops have already been optimized, but the Map-based data access is not actually necessary because the child objects are queried with the parents.
for(Case cs: caselist ){
milestonemap.put(cs.id,cs.Casemilestones);
}
This is not needed at all and can be removed. Instead, change the inner for loop to refer to the child CaseMilestones list directly.
for(Case c: caseids){
if(c.Request_for_Closure_Date__c!=null){
for(CaseMilestone ml : c.CaseMilestones){
The loop simply won't execute if c.CaseMilestones is empty.
There is no further Map-based optimization for this code.
What David said in above comment, the access is pretty optimised as is.
If you seek further optimisation I'd:
Move if(c.Request_for_Closure_Date__c!=null and improve the WHERE clause (why you want to retrieve some Cases if you skip them in processing?). So something like
SELECT Id,
(SELECT CaseId, Id, isCompleted...
FROM CaseMilestones
WHERE ...)
FROM Case
WHEREId IN :caseids AND Request_for_Closure_Date__c!=null
throw this code away. If it's just for system.debug - you're just wasting the calculation time.
Integer ms = Integer.valueOf((BusinessHours.diff(bh.id, ml.CompletionDate, c.Request_for_Closure_Date__c))/1000);
system.debug('Time#calculation'+ms);
Integer sec = ms;
Integer mns = sec/60;
integer days = mns / 60 / 24 ;
integer hours = (mns - days * 60 * 24) / 60 ;
integer mins = mns - days * 60 * 24 - hours * 60 ;
String timeSpentOnCase = days+'Days '+hours+'Hours '+mins+'Minutes'+sec+'Secs';
System.debug('Time'+timeSpentOnCase);
If after these 2 optimisations you still have performance problems then you might have to rethink your business logic and query. Is it guaranteed that there will be at most 1 milestone of each type on case?
Maybe you need a subquery with just one type that would give you latest milestone? Something like SELECT Id, (SELECT CompletionDate FROM CaseMilestones WHERE Type = 'x' ORDER BY CompletionDate DESC LIMIT 1) FROM Case? Then you run this query for second type and process. Ok, wastes 2 queries but guaranteed to return only few rows.
Or maybe you can simplify this code completely by doing something like
SELECT CaseId, MAX(CompletionDate) d, MilestoneType.Name t
FROM CaseMilestone
WHERE CaseId IN :... AND MilestoneType.Name IN :...
GROUP BY CaseId, MilestoneType.Name

Convert SQL Server varbinary(max) into a set of primary keys of type int

Disclaimer: not my code, not my database design!
I have a column of censusblocks(varbinary(max), null) in a MS SQL Server 2008 db table (call it foo for simplicity).
This column is actually a null or 1 to n long list of int. The ints are actually foreign keys to another table (call it censusblock with a pk id of type of int), numbering from 1 to ~9600000.
I want to query to extract the censusblocks list from foo, and use the extracted list of int from each row to look up the corresponding censusblock row. There's a long, boring rest of the query that will be used from there, but it needs to start with the census blocks pulled from the foo table's censusblocks column.
This conversion-and-look-up is currently handled on the middle tier, with a small .NET utility class to convert from List<int> to byte[] (and vice versa), which is then written into/read from the db as varbinary. I would like to do the same thing, purely in SQL.
The desired query would go something along the lines of
SELECT f.id, c.id
FROM foo f
LEFT OUTER JOIN censusblock c ON
c.id IN f.censusblocks --this is where the magic happens
where f.id in (1,2)
Which would result in:
f.id | c.id
1 8437314
1 8438819
1 8439744
1 8441795
1 8442741
1 8444984
1 8445568
1 8445641
1 8447953
2 5860657
2 5866881
2 5866881
2 5866858
2 5862557
2 5870475
2 5868983
2 5865207
2 5863465
2 5867301
2 5864057
2 5862256
NB: the 7-digit results are coincidental. The range is, as stated above, 1-7 digits.
The actual censusblocks column looks like
SELECT TOP 2 censusblocks FROM foo
which results in
censublocks
0x80BE4280C42380C7C080CFC380D37580DC3880DE8080DEC980E7D1
0x596D3159858159856A59749D59938B598DB7597EF7597829598725597A79597370
For further clarification, here's the guts of the .NET utility classes conversion methods:
public static List<int> getIntegersFromBytes(byte[] data)
{
List<int> values = new List<int>();
if (data != null && data.Length > 2)
{
long ids = data.Length / 3;
byte[] oneId = new byte[4];
oneId[0] = 0;
for (long i = 0; i < ids; i++)
{
oneId[0] = 0;
Array.Copy(data, i * 3, oneId, 1, 3);
if (BitConverter.IsLittleEndian)
{ Array.Reverse(oneId); }
values.Add(BitConverter.ToInt32(oneId, 0));
}}
return values;
}
public static byte[] getBytesFromIntegers(List<int> values)
{
byte[] data = null;
if (values != null && values.Count > 0)
{
data = new byte[values.Count * 3];
int count = 0;
byte[] idBytes = null;
foreach (int id in values)
{
idBytes = BitConverter.GetBytes(id);
if (BitConverter.IsLittleEndian)
{ Array.Reverse(idBytes); }
Array.Copy(idBytes, 1, data, count * 3, 3);
count++;
} }
return data;
}
An example of how this might be done. It is unlikely to scale brilliantly.
If you have a numbers table in your database it should be used in place of nums_cte.
This works by converting the binary value to a literal hex string, then reading it in 8-character chunks
-- create test data
DECLARE #foo TABLE
(id int ,
censusblocks varbinary(max)
)
DECLARE #censusblock TABLE
(id int)
INSERT #censusblock (id)
VALUES(1),(2),(1003),(5030),(5031),(2),(6)
INSERT #foo (id,censusblocks)
VALUES (1,0x0000000100000002000003EB),
(2,0x000013A6000013A7)
--query
DECLARE #biMaxLen bigint
SELECT #biMaxLen = MAX(LEN(CONVERT(varchar(max),censusblocks,2))) FROM #foo
;with nums_cte
AS
(
SELECT TOP (#biMaxLen) ((ROW_NUMBER() OVER (ORDER BY a.type) - 1) * 8) AS n
FROM master..spt_values as a
CROSS JOIN master..spt_values as b
)
,binCTE
AS
(
SELECT d.id, CAST(CONVERT(binary(4),SUBSTRING(s,n + 1,8),2) AS int) as cblock
FROM (SELECT Id, CONVERT(varchar(max),censusblocks,2) AS s FROM #foo) AS d
JOIN nums_cte
ON n < LEN(d.s)
)
SELECT *
FROM binCTE as b
LEFT
JOIN #censusblock c
ON c.id = b.cblock
ORDER BY b.id, b.cblock
You could also consider adding your existing .Net conversion methods into the database as an assembly and accessing them through CLR functions.
This is off-topic, but I couldn't resist writing these conversions so they use IEnumerables instead of arrays and Lists. This might not be faster per se, but is more general and would allow you to perform the conversion without loading the whole array at once, which may be helpful if the arrays you are dealing with are large.
Here it is, for what it's worth:
static IEnumerable<int> BytesToInts(IEnumerable<byte> bytes) {
var buff = new byte[4];
using (var en = bytes.GetEnumerator()) {
while (en.MoveNext()) {
buff[0] = en.Current;
if (en.MoveNext()) {
buff[1] = en.Current;
if (en.MoveNext()) {
buff[2] = en.Current;
if (en.MoveNext()) {
buff[3] = en.Current;
if (BitConverter.IsLittleEndian)
Array.Reverse(buff);
yield return BitConverter.ToInt32(buff, 0);
continue;
}
}
}
throw new ArgumentException("Wrong number of bytes.", "bytes");
}
}
}
static IEnumerable<byte> IntsToBytes(IEnumerable<int> ints) {
if (BitConverter.IsLittleEndian)
return ints.SelectMany(
b => {
var buff = BitConverter.GetBytes(b);
Array.Reverse(buff);
return buff;
}
);
return ints.SelectMany(BitConverter.GetBytes);
}
Your code seems to like encoding an int into 3 bytes instead of 4, which would cause problems with values that don't fit into 3 bytes (including negatives) - is that intentional?
BTW, you should be able to adapt this (or your) code for execution in SQL Server CLR. This is not exactly "in SQL", but is "in DBMS".
you can use Convert(int, censusBlock) to convert the varchar value to int value.
the you can join on that column.
Or have i misunderstood the question?

Resources