个人记录 前端学习数据库

76 阅读5分钟

试了一下把sql翻译为了js的数据流操作,就能理解为什么有些sql是合法的,有些为什么效率低

table inventory_item

idnametypeprice
1red potionconsume60
2sliver swordtool2000
3copper fish rodtool1000
4iron bootequipment200
5pomeloother20
6crystal ballother600
const table = [
    {id: 1, name: 'red potion', type: 'consume', price: 60},
    {id: 2, name: 'sliver sword', type: 'tool', price: 2000},
    {id: 3, name: 'copper fish rod', type: 'tool', price: 1000},
    {id: 4, name: 'iron boot', type: 'equipment', price: 200},
    {id: 5, name: 'pomelo', type: 'other', price: 20},
    {id: 6, name: 'crystal ball', type: 'other', price: 600},
]

下面是一些实现.这是为了理解方便的原因, 可能有些实现很低效

// select * from inventory_item
const res = table


// select count(1) from inventory_item
table.length


// select name from inventory_item
table.map(({name}) => name)


// select name from inventory_item where price >= 200
table.filter(({price}) => price > 200).map(({name}) => name)

/*
    SELECT name, type
    FROM inventory_item
    UNION ALL
    SELECT name, type
    FROM inventory_item2;
*/

table.conact(table2)

/*
    select type,avg(price) as price_avg,count(1) as type_count 
    max(price) as max_pice,min(price) as min_price
    from inventory_item 
    group by type
*/
Object.entries(table.reduce((acc, cur) => {
    if (!Reflect.has(acc, cur.type)) {
        Reflect.set(acc, cur.type, [])
    }

    Reflect.get(acc, cur.type).push(cur)

    return acc
}, {})).map(([gName, gItems]) => ({
    type: gName,
    price_avg: gItems.reduce((a, c) => a + c.price, 0) / gItems.length,
    type_count: gItems.length,
    max_pice: Math.max(...gItems.map(({price}) => price)),
    min_price: Math.min(...gItems.map(({price}) => price)),
}))

// 简化一下上面的代码
Object.entries(groupBy(table, ({type}) => type))
    .map(([gName, gItems]) => ({
        type: gName,
        price_avg: gItems.reduce((a, c) => a + c.price, 0) / gItems.length,
        type_count: gItems.length,
        max_price: Math.max(...gItems.map(({price}) => price)),
        min_price: Math.min(...gItems.map(({price}) => price)),
    }))

function groupBy(table, getGroupName) {
    return table.reduce((acc, cur, i) => {
        const gName = getGroupName(cur, i)

        if (!Reflect.has(acc, gName)) {
            Reflect.set(acc, gName, [])
        }

        Reflect.get(acc, gName).push(cur)

        return acc
    }, {})
}

//  having 对每一个分组内部执行过滤
/*
    select type,avg(price) as avg_p,max(price) as max_p
    from inventory_item
    group by type
    having avg_p > 200;
*/

Object.entries(Object.groupBy(table, ii => ii.type)).map(([gName, g]) => {
    return {
        type: gName,
        avg_p: avgBy(g, ({price}) => price),
        max_p: Math.max(...g.map(({price}) => price))
    }
}).filter(({avg_p}) => avg_p > 200)



//  from 子查询
/*
select concat(type,_,type_of_prod_cnt) as bind_type_count from 
(
    select type ,count(1) as type_of_prod_cnt from inventory_item 
    group by type
) as ii
*/
Object.entries(groupBy(table, ii => ii.type)).map(([gName, g]) => {
    return {
        type: gName,
        type_of_prod_cnt: g.length,
    }
}).map(({type, type_of_prod_cnt}) => {
    return {
        bind_type_count: `${type}_${type_of_prod_cnt}`
    }
})

//  select 子查询
/*
select *,(select avg(price) from inventory_item) as avg_price 
from inventory_item
*/
table.map(row => {
    return Object.assign({}, row, avgBy(table, (item) => item.price))
})

function avgBy(table, evaluateV) {
    return summaryBy(table, evaluateV) / table.length
}

function summaryBy(table, getItemV) {
    return table.reduce((a, c, i) => a + getItemV(c, i), 0)
}

//  where 子查询
/*
select * from inventory_item
 where price >= (select avg(price) from inventory_item)
*/

table.filter((item) => {
    return item.price >= avgBy(table, (item) => item.price)
})


//  关联子查询
/*
select * from inventory_item as iio
where price >= 
(
    select avg(price) as avg_price
    from inventory_item iii 
    where iio.type = iii.type 
    group by type
)
*/

table.filter(iio => {
    return iio.price >=
        avgBy(Object.values(groupBy(table.filter((iii) => iii.type === iio.type), ({type}) => type))[0])
})


// inner join 必须两边都不能为空才有效

const sale_history = [
    {id: 1, item_id: 2, sale_time: '2077-06-07', buyer: "spider man"},
    {id: 2, item_id: 3, sale_time: '2077-02-05', buyer: "hero"},
    {id: 3, item_id: 3, sale_time: '2075-01-02', buyer: "bat man"},
]

/*
select ii.name as name, sh.buyer as buyer,sh.sale_time as sale_time
from  inventory_item as ii
inner join sale_history as sh on ii.id = sh.item_id
*/
// table.filter((ii)=>{
//     ii.id === sh.item_id
// })

table.flatMap((ii) => {
    return sale_history
        .filter((sh) => ii.id === sh.item_id)
        .map(sh => ({
            name: ii.name,
            buyer: sh.buyer,
            sale_time: sh.sale_time,
        }))
})


//  left join   左边表为基准,返回左边表的全部数据,右边字段填充为空
table.flatMap((ii) => {
    return sale_history
        .filter((sh) => ii.id === sh.item_id)
        .map(sh => ({
            name: ii.name,
            buyer: sh.buyer,
            sale_time: sh.sale_time,
        }))
}).concat(
    (() => {
        const ids = Object.keys(groupBy(sale_history, (sh) => sh.item_id)).map(Number)
        return table.filter(ii => !ids.includes(ii.id)).map(ii => ({
            name: ii.name,
            buyer: null,
            sale_time: null,
        }))
    })()
)

//  right join  同上,方向相反

//  full outer join 

//  分页
/*
select * from inventory_item 
limit 20,10

limit start,offset
*/

table.slice(20, 20 + 10)

// table.slice(start,start + offset)

一对多 比如物品表和销售物品销售记录表,每一个销售记录绝对不可能被多个用户共享,所以一个物品对应了多个该物品的销售记录 这里就出现了多张表相关的情况 一个物品记录中有一个买家id,卖家id,物品id 和一些销售记录自己的信息

多对多, 需要使用中间表,防止数据重复并且这样可以优化查询效率,其实放在一张表也是可以的 比如游戏中有物品和角色,一个角色可以有多个物品,但是每一个物品同时也可以有多个角色, 加入a和b角色有sword这个物品,sword应该被a和b同时拥有,因为角色a和角色b的sword就是同一个(假设物品没有随机属性) 在前端中可能更加喜欢json的方式 no sql db 可能就是这样来定义的了

武器表 [{itemName:"sword",itemId:1},{itemName:"shield",itemId:2}]

角色表 [{roleName:"bat man",ownItems:[1,2]},{roleName:"spider man",ownItems:[2]}]

接下来就是事务了,其实可以这么理解,每一个表就是一个全局变量,多线程编程中,如果是全局变量 就要特别的小心,因为我们执行的指令比如 a = a + 1 这个操作需要从内存中先读取a之后+1再写回到内存 汇编代码如下 1 lw x5 , 4(x6) // 假设全局变量a在内存地址 x6 + 4 的位置,把a从内存读取到寄存器x5 2 addi x5 , x5, 1 // 把a的值+1并存放到x5中 3 sw x5 , 4(x6) // 把a从寄存器写回到内存中

这样确实没什么问题,但是问题是我们有多个核心,每一个核心都有自己的寄存器

假设核心二也在操作这个全局变量 a = a + 3 4 lw x5 , 4(x6) // 假设全局变量a在内存地址 x6 + 4 的位置,把a从内存读取到寄存器x5 5 addi x5 , x5, 3 // 把a的值+3并存放到x5中 6 sw x5 , 4(x6) // 把a从寄存器写回到内存中

因为cpu多个核心现在是并行执行的,所以真正的执行顺序可能有多种 例如 1 -> 2 -> 3 -> 4 -> 5 -> 6 但是这是运气好的时候

也可能会有这种情况发生 例如 1 -> 2 -> 4 -> 5 -> 3 -> 6 这就不对了,期望是4,但是实际是3 我们想要的是a = a + n 这个操作是一个原子操作,但是芯片中并不是这样的,所以我们如果能在1,2,3外面加一个锁, 也就是只能有一个核心进来修改数据 ,这样就可以把三个指令变为原子操作了

但是锁也不是万能的,还有死锁的问题 比如说我们需要让多个变量的操作为一个原子操作 伪代码




const lockA = new Mutex()
const lockB = new Mutex()

let a = 0
let b = 2

async function x() {
    lockA.acquire()
    await sleep(1)  //  do some task
    lockB.acquire()

    a = a + b
    b = a + 3

    lockB.release()
    lockA.release()
}

async function y() {
    lockB.acquire()
    lockA.acquire()

    a = a + b
    b = a + 3

    lockA.release()
    lockB.release()
}


class Mutex {
    constructor() {
        this.locked = false;
    }

    acquire() {
        return new Promise((resolve) => {
            const tryAcquire = () => {
                if (!this.locked) {
                    this.locked = true;
                    resolve();
                } else {
                    setTimeout(tryAcquire, 1); // 等待锁释放
                }
            };
            tryAcquire();
        });
    }

    release() {
        this.locked = false;
    }
}



数据库看为是一个全局变量的集合

因为需要上锁

未完待续。。。